]> granicus.if.org Git - postgresql/commitdiff
Add more infinite recursion detection while locking a view.
authorTatsuo Ishii <ishii@postgresql.org>
Tue, 17 Apr 2018 07:59:17 +0000 (16:59 +0900)
committerTatsuo Ishii <ishii@postgresql.org>
Tue, 17 Apr 2018 07:59:17 +0000 (16:59 +0900)
Also add regression test cases for detecting infinite recursion in
locking view tests.  Some document enhancements. Patch by Yugo Nagata.

doc/src/sgml/ref/lock.sgml
src/backend/commands/lockcmds.c
src/test/regress/expected/lock.out
src/test/regress/sql/lock.sql

index 96d477c20388bef94d7144d9be5865c2757b0e7f..a225cea63b2afeb4beb0b5abbbb79ac7143a0540 100644 (file)
@@ -46,8 +46,8 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
   </para>
 
   <para>
-   When a view is specified to be locked, all relations appearing in the view
-   definition query are also locked recursively with the same lock mode. 
+   When a view is locked, all relations appearing in the view definition
+   query are also locked recursively with the same lock mode.
   </para>
 
   <para>
@@ -173,6 +173,13 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
     or <literal>TRUNCATE</literal> privileges.
    </para>
 
+   <para>
+    The user performing the lock on the view must have the corresponding privilege
+    on the view.  In addition the view's owner must have the relevant privileges on
+    the underlying base relations, but the user performing the lock does
+    not need any permissions on the underlying base relations.
+   </para>
+
    <para>
     <command>LOCK TABLE</command> is useless outside a transaction block: the lock
     would remain held only to the completion of the statement.  Therefore
index 6daf8e9b453a70cafeca29c529fd0c0eefd73718..8a2aa453ecabfa95d04d6cd84d3185ed779e7c15 100644 (file)
@@ -31,7 +31,7 @@ static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid use
 static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode, Oid userid);
 static void RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid,
                                                         Oid oldrelid, void *arg);
-static void LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait);
+static void LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, List *ancestor_views);
 
 /*
  * LOCK TABLE
@@ -67,7 +67,7 @@ LockTableCommand(LockStmt *lockstmt)
                                                                                  (void *) &lockstmt->mode);
 
                if (get_rel_relkind(reloid) == RELKIND_VIEW)
-                       LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+                       LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
                else if (recurse)
                        LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
        }
@@ -92,7 +92,6 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
                return;                                 /* woops, concurrently dropped; no permissions
                                                                 * check */
 
-
        /* Currently, we only allow plain tables or views to be locked */
        if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
                relkind != RELKIND_VIEW)
@@ -178,11 +177,11 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 
 typedef struct
 {
-       Oid root_reloid;
-       LOCKMODE lockmode;
-       bool nowait;
-       Oid viewowner;
-       Oid viewoid;
+       LOCKMODE        lockmode;               /* lock mode to use */
+       bool            nowait;                 /* no wait mode */
+       Oid                     viewowner;              /* view owner for checking the privilege */
+       Oid                     viewoid;                /* OID of the view to be locked */
+       List       *ancestor_views;     /* OIDs of ancestor views */
 } LockViewRecurse_context;
 
 static bool
@@ -193,19 +192,22 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 
        if (IsA(node, Query))
        {
-               Query           *query = (Query *) node;
-               ListCell        *rtable;
+               Query      *query = (Query *) node;
+               ListCell   *rtable;
 
                foreach(rtable, query->rtable)
                {
-                       RangeTblEntry   *rte = lfirst(rtable);
-                       AclResult                aclresult;
+                       RangeTblEntry *rte = lfirst(rtable);
+                       AclResult       aclresult;
 
-                       Oid relid = rte->relid;
-                       char relkind = rte->relkind;
-                       char *relname = get_rel_name(relid);
+                       Oid                     relid = rte->relid;
+                       char            relkind = rte->relkind;
+                       char       *relname = get_rel_name(relid);
 
-                       /* The OLD and NEW placeholder entries in the view's rtable are skipped. */
+                       /*
+                        * The OLD and NEW placeholder entries in the view's rtable are
+                        * skipped.
+                        */
                        if (relid == context->viewoid &&
                                (!strcmp(rte->eref->aliasname, "old") || !strcmp(rte->eref->aliasname, "new")))
                                continue;
@@ -216,11 +218,11 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
                                continue;
 
                        /* Check infinite recursion in the view definition. */
-                       if (relid == context->root_reloid)
+                       if (list_member_oid(context->ancestor_views, relid))
                                ereport(ERROR,
                                                (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                                               errmsg("infinite recursion detected in rules for relation \"%s\"",
-                                                               get_rel_name(context->root_reloid))));
+                                                errmsg("infinite recursion detected in rules for relation \"%s\"",
+                                                               get_rel_name(relid))));
 
                        /* Check permissions with the view owner's privilege. */
                        aclresult = LockTableAclCheck(relid, context->lockmode, context->viewowner);
