Branch data Line data Source code
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 : 592 : LockTableCommand(LockStmt *lockstmt)
42 : : {
43 : : ListCell *p;
44 : :
45 : : /*
46 : : * Iterate over the list and process the named relations one at a time
47 : : */
48 [ + - + + : 7121 : foreach(p, lockstmt->relations)
+ + ]
49 : : {
50 : 6576 : RangeVar *rv = (RangeVar *) lfirst(p);
51 : 6576 : bool recurse = rv->inh;
52 : : Oid reloid;
53 : :
54 : 6576 : reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
55 : 6576 : lockstmt->nowait ? RVR_NOWAIT : 0,
56 : : RangeVarCallbackForLockTable,
57 [ + + ]: 6576 : &lockstmt->mode);
58 : :
59 [ + + ]: 6533 : if (get_rel_relkind(reloid) == RELKIND_VIEW)
60 : 44 : LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
61 [ + + ]: 6489 : else if (recurse)
62 : 6485 : LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
63 : : }
64 : 545 : }
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 : 6639 : RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
72 : : void *arg)
73 : : {
74 : 6639 : LOCKMODE lockmode = *(LOCKMODE *) arg;
75 : : char relkind;
76 : : char relpersistence;
77 : : AclResult aclresult;
78 : :
79 [ - + ]: 6639 : if (!OidIsValid(relid))
80 : 0 : return; /* doesn't exist, so no permissions check */
81 : 6639 : relkind = get_rel_relkind(relid);
82 [ - + ]: 6639 : 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 [ + + + + : 6639 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
- + ]
100 : : relkind != RELKIND_VIEW)
101 [ # # ]: 0 : ereport(ERROR,
102 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
103 : : errmsg("cannot lock relation \"%s\"",
104 : : rv->relname),
105 : : errdetail_relkind_not_supported(relkind)));
106 : :
107 : : /*
108 : : * Make note if a temporary relation has been accessed in this
109 : : * transaction.
110 : : */
111 : 6639 : relpersistence = get_rel_persistence(relid);
112 [ + + ]: 6639 : if (relpersistence == RELPERSISTENCE_TEMP)
113 : 7 : MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
114 : :
115 : : /* Check permissions. */
116 : 6639 : aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
117 [ + + ]: 6639 : if (aclresult != ACLCHECK_OK)
118 : 32 : aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
119 : : }
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 : 6533 : LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
130 : : {
131 : : List *children;
132 : : ListCell *lc;
133 : :
134 : 6533 : children = find_all_inheritors(reloid, NoLock, NULL);
135 : :
136 [ + - + + : 15579 : foreach(lc, children)
+ + ]
137 : : {
138 : 9046 : Oid childreloid = lfirst_oid(lc);
139 : :
140 : : /* Parent already locked. */
141 [ + + ]: 9046 : if (childreloid == reloid)
142 : 6533 : continue;
143 : :
144 [ + + ]: 2513 : if (!nowait)
145 : 2505 : LockRelationOid(childreloid, lockmode);
146 [ - + ]: 8 : else if (!ConditionalLockRelationOid(childreloid, lockmode))
147 : : {
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 : : (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
155 : : errmsg("could not obtain lock on relation \"%s\"",
156 : : relname)));
157 : : }
158 : :
159 : : /*
160 : : * Even if we got the lock, child might have been concurrently
161 : : * dropped. If so, we can skip it.
162 : : */
163 [ - + ]: 2513 : if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid)))
164 : : {
165 : : /* Release useless lock */
166 : 0 : UnlockRelationOid(childreloid, lockmode);
167 : 0 : continue;
168 : : }
169 : : }
170 : 6533 : }
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 : 1468 : LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
190 : : {
191 [ + + ]: 1468 : if (node == NULL)
192 : 960 : return false;
193 : :
194 [ + + ]: 508 : if (IsA(node, Query))
195 : : {
196 : 72 : Query *query = (Query *) node;
197 : : ListCell *rtable;
198 : :
199 [ + - + + : 148 : foreach(rtable, query->rtable)
+ + ]
200 : : {
201 : 80 : RangeTblEntry *rte = lfirst(rtable);
202 : : AclResult aclresult;
203 : :
204 : 80 : Oid relid = rte->relid;
205 : 80 : char relkind = rte->relkind;
206 : 80 : char *relname = get_rel_name(relid);
207 : :
208 : : /* Currently, we only allow plain tables or views to be locked. */
209 [ + + + - : 80 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+ + ]
210 : : relkind != RELKIND_VIEW)
211 : 4 : 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 [ - + ]: 76 : 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 [ + + ]: 76 : if (list_member_oid(context->ancestor_views, relid))
228 : 8 : continue;
229 : :
230 : : /*
231 : : * Check permissions as the specified user. This will either be
232 : : * the view owner or the current user.
233 : : */
234 : 68 : aclresult = LockTableAclCheck(relid, context->lockmode,
235 : : context->check_as_user);
236 [ + + ]: 68 : if (aclresult != ACLCHECK_OK)
237 : 4 : aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
238 : :
239 : : /* We have enough rights to lock the relation; do so. */
240 [ + - ]: 64 : if (!context->nowait)
241 : 64 : LockRelationOid(relid, context->lockmode);
242 [ # # ]: 0 : else if (!ConditionalLockRelationOid(relid, context->lockmode))
243 [ # # ]: 0 : ereport(ERROR,
244 : : (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
245 : : errmsg("could not obtain lock on relation \"%s\"",
246 : : relname)));
247 : :
248 [ + + ]: 64 : if (relkind == RELKIND_VIEW)
249 : 16 : LockViewRecurse(relid, context->lockmode, context->nowait,
250 : : context->ancestor_views);
251 [ + - ]: 48 : else if (rte->inh)
252 : 48 : LockTableRecurse(relid, context->lockmode, context->nowait);
253 : : }
254 : :
255 : 68 : return query_tree_walker(query,
256 : : LockViewRecurse_walker,
257 : : context,
258 : : QTW_IGNORE_JOINALIASES);
259 : : }
260 : :
261 : 436 : return expression_tree_walker(node,
262 : : LockViewRecurse_walker,
263 : : context);
264 : : }
265 : :
266 : : static void
267 : 60 : LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait,
268 : : List *ancestor_views)
269 : : {
270 : : LockViewRecurse_context context;
271 : : Relation view;
272 : : Query *viewquery;
273 : :
274 : : /* caller has already locked the view */
275 : 60 : view = table_open(reloid, NoLock);
276 : 60 : 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 : 60 : context.lockmode = lockmode;
283 : 60 : context.nowait = nowait;
284 [ + + + - ]: 60 : if (RelationHasSecurityInvoker(view))
285 : 8 : context.check_as_user = GetUserId();
286 : : else
287 : 52 : context.check_as_user = view->rd_rel->relowner;
288 : 60 : context.viewoid = reloid;
289 : 60 : context.ancestor_views = lappend_oid(ancestor_views, reloid);
290 : :
291 : 60 : LockViewRecurse_walker((Node *) viewquery, &context);
292 : :
293 : 56 : context.ancestor_views = list_delete_last(context.ancestor_views);
294 : :
295 : 56 : table_close(view, NoLock);
296 : 56 : }
297 : :
298 : : /*
299 : : * Check whether the current user is permitted to lock this relation.
300 : : */
301 : : static AclResult
302 : 6707 : LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
303 : : {
304 : : AclResult aclresult;
305 : : AclMode aclmask;
306 : :
307 : : /* any of these privileges permit any lock mode */
308 : 6707 : aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
309 : :
310 : : /* SELECT privileges also permit ACCESS SHARE and below */
311 [ + + ]: 6707 : if (lockmode <= AccessShareLock)
312 : 6228 : aclmask |= ACL_SELECT;
313 : :
314 : : /* INSERT privileges also permit ROW EXCLUSIVE and below */
315 [ + + ]: 6707 : if (lockmode <= RowExclusiveLock)
316 : 6294 : aclmask |= ACL_INSERT;
317 : :
318 : 6707 : aclresult = pg_class_aclcheck(reloid, userid, aclmask);
319 : :
320 : 6707 : return aclresult;
321 : : }
|