LCOV - code coverage report
Current view: top level - src/backend/commands - lockcmds.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 87.4 % 95 83
Test Date: 2026-07-15 08:16:32 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 73.3 % 86 63

             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                 :             : }
        

Generated by: LCOV version 2.0-1