From: Peter Eisentraut <peter_e@gmx.net>
Date: Mon, 12 Jan 2009 08:54:27 +0000 (+0000)
Subject: Add ONLY support to LOCK and TRUNCATE.  By default, these commands are now
X-Git-Tag: REL8_4_BETA1~404
X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ca8100f9eb7f02f410d1a45f2d5fee8373eace84;p=postgresql

Add ONLY support to LOCK and TRUNCATE.  By default, these commands are now
recursive.

=> Note this incompatibility in the release notes.
---

diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index f49d18618c..567f363451 100644
--- a/doc/src/sgml/ref/lock.sgml
+++ b/doc/src/sgml/ref/lock.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/lock.sgml,v 1.51 2008/11/14 10:22:47 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/lock.sgml,v 1.52 2009/01/12 08:54:25 petere Exp $
 PostgreSQL documentation
 -->
 
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-LOCK [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ IN <replaceable class="PARAMETER">lockmode</replaceable> MODE ] [ NOWAIT ]
+LOCK [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ IN <replaceable class="PARAMETER">lockmode</replaceable> MODE ] [ NOWAIT ]
 
 where <replaceable class="PARAMETER">lockmode</replaceable> is one of:
 
@@ -109,7 +109,9 @@ where <replaceable class="PARAMETER">lockmode</replaceable> is one of:
     <listitem>
      <para>
       The name (optionally schema-qualified) of an existing table to
-      lock.
+      lock.  If <literal>ONLY</> is specified, only that table is
+      locked.  If <literal>ONLY</> is not specified, the table and all
+      its descendant tables (if any) are locked.
      </para>
 
      <para>
diff --git a/doc/src/sgml/ref/truncate.sgml b/doc/src/sgml/ref/truncate.sgml
index d765c1bd8c..a512b9a52d 100644
--- a/doc/src/sgml/ref/truncate.sgml
+++ b/doc/src/sgml/ref/truncate.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/truncate.sgml,v 1.31 2008/12/18 10:45:00 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/truncate.sgml,v 1.32 2009/01/12 08:54:25 petere Exp $
 PostgreSQL documentation
 -->
 
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ... ]
+TRUNCATE [ TABLE ] [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [, ... ]
     [ RESTART IDENTITY | CONTINUE IDENTITY ] [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
@@ -47,7 +47,10 @@ TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ... ]
     <term><replaceable class="PARAMETER">name</replaceable></term>
     <listitem>
      <para>
-      The name (optionally schema-qualified) of a table to be truncated.
+      The name (optionally schema-qualified) of a table to be
+      truncated.  If <literal>ONLY</> is specified, only that table is
+      truncated.  If <literal>ONLY</> is not specified, the table and
+      all its descendant tables (if any) are truncated.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 7d0879da55..27805be5d8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/lockcmds.c,v 1.20 2009/01/01 17:23:38 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/lockcmds.c,v 1.21 2009/01/12 08:54:26 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,6 +18,8 @@
 #include "catalog/namespace.h"
 #include "commands/lockcmds.h"
 #include "miscadmin.h"
+#include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "utils/acl.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
@@ -40,38 +42,48 @@ LockTableCommand(LockStmt *lockstmt)
 	{
 		RangeVar   *relation = lfirst(p);
 		Oid			reloid;
-		AclResult	aclresult;
-		Relation	rel;
+		bool		recurse = interpretInhOption(relation->inhOpt);
+		List	   *children_and_self;
+		ListCell   *child;
 
-		/*
-		 * We don't want to open the relation until we've checked privilege.
-		 * So, manually get the relation OID.
-		 */
 		reloid = RangeVarGetRelid(relation, false);
 
-		if (lockstmt->mode == AccessShareLock)
-			aclresult = pg_class_aclcheck(reloid, GetUserId(),
-										  ACL_SELECT);
+		if (recurse)
+			children_and_self = find_all_inheritors(reloid);
 		else
-			aclresult = pg_class_aclcheck(reloid, GetUserId(),
-										  ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE);
+			children_and_self = list_make1_oid(reloid);
 
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult, ACL_KIND_CLASS,
-						   get_rel_name(reloid));
+		foreach(child, children_and_self)
+		{
+			Oid			childreloid = lfirst_oid(child);
+			Relation	rel;
+			AclResult	aclresult;
 
-		if (lockstmt->nowait)
-			rel = relation_open_nowait(reloid, lockstmt->mode);
-		else
-			rel = relation_open(reloid, lockstmt->mode);
+			/* We don't want to open the relation until we've checked privilege. */
+			if (lockstmt->mode == AccessShareLock)
+				aclresult = pg_class_aclcheck(childreloid, GetUserId(),
+											  ACL_SELECT);
+			else
+				aclresult = pg_class_aclcheck(childreloid, GetUserId(),
+											  ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE);
+
+			if (aclresult != ACLCHECK_OK)
+				aclcheck_error(aclresult, ACL_KIND_CLASS,
+							   get_rel_name(childreloid));
+
+			if (lockstmt->nowait)
+				rel = relation_open_nowait(childreloid, lockstmt->mode);
+			else
+				rel = relation_open(childreloid, lockstmt->mode);
 
-		/* Currently, we only allow plain tables to be locked */
-		if (rel->rd_rel->relkind != RELKIND_RELATION)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("\"%s\" is not a table",
-							relation->relname)));
+			/* Currently, we only allow plain tables to be locked */
+			if (rel->rd_rel->relkind != RELKIND_RELATION)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("\"%s\" is not a table",
+								get_rel_name(childreloid))));
 