@@ -233,11 +235,11 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
                        else if (!ConditionalLockRelationOid(relid, context->lockmode))
                                ereport(ERROR,
                                                (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
-                                               errmsg("could not obtain lock on relation \"%s\"",
+                                                errmsg("could not obtain lock on relation \"%s\"",
                                                                relname)));
 
                        if (relkind == RELKIND_VIEW)
-                               LockViewRecurse(relid, context->root_reloid, context->lockmode, context->nowait);
+                               LockViewRecurse(relid, context->lockmode, context->nowait, context->ancestor_views);
                        else if (rte->inh)
                                LockTableRecurse(relid, context->lockmode, context->nowait, context->viewowner);
                }
@@ -254,24 +256,26 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 }
 
 static void
-LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+LockViewRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, List *ancestor_views)
 {
        LockViewRecurse_context context;
 
-       Relation                 view;
-       Query                   *viewquery;
+       Relation        view;
+       Query      *viewquery;
 
        view = heap_open(reloid, NoLock);
        viewquery = get_view_query(view);
 
-       context.root_reloid = root_reloid;
        context.lockmode = lockmode;
        context.nowait = nowait;
        context.viewowner = view->rd_rel->relowner;
        context.viewoid = reloid;
+       context.ancestor_views = lcons_oid(reloid, ancestor_views);
 
        LockViewRecurse_walker((Node *) viewquery, &context);
 
+       ancestor_views = list_delete_oid(ancestor_views, reloid);
+
        heap_close(view, NoLock);
 }
 
index 964e6f2cdf77a0158c7f05331f5e644a2a8d78ae..185fd2f879b2d31dfd6185de7d36349f502f2f58 100644 (file)
@@ -120,6 +120,17 @@ select relname from pg_locks l, pg_class c
  lock_view6
 (2 rows)
 
+ROLLBACK;
+-- detecting infinite recursions in view definitions
+CREATE OR REPLACE VIEW lock_view2 AS SELECT * from lock_view3;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;
+ERROR:  infinite recursion detected in rules for relation "lock_view2"
+ROLLBACK;
+CREATE VIEW lock_view7 AS SELECT * from lock_view2;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view7 IN EXCLUSIVE MODE;
+ERROR:  infinite recursion detected in rules for relation "lock_view2"
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -142,11 +153,12 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view7;
 DROP VIEW lock_view6;
 DROP VIEW lock_view5;
 DROP VIEW lock_view4;
-DROP VIEW lock_view3;
-DROP VIEW lock_view2;
+DROP VIEW lock_view3 CASCADE;
+NOTICE:  drop cascades to view lock_view2
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
index e22c9e2f3aef709b3769fa4aac885868740b7a88..26a7e59a136f02b27f2aa76714d807125c393a7d 100644 (file)
@@ -84,6 +84,15 @@ select relname from pg_locks l, pg_class c
  where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
  order by relname;
 ROLLBACK;
+-- detecting infinite recursions in view definitions
+CREATE OR REPLACE VIEW lock_view2 AS SELECT * from lock_view3;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;
+ROLLBACK;
+CREATE VIEW lock_view7 AS SELECT * from lock_view2;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view7 IN EXCLUSIVE MODE;
+ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -107,11 +116,11 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view7;
 DROP VIEW lock_view6;
 DROP VIEW lock_view5;
 DROP VIEW lock_view4;
-DROP VIEW lock_view3;
-DROP VIEW lock_view2;
+DROP VIEW lock_view3 CASCADE;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;