Coverage Report

Created: 2026-09-28 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}