-		relation_close(rel, NoLock);	/* close rel, keep lock */
+			relation_close(rel, NoLock);	/* close rel, keep lock */
+		}
 	}
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f932b65e1f..061d45c30a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.276 2009/01/01 17:23:39 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.277 2009/01/12 08:54:26 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -772,17 +772,41 @@ ExecuteTruncate(TruncateStmt *stmt)
 	{
 		RangeVar   *rv = lfirst(cell);
 		Relation	rel;
+		bool		recurse = interpretInhOption(rv->inhOpt);
+		Oid			myrelid;
 
 		rel = heap_openrv(rv, AccessExclusiveLock);
+		myrelid = RelationGetRelid(rel);
 		/* don't throw error for "TRUNCATE foo, foo" */
-		if (list_member_oid(relids, RelationGetRelid(rel)))
+		if (list_member_oid(relids, myrelid))
 		{
 			heap_close(rel, AccessExclusiveLock);
 			continue;
 		}
 		truncate_check_rel(rel);
 		rels = lappend(rels, rel);
-		relids = lappend_oid(relids, RelationGetRelid(rel));
+		relids = lappend_oid(relids, myrelid);
+
+		if (recurse)
+		{
+			ListCell   *child;
+			List	   *children;
+
+			children = find_all_inheritors(myrelid);
+
+			foreach(child, children)
+			{
+				Oid			childrelid = lfirst_oid(child);
+
+				if (list_member_oid(relids, childrelid))
+					continue;
+
+				rel = heap_open(childrelid, AccessExclusiveLock);
+				truncate_check_rel(rel);
+				rels = lappend(rels, rel);
+				relids = lappend_oid(relids, childrelid);
+			}
+		}
 	}
 
 	/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 21b7498341..145e4aac5d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.652 2009/01/07 22:54:45 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.653 2009/01/12 08:54:26 petere Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -284,6 +284,7 @@ static TypeName *TableFuncTypeName(List *columns);
 				execute_param_clause using_clause returning_clause
 				enum_val_list table_func_column_list
 				create_generic_options alter_generic_options
+				relation_expr_list
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target
@@ -3794,7 +3795,7 @@ attrs:		'.' attr_name
  *****************************************************************************/
 
 TruncateStmt:
