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 : 68 : LockTableCommand(LockStmt *lockstmt)
42 : : {
43 : 68 : ListCell *p;
44 : :
45 : : /*
46 : : * Iterate over the list and process the named relations one at a time
47 : : */
48 [ + + + + : 121 : foreach(p, lockstmt->relations)
+ + ]
49 : : {
50 : 53 : RangeVar *rv = (RangeVar *) lfirst(p);
51 : 53 : bool recurse = rv->inh;
52 : 53 : Oid reloid;
53 : :
54 : 106 : reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
55 : 53 : lockstmt->nowait ? RVR_NOWAIT : 0,
56 : : RangeVarCallbackForLockTable,
57 : 53 : &lockstmt->mode);
58 : :
59 [ + + ]: 53 : if (get_rel_relkind(reloid) == RELKIND_VIEW)
60 : 11 : LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
61 [ + + ]: 42 : else if (recurse)
62 : 41 : LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
63 : 53 : }
64 : 52 : }
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 : 82 : RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
72 : : void *arg)
73 : : {
74 : 82 : LOCKMODE lockmode = *(LOCKMODE *) arg;
75 : 82 : char relkind;
76 : 82 : char relpersistence;
77 : 82 : AclResult aclresult;
78 : :
79 [ + - ]: 82 : if (!OidIsValid(relid))
80 : 0 : return; /* doesn't exist, so no permissions check */
81 : 82 : relkind = get_rel_relkind(relid);
82 [ + - ]: 82 : if (!relkind)
83 : 0 : return; /* woops, concurrently dropped; no permissions
84 : : * check */
85 : :
86 : : /* Currently, we only allow plain tables or views to be locked */
87 [ + + + - : 82 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+ - ]
88 : 21 : relkind != RELKIND_VIEW)
89 [ # # # # ]: 0 : ereport(ERROR,
90 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
91 : : errmsg("cannot lock relation \"%s\"",
92 : : rv->relname),
93 : : errdetail_relkind_not_supported(relkind)));
94 : :
95 : : /*
96 : : * Make note if a temporary relation has been accessed in this
97 : : * transaction.
98 : : */
99 : 82 : relpersistence = get_rel_persistence(relid);
100 [ + + ]: 82 : if (relpersistence == RELPERSISTENCE_TEMP)
101 : 2 : MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
102 : :
103 : : /* Check permissions. */
104 : 82 : aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
105 [ + + ]: 82 : if (aclresult != ACLCHECK_OK)
106 : 8 : aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
107 [ - + ]: 82 : }
108 : :
109 : : /*
110 : : * Apply LOCK TABLE recursively over an inheritance tree
111 : : *
112 : : * This doesn't check permission to perform LOCK TABLE on the child tables,
113 : : * because getting here means that the user has permission to lock the
114 : : * parent which is enough.
115 : : */
116 : : static void
117 : 53 : LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
118 : : {
119 : 53 : List *children;
120 : 53 : ListCell *lc;
121 : :
122 : 53 : children = find_all_inheritors(reloid, NoLock, NULL);
123 : :
124 [ + - + + : 115 : foreach(lc, children)
+ + ]
125 : : {
126 : 62 : Oid childreloid = lfirst_oid(lc);
127 : :
128 : : /* Parent already locked. */
129 [ + + ]: 62 : if (childreloid == reloid)
130 : 53 : continue;
131 : :
132 [ - + ]: 9 : if (!nowait)
133 : 9 : LockRelationOid(childreloid, lockmode);
134 [ # # ]: 0 : else if (!ConditionalLockRelationOid(childreloid, lockmode))
135 : : {
136 : : /* try to throw error by name; relation could be deleted... */
137 : 0 : char *relname = get_rel_name(childreloid);
138 : :
139 [ # # ]: 0 : if (!relname)
140 : 0 : continue; /* child concurrently dropped, just skip it */
141 [ # # # # ]: 0 : ereport(ERROR,
142 : : (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
143 : : errmsg("could not obtain lock on relation \"%s\"",
144 : : relname)));
145 [ # # ]: 0 : }
146 : :
147 : : /*
148 : : * Even if we got the lock, child might have been concurrently
149 : : * dropped. If so, we can skip it.
150 : : */
151 [ + - ]: 9 : if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid)))
152 : : {
153 : : /* Release useless lock */
154 : 0 : UnlockRelationOid(childreloid, lockmode);
155 : 0 : continue;
156 : : }
157 [ - + + ]: 62 : }
158 : 53 : }
159 : :
160 : : /*
161 : : * Apply LOCK TABLE recursively over a view
162 : : *
163 : : * All tables and views appearing in the view definition query are locked
164 : : * recursively with the same lock mode.
165 : : */
166 : :
167 : : typedef struct
168 : : {
169 : : LOCKMODE lockmode; /* lock mode to use */
170 : : bool nowait; /* no wait mode */
171 : : Oid check_as_user; /* user for checking the privilege */
172 : : Oid viewoid; /* OID of the view to be locked */
173 : : List *ancestor_views; /* OIDs of ancestor views */
174 : : } LockViewRecurse_context;
175 : :
176 : : static bool
177 : 351 : LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
178 : : {
179 [ + + ]: 351 : if (node == NULL)
180 : 223 : return false;
181 : :
182 [ + + ]: 128 : if (IsA(node, Query))
183 : : {
184 : 19 : Query *query = (Query *) node;
185 : 19 : ListCell *rtable;
186 : :
187 [ + + + + : 38 : foreach(rtable, query->rtable)
+ + ]
188 : : {
189 : 19 : RangeTblEntry *rte = lfirst(rtable);
190 : 19 : AclResult aclresult;
191 : :
192 : 19 : Oid relid = rte->relid;
193 : 19 : char relkind = rte->relkind;
194 : 19 : char *relname = get_rel_name(relid);
195 : :
196 : : /* Currently, we only allow plain tables or views to be locked. */
197 [ + + + - : 19 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+ + ]
198 : 7 : relkind != RELKIND_VIEW)
199 : 1 : continue;
200 : :
201 : : /*
202 : : * We might be dealing with a self-referential view. If so, we
203 : : * can just stop recursing, since we already locked it.
204 : : */
205 [ + + ]: 18 : if (list_member_oid(context->ancestor_views, relid))
206 : 2 : continue;
207 : :
208 : : /*
209 : : * Check permissions as the specified user. This will either be
210 : : * the view owner or the current user.
211 : : */
212 : 32 : aclresult = LockTableAclCheck(relid, context->lockmode,
213 : 16 : context->check_as_user);
214 [ + + ]: 16 : if (aclresult != ACLCHECK_OK)
215 : 1 : aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
216 : :
217 : : /* We have enough rights to lock the relation; do so. */
218 [ - + ]: 16 : if (!context->nowait)
219 : 16 : LockRelationOid(relid, context->lockmode);
220 [ # # ]: 0 : else if (!ConditionalLockRelationOid(relid, context->lockmode))
221 [ # # # # ]: 0 : ereport(ERROR,
222 : : (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
223 : : errmsg("could not obtain lock on relation \"%s\"",
224 : : relname)));
225 : :
226 [ + + ]: 16 : if (relkind == RELKIND_VIEW)
227 : 8 : LockViewRecurse(relid, context->lockmode, context->nowait,
228 : 4 : context->ancestor_views);
229 [ - + ]: 12 : else if (rte->inh)
230 : 12 : LockTableRecurse(relid, context->lockmode, context->nowait);
231 [ - + + ]: 19 : }
232 : :
233 : 17 : return query_tree_walker(query,
234 : : LockViewRecurse_walker,
235 : : context,
236 : : QTW_IGNORE_JOINALIASES);
237 : 17 : }
238 : :
239 : 109 : return expression_tree_walker(node,
240 : : LockViewRecurse_walker,
241 : : context);
242 : 349 : }
243 : :
244 : : static void
245 : 15 : LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait,
246 : : List *ancestor_views)
247 : : {
248 : 15 : LockViewRecurse_context context;
249 : 15 : Relation view;
250 : 15 : Query *viewquery;
251 : :
252 : : /* caller has already locked the view */
253 : 15 : view = table_open(reloid, NoLock);
254 : 15 : viewquery = get_view_query(view);
255 : :
256 : : /*
257 : : * If the view has the security_invoker property set, check permissions as
258 : : * the current user. Otherwise, check permissions as the view owner.
259 : : */
260 : 15 : context.lockmode = lockmode;
261 : 15 : context.nowait = nowait;
262 [ + - + + : 15 : if (RelationHasSecurityInvoker(view))
+ + ]
263 : 2 : context.check_as_user = GetUserId();
264 : : else
265 : 13 : context.check_as_user = view->rd_rel->relowner;
266 : 15 : context.viewoid = reloid;
267 : 15 : context.ancestor_views = lappend_oid(ancestor_views, reloid);
268 : :
269 : 15 : LockViewRecurse_walker((Node *) viewquery, &context);
270 : :
271 : 15 : context.ancestor_views = list_delete_last(context.ancestor_views);
272 : :
273 : 15 : table_close(view, NoLock);
274 : 15 : }
275 : :
276 : : /*
277 : : * Check whether the current user is permitted to lock this relation.
278 : : */
279 : : static AclResult
280 : 107 : LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
281 : : {
282 : 107 : AclResult aclresult;
283 : 107 : AclMode aclmask;
284 : :
285 : : /* any of these privileges permit any lock mode */
286 : 107 : aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
287 : :
288 : : /* SELECT privileges also permit ACCESS SHARE and below */
289 [ + + ]: 107 : if (lockmode <= AccessShareLock)
290 : 14 : aclmask |= ACL_SELECT;
291 : :
292 : : /* INSERT privileges also permit ROW EXCLUSIVE and below */
293 [ + + ]: 107 : if (lockmode <= RowExclusiveLock)
294 : 27 : aclmask |= ACL_INSERT;
295 : :
296 : 107 : aclresult = pg_class_aclcheck(reloid, userid, aclmask);
297 : :
298 : 214 : return aclresult;
299 : 107 : }
|