From a0b76dc662efde6e02921c2d16e06418483b7534 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 8 Sep 2008 00:47:41 +0000
Subject: [PATCH] Create a separate grantable privilege for TRUNCATE, rather
 than having it be always owner-only.  The TRUNCATE privilege works
 identically to the DELETE privilege so far as interactions with the rest of
 the system go.

Robert Haas
---
 doc/src/sgml/ddl.sgml                      |  4 +--
 doc/src/sgml/func.sgml                     |  4 +--
 doc/src/sgml/information_schema.sgml       | 14 ++++-----
 doc/src/sgml/ref/grant.sgml                | 26 +++++++++++-----
 doc/src/sgml/ref/lock.sgml                 |  5 +--
 doc/src/sgml/ref/revoke.sgml               |  4 +--
 doc/src/sgml/ref/truncate.sgml             |  5 +--
 doc/src/sgml/user-manag.sgml               |  4 +--
 src/backend/catalog/aclchk.c               | 10 ++++--
 src/backend/catalog/information_schema.sql | 13 ++++++--
 src/backend/commands/lockcmds.c            |  4 +--
 src/backend/commands/tablecmds.c           | 10 ++++--
 src/backend/utils/adt/acl.c                | 12 +++++++-
 src/bin/pg_dump/dumputils.c                |  4 ++-
 src/bin/psql/tab-complete.c                |  8 ++---
 src/include/catalog/catversion.h           |  4 +--
 src/include/nodes/parsenodes.h             |  4 +--
 src/include/utils/acl.h                    |  7 +++--
 src/test/regress/expected/dependency.out   | 22 ++++++-------
 src/test/regress/expected/privileges.out   | 36 ++++++++++++++++++++--
 src/test/regress/sql/dependency.sql        |  2 +-
 src/test/regress/sql/privileges.sql        | 16 +++++++++-
 22 files changed, 153 insertions(+), 65 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 183d1e89f5f..94341df0f4b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/ddl.sgml,v 1.82 2008/05/09 23:32:03 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/ddl.sgml,v 1.83 2008/09/08 00:47:40 tgl Exp $ -->
 
 <chapter id="ddl">
  <title>Data Definition</title>
@@ -1356,7 +1356,7 @@ ALTER TABLE products RENAME TO items;
   <para>
    There are several different privileges: <literal>SELECT</>,
    <literal>INSERT</>, <literal>UPDATE</>, <literal>DELETE</>,
-   <literal>REFERENCES</>, <literal>TRIGGER</>,
+   <literal>TRUNCATE</>, <literal>REFERENCES</>, <literal>TRIGGER</>,
    <literal>CREATE</>, <literal>CONNECT</>, <literal>TEMPORARY</>,
    <literal>EXECUTE</>, and <literal>USAGE</>.
    The privileges applicable to a particular
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c03863af99a..cb4e6f991b1 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.445 2008/09/07 01:29:36 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.446 2008/09/08 00:47:40 tgl Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
@@ -11369,7 +11369,7 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
     The desired access privilege type
     is specified by a text string, which must evaluate to one of the
     values <literal>SELECT</literal>, <literal>INSERT</literal>,
-    <literal>UPDATE</literal>, <literal>DELETE</literal>,
+    <literal>UPDATE</literal>, <literal>DELETE</literal>, <literal>TRUNCATE</>,
     <literal>REFERENCES</literal>, or <literal>TRIGGER</literal>.
     (Case of the string is not significant, however.)
     An example is:
diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml
index d4d51a34a03..7289e0bd456 100644
--- a/doc/src/sgml/information_schema.sgml
+++ b/doc/src/sgml/information_schema.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/information_schema.sgml,v 1.33 2007/02/20 23:14:19 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/information_schema.sgml,v 1.34 2008/09/08 00:47:40 tgl Exp $ -->
 
 <chapter id="information-schema">
  <title>The Information Schema</title>
@@ -2820,9 +2820,9 @@ ORDER BY c.ordinal_position;
       <entry><type>character_data</type></entry>
       <entry>
        Type of the privilege: <literal>SELECT</literal>,
