[PATCH] Lockable views

Started by Yugo Nagataover 8 years ago59 messages
#1Yugo Nagata
nagata@sraoss.co.jp
1 attachment(s)

Hi,

Attached is a patch to enable views to be locked.

PostgreSQL has supported automatically updatable views since 9.3, so we can
udpate simply defined views like regular tables. However, currently,
table-level locks on views are not supported. We can not execute LOCK TABLE
for views, while we can get row-level locks by FOR UPDATE/SHARE. In some
situations that we need table-level locks on tables, we may also need
table-level locks on automatically updatable views. Although we can lock
base-relations manually, it would be useful if we can lock views without
knowing the definition of the views.

In the attached patch, only automatically-updatable views that do not have
INSTEAD OF rules or INSTEAD OF triggers are lockable. It is assumed that
those views definition have only one base-relation. When an auto-updatable
view is locked, its base relation is also locked. If the base relation is a
view again, base relations are processed recursively. For locking a view,
the view owner have to have he priviledge to lock the base relation.

* Example

test=# CREATE TABLE tbl (i int);
CREATE TABLE

test=# CREATE VIEW v1 AS SELECT * FROM tbl;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v1;
LOCK TABLE
test=# SELECT relname, locktype, mode FROM pg_locks,pg_class c WHERE c.oid=relation AND relname NOT LIKE 'pg%';
relname | locktype | mode
---------+----------+---------------------
tbl | relation | AccessExclusiveLock
v1 | relation | AccessExclusiveLock
(2 rows)

test=# END;
COMMIT

test=# CREATE VIEW v2 AS SELECT * FROM v1;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v2;
LOCK TABLE
test=# SELECT relname, locktype, mode FROM pg_locks,pg_class c WHERE c.oid=relation AND relname NOT LIKE 'pg%';
relname | locktype | mode
---------+----------+---------------------
v2 | relation | AccessExclusiveLock
tbl | relation | AccessExclusiveLock
v1 | relation | AccessExclusiveLock
(3 rows)

test=# END;
COMMIT

test=# CREATE VIEW v3 AS SELECT count(*) FROM v1;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v3;
ERROR: cannot lock view "v3"
DETAIL: Views that return aggregate functions are not automatically updatable.
test=# END;
ROLLBACK

test=# CREATE FUNCTION fnc() RETURNS trigger AS $$ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql;
CREATE FUNCTION
test=# CREATE TRIGGER trg INSTEAD OF INSERT ON v1 FOR EACH ROW EXECUTE PROCEDURE fnc();
CREATE TRIGGER
test=# BEGIN;
BEGIN
test=# LOCK TABLE v1;
ERROR: cannot lock view "v1"
DETAIL: views that have an INSTEAD OF trigger are not lockable
test=# END;
ROLLBACK

Regards,

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view.patchtext/x-diff; name=lock_view.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b946eab..5a431b7 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,13 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
   </para>
 
   <para>
+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>
+   rules are also lockable. When a view is locked, its base relations are
+   also locked recursively with the same lock mode.
+  </para>
+
+  <para>
    When acquiring locks automatically for commands that reference
    tables, <productname>PostgreSQL</productname> always uses the least
    restrictive lock mode possible. <command>LOCK TABLE</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 9fe9e02..37fd028 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -19,15 +19,20 @@
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
+#include "parser/parsetree.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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 LockViewCheck(Relation view, Query *viewquery);
 
 /*
  * LOCK TABLE
@@ -62,8 +67,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +93,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, ACL_KIND_CLASS, rv->relname);
 }
@@ -107,7 +116,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +129,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +166,82 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ */
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	Relation		 view;
+	Query			*viewquery;
+	RangeTblRef		*rtr;
+	RangeTblEntry	*base_rte;
+	Oid				 baseoid;
+	AclResult		 aclresult;
+	char			*relname;
+	char			 relkind;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+
+	/* Check whether the view is lockable */
+	LockViewCheck(view, viewquery);
+
+	heap_close(view, NoLock);
+
+	Assert(list_length(viewquery->jointree->fromlist) == 1);
+	rtr = linitial_node(RangeTblRef, viewquery->jointree->fromlist);
+	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+	Assert(base_rte->rtekind == RTE_RELATION);
+
+	baseoid = base_rte->relid;
+	relname = get_rel_name(baseoid);
+	relkind = get_rel_relkind(baseoid);
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table or view",
+						relname)));
+
+	/* Check infinite recursion in the view definition */
+	if (baseoid == root_reloid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("infinite recursion detected in rules for relation \"%s\"",
+						get_rel_name(root_reloid))));
+
+	/* Check permissions with the view owner's priviledge. */
+	aclresult = LockTableAclCheck(baseoid, lockmode, view->rd_rel->relowner);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_CLASS, relname);
+
+	/* We have enough rights to lock the relation; do so. */
+	if (!nowait)
+		LockRelationOid(baseoid, lockmode);
+	else if (!ConditionalLockRelationOid(baseoid, lockmode))
+		ereport(ERROR,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+				 errmsg("could not obtain lock on relation \"%s\"",
+						relname)));
+
+	if (relkind == RELKIND_VIEW)
+		LockViewRecurse(baseoid, root_reloid, lockmode, nowait);
+	else if (base_rte->inh)
+		LockTableRecurse(baseoid, lockmode, nowait, view->rd_rel->relowner);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +254,50 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
+
+/*
+ * Check whether the view is lockable.
+ *
+ * Currently, only auto-updatable views can be locked, that is,
+ * views whose definition are simple and that doesn't have
+ * instead of rules or triggers are lockable.
+ */
+static void
+LockViewCheck(Relation view, Query *viewquery)
+{
+	const char	*relname = RelationGetRelationName(view);
+	const char	*auto_update_detail;
+	int			 i;
+
+	auto_update_detail = view_query_is_auto_updatable(viewquery, false);
+	if (auto_update_detail)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("%s", _(auto_update_detail))));
+
+
+	/* Confirm the view doesn't have instead of rules */
+	for (i=0; i<view->rd_rules->numLocks; i++)
+	{
+		if (view->rd_rules->rules[i]->isInstead && view->rd_rules->rules[i]->event != CMD_SELECT)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("views that have an INSTEAD OF rule are not lockable")));
+	}
+
+	/* Confirm the view doesn't have instead of triggers */
+	if (view->trigdesc &&
+		(view->trigdesc->trig_insert_instead_row ||
+		 view->trigdesc->trig_update_instead_row ||
+		 view->trigdesc->trig_delete_instead_row))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot lock view \"%s\"", relname),
+				 errdetail_internal("views that have an INSTEAD OF trigger are not lockable")));
+}
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index fd27344..b6becb8 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,9 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +32,17 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an autoupdatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-autoupdatable view
+ERROR:  cannot lock view "lock_view2"
+DETAIL:  Views that return aggregate functions are not automatically updatable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-autoupdatable view
+ERROR:  cannot lock view "lock_view3"
+DETAIL:  Views that do not select from a single table or view are not automatically updatable.
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -55,6 +66,8 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..d8e97d7 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,9 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +35,13 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an autoupdatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-autoupdatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-autoupdatable view
 ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
@@ -59,6 +67,8 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
#2Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#1)
Re: [PATCH] Lockable views

Hi,

Attached is a patch to enable views to be locked.

Nice.

PostgreSQL has supported automatically updatable views since 9.3, so we can
udpate simply defined views like regular tables. However, currently,
table-level locks on views are not supported. We can not execute LOCK TABLE
for views, while we can get row-level locks by FOR UPDATE/SHARE. In some
situations that we need table-level locks on tables, we may also need
table-level locks on automatically updatable views. Although we can lock
base-relations manually, it would be useful if we can lock views without
knowing the definition of the views.

In the attached patch, only automatically-updatable views that do not have
INSTEAD OF rules or INSTEAD OF triggers are lockable. It is assumed that
those views definition have only one base-relation. When an auto-updatable
view is locked, its base relation is also locked. If the base relation is a
view again, base relations are processed recursively. For locking a view,
the view owner have to have he priviledge to lock the base relation.

* Example

test=# CREATE TABLE tbl (i int);
CREATE TABLE

test=# CREATE VIEW v1 AS SELECT * FROM tbl;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v1;
LOCK TABLE
test=# SELECT relname, locktype, mode FROM pg_locks,pg_class c WHERE c.oid=relation AND relname NOT LIKE 'pg%';
relname | locktype | mode
---------+----------+---------------------
tbl | relation | AccessExclusiveLock
v1 | relation | AccessExclusiveLock
(2 rows)

test=# END;
COMMIT

test=# CREATE VIEW v2 AS SELECT * FROM v1;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v2;
LOCK TABLE
test=# SELECT relname, locktype, mode FROM pg_locks,pg_class c WHERE c.oid=relation AND relname NOT LIKE 'pg%';
relname | locktype | mode
---------+----------+---------------------
v2 | relation | AccessExclusiveLock
tbl | relation | AccessExclusiveLock
v1 | relation | AccessExclusiveLock
(3 rows)

test=# END;
COMMIT

test=# CREATE VIEW v3 AS SELECT count(*) FROM v1;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v3;
ERROR: cannot lock view "v3"
DETAIL: Views that return aggregate functions are not automatically updatable.

It would be nice if the message would be something like:

DETAIL: Views that return aggregate functions are not lockable

test=# END;
ROLLBACK

test=# CREATE FUNCTION fnc() RETURNS trigger AS $$ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql;
CREATE FUNCTION
test=# CREATE TRIGGER trg INSTEAD OF INSERT ON v1 FOR EACH ROW EXECUTE PROCEDURE fnc();
CREATE TRIGGER
test=# BEGIN;
BEGIN
test=# LOCK TABLE v1;
ERROR: cannot lock view "v1"
DETAIL: views that have an INSTEAD OF trigger are not lockable
test=# END;
ROLLBACK

I wonder if we should lock tables in a subquery as well. For example,

create view v1 as select * from t1 where i in (select i from t2);

In this case should we lock t2 as well?

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Tatsuo Ishii (#2)
Re: [PATCH] Lockable views

test=# CREATE VIEW v3 AS SELECT count(*) FROM v1;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v3;
ERROR: cannot lock view "v3"
DETAIL: Views that return aggregate functions are not automatically updatable.

It would be nice if the message would be something like:

DETAIL: Views that return aggregate functions are not lockable

test=# END;
ROLLBACK

test=# CREATE FUNCTION fnc() RETURNS trigger AS $$ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql;
CREATE FUNCTION
test=# CREATE TRIGGER trg INSTEAD OF INSERT ON v1 FOR EACH ROW EXECUTE PROCEDURE fnc();
CREATE TRIGGER
test=# BEGIN;
BEGIN
test=# LOCK TABLE v1;
ERROR: cannot lock view "v1"
DETAIL: views that have an INSTEAD OF trigger are not lockable
test=# END;
ROLLBACK

I wonder if we should lock tables in a subquery as well. For example,

create view v1 as select * from t1 where i in (select i from t2);

In this case should we lock t2 as well?

Current the patch ignores t2 in the case above.

So we have options below:

- Leave as it is (ignore tables appearing in a subquery)

- Lock all tables including in a subquery

- Check subquery in the view definition. If there are some tables
involved, emit an error and abort.

The first one might be different from what users expect. There may be
a risk that the second one could cause deadlock. So it seems the third
one seems to be the safest IMO.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#3)
Re: [PATCH] Lockable views

On Thu, 12 Oct 2017 13:11:45 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

test=# CREATE VIEW v3 AS SELECT count(*) FROM v1;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v3;
ERROR: cannot lock view "v3"
DETAIL: Views that return aggregate functions are not automatically updatable.

It would be nice if the message would be something like:

DETAIL: Views that return aggregate functions are not lockable

This uses messages from view_query_is_auto_updatable() of the rewrite system directly.
Although we can modify the messages, I think it is not necessary for now
since we can lock only automatically updatable views.

test=# END;
ROLLBACK

test=# CREATE FUNCTION fnc() RETURNS trigger AS $$ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql;
CREATE FUNCTION
test=# CREATE TRIGGER trg INSTEAD OF INSERT ON v1 FOR EACH ROW EXECUTE PROCEDURE fnc();
CREATE TRIGGER
test=# BEGIN;
BEGIN
test=# LOCK TABLE v1;
ERROR: cannot lock view "v1"
DETAIL: views that have an INSTEAD OF trigger are not lockable
test=# END;
ROLLBACK

I wonder if we should lock tables in a subquery as well. For example,

create view v1 as select * from t1 where i in (select i from t2);

In this case should we lock t2 as well?

Current the patch ignores t2 in the case above.

So we have options below:

- Leave as it is (ignore tables appearing in a subquery)

- Lock all tables including in a subquery

- Check subquery in the view definition. If there are some tables
involved, emit an error and abort.

The first one might be different from what users expect. There may be
a risk that the second one could cause deadlock. So it seems the third
one seems to be the safest IMO.

Make sense. Even if the view is locked, when tables in a subquery is
modified, the contents of view can change. To avoid it, we have to
lock tables, or give up to lock such views.

We can say the same thing for functions in a subquery. If the definition
of the functions are changed, the result of the view can change.
We cannot lock functions, but should we abtain row-level lock on pg_proc
in such cases? (of cause, or give up to lock such views....)

BTW, though you mentioned the risk of deadlocks, even when there
are no subquery, deadlock can occur in the current patch.

For example, lock a table T in Session1, and then lock a view V
whose base relation is T in Session2. Session2 will wait for
Session1 to release the lock on T. After this, when Session1 try to
lock view V, the deadlock occurs and the query is canceled.

Is this unacceptable behavior?

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#4)
Re: [PATCH] Lockable views

test=# CREATE VIEW v3 AS SELECT count(*) FROM v1;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v3;
ERROR: cannot lock view "v3"
DETAIL: Views that return aggregate functions are not automatically updatable.

It would be nice if the message would be something like:

DETAIL: Views that return aggregate functions are not lockable

This uses messages from view_query_is_auto_updatable() of the rewrite system directly.
Although we can modify the messages, I think it is not necessary for now
since we can lock only automatically updatable views.

You could add a flag to view_query_is_auto_updatable() to switch the
message between

DETAIL: Views that return aggregate functions are not automatically updatable.

and

DETAIL: Views that return aggregate functions are not lockable

I wonder if we should lock tables in a subquery as well. For example,

create view v1 as select * from t1 where i in (select i from t2);

In this case should we lock t2 as well?

Current the patch ignores t2 in the case above.

So we have options below:

- Leave as it is (ignore tables appearing in a subquery)

- Lock all tables including in a subquery

- Check subquery in the view definition. If there are some tables
involved, emit an error and abort.

The first one might be different from what users expect. There may be
a risk that the second one could cause deadlock. So it seems the third
one seems to be the safest IMO.

Make sense. Even if the view is locked, when tables in a subquery is
modified, the contents of view can change. To avoid it, we have to
lock tables, or give up to lock such views.

We can say the same thing for functions in a subquery. If the definition
of the functions are changed, the result of the view can change.
We cannot lock functions, but should we abtain row-level lock on pg_proc
in such cases? (of cause, or give up to lock such views....)

I think we don't need to care about function definition changes used
in where clauses in views. None view queries using functions do not
care about the definition changes of functions while executing the
query. So why updatable views need to care them?

BTW, though you mentioned the risk of deadlocks, even when there
are no subquery, deadlock can occur in the current patch.

For example, lock a table T in Session1, and then lock a view V
whose base relation is T in Session2. Session2 will wait for
Session1 to release the lock on T. After this, when Session1 try to
lock view V, the deadlock occurs and the query is canceled.

You are right. Dealocks could occur in any case.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#5)
Re: [PATCH] Lockable views

On Mon, 16 Oct 2017 10:07:48 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

test=# CREATE VIEW v3 AS SELECT count(*) FROM v1;
CREATE VIEW
test=# BEGIN;
BEGIN
test=# LOCK TABLE v3;
ERROR: cannot lock view "v3"
DETAIL: Views that return aggregate functions are not automatically updatable.

It would be nice if the message would be something like:

DETAIL: Views that return aggregate functions are not lockable

This uses messages from view_query_is_auto_updatable() of the rewrite system directly.
Although we can modify the messages, I think it is not necessary for now
since we can lock only automatically updatable views.

You could add a flag to view_query_is_auto_updatable() to switch the
message between

DETAIL: Views that return aggregate functions are not automatically updatable.

and

DETAIL: Views that return aggregate functions are not lockable

OK. I'll change view_query_is_auto_updatable() so.

I wonder if we should lock tables in a subquery as well. For example,

create view v1 as select * from t1 where i in (select i from t2);

In this case should we lock t2 as well?

Current the patch ignores t2 in the case above.

So we have options below:

- Leave as it is (ignore tables appearing in a subquery)

- Lock all tables including in a subquery

- Check subquery in the view definition. If there are some tables
involved, emit an error and abort.

The first one might be different from what users expect. There may be
a risk that the second one could cause deadlock. So it seems the third
one seems to be the safest IMO.

Make sense. Even if the view is locked, when tables in a subquery is
modified, the contents of view can change. To avoid it, we have to
lock tables, or give up to lock such views.

We can say the same thing for functions in a subquery. If the definition
of the functions are changed, the result of the view can change.
We cannot lock functions, but should we abtain row-level lock on pg_proc
in such cases? (of cause, or give up to lock such views....)

I think we don't need to care about function definition changes used
in where clauses in views. None view queries using functions do not
care about the definition changes of functions while executing the
query. So why updatable views need to care them?

I'm a bit confused. What is difference between tables and functions
in a subquery with regard to view locking? I think also none view queries
using a subquery do not care about the changes of tables in the
subquery while executing the query. I might be misnderstanding
the problem you mentioned.

BTW, I found that if we have to handle subqueries in where clause, we would
also have to care about subqueries in target list... The view defined as
below is also updatable.

=# create view v7 as select (select * from tbl2 limit 1) from tbl;

BTW, though you mentioned the risk of deadlocks, even when there
are no subquery, deadlock can occur in the current patch.

For example, lock a table T in Session1, and then lock a view V
whose base relation is T in Session2. Session2 will wait for
Session1 to release the lock on T. After this, when Session1 try to
lock view V, the deadlock occurs and the query is canceled.

You are right. Dealocks could occur in any case.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#6)
Re: [PATCH] Lockable views

I'm a bit confused. What is difference between tables and functions
in a subquery with regard to view locking? I think also none view queries
using a subquery do not care about the changes of tables in the
subquery while executing the query. I might be misnderstanding
the problem you mentioned.

The difference is in the function cases we concern the function
definition. While the table cases need to care about table
definitions *and* contents of the table.

If we are changing the table definition, AccessExclusiveLock will be
held for the table and the updation will be blocked anyway. So we
don't need to care about the table definition changes.

On the other hand, table contents changes need to be cared because no
automatic locking are held in some cases. I think whether tables in
the subquery need locking or not is depending on use cases.

So I dug into the previous candidates a little bit more:

1) Leave as it is (ignore tables appearing in a subquery)

2) Lock all tables including in a subquery

3) Check subquery in the view definition. If there are some tables
involved, emit an error and abort.

I think one of the problems with #2 is, we will lock tables involved
by the view in random order, which could cause unwanted dead
locks. This is not good and I cannot see any easy way to avoid
this. Also some tables may not need to be locked.

