/src/postgres/src/backend/commands/lockcmds.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * lockcmds.c |
4 | | * LOCK command support code |
5 | | * |
6 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * |
10 | | * IDENTIFICATION |
11 | | * src/backend/commands/lockcmds.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | #include "postgres.h" |
16 | | |
17 | | #include "access/table.h" |
18 | | #include "access/xact.h" |
19 | | #include "catalog/namespace.h" |
20 | | #include "catalog/pg_inherits.h" |
21 | | #include "commands/lockcmds.h" |
22 | | #include "miscadmin.h" |
23 | | #include "nodes/nodeFuncs.h" |
24 | | #include "rewrite/rewriteHandler.h" |
25 | | #include "storage/lmgr.h" |
26 | | #include "utils/acl.h" |
27 | | #include "utils/lsyscache.h" |
28 | | #include "utils/syscache.h" |
29 | | |
30 | | static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait); |
31 | | static AclResult LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid); |
32 | | static void RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, |
33 | | Oid oldrelid, void *arg); |
34 | | static void LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, |
35 | | List *ancestor_views); |
36 | | |
37 | | /* |
38 | | * LOCK TABLE |
39 | | */ |
40 | | void |
41 | | LockTableCommand(LockStmt *lockstmt) |
42 | 0 | { |
43 | 0 | ListCell *p; |
44 | | |
45 | | /* |
46 | | * Iterate over the list and process the named relations one at a time |
47 | | */ |
48 | 0 | foreach(p, lockstmt->relations) |
49 | 0 | { |
50 | 0 | RangeVar *rv = (RangeVar *) lfirst(p); |
51 | 0 | bool recurse = rv->inh; |
52 | 0 | Oid reloid; |
53 | |
|
54 | 0 | reloid = RangeVarGetRelidExtended(rv, lockstmt->mode, |
55 | 0 | lockstmt->nowait ? RVR_NOWAIT : 0, |
56 | 0 | RangeVarCallbackForLockTable, |
57 | 0 | &lockstmt->mode); |
58 | |
|
59 | 0 | if (get_rel_relkind(reloid) == RELKIND_VIEW) |
60 | 0 | LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL); |
61 | 0 | else if (recurse) |
62 | 0 | LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait); |
63 | 0 | } |
64 | 0 | } |
65 | | |
66 | | /* |
67 | | * Before acquiring a table lock on the named table, check whether we have |
68 | | * permission to do so. |
69 | | */ |
70 | | static void |
71 | | RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid, |
72 | | void *arg) |
73 | 0 | { |
74 | 0 | LOCKMODE lockmode = *(LOCKMODE *) arg; |
75 | 0 | char relkind; |
76 | 0 | char relpersistence; |
77 | 0 | AclResult aclresult; |
78 | |
|
79 | 0 | if (!OidIsValid(relid)) |
80 | 0 | return; /* doesn't exist, so no permissions check */ |
81 | 0 | relkind = get_rel_relkind(relid); |
82 | 0 | if (!relkind) |
83 | 0 | return; /* woops, concurrently dropped; no permissions |
84 | | * check */ |
85 | | |
86 | | /* |
87 | | * Note: Conflict log tables are deliberately NOT blocked here, even |
88 | | * though other direct DDL on them is rejected elsewhere. pg_dump relies |
89 | | * on being able to take an ACCESS SHARE lock on these tables to safely |
90 | | * dump their definitions during a binary upgrade, so we permit LOCK |
91 | | * commands on them and treat them like ordinary tables here. It's true |
92 | | * that a strong lock (ShareLock or above) on such a table would conflict |
93 | | * with the RowExclusiveLock taken by the apply worker's inserts and could |
94 | | * stall conflict logging as well as the apply worker for as long as it is |
95 | | * held. But locking a system-managed conflict log table is an unusual |
96 | | * thing to do, and it doesn't seem worth the trouble of filtering by lock |
97 | | * mode here. |
98 | | */ |
99 | 0 | if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE && |
100 | 0 | relkind != RELKIND_VIEW) |
101 | 0 | ereport(ERROR, |
102 | 0 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
103 | 0 | errmsg("cannot lock relation \"%s\"", |
104 | 0 | rv->relname), |
105 | 0 | errdetail_relkind_not_supported(relkind))); |
106 | | |
107 | | /* |
108 | | * Make note if a temporary relation has been accessed in this |
109 | | * transaction. |
110 | | */ |
111 | 0 | relpersistence = get_rel_persistence(relid); |
112 | 0 | if (relpersistence == RELPERSISTENCE_TEMP) |
113 | 0 | MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE; |
114 | | |
115 | | /* Check permissions. */ |
116 | 0 | aclresult = LockTableAclCheck(relid, lockmode, GetUserId()); |
117 | 0 | if (aclresult != ACLCHECK_OK) |
118 | 0 | aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname); |
119 | 0 | } |
120 | | |
121 | | /* |
122 | | * Apply LOCK TABLE recursively over an inheritance tree |
123 | | * |
124 | | * This doesn't check permission to perform LOCK TABLE on the child tables, |
125 | | * because getting here means that the user has permission to lock the |
126 | | * parent which is enough. |
127 | | */ |
128 | | static void |
129 | | LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait) |
130 | 0 | { |
131 | 0 | List *children; |
132 | 0 | ListCell *lc; |
133 | |
|
134 | 0 | children = find_all_inheritors(reloid, NoLock, NULL); |
135 | |
|
136 | 0 | foreach(lc, children) |
137 | 0 | { |
138 | 0 | Oid childreloid = lfirst_oid(lc); |
139 | | |
140 | | /* Parent already locked. */ |
141 | 0 | if (childreloid == reloid) |
142 | 0 | continue; |
143 | | |
144 | 0 | if (!nowait) |
145 | 0 | LockRelationOid(childreloid, lockmode); |
146 | 0 | else if (!ConditionalLockRelationOid(childreloid, lockmode)) |
147 | 0 | { |
148 | | /* try to throw error by name; relation could be deleted... */ |
149 | 0 | char *relname = get_rel_name(childreloid); |
150 | |
|
151 | 0 | if (!relname) |
152 | 0 | continue; /* child concurrently dropped, just skip it */ |
153 | 0 | ereport(ERROR, |
154 | 0 | (errcode(ERRCODE_LOCK_NOT_AVAILABLE), |
155 | 0 | errmsg("could not obtain lock on relation \"%s\"", |
156 | 0 | relname))); |
157 | 0 | } |
158 | | |
159 | | /* |
160 | | * Even if we got the lock, child might have been concurrently |
161 | | * dropped. If so, we can skip it. |
162 | | */ |
163 | 0 | if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid))) |
164 | 0 | { |
165 | | /* Release useless lock */ |
166 | 0 | UnlockRelationOid(childreloid, lockmode); |
167 | 0 | continue; |
168 | 0 | } |
169 | 0 | } |
170 | 0 | } |
171 | | |
172 | | /* |
173 | | * Apply LOCK TABLE recursively over a view |
174 | | * |
175 | | * All tables and views appearing in the view definition query are locked |
176 | | * recursively with the same lock mode. |
177 | | */ |
178 | | |
179 | | typedef struct |
180 | | { |
181 | | LOCKMODE lockmode; /* lock mode to use */ |
182 | | bool nowait; /* no wait mode */ |
183 | | Oid check_as_user; /* user for checking the privilege */ |
184 | | Oid viewoid; /* OID of the view to be locked */ |
185 | | List *ancestor_views; /* OIDs of ancestor views */ |
186 | | } LockViewRecurse_context; |
187 | | |
188 | | static bool |
189 | | LockViewRecurse_walker(Node *node, LockViewRecurse_context *context) |
190 | 0 | { |
191 | 0 | if (node == NULL) |
192 | 0 | return false; |
193 | | |
194 | 0 | if (IsA(node, Query)) |
195 | 0 | { |
196 | 0 | Query *query = (Query *) node; |
197 | 0 | ListCell *rtable; |
198 | |
|
199 | 0 | foreach(rtable, query->rtable) |
200 | 0 | { |
201 | 0 | RangeTblEntry *rte = lfirst(rtable); |
202 | 0 | AclResult aclresult; |
203 | |
|
204 | 0 | Oid relid = rte->relid; |
205 | 0 | char relkind = rte->relkind; |
206 | 0 | char *relname = get_rel_name(relid); |
207 | | |
208 | | /* Currently, we only allow plain tables or views to be locked. */ |
209 | 0 | if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE && |
210 | 0 | relkind != RELKIND_VIEW) |
211 | 0 | continue; |
212 | | |
213 | | /* |
214 | | * Conflict log tables only support SELECT, DELETE, and TRUNCATE. |
215 | | * A direct LOCK on them is permitted solely so that pg_dump can |
216 | | * lock them during a binary upgrade; locking one indirectly by |
217 | | * locking a view over it is not needed for that, so skip it here |
218 | | * rather than locking it. |
219 | | */ |
220 | 0 | if (IsConflictLogTableNamespace(get_rel_namespace(relid))) |
221 | 0 | continue; |
222 | | |
223 | | /* |
224 | | * We might be dealing with a self-referential view. If so, we |
225 | | * can just stop recursing, since we already locked it. |
226 | | */ |
227 | 0 | if (list_member_oid(context->ancestor_views, relid)) |
228 | 0 | continue; |
229 | | |
230 | | /* |
231 | | * Check permissions as the specified user. This will either be |
232 | | * the view owner or the current user. |
233 | | */ |
234 | 0 | aclresult = LockTableAclCheck(relid, context->lockmode, |
235 | 0 | context->check_as_user); |
236 | 0 | if (aclresult != ACLCHECK_OK) |
237 | 0 | aclcheck_error(aclresult, get_relkind_objtype(relkind), relname); |
238 | | |
239 | | /* We have enough rights to lock the relation; do so. */ |
240 | 0 | if (!context->nowait) |
241 | 0 | LockRelationOid(relid, context->lockmode); |
242 | 0 | else if (!ConditionalLockRelationOid(relid, context->lockmode)) |
243 | 0 | ereport(ERROR, |
244 | 0 | (errcode(ERRCODE_LOCK_NOT_AVAILABLE), |
245 | 0 | errmsg("could not obtain lock on relation \"%s\"", |
246 | 0 | relname))); |
247 | | |
248 | 0 | if (relkind == RELKIND_VIEW) |
249 | 0 | LockViewRecurse(relid, context->lockmode, context->nowait, |
250 | 0 | context->ancestor_views); |
251 | 0 | else if (rte->inh) |
252 | 0 | LockTableRecurse(relid, context->lockmode, context->nowait); |
253 | 0 | } |
254 | | |
255 | 0 | return query_tree_walker(query, |
256 | 0 | LockViewRecurse_walker, |
257 | 0 | context, |
258 | 0 | QTW_IGNORE_JOINALIASES); |
259 | 0 | } |
260 | | |
261 | 0 | return expression_tree_walker(node, |
262 | 0 | LockViewRecurse_walker, |
263 | 0 | context); |
264 | 0 | } |
265 | | |
266 | | static void |
267 | | LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, |
268 | | List *ancestor_views) |
269 | 0 | { |
270 | 0 | LockViewRecurse_context context; |
271 | 0 | Relation view; |
272 | 0 | Query *viewquery; |
273 | | |
274 | | /* caller has already locked the view */ |
275 | 0 | view = table_open(reloid, NoLock); |
276 | 0 | viewquery = get_view_query(view); |
277 | | |
278 | | /* |
279 | | * If the view has the security_invoker property set, check permissions as |
280 | | * the current user. Otherwise, check permissions as the view owner. |
281 | | */ |
282 | 0 | context.lockmode = lockmode; |
283 | 0 | context.nowait = nowait; |
284 | 0 | if (RelationHasSecurityInvoker(view)) |
285 | 0 | context.check_as_user = GetUserId(); |
286 | 0 | else |
287 | 0 | context.check_as_user = view->rd_rel->relowner; |
288 | 0 | context.viewoid = reloid; |
289 | 0 | context.ancestor_views = lappend_oid(ancestor_views, reloid); |
290 | |
|
291 | 0 | LockViewRecurse_walker((Node *) viewquery, &context); |
292 | |
|
293 | 0 | context.ancestor_views = list_delete_last(context.ancestor_views); |
294 | |
|
295 | 0 | table_close(view, NoLock); |
296 | 0 | } |
297 | | |
298 | | /* |
299 | | * Check whether the current user is permitted to lock this relation. |
300 | | */ |
301 | | static AclResult |
302 | | LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid) |
303 | 0 | { |
304 | 0 | AclResult aclresult; |
305 | 0 | AclMode aclmask; |
306 | | |
307 | | /* any of these privileges permit any lock mode */ |
308 | 0 | aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE; |
309 | | |
310 | | /* SELECT privileges also permit ACCESS SHARE and below */ |
311 | 0 | if (lockmode <= AccessShareLock) |
312 | 0 | aclmask |= ACL_SELECT; |
313 | | |
314 | | /* INSERT privileges also permit ROW EXCLUSIVE and below */ |
315 | 0 | if (lockmode <= RowExclusiveLock) |
316 | 0 | aclmask |= ACL_INSERT; |
317 | |
|
318 | 0 | aclresult = pg_class_aclcheck(reloid, userid, aclmask); |
319 | |
|
320 | 0 | return aclresult; |
321 | 0 | } |