-       <literal>DELETE</literal>, <literal>INSERT</literal>,
-       <literal>UPDATE</literal>, <literal>REFERENCES</literal>,
-       or <literal>TRIGGER</literal>
+       <literal>INSERT</literal>, <literal>UPDATE</literal>,
+       <literal>DELETE</literal>, <literal>TRUNCATE</literal>,
+       <literal>REFERENCES</literal>, or <literal>TRIGGER</literal>
       </entry>
      </row>
 
@@ -4406,9 +4406,9 @@ ORDER BY c.ordinal_position;
       <entry><type>character_data</type></entry>
       <entry>
        Type of the privilege: <literal>SELECT</literal>,
-       <literal>DELETE</literal>, <literal>INSERT</literal>,
-       <literal>UPDATE</literal>, <literal>REFERENCES</literal>,
-       or <literal>TRIGGER</literal>
+       <literal>INSERT</literal>, <literal>UPDATE</literal>,
+       <literal>DELETE</literal>, <literal>TRUNCATE</literal>,
+       <literal>REFERENCES</literal>, or <literal>TRIGGER</literal>
       </entry>
      </row>
 
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index c80a33c5074..295a7646575 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/grant.sgml,v 1.70 2008/07/03 15:59:55 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/grant.sgml,v 1.71 2008/09/08 00:47:40 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -20,7 +20,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-GRANT { { SELECT | INSERT | UPDATE | DELETE | REFERENCES | TRIGGER }
+GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
     [,...] | ALL [ PRIVILEGES ] }
     ON [ TABLE ] <replaceable class="PARAMETER">tablename</replaceable> [, ...]
     TO { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
@@ -192,6 +192,16 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term>TRUNCATE</term>
+     <listitem>
+      <para>
+       Allows <xref linkend="sql-truncate" endterm="sql-truncate-title"> on
+       the specified table.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term>REFERENCES</term>
      <listitem>
@@ -421,8 +431,8 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
 =&gt; \z mytable
                 Access privileges
  Schema |  Name   | Type  |  Access privileges   
---------+---------+-------+----------------------
- public | mytable | table | miriam=arwdxt/miriam
+--------+---------+-------+-----------------------
+ public | mytable | table | miriam=arwdDxt/miriam
                           : =r/miriam
                           : admin=arw/miriam
 (1 row)
@@ -436,6 +446,7 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
                   w -- UPDATE ("write")
                   a -- INSERT ("append")
                   d -- DELETE
+                  D -- TRUNCATE
                   x -- REFERENCES
                   t -- TRIGGER
                   X -- EXECUTE
@@ -443,7 +454,7 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
                   C -- CREATE
                   c -- CONNECT
                   T -- TEMPORARY
-             arwdxt -- ALL PRIVILEGES (for tables)
+            arwdDxt -- ALL PRIVILEGES (for tables)
                   * -- grant option for preceding privilege
 
               /yyyy -- role that granted this privilege
@@ -466,7 +477,7 @@ GRANT SELECT, UPDATE, INSERT ON mytable TO admin;
     object type, as explained above.  The first <command>GRANT</> or
     <command>REVOKE</> on an object
     will instantiate the default privileges (producing, for example,
-    <literal>{miriam=arwdxt/miriam}</>) and then modify them per the
+    <literal>{miriam=arwdDxt/miriam}</>) and then modify them per the
     specified request.
    </para>
 
@@ -524,7 +535,8 @@ GRANT admins TO joe;
    <para>
     <productname>PostgreSQL</productname> allows an object owner to revoke his
     own ordinary privileges: for example, a table owner can make the table
-    read-only to himself by revoking his own INSERT, UPDATE, and DELETE
+    read-only to himself by revoking his own <literal>INSERT</>,
+    <literal>UPDATE</>, <literal>DELETE</>, and <literal>TRUNCATE</>
     privileges.  This is not possible according to the SQL standard.  The
     reason is that <productname>PostgreSQL</productname> treats the owner's
     privileges as having been granted by the owner to himself; therefore he
diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml
index 0c2cb8fe2e4..d32e6d364c8 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.48 2006/09/16 00:30:19 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/lock.sgml,v 1.49 2008/09/08 00:47:40 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -155,7 +155,8 @@ where <replaceable class="PARAMETER">lockmode</replaceable> is one of:
    <para>
     <literal>LOCK TABLE ... IN ACCESS SHARE MODE</> requires <literal>SELECT</>
     privileges on the target table.  All other forms of <command>LOCK</>
-    require <literal>UPDATE</> and/or <literal>DELETE</> privileges.
+    require at least one of <literal>UPDATE</>, <literal>DELETE</>, or
+    <literal>TRUNCATE</> privileges.
    </para>
 
    <para>
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 190300d5339..86cee9d760c 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/revoke.sgml,v 1.47 2008/03/03 19:17:27 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/revoke.sgml,v 1.48 2008/09/08 00:47:40 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -21,7 +21,7 @@ PostgreSQL documentation
  <refsynopsisdiv>
 <synopsis>
 REVOKE [ GRANT OPTION FOR ]
-    { { SELECT | INSERT | UPDATE | DELETE | REFERENCES | TRIGGER }
+    { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
     [,...] | ALL [ PRIVILEGES ] }
     ON [ TABLE ] <replaceable class="PARAMETER">tablename</replaceable> [, ...]
     FROM { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...]
diff --git a/doc/src/sgml/ref/truncate.sgml b/doc/src/sgml/ref/truncate.sgml
index 152b6640d8f..f4ef810ce31 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.27 2008/05/17 23:36:27 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/truncate.sgml,v 1.28 2008/09/08 00:47:40 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -97,7 +97,8 @@ TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ... ]
   <title>Notes</title>
 
   <para>
-   Only the owner of a table can <command>TRUNCATE</> it.
+   You must have the <literal>TRUNCATE</literal> privilege on a table
+   to truncate it.
   </para>
 
   <para>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 871aef702c7..d4d9fcc5150 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/user-manag.sgml,v 1.39 2007/02/01 00:28:18 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/user-manag.sgml,v 1.40 2008/09/08 00:47:40 tgl Exp $ -->
 
 <chapter id="user-manag">
  <title>Database Roles and Privileges</title>
@@ -293,7 +293,7 @@ ALTER ROLE myname SET enable_indexscan TO off;
    granted.
    There are several different kinds of privilege: <literal>SELECT</>,
    <literal>INSERT</>, <literal>UPDATE</>, <literal>DELETE</>,
-   <literal>REFERENCES</>, <literal>TRIGGER</>,
+   <literal>TRUNCATE</>, <literal>REFERENCES</>, <literal>TRIGGER</>,
    <literal>CREATE</>, <literal>CONNECT</>, <literal>TEMPORARY</>,
    <literal>EXECUTE</>, and <literal>USAGE</>.
    For more information on the different types of privileges supported by
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index e71f944f1b3..941ee62da77 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.147 2008/06/19 00:46:03 alvherre Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.148 2008/09/08 00:47:40 tgl Exp $
  *
  * NOTES
  *	  See acl.h.
@@ -1331,6 +1331,8 @@ string_to_privilege(const char *privname)
 		return ACL_UPDATE;
 	if (strcmp(privname, "delete") == 0)
 		return ACL_DELETE;
+	if (strcmp(privname, "truncate") == 0)
+		return ACL_TRUNCATE;
 	if (strcmp(privname, "references") == 0)
 		return ACL_REFERENCES;
 	if (strcmp(privname, "trigger") == 0)
@@ -1368,6 +1370,8 @@ privilege_to_string(AclMode privilege)
 			return "UPDATE";
 		case ACL_DELETE:
 			return "DELETE";
+		case ACL_TRUNCATE:
+			return "TRUNCATE";
 		case ACL_REFERENCES:
 			return "REFERENCES";
 		case ACL_TRIGGER:
@@ -1582,7 +1586,7 @@ pg_class_aclmask(Oid table_oid, Oid roleid,
 	 * protected in this way.  Assume the view rules can take care of
 	 * themselves.	ACL_USAGE is if we ever have system sequences.
 	 */
-	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_USAGE)) &&
+	if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
 		IsSystemClass(classForm) &&
 		classForm->relkind != RELKIND_VIEW &&
 		!has_rolcatupdate(roleid) &&
@@ -1591,7 +1595,7 @@ pg_class_aclmask(Oid table_oid, Oid roleid,
 #ifdef ACLDEBUG
 		elog(DEBUG2, "permission denied for system catalog update");
 #endif
-		mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_USAGE);
+		mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE);
 	}
 
 	/*
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index b10a2e8ea69..970b48b7dff 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -4,7 +4,7 @@
  *
  * Copyright (c) 2003-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.45 2008/07/18 03:32:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.46 2008/09/08 00:47:40 tgl Exp $
  */
 
 /*
@@ -1214,9 +1214,10 @@ CREATE VIEW role_table_grants AS
          pg_authid u_grantor,
          pg_authid g_grantee,
          (SELECT 'SELECT' UNION ALL
-          SELECT 'DELETE' UNION ALL
           SELECT 'INSERT' UNION ALL
           SELECT 'UPDATE' UNION ALL
+          SELECT 'DELETE' UNION ALL
+          SELECT 'TRUNCATE' UNION ALL
           SELECT 'REFERENCES' UNION ALL
           SELECT 'TRIGGER') AS pr (type)
 
@@ -1728,6 +1729,7 @@ CREATE VIEW table_constraints AS
                OR has_table_privilege(r.oid, 'INSERT')
                OR has_table_privilege(r.oid, 'UPDATE')
                OR has_table_privilege(r.oid, 'DELETE')
+               OR has_table_privilege(r.oid, 'TRUNCATE')
                OR has_table_privilege(r.oid, 'REFERENCES')
                OR has_table_privilege(r.oid, 'TRIGGER') )
 
@@ -1761,6 +1763,7 @@ CREATE VIEW table_constraints AS
                OR has_table_privilege(r.oid, 'INSERT')
                OR has_table_privilege(r.oid, 'UPDATE')
                OR has_table_privilege(r.oid, 'DELETE')
+               OR has_table_privilege(r.oid, 'TRUNCATE')
                OR has_table_privilege(r.oid, 'REFERENCES')
                OR has_table_privilege(r.oid, 'TRIGGER') );
 
@@ -1802,9 +1805,10 @@ CREATE VIEW table_privileges AS
            SELECT 0::oid, 'PUBLIC'
          ) AS grantee (oid, rolname),
          (SELECT 'SELECT' UNION ALL
-          SELECT 'DELETE' UNION ALL
           SELECT 'INSERT' UNION ALL
           SELECT 'UPDATE' UNION ALL
+          SELECT 'DELETE' UNION ALL
+          SELECT 'TRUNCATE' UNION ALL
           SELECT 'REFERENCES' UNION ALL
           SELECT 'TRIGGER') AS pr (type)
 
@@ -1861,6 +1865,7 @@ CREATE VIEW tables AS
                OR has_table_privilege(c.oid, 'INSERT')
                OR has_table_privilege(c.oid, 'UPDATE')
                OR has_table_privilege(c.oid, 'DELETE')
+               OR has_table_privilege(c.oid, 'TRUNCATE')
                OR has_table_privilege(c.oid, 'REFERENCES')
                OR has_table_privilege(c.oid, 'TRIGGER') );
 
@@ -1982,6 +1987,7 @@ CREATE VIEW triggers AS
                OR has_table_privilege(c.oid, 'INSERT')
                OR has_table_privilege(c.oid, 'UPDATE')
                OR has_table_privilege(c.oid, 'DELETE')
+               OR has_table_privilege(c.oid, 'TRUNCATE')
                OR has_table_privilege(c.oid, 'REFERENCES')
                OR has_table_privilege(c.oid, 'TRIGGER') );
 
@@ -2180,6 +2186,7 @@ CREATE VIEW views AS
                OR has_table_privilege(c.oid, 'INSERT')
                OR has_table_privilege(c.oid, 'UPDATE')
                OR has_table_privilege(c.oid, 'DELETE')
+               OR has_table_privilege(c.oid, 'TRUNCATE')
                OR has_table_privilege(c.oid, 'REFERENCES')
                OR has_table_privilege(c.oid, 'TRIGGER') );
 
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index bad0afc77b4..c5edc2eefcf 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.18 2008/06/19 00:46:04 alvherre Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/lockcmds.c,v 1.19 2008/09/08 00:47:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,7 +54,7 @@ LockTableCommand(LockStmt *lockstmt)
 										  ACL_SELECT);
 		else
 			aclresult = pg_class_aclcheck(reloid, GetUserId(),
-										  ACL_UPDATE | ACL_DELETE);
+										  ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE);
 
 		if (aclresult != ACLCHECK_OK)
 			aclcheck_error(aclresult, ACL_KIND_CLASS,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8b7b1015955..62aeb2f3e6a 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.265 2008/09/01 20:42:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.266 2008/09/08 00:47:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -989,6 +989,8 @@ ExecuteTruncate(TruncateStmt *stmt)
 static void
 truncate_check_rel(Relation rel)
 {
+	AclResult	aclresult;
+
 	/* Only allow truncate on regular tables */
 	if (rel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
@@ -997,8 +999,10 @@ truncate_check_rel(Relation rel)
 						RelationGetRelationName(rel))));
 
 	/* Permissions checks */
-	if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
-		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+	aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
+								  ACL_TRUNCATE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_CLASS,
 					   RelationGetRelationName(rel));
 
 	if (!allowSystemTableMods && IsSystemRelation(rel))
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 3cf54e58750..d0d07751188 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.140 2008/03/25 22:42:43 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.141 2008/09/08 00:47:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -265,6 +265,9 @@ aclparse(const char *s, AclItem *aip)
 			case ACL_DELETE_CHR:
 				read = ACL_DELETE;
 				break;
+			case ACL_TRUNCATE_CHR:
+				read = ACL_TRUNCATE;
+				break;
 			case ACL_REFERENCES_CHR:
 				read = ACL_REFERENCES;
 				break;
@@ -1323,6 +1326,8 @@ convert_priv_string(text *priv_type_text)
 		return ACL_UPDATE;
 	if (pg_strcasecmp(priv_type, "DELETE") == 0)
 		return ACL_DELETE;
+	if (pg_strcasecmp(priv_type, "TRUNCATE") == 0)
+		return ACL_TRUNCATE;
 	if (pg_strcasecmp(priv_type, "REFERENCES") == 0)
 		return ACL_REFERENCES;
 	if (pg_strcasecmp(priv_type, "TRIGGER") == 0)
@@ -1548,6 +1553,11 @@ convert_table_priv_string(text *priv_type_text)
 	if (pg_strcasecmp(priv_type, "DELETE WITH GRANT OPTION") == 0)
 		return ACL_GRANT_OPTION_FOR(ACL_DELETE);
 
+	if (pg_strcasecmp(priv_type, "TRUNCATE") == 0)
+		return ACL_TRUNCATE;
+	if (pg_strcasecmp(priv_type, "TRUNCATE WITH GRANT OPTION") == 0)
+		return ACL_GRANT_OPTION_FOR(ACL_TRUNCATE);
+
 	if (pg_strcasecmp(priv_type, "REFERENCES") == 0)
 		return ACL_REFERENCES;
 	if (pg_strcasecmp(priv_type, "REFERENCES WITH GRANT OPTION") == 0)
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index b750d1e900a..bf6fa6e445b 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.c,v 1.40 2008/01/01 19:45:55 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.c,v 1.41 2008/09/08 00:47:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -659,6 +659,8 @@ do { \
 				CONVERT_PRIV('x', "REFERENCES");
 				CONVERT_PRIV('t', "TRIGGER");
 			}
+			if (remoteVersion >= 80400)
+				CONVERT_PRIV('D', "TRUNCATE");
 		}
 
 		/* UPDATE */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 97d9f13d7a1..fbccb2b0aef 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.171 2008/08/16 01:36:35 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.172 2008/09/08 00:47:40 tgl Exp $
  */
 
 /*----------------------------------------------------------------------
@@ -1610,9 +1610,9 @@ psql_completion(char *text, int start, int end)
 			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
 	{
 		static const char *const list_privileg[] =
-		{"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "REFERENCES",
-			"TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
-		"ALL", NULL};
+		{"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES",
+		 "TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
+		 "ALL", NULL};
 
 		COMPLETE_WITH_LIST(list_privileg);
 	}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index fd596bd52b8..272093e5a00 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.483 2008/09/06 00:01:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.484 2008/09/08 00:47:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200809051
+#define CATALOG_VERSION_NO	200809071
 
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2a4f6f1421a..8b8757659b7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.374 2008/09/01 20:42:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.375 2008/09/08 00:47:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -63,7 +63,7 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_SELECT		(1<<1)
 #define ACL_UPDATE		(1<<2)
 #define ACL_DELETE		(1<<3)
-/* #define ACL_RULE		(1<<4)	unused, available */
+#define ACL_TRUNCATE	(1<<4)
 #define ACL_REFERENCES	(1<<5)
 #define ACL_TRIGGER		(1<<6)
 #define ACL_EXECUTE		(1<<7)	/* for functions */
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index f4940a80238..553ceabee3b 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.103 2008/01/01 19:45:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.104 2008/09/08 00:47:41 tgl Exp $
  *
  * NOTES
  *	  An ACL array is simply an array of AclItems, representing the union
@@ -128,6 +128,7 @@ typedef ArrayType Acl;
 #define ACL_SELECT_CHR			'r'		/* formerly known as "read" */
 #define ACL_UPDATE_CHR			'w'		/* formerly known as "write" */
 #define ACL_DELETE_CHR			'd'
+#define ACL_TRUNCATE_CHR		'D'		/* super-delete, as it were */
 #define ACL_REFERENCES_CHR		'x'
 #define ACL_TRIGGER_CHR			't'
 #define ACL_EXECUTE_CHR			'X'
@@ -137,12 +138,12 @@ typedef ArrayType Acl;
 #define ACL_CONNECT_CHR			'c'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdRxtXUCTc"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTc"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
  */
-#define ACL_ALL_RIGHTS_RELATION		(ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_REFERENCES|ACL_TRIGGER)
+#define ACL_ALL_RIGHTS_RELATION		(ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER)
 #define ACL_ALL_RIGHTS_SEQUENCE		(ACL_USAGE|ACL_SELECT|ACL_UPDATE)
 #define ACL_ALL_RIGHTS_DATABASE		(ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
 #define ACL_ALL_RIGHTS_FUNCTION		(ACL_EXECUTE)
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 4c6a6af3042..178f4221fca 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -21,7 +21,7 @@ DETAIL:  access to table deptest
 REVOKE SELECT ON deptest FROM GROUP regression_group;
 DROP GROUP regression_group;
 -- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, RULE, REFERENCES ON deptest FROM regression_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regression_user;
 DROP USER regression_user;
 ERROR:  role "regression_user" cannot be dropped because some objects depend on it
 DETAIL:  access to table deptest
@@ -68,21 +68,21 @@ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "deptest_pkey" fo
 GRANT ALL ON deptest1 TO regression_user2;
 RESET SESSION AUTHORIZATION;
 \z deptest1
-                             Access privileges
- Schema |   Name   | Type  |               Access privileges                
---------+----------+-------+------------------------------------------------
- public | deptest1 | table | regression_user0=arwdxt/regression_user0
-                           : regression_user1=a*r*w*d*x*t*/regression_user0
-                           : regression_user2=arwdxt/regression_user1
+                              Access privileges
+ Schema |   Name   | Type  |                Access privileges                 
+--------+----------+-------+--------------------------------------------------
+ public | deptest1 | table | regression_user0=arwdDxt/regression_user0
+                           : regression_user1=a*r*w*d*D*x*t*/regression_user0
+                           : regression_user2=arwdDxt/regression_user1
 (1 row)
 
 DROP OWNED BY regression_user1;
 -- all grants revoked
 \z deptest1
-                          Access privileges
- Schema |   Name   | Type  |            Access privileges             
---------+----------+-------+------------------------------------------
- public | deptest1 | table | regression_user0=arwdxt/regression_user0
+                           Access privileges
+ Schema |   Name   | Type  |             Access privileges             
+--------+----------+-------+-------------------------------------------
+ public | deptest1 | table | regression_user0=arwdDxt/regression_user0
 (1 row)
 
 -- table was dropped
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 32dd625f9bf..21f9fc26fd9 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -10,14 +10,16 @@ DROP ROLE IF EXISTS regressuser1;
 DROP ROLE IF EXISTS regressuser2;
 DROP ROLE IF EXISTS regressuser3;
 DROP ROLE IF EXISTS regressuser4;
+DROP ROLE IF EXISTS regressuser5;
 RESET client_min_messages;
 -- test proper begins here
 CREATE USER regressuser1;
 CREATE USER regressuser2;
 CREATE USER regressuser3;
 CREATE USER regressuser4;
-CREATE USER regressuser4;	-- duplicate
-ERROR:  role "regressuser4" already exists
+CREATE USER regressuser5;
+CREATE USER regressuser5;	-- duplicate
+ERROR:  role "regressuser5" already exists
 CREATE GROUP regressgroup1;
 CREATE GROUP regressgroup2 WITH USER regressuser1, regressuser2;
 ALTER GROUP regressgroup1 ADD USER regressuser4;
@@ -42,6 +44,7 @@ SELECT * FROM atest1;
 INSERT INTO atest1 VALUES (1, 'one');
 DELETE FROM atest1;
 UPDATE atest1 SET a = 1 WHERE b = 'blech';
+TRUNCATE atest1;
 LOCK atest1 IN ACCESS EXCLUSIVE MODE;
 REVOKE ALL ON atest1 FROM PUBLIC;
 SELECT * FROM atest1;
@@ -60,6 +63,7 @@ CREATE TABLE atest2 (col1 varchar(10), col2 boolean);
 GRANT SELECT ON atest2 TO regressuser2;
 GRANT UPDATE ON atest2 TO regressuser3;
 GRANT INSERT ON atest2 TO regressuser4;
+GRANT TRUNCATE ON atest2 TO regressuser5;
 SET SESSION AUTHORIZATION regressuser2;
 SELECT session_user, current_user;
  session_user | current_user 
@@ -96,6 +100,8 @@ SELECT * FROM atest2 FOR UPDATE; -- fail
 ERROR:  permission denied for relation atest2
 DELETE FROM atest2; -- fail
 ERROR:  permission denied for relation atest2
+TRUNCATE atest2; -- fail
+ERROR:  permission denied for relation atest2
 LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- fail
 ERROR:  permission denied for relation atest2
 COPY atest2 FROM stdin; -- fail
@@ -147,6 +153,8 @@ SELECT * FROM atest2 FOR UPDATE; -- fail
 ERROR:  permission denied for relation atest2
 DELETE FROM atest2; -- fail
 ERROR:  permission denied for relation atest2
+TRUNCATE atest2; -- fail
+ERROR:  permission denied for relation atest2
 LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- ok
 COPY atest2 FROM stdin; -- fail
 ERROR:  permission denied for relation atest2
@@ -285,6 +293,11 @@ ERROR:  must be owner of function testfunc1
 DROP FUNCTION testfunc1(int); -- ok
 -- restore to sanity
 GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC;
+-- truncate
+SET SESSION AUTHORIZATION regressuser5;
+TRUNCATE atest2; -- ok
+TRUNCATE atest3; -- fail
+ERROR:  permission denied for relation atest3
 -- has_table_privilege function
 -- bad-input checks
 select has_table_privilege(NULL,'pg_authid','select');
@@ -375,6 +388,12 @@ select has_table_privilege('pg_authid','delete');
  t
 (1 row)
 
+select has_table_privilege('pg_authid','truncate');
+ has_table_privilege 
+---------------------
+ t
+(1 row)
+
 select has_table_privilege(t1.oid,'select')
 from (select oid from pg_class where relname = 'pg_authid') as t1;
  has_table_privilege 
@@ -452,6 +471,12 @@ select has_table_privilege('pg_class','delete');
  f
 (1 row)
 
+select has_table_privilege('pg_class','truncate');
+ has_table_privilege 
+---------------------
+ f
+(1 row)
+
 select has_table_privilege(t1.oid,'select')
 from (select oid from pg_class where relname = 'pg_class') as t1;
  has_table_privilege 
@@ -527,6 +552,12 @@ select has_table_privilege('atest1','delete');
  f
 (1 row)
 
+select has_table_privilege('atest1','truncate');
+ has_table_privilege 
+---------------------
+ f
+(1 row)
+
 select has_table_privilege(t1.oid,'select')
 from (select oid from pg_class where relname = 'atest1') as t1;
  has_table_privilege 
@@ -604,3 +635,4 @@ DROP USER regressuser1;
 DROP USER regressuser2;
 DROP USER regressuser3;
 DROP USER regressuser4;
+DROP USER regressuser5;
diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql
index 6f8e0e84d51..c1d81569c69 100644
--- a/src/test/regress/sql/dependency.sql
+++ b/src/test/regress/sql/dependency.sql
@@ -21,7 +21,7 @@ REVOKE SELECT ON deptest FROM GROUP regression_group;
 DROP GROUP regression_group;
 
 -- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, RULE, REFERENCES ON deptest FROM regression_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regression_user;
 DROP USER regression_user;
 
 -- now we are OK to drop him
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 90b4781f724..450d5d9d68d 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -14,6 +14,7 @@ DROP ROLE IF EXISTS regressuser1;
 DROP ROLE IF EXISTS regressuser2;
 DROP ROLE IF EXISTS regressuser3;
 DROP ROLE IF EXISTS regressuser4;
+DROP ROLE IF EXISTS regressuser5;
 
 RESET client_min_messages;
 
@@ -23,7 +24,8 @@ CREATE USER regressuser1;
 CREATE USER regressuser2;
 CREATE USER regressuser3;
 CREATE USER regressuser4;
-CREATE USER regressuser4;	-- duplicate
+CREATE USER regressuser5;
+CREATE USER regressuser5;	-- duplicate
 
 CREATE GROUP regressgroup1;
 CREATE GROUP regressgroup2 WITH USER regressuser1, regressuser2;
@@ -45,6 +47,7 @@ SELECT * FROM atest1;
 INSERT INTO atest1 VALUES (1, 'one');
 DELETE FROM atest1;
 UPDATE atest1 SET a = 1 WHERE b = 'blech';
+TRUNCATE atest1;
 LOCK atest1 IN ACCESS EXCLUSIVE MODE;
 
 REVOKE ALL ON atest1 FROM PUBLIC;
@@ -58,6 +61,7 @@ CREATE TABLE atest2 (col1 varchar(10), col2 boolean);
 GRANT SELECT ON atest2 TO regressuser2;
 GRANT UPDATE ON atest2 TO regressuser3;
 GRANT INSERT ON atest2 TO regressuser4;
+GRANT TRUNCATE ON atest2 TO regressuser5;
 
 
 SET SESSION AUTHORIZATION regressuser2;
@@ -75,6 +79,7 @@ UPDATE atest2 SET col2 = NOT col2; -- fail
 SELECT * FROM atest1 FOR UPDATE; -- ok
 SELECT * FROM atest2 FOR UPDATE; -- fail
 DELETE FROM atest2; -- fail
+TRUNCATE atest2; -- fail
 LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- fail
 COPY atest2 FROM stdin; -- fail
 GRANT ALL ON atest1 TO PUBLIC; -- fail
@@ -99,6 +104,7 @@ UPDATE atest2 SET col2 = true FROM atest1 WHERE atest1.a = 5; -- ok
 SELECT * FROM atest1 FOR UPDATE; -- fail
 SELECT * FROM atest2 FOR UPDATE; -- fail
 DELETE FROM atest2; -- fail
+TRUNCATE atest2; -- fail
 LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- ok
 COPY atest2 FROM stdin; -- fail
 
@@ -205,6 +211,10 @@ DROP FUNCTION testfunc1(int); -- ok
 -- restore to sanity
 GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC;
 
+-- truncate
+SET SESSION AUTHORIZATION regressuser5;
+TRUNCATE atest2; -- ok
+TRUNCATE atest3; -- fail
 
 -- has_table_privilege function
 
@@ -243,6 +253,7 @@ from (select oid from pg_class where relname = 'pg_authid') as t1,
 
 select has_table_privilege('pg_authid','update');
 select has_table_privilege('pg_authid','delete');
+select has_table_privilege('pg_authid','truncate');
 
 select has_table_privilege(t1.oid,'select')
 from (select oid from pg_class where relname = 'pg_authid') as t1;
@@ -272,6 +283,7 @@ from (select oid from pg_class where relname = 'pg_class') as t1,
 
 select has_table_privilege('pg_class','update');
 select has_table_privilege('pg_class','delete');
+select has_table_privilege('pg_class','truncate');
 
 select has_table_privilege(t1.oid,'select')
 from (select oid from pg_class where relname = 'pg_class') as t1;
@@ -298,6 +310,7 @@ from (select oid from pg_class where relname = 'atest1') as t1,
 
 select has_table_privilege('atest1','update');
 select has_table_privilege('atest1','delete');
+select has_table_privilege('atest1','truncate');
 
 select has_table_privilege(t1.oid,'select')
 from (select oid from pg_class where relname = 'atest1') as t1;
@@ -359,3 +372,4 @@ DROP USER regressuser1;
 DROP USER regressuser2;
 DROP USER regressuser3;
 DROP USER regressuser4;
+DROP USER regressuser5;
-- 
GitLab