LCOV - code coverage report
Current view: top level - src/backend/commands - lockcmds.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 88.2 % 93 82
Test Date: 2026-03-12 19:14:48 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            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          576 : 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         6757 :     foreach(p, lockstmt->relations)
      49              :     {
      50         6219 :         RangeVar   *rv = (RangeVar *) lfirst(p);
      51         6219 :         bool        recurse = rv->inh;
      52              :         Oid         reloid;
      53              : 
      54         6219 :         reloid = RangeVarGetRelidExtended(rv, lockstmt->mode,
      55         6219 :                                           lockstmt->nowait ? RVR_NOWAIT : 0,
      56              :                                           RangeVarCallbackForLockTable,
      57         6219 :                                           &lockstmt->mode);
      58              : 
      59         6184 :         if (get_rel_relkind(reloid) == RELKIND_VIEW)
      60           33 :             LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
      61         6151 :         else if (recurse)
      62         6148 :             LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
      63              :     }
      64          538 : }
      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         6246 : RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
      72              :                              void *arg)
      73              : {
      74         6246 :     LOCKMODE    lockmode = *(LOCKMODE *) arg;
      75              :     char        relkind;
      76              :     char        relpersistence;
      77              :     AclResult   aclresult;
      78              : 
      79         6246 :     if (!OidIsValid(relid))
      80            0 :         return;                 /* doesn't exist, so no permissions check */
      81         6246 :     relkind = get_rel_relkind(relid);
      82         6246 :     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         6246 :     if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
      88              :         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         6246 :     relpersistence = get_rel_persistence(relid);
     100         6246 :     if (relpersistence == RELPERSISTENCE_TEMP)
     101            4 :         MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
     102              : 
     103              :     /* Check permissions. */
     104         6246 :     aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
     105         6246 :     if (aclresult != ACLCHECK_OK)
     106           24 :         aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
     107              : }
     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         6184 : LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
     118              : {
     119              :     List       *children;
     120              :     ListCell   *lc;
     121              : 
     122         6184 :     children = find_all_inheritors(reloid, NoLock, NULL);
     123              : 
     124        14763 :     foreach(lc, children)
     125              :     {
     126         8579 :         Oid         childreloid = lfirst_oid(lc);
     127              : 
     128              :         /* Parent already locked. */
     129         8579 :         if (childreloid == reloid)
     130         6184 :             continue;
     131              : 
     132         2395 :         if (!nowait)
     133         2387 :             LockRelationOid(childreloid, lockmode);
     134            8 :         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              :         }
     146              : 
     147              :         /*
     148              :          * Even if we got the lock, child might have been concurrently
     149              :          * dropped. If so, we can skip it.
     150              :          */
     151         2395 :         if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(childreloid)))
     152              :         {
     153              :             /* Release useless lock */
     154            0 :             UnlockRelationOid(childreloid, lockmode);
     155            0 :             continue;
     156              :         }
     157              :     }
     158         6184 : }
     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         1050 : LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
     178              : {
     179         1050 :     if (node == NULL)
     180          669 :         return false;
     181              : 
     182          381 :     if (IsA(node, Query))
     183              :     {
     184           54 :         Query      *query = (Query *) node;
     185              :         ListCell   *rtable;
     186              : 
     187          111 :         foreach(rtable, query->rtable)
     188              :         {
     189           60 :             RangeTblEntry *rte = lfirst(rtable);
     190              :             AclResult   aclresult;
     191              : 
     192           60 :             Oid         relid = rte->relid;
     193           60 :             char        relkind = rte->relkind;
     194           60 :             char       *relname = get_rel_name(relid);
     195              : 
     196              :             /* Currently, we only allow plain tables or views to be locked. */
     197           60 :             if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
     198              :                 relkind != RELKIND_VIEW)
     199            3 :                 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           57 :             if (list_member_oid(context->ancestor_views, relid))
     206            6 :                 continue;
     207              : 
     208              :             /*
     209              :              * Check permissions as the specified user.  This will either be
     210              :              * the view owner or the current user.
     211              :              */
     212           51 :             aclresult = LockTableAclCheck(relid, context->lockmode,
     213              :                                           context->check_as_user);
     214           51 :             if (aclresult != ACLCHECK_OK)
     215            3 :                 aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
     216              : 
     217              :             /* We have enough rights to lock the relation; do so. */
     218           48 :             if (!context->nowait)
     219           48 :                 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           48 :             if (relkind == RELKIND_VIEW)
     227           12 :                 LockViewRecurse(relid, context->lockmode, context->nowait,
     228              :                                 context->ancestor_views);
     229           36 :             else if (rte->inh)
     230           36 :                 LockTableRecurse(relid, context->lockmode, context->nowait);
     231              :         }
     232              : 
     233           51 :         return query_tree_walker(query,
     234              :                                  LockViewRecurse_walker,
     235              :                                  context,
     236              :                                  QTW_IGNORE_JOINALIASES);
     237              :     }
     238              : 
     239          327 :     return expression_tree_walker(node,
     240              :                                   LockViewRecurse_walker,
     241              :                                   context);
     242              : }
     243              : 
     244              : static void
     245           45 : LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait,
     246              :                 List *ancestor_views)
     247              : {
     248              :     LockViewRecurse_context context;
     249              :     Relation    view;
     250              :     Query      *viewquery;
     251              : 
     252              :     /* caller has already locked the view */
     253           45 :     view = table_open(reloid, NoLock);
     254           45 :     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           45 :     context.lockmode = lockmode;
     261           45 :     context.nowait = nowait;
     262           45 :     if (RelationHasSecurityInvoker(view))
     263            6 :         context.check_as_user = GetUserId();
     264              :     else
     265           39 :         context.check_as_user = view->rd_rel->relowner;
     266           45 :     context.viewoid = reloid;
     267           45 :     context.ancestor_views = lappend_oid(ancestor_views, reloid);
     268              : 
     269           45 :     LockViewRecurse_walker((Node *) viewquery, &context);
     270              : 
     271           42 :     context.ancestor_views = list_delete_last(context.ancestor_views);
     272              : 
     273           42 :     table_close(view, NoLock);
     274           42 : }
     275              : 
     276              : /*
     277              :  * Check whether the current user is permitted to lock this relation.
     278              :  */
     279              : static AclResult
     280         6297 : LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
     281              : {
     282              :     AclResult   aclresult;
     283              :     AclMode     aclmask;
     284              : 
     285              :     /* any of these privileges permit any lock mode */
     286         6297 :     aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
     287              : 
     288              :     /* SELECT privileges also permit ACCESS SHARE and below */
     289         6297 :     if (lockmode <= AccessShareLock)
     290         5925 :         aclmask |= ACL_SELECT;
     291              : 
     292              :     /* INSERT privileges also permit ROW EXCLUSIVE and below */
     293         6297 :     if (lockmode <= RowExclusiveLock)
     294         5976 :         aclmask |= ACL_INSERT;
     295              : 
     296         6297 :     aclresult = pg_class_aclcheck(reloid, userid, aclmask);
     297              : 
     298         6297 :     return aclresult;
     299              : }
        

Generated by: LCOV version 2.0-1