Problem with #3 is, it does not help a user who wants to control
lockings by himself/herself.

So it seem #1 is the most reasonable way to deal with the problem
assuming that it's user's responsibility to take appropriate locks on
the tables in the subquery.

BTW, I found that if we have to handle subqueries in where clause, we would
also have to care about subqueries in target list... The view defined as
below is also updatable.

=# create view v7 as select (select * from tbl2 limit 1) from tbl;

The view is not updatable. You will get something like if you try to update v7:

DETAIL: Views that have no updatable columns are not automatically updatable.

On the other hand this:

create view v7 as select i, (select j from tbl2 limit 1) from tbl;

will be updatable. In this case column j of v7 will never be
updatable. And you should do something like:

insert into v7(i) values...

In short, you don't need to care about a subquery appearing in the TLE
as far as the view locking concerns.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Robert Haas
robertmhaas@gmail.com
In reply to: Yugo Nagata (#1)
Re: [PATCH] Lockable views

On Wed, Oct 11, 2017 at 11:36 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote:

In the attached patch, only automatically-updatable views that do not have
INSTEAD OF rules or INSTEAD OF triggers are lockable. It is assumed that
those views definition have only one base-relation. When an auto-updatable
view is locked, its base relation is also locked. If the base relation is a
view again, base relations are processed recursively. For locking a view,
the view owner have to have he priviledge to lock the base relation.

Why is this the right behavior?

I would have expected LOCK TABLE v to lock the view and nothing else.

See /messages/by-id/AANLkTi=KupesJHRdEvGfbT30aU_iYRO6zwK+fwwY_sGd@mail.gmail.com
for previous discussion of this topic.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#8)
Re: [HACKERS] [PATCH] Lockable views

On Fri, Oct 27, 2017 at 2:11 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Oct 11, 2017 at 11:36 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote:

In the attached patch, only automatically-updatable views that do not have
INSTEAD OF rules or INSTEAD OF triggers are lockable. It is assumed that
those views definition have only one base-relation. When an auto-updatable
view is locked, its base relation is also locked. If the base relation is a
view again, base relations are processed recursively. For locking a view,
the view owner have to have he priviledge to lock the base relation.

Why is this the right behavior?

I would have expected LOCK TABLE v to lock the view and nothing else.

See /messages/by-id/AANLkTi=KupesJHRdEvGfbT30aU_iYRO6zwK+fwwY_sGd@mail.gmail.com
for previous discussion of this topic.

That's what I would expect as well.. But I may be missing something. I
am marking the patch as returned with feedback as this has not been
replied in one month.
--
Michael

#10Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#7)
Re: [HACKERS] [PATCH] Lockable views

On Tue, 17 Oct 2017 11:59:05 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

I'm a bit confused. What is difference between tables and functions
in a subquery with regard to view locking? I think also none view queries
using a subquery do not care about the changes of tables in the
subquery while executing the query. I might be misnderstanding
the problem you mentioned.

The difference is in the function cases we concern the function
definition. While the table cases need to care about table
definitions *and* contents of the table.

If we are changing the table definition, AccessExclusiveLock will be
held for the table and the updation will be blocked anyway. So we
don't need to care about the table definition changes.

On the other hand, table contents changes need to be cared because no
automatic locking are held in some cases. I think whether tables in
the subquery need locking or not is depending on use cases.

So I dug into the previous candidates a little bit more:

1) Leave as it is (ignore tables appearing in a subquery)

2) Lock all tables including in a subquery

3) Check subquery in the view definition. If there are some tables
involved, emit an error and abort.

I think one of the problems with #2 is, we will lock tables involved
by the view in random order, which could cause unwanted dead
locks. This is not good and I cannot see any easy way to avoid
this. Also some tables may not need to be locked.

Problem with #3 is, it does not help a user who wants to control
lockings by himself/herself.

So it seem #1 is the most reasonable way to deal with the problem
assuming that it's user's responsibility to take appropriate locks on
the tables in the subquery.

Thank you for your response. I agree to adopt #1.

BTW, I found that if we have to handle subqueries in where clause, we would
also have to care about subqueries in target list... The view defined as
below is also updatable.

=# create view v7 as select (select * from tbl2 limit 1) from tbl;

The view is not updatable. You will get something like if you try to update v7:

DETAIL: Views that have no updatable columns are not automatically updatable.

Although you can not insert into or update v7, you can delete tuples from v7
since it just delete tuples from table tbl regardless with any column.
However, as disussed above, if it is user's responsibility to take appropriate
locks on the tables in subqueries in the target list, we don't need to
care about these.

On the other hand this:

create view v7 as select i, (select j from tbl2 limit 1) from tbl;

will be updatable. In this case column j of v7 will never be
updatable. And you should do something like:

insert into v7(i) values...

In short, you don't need to care about a subquery appearing in the TLE
as far as the view locking concerns.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
Yugo Nagata <nagata@sraoss.co.jp>

#11Yugo Nagata
nagata@sraoss.co.jp
In reply to: Robert Haas (#8)
Re: [HACKERS] [PATCH] Lockable views

On Fri, 27 Oct 2017 07:11:14 +0200
Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Oct 11, 2017 at 11:36 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote:

In the attached patch, only automatically-updatable views that do not have
INSTEAD OF rules or INSTEAD OF triggers are lockable. It is assumed that
those views definition have only one base-relation. When an auto-updatable
view is locked, its base relation is also locked. If the base relation is a
view again, base relations are processed recursively. For locking a view,
the view owner have to have he priviledge to lock the base relation.

Why is this the right behavior?

I would have expected LOCK TABLE v to lock the view and nothing else.

See /messages/by-id/AANLkTi=KupesJHRdEvGfbT30aU_iYRO6zwK+fwwY_sGd@mail.gmail.com
for previous discussion of this topic.

This discussion is one about 7 years ago when automatically-updatable views
are not supported. Since 9.3, simple views can be updated as well as tables,
so now I think it is reasonable that LOCK TABLE for views locks their base
tables.

If we want to lock only the view, it seems to me that LOCK VIEW syntax is good.
However, to realize this, changing the syntax to avoid a shift/reduce
conflict will be needed as disucussed in the "LOCK for non-tables" thread.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
Yugo Nagata <nagata@sraoss.co.jp>

#12Yugo Nagata
nagata@sraoss.co.jp
In reply to: Michael Paquier (#9)
Re: [HACKERS] [PATCH] Lockable views

On Wed, 29 Nov 2017 11:29:36 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:

On Fri, Oct 27, 2017 at 2:11 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Oct 11, 2017 at 11:36 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote:

In the attached patch, only automatically-updatable views that do not have
INSTEAD OF rules or INSTEAD OF triggers are lockable. It is assumed that
those views definition have only one base-relation. When an auto-updatable
view is locked, its base relation is also locked. If the base relation is a
view again, base relations are processed recursively. For locking a view,
the view owner have to have he priviledge to lock the base relation.

Why is this the right behavior?

I would have expected LOCK TABLE v to lock the view and nothing else.

See /messages/by-id/AANLkTi=KupesJHRdEvGfbT30aU_iYRO6zwK+fwwY_sGd@mail.gmail.com
for previous discussion of this topic.

That's what I would expect as well.. But I may be missing something. I
am marking the patch as returned with feedback as this has not been
replied in one month.

I was busy for and I could not work on this patch. After reading the
previous discussion, I still think the behavior of this patch would
be right. So, I would like to reregister to CF 2018-1. Do I need to
create a new entry on CF? or should I change the status to
"Moved to next CF"?

--
Michael

--
Yugo Nagata <nagata@sraoss.co.jp>

#13Michael Paquier
michael.paquier@gmail.com
In reply to: Yugo Nagata (#12)
Re: [HACKERS] [PATCH] Lockable views

On Fri, Dec 22, 2017 at 04:19:46PM +0900, Yugo Nagata wrote:

I was busy for and I could not work on this patch. After reading the
previous discussion, I still think the behavior of this patch would
be right. So, I would like to reregister to CF 2018-1. Do I need to
create a new entry on CF? or should I change the status to
"Moved to next CF"?

This is entirely up to you. It may make sense as well to spawn a new thread
and mark the new patch set as v2, based on the feedback received for v1, as
well as it could make sense to continue with this thread. If the move involves
a complete patch rewrite with a rather different concept, a new thread is more
adapted in my opinion.
--
Michael

#14Yugo Nagata
nagata@sraoss.co.jp
In reply to: Michael Paquier (#13)
Re: [HACKERS] [PATCH] Lockable views

On Sat, 23 Dec 2017 09:44:30 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:

On Fri, Dec 22, 2017 at 04:19:46PM +0900, Yugo Nagata wrote:

I was busy for and I could not work on this patch. After reading the
previous discussion, I still think the behavior of this patch would
be right. So, I would like to reregister to CF 2018-1. Do I need to
create a new entry on CF? or should I change the status to
"Moved to next CF"?

This is entirely up to you. It may make sense as well to spawn a new thread
and mark the new patch set as v2, based on the feedback received for v1, as
well as it could make sense to continue with this thread. If the move involves
a complete patch rewrite with a rather different concept, a new thread is more
adapted in my opinion.

Thank you for your comment.

I have created a new entry in CF-2017-1 and registered this thread again.

--
Michael

--
Yugo Nagata <nagata@sraoss.co.jp>

#15Michael Paquier
michael.paquier@gmail.com
In reply to: Yugo Nagata (#14)
Re: [HACKERS] [PATCH] Lockable views

On Tue, Dec 26, 2017 at 06:37:06PM +0900, Yugo Nagata wrote:

I have created a new entry in CF-2017-1 and registered this thread again.

Fine for me. Thanks for the update. And I guess that you are planning to
send a new version before the beginning of the next commit fest using
the feedback provided?
--
Michael

#16Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Yugo Nagata (#11)
Re: [HACKERS] [PATCH] Lockable views

Yugo Nagata wrote:

On Fri, 27 Oct 2017 07:11:14 +0200
Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Oct 11, 2017 at 11:36 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote:

In the attached patch, only automatically-updatable views that do not have
INSTEAD OF rules or INSTEAD OF triggers are lockable. It is assumed that
those views definition have only one base-relation. When an auto-updatable
view is locked, its base relation is also locked. If the base relation is a
view again, base relations are processed recursively. For locking a view,
the view owner have to have he priviledge to lock the base relation.

Why is this the right behavior?

I would have expected LOCK TABLE v to lock the view and nothing else.

This discussion is one about 7 years ago when automatically-updatable views
are not supported. Since 9.3, simple views can be updated as well as tables,
so now I think it is reasonable that LOCK TABLE for views locks their base
tables.

I agree with Yugo Nagata -- LOCK TABLE is in some cases necessary to
provide the right isolation so that an operation can be carried out
without interference from other processes that want to process the same
data -- and if a view is provided on top of existing tables, preventing
concurrent changes to the data returned by the view is done by locking
the view and recursively the tables that the view are built on, as if
the view were a table. This is why LOCK TABLE is the right command to
do it.

Also, if an application is designed using a table and concurrent changes
are prevented via LOCK TABLE, then when the underlying schema is changed
and the table is replaced by a view, the application continues to work
unchanged; not only syntactically (no error because of table-locking a
view) but also semantically because new application code that modifies
data in underlying tables from paths other than the view will need to
compete with those through the view, which is correct.

See /messages/by-id/AANLkTi=KupesJHRdEvGfbT30aU_iYRO6zwK+fwwY_sGd@mail.gmail.com
for previous discussion of this topic.

If we want to lock only the view, it seems to me that LOCK VIEW syntax is good.
However, to realize this, changing the syntax to avoid a shift/reduce
conflict will be needed as disucussed in the "LOCK for non-tables" thread.

+1 on making TABLE mandatory in LOCK [TABLE], since that will support
this new LOCK VIEW thing as well as locking other object types.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#17Yugo Nagata
nagata@sraoss.co.jp
In reply to: Michael Paquier (#15)
Re: [HACKERS] [PATCH] Lockable views

On Tue, 26 Dec 2017 22:22:33 +0900
Michael Paquier <michael.paquier@gmail.com> wrote:

On Tue, Dec 26, 2017 at 06:37:06PM +0900, Yugo Nagata wrote:

I have created a new entry in CF-2017-1 and registered this thread again.

Fine for me. Thanks for the update. And I guess that you are planning to
send a new version before the beginning of the next commit fest using
the feedback provided?

Yes. I'll update the patch according to Ishii-san's comments.

--
Michael

--
Yugo Nagata <nagata@sraoss.co.jp>

#18Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#10)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

Hi,

Attached is the updated patch.

On Mon, 16 Oct 2017 10:07:48 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

It would be nice if the message would be something like:

DETAIL: Views that return aggregate functions are not lockable

You could add a flag to view_query_is_auto_updatable() to switch the
message between

DETAIL: Views that return aggregate functions are not automatically updatable.

and

DETAIL: Views that return aggregate functions are not lockable

I didn't want to change the interface of view_query_is_auto_updatable()
because this might be called from other third-patry software, so I renamed
this function to view_query_is_auto_updatable_or_lockable() and added the flag
to this. I created view_query_is_auto_updatable() as a wrapper of this function.
I also made view_query_is_lockable() that returns a other message than
view_query_is_auto_updatable().

On Tue, 17 Oct 2017 11:59:05 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

1) Leave as it is (ignore tables appearing in a subquery)

2) Lock all tables including in a subquery

3) Check subquery in the view

So it seem #1 is the most reasonable way to deal with the problem
assuming that it's user's responsibility to take appropriate locks on
the tables in the subquery.

I adopted #1 and I didn't change anything about this.

Regards,

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view-v2.patchtext/x-diff; name=lock_view-v2.patchDownload
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 9fe9e02..80e00f7 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -19,15 +19,20 @@
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
+#include "parser/parsetree.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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 LockViewCheck(Relation view, Query *viewquery);
 
 /*
  * LOCK TABLE
@@ -62,8 +67,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +93,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, ACL_KIND_CLASS, rv->relname);
 }
@@ -107,7 +116,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +129,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +166,82 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ */
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	Relation		 view;
+	Query			*viewquery;
+	RangeTblRef		*rtr;
+	RangeTblEntry	*base_rte;
+	Oid				 baseoid;
+	AclResult		 aclresult;
+	char			*relname;
+	char			 relkind;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+
+	/* Check whether the view is lockable */
+	LockViewCheck(view, viewquery);
+
+	heap_close(view, NoLock);
+
+	Assert(list_length(viewquery->jointree->fromlist) == 1);
+	rtr = linitial_node(RangeTblRef, viewquery->jointree->fromlist);
+	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+	Assert(base_rte->rtekind == RTE_RELATION);
+
+	baseoid = base_rte->relid;
+	relname = get_rel_name(baseoid);
+	relkind = get_rel_relkind(baseoid);
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table or view",
+						relname)));
+
+	/* Check infinite recursion in the view definition */
+	if (baseoid == root_reloid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("infinite recursion detected in rules for relation \"%s\"",
+						get_rel_name(root_reloid))));
+
+	/* Check permissions with the view owner's priviledge. */
+	aclresult = LockTableAclCheck(baseoid, lockmode, view->rd_rel->relowner);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_CLASS, relname);
+
+	/* We have enough rights to lock the relation; do so. */
+	if (!nowait)
+		LockRelationOid(baseoid, lockmode);
+	else if (!ConditionalLockRelationOid(baseoid, lockmode))
+		ereport(ERROR,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+				 errmsg("could not obtain lock on relation \"%s\"",
+						relname)));
+
+	if (relkind == RELKIND_VIEW)
+		LockViewRecurse(baseoid, root_reloid, lockmode, nowait);
+	else if (base_rte->inh)
+		LockTableRecurse(baseoid, lockmode, nowait, view->rd_rel->relowner);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +254,50 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
+
+/*
+ * Check whether the view is lockable.
+ *
+ * Currently, only auto-updatable views can be locked, that is,
+ * views whose definition are simple and that doesn't have
+ * instead of rules or triggers are lockable.
+ */
+static void
+LockViewCheck(Relation view, Query *viewquery)
+{
+	const char	*relname = RelationGetRelationName(view);
+	const char	*lockable_detail;
+	int			 i;
+
+	lockable_detail = view_query_is_lockable(viewquery);
+	if (lockable_detail)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("%s", _(lockable_detail))));
+
+
+	/* Confirm the view doesn't have instead of rules */
+	for (i=0; i<view->rd_rules->numLocks; i++)
+	{
+		if (view->rd_rules->rules[i]->isInstead && view->rd_rules->rules[i]->event != CMD_SELECT)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("views that have an INSTEAD OF rule are not lockable")));
+	}
+
+	/* Confirm the view doesn't have instead of triggers */
+	if (view->trigdesc &&
+		(view->trigdesc->trig_insert_instead_row ||
+		 view->trigdesc->trig_update_instead_row ||
+		 view->trigdesc->trig_delete_instead_row))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot lock view \"%s\"", relname),
+				 errdetail_internal("views that have an INSTEAD OF trigger are not lockable")));
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index e93552a..da7fce0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2236,20 +2236,23 @@ view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle)
 
 
 /*
- * view_query_is_auto_updatable - test whether the specified view definition
- * represents an auto-updatable view. Returns NULL (if the view can be updated)
- * or a message string giving the reason that it cannot be.
+ * view_query_is_auto_updatable_or_lockable - test whether the specified view
+ * definition represents an auto-updatable view. Returns NULL (if the view can
+ * be updated) or a message string giving the reason that it cannot be.
  *
  * If check_cols is true, the view is required to have at least one updatable
  * column (necessary for INSERT/UPDATE). Otherwise the view's columns are not
  * checked for updatability. See also view_cols_are_auto_updatable.
  *
+ * If for_lock is true, the returned message describes the reason that it
+ * cannot be lockable rather than auto-updatable.
+ *
  * Note that the checks performed here are only based on the view definition.
  * We do not check whether any base relations referred to by the view are
  * updatable.
  */
-const char *
-view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+static const char *
+view_query_is_auto_updatable_or_lockable(Query *viewquery, bool check_cols, bool for_lock)
 {
 	RangeTblRef *rtr;
 	RangeTblEntry *base_rte;
@@ -2287,22 +2290,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 *----------
 	 */
 	if (viewquery->distinctClause != NIL)
-		return gettext_noop("Views containing DISTINCT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing DISTINCT are not automatically updatable.") :
+			gettext_noop("Views containing DISTINCT are not lockable."));
 
 	if (viewquery->groupClause != NIL || viewquery->groupingSets)
-		return gettext_noop("Views containing GROUP BY are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing GROUP BY are not automatically updatable.") :
+			gettext_noop("Views containing GROUP BY are not lockable."));
 
 	if (viewquery->havingQual != NULL)
-		return gettext_noop("Views containing HAVING are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing HAVING are not automatically updatable.") :
+			gettext_noop("Views containing HAVING are not lockable."));
 
 	if (viewquery->setOperations != NULL)
-		return gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.") :
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not lockable."));
 
 	if (viewquery->cteList != NIL)
-		return gettext_noop("Views containing WITH are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing WITH are not automatically updatable.") :
+			gettext_noop("Views containing WITH are not lockable."));
 
 	if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL)