-			TRUNCATE opt_table qualified_name_list opt_restart_seqs opt_drop_behavior
+			TRUNCATE opt_table relation_expr_list opt_restart_seqs opt_drop_behavior
 				{
 					TruncateStmt *n = makeNode(TruncateStmt);
 					n->relations = $3;
@@ -6558,7 +6559,15 @@ using_clause:
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
-LockStmt:	LOCK_P opt_table qualified_name_list opt_lock opt_nowait
+
+/*****************************************************************************
+ *
+ *		QUERY:
+ *				LOCK TABLE
+ *
+ *****************************************************************************/
+
+LockStmt:	LOCK_P opt_table relation_expr_list opt_lock opt_nowait
 				{
 					LockStmt *n = makeNode(LockStmt);
 
@@ -7487,6 +7496,12 @@ relation_expr:
 		;
 
 
+relation_expr_list:
+			relation_expr							{ $$ = list_make1($1); }
+			| relation_expr_list ',' relation_expr	{ $$ = lappend($1, $3); }
+		;
+
+
 /*
  * Given "UPDATE foo set set ...", we have to decide without looking any
  * further ahead whether the first "set" is an alias or the UPDATE's SET
diff --git a/src/test/regress/expected/truncate.out b/src/test/regress/expected/truncate.out
index 3055679f7e..7f43df710c 100644
--- a/src/test/regress/expected/truncate.out
+++ b/src/test/regress/expected/truncate.out
@@ -141,6 +141,150 @@ SELECT * FROM trunc_e;
 (0 rows)
 
 DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
+-- Test TRUNCATE with inheritance
+CREATE TABLE trunc_f (col1 integer primary key);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "trunc_f_pkey" for table "trunc_f"
+INSERT INTO trunc_f VALUES (1);
+INSERT INTO trunc_f VALUES (2);
+CREATE TABLE trunc_fa (col2a text) INHERITS (trunc_f);
+INSERT INTO trunc_fa VALUES (3, 'three');
+CREATE TABLE trunc_fb (col2b int) INHERITS (trunc_f);
+INSERT INTO trunc_fb VALUES (4, 444);
+CREATE TABLE trunc_faa (col3 text) INHERITS (trunc_fa);
+INSERT INTO trunc_faa VALUES (5, 'five', 'FIVE');
+BEGIN;
+SELECT * FROM trunc_f;
+ col1 
+------
+    1
+    2
+    3
+    4
+    5
+(5 rows)
+
+TRUNCATE trunc_f;
+SELECT * FROM trunc_f;
+ col1 
+------
+(0 rows)
+
+ROLLBACK;
+BEGIN;
+SELECT * FROM trunc_f;
+ col1 
+------
+    1
+    2
+    3
+    4
+    5
+(5 rows)
+
+TRUNCATE ONLY trunc_f;
+SELECT * FROM trunc_f;
+ col1 
+------
+    3
+    4
+    5
+(3 rows)
+
+ROLLBACK;
+BEGIN;
+SELECT * FROM trunc_f;
+ col1 
+------
+    1
+    2
+    3
+    4
+    5
+(5 rows)
+
+SELECT * FROM trunc_fa;
+ col1 | col2a 
+------+-------
+    3 | three
+    5 | five
+(2 rows)
+
+SELECT * FROM trunc_faa;
+ col1 | col2a | col3 
+------+-------+------
+    5 | five  | FIVE
+(1 row)
+
+TRUNCATE ONLY trunc_fb, ONLY trunc_fa;
+SELECT * FROM trunc_f;
+ col1 
+------
+    1
+    2
+    5
+(3 rows)
+
+SELECT * FROM trunc_fa;
+ col1 | col2a 
+------+-------
+    5 | five
+(1 row)
+
+SELECT * FROM trunc_faa;
+ col1 | col2a | col3 
+------+-------+------
+    5 | five  | FIVE
+(1 row)
+
+ROLLBACK;
+BEGIN;
+SELECT * FROM trunc_f;
+ col1 
+------
+    1
+    2
+    3
+    4
+    5
+(5 rows)
+
+SELECT * FROM trunc_fa;
+ col1 | col2a 
+------+-------
+    3 | three
+    5 | five
+(2 rows)
+
+SELECT * FROM trunc_faa;
+ col1 | col2a | col3 
+------+-------+------
+    5 | five  | FIVE
+(1 row)
+
+TRUNCATE ONLY trunc_fb, trunc_fa;
+SELECT * FROM trunc_f;
+ col1 
+------
+    1
+    2
+(2 rows)
+
+SELECT * FROM trunc_fa;
+ col1 | col2a 
+------+-------
+(0 rows)
+
+SELECT * FROM trunc_faa;
+ col1 | col2a | col3 
+------+-------+------
+(0 rows)
+
+ROLLBACK;
+DROP TABLE trunc_f CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table trunc_fa
+drop cascades to table trunc_faa
+drop cascades to table trunc_fb
 -- Test ON TRUNCATE triggers
 CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);
 CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text,
diff --git a/src/test/regress/sql/truncate.sql b/src/test/regress/sql/truncate.sql
index 3cce3ee314..b348e94c48 100644
--- a/src/test/regress/sql/truncate.sql
+++ b/src/test/regress/sql/truncate.sql
@@ -78,6 +78,55 @@ SELECT * FROM trunc_e;
 
 DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
 
+-- Test TRUNCATE with inheritance
+
+CREATE TABLE trunc_f (col1 integer primary key);
+INSERT INTO trunc_f VALUES (1);
+INSERT INTO trunc_f VALUES (2);
+
+CREATE TABLE trunc_fa (col2a text) INHERITS (trunc_f);
+INSERT INTO trunc_fa VALUES (3, 'three');
+
+CREATE TABLE trunc_fb (col2b int) INHERITS (trunc_f);
+INSERT INTO trunc_fb VALUES (4, 444);
+
+CREATE TABLE trunc_faa (col3 text) INHERITS (trunc_fa);
+INSERT INTO trunc_faa VALUES (5, 'five', 'FIVE');
+
+BEGIN;
+SELECT * FROM trunc_f;
+TRUNCATE trunc_f;
+SELECT * FROM trunc_f;
+ROLLBACK;
+
+BEGIN;
+SELECT * FROM trunc_f;
+TRUNCATE ONLY trunc_f;
+SELECT * FROM trunc_f;
+ROLLBACK;
+
+BEGIN;
+SELECT * FROM trunc_f;
+SELECT * FROM trunc_fa;
+SELECT * FROM trunc_faa;
+TRUNCATE ONLY trunc_fb, ONLY trunc_fa;
+SELECT * FROM trunc_f;
+SELECT * FROM trunc_fa;
+SELECT * FROM trunc_faa;
+ROLLBACK;
+
+BEGIN;
+SELECT * FROM trunc_f;
+SELECT * FROM trunc_fa;
+SELECT * FROM trunc_faa;
+TRUNCATE ONLY trunc_fb, trunc_fa;
+SELECT * FROM trunc_f;
+SELECT * FROM trunc_fa;
+SELECT * FROM trunc_faa;
+ROLLBACK;
+
+DROP TABLE trunc_f CASCADE;
+
 -- Test ON TRUNCATE triggers
 
 CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);