Coverage Report

Created: 2025-07-03 06:49

/src/postgres/src/backend/commands/lockcmds.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * lockcmds.c
4
 *    LOCK command support code
5
 *
6
 * Portions Copyright (c) 1996-2025, 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
  /* Currently, we only allow plain tables or views to be locked */
87
0
  if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
88
0
    relkind != RELKIND_VIEW)
89
0
    ereport(ERROR,
90
0
        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
91
0
         errmsg("cannot lock relation \"%s\"",
92
0
            rv->relname),
93
0
         errdetail_relkind_not_supported(relkind)));
94
95
  /*
96
   * Make note if a temporary relation has been accessed in this
97
   * transaction.
98
   */
99
0
  relpersistence = get_rel_persistence(relid);
100
0
  if (relpersistence == RELPERSISTENCE_TEMP)
101
0
    MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
102
103
  /* Check permissions. */
104
0
  aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
105
0
  if (aclresult != ACLCHECK_OK)
106
0
    aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
107
0
}
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
LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
118
0
{
119
0
  List     *children;
120
0
  ListCell   *lc;
121
122
0
  children = find_all_inheritors(reloid, NoLock, NULL);
123
124
0
  foreach(lc, children)
125
0
  {
126
0
    Oid     childreloid = lfirst_oid(lc);
127
128
    /* Parent already locked. */
129
0
    if (childreloid == reloid)
130
0
      continue;
131
132
0
    if (!nowait)
133
0
      LockRelationOid(childreloid, lockmode);
134
0
    else if (!ConditionalLockRelationOid(childreloid, lockmode))
135
0
    {
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
0
          (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
143
0
           errmsg("could not obtain lock on relation \"%s\"",
144
0
              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
0
    if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid)))
152
0
    {
153
      /* Release useless lock */
154
0
      UnlockRelationOid(childreloid, lockmode);
155
0
      continue;
156
0
    }
157
0
  }
158
0
}
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
LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
178
0
{
179
0
  if (node == NULL)
180
0
    return false;
181
182
0
  if (IsA(node, Query))
183
0
  {
184
0
    Query    *query = (Query *) node;
185
0
    ListCell   *rtable;
186
187
0
    foreach(rtable, query->rtable)
188
0
    {
189
0
      RangeTblEntry *rte = lfirst(rtable);
190
0
      AclResult aclresult;
191
192
0
      Oid     relid = rte->relid;
193
0
      char    relkind = rte->relkind;
194
0
      char     *relname = get_rel_name(relid);
195
196
      /* Currently, we only allow plain tables or views to be locked. */
197
0
      if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
198
0
        relkind != RELKIND_VIEW)
199
0
        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
0
      if (list_member_oid(context->ancestor_views, relid))
206
0
        continue;
207
208
      /*
209
       * Check permissions as the specified user.  This will either be
210
       * the view owner or the current user.
211
       */
212
0
      aclresult = LockTableAclCheck(relid, context->lockmode,
213
0
                      context->check_as_user);
214
0
      if (aclresult != ACLCHECK_OK)
215
0
        aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
216
217
      /* We have enough rights to lock the relation; do so. */
218
0
      if (!context->nowait)
219
0
        LockRelationOid(relid, context->lockmode);
220
0
      else if (!ConditionalLockRelationOid(relid, context->lockmode))
221
0
        ereport(ERROR,
222
0
            (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
223
0
             errmsg("could not obtain lock on relation \"%s\"",
224
0
                relname)));
225
226
0
      if (relkind == RELKIND_VIEW)
227
0
        LockViewRecurse(relid, context->lockmode, context->nowait,
228
0
                context->ancestor_views);
229
0
      else if (rte->inh)
230
0
        LockTableRecurse(relid, context->lockmode, context->nowait);
231
0
    }
232
233
0
    return query_tree_walker(query,
234
0
                 LockViewRecurse_walker,
235
0
                 context,
236
0
                 QTW_IGNORE_JOINALIASES);
237
0
  }
238
239
0
  return expression_tree_walker(node,
240
0
                  LockViewRecurse_walker,
241
0
                  context);
242
0
}
243
244
static void
245
LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait,
246
        List *ancestor_views)
247
0
{
248
0
  LockViewRecurse_context context;
249
0
  Relation  view;
250
0
  Query    *viewquery;
251
252
  /* caller has already locked the view */
253
0
  view = table_open(reloid, NoLock);
254
0
  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
0
  context.lockmode = lockmode;
261
0
  context.nowait = nowait;
262
0
  if (RelationHasSecurityInvoker(view))
263
0
    context.check_as_user = GetUserId();
264
0
  else
265
0
    context.check_as_user = view->rd_rel->relowner;
266
0
  context.viewoid = reloid;
267
0
  context.ancestor_views = lappend_oid(ancestor_views, reloid);
268
269
0
  LockViewRecurse_walker((Node *) viewquery, &context);
270
271
0
  context.ancestor_views = list_delete_last(context.ancestor_views);
272
273
0
  table_close(view, NoLock);
274
0
}
275
276
/*
277
 * Check whether the current user is permitted to lock this relation.
278
 */
279
static AclResult
280
LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
281
0
{
282
0
  AclResult aclresult;
283
0
  AclMode   aclmask;
284
285
  /* any of these privileges permit any lock mode */
286
0
  aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
287
288
  /* SELECT privileges also permit ACCESS SHARE and below */
289
0
  if (lockmode <= AccessShareLock)
290
0
    aclmask |= ACL_SELECT;
291
292
  /* INSERT privileges also permit ROW EXCLUSIVE and below */
293
0
  if (lockmode <= RowExclusiveLock)
294
0
    aclmask |= ACL_INSERT;
295
296
0
  aclresult = pg_class_aclcheck(reloid, userid, aclmask);
297
298
0
  return aclresult;
299
0
}