-		return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.") :
+			gettext_noop("Views containing LIMIT or OFFSET are not lockable."));
 
 	/*
 	 * We must not allow window functions or set returning functions in the
@@ -2314,24 +2329,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 * unique row in the underlying base relation.
 	 */
 	if (viewquery->hasAggs)
-		return gettext_noop("Views that return aggregate functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return aggregate functions are not automatically updatable.") :
+			gettext_noop("Views that return aggregate functions are not lockable."));
 
 	if (viewquery->hasWindowFuncs)
-		return gettext_noop("Views that return window functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return window functions are not automatically updatable.") :
+			gettext_noop("Views that return window functions are not lockable."));
 
 	if (viewquery->hasTargetSRFs)
-		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return set-returning functions are not automatically updatable.") :
+			gettext_noop("Views that return set-returning functions are not lockable."));
 
 	/*
 	 * The view query should select from a single base relation, which must be
 	 * a table or another view.
 	 */
 	if (list_length(viewquery->jointree->fromlist) != 1)
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
 	if (!IsA(rtr, RangeTblRef))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
 	if (base_rte->rtekind != RTE_RELATION ||
@@ -2339,10 +2364,14 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
 		 base_rte->relkind != RELKIND_VIEW &&
 		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	if (base_rte->tablesample)
-		return gettext_noop("Views containing TABLESAMPLE are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing TABLESAMPLE are not automatically updatable.") :
+			gettext_noop("Views containing TABLESAMPLE are not lockable."));
 
 	/*
 	 * Check that the view has at least one updatable column. This is required
@@ -2374,6 +2403,29 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 
 
 /*
+ * view_query_is_auto_updatable - a simple wrapper function of
+ * view_query_is_auto_updatable_or_lockable
+ */
+const char *
+view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, check_cols, false);
+}
+
+/*
+ * view_query_is_lockable - a wrapper function of
+ * view_query_is_auto_updatable_or_lockable to check whether a given view is
+ * lockable. Returns NULL (if the view can be locked) or a message string
+ * giving the reason that it cannot be.
+ */
+const char *
+view_query_is_lockable(Query *viewquery)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, false, true);
+}
+
+
+/*
  * view_cols_are_auto_updatable - test whether all of the required columns of
  * an auto-updatable view are actually updatable. Returns NULL (if all the
  * required columns can be updated) or a message string giving the reason that
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 86ae571..edd6693 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -29,6 +29,7 @@ extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 							 bool check_cols);
+extern const char *view_query_is_lockable(Query *viewquery);
 extern int relation_is_updatable(Oid reloid,
 					  bool include_triggers,
 					  Bitmapset *include_cols);
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index fd27344..8c1f587 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,9 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +32,17 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an autoupdatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-autoupdatable view
+ERROR:  cannot lock view "lock_view2"
+DETAIL:  Views that return aggregate functions are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-autoupdatable view
+ERROR:  cannot lock view "lock_view3"
+DETAIL:  Views that do not select from a single table or view are not lockable.
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -55,6 +66,8 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..d8e97d7 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,9 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +35,13 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an autoupdatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-autoupdatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-autoupdatable view
 ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
@@ -59,6 +67,8 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
#19Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#18)
Re: [HACKERS] [PATCH] Lockable views

I didn't want to change the interface of view_query_is_auto_updatable()
because this might be called from other third-patry software, so I renamed
this function to view_query_is_auto_updatable_or_lockable() and added the flag
to this. I created view_query_is_auto_updatable() as a wrapper of this function.
I also made view_query_is_lockable() that returns a other message than
view_query_is_auto_updatable().

On Tue, 17 Oct 2017 11:59:05 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

1) Leave as it is (ignore tables appearing in a subquery)

2) Lock all tables including in a subquery

3) Check subquery in the view

So it seem #1 is the most reasonable way to deal with the problem
assuming that it's user's responsibility to take appropriate locks on
the tables in the subquery.

I adopted #1 and I didn't change anything about this.

Looks good to me except that the patch lacks documents and the
regression test needs more cases. For example, it needs a test for the
case #1 above: probably using pg_locks to make sure that the tables
appearing in the subquery do not hold locks.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#20Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#19)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Thu, 28 Dec 2017 09:29:11 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

I didn't want to change the interface of view_query_is_auto_updatable()
because this might be called from other third-patry software, so I renamed
this function to view_query_is_auto_updatable_or_lockable() and added the flag
to this. I created view_query_is_auto_updatable() as a wrapper of this function.
I also made view_query_is_lockable() that returns a other message than
view_query_is_auto_updatable().

On Tue, 17 Oct 2017 11:59:05 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

1) Leave as it is (ignore tables appearing in a subquery)

2) Lock all tables including in a subquery

3) Check subquery in the view

So it seem #1 is the most reasonable way to deal with the problem
assuming that it's user's responsibility to take appropriate locks on
the tables in the subquery.

I adopted #1 and I didn't change anything about this.

Looks good to me except that the patch lacks documents and the
regression test needs more cases. For example, it needs a test for the
case #1 above: probably using pg_locks to make sure that the tables
appearing in the subquery do not hold locks.

Attached is the update patch, v3. I add some regression test and
the documentation.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view-v3.patchtext/x-diff; name=lock_view-v3.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..986ae71 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,13 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
   </para>
 
   <para>
+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>
+   rules are also lockable. When a view is locked, its base relations are
+   also locked recursively with the same lock mode.
+  </para>
+
+  <para>
    When acquiring locks automatically for commands that reference
    tables, <productname>PostgreSQL</productname> always uses the least
    restrictive lock mode possible. <command>LOCK TABLE</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 9fe9e02..80e00f7 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -19,15 +19,20 @@
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
+#include "parser/parsetree.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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 LockViewCheck(Relation view, Query *viewquery);
 
 /*
  * LOCK TABLE
@@ -62,8 +67,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +93,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, ACL_KIND_CLASS, rv->relname);
 }
@@ -107,7 +116,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +129,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +166,82 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ */
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	Relation		 view;
+	Query			*viewquery;
+	RangeTblRef		*rtr;
+	RangeTblEntry	*base_rte;
+	Oid				 baseoid;
+	AclResult		 aclresult;
+	char			*relname;
+	char			 relkind;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+
+	/* Check whether the view is lockable */
+	LockViewCheck(view, viewquery);
+
+	heap_close(view, NoLock);
+
+	Assert(list_length(viewquery->jointree->fromlist) == 1);
+	rtr = linitial_node(RangeTblRef, viewquery->jointree->fromlist);
+	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+	Assert(base_rte->rtekind == RTE_RELATION);
+
+	baseoid = base_rte->relid;
+	relname = get_rel_name(baseoid);
+	relkind = get_rel_relkind(baseoid);
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table or view",
+						relname)));
+
+	/* Check infinite recursion in the view definition */
+	if (baseoid == root_reloid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("infinite recursion detected in rules for relation \"%s\"",
+						get_rel_name(root_reloid))));
+
+	/* Check permissions with the view owner's priviledge. */
+	aclresult = LockTableAclCheck(baseoid, lockmode, view->rd_rel->relowner);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_CLASS, relname);
+
+	/* We have enough rights to lock the relation; do so. */
+	if (!nowait)
+		LockRelationOid(baseoid, lockmode);
+	else if (!ConditionalLockRelationOid(baseoid, lockmode))
+		ereport(ERROR,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+				 errmsg("could not obtain lock on relation \"%s\"",
+						relname)));
+
+	if (relkind == RELKIND_VIEW)
+		LockViewRecurse(baseoid, root_reloid, lockmode, nowait);
+	else if (base_rte->inh)
+		LockTableRecurse(baseoid, lockmode, nowait, view->rd_rel->relowner);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +254,50 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
+
+/*
+ * Check whether the view is lockable.
+ *
+ * Currently, only auto-updatable views can be locked, that is,
+ * views whose definition are simple and that doesn't have
+ * instead of rules or triggers are lockable.
+ */
+static void
+LockViewCheck(Relation view, Query *viewquery)
+{
+	const char	*relname = RelationGetRelationName(view);
+	const char	*lockable_detail;
+	int			 i;
+
+	lockable_detail = view_query_is_lockable(viewquery);
+	if (lockable_detail)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("%s", _(lockable_detail))));
+
+
+	/* Confirm the view doesn't have instead of rules */
+	for (i=0; i<view->rd_rules->numLocks; i++)
+	{
+		if (view->rd_rules->rules[i]->isInstead && view->rd_rules->rules[i]->event != CMD_SELECT)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("views that have an INSTEAD OF rule are not lockable")));
+	}
+
+	/* Confirm the view doesn't have instead of triggers */
+	if (view->trigdesc &&
+		(view->trigdesc->trig_insert_instead_row ||
+		 view->trigdesc->trig_update_instead_row ||
+		 view->trigdesc->trig_delete_instead_row))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot lock view \"%s\"", relname),
+				 errdetail_internal("views that have an INSTEAD OF trigger are not lockable")));
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index e93552a..da7fce0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2236,20 +2236,23 @@ view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle)
 
 
 /*
- * view_query_is_auto_updatable - test whether the specified view definition
- * represents an auto-updatable view. Returns NULL (if the view can be updated)
- * or a message string giving the reason that it cannot be.
+ * view_query_is_auto_updatable_or_lockable - test whether the specified view
+ * definition represents an auto-updatable view. Returns NULL (if the view can
+ * be updated) or a message string giving the reason that it cannot be.
  *
  * If check_cols is true, the view is required to have at least one updatable
  * column (necessary for INSERT/UPDATE). Otherwise the view's columns are not
  * checked for updatability. See also view_cols_are_auto_updatable.
  *
+ * If for_lock is true, the returned message describes the reason that it
+ * cannot be lockable rather than auto-updatable.
+ *
  * Note that the checks performed here are only based on the view definition.
  * We do not check whether any base relations referred to by the view are
  * updatable.
  */
-const char *
-view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+static const char *
+view_query_is_auto_updatable_or_lockable(Query *viewquery, bool check_cols, bool for_lock)
 {
 	RangeTblRef *rtr;
 	RangeTblEntry *base_rte;
@@ -2287,22 +2290,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 *----------
 	 */
 	if (viewquery->distinctClause != NIL)
-		return gettext_noop("Views containing DISTINCT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing DISTINCT are not automatically updatable.") :
+			gettext_noop("Views containing DISTINCT are not lockable."));
 
 	if (viewquery->groupClause != NIL || viewquery->groupingSets)
-		return gettext_noop("Views containing GROUP BY are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing GROUP BY are not automatically updatable.") :
+			gettext_noop("Views containing GROUP BY are not lockable."));
 
 	if (viewquery->havingQual != NULL)
-		return gettext_noop("Views containing HAVING are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing HAVING are not automatically updatable.") :
+			gettext_noop("Views containing HAVING are not lockable."));
 
 	if (viewquery->setOperations != NULL)
-		return gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.") :
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not lockable."));
 
 	if (viewquery->cteList != NIL)
-		return gettext_noop("Views containing WITH are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing WITH are not automatically updatable.") :
+			gettext_noop("Views containing WITH are not lockable."));
 
 	if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL)
-		return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.") :
+			gettext_noop("Views containing LIMIT or OFFSET are not lockable."));
 
 	/*
 	 * We must not allow window functions or set returning functions in the
@@ -2314,24 +2329,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 * unique row in the underlying base relation.
 	 */
 	if (viewquery->hasAggs)
-		return gettext_noop("Views that return aggregate functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return aggregate functions are not automatically updatable.") :
+			gettext_noop("Views that return aggregate functions are not lockable."));
 
 	if (viewquery->hasWindowFuncs)
-		return gettext_noop("Views that return window functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return window functions are not automatically updatable.") :
+			gettext_noop("Views that return window functions are not lockable."));
 
 	if (viewquery->hasTargetSRFs)
-		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return set-returning functions are not automatically updatable.") :
+			gettext_noop("Views that return set-returning functions are not lockable."));
 
 	/*
 	 * The view query should select from a single base relation, which must be
 	 * a table or another view.
 	 */
 	if (list_length(viewquery->jointree->fromlist) != 1)
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
 	if (!IsA(rtr, RangeTblRef))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
 	if (base_rte->rtekind != RTE_RELATION ||
@@ -2339,10 +2364,14 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
 		 base_rte->relkind != RELKIND_VIEW &&
 		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	if (base_rte->tablesample)
-		return gettext_noop("Views containing TABLESAMPLE are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing TABLESAMPLE are not automatically updatable.") :
+			gettext_noop("Views containing TABLESAMPLE are not lockable."));
 
 	/*
 	 * Check that the view has at least one updatable column. This is required
@@ -2374,6 +2403,29 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 
 
 /*
+ * view_query_is_auto_updatable - a simple wrapper function of
+ * view_query_is_auto_updatable_or_lockable
+ */
+const char *
+view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, check_cols, false);
+}
+
+/*
+ * view_query_is_lockable - a wrapper function of
+ * view_query_is_auto_updatable_or_lockable to check whether a given view is
+ * lockable. Returns NULL (if the view can be locked) or a message string
+ * giving the reason that it cannot be.
+ */
+const char *
+view_query_is_lockable(Query *viewquery)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, false, true);
+}
+
+
+/*
  * view_cols_are_auto_updatable - test whether all of the required columns of
  * an auto-updatable view are actually updatable. Returns NULL (if all the
  * required columns can be updated) or a message string giving the reason that
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 86ae571..edd6693 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -29,6 +29,7 @@ extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 							 bool check_cols);
+extern const char *view_query_is_lockable(Query *viewquery);
 extern int relation_is_updatable(Oid reloid,
 					  bool include_triggers,
 					  Bitmapset *include_cols);
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index fd27344..0689c20 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +35,60 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+ROLLBACK;
+-- Verify that we  can lock a auto-updatable views 
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ pg_locks
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view2"
+DETAIL:  Views that return aggregate functions are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view3"
+DETAIL:  Views that do not select from a single table or view are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ lock_view4
+ pg_locks
+(4 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view5
+ pg_locks
+(3 rows)
+
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -55,10 +112,26 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+ERROR:  cannot drop view lock_view1 because other objects depend on it
+DETAIL:  view lock_view4 depends on view lock_view1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+ERROR:  cannot drop table lock_tbl1 because other objects depend on it
+DETAIL:  view lock_view1 depends on table lock_tbl1
+view lock_view4 depends on view lock_view1
+view lock_view5 depends on table lock_tbl1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 DROP SCHEMA lock_schema1 CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table lock_tbl1
+drop cascades to table no_lock_tbl
+drop cascades to view lock_view1
+drop cascades to view lock_view4
+drop cascades to view lock_view5
 DROP ROLE regress_rol_lock1;
 -- atomic ops tests
 RESET search_path;
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..caaa5f6 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +38,35 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+ROLLBACK;
+
+-- Verify that we  can lock a auto-updatable views 
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
 ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
@@ -59,6 +92,8 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
#21Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#20)
Re: [HACKERS] [PATCH] Lockable views

1) Leave as it is (ignore tables appearing in a subquery)

2) Lock all tables including in a subquery

3) Check subquery in the view

So it seem #1 is the most reasonable way to deal with the problem
assuming that it's user's responsibility to take appropriate locks on
the tables in the subquery.

I adopted #1 and I didn't change anything about this.

Looks good to me except that the patch lacks documents and the
regression test needs more cases. For example, it needs a test for the
case #1 above: probably using pg_locks to make sure that the tables
appearing in the subquery do not hold locks.

Attached is the update patch, v3. I add some regression test and
the documentation.

The patch produces a warning.

/home/t-ishii/lock_view-v3.patch:542: trailing whitespace.
-- Verify that we can lock a auto-updatable views
warning: 1 line adds whitespace errors.

Your addition to the doc:
+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>
+   rules are also lockable. When a view is locked, its base relations are
+   also locked recursively with the same lock mode.

does not mention about the point:

1) Leave as it is (ignore tables appearing in a subquery)

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#22Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#21)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

Hi,

The updated patch is attached.

On Fri, 29 Dec 2017 23:39:39 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

The patch produces a warning.

/home/t-ishii/lock_view-v3.patch:542: trailing whitespace.
-- Verify that we can lock a auto-updatable views
warning: 1 line adds whitespace errors.

Fixed.

Your addition to the doc:
+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>
+   rules are also lockable. When a view is locked, its base relations are
+   also locked recursively with the same lock mode.

does not mention about the point:

1) Leave as it is (ignore tables appearing in a subquery)

I added this point to the documentation.

Regards,

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view-v4.patchtext/x-diff; name=lock_view-v4.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..c2ed481 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,14 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
   </para>
 
   <para>
+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>
+   rules are also lockable. When a view is locked, its base relations are
+   also locked recursively with the same lock mode. Tables appearing in a
+   subquery are ignored and not locked.
+  </para>
+
+  <para>
    When acquiring locks automatically for commands that reference
    tables, <productname>PostgreSQL</productname> always uses the least
    restrictive lock mode possible. <command>LOCK TABLE</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 9fe9e02..80e00f7 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -19,15 +19,20 @@
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
+#include "parser/parsetree.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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 LockViewCheck(Relation view, Query *viewquery);
 
 /*
  * LOCK TABLE
@@ -62,8 +67,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +93,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, ACL_KIND_CLASS, rv->relname);
 }
@@ -107,7 +116,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +129,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +166,82 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ */
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	Relation		 view;
+	Query			*viewquery;
+	RangeTblRef		*rtr;
+	RangeTblEntry	*base_rte;
+	Oid				 baseoid;
+	AclResult		 aclresult;
+	char			*relname;
+	char			 relkind;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+
+	/* Check whether the view is lockable */
+	LockViewCheck(view, viewquery);
+
+	heap_close(view, NoLock);
+
+	Assert(list_length(viewquery->jointree->fromlist) == 1);
+	rtr = linitial_node(RangeTblRef, viewquery->jointree->fromlist);
+	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+	Assert(base_rte->rtekind == RTE_RELATION);
+
+	baseoid = base_rte->relid;
+	relname = get_rel_name(baseoid);
+	relkind = get_rel_relkind(baseoid);
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table or view",
+						relname)));
+
+	/* Check infinite recursion in the view definition */
+	if (baseoid == root_reloid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("infinite recursion detected in rules for relation \"%s\"",
+						get_rel_name(root_reloid))));
+
+	/* Check permissions with the view owner's priviledge. */
+	aclresult = LockTableAclCheck(baseoid, lockmode, view->rd_rel->relowner);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_CLASS, relname);
+
+	/* We have enough rights to lock the relation; do so. */
+	if (!nowait)
+		LockRelationOid(baseoid, lockmode);
+	else if (!ConditionalLockRelationOid(baseoid, lockmode))
+		ereport(ERROR,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+				 errmsg("could not obtain lock on relation \"%s\"",
+						relname)));
+
+	if (relkind == RELKIND_VIEW)
+		LockViewRecurse(baseoid, root_reloid, lockmode, nowait);
+	else if (base_rte->inh)
+		LockTableRecurse(baseoid, lockmode, nowait, view->rd_rel->relowner);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +254,50 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
+
+/*
+ * Check whether the view is lockable.
+ *
+ * Currently, only auto-updatable views can be locked, that is,
+ * views whose definition are simple and that doesn't have
+ * instead of rules or triggers are lockable.
+ */
+static void
+LockViewCheck(Relation view, Query *viewquery)
+{
+	const char	*relname = RelationGetRelationName(view);
+	const char	*lockable_detail;
+	int			 i;
+
+	lockable_detail = view_query_is_lockable(viewquery);
+	if (lockable_detail)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("%s", _(lockable_detail))));
+
+
+	/* Confirm the view doesn't have instead of rules */
+	for (i=0; i<view->rd_rules->numLocks; i++)
+	{
+		if (view->rd_rules->rules[i]->isInstead && view->rd_rules->rules[i]->event != CMD_SELECT)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("views that have an INSTEAD OF rule are not lockable")));
+	}
+
+	/* Confirm the view doesn't have instead of triggers */
+	if (view->trigdesc &&
+		(view->trigdesc->trig_insert_instead_row ||
+		 view->trigdesc->trig_update_instead_row ||
+		 view->trigdesc->trig_delete_instead_row))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot lock view \"%s\"", relname),
+				 errdetail_internal("views that have an INSTEAD OF trigger are not lockable")));
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index e93552a..da7fce0 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2236,20 +2236,23 @@ view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle)
 
 
 /*
- * view_query_is_auto_updatable - test whether the specified view definition
- * represents an auto-updatable view. Returns NULL (if the view can be updated)
- * or a message string giving the reason that it cannot be.
+ * view_query_is_auto_updatable_or_lockable - test whether the specified view
+ * definition represents an auto-updatable view. Returns NULL (if the view can
+ * be updated) or a message string giving the reason that it cannot be.
  *
  * If check_cols is true, the view is required to have at least one updatable
  * column (necessary for INSERT/UPDATE). Otherwise the view's columns are not
  * checked for updatability. See also view_cols_are_auto_updatable.
  *
+ * If for_lock is true, the returned message describes the reason that it
+ * cannot be lockable rather than auto-updatable.
+ *
  * Note that the checks performed here are only based on the view definition.
  * We do not check whether any base relations referred to by the view are
  * updatable.
  */
-const char *
-view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+static const char *
+view_query_is_auto_updatable_or_lockable(Query *viewquery, bool check_cols, bool for_lock)
 {
 	RangeTblRef *rtr;
 	RangeTblEntry *base_rte;
@@ -2287,22 +2290,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 *----------
 	 */
 	if (viewquery->distinctClause != NIL)
-		return gettext_noop("Views containing DISTINCT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing DISTINCT are not automatically updatable.") :
+			gettext_noop("Views containing DISTINCT are not lockable."));
 
 	if (viewquery->groupClause != NIL || viewquery->groupingSets)
-		return gettext_noop("Views containing GROUP BY are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing GROUP BY are not automatically updatable.") :
+			gettext_noop("Views containing GROUP BY are not lockable."));
 
 	if (viewquery->havingQual != NULL)
-		return gettext_noop("Views containing HAVING are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing HAVING are not automatically updatable.") :
+			gettext_noop("Views containing HAVING are not lockable."));
 
 	if (viewquery->setOperations != NULL)
-		return gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.") :
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not lockable."));
 
 	if (viewquery->cteList != NIL)
-		return gettext_noop("Views containing WITH are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing WITH are not automatically updatable.") :
+			gettext_noop("Views containing WITH are not lockable."));
 
 	if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL)
-		return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.") :
+			gettext_noop("Views containing LIMIT or OFFSET are not lockable."));
 
 	/*
 	 * We must not allow window functions or set returning functions in the
@@ -2314,24 +2329,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 * unique row in the underlying base relation.
 	 */
 	if (viewquery->hasAggs)
-		return gettext_noop("Views that return aggregate functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return aggregate functions are not automatically updatable.") :
+			gettext_noop("Views that return aggregate functions are not lockable."));
 
 	if (viewquery->hasWindowFuncs)
-		return gettext_noop("Views that return window functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return window functions are not automatically updatable.") :
+			gettext_noop("Views that return window functions are not lockable."));
 
 	if (viewquery->hasTargetSRFs)
-		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return set-returning functions are not automatically updatable.") :
+			gettext_noop("Views that return set-returning functions are not lockable."));
 
 	/*
 	 * The view query should select from a single base relation, which must be
 	 * a table or another view.
 	 */
 	if (list_length(viewquery->jointree->fromlist) != 1)
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
 	if (!IsA(rtr, RangeTblRef))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
 	if (base_rte->rtekind != RTE_RELATION ||
@@ -2339,10 +2364,14 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
 		 base_rte->relkind != RELKIND_VIEW &&
 		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	if (base_rte->tablesample)
-		return gettext_noop("Views containing TABLESAMPLE are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing TABLESAMPLE are not automatically updatable.") :
+			gettext_noop("Views containing TABLESAMPLE are not lockable."));
 
 	/*
 	 * Check that the view has at least one updatable column. This is required
@@ -2374,6 +2403,29 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 
 
 /*
+ * view_query_is_auto_updatable - a simple wrapper function of
+ * view_query_is_auto_updatable_or_lockable
+ */
+const char *
+view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, check_cols, false);
+}
+
+/*
+ * view_query_is_lockable - a wrapper function of
+ * view_query_is_auto_updatable_or_lockable to check whether a given view is
+ * lockable. Returns NULL (if the view can be locked) or a message string
+ * giving the reason that it cannot be.
+ */
+const char *
+view_query_is_lockable(Query *viewquery)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, false, true);
+}
+
+
+/*
  * view_cols_are_auto_updatable - test whether all of the required columns of
  * an auto-updatable view are actually updatable. Returns NULL (if all the
  * required columns can be updated) or a message string giving the reason that
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 86ae571..edd6693 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -29,6 +29,7 @@ extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 							 bool check_cols);
+extern const char *view_query_is_lockable(Query *viewquery);
 extern int relation_is_updatable(Oid reloid,
 					  bool include_triggers,
 					  Bitmapset *include_cols);
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index fd27344..a46212f 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +35,60 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+ROLLBACK;
+-- Verify that we can lock a auto-updatable views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ pg_locks
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view2"
+DETAIL:  Views that return aggregate functions are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view3"
+DETAIL:  Views that do not select from a single table or view are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ lock_view4
+ pg_locks
+(4 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view5
+ pg_locks
+(3 rows)
+
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -55,10 +112,26 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+ERROR:  cannot drop view lock_view1 because other objects depend on it
+DETAIL:  view lock_view4 depends on view lock_view1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+ERROR:  cannot drop table lock_tbl1 because other objects depend on it
+DETAIL:  view lock_view1 depends on table lock_tbl1
+view lock_view4 depends on view lock_view1
+view lock_view5 depends on table lock_tbl1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 DROP SCHEMA lock_schema1 CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table lock_tbl1
+drop cascades to table no_lock_tbl
+drop cascades to view lock_view1
+drop cascades to view lock_view4
+drop cascades to view lock_view5
 DROP ROLE regress_rol_lock1;
 -- atomic ops tests
 RESET search_path;
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..909212b 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +38,35 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+ROLLBACK;
+
+-- Verify that we can lock a auto-updatable views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
 ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
@@ -59,6 +92,8 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
#23Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Yugo Nagata (#22)
Re: [HACKERS] [PATCH] Lockable views

On Sun, Dec 31, 2017 at 11:57 PM, Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Fri, 29 Dec 2017 23:39:39 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Your addition to the doc:
+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>
+   rules are also lockable. When a view is locked, its base relations are
+   also locked recursively with the same lock mode.

I added this point to the documentation.

+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>

Tthe documentation doesn't build: you now need to say </literal>
instead of </>, and you need to say <xref ... />.

--
Thomas Munro
http://www.enterprisedb.com

#24Yugo Nagata
nagata@sraoss.co.jp
In reply to: Thomas Munro (#23)
Re: [HACKERS] [PATCH] Lockable views

On Thu, 25 Jan 2018 20:51:41 +1300
Thomas Munro <thomas.munro@enterprisedb.com> wrote:

On Sun, Dec 31, 2017 at 11:57 PM, Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Fri, 29 Dec 2017 23:39:39 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Your addition to the doc:
+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>
+   rules are also lockable. When a view is locked, its base relations are
+   also locked recursively with the same lock mode.

I added this point to the documentation.

+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>

Thank you for pointing out this.

Also, I've noticed this patch cannot be applied to the HEAD, so I'll fix
them together and update the patch.

Tthe documentation doesn't build: you now need to say </literal>
instead of </>, and you need to say <xref ... />.

--
Thomas Munro
http://www.enterprisedb.com

--
Yugo Nagata <nagata@sraoss.co.jp>

#25Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#24)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Fri, 26 Jan 2018 21:30:49 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Thu, 25 Jan 2018 20:51:41 +1300
Thomas Munro <thomas.munro@enterprisedb.com> wrote:

On Sun, Dec 31, 2017 at 11:57 PM, Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Fri, 29 Dec 2017 23:39:39 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Your addition to the doc:
+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>
+   rules are also lockable. When a view is locked, its base relations are
+   also locked recursively with the same lock mode.

I added this point to the documentation.

+   Automatically updatable views (see <xref linkend="sql-createview">)
+   that do not have <literal>INSTEAD OF</> triggers or <literal>INSTEAD</>

Thank you for pointing out this.

Also, I've noticed this patch cannot be applied to the HEAD, so I'll fix
them together and update the patch.

Attached is the updated patch v5 including fixing SGML and rebase to HEAD.

Regards,

Tthe documentation doesn't build: you now need to say </literal>
instead of </>, and you need to say <xref ... />.

--
Thomas Munro
http://www.enterprisedb.com

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view-v5.patchtext/x-diff; name=lock_view-v5.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..6ddd128 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,14 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
   </para>
 
   <para>
+   Automatically updatable views (see <xref linkend="sql-createview"/>)
+   that do not have <literal>INSTEAD OF</literal> triggers or
+   <literal>INSTEAD</literal> rules are also lockable. When a view is
+   locked, its base relations are also locked recursively with the same
+   lock mode. Tables appearing in a subquery are ignored and not locked.
+  </para>
+
+  <para>
    When acquiring locks automatically for commands that reference
    tables, <productname>PostgreSQL</productname> always uses the least
    restrictive lock mode possible. <command>LOCK TABLE</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 6479dcb..7ae3a84 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -19,15 +19,20 @@
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
+#include "parser/parsetree.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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 LockViewCheck(Relation view, Query *viewquery);
 
 /*
  * LOCK TABLE
@@ -62,8 +67,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +93,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
 }
@@ -107,7 +116,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +129,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +166,82 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ */
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	Relation		 view;
+	Query			*viewquery;
+	RangeTblRef		*rtr;
+	RangeTblEntry	*base_rte;
+	Oid				 baseoid;
+	AclResult		 aclresult;
+	char			*relname;
+	char			 relkind;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+
+	/* Check whether the view is lockable */
+	LockViewCheck(view, viewquery);
+
+	heap_close(view, NoLock);
+
+	Assert(list_length(viewquery->jointree->fromlist) == 1);
+	rtr = linitial_node(RangeTblRef, viewquery->jointree->fromlist);
+	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+	Assert(base_rte->rtekind == RTE_RELATION);
+
+	baseoid = base_rte->relid;
+	relname = get_rel_name(baseoid);
+	relkind = get_rel_relkind(baseoid);
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table or view",
+						relname)));
+
+	/* Check infinite recursion in the view definition */
+	if (baseoid == root_reloid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("infinite recursion detected in rules for relation \"%s\"",
+						get_rel_name(root_reloid))));
+
+	/* Check permissions with the view owner's priviledge. */
+	aclresult = LockTableAclCheck(baseoid, lockmode, view->rd_rel->relowner);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(baseoid)), relname);
+
+	/* We have enough rights to lock the relation; do so. */
+	if (!nowait)
+		LockRelationOid(baseoid, lockmode);
+	else if (!ConditionalLockRelationOid(baseoid, lockmode))
+		ereport(ERROR,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+				 errmsg("could not obtain lock on relation \"%s\"",
+						relname)));
+
+	if (relkind == RELKIND_VIEW)
+		LockViewRecurse(baseoid, root_reloid, lockmode, nowait);
+	else if (base_rte->inh)
+		LockTableRecurse(baseoid, lockmode, nowait, view->rd_rel->relowner);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +254,50 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
+
+/*
+ * Check whether the view is lockable.
+ *
+ * Currently, only auto-updatable views can be locked, that is,
+ * views whose definition are simple and that doesn't have
+ * instead of rules or triggers are lockable.
+ */
+static void
+LockViewCheck(Relation view, Query *viewquery)
+{
+	const char	*relname = RelationGetRelationName(view);
+	const char	*lockable_detail;
+	int			 i;
+
+	lockable_detail = view_query_is_lockable(viewquery);
+	if (lockable_detail)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("%s", _(lockable_detail))));
+
+
+	/* Confirm the view doesn't have instead of rules */
+	for (i=0; i<view->rd_rules->numLocks; i++)
+	{
+		if (view->rd_rules->rules[i]->isInstead && view->rd_rules->rules[i]->event != CMD_SELECT)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("views that have an INSTEAD OF rule are not lockable")));
+	}
+
+	/* Confirm the view doesn't have instead of triggers */
+	if (view->trigdesc &&
+		(view->trigdesc->trig_insert_instead_row ||
+		 view->trigdesc->trig_update_instead_row ||
+		 view->trigdesc->trig_delete_instead_row))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot lock view \"%s\"", relname),
+				 errdetail_internal("views that have an INSTEAD OF trigger are not lockable")));
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 32e3798..f145a62 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2236,20 +2236,23 @@ view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle)
 
 
 /*
- * view_query_is_auto_updatable - test whether the specified view definition
- * represents an auto-updatable view. Returns NULL (if the view can be updated)
- * or a message string giving the reason that it cannot be.
+ * view_query_is_auto_updatable_or_lockable - test whether the specified view
+ * definition represents an auto-updatable view. Returns NULL (if the view can
+ * be updated) or a message string giving the reason that it cannot be.
  *
  * If check_cols is true, the view is required to have at least one updatable
  * column (necessary for INSERT/UPDATE). Otherwise the view's columns are not
  * checked for updatability. See also view_cols_are_auto_updatable.
  *
+ * If for_lock is true, the returned message describes the reason that it
+ * cannot be lockable rather than auto-updatable.
+ *
  * Note that the checks performed here are only based on the view definition.
  * We do not check whether any base relations referred to by the view are
  * updatable.
  */
-const char *
-view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+static const char *
+view_query_is_auto_updatable_or_lockable(Query *viewquery, bool check_cols, bool for_lock)
 {
 	RangeTblRef *rtr;
 	RangeTblEntry *base_rte;
@@ -2287,22 +2290,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 *----------
 	 */
 	if (viewquery->distinctClause != NIL)
-		return gettext_noop("Views containing DISTINCT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing DISTINCT are not automatically updatable.") :
+			gettext_noop("Views containing DISTINCT are not lockable."));
 
 	if (viewquery->groupClause != NIL || viewquery->groupingSets)
-		return gettext_noop("Views containing GROUP BY are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing GROUP BY are not automatically updatable.") :
+			gettext_noop("Views containing GROUP BY are not lockable."));
 
 	if (viewquery->havingQual != NULL)
-		return gettext_noop("Views containing HAVING are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing HAVING are not automatically updatable.") :
+			gettext_noop("Views containing HAVING are not lockable."));
 
 	if (viewquery->setOperations != NULL)
-		return gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.") :
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not lockable."));
 
 	if (viewquery->cteList != NIL)
-		return gettext_noop("Views containing WITH are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing WITH are not automatically updatable.") :
+			gettext_noop("Views containing WITH are not lockable."));
 
 	if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL)
-		return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.") :
+			gettext_noop("Views containing LIMIT or OFFSET are not lockable."));
 
 	/*
 	 * We must not allow window functions or set returning functions in the
@@ -2314,24 +2329,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 * unique row in the underlying base relation.
 	 */
 	if (viewquery->hasAggs)
-		return gettext_noop("Views that return aggregate functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return aggregate functions are not automatically updatable.") :
+			gettext_noop("Views that return aggregate functions are not lockable."));
 
 	if (viewquery->hasWindowFuncs)
-		return gettext_noop("Views that return window functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return window functions are not automatically updatable.") :
+			gettext_noop("Views that return window functions are not lockable."));
 
 	if (viewquery->hasTargetSRFs)
-		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return set-returning functions are not automatically updatable.") :
+			gettext_noop("Views that return set-returning functions are not lockable."));
 
 	/*
 	 * The view query should select from a single base relation, which must be
 	 * a table or another view.
 	 */
 	if (list_length(viewquery->jointree->fromlist) != 1)
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
 	if (!IsA(rtr, RangeTblRef))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
 	if (base_rte->rtekind != RTE_RELATION ||
@@ -2339,10 +2364,14 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
 		 base_rte->relkind != RELKIND_VIEW &&
 		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	if (base_rte->tablesample)
-		return gettext_noop("Views containing TABLESAMPLE are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing TABLESAMPLE are not automatically updatable.") :
+			gettext_noop("Views containing TABLESAMPLE are not lockable."));
 
 	/*
 	 * Check that the view has at least one updatable column. This is required
@@ -2374,6 +2403,29 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 
 
 /*
+ * view_query_is_auto_updatable - a simple wrapper function of
+ * view_query_is_auto_updatable_or_lockable
+ */
+const char *
+view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, check_cols, false);
+}
+
+/*
+ * view_query_is_lockable - a wrapper function of
+ * view_query_is_auto_updatable_or_lockable to check whether a given view is
+ * lockable. Returns NULL (if the view can be locked) or a message string
+ * giving the reason that it cannot be.
+ */
+const char *
+view_query_is_lockable(Query *viewquery)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, false, true);
+}
+
+
+/*
  * view_cols_are_auto_updatable - test whether all of the required columns of
  * an auto-updatable view are actually updatable. Returns NULL (if all the
  * required columns can be updated) or a message string giving the reason that
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 8128199..c8e8c29 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -29,6 +29,7 @@ extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 							 bool check_cols);
+extern const char *view_query_is_lockable(Query *viewquery);
 extern int relation_is_updatable(Oid reloid,
 					  bool include_triggers,
 					  Bitmapset *include_cols);
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index 74a434d..e7c900e 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +35,60 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+ROLLBACK;
+-- Verify that we can lock a auto-updatable views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ pg_locks
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view2"
+DETAIL:  Views that return aggregate functions are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view3"
+DETAIL:  Views that do not select from a single table or view are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ lock_view4
+ pg_locks
+(4 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view5
+ pg_locks
+(3 rows)
+
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -55,10 +112,26 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+ERROR:  cannot drop view lock_view1 because other objects depend on it
+DETAIL:  view lock_view4 depends on view lock_view1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+ERROR:  cannot drop table lock_tbl1 because other objects depend on it
+DETAIL:  view lock_view1 depends on table lock_tbl1
+view lock_view4 depends on view lock_view1
+view lock_view5 depends on table lock_tbl1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
 DROP SCHEMA lock_schema1 CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table lock_tbl1
+drop cascades to table no_lock_tbl
+drop cascades to view lock_view1
+drop cascades to view lock_view4
+drop cascades to view lock_view5
 DROP ROLE regress_rol_lock1;
 -- atomic ops tests
 RESET search_path;
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..909212b 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +38,35 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+ROLLBACK;
+
+-- Verify that we can lock a auto-updatable views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
 ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
@@ -59,6 +92,8 @@ RESET ROLE;
 -- Clean up
 --
 DROP VIEW lock_view1;
+DROP VIEW lock_view2;
+DROP VIEW lock_view3;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
#26Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#25)
Re: [HACKERS] [PATCH] Lockable views

Attached is the updated patch v5 including fixing SGML and rebase to HEAD.

You need to DROP VIEW lock_view4 and lock_view5 in the regression
test as well.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#27Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#26)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Tue, 30 Jan 2018 13:58:30 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Attached is the updated patch v5 including fixing SGML and rebase to HEAD.

You need to DROP VIEW lock_view4 and lock_view5 in the regression
test as well.

Thank you for reviewing the patch.

I fixed this and attached a updated patch v6.

Regards,

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view-v6.patchtext/x-diff; name=lock_view-v6.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..6ddd128 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,14 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
   </para>
 
   <para>
+   Automatically updatable views (see <xref linkend="sql-createview"/>)
+   that do not have <literal>INSTEAD OF</literal> triggers or
+   <literal>INSTEAD</literal> rules are also lockable. When a view is
+   locked, its base relations are also locked recursively with the same
+   lock mode. Tables appearing in a subquery are ignored and not locked.
+  </para>
+
+  <para>
    When acquiring locks automatically for commands that reference
    tables, <productname>PostgreSQL</productname> always uses the least
    restrictive lock mode possible. <command>LOCK TABLE</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 6479dcb..7ae3a84 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -19,15 +19,20 @@
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
+#include "parser/parsetree.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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 LockViewCheck(Relation view, Query *viewquery);
 
 /*
  * LOCK TABLE
@@ -62,8 +67,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +93,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
 }
@@ -107,7 +116,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +129,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +166,82 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ */
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	Relation		 view;
+	Query			*viewquery;
+	RangeTblRef		*rtr;
+	RangeTblEntry	*base_rte;
+	Oid				 baseoid;
+	AclResult		 aclresult;
+	char			*relname;
+	char			 relkind;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+
+	/* Check whether the view is lockable */
+	LockViewCheck(view, viewquery);
+
+	heap_close(view, NoLock);
+
+	Assert(list_length(viewquery->jointree->fromlist) == 1);
+	rtr = linitial_node(RangeTblRef, viewquery->jointree->fromlist);
+	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+	Assert(base_rte->rtekind == RTE_RELATION);
+
+	baseoid = base_rte->relid;
+	relname = get_rel_name(baseoid);
+	relkind = get_rel_relkind(baseoid);
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table or view",
+						relname)));
+
+	/* Check infinite recursion in the view definition */
+	if (baseoid == root_reloid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("infinite recursion detected in rules for relation \"%s\"",
+						get_rel_name(root_reloid))));
+
+	/* Check permissions with the view owner's priviledge. */
+	aclresult = LockTableAclCheck(baseoid, lockmode, view->rd_rel->relowner);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(baseoid)), relname);
+
+	/* We have enough rights to lock the relation; do so. */
+	if (!nowait)
+		LockRelationOid(baseoid, lockmode);
+	else if (!ConditionalLockRelationOid(baseoid, lockmode))
+		ereport(ERROR,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+				 errmsg("could not obtain lock on relation \"%s\"",
+						relname)));
+
+	if (relkind == RELKIND_VIEW)
+		LockViewRecurse(baseoid, root_reloid, lockmode, nowait);
+	else if (base_rte->inh)
+		LockTableRecurse(baseoid, lockmode, nowait, view->rd_rel->relowner);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +254,50 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
+
+/*
+ * Check whether the view is lockable.
+ *
+ * Currently, only auto-updatable views can be locked, that is,
+ * views whose definition are simple and that doesn't have
+ * instead of rules or triggers are lockable.
+ */
+static void
+LockViewCheck(Relation view, Query *viewquery)
+{
+	const char	*relname = RelationGetRelationName(view);
+	const char	*lockable_detail;
+	int			 i;
+
+	lockable_detail = view_query_is_lockable(viewquery);
+	if (lockable_detail)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("%s", _(lockable_detail))));
+
+
+	/* Confirm the view doesn't have instead of rules */
+	for (i=0; i<view->rd_rules->numLocks; i++)
+	{
+		if (view->rd_rules->rules[i]->isInstead && view->rd_rules->rules[i]->event != CMD_SELECT)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("views that have an INSTEAD OF rule are not lockable")));
+	}
+
+	/* Confirm the view doesn't have instead of triggers */
+	if (view->trigdesc &&
+		(view->trigdesc->trig_insert_instead_row ||
+		 view->trigdesc->trig_update_instead_row ||
+		 view->trigdesc->trig_delete_instead_row))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot lock view \"%s\"", relname),
+				 errdetail_internal("views that have an INSTEAD OF trigger are not lockable")));
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 32e3798..f145a62 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2236,20 +2236,23 @@ view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle)
 
 
 /*
- * view_query_is_auto_updatable - test whether the specified view definition
- * represents an auto-updatable view. Returns NULL (if the view can be updated)
- * or a message string giving the reason that it cannot be.
+ * view_query_is_auto_updatable_or_lockable - test whether the specified view
+ * definition represents an auto-updatable view. Returns NULL (if the view can
+ * be updated) or a message string giving the reason that it cannot be.
  *
  * If check_cols is true, the view is required to have at least one updatable
  * column (necessary for INSERT/UPDATE). Otherwise the view's columns are not
  * checked for updatability. See also view_cols_are_auto_updatable.
  *
+ * If for_lock is true, the returned message describes the reason that it
+ * cannot be lockable rather than auto-updatable.
+ *
  * Note that the checks performed here are only based on the view definition.
  * We do not check whether any base relations referred to by the view are
  * updatable.
  */
-const char *
-view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+static const char *
+view_query_is_auto_updatable_or_lockable(Query *viewquery, bool check_cols, bool for_lock)
 {
 	RangeTblRef *rtr;
 	RangeTblEntry *base_rte;
@@ -2287,22 +2290,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 *----------
 	 */
 	if (viewquery->distinctClause != NIL)
-		return gettext_noop("Views containing DISTINCT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing DISTINCT are not automatically updatable.") :
+			gettext_noop("Views containing DISTINCT are not lockable."));
 
 	if (viewquery->groupClause != NIL || viewquery->groupingSets)
-		return gettext_noop("Views containing GROUP BY are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing GROUP BY are not automatically updatable.") :
+			gettext_noop("Views containing GROUP BY are not lockable."));
 
 	if (viewquery->havingQual != NULL)
-		return gettext_noop("Views containing HAVING are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing HAVING are not automatically updatable.") :
+			gettext_noop("Views containing HAVING are not lockable."));
 
 	if (viewquery->setOperations != NULL)
-		return gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.") :
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not lockable."));
 
 	if (viewquery->cteList != NIL)
-		return gettext_noop("Views containing WITH are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing WITH are not automatically updatable.") :
+			gettext_noop("Views containing WITH are not lockable."));
 
 	if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL)
-		return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.") :
+			gettext_noop("Views containing LIMIT or OFFSET are not lockable."));
 
 	/*
 	 * We must not allow window functions or set returning functions in the
@@ -2314,24 +2329,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 * unique row in the underlying base relation.
 	 */
 	if (viewquery->hasAggs)
-		return gettext_noop("Views that return aggregate functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return aggregate functions are not automatically updatable.") :
+			gettext_noop("Views that return aggregate functions are not lockable."));
 
 	if (viewquery->hasWindowFuncs)
-		return gettext_noop("Views that return window functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return window functions are not automatically updatable.") :
+			gettext_noop("Views that return window functions are not lockable."));
 
 	if (viewquery->hasTargetSRFs)
-		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return set-returning functions are not automatically updatable.") :
+			gettext_noop("Views that return set-returning functions are not lockable."));
 
 	/*
 	 * The view query should select from a single base relation, which must be
 	 * a table or another view.
 	 */
 	if (list_length(viewquery->jointree->fromlist) != 1)
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
 	if (!IsA(rtr, RangeTblRef))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
 	if (base_rte->rtekind != RTE_RELATION ||
@@ -2339,10 +2364,14 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
 		 base_rte->relkind != RELKIND_VIEW &&
 		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	if (base_rte->tablesample)
-		return gettext_noop("Views containing TABLESAMPLE are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing TABLESAMPLE are not automatically updatable.") :
+			gettext_noop("Views containing TABLESAMPLE are not lockable."));
 
 	/*
 	 * Check that the view has at least one updatable column. This is required
@@ -2374,6 +2403,29 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 
 
 /*
+ * view_query_is_auto_updatable - a simple wrapper function of
+ * view_query_is_auto_updatable_or_lockable
+ */
+const char *
+view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, check_cols, false);
+}
+
+/*
+ * view_query_is_lockable - a wrapper function of
+ * view_query_is_auto_updatable_or_lockable to check whether a given view is
+ * lockable. Returns NULL (if the view can be locked) or a message string
+ * giving the reason that it cannot be.
+ */
+const char *
+view_query_is_lockable(Query *viewquery)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, false, true);
+}
+
+
+/*
  * view_cols_are_auto_updatable - test whether all of the required columns of
  * an auto-updatable view are actually updatable. Returns NULL (if all the
  * required columns can be updated) or a message string giving the reason that
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 8128199..c8e8c29 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -29,6 +29,7 @@ extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 							 bool check_cols);
+extern const char *view_query_is_lockable(Query *viewquery);
 extern int relation_is_updatable(Oid reloid,
 					  bool include_triggers,
 					  Bitmapset *include_cols);
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index 74a434d..bc62082 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +35,60 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+ROLLBACK;
+-- Verify that we can lock a auto-updatable views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ pg_locks
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view2"
+DETAIL:  Views that return aggregate functions are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view3"
+DETAIL:  Views that do not select from a single table or view are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ lock_view4
+ pg_locks
+(4 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view5
+ pg_locks
+(3 rows)
+
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -54,10 +111,15 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE no_lock_tbl;
 DROP SCHEMA lock_schema1 CASCADE;
 DROP ROLE regress_rol_lock1;
 -- atomic ops tests
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..ad9bc13 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +38,35 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+ROLLBACK;
+
+-- Verify that we can lock a auto-updatable views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
 ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
@@ -58,10 +91,15 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE no_lock_tbl;
 DROP SCHEMA lock_schema1 CASCADE;
 DROP ROLE regress_rol_lock1;
 
#28Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#27)
Re: [HACKERS] [PATCH] Lockable views

You need to DROP VIEW lock_view4 and lock_view5 in the regression
test as well.

Thank you for reviewing the patch.

I fixed this and attached a updated patch v6.

Looks good to me. If there's no objection, especially from Thomas
Munro, I will mark this as "ready for committer".

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#29Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Tatsuo Ishii (#28)
Re: [HACKERS] [PATCH] Lockable views

On Tue, Jan 30, 2018 at 6:48 PM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

You need to DROP VIEW lock_view4 and lock_view5 in the regression
test as well.

Thank you for reviewing the patch.

I fixed this and attached a updated patch v6.

Looks good to me. If there's no objection, especially from Thomas
Munro, I will mark this as "ready for committer".

About the idea: it makes some kind of sense to me that we should lock
the underlying table, in all the same cases that you could do DML on
the view automatically. I wonder if this is a problem for the
soundness: "Tables appearing in a subquery are ignored and not
locked." I can imagine using this for making backwards-compatible
schema changes, in which case the LOCK-based transaction isolation
techniques might benefit from this behaviour. I'd be interested to
hear about the ideal use case you have in mind.

About the patch: I didn't study it in detail. It builds, has
documentation and passes all tests. Would it be a good idea to add an
isolation test to show that the underlying relation is actually
locked?

Typo:

+ /* Check permissions with the view owner's priviledge. */

s/priviledge/privilege/

Grammar:

+/*
+ * Check whether the view is lockable.
+ *
+ * Currently, only auto-updatable views can be locked, that is,
+ * views whose definition are simple and that doesn't have
+ * instead of rules or triggers are lockable.

s/definition are simple and that doesn't/definition is simple and that don't/
s/instead of/INSTEAD OF/ ?

--
Thomas Munro
http://www.enterprisedb.com

#30Yugo Nagata
nagata@sraoss.co.jp
In reply to: Thomas Munro (#29)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Tue, 30 Jan 2018 19:21:04 +1300
Thomas Munro <thomas.munro@enterprisedb.com> wrote:

On Tue, Jan 30, 2018 at 6:48 PM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

You need to DROP VIEW lock_view4 and lock_view5 in the regression
test as well.

Thank you for reviewing the patch.

I fixed this and attached a updated patch v6.

Looks good to me. If there's no objection, especially from Thomas
Munro, I will mark this as "ready for committer".

About the idea: it makes some kind of sense to me that we should lock
the underlying table, in all the same cases that you could do DML on
the view automatically. I wonder if this is a problem for the
soundness: "Tables appearing in a subquery are ignored and not
locked." I can imagine using this for making backwards-compatible
schema changes, in which case the LOCK-based transaction isolation
techniques might benefit from this behaviour. I'd be interested to
hear about the ideal use case you have in mind.

I think the use case is almost similar to that of auto-updatable views.
There are some purpose to use views, for example 1) preventing from
modifying application due to schema changes, 2) protecting the underlying
table from users without proper privileges, 3) making a shorthand of a
query with complex WHERE condition. When these are updatable views and
users need transaction isolation during updating them, I think the lockable
views feature is benefitical because users don't need to refer to the
underlying table. Users might don't know the underlying table, or even
might not have the privilege to lock this.

About the patch: I didn't study it in detail. It builds, has
documentation and passes all tests. Would it be a good idea to add an
isolation test to show that the underlying relation is actually
locked?

Whether the underlying relation is actually locked or not is confirmed
in the regression test using pg_locks, so I don't believe that we need
to add an isolation test.

Typo:

+ /* Check permissions with the view owner's priviledge. */

s/priviledge/privilege/

Grammar:

+/*
+ * Check whether the view is lockable.
+ *
+ * Currently, only auto-updatable views can be locked, that is,
+ * views whose definition are simple and that doesn't have
+ * instead of rules or triggers are lockable.

s/definition are simple and that doesn't/definition is simple and that don't/
s/instead of/INSTEAD OF/ ?

Thank you for pointing out these. I attached the fixed patch.

Regards,

--
Thomas Munro
http://www.enterprisedb.com

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view-v7.patchtext/x-diff; name=lock_view-v7.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..6ddd128 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,14 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
   </para>
 
   <para>
+   Automatically updatable views (see <xref linkend="sql-createview"/>)
+   that do not have <literal>INSTEAD OF</literal> triggers or
+   <literal>INSTEAD</literal> rules are also lockable. When a view is
+   locked, its base relations are also locked recursively with the same
+   lock mode. Tables appearing in a subquery are ignored and not locked.
+  </para>
+
+  <para>
    When acquiring locks automatically for commands that reference
    tables, <productname>PostgreSQL</productname> always uses the least
    restrictive lock mode possible. <command>LOCK TABLE</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 6479dcb..f6b5962 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -19,15 +19,20 @@
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
+#include "parser/parsetree.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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 LockViewCheck(Relation view, Query *viewquery);
 
 /*
  * LOCK TABLE
@@ -62,8 +67,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +93,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
 }
@@ -107,7 +116,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +129,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +166,82 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ */
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	Relation		 view;
+	Query			*viewquery;
+	RangeTblRef		*rtr;
+	RangeTblEntry	*base_rte;
+	Oid				 baseoid;
+	AclResult		 aclresult;
+	char			*relname;
+	char			 relkind;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+
+	/* Check whether the view is lockable */
+	LockViewCheck(view, viewquery);
+
+	heap_close(view, NoLock);
+
+	Assert(list_length(viewquery->jointree->fromlist) == 1);
+	rtr = linitial_node(RangeTblRef, viewquery->jointree->fromlist);
+	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
+	Assert(base_rte->rtekind == RTE_RELATION);
+
+	baseoid = base_rte->relid;
+	relname = get_rel_name(baseoid);
+	relkind = get_rel_relkind(baseoid);
+
+	/* Currently, we only allow plain tables or auto-updatable views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table or view",
+						relname)));
+
+	/* Check infinite recursion in the view definition */
+	if (baseoid == root_reloid)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("infinite recursion detected in rules for relation \"%s\"",
+						get_rel_name(root_reloid))));
+
+	/* Check permissions with the view owner's privilege. */
+	aclresult = LockTableAclCheck(baseoid, lockmode, view->rd_rel->relowner);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(baseoid)), relname);
+
+	/* We have enough rights to lock the relation; do so. */
+	if (!nowait)
+		LockRelationOid(baseoid, lockmode);
+	else if (!ConditionalLockRelationOid(baseoid, lockmode))
+		ereport(ERROR,
+				(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+				 errmsg("could not obtain lock on relation \"%s\"",
+						relname)));
+
+	if (relkind == RELKIND_VIEW)
+		LockViewRecurse(baseoid, root_reloid, lockmode, nowait);
+	else if (base_rte->inh)
+		LockTableRecurse(baseoid, lockmode, nowait, view->rd_rel->relowner);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +254,50 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
+
+/*
+ * Check whether the view is lockable.
+ *
+ * Currently, only auto-updatable views can be locked, that is,
+ * views whose definition are simple and that don't have
+ * INSTEAD OF rules or triggers are lockable.
+ */
+static void
+LockViewCheck(Relation view, Query *viewquery)
+{
+	const char	*relname = RelationGetRelationName(view);
+	const char	*lockable_detail;
+	int			 i;
+
+	lockable_detail = view_query_is_lockable(viewquery);
+	if (lockable_detail)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("%s", _(lockable_detail))));
+
+
+	/* Confirm the view doesn't have INSTEAD OF rules */
+	for (i=0; i<view->rd_rules->numLocks; i++)
+	{
+		if (view->rd_rules->rules[i]->isInstead && view->rd_rules->rules[i]->event != CMD_SELECT)
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("cannot lock view \"%s\"", relname),
+					 errdetail_internal("views that have an INSTEAD OF rule are not lockable")));
+	}
+
+	/* Confirm the view doesn't have INSTEAD OF triggers */
+	if (view->trigdesc &&
+		(view->trigdesc->trig_insert_instead_row ||
+		 view->trigdesc->trig_update_instead_row ||
+		 view->trigdesc->trig_delete_instead_row))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot lock view \"%s\"", relname),
+				 errdetail_internal("views that have an INSTEAD OF trigger are not lockable")));
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 32e3798..f145a62 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2236,20 +2236,23 @@ view_col_is_auto_updatable(RangeTblRef *rtr, TargetEntry *tle)
 
 
 /*
- * view_query_is_auto_updatable - test whether the specified view definition
- * represents an auto-updatable view. Returns NULL (if the view can be updated)
- * or a message string giving the reason that it cannot be.
+ * view_query_is_auto_updatable_or_lockable - test whether the specified view
+ * definition represents an auto-updatable view. Returns NULL (if the view can
+ * be updated) or a message string giving the reason that it cannot be.
  *
  * If check_cols is true, the view is required to have at least one updatable
  * column (necessary for INSERT/UPDATE). Otherwise the view's columns are not
  * checked for updatability. See also view_cols_are_auto_updatable.
  *
+ * If for_lock is true, the returned message describes the reason that it
+ * cannot be lockable rather than auto-updatable.
+ *
  * Note that the checks performed here are only based on the view definition.
  * We do not check whether any base relations referred to by the view are
  * updatable.
  */
-const char *
-view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+static const char *
+view_query_is_auto_updatable_or_lockable(Query *viewquery, bool check_cols, bool for_lock)
 {
 	RangeTblRef *rtr;
 	RangeTblEntry *base_rte;
@@ -2287,22 +2290,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 *----------
 	 */
 	if (viewquery->distinctClause != NIL)
-		return gettext_noop("Views containing DISTINCT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing DISTINCT are not automatically updatable.") :
+			gettext_noop("Views containing DISTINCT are not lockable."));
 
 	if (viewquery->groupClause != NIL || viewquery->groupingSets)
-		return gettext_noop("Views containing GROUP BY are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing GROUP BY are not automatically updatable.") :
+			gettext_noop("Views containing GROUP BY are not lockable."));
 
 	if (viewquery->havingQual != NULL)
-		return gettext_noop("Views containing HAVING are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing HAVING are not automatically updatable.") :
+			gettext_noop("Views containing HAVING are not lockable."));
 
 	if (viewquery->setOperations != NULL)
-		return gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.") :
+			gettext_noop("Views containing UNION, INTERSECT, or EXCEPT are not lockable."));
 
 	if (viewquery->cteList != NIL)
-		return gettext_noop("Views containing WITH are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing WITH are not automatically updatable.") :
+			gettext_noop("Views containing WITH are not lockable."));
 
 	if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL)
-		return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.") :
+			gettext_noop("Views containing LIMIT or OFFSET are not lockable."));
 
 	/*
 	 * We must not allow window functions or set returning functions in the
@@ -2314,24 +2329,34 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	 * unique row in the underlying base relation.
 	 */
 	if (viewquery->hasAggs)
-		return gettext_noop("Views that return aggregate functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return aggregate functions are not automatically updatable.") :
+			gettext_noop("Views that return aggregate functions are not lockable."));
 
 	if (viewquery->hasWindowFuncs)
-		return gettext_noop("Views that return window functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return window functions are not automatically updatable.") :
+			gettext_noop("Views that return window functions are not lockable."));
 
 	if (viewquery->hasTargetSRFs)
-		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that return set-returning functions are not automatically updatable.") :
+			gettext_noop("Views that return set-returning functions are not lockable."));
 
 	/*
 	 * The view query should select from a single base relation, which must be
 	 * a table or another view.
 	 */
 	if (list_length(viewquery->jointree->fromlist) != 1)
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist);
 	if (!IsA(rtr, RangeTblRef))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	base_rte = rt_fetch(rtr->rtindex, viewquery->rtable);
 	if (base_rte->rtekind != RTE_RELATION ||
@@ -2339,10 +2364,14 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 		 base_rte->relkind != RELKIND_FOREIGN_TABLE &&
 		 base_rte->relkind != RELKIND_VIEW &&
 		 base_rte->relkind != RELKIND_PARTITIONED_TABLE))
-		return gettext_noop("Views that do not select from a single table or view are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views that do not select from a single table or view are not automatically updatable.") :
+			gettext_noop("Views that do not select from a single table or view are not lockable."));
 
 	if (base_rte->tablesample)
-		return gettext_noop("Views containing TABLESAMPLE are not automatically updatable.");
+		return (!for_lock ?
+			gettext_noop("Views containing TABLESAMPLE are not automatically updatable.") :
+			gettext_noop("Views containing TABLESAMPLE are not lockable."));
 
 	/*
 	 * Check that the view has at least one updatable column. This is required
@@ -2374,6 +2403,29 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 
 
 /*
+ * view_query_is_auto_updatable - a simple wrapper function of
+ * view_query_is_auto_updatable_or_lockable
+ */
+const char *
+view_query_is_auto_updatable(Query *viewquery, bool check_cols)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, check_cols, false);
+}
+
+/*
+ * view_query_is_lockable - a wrapper function of
+ * view_query_is_auto_updatable_or_lockable to check whether a given view is
+ * lockable. Returns NULL (if the view can be locked) or a message string
+ * giving the reason that it cannot be.
+ */
+const char *
+view_query_is_lockable(Query *viewquery)
+{
+	return view_query_is_auto_updatable_or_lockable(viewquery, false, true);
+}
+
+
+/*
  * view_cols_are_auto_updatable - test whether all of the required columns of
  * an auto-updatable view are actually updatable. Returns NULL (if all the
  * required columns can be updated) or a message string giving the reason that
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 8128199..c8e8c29 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -29,6 +29,7 @@ extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 extern Query *get_view_query(Relation view);
 extern const char *view_query_is_auto_updatable(Query *viewquery,
 							 bool check_cols);
+extern const char *view_query_is_lockable(Query *viewquery);
 extern int relation_is_updatable(Oid reloid,
 					  bool include_triggers,
 					  Bitmapset *include_cols);
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index 74a434d..bc62082 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +35,60 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+ROLLBACK;
+-- Verify that we can lock a auto-updatable views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ pg_locks
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view2"
+DETAIL:  Views that return aggregate functions are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ERROR:  cannot lock view "lock_view3"
+DETAIL:  Views that do not select from a single table or view are not lockable.
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ lock_view4
+ pg_locks
+(4 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view5
+ pg_locks
+(3 rows)
+
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -54,10 +111,15 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE no_lock_tbl;
 DROP SCHEMA lock_schema1 CASCADE;
 DROP ROLE regress_rol_lock1;
 -- atomic ops tests
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..ad9bc13 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE no_lock_tbl (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2 AS SELECT count(*) FROM lock_tbl1;
+CREATE VIEW lock_view3 AS SELECT 1;
+CREATE VIEW lock_view4 AS SELECT * from lock_view1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from no_lock_tbl);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +38,35 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+ROLLBACK;
+
+-- Verify that we can lock a auto-updatable views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1 and lock_tbl are locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-auto-updatable view
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view4, lock_view1, and lock_tbl are locked recursively
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;   -- OK; can lock an auto-updatable view
+-- lock_view1, and lock_tbl are locked; no_lock_tbl in the subquery is not locked
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
 ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
@@ -58,10 +91,15 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE no_lock_tbl;
 DROP SCHEMA lock_schema1 CASCADE;
 DROP ROLE regress_rol_lock1;
 
#31Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Tatsuo Ishii (#28)
Re: [HACKERS] [PATCH] Lockable views

On Tue, Jan 30, 2018 at 6:48 PM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Looks good to me. If there's no objection, especially from Thomas
Munro, I will mark this as "ready for committer".

No objection from me.

--
Thomas Munro
http://www.enterprisedb.com

#32Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Thomas Munro (#31)
Re: [HACKERS] [PATCH] Lockable views

On Tue, Jan 30, 2018 at 6:48 PM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Looks good to me. If there's no objection, especially from Thomas
Munro, I will mark this as "ready for committer".

No objection from me.

I marked this as "Ready for Committer".

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#33Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#32)
Re: [HACKERS] [PATCH] Lockable views

On Thu, 01 Feb 2018 09:48:49 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

On Tue, Jan 30, 2018 at 6:48 PM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Looks good to me. If there's no objection, especially from Thomas
Munro, I will mark this as "ready for committer".

No objection from me.

I marked this as "Ready for Committer".

Thank you for reviewing the patch!

Regards,

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

#34Robert Haas
robertmhaas@gmail.com
In reply to: Thomas Munro (#29)
Re: [HACKERS] [PATCH] Lockable views

On Tue, Jan 30, 2018 at 1:21 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

About the idea: it makes some kind of sense to me that we should lock
the underlying table, in all the same cases that you could do DML on
the view automatically. I wonder if this is a problem for the
soundness: "Tables appearing in a subquery are ignored and not
locked."

Yeah, that seems like a pretty bad idea. It's exposing what is
basically an implementation detail to users. I think that if we
change the rules for which subqueries get flattened in a future
release, then the behavior will also change. That seems bad.

I also think that this is a bad idea for another reason, which is that
it leaves us with no syntax to say that you want to lock the view
itself, and pg_dump wants do that if only we had syntax for it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#35Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Robert Haas (#34)
Re: [HACKERS] [PATCH] Lockable views

On Tue, Jan 30, 2018 at 1:21 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:

About the idea: it makes some kind of sense to me that we should lock
the underlying table, in all the same cases that you could do DML on
the view automatically. I wonder if this is a problem for the
soundness: "Tables appearing in a subquery are ignored and not
locked."

Yeah, that seems like a pretty bad idea. It's exposing what is
basically an implementation detail to users.

Initially I thought all base tables including ones in a subquery also
should be locked like you. But after some discussions with Yugo, I
agree that locking table in a subquery is less valuable for users (and
I am afraid it may introduce more deadlock chances). See upthead
discussions.

I think that if we
change the rules for which subqueries get flattened in a future
release, then the behavior will also change. That seems bad.

I doubt it could happen in the future but if that happend we should
disallow locking on such views.

I also think that this is a bad idea for another reason, which is that
it leaves us with no syntax to say that you want to lock the view
itself, and pg_dump wants do that if only we had syntax for it.

I agree with Yugo and Alvaro. It's better to have a separate syntax
for locking views itself.

/messages/by-id/20171226143407.6wjzjn42pt54qskm@alvherre.pgsql

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#36Robert Haas
robertmhaas@gmail.com
In reply to: Tatsuo Ishii (#35)
Re: [HACKERS] [PATCH] Lockable views

On Thu, Feb 1, 2018 at 8:09 PM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Initially I thought all base tables including ones in a subquery also
should be locked like you. But after some discussions with Yugo, I
agree that locking table in a subquery is less valuable for users (and
I am afraid it may introduce more deadlock chances). See upthead
discussions.

I just reread those discussions but I don't see that they really make
any argument for the behavior the patch implements. I see no
explanation on the thread for why locking a table inside of a subquery
is more or less likely to cause deadlock than locking one outside of a
subquery.

I think that if we
change the rules for which subqueries get flattened in a future
release, then the behavior will also change. That seems bad.

I doubt it could happen in the future but if that happend we should
disallow locking on such views.

That doesn't make any sense to me. When someone migrates from
PostgreSQL 11 to, say, PostgreSQL 14, the view definition is going to
be recreated from an SQL query. Neither the user nor the database
will know whether the query was optimized the same way on both
databases, so how could we disallow locking only those views where
there was a difference on the two releases? Even if we could, how
does that help anything? Throwing an error is just as much a
backward-incompatibility in the command as silently changing what gets
locked.

But my complaint may have been a little off base all the same -- I
guess we're doing this based on the rewriter output, rather than the
optimizer output, which makes it a lot less likely that we would
decide to change anything here.

I also think that this is a bad idea for another reason, which is that
it leaves us with no syntax to say that you want to lock the view
itself, and pg_dump wants do that if only we had syntax for it.

I agree with Yugo and Alvaro. It's better to have a separate syntax
for locking views itself.

/messages/by-id/20171226143407.6wjzjn42pt54qskm@alvherre.pgsql

Hmm, Alvaro's argument makes sense, I guess. I withdraw this complaint.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#37Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Robert Haas (#36)
Re: [HACKERS] [PATCH] Lockable views

Robert,

I just reread those discussions but I don't see that they really make
any argument for the behavior the patch implements. I see no
explanation on the thread for why locking a table inside of a subquery
is more or less likely to cause deadlock than locking one outside of a
subquery.

If we allow to lock a table in a subquery, following could happen:

We have: CREATE VIEW v1 AS SELECT * FROM t1 WHERE i = (SELECT i FROM t2);

1. Session A tries to lock v1 (I suppose it tries to acquire lock in
the order of t1, then t2). A acquires lock on t1 but yet on t2.

2. Another session B acquires lock on t2.

3. A continues to try to acquire lock on t2 (blocked).

4. B tries to acquire lock on t1. Deadlock occurs.

So if a user mixes locking a view and a underlying base table, there's
a possibility of deadlocks. If we regard that it is user's
responsibility not to mix them or to be careful about the consequence
the mixing of locks so that dealocks do not happen, then I would agree
that we should lock a table inside a subquery.

What do you think?

I think that if we
change the rules for which subqueries get flattened in a future
release, then the behavior will also change. That seems bad.

I doubt it could happen in the future but if that happend we should
disallow locking on such views.

That doesn't make any sense to me. When someone migrates from
PostgreSQL 11 to, say, PostgreSQL 14, the view definition is going to
be recreated from an SQL query. Neither the user nor the database
will know whether the query was optimized the same way on both
databases, so how could we disallow locking only those views where
there was a difference on the two releases? Even if we could, how
does that help anything? Throwing an error is just as much a
backward-incompatibility in the command as silently changing what gets
locked.

But my complaint may have been a little off base all the same -- I
guess we're doing this based on the rewriter output, rather than the
optimizer output, which makes it a lot less likely that we would
decide to change anything here.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#38Robert Haas
robertmhaas@gmail.com
In reply to: Tatsuo Ishii (#37)
Re: [HACKERS] [PATCH] Lockable views

On Mon, Feb 5, 2018 at 8:18 PM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

We have: CREATE VIEW v1 AS SELECT * FROM t1 WHERE i = (SELECT i FROM t2);

1. Session A tries to lock v1 (I suppose it tries to acquire lock in
the order of t1, then t2). A acquires lock on t1 but yet on t2.

2. Another session B acquires lock on t2.

3. A continues to try to acquire lock on t2 (blocked).

4. B tries to acquire lock on t1. Deadlock occurs.

True. But the same exact analysis also applies to this definition,
which contains no subquery:

CREATE VIEW v1 AS SELECT t1.* FROM t1, t2 WHERE t1.i = t2.i;

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#39Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Robert Haas (#38)
Re: [HACKERS] [PATCH] Lockable views

True. But the same exact analysis also applies to this definition,
which contains no subquery:

CREATE VIEW v1 AS SELECT t1.* FROM t1, t2 WHERE t1.i = t2.i;

That's not an updatable view, thus cannot be locked according to the
proposed implementation.

Anyway do you want to allow to lock all base tables in a view
definition if the view is updatable?

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#40Robert Haas
robertmhaas@gmail.com
In reply to: Tatsuo Ishii (#39)
Re: [HACKERS] [PATCH] Lockable views

On Mon, Feb 5, 2018 at 10:26 PM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

True. But the same exact analysis also applies to this definition,
which contains no subquery:

CREATE VIEW v1 AS SELECT t1.* FROM t1, t2 WHERE t1.i = t2.i;

That's not an updatable view, thus cannot be locked according to the
proposed implementation.

Hmm, true. Why exactly are we imposing the restriction to updateable
views, anyway?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#41Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Robert Haas (#40)
Re: [HACKERS] [PATCH] Lockable views

Hmm, true. Why exactly are we imposing the restriction to updateable
views, anyway?

In my understanding, because of ambiguity to determine which rows in
which base tables needs to be modified by just looking at the DML
against a view. There could be multiple ways to modify the base
tables.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#42Robert Haas
robertmhaas@gmail.com
In reply to: Tatsuo Ishii (#41)
Re: [HACKERS] [PATCH] Lockable views

On Mon, Feb 5, 2018 at 10:49 PM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

Hmm, true. Why exactly are we imposing the restriction to updateable
views, anyway?

In my understanding, because of ambiguity to determine which rows in
which base tables needs to be modified by just looking at the DML
against a view. There could be multiple ways to modify the base
tables.

But what does that have to do with locking?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#43Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Robert Haas (#42)
Re: [HACKERS] [PATCH] Lockable views

But what does that have to do with locking?

Well, if the view is not updatable, I think there will be less point
to allow to lock the base tables in the view because locking is
typically used in a case when updates are required.

Of course we could add special triggers to allow to update views that
are not automatically updatable but that kind of views are tend to
complex and IMO there's less need the automatic view locking feature.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#44Robert Haas
robertmhaas@gmail.com
In reply to: Tatsuo Ishii (#43)
Re: [HACKERS] [PATCH] Lockable views

On Tue, Feb 6, 2018 at 1:28 AM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

But what does that have to do with locking?

Well, if the view is not updatable, I think there will be less point
to allow to lock the base tables in the view because locking is
typically used in a case when updates are required.

Of course we could add special triggers to allow to update views that
are not automatically updatable but that kind of views are tend to
complex and IMO there's less need the automatic view locking feature.

Hmm. Well, I see now why you've designed the feature in the way that
you have, but I guess it still seems somewhat arbitrary to me. If you
ignore the deadlock consideration, then there's no reason not to
define the feature as locking every table mentioned anywhere in the
query, including subqueries, and it can work for all views whether
updatable or not. If the deadlock consideration is controlling, then
I guess we can't do better than what you have, but I'm not sure how
future-proof it is. If in the future somebody makes views updateable
that involve a join, say from the primary key of one table to a unique
key of another so that no duplicate rows can be introduced, then
they'll either have to write code to make this feature identify and
lock the "main" table, which I'm not sure would be strong enough in
all cases, or lock them all, which reintroduces the deadlock problem.

Personally, I would be inclined to view the deadlock problem as not
very important. I just don't see how that is going to come up very
often. What I do think will be an issue is that if you start locking
lots of tables, you might prevent the system from getting much work
done, whether or not you also cause any deadlocks. But I don't see
what we can do about that, really. If users want full control over
which tables get locked, then they have to name them explicitly. Or
alternatively, maybe they should avoid the need for full-table locks
by using SSI, gaining the benefits of (1) optimistic rather than
pessimistic concurrency control, (2) finer-grained locking, and (3)
not needing to issue explicit LOCK commands.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#45Yugo Nagata
nagata@sraoss.co.jp
In reply to: Robert Haas (#44)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Tue, 6 Feb 2018 11:12:37 -0500
Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Feb 6, 2018 at 1:28 AM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

But what does that have to do with locking?

Well, if the view is not updatable, I think there will be less point
to allow to lock the base tables in the view because locking is
typically used in a case when updates are required.

Of course we could add special triggers to allow to update views that
are not automatically updatable but that kind of views are tend to
complex and IMO there's less need the automatic view locking feature.

Hmm. Well, I see now why you've designed the feature in the way that
you have, but I guess it still seems somewhat arbitrary to me. If you
ignore the deadlock consideration, then there's no reason not to
define the feature as locking every table mentioned anywhere in the
query, including subqueries, and it can work for all views whether
updatable or not. If the deadlock consideration is controlling, then
I guess we can't do better than what you have, but I'm not sure how
future-proof it is. If in the future somebody makes views updateable
that involve a join, say from the primary key of one table to a unique
key of another so that no duplicate rows can be introduced, then
they'll either have to write code to make this feature identify and
lock the "main" table, which I'm not sure would be strong enough in
all cases, or lock them all, which reintroduces the deadlock problem.

Personally, I would be inclined to view the deadlock problem as not
very important. I just don't see how that is going to come up very

I agree that the deadlock won't occur very often and this is not
so important.

I have updated the lockable-view patch to v8.

This new version doen't consider the deadlock problem, and all tables
or views appearing in the view definition query are locked recursively.
Also, this allows all kinds of views to be locked even if it is not
auto-updatable view.

often. What I do think will be an issue is that if you start locking
lots of tables, you might prevent the system from getting much work
done, whether or not you also cause any deadlocks. But I don't see
what we can do about that, really. If users want full control over
which tables get locked, then they have to name them explicitly. Or
alternatively, maybe they should avoid the need for full-table locks
by using SSI, gaining the benefits of (1) optimistic rather than
pessimistic concurrency control, (2) finer-grained locking, and (3)
not needing to issue explicit LOCK commands.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view-v8.patchtext/x-diff; name=lock_view-v8.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..96d477c 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,11 @@ 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. 
+  </para>
+
+  <para>
    When acquiring locks automatically for commands that reference
    tables, <productname>PostgreSQL</productname> always uses the least
    restrictive lock mode possible. <command>LOCK TABLE</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 6479dcb..daf3d81 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -23,11 +23,15 @@
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
+#include "nodes/nodeFuncs.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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);
 
 /*
  * LOCK TABLE
@@ -62,8 +66,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +92,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
 }
@@ -107,7 +115,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +128,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +165,120 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ *
+ * All tables and views appearing in the view definition query are locked
+ * recursively with the same lock mode. 
+ */
+
+typedef struct
+{
+	Oid root_reloid;
+	LOCKMODE lockmode;
+	bool nowait;
+	Oid viewowner;
+} LockViewRecurse_context;
+
+static bool
+LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query		*query = (Query *) node;
+		ListCell	*rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry	*rte = lfirst(rtable);
+			AclResult		 aclresult;
+
+			Oid relid = rte->relid;
+			char relkind = rte->relkind;
+			char *relname = get_rel_name(relid);
+
+			/* The OLD and NEW placeholder entries are skipped */
+			if (!strcmp(rte->eref->aliasname, "old") || !strcmp(rte->eref->aliasname, "new"))
+				continue;
+
+			/* Currently, we only allow plain tables or views to be locked */
+			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+				relkind != RELKIND_VIEW)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						errmsg("\"%s\" is not a table or view",
+						relname)));
+
+			/* Check infinite recursion in the view definition */
+			if (relid == context->root_reloid)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						errmsg("infinite recursion detected in rules for relation \"%s\"",
+								get_rel_name(context->root_reloid))));
+
+			/* Check permissions with the view owner's privilege. */
+			aclresult = LockTableAclCheck(relid, context->lockmode, context->viewowner);
+			if (aclresult != ACLCHECK_OK)
+				aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
+
+			/* We have enough rights to lock the relation; do so. */
+			if (!context->nowait)
+				LockRelationOid(relid, context->lockmode);
+			else if (!ConditionalLockRelationOid(relid, context->lockmode))
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+
+			if (relkind == RELKIND_VIEW)
+				LockViewRecurse(relid, context->root_reloid, context->lockmode, context->nowait);
+			else if (rte->inh)
+				LockTableRecurse(relid, context->lockmode, context->nowait, context->viewowner);
+		}
+
+		return query_tree_walker(query,
+								 LockViewRecurse_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  LockViewRecurse_walker,
+								  context);
+}
+
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	LockViewRecurse_context context;
+
+	Relation		 view;
+	Query			*viewquery;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+	heap_close(view, NoLock);
+
+	context.root_reloid = root_reloid;
+	context.lockmode = lockmode;
+	context.nowait = nowait;
+	context.viewowner = view->rd_rel->relowner;
+
+	LockViewRecurse_walker((Node *) viewquery, &context);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +291,7 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index 74a434d..328e230 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE lock_tbl1a (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a;
+CREATE VIEW lock_view3 AS SELECT * from lock_view2;
+CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +35,82 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+ROLLBACK;
+-- Verify that we can lock views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;
+-- lock_view1 and lock_tbl1 are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+ pg_locks
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;
+-- lock_view1, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view2
+ pg_locks
+(4 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;
+-- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view2
+ lock_view3
+ pg_locks
+(5 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;
+-- lock_view4, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view4
+ pg_locks
+(4 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;
+-- lock_view5, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view5
+ pg_locks
+(4 rows)
+
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -54,11 +133,18 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE no_lock_tbl;
+ERROR:  table "no_lock_tbl" does not exist
 DROP SCHEMA lock_schema1 CASCADE;
+NOTICE:  drop cascades to table lock_tbl1a
 DROP ROLE regress_rol_lock1;
 -- atomic ops tests
 RESET search_path;
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..c9d6495 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,12 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE lock_tbl1a (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a;
+CREATE VIEW lock_view3 AS SELECT * from lock_view2;
+CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a);
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +38,43 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+ROLLBACK;
+
+-- Verify that we can lock views
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;
+-- lock_view1 and lock_tbl1 are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;
+-- lock_view1, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;
+-- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;
+-- lock_view4, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;
+-- lock_view5, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%'
+ order by relname;
 ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
@@ -58,10 +99,15 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE no_lock_tbl;
 DROP SCHEMA lock_schema1 CASCADE;
 DROP ROLE regress_rol_lock1;
 
#46Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#45)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Tue, 27 Mar 2018 23:28:04 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

I found the previous patch was broken and this can't handle
views that has subqueries as bellow;

 CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;

I fixed this and attached the updated version including additional tests.

Regards,

On Tue, 6 Feb 2018 11:12:37 -0500
Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Feb 6, 2018 at 1:28 AM, Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

But what does that have to do with locking?

Well, if the view is not updatable, I think there will be less point
to allow to lock the base tables in the view because locking is
typically used in a case when updates are required.

Of course we could add special triggers to allow to update views that
are not automatically updatable but that kind of views are tend to
complex and IMO there's less need the automatic view locking feature.

Hmm. Well, I see now why you've designed the feature in the way that
you have, but I guess it still seems somewhat arbitrary to me. If you
ignore the deadlock consideration, then there's no reason not to
define the feature as locking every table mentioned anywhere in the
query, including subqueries, and it can work for all views whether
updatable or not. If the deadlock consideration is controlling, then
I guess we can't do better than what you have, but I'm not sure how
future-proof it is. If in the future somebody makes views updateable
that involve a join, say from the primary key of one table to a unique
key of another so that no duplicate rows can be introduced, then
they'll either have to write code to make this feature identify and
lock the "main" table, which I'm not sure would be strong enough in
all cases, or lock them all, which reintroduces the deadlock problem.

Personally, I would be inclined to view the deadlock problem as not
very important. I just don't see how that is going to come up very

I agree that the deadlock won't occur very often and this is not
so important.

I have updated the lockable-view patch to v8.

This new version doen't consider the deadlock problem, and all tables
or views appearing in the view definition query are locked recursively.
Also, this allows all kinds of views to be locked even if it is not
auto-updatable view.

often. What I do think will be an issue is that if you start locking
lots of tables, you might prevent the system from getting much work
done, whether or not you also cause any deadlocks. But I don't see
what we can do about that, really. If users want full control over
which tables get locked, then they have to name them explicitly. Or
alternatively, maybe they should avoid the need for full-table locks
by using SSI, gaining the benefits of (1) optimistic rather than
pessimistic concurrency control, (2) finer-grained locking, and (3)
not needing to issue explicit LOCK commands.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view-v9.patchtext/x-diff; name=lock_view-v9.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..96d477c 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,11 @@ 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. 
+  </para>
+
+  <para>
    When acquiring locks automatically for commands that reference
    tables, <productname>PostgreSQL</productname> always uses the least
    restrictive lock mode possible. <command>LOCK TABLE</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 6479dcb..e15d87d 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -23,11 +23,15 @@
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
+#include "nodes/nodeFuncs.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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);
 
 /*
  * LOCK TABLE
@@ -62,8 +66,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +92,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
 }
@@ -107,7 +115,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +128,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +165,120 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ *
+ * All tables and views appearing in the view definition query are locked
+ * recursively with the same lock mode.
+ */
+
+typedef struct
+{
+	Oid root_reloid;
+	LOCKMODE lockmode;
+	bool nowait;
+	Oid viewowner;
+	Oid viewoid
+} LockViewRecurse_context;
+
+static bool
+LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query		*query = (Query *) node;
+		ListCell	*rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry	*rte = lfirst(rtable);
+			AclResult		 aclresult;
+
+			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. */
+			if (relid == context->viewoid &&
+				(!strcmp(rte->eref->aliasname, "old") || !strcmp(rte->eref->aliasname, "new")))
+				continue;
+
+			/* Currently, we only allow plain tables or views to be locked. */
+			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+				relkind != RELKIND_VIEW)
+				continue;
+
+			/* Check infinite recursion in the view definition. */
+			if (relid == context->root_reloid)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						errmsg("infinite recursion detected in rules for relation \"%s\"",
+								get_rel_name(context->root_reloid))));
+
+			/* Check permissions with the view owner's privilege. */
+			aclresult = LockTableAclCheck(relid, context->lockmode, context->viewowner);
+			if (aclresult != ACLCHECK_OK)
+				aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
+
+			/* We have enough rights to lock the relation; do so. */
+			if (!context->nowait)
+				LockRelationOid(relid, context->lockmode);
+			else if (!ConditionalLockRelationOid(relid, context->lockmode))
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+
+			if (relkind == RELKIND_VIEW)
+				LockViewRecurse(relid, context->root_reloid, context->lockmode, context->nowait);
+			else if (rte->inh)
+				LockTableRecurse(relid, context->lockmode, context->nowait, context->viewowner);
+		}
+
+		return query_tree_walker(query,
+								 LockViewRecurse_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  LockViewRecurse_walker,
+								  context);
+}
+
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	LockViewRecurse_context context;
+
+	Relation		 view;
+	Query			*viewquery;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+	heap_close(view, NoLock);
+
+	context.root_reloid = root_reloid;
+	context.lockmode = lockmode;
+	context.nowait = nowait;
+	context.viewowner = view->rd_rel->relowner;
+	context.viewoid = reloid;
+
+	LockViewRecurse_walker((Node *) viewquery, &context);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +291,7 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index 74a434d..7954c45 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,13 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE lock_tbl1a (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a;
+CREATE VIEW lock_view3 AS SELECT * from lock_view2;
+CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a);
+CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +36,90 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+ROLLBACK;
+-- Verify that we can lock views.
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;
+-- lock_view1 and lock_tbl1 are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+(2 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;
+-- lock_view1, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view2
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;
+-- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view2
+ lock_view3
+(4 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;
+-- lock_view4, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view4
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;
+-- lock_view5, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view5
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view6 IN EXCLUSIVE MODE;
+-- lock_view6 an lock_tbl1 are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view6
+(2 rows)
+
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -54,10 +142,16 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view6;
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE lock_tbl1a;
 DROP SCHEMA lock_schema1 CASCADE;
 DROP ROLE regress_rol_lock1;
 -- atomic ops tests
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..91946d9 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,13 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE lock_tbl1a (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a;
+CREATE VIEW lock_view3 AS SELECT * from lock_view2;
+CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a);
+CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +39,50 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+ROLLBACK;
+
+-- Verify that we can lock views.
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;
+-- lock_view1 and lock_tbl1 are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;
+-- lock_view1, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;
+-- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;
+-- lock_view4, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;
+-- lock_view5, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view6 IN EXCLUSIVE MODE;
+-- lock_view6 an lock_tbl1 are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and mode = 'ExclusiveLock'
+ order by relname;
 ROLLBACK;
 
 -- Verify that we can lock a table with inheritance children.
@@ -58,10 +107,16 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view6;
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE lock_tbl1a;
 DROP SCHEMA lock_schema1 CASCADE;
 DROP ROLE regress_rol_lock1;
 
#47Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#46)
Re: [HACKERS] [PATCH] Lockable views

On Tue, 27 Mar 2018 23:28:04 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

I found the previous patch was broken and this can't handle
views that has subqueries as bellow;

 CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;

I fixed this and attached the updated version including additional tests.

This patch gives a warning while compiling:

lockcmds.c:186:1: warning: no semicolon at end of struct or union
} LockViewRecurse_context;
^

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#48Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Tatsuo Ishii (#47)
Re: [HACKERS] [PATCH] Lockable views

I found the previous patch was broken and this can't handle
views that has subqueries as bellow;

 CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;

I fixed this and attached the updated version including additional tests.

This patch gives a warning while compiling:

lockcmds.c:186:1: warning: no semicolon at end of struct or union
} LockViewRecurse_context;
^

Also I get a regression test failure:

*** /usr/local/src/pgsql/current/postgresql/src/test/regress/expected/lock.out	2018-03-28 15:24:13.805314577 +0900
--- /usr/local/src/pgsql/current/postgresql/src/test/regress/results/lock.out	2018-03-28 15:42:07.975592594 +0900
***************
*** 118,124 ****
  ------------
   lock_tbl1
   lock_view6
! (2 rows)
  ROLLBACK;
  -- Verify that we can lock a table with inheritance children.
--- 118,125 ----
  ------------
   lock_tbl1
   lock_view6
!  mvtest_tm
! (3 rows)

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#49Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#48)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Wed, 28 Mar 2018 15:45:09 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

I found the previous patch was broken and this can't handle
views that has subqueries as bellow;

 CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;

I fixed this and attached the updated version including additional tests.

This patch gives a warning while compiling:

lockcmds.c:186:1: warning: no semicolon at end of struct or union
} LockViewRecurse_context;
^

Also I get a regression test failure:

Thank you for your reviewing my patch.
I attached the updated patch, v10.

Regards,

*** /usr/local/src/pgsql/current/postgresql/src/test/regress/expected/lock.out	2018-03-28 15:24:13.805314577 +0900
--- /usr/local/src/pgsql/current/postgresql/src/test/regress/results/lock.out	2018-03-28 15:42:07.975592594 +0900
***************
*** 118,124 ****
------------
lock_tbl1
lock_view6
! (2 rows)
ROLLBACK;
-- Verify that we can lock a table with inheritance children.
--- 118,125 ----
------------
lock_tbl1
lock_view6
!  mvtest_tm
! (3 rows)

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

lock_view-v10.patchtext/x-diff; name=lock_view-v10.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..96d477c 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,11 @@ 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. 
+  </para>
+
+  <para>
    When acquiring locks automatically for commands that reference
    tables, <productname>PostgreSQL</productname> always uses the least
    restrictive lock mode possible. <command>LOCK TABLE</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 6479dcb..e8a8877 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -23,11 +23,15 @@
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
+#include "access/heapam.h"
+#include "nodes/nodeFuncs.h"
 
-static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
-static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
+static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
+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);
 
 /*
  * LOCK TABLE
@@ -62,8 +66,10 @@ LockTableCommand(LockStmt *lockstmt)
 										  RangeVarCallbackForLockTable,
 										  (void *) &lockstmt->mode);
 
-		if (recurse)
-			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
+		if (get_rel_relkind(reloid) == RELKIND_VIEW)
+			LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
+		else if (recurse)
+			LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
 	}
 }
 
@@ -86,15 +92,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 		return;					/* woops, concurrently dropped; no permissions
 								 * check */
 
-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+
+	/* Currently, we only allow plain tables or views to be locked */
+	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+		relkind != RELKIND_VIEW)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
+				 errmsg("\"%s\" is not a table or view",
 						rv->relname)));
 
 	/* Check permissions. */
-	aclresult = LockTableAclCheck(relid, lockmode);
+	aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
 }
@@ -107,7 +115,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
  * multiply-inheriting children more than once, but that's no problem.
  */
 static void
-LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
+LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
 {
 	List	   *children;
 	ListCell   *lc;
@@ -120,7 +128,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 		AclResult	aclresult;
 
 		/* Check permissions before acquiring the lock. */
-		aclresult = LockTableAclCheck(childreloid, lockmode);
+		aclresult = LockTableAclCheck(childreloid, lockmode, userid);
 		if (aclresult != ACLCHECK_OK)
 		{
 			char	   *relname = get_rel_name(childreloid);
@@ -157,15 +165,120 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
 			continue;
 		}
 
-		LockTableRecurse(childreloid, lockmode, nowait);
+		LockTableRecurse(childreloid, lockmode, nowait, userid);
 	}
 }
 
 /*
+ * Apply LOCK TABLE recursively over a view
+ *
+ * All tables and views appearing in the view definition query are locked
+ * recursively with the same lock mode.
+ */
+
+typedef struct
+{
+	Oid root_reloid;
+	LOCKMODE lockmode;
+	bool nowait;
+	Oid viewowner;
+	Oid viewoid;
+} LockViewRecurse_context;
+
+static bool
+LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query		*query = (Query *) node;
+		ListCell	*rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry	*rte = lfirst(rtable);
+			AclResult		 aclresult;
+
+			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. */
+			if (relid == context->viewoid &&
+				(!strcmp(rte->eref->aliasname, "old") || !strcmp(rte->eref->aliasname, "new")))
+				continue;
+
+			/* Currently, we only allow plain tables or views to be locked. */
+			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+				relkind != RELKIND_VIEW)
+				continue;
+
+			/* Check infinite recursion in the view definition. */
+			if (relid == context->root_reloid)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						errmsg("infinite recursion detected in rules for relation \"%s\"",
+								get_rel_name(context->root_reloid))));
+
+			/* Check permissions with the view owner's privilege. */
+			aclresult = LockTableAclCheck(relid, context->lockmode, context->viewowner);
+			if (aclresult != ACLCHECK_OK)
+				aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
+
+			/* We have enough rights to lock the relation; do so. */
+			if (!context->nowait)
+				LockRelationOid(relid, context->lockmode);
+			else if (!ConditionalLockRelationOid(relid, context->lockmode))
+				ereport(ERROR,
+						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s\"",
+								relname)));
+
+			if (relkind == RELKIND_VIEW)
+				LockViewRecurse(relid, context->root_reloid, context->lockmode, context->nowait);
+			else if (rte->inh)
+				LockTableRecurse(relid, context->lockmode, context->nowait, context->viewowner);
+		}
+
+		return query_tree_walker(query,
+								 LockViewRecurse_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  LockViewRecurse_walker,
+								  context);
+}
+
+static void
+LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
+{
+	LockViewRecurse_context context;
+
+	Relation		 view;
+	Query			*viewquery;
+
+	view = heap_open(reloid, NoLock);
+	viewquery = get_view_query(view);
+	heap_close(view, NoLock);
+
+	context.root_reloid = root_reloid;
+	context.lockmode = lockmode;
+	context.nowait = nowait;
+	context.viewowner = view->rd_rel->relowner;
+	context.viewoid = reloid;
+
+	LockViewRecurse_walker((Node *) viewquery, &context);
+}
+
+/*
  * Check whether the current user is permitted to lock this relation.
  */
 static AclResult
-LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
+LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
 {
 	AclResult	aclresult;
 	AclMode		aclmask;
@@ -178,7 +291,7 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
 	else
 		aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
 
-	aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
+	aclresult = pg_class_aclcheck(reloid, userid, aclmask);
 
 	return aclresult;
 }
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index 74a434d..964e6f2 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -5,7 +5,13 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE lock_tbl1a (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a;
+CREATE VIEW lock_view3 AS SELECT * from lock_view2;
+CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a);
+CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +36,90 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
-ERROR:  "lock_view1" is not a table
+ROLLBACK;
+-- Verify that we can lock views.
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;
+-- lock_view1 and lock_tbl1 are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view1
+(2 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;
+-- lock_view1, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view2
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;
+-- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view2
+ lock_view3
+(4 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;
+-- lock_view4, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view4
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;
+-- lock_view5, lock_tbl1, and lock_tbl1a are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_tbl1a
+ lock_view5
+(3 rows)
+
+ROLLBACK;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view6 IN EXCLUSIVE MODE;
+-- lock_view6 an lock_tbl1 are locked.
+select relname from pg_locks l, pg_class c
+ where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
+ order by relname;
+  relname   
+------------
+ lock_tbl1
+ lock_view6
+(2 rows)
+
 ROLLBACK;
 -- Verify that we can lock a table with inheritance children.
 CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -54,10 +142,16 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view6;
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE lock_tbl1a;
 DROP SCHEMA lock_schema1 CASCADE;
 DROP ROLE regress_rol_lock1;
 -- atomic ops tests
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index 567e8bc..e22c9e2 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -6,7 +6,13 @@
 CREATE SCHEMA lock_schema1;
 SET search_path = lock_schema1;
 CREATE TABLE lock_tbl1 (a BIGINT);
-CREATE VIEW lock_view1 AS SELECT 1;
+CREATE TABLE lock_tbl1a (a BIGINT);
+CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
+CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a;
+CREATE VIEW lock_view3 AS SELECT * from lock_view2;
+CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1;
+CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a);
+CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;
 CREATE ROLE regress_rol_lock1;
 ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
 GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -33,7 +39,50 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
 LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
-LOCK TABLE lock_view1 IN EXCLUSIVE MODE;   -- Will fail; can't lock a non-table
+ROLLBACK;
+
+-- Verify that we can lock views.
+BEGIN TRANSACTION;
+LOCK TABLE lock_view1 IN EXCLUSIVE MODE;
+-- lock_view1 and lock_tbl1 are locked.
+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;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view2 IN EXCLUSIVE MODE;
+-- lock_view1, lock_tbl1, and lock_tbl1a are locked.
+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;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view3 IN EXCLUSIVE MODE;
+-- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively.
+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;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view4 IN EXCLUSIVE MODE;
+-- lock_view4, lock_tbl1, and lock_tbl1a are locked.
+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;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view5 IN EXCLUSIVE MODE;
+-- lock_view5, lock_tbl1, and lock_tbl1a are locked.
+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;
+BEGIN TRANSACTION;
+LOCK TABLE lock_view6 IN EXCLUSIVE MODE;
+-- lock_view6 an lock_tbl1 are locked.
+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;
 
 -- Verify that we can lock a table with inheritance children.
@@ -58,10 +107,16 @@ RESET ROLE;
 --
 -- Clean up
 --
+DROP VIEW lock_view6;
+DROP VIEW lock_view5;
+DROP VIEW lock_view4;
+DROP VIEW lock_view3;
+DROP VIEW lock_view2;
 DROP VIEW lock_view1;
 DROP TABLE lock_tbl3;
 DROP TABLE lock_tbl2;
 DROP TABLE lock_tbl1;
+DROP TABLE lock_tbl1a;
 DROP SCHEMA lock_schema1 CASCADE;
 DROP ROLE regress_rol_lock1;
 
#50Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#49)
Re: [HACKERS] [PATCH] Lockable views

On Wed, 28 Mar 2018 15:45:09 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

I found the previous patch was broken and this can't handle
views that has subqueries as bellow;

 CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;

I fixed this and attached the updated version including additional tests.

This patch gives a warning while compiling:

lockcmds.c:186:1: warning: no semicolon at end of struct or union
} LockViewRecurse_context;
^

Also I get a regression test failure:

Thank you for your reviewing my patch.
I attached the updated patch, v10.

Thanks. Looks good to me. I marked the patch as "Ready for Committer".
Unless there's an objection, especially from Robert or Thomas Munro, I
am going to commit/push it.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#51Andres Freund
andres@anarazel.de
In reply to: Yugo Nagata (#49)
Re: [HACKERS] [PATCH] Lockable views

Hi,

On 2018-03-28 20:26:48 +0900, Yugo Nagata wrote:

diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..96d477c 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,11 @@ 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. 
+  </para>

Trailing space added. I'd remove "specified to be" from the sentence.

I think mentioning how this interacts with permissions would be a good
idea too. Given how operations use the view's owner to recurse, that's
not obvious. Should also explain what permissions are required to do the
operation on the view.

@@ -86,15 +92,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
return; /* woops, concurrently dropped; no permissions
* check */

-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+

This newline looks spurious to me.

/*
+ * Apply LOCK TABLE recursively over a view
+ *
+ * All tables and views appearing in the view definition query are locked
+ * recursively with the same lock mode.
+ */
+
+typedef struct
+{
+	Oid root_reloid;
+	LOCKMODE lockmode;
+	bool nowait;
+	Oid viewowner;
+	Oid viewoid;
+} LockViewRecurse_context;

Probably wouldn't hurt to pgindent the larger changes in the patch.

+static bool
+LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query		*query = (Query *) node;
+		ListCell	*rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry	*rte = lfirst(rtable);
+			AclResult		 aclresult;
+
+			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. */
+			if (relid == context->viewoid &&
+				(!strcmp(rte->eref->aliasname, "old") || !strcmp(rte->eref->aliasname, "new")))
+				continue;
+
+			/* Currently, we only allow plain tables or views to be locked. */
+			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+				relkind != RELKIND_VIEW)
+				continue;
+
+			/* Check infinite recursion in the view definition. */
+			if (relid == context->root_reloid)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						errmsg("infinite recursion detected in rules for relation \"%s\"",
+								get_rel_name(context->root_reloid))));

Hm, how can that happen? And if it can happen, why can it only happen
with the root relation?

Greetings,

Andres Freund

#52Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Andres Freund (#51)
Re: [HACKERS] [PATCH] Lockable views

Andres,

I have just pushed the v10 patch. Yugo will reply back to your point
but I will look into your review as well.

Thanks.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

Show quoted text

Hi,

On 2018-03-28 20:26:48 +0900, Yugo Nagata wrote:

diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..96d477c 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,11 @@ 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. 
+  </para>

Trailing space added. I'd remove "specified to be" from the sentence.

I think mentioning how this interacts with permissions would be a good
idea too. Given how operations use the view's owner to recurse, that's
not obvious. Should also explain what permissions are required to do the
operation on the view.

@@ -86,15 +92,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
return; /* woops, concurrently dropped; no permissions
* check */

-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+

This newline looks spurious to me.

/*
+ * Apply LOCK TABLE recursively over a view
+ *
+ * All tables and views appearing in the view definition query are locked
+ * recursively with the same lock mode.
+ */
+
+typedef struct
+{
+	Oid root_reloid;
+	LOCKMODE lockmode;
+	bool nowait;
+	Oid viewowner;
+	Oid viewoid;
+} LockViewRecurse_context;

Probably wouldn't hurt to pgindent the larger changes in the patch.

+static bool
+LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query		*query = (Query *) node;
+		ListCell	*rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry	*rte = lfirst(rtable);
+			AclResult		 aclresult;
+
+			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. */
+			if (relid == context->viewoid &&
+				(!strcmp(rte->eref->aliasname, "old") || !strcmp(rte->eref->aliasname, "new")))
+				continue;
+
+			/* Currently, we only allow plain tables or views to be locked. */
+			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+				relkind != RELKIND_VIEW)
+				continue;
+
+			/* Check infinite recursion in the view definition. */
+			if (relid == context->root_reloid)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						errmsg("infinite recursion detected in rules for relation \"%s\"",
+								get_rel_name(context->root_reloid))));

Hm, how can that happen? And if it can happen, why can it only happen
with the root relation?

Greetings,

Andres Freund

#53Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tatsuo Ishii (#52)
Re: [HACKERS] [PATCH] Lockable views

Tatsuo Ishii <ishii@sraoss.co.jp> writes:

I have just pushed the v10 patch.

The buildfarm is fairly unhappy, and I think it's because of this patch.

regards, tom lane

#54Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Tom Lane (#53)
Re: [HACKERS] [PATCH] Lockable views

I have just pushed the v10 patch.

The buildfarm is fairly unhappy, and I think it's because of this patch.

Thanks for the info. Yes, at least prion is unhappy because of the
patch. I will look into this.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#55Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Tatsuo Ishii (#54)
Re: [HACKERS] [PATCH] Lockable views

The buildfarm is fairly unhappy, and I think it's because of this patch.

Thanks for the info. Yes, at least prion is unhappy because of the
patch. I will look into this.

Done. See if the buildarm becomes happy.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#56Yugo Nagata
nagata@sraoss.co.jp
In reply to: Andres Freund (#51)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Thu, 29 Mar 2018 17:26:36 -0700
Andres Freund <andres@anarazel.de> wrote:

Thank you for your comments. I attach a patch to fix issues
you've pointed out.

Hi,

On 2018-03-28 20:26:48 +0900, Yugo Nagata wrote:

diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..96d477c 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,11 @@ 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. 
+  </para>

Trailing space added. I'd remove "specified to be" from the sentence.

Fixed.

I think mentioning how this interacts with permissions would be a good
idea too. Given how operations use the view's owner to recurse, that's
not obvious. Should also explain what permissions are required to do the
operation on the view.

Added a description about permissions into the documentation.

@@ -86,15 +92,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
return; /* woops, concurrently dropped; no permissions
* check */

-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+

This newline looks spurious to me.

Removed.

/*
+ * Apply LOCK TABLE recursively over a view
+ *
+ * All tables and views appearing in the view definition query are locked
+ * recursively with the same lock mode.
+ */
+
+typedef struct
+{
+	Oid root_reloid;
+	LOCKMODE lockmode;
+	bool nowait;
+	Oid viewowner;
+	Oid viewoid;
+} LockViewRecurse_context;

Probably wouldn't hurt to pgindent the larger changes in the patch.

+static bool
+LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query		*query = (Query *) node;
+		ListCell	*rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry	*rte = lfirst(rtable);
+			AclResult		 aclresult;
+
+			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. */
+			if (relid == context->viewoid &&
+				(!strcmp(rte->eref->aliasname, "old") || !strcmp(rte->eref->aliasname, "new")))
+				continue;
+
+			/* Currently, we only allow plain tables or views to be locked. */
+			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+				relkind != RELKIND_VIEW)
+				continue;
+
+			/* Check infinite recursion in the view definition. */
+			if (relid == context->root_reloid)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						errmsg("infinite recursion detected in rules for relation \"%s\"",
+								get_rel_name(context->root_reloid))));

Hm, how can that happen? And if it can happen, why can it only happen
with the root relation?

For example, the following queries cause the infinite recursion of views.
This is detected and the error is raised.

create table t (i int);
create view v1 as select 1;
create view v2 as select * from v1;
create or replace view v1 as select * from v2;
begin;
lock v1;
abort;

However, I found that the previous patch could not handle the following
situation in which the root relation itself doesn't have infinite recursion.

create view v3 as select * from v1;
begin;
lock v3;
abort;

This fixed in the attached patch.

Regards,

Greetings,

Andres Freund

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

fix_lockable_view.patchtext/x-diff; name=fix_lockable_view.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index 96d477c..12303fe 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -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>
@@ -174,6 +174,13 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
    </para>
 
    <para>
+    The user performint 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
     <productname>PostgreSQL</productname> reports an error if <command>LOCK</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b247c0f..c46dc36 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -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;
+	List *ancestor_views;
 } LockViewRecurse_context;
 
 static bool
@@ -216,11 +215,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))));
+								get_rel_name(relid))));
 
 			/* Check permissions with the view owner's privilege. */
 			aclresult = LockTableAclCheck(relid, context->lockmode, context->viewowner);
@@ -237,7 +236,7 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 								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,7 +253,7 @@ 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;
 
@@ -264,14 +263,17 @@ LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
 	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);
 }
 
#57Yugo Nagata
nagata@sraoss.co.jp
In reply to: Yugo Nagata (#56)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Mon, 2 Apr 2018 18:32:53 +0900
Yugo Nagata <nagata@sraoss.co.jp> wrote:

On Thu, 29 Mar 2018 17:26:36 -0700
Andres Freund <andres@anarazel.de> wrote:

Thank you for your comments. I attach a patch to fix issues
you've pointed out.

I found a typo in the documentation and attach the updated patch.

Regards,

Hi,

On 2018-03-28 20:26:48 +0900, Yugo Nagata wrote:

diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index b2c7203..96d477c 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -46,6 +46,11 @@ 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. 
+  </para>

Trailing space added. I'd remove "specified to be" from the sentence.

Fixed.

I think mentioning how this interacts with permissions would be a good
idea too. Given how operations use the view's owner to recurse, that's
not obvious. Should also explain what permissions are required to do the
operation on the view.

Added a description about permissions into the documentation.

@@ -86,15 +92,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
return; /* woops, concurrently dropped; no permissions
* check */

-	/* Currently, we only allow plain tables to be locked */
-	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
+

This newline looks spurious to me.

Removed.

/*
+ * Apply LOCK TABLE recursively over a view
+ *
+ * All tables and views appearing in the view definition query are locked
+ * recursively with the same lock mode.
+ */
+
+typedef struct
+{
+	Oid root_reloid;
+	LOCKMODE lockmode;
+	bool nowait;
+	Oid viewowner;
+	Oid viewoid;
+} LockViewRecurse_context;

Probably wouldn't hurt to pgindent the larger changes in the patch.

+static bool
+LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query		*query = (Query *) node;
+		ListCell	*rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry	*rte = lfirst(rtable);
+			AclResult		 aclresult;
+
+			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. */
+			if (relid == context->viewoid &&
+				(!strcmp(rte->eref->aliasname, "old") || !strcmp(rte->eref->aliasname, "new")))
+				continue;
+
+			/* Currently, we only allow plain tables or views to be locked. */
+			if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
+				relkind != RELKIND_VIEW)
+				continue;
+
+			/* Check infinite recursion in the view definition. */
+			if (relid == context->root_reloid)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						errmsg("infinite recursion detected in rules for relation \"%s\"",
+								get_rel_name(context->root_reloid))));

Hm, how can that happen? And if it can happen, why can it only happen
with the root relation?

For example, the following queries cause the infinite recursion of views.
This is detected and the error is raised.

create table t (i int);
create view v1 as select 1;
create view v2 as select * from v1;
create or replace view v1 as select * from v2;
begin;
lock v1;
abort;

However, I found that the previous patch could not handle the following
situation in which the root relation itself doesn't have infinite recursion.

create view v3 as select * from v1;
begin;
lock v3;
abort;

This fixed in the attached patch.

Regards,

Greetings,

Andres Freund

--
Yugo Nagata <nagata@sraoss.co.jp>

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

fix_lockable_view-v2.patchtext/x-diff; name=fix_lockable_view-v2.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index 96d477c..a225cea 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -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>
@@ -174,6 +174,13 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
    </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
     <productname>PostgreSQL</productname> reports an error if <command>LOCK</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b247c0f..1c346e8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -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;
+	List   *ancestor_views;
 } LockViewRecurse_context;
 
 static bool
@@ -216,11 +215,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))));
+								get_rel_name(relid))));
 
 			/* Check permissions with the view owner's privilege. */
 			aclresult = LockTableAclCheck(relid, context->lockmode, context->viewowner);
@@ -237,7 +236,7 @@ LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
 								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,7 +253,7 @@ 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;
 
@@ -264,14 +263,16 @@ LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
 	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);
 }
 
#58Tatsuo Ishii
ishii@sraoss.co.jp
In reply to: Yugo Nagata (#57)
Re: [HACKERS] [PATCH] Lockable views
+typedef struct
+{
+	Oid root_reloid;
+	LOCKMODE lockmode;
+	bool nowait;
+	Oid viewowner;
+	Oid viewoid;
+} LockViewRecurse_context;

Probably wouldn't hurt to pgindent the larger changes in the patch.

Yeah. Also, each struct member needs a comment.

Hm, how can that happen? And if it can happen, why can it only happen
with the root relation?

For example, the following queries cause the infinite recursion of views.
This is detected and the error is raised.

create table t (i int);
create view v1 as select 1;
create view v2 as select * from v1;
create or replace view v1 as select * from v2;
begin;
lock v1;
abort;

However, I found that the previous patch could not handle the following
situation in which the root relation itself doesn't have infinite recursion.

create view v3 as select * from v1;
begin;
lock v3;
abort;

Shouldn't they be in the regression test?

It's shame that create_view test does not include the cases, but it's
another story.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

#59Yugo Nagata
nagata@sraoss.co.jp
In reply to: Tatsuo Ishii (#58)
1 attachment(s)
Re: [HACKERS] [PATCH] Lockable views

On Thu, 05 Apr 2018 07:53:42 +0900 (JST)
Tatsuo Ishii <ishii@sraoss.co.jp> wrote:

I update the patch to fix the lockable view issues.

+typedef struct
+{
+	Oid root_reloid;
+	LOCKMODE lockmode;
+	bool nowait;
+	Oid viewowner;
+	Oid viewoid;
+} LockViewRecurse_context;

Probably wouldn't hurt to pgindent the larger changes in the patch.

Yeah. Also, each struct member needs a comment.

I applied pgindent and added comments to struct members.

Hm, how can that happen? And if it can happen, why can it only happen
with the root relation?

For example, the following queries cause the infinite recursion of views.
This is detected and the error is raised.

create table t (i int);
create view v1 as select 1;
create view v2 as select * from v1;
create or replace view v1 as select * from v2;
begin;
lock v1;
abort;

However, I found that the previous patch could not handle the following
situation in which the root relation itself doesn't have infinite recursion.

create view v3 as select * from v1;
begin;
lock v3;
abort;

Shouldn't they be in the regression test?

Added tests for the infinite recursion detection.

Regards,

It's shame that create_view test does not include the cases, but it's
another story.

Best regards,
--
Tatsuo Ishii
SRA OSS, Inc. Japan
English: http://www.sraoss.co.jp/index_en.php
Japanese:http://www.sraoss.co.jp

--
Yugo Nagata <nagata@sraoss.co.jp>

Attachments:

fix_lockable_view-v3.patchtext/x-diff; name=fix_lockable_view-v3.patchDownload
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index 96d477c..a225cea 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -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>
@@ -174,6 +174,13 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
    </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
     <productname>PostgreSQL</productname> reports an error if <command>LOCK</command>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index b247c0f..5e0ef48 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -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);
 }
 
diff --git a/src/test/regress/expected/lock.out b/src/test/regress/expected/lock.out
index 964e6f2..185fd2f 100644
--- a/src/test/regress/expected/lock.out
+++ b/src/test/regress/expected/lock.out
@@ -121,6 +121,17 @@ select relname from pg_locks l, pg_class c
 (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);
 CREATE TABLE lock_tbl3 () INHERITS (lock_tbl2);
@@ -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;
diff --git a/src/test/regress/sql/lock.sql b/src/test/regress/sql/lock.sql
index e22c9e2..26a7e59 100644
--- a/src/test/regress/sql/lock.sql
+++ b/src/test/regress/sql/lock.sql
@@ -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;