From 3cb5d6580a335e0b7fcf25da7fcebee3a776edb4 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 22 Jan 2009 20:16:10 +0000
Subject: [PATCH] Support column-level privileges, as required by SQL standard.

Stephen Frost, with help from KaiGai Kohei and others
---
 doc/src/sgml/catalogs.sgml               |  23 +-
 doc/src/sgml/ref/grant.sgml              |  86 ++-
 doc/src/sgml/ref/insert.sgml             |  13 +-
 doc/src/sgml/ref/revoke.sgml             |  22 +-
 doc/src/sgml/ref/select.sgml             |  11 +-
 doc/src/sgml/ref/update.sgml             |   9 +-
 src/backend/access/common/tupdesc.c      |  16 +-
 src/backend/bootstrap/bootstrap.c        |  15 +-
 src/backend/catalog/aclchk.c             | 810 ++++++++++++++++++++---
 src/backend/catalog/dependency.c         |  16 +-
 src/backend/catalog/heap.c               |  23 +-
 src/backend/catalog/index.c              |   6 +-
 src/backend/catalog/pg_operator.c        |   4 +-
 src/backend/catalog/pg_proc.c            |   4 +-
 src/backend/catalog/pg_shdepend.c        | 111 ++--
 src/backend/catalog/pg_type.c            |   4 +-
 src/backend/commands/analyze.c           |   9 +-
 src/backend/commands/tablecmds.c         |  62 +-
 src/backend/commands/tablespace.c        |   4 +-
 src/backend/commands/trigger.c           |  33 +-
 src/backend/commands/tsearchcmds.c       |   4 +-
 src/backend/commands/user.c              |  14 +-
 src/backend/executor/execMain.c          | 115 +++-
 src/backend/nodes/copyfuncs.c            |  18 +-
 src/backend/nodes/equalfuncs.c           |  16 +-
 src/backend/nodes/outfuncs.c             |   6 +-
 src/backend/nodes/readfuncs.c            |  49 +-
 src/backend/optimizer/plan/setrefs.c     |   5 +-
 src/backend/parser/analyze.c             |  32 +-
 src/backend/parser/gram.y                |  97 ++-
 src/backend/parser/parse_clause.c        |  31 +-
 src/backend/parser/parse_expr.c          |   5 +-
 src/backend/parser/parse_relation.c      | 173 ++++-
 src/backend/parser/parse_target.c        |  32 +-
 src/backend/rewrite/rewriteHandler.c     |   6 +-
 src/backend/tcop/utility.c               |   4 +-
 src/backend/utils/adt/acl.c              |  66 +-
 src/backend/utils/cache/relcache.c       |  12 +-
 src/bin/initdb/initdb.c                  |   4 +-
 src/bin/pg_dump/dumputils.c              |  80 ++-
 src/bin/pg_dump/dumputils.h              |   6 +-
 src/bin/pg_dump/pg_dump.c                |  69 +-
 src/bin/pg_dump/pg_dumpall.c             |   6 +-
 src/bin/psql/describe.c                  |  21 +-
 src/include/catalog/catversion.h         |   4 +-
 src/include/catalog/dependency.h         |   9 +-
 src/include/catalog/indexing.h           |   4 +-
 src/include/catalog/pg_attribute.h       | 507 +++++++-------
 src/include/catalog/pg_class.h           |   4 +-
 src/include/catalog/pg_shdepend.h        |  15 +-
 src/include/commands/trigger.h           |   5 +-
 src/include/nodes/nodes.h                |   4 +-
 src/include/nodes/parsenodes.h           |  42 +-
 src/include/parser/parse_node.h          |   7 +-
 src/include/parser/parse_relation.h      |   4 +-
 src/include/utils/acl.h                  |  23 +-
 src/test/regress/expected/dependency.out |  20 +-
 src/test/regress/expected/privileges.out | 143 ++++
 src/test/regress/sql/privileges.sql      |  89 +++
 59 files changed, 2312 insertions(+), 720 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bd267dcb758..4fae50cd326 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.190 2009/01/22 17:27:54 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.191 2009/01/22 20:15:59 tgl Exp $ -->
 <!--
  Documentation of the system catalogs, directed toward PostgreSQL developers
  -->
@@ -1028,6 +1028,16 @@
       </entry>
      </row>
 
+     <row>
+      <entry><structfield>attacl</structfield></entry>
+      <entry><type>aclitem[]</type></entry>
+      <entry></entry>
+      <entry>
+       Column-level access privileges, if any have been granted specifically
+       on this column
+      </entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
@@ -4250,6 +4260,17 @@
       <entry>The OID of the specific dependent object</entry>
      </row>
 
+     <row>
+      <entry><structfield>objsubid</structfield></entry>
+      <entry><type>int4</type></entry>
+      <entry></entry>
+      <entry>
+       For a table column, this is the column number (the
+       <structfield>objid</> and <structfield>classid</> refer to the
+       table itself).  For all other object types, this column is zero
+      </entry>
+     </row>
+
      <row>
       <entry><structfield>refclassid</structfield></entry>
       <entry><type>oid</type></entry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index dafb8ffb523..ceda72c141d 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.73 2008/12/19 16:25:16 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/grant.sgml,v 1.74 2009/01/22 20:15:59 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -26,6 +26,11 @@ GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
     ON [ TABLE ] <replaceable class="PARAMETER">tablename</replaceable> [, ...]
     TO { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
 
+GRANT { { SELECT | INSERT | UPDATE | REFERENCES } ( <replaceable class="PARAMETER">column</replaceable> [, ...] )
+    [,...] | ALL [ PRIVILEGES ] ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) }
+    ON [ TABLE ] <replaceable class="PARAMETER">tablename</replaceable> [, ...]
+    TO { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+
 GRANT { { USAGE | SELECT | UPDATE }
     [,...] | ALL [ PRIVILEGES ] }
     ON SEQUENCE <replaceable class="PARAMETER">sequencename</replaceable> [, ...]
@@ -68,7 +73,7 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
 
   <para>
    The <command>GRANT</command> command has two basic variants: one
-   that grants privileges on a database object (table, view, sequence,
+   that grants privileges on a database object (table, column, view, sequence,
    database, foreign-data wrapper, foreign server, function,
    procedural language, schema, or tablespace), and one that grants
    membership in a role.  These variants are similar in many ways, but
@@ -125,7 +130,8 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
   <para>
    Depending on the type of object, the initial default privileges might
    include granting some privileges to <literal>PUBLIC</literal>.
-   The default is no public access for tables, schemas, and tablespaces;
+   The default is no public access for tables, columns, schemas, and
+   tablespaces;
    <literal>CONNECT</> privilege and <literal>TEMP</> table creation privilege
    for databases;
    <literal>EXECUTE</> privilege for functions; and
@@ -145,7 +151,8 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
      <listitem>
       <para>
        Allows <xref linkend="sql-select" endterm="sql-select-title"> from
-       any column of the specified table, view, or sequence.
+       any column, or the specific columns listed, of the specified table,
+       view, or sequence.
        Also allows the use of
        <xref linkend="sql-copy" endterm="sql-copy-title"> TO.
        This privilege is also needed to reference existing column values in
@@ -162,7 +169,9 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
      <listitem>
       <para>
        Allows <xref linkend="sql-insert" endterm="sql-insert-title"> of a new
-       row into the specified table.
+       row into the specified table.  If specific columns are listed,
+       only those columns may be assigned to in the <command>INSERT</>
+       command (other columns will therefore receive default values).
        Also allows <xref linkend="sql-copy" endterm="sql-copy-title"> FROM.
       </para>
      </listitem>
@@ -173,14 +182,14 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
      <listitem>
       <para>
        Allows <xref linkend="sql-update" endterm="sql-update-title"> of any
-       column of the specified table.
+       column, or the specific columns listed, of the specified table.
        (In practice, any nontrivial <command>UPDATE</> command will require
        <literal>SELECT</> privilege as well, since it must reference table
        columns to determine which rows to update, and/or to compute new
        values for columns.)
        <literal>SELECT ... FOR UPDATE</literal>
        and <literal>SELECT ... FOR SHARE</literal>
-       also require this privilege, in addition to the
+       also require this privilege on at least one column, in addition to the
        <literal>SELECT</literal> privilege.  For sequences, this
        privilege allows the use of the <function>nextval</function> and
        <function>setval</function> functions.
@@ -217,7 +226,8 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
       <para>
        To create a foreign key constraint, it is
        necessary to have this privilege on both the referencing and
-       referenced tables.
+       referenced columns.  The privilege may be granted for all columns
+       of a table, or just specific columns.
       </para>
      </listitem>
     </varlistentry>
@@ -373,6 +383,14 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
     to revoke access privileges.
    </para>
 
+   <para>
+    A user may perform <command>SELECT</>, <command>INSERT</>, etc. on a
+    column if he holds that privilege for either the specific column or
+    its whole table.  Granting the privilege at the table level and then
+    revoking it for one column will not do what you might wish: the
+    table-level grant is unaffected by a column-level operation.
+   </para>
+
    <para>
     When a non-owner of an object attempts to <command>GRANT</> privileges
     on the object, the command will fail outright if the user has no
@@ -428,33 +446,27 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
    </para>
 
    <para>
-    Granting permission on a table does not automatically extend 
-    permissions to any sequences used by the table, including 
-    sequences tied to <type>SERIAL</> columns.  Permissions on 
-    sequence must be set separately.
+    Granting permission on a table does not automatically extend
+    permissions to any sequences used by the table, including
+    sequences tied to <type>SERIAL</> columns.  Permissions on
+    sequences must be set separately.
    </para>
 
    <para>
-    Currently, <productname>PostgreSQL</productname> does not support
-    granting or revoking privileges for individual columns of a table.
-    One possible workaround is to create a view having just the desired
-    columns and then grant privileges to that view.
-   </para>
-
-   <para>
-    Use <xref linkend="app-psql">'s <command>\z</command> command
-    to obtain information about existing privileges, for example:
+    Use <xref linkend="app-psql">'s <command>\dp</command> command
+    to obtain information about existing privileges for tables and
+    columns.  For example:
 <programlisting>
-=&gt; \z mytable
-                Access privileges
- Schema |  Name   | Type  |  Access privileges   
---------+---------+-------+-----------------------
- public | mytable | table | miriam=arwdDxt/miriam
-                          : =r/miriam
-                          : admin=arw/miriam
+=&gt; \dp mytable
+                              Access privileges
+ Schema |  Name   | Type  |   Access privileges   | Column access privileges 
+--------+---------+-------+-----------------------+--------------------------
+ public | mytable | table | miriam=arwdDxt/miriam | col1:
+                          : =r/miriam             :   miriam_rw=rw/miriam
+                          : admin=arw/miriam        
 (1 row)
 </programlisting>
-    The entries shown by <command>\z</command> are interpreted thus:
+    The entries shown by <command>\dp</command> are interpreted thus:
 <programlisting>
       rolename=xxxx -- privileges granted to a role
               =xxxx -- privileges granted to PUBLIC
@@ -471,7 +483,7 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
                   C -- CREATE
                   c -- CONNECT
                   T -- TEMPORARY
-            arwdDxt -- ALL PRIVILEGES (for tables)
+            arwdDxt -- ALL PRIVILEGES (for tables, varies for other objects)
                   * -- grant option for preceding privilege
 
               /yyyy -- role that granted this privilege
@@ -483,9 +495,15 @@ GRANT <replaceable class="PARAMETER">role</replaceable> [, ...] TO <replaceable
 <programlisting>
 GRANT SELECT ON mytable TO PUBLIC;
 GRANT SELECT, UPDATE, INSERT ON mytable TO admin;
+GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;
 </programlisting>
    </para>
 
+   <para>
+    For non-table objects there are other <command>\d</> commands
+    that can display their privileges.
+   </para>
+
    <para>
     If the <quote>Access privileges</> column is empty for a given object,
     it means the object has default privileges (that is, its privileges column
@@ -495,7 +513,8 @@ GRANT SELECT, UPDATE, INSERT ON mytable TO admin;
     <command>REVOKE</> on an object
     will instantiate the default privileges (producing, for example,
     <literal>{miriam=arwdDxt/miriam}</>) and then modify them per the
-    specified request.
+    specified request.  Entries are shown in <quote>Column access
+    privileges</> only for columns with nondefault privileges.
    </para>
 
    <para>
@@ -562,11 +581,6 @@ GRANT admins TO joe;
     <quote>_SYSTEM</>, the owner cannot revoke these rights.
    </para>
 
-   <para>
-    <productname>PostgreSQL</productname> does not support the SQL-standard 
-    functionality of setting privileges for individual columns.
-   </para>
-
    <para>
     The SQL standard provides for a <literal>USAGE</literal> privilege
     on other kinds of objects: character sets, collations,
diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml
index 98a1ca28b79..a2a52d8ba42 100644
--- a/doc/src/sgml/ref/insert.sgml
+++ b/doc/src/sgml/ref/insert.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/insert.sgml,v 1.37 2008/11/14 10:22:47 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/insert.sgml,v 1.38 2009/01/22 20:15:59 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -69,11 +69,14 @@ INSERT INTO <replaceable class="PARAMETER">table</replaceable> [ ( <replaceable
 
   <para>
    You must have <literal>INSERT</literal> privilege on a table in
-   order to insert into it, and <literal>SELECT</> privilege on it to
-   use <literal>RETURNING</>.  If you use the <replaceable
+   order to insert into it.  If a column list is specified, you only
+   need <literal>INSERT</literal> privilege on the listed columns.
+   Use of the <literal>RETURNING</> clause requires <literal>SELECT</>
+   privilege on all columns mentioned in <literal>RETURNING</>.
+   If you use the <replaceable
    class="PARAMETER">query</replaceable> clause to insert rows from a
-   query, you also need to have <literal>SELECT</literal> privilege on
-   any table used in the query.
+   query, you of course need to have <literal>SELECT</literal> privilege on
+   any table or column used in the query.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index c8e91e0a159..0f967770c11 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.50 2008/12/19 16:25:16 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/revoke.sgml,v 1.51 2009/01/22 20:15:59 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -28,6 +28,13 @@ REVOKE [ GRANT OPTION FOR ]
     FROM { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...]
     [ CASCADE | RESTRICT ]
 
+REVOKE [ GRANT OPTION FOR ]
+    { { SELECT | INSERT | UPDATE | REFERENCES } ( <replaceable class="PARAMETER">column</replaceable> [, ...] )
+    [,...] | ALL [ PRIVILEGES ] ( <replaceable class="PARAMETER">column</replaceable> [, ...] ) }
+    ON [ TABLE ] <replaceable class="PARAMETER">tablename</replaceable> [, ...]
+    FROM { [ GROUP ] <replaceable class="PARAMETER">rolename</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
+
 REVOKE [ GRANT OPTION FOR ]
     { { USAGE | SELECT | UPDATE }
     [,...] | ALL [ PRIVILEGES ] }
@@ -131,6 +138,11 @@ REVOKE [ ADMIN OPTION FOR ]
    was also granted through other users.
   </para>
 
+  <para>
+   When revoking privileges on a table, the corresponding column privileges
+   (if any) are automatically revoked on each column of the table, as well.
+  </para>
+
   <para>
    When revoking membership in a role, <literal>GRANT OPTION</> is instead
    called <literal>ADMIN OPTION</>, but the behavior is similar.
@@ -143,9 +155,11 @@ REVOKE [ ADMIN OPTION FOR ]
   <title>Notes</title>
 
   <para>
-   Use <xref linkend="app-psql">'s <command>\z</command> command to
-   display the privileges granted on existing objects.  See <xref
-   linkend="sql-grant" endterm="sql-grant-title"> for information about the format.
+   Use <xref linkend="app-psql">'s <command>\dp</command> command to
+   display the privileges granted on existing tables and columns.  See <xref
+   linkend="sql-grant" endterm="sql-grant-title"> for information about the
+   format.  For non-table objects there are other <command>\d</> commands
+   that can display their privileges.
   </para>
 
   <para>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index ba14437764f..5cedb1cf318 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.117 2009/01/12 14:06:20 petere Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/select.sgml,v 1.118 2009/01/22 20:15:59 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -186,10 +186,11 @@ TABLE { [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] |
   </para>
 
   <para>
-   You must have <literal>SELECT</literal> privilege on a table to
-   read its values.  The use of <literal>FOR UPDATE</literal> or
-   <literal>FOR SHARE</literal> requires
-   <literal>UPDATE</literal> privilege as well.
+   You must have <literal>SELECT</literal> privilege on each column used
+   in a <command>SELECT</> command.  The use of <literal>FOR UPDATE</literal>
+   or <literal>FOR SHARE</literal> requires
+   <literal>UPDATE</literal> privilege as well (for at least one column
+   of each table so selected).
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index 2464bf16f93..8f32c83dc44 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/update.sgml,v 1.48 2008/11/16 17:34:28 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/update.sgml,v 1.49 2009/01/22 20:16:00 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -66,9 +66,10 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table</replaceable> [ [ AS ] <rep
   </para>
 
   <para>
-   You must have the <literal>UPDATE</literal> privilege on the table
-   to update it, as well as the <literal>SELECT</literal>
-   privilege to any table whose values are read in the
+   You must have the <literal>UPDATE</literal> privilege on the table,
+   or at least on the column(s) that are listed to be updated.
+   You must also have the <literal>SELECT</literal>
+   privilege on any column whose values are read in the
    <replaceable class="parameter">expressions</replaceable> or
    <replaceable class="parameter">condition</replaceable>.
   </para>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 4958299a2e4..ea16913c8e2 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.124 2009/01/01 17:23:34 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.125 2009/01/22 20:16:00 tgl Exp $
  *
  * NOTES
  *	  some of the executor utility code such as "ExecTypeFromTL" should be
@@ -53,10 +53,14 @@ CreateTemplateTupleDesc(int natts, bool hasoid)
 	 * struct pointer alignment requirement, and hence we don't need to insert
 	 * alignment padding between the struct and the array of attribute row
 	 * pointers.
+	 *
+	 * Note: Only the fixed part of pg_attribute rows is included in tuple
+	 * descriptors, so we only need ATTRIBUTE_FIXED_PART_SIZE space
+	 * per attr.  That might need alignment padding, however.
 	 */
 	attroffset = sizeof(struct tupleDesc) + natts * sizeof(Form_pg_attribute);
 	attroffset = MAXALIGN(attroffset);
-	stg = palloc(attroffset + natts * MAXALIGN(ATTRIBUTE_TUPLE_SIZE));
+	stg = palloc(attroffset + natts * MAXALIGN(ATTRIBUTE_FIXED_PART_SIZE));
 	desc = (TupleDesc) stg;
 
 	if (natts > 0)
@@ -70,7 +74,7 @@ CreateTemplateTupleDesc(int natts, bool hasoid)
 		for (i = 0; i < natts; i++)
 		{
 			attrs[i] = (Form_pg_attribute) stg;
-			stg += MAXALIGN(ATTRIBUTE_TUPLE_SIZE);
+			stg += MAXALIGN(ATTRIBUTE_FIXED_PART_SIZE);
 		}
 	}
 	else
@@ -139,7 +143,7 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 
 	for (i = 0; i < desc->natts; i++)
 	{
-		memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_TUPLE_SIZE);
+		memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
 		desc->attrs[i]->attnotnull = false;
 		desc->attrs[i]->atthasdef = false;
 	}
@@ -166,7 +170,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 	for (i = 0; i < desc->natts; i++)
 	{
-		memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_TUPLE_SIZE);
+		memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
 	}
 
 	if (constr)
@@ -356,6 +360,7 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (attr1->attinhcount != attr2->attinhcount)
 			return false;
+		/* attacl is ignored, since it's not even present... */
 	}
 
 	if (tupdesc1->constr != NULL)
@@ -471,6 +476,7 @@ TupleDescInitEntry(TupleDesc desc,
 	att->attisdropped = false;
 	att->attislocal = true;
 	att->attinhcount = 0;
+	/* attacl is not set because it's not present in tupledescs */
 
 	tuple = SearchSysCache(TYPEOID,
 						   ObjectIdGetDatum(oidtypeid),
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 00b52dce801..19aab42554e 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.248 2009/01/01 17:23:36 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.249 2009/01/22 20:16:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -633,7 +633,7 @@ boot_openrel(char *relname)
 		closerel(NULL);
 
 	elog(DEBUG4, "open relation %s, attrsize %d",
-		 relname, (int) ATTRIBUTE_TUPLE_SIZE);
+		 relname, (int) ATTRIBUTE_FIXED_PART_SIZE);
 
 	boot_reldesc = heap_openrv(makeRangeVar(NULL, relname, -1), NoLock);
 	numattr = boot_reldesc->rd_rel->relnatts;
@@ -643,7 +643,7 @@ boot_openrel(char *relname)
 			attrtypes[i] = AllocateAttribute();
 		memmove((char *) attrtypes[i],
 				(char *) boot_reldesc->rd_att->attrs[i],
-				ATTRIBUTE_TUPLE_SIZE);
+				ATTRIBUTE_FIXED_PART_SIZE);
 
 		{
 			Form_pg_attribute at = attrtypes[i];
@@ -709,7 +709,7 @@ DefineAttr(char *name, char *type, int attnum)
 
 	if (attrtypes[attnum] == NULL)
 		attrtypes[attnum] = AllocateAttribute();
-	MemSet(attrtypes[attnum], 0, ATTRIBUTE_TUPLE_SIZE);
+	MemSet(attrtypes[attnum], 0, ATTRIBUTE_FIXED_PART_SIZE);
 
 	namestrcpy(&attrtypes[attnum]->attname, name);
 	elog(DEBUG4, "column %s %s", NameStr(attrtypes[attnum]->attname), type);
@@ -1017,16 +1017,19 @@ boot_get_type_io_data(Oid typid,
 
 /* ----------------
  *		AllocateAttribute
+ *
+ * Note: bootstrap never sets any per-column ACLs, so we only need
+ * ATTRIBUTE_FIXED_PART_SIZE space per attribute.
  * ----------------
  */
 static Form_pg_attribute
 AllocateAttribute(void)
 {
-	Form_pg_attribute attribute = (Form_pg_attribute) malloc(ATTRIBUTE_TUPLE_SIZE);
+	Form_pg_attribute attribute = (Form_pg_attribute) malloc(ATTRIBUTE_FIXED_PART_SIZE);
 
 	if (!PointerIsValid(attribute))
 		elog(FATAL, "out of memory");
-	MemSet(attribute, 0, ATTRIBUTE_TUPLE_SIZE);
+	MemSet(attribute, 0, ATTRIBUTE_FIXED_PART_SIZE);
 
 	return attribute;
 }
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index ad32f49e7d0..b49c80e485b 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.151 2009/01/01 17:23:36 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.152 2009/01/22 20:16:00 tgl Exp $
  *
  * NOTES
  *	  See acl.h.
@@ -61,14 +61,23 @@ static void ExecGrant_Namespace(InternalGrant *grantStmt);
 static void ExecGrant_Tablespace(InternalGrant *grantStmt);
 
 static List *objectNamesToOids(GrantObjectType objtype, List *objnames);
+static void expand_col_privileges(List *colnames, Oid table_oid,
+								  AclMode this_privileges,
+								  AclMode *col_privileges,
+								  int num_col_privileges);
+static void expand_all_col_privileges(Oid table_oid, Form_pg_class classForm,
+									  AclMode this_privileges,
+									  AclMode *col_privileges,
+									  int num_col_privileges);
 static AclMode string_to_privilege(const char *privname);
 static const char *privilege_to_string(AclMode privilege);
 static AclMode restrict_and_check_grant(bool is_grant, AclMode avail_goptions,
 						 bool all_privs, AclMode privileges,
 						 Oid objectId, Oid grantorId,
-						 AclObjectKind objkind, char *objname);
-static AclMode pg_aclmask(AclObjectKind objkind, Oid table_oid, Oid roleid,
-		   AclMode mask, AclMaskHow how);
+						 AclObjectKind objkind, const char *objname,
+						 AttrNumber att_number, const char *colname);
+static AclMode pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum,
+						  Oid roleid, AclMode mask, AclMaskHow how);
 
 
 #ifdef ACLDEBUG
@@ -118,7 +127,7 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant,
 		AclItem aclitem;
 		Acl		   *newer_acl;
 
-		aclitem.	ai_grantee = lfirst_oid(j);
+		aclitem.ai_grantee = lfirst_oid(j);
 
 		/*
 		 * Grant options can only be granted to individual roles, not PUBLIC.
@@ -131,7 +140,7 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant,
 					(errcode(ERRCODE_INVALID_GRANT_OPERATION),
 					 errmsg("grant options can only be granted to roles")));
 
-		aclitem.	ai_grantor = grantorId;
+		aclitem.ai_grantor = grantorId;
 
 		/*
 		 * The asymmetry in the conditions here comes from the spec.  In
@@ -165,13 +174,17 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant,
 static AclMode
 restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 						 AclMode privileges, Oid objectId, Oid grantorId,
-						 AclObjectKind objkind, char *objname)
+						 AclObjectKind objkind, const char *objname,
+						 AttrNumber att_number, const char *colname)
 {
 	AclMode		this_privileges;
 	AclMode		whole_mask;
 
 	switch (objkind)
 	{
+		case ACL_KIND_COLUMN:
+			whole_mask = ACL_ALL_RIGHTS_COLUMN;
+			break;
 		case ACL_KIND_CLASS:
 			whole_mask = ACL_ALL_RIGHTS_RELATION;
 			break;
@@ -212,10 +225,15 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 	 */
 	if (avail_goptions == ACL_NO_RIGHTS)
 	{
-		if (pg_aclmask(objkind, objectId, grantorId,
+		if (pg_aclmask(objkind, objectId, att_number, grantorId,
 					   whole_mask | ACL_GRANT_OPTION_FOR(whole_mask),
 					   ACLMASK_ANY) == ACL_NO_RIGHTS)
-			aclcheck_error(ACLCHECK_NO_PRIV, objkind, objname);
+		{
+			if (objkind == ACL_KIND_COLUMN && colname)
+				aclcheck_error_col(ACLCHECK_NO_PRIV, objkind, objname, colname);
+			else
+				aclcheck_error(ACLCHECK_NO_PRIV, objkind, objname);
+		}
 	}
 
 	/*
@@ -271,12 +289,11 @@ ExecuteGrantStmt(GrantStmt *stmt)
 	istmt.objects = objectNamesToOids(stmt->objtype, stmt->objects);
 	/* all_privs to be filled below */
 	/* privileges to be filled below */
-	istmt.grantees = NIL;
-	/* filled below */
+	istmt.col_privs = NIL;		/* may get filled below */
+	istmt.grantees = NIL;		/* filled below */
 	istmt.grant_option = stmt->grant_option;
 	istmt.behavior = stmt->behavior;
 
-
 	/*
 	 * Convert the PrivGrantee list into an Oid list.  Note that at this point
 	 * we insert an ACL_ID_PUBLIC into the list if an empty role name is
@@ -297,7 +314,8 @@ ExecuteGrantStmt(GrantStmt *stmt)
 	}
 
 	/*
-	 * Convert stmt->privileges, a textual list, into an AclMode bitmask.
+	 * Convert stmt->privileges, a list of AccessPriv nodes, into an
+	 * AclMode bitmask.  Note: objtype can't be ACL_OBJECT_COLUMN.
 	 */
 	switch (stmt->objtype)
 	{
@@ -367,8 +385,26 @@ ExecuteGrantStmt(GrantStmt *stmt)
 
 		foreach(cell, stmt->privileges)
 		{
-			char	   *privname = strVal(lfirst(cell));
-			AclMode		priv = string_to_privilege(privname);
+			AccessPriv *privnode = (AccessPriv *) lfirst(cell);
+			AclMode		priv;
+
+			/*
+			 * If it's a column-level specification, we just set it aside
+			 * in col_privs for the moment; but insist it's for a relation.
+			 */
+			if (privnode->cols)
+			{
+				if (stmt->objtype != ACL_OBJECT_RELATION)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_GRANT_OPERATION),
+							 errmsg("column privileges are only valid for relations")));
+				istmt.col_privs = lappend(istmt.col_privs, privnode);
+				continue;
+			}
+
+			if (privnode->priv_name == NULL)		/* parser mistake? */
+				elog(ERROR, "AccessPriv node must specify privilege or columns");
+			priv = string_to_privilege(privnode->priv_name);
 
 			if (priv & ~((AclMode) all_privileges))
 				ereport(ERROR,
@@ -385,7 +421,9 @@ ExecuteGrantStmt(GrantStmt *stmt)
 /*
  * ExecGrantStmt_oids
  *
- * "Internal" entrypoint for granting and revoking privileges.
+ * "Internal" entrypoint for granting and revoking privileges.  This is
+ * exported for pg_shdepend.c to use in revoking privileges when dropping
+ * a role.
  */
 void
 ExecGrantStmt_oids(InternalGrant *istmt)
@@ -571,6 +609,234 @@ objectNamesToOids(GrantObjectType objtype, List *objnames)
 	return objects;
 }
 
+/*
+ * expand_col_privileges
+ *
+ * OR the specified privilege(s) into per-column array entries for each
+ * specified attribute.  The per-column array is indexed starting at
+ * FirstLowInvalidHeapAttributeNumber, up to relation's last attribute.
+ */
+static void
+expand_col_privileges(List *colnames, Oid table_oid,
+					  AclMode this_privileges,
+					  AclMode *col_privileges,
+					  int num_col_privileges)
+{
+	ListCell   *cell;
+
+	foreach(cell, colnames)
+	{
+		char	   *colname = strVal(lfirst(cell));
+		AttrNumber	attnum;
+
+		attnum = get_attnum(table_oid, colname);
+		if (attnum == InvalidAttrNumber)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" of relation \"%s\" does not exist",
+							colname, get_rel_name(table_oid))));
+		attnum -= FirstLowInvalidHeapAttributeNumber;
+		if (attnum <= 0 || attnum >= num_col_privileges)
+			elog(ERROR, "column number out of range");		/* safety check */
+		col_privileges[attnum] |= this_privileges;
+	}
+}
+
+/*
+ * expand_all_col_privileges
+ *
+ * OR the specified privilege(s) into per-column array entries for each valid
+ * attribute of a relation.  The per-column array is indexed starting at
+ * FirstLowInvalidHeapAttributeNumber, up to relation's last attribute.
+ */
+static void
+expand_all_col_privileges(Oid table_oid, Form_pg_class classForm,
+						  AclMode this_privileges,
+						  AclMode *col_privileges,
+						  int num_col_privileges)
+{
+	AttrNumber	curr_att;
+
+	Assert(classForm->relnatts - FirstLowInvalidHeapAttributeNumber < num_col_privileges);
+	for (curr_att = FirstLowInvalidHeapAttributeNumber + 1;
+		 curr_att <= classForm->relnatts;
+		 curr_att++)
+	{
+		HeapTuple	attTuple;
+		bool		isdropped;
+
+		if (curr_att == InvalidAttrNumber)
+			continue;
+
+		/* Skip OID column if it doesn't exist */
+		if (curr_att == ObjectIdAttributeNumber && !classForm->relhasoids)
+			continue;
+
+		/* Views don't have any system columns at all */
+		if (classForm->relkind == RELKIND_VIEW && curr_att < 0)
+			continue;
+
+		attTuple = SearchSysCache(ATTNUM,
+								  ObjectIdGetDatum(table_oid),
+								  Int16GetDatum(curr_att),
+								  0, 0);
+		if (!HeapTupleIsValid(attTuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 curr_att, table_oid);
+
+		isdropped = ((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped;
+
+		ReleaseSysCache(attTuple);
+
+		/* ignore dropped columns */
+		if (isdropped)
+			continue;
+
+		col_privileges[curr_att - FirstLowInvalidHeapAttributeNumber] |= this_privileges;
+	}
+}
+
+/*
+ *	This processes attributes, but expects to be called from
+ *	ExecGrant_Relation, not directly from ExecGrantStmt.
+ */
+static void
+ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname,
+					AttrNumber attnum, Oid ownerId, AclMode col_privileges,
+					Relation attRelation, const Acl *old_rel_acl)
+{
+	HeapTuple			attr_tuple;
+	Form_pg_attribute	pg_attribute_tuple;
+	Acl				   *old_acl;
+	Acl				   *new_acl;
+	Acl				   *merged_acl;
+	Datum				aclDatum;
+	bool				isNull;
+	Oid					grantorId;
+	AclMode				avail_goptions;
+	bool				need_update;
+	HeapTuple			newtuple;
+	Datum				values[Natts_pg_attribute];
+	bool				nulls[Natts_pg_attribute];
+	bool				replaces[Natts_pg_attribute];
+	int					noldmembers;
+	int					nnewmembers;
+	Oid		   		   *oldmembers;
+	Oid		   		   *newmembers;
+
+	attr_tuple = SearchSysCache(ATTNUM,
+								ObjectIdGetDatum(relOid),
+								Int16GetDatum(attnum),
+								0, 0);
+	if (!HeapTupleIsValid(attr_tuple))
+		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+			 attnum, relOid);
+	pg_attribute_tuple = (Form_pg_attribute) GETSTRUCT(attr_tuple);
+
+	/*
+	 * Get working copy of existing ACL. If there's no ACL,
+	 * substitute the proper default.
+	 */
+	aclDatum = SysCacheGetAttr(ATTNUM, attr_tuple, Anum_pg_attribute_attacl,
+							   &isNull);
+	if (isNull)
+		old_acl = acldefault(ACL_OBJECT_COLUMN, ownerId);
+	else
+		old_acl = DatumGetAclPCopy(aclDatum);
+
+	/*
+	 * In select_best_grantor we should consider existing table-level ACL bits
+	 * as well as the per-column ACL.  Build a new ACL that is their
+	 * concatenation.  (This is a bit cheap and dirty compared to merging
+	 * them properly with no duplications, but it's all we need here.)
+	 */
+	merged_acl = aclconcat(old_rel_acl, old_acl);
+
+	/* Determine ID to do the grant as, and available grant options */
+	select_best_grantor(GetUserId(), col_privileges,
+						merged_acl, ownerId,
+						&grantorId, &avail_goptions);
+
+	pfree(merged_acl);
+
+	/*
+	 * Restrict the privileges to what we can actually grant, and emit
+	 * the standards-mandated warning and error messages.  Note: we don't
+	 * track whether the user actually used the ALL PRIVILEGES(columns)
+	 * syntax for each column; we just approximate it by whether all the
+	 * possible privileges are specified now.  Since the all_privs flag only
+	 * determines whether a warning is issued, this seems close enough.
+	 */
+	col_privileges =
+		restrict_and_check_grant(istmt->is_grant, avail_goptions,
+								 (col_privileges == ACL_ALL_RIGHTS_COLUMN),
+								 col_privileges,
+								 relOid, grantorId, ACL_KIND_COLUMN,
+								 relname, attnum,
+								 NameStr(pg_attribute_tuple->attname));
+
+	/*
+	 * Generate new ACL.
+	 *
+	 * We need the members of both old and new ACLs so we can correct
+	 * the shared dependency information.
+	 */
+	noldmembers = aclmembers(old_acl, &oldmembers);
+
+	new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+								   istmt->grant_option,
+								   istmt->behavior, istmt->grantees,
+								   col_privileges, grantorId,
+								   ownerId);
+
+	nnewmembers = aclmembers(new_acl, &newmembers);
+
+	/* finished building new ACL value, now insert it */
+	MemSet(values, 0, sizeof(values));
+	MemSet(nulls, false, sizeof(nulls));
+	MemSet(replaces, false, sizeof(replaces));
+
+	/*
+	 * If the updated ACL is empty, we can set attacl to null, and maybe
+	 * even avoid an update of the pg_attribute row.  This is worth testing
+	 * because we'll come through here multiple times for any relation-level
+	 * REVOKE, even if there were never any column GRANTs.  Note we are
+	 * assuming that the "default" ACL state for columns is empty.
+	 */
+	if (ACL_NUM(new_acl) > 0)
+	{
+		values[Anum_pg_attribute_attacl - 1] = PointerGetDatum(new_acl);
+		need_update = true;
+	}
+	else
+	{
+		nulls[Anum_pg_attribute_attacl - 1] = true;
+		need_update = !isNull;
+	}
+	replaces[Anum_pg_attribute_attacl - 1] = true;
+
+	if (need_update)
+	{
+		newtuple = heap_modify_tuple(attr_tuple, RelationGetDescr(attRelation),
+									 values, nulls, replaces);
+
+		simple_heap_update(attRelation, &newtuple->t_self, newtuple);
+
+		/* keep the catalog indexes up to date */
+		CatalogUpdateIndexes(attRelation, newtuple);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(RelationRelationId, relOid, attnum,
+							  ownerId, istmt->is_grant,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+	}
+
+	pfree(new_acl);
+
+	ReleaseSysCache(attr_tuple);
+}
+
 /*
  *	This processes both sequences and non-sequences.
  */
@@ -578,9 +844,11 @@ static void
 ExecGrant_Relation(InternalGrant *istmt)
 {
 	Relation	relation;
+	Relation	attRelation;
 	ListCell   *cell;
 
 	relation = heap_open(RelationRelationId, RowExclusiveLock);
+	attRelation = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	foreach(cell, istmt->objects)
 	{
@@ -588,21 +856,15 @@ ExecGrant_Relation(InternalGrant *istmt)
 		Datum		aclDatum;
 		Form_pg_class pg_class_tuple;
 		bool		isNull;
-		AclMode		avail_goptions;
 		AclMode		this_privileges;
+		AclMode	   *col_privileges;
+		int			num_col_privileges;
+		bool		have_col_privileges;
 		Acl		   *old_acl;
-		Acl		   *new_acl;
-		Oid			grantorId;
+		Acl		   *old_rel_acl;
 		Oid			ownerId;
 		HeapTuple	tuple;
-		HeapTuple	newtuple;
-		Datum		values[Natts_pg_class];
-		bool		nulls[Natts_pg_class];
-		bool		replaces[Natts_pg_class];
-		int			noldmembers;
-		int			nnewmembers;
-		Oid		   *oldmembers;
-		Oid		   *newmembers;
+		ListCell   *cell_colprivs;
 
 		tuple = SearchSysCache(RELOID,
 							   ObjectIdGetDatum(relOid),
@@ -655,9 +917,9 @@ ExecGrant_Relation(InternalGrant *istmt)
 			if (pg_class_tuple->relkind == RELKIND_SEQUENCE)
 			{
 				/*
-				 * For backward compatibility, throw just a warning for
+				 * For backward compatibility, just throw a warning for
 				 * invalid sequence permissions when using the non-sequence
-				 * GRANT syntax is used.
+				 * GRANT syntax.
 				 */
 				if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_SEQUENCE))
 				{
@@ -668,7 +930,7 @@ ExecGrant_Relation(InternalGrant *istmt)
 					 */
 					ereport(WARNING,
 							(errcode(ERRCODE_INVALID_GRANT_OPERATION),
-							 errmsg("sequence \"%s\" only supports USAGE, SELECT, and UPDATE",
+							 errmsg("sequence \"%s\" only supports USAGE, SELECT, and UPDATE privileges",
 									NameStr(pg_class_tuple->relname))));
 					this_privileges &= (AclMode) ACL_ALL_RIGHTS_SEQUENCE;
 				}
@@ -676,7 +938,7 @@ ExecGrant_Relation(InternalGrant *istmt)
 			else
 			{
 				if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_RELATION))
-
+				{
 					/*
 					 * USAGE is the only permission supported by sequences but
 					 * not by non-sequences.  Don't mention the object name
@@ -686,9 +948,36 @@ ExecGrant_Relation(InternalGrant *istmt)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_GRANT_OPERATION),
 						  errmsg("invalid privilege type USAGE for table")));
+				}
 			}
 		}
 
+		/*
+		 * Set up array in which we'll accumulate any column privilege bits
+		 * that need modification.  The array is indexed such that entry [0]
+		 * corresponds to FirstLowInvalidHeapAttributeNumber.
+		 */
+		num_col_privileges = pg_class_tuple->relnatts - FirstLowInvalidHeapAttributeNumber + 1;
+		col_privileges = (AclMode *) palloc0(num_col_privileges * sizeof(AclMode));
+		have_col_privileges = false;
+
+		/*
+		 * If we are revoking relation privileges that are also column
+		 * privileges, we must implicitly revoke them from each column too,
+		 * per SQL spec.  (We don't need to implicitly add column privileges
+		 * during GRANT because the permissions-checking code always checks
+		 * both relation and per-column privileges.)
+		 */
+		if (!istmt->is_grant &&
+			(this_privileges & ACL_ALL_RIGHTS_COLUMN) != 0)
+		{
+			expand_all_col_privileges(relOid, pg_class_tuple,
+									  this_privileges & ACL_ALL_RIGHTS_COLUMN,
+									  col_privileges,
+									  num_col_privileges);
+			have_col_privileges = true;
+		}
+
 		/*
 		 * Get owner ID and working copy of existing ACL. If there's no ACL,
 		 * substitute the proper default.
@@ -703,67 +992,160 @@ ExecGrant_Relation(InternalGrant *istmt)
 		else
 			old_acl = DatumGetAclPCopy(aclDatum);
 
-		/* Determine ID to do the grant as, and available grant options */
-		select_best_grantor(GetUserId(), this_privileges,
-							old_acl, ownerId,
-							&grantorId, &avail_goptions);
+		/* Need an extra copy of original rel ACL for column handling */
+		old_rel_acl = aclcopy(old_acl);
 
 		/*
-		 * Restrict the privileges to what we can actually grant, and emit the
-		 * standards-mandated warning and error messages.
+		 * Handle relation-level privileges, if any were specified
 		 */
-		this_privileges =
-			restrict_and_check_grant(istmt->is_grant, avail_goptions,
-									 istmt->all_privs, this_privileges,
-									 relOid, grantorId,
-								  pg_class_tuple->relkind == RELKIND_SEQUENCE
-									 ? ACL_KIND_SEQUENCE : ACL_KIND_CLASS,
-									 NameStr(pg_class_tuple->relname));
+		if (this_privileges != ACL_NO_RIGHTS)
+		{
+			AclMode		avail_goptions;
+			Acl		   *new_acl;
+			Oid			grantorId;
+			HeapTuple	newtuple;
+			Datum		values[Natts_pg_class];
+			bool		nulls[Natts_pg_class];
+			bool		replaces[Natts_pg_class];
+			int			noldmembers;
+			int			nnewmembers;
+			Oid		   *oldmembers;
+			Oid		   *newmembers;
+
+			/* Determine ID to do the grant as, and available grant options */
+			select_best_grantor(GetUserId(), this_privileges,
+								old_acl, ownerId,
+								&grantorId, &avail_goptions);
+
+			/*
+			 * Restrict the privileges to what we can actually grant, and emit
+			 * the standards-mandated warning and error messages.
+			 */
+			this_privileges =
+				restrict_and_check_grant(istmt->is_grant, avail_goptions,
+										 istmt->all_privs, this_privileges,
+										 relOid, grantorId,
+										 pg_class_tuple->relkind == RELKIND_SEQUENCE
+										 ? ACL_KIND_SEQUENCE : ACL_KIND_CLASS,
+										 NameStr(pg_class_tuple->relname),
+										 0, NULL);
+
+			/*
+			 * Generate new ACL.
+			 *
+			 * We need the members of both old and new ACLs so we can correct
+			 * the shared dependency information.
+			 */
+			noldmembers = aclmembers(old_acl, &oldmembers);
+
+			new_acl = merge_acl_with_grant(old_acl,
+										   istmt->is_grant,
+										   istmt->grant_option,
+										   istmt->behavior,
+										   istmt->grantees,
+										   this_privileges,
+										   grantorId,
+										   ownerId);
+
+			nnewmembers = aclmembers(new_acl, &newmembers);
+
+			/* finished building new ACL value, now insert it */
+			MemSet(values, 0, sizeof(values));
+			MemSet(nulls, false, sizeof(nulls));
+			MemSet(replaces, false, sizeof(replaces));
+
+			replaces[Anum_pg_class_relacl - 1] = true;
+			values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl);
+
+			newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation),
+										 values, nulls, replaces);
+
+			simple_heap_update(relation, &newtuple->t_self, newtuple);
+
+			/* keep the catalog indexes up to date */
+			CatalogUpdateIndexes(relation, newtuple);
+
+			/* Update the shared dependency ACL info */
+			updateAclDependencies(RelationRelationId, relOid, 0,
+								  ownerId, istmt->is_grant,
+								  noldmembers, oldmembers,
+								  nnewmembers, newmembers);
+
+			pfree(new_acl);
+		}
 
 		/*
-		 * Generate new ACL.
-		 *
-		 * We need the members of both old and new ACLs so we can correct the
-		 * shared dependency information.
+		 * Handle column-level privileges, if any were specified or implied.
+		 * We first expand the user-specified column privileges into the
+		 * array, and then iterate over all nonempty array entries.
 		 */
-		noldmembers = aclmembers(old_acl, &oldmembers);
+		foreach(cell_colprivs, istmt->col_privs)
+		{
+			AccessPriv	   *col_privs = (AccessPriv *) lfirst(cell_colprivs);
 
-		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
-									   istmt->grant_option, istmt->behavior,
-									   istmt->grantees, this_privileges,
-									   grantorId, ownerId);
+			if (col_privs->priv_name == NULL)
+				this_privileges = ACL_ALL_RIGHTS_COLUMN;
+			else
+				this_privileges = string_to_privilege(col_privs->priv_name);
 
-		nnewmembers = aclmembers(new_acl, &newmembers);
+			if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_COLUMN))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_GRANT_OPERATION),
+						 errmsg("invalid privilege type %s for column",
+								privilege_to_string(this_privileges))));
 
-		/* finished building new ACL value, now insert it */
-		MemSet(values, 0, sizeof(values));
-		MemSet(nulls, false, sizeof(nulls));
-		MemSet(replaces, false, sizeof(replaces));
+			if (pg_class_tuple->relkind == RELKIND_SEQUENCE &&
+				this_privileges & ~((AclMode) ACL_SELECT))
+			{
+				/*
+				 * The only column privilege allowed on sequences is SELECT.
+				 * This is a warning not error because we do it that way
+				 * for relation-level privileges.
+				 */
+				ereport(WARNING,
+						(errcode(ERRCODE_INVALID_GRANT_OPERATION),
+						 errmsg("sequence \"%s\" only supports SELECT column privileges",
+								NameStr(pg_class_tuple->relname))));
 
-		replaces[Anum_pg_class_relacl - 1] = true;
-		values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl);
+				this_privileges &= (AclMode) ACL_SELECT;
+			}
 
-		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);
+			expand_col_privileges(col_privs->cols, relOid,
+								  this_privileges,
+								  col_privileges,
+								  num_col_privileges);
+			have_col_privileges = true;
+		}
 
-		simple_heap_update(relation, &newtuple->t_self, newtuple);
+		if (have_col_privileges)
+		{
+			AttrNumber i;
 
-		/* keep the catalog indexes up to date */
-		CatalogUpdateIndexes(relation, newtuple);
+			for (i = 0; i < num_col_privileges; i++)
+			{
+				if (col_privileges[i] == ACL_NO_RIGHTS)
+					continue;
+				ExecGrant_Attribute(istmt,
+									relOid,
+									NameStr(pg_class_tuple->relname),
+									i + FirstLowInvalidHeapAttributeNumber,
+									ownerId,
+									col_privileges[i],
+									attRelation,
+									old_rel_acl);
+			}
+		}
 
-		/* Update the shared dependency ACL info */
-		updateAclDependencies(RelationRelationId, relOid,
-							  ownerId, istmt->is_grant,
-							  noldmembers, oldmembers,
-							  nnewmembers, newmembers);
+		pfree(old_rel_acl);
+		pfree(col_privileges);
 
 		ReleaseSysCache(tuple);
 
-		pfree(new_acl);
-
 		/* prevent error when processing duplicate objects */
 		CommandCounterIncrement();
 	}
 
+	heap_close(attRelation, RowExclusiveLock);
 	heap_close(relation, RowExclusiveLock);
 }
 
@@ -833,7 +1215,8 @@ ExecGrant_Database(InternalGrant *istmt)
 			restrict_and_check_grant(istmt->is_grant, avail_goptions,
 									 istmt->all_privs, istmt->privileges,
 									 datId, grantorId, ACL_KIND_DATABASE,
-									 NameStr(pg_database_tuple->datname));
+									 NameStr(pg_database_tuple->datname),
+									 0, NULL);
 
 		/*
 		 * Generate new ACL.
@@ -867,7 +1250,7 @@ ExecGrant_Database(InternalGrant *istmt)
 		CatalogUpdateIndexes(relation, newtuple);
 
 		/* Update the shared dependency ACL info */
-		updateAclDependencies(DatabaseRelationId, HeapTupleGetOid(tuple),
+		updateAclDependencies(DatabaseRelationId, HeapTupleGetOid(tuple), 0,
 							  ownerId, istmt->is_grant,
 							  noldmembers, oldmembers,
 							  nnewmembers, newmembers);
@@ -950,7 +1333,8 @@ ExecGrant_Fdw(InternalGrant *istmt)
 			restrict_and_check_grant(istmt->is_grant, avail_goptions,
 									 istmt->all_privs, istmt->privileges,
 									 fdwid, grantorId, ACL_KIND_FDW,
-									 NameStr(pg_fdw_tuple->fdwname));
+									 NameStr(pg_fdw_tuple->fdwname),
+									 0, NULL);
 
 		/*
 		 * Generate new ACL.
@@ -984,7 +1368,8 @@ ExecGrant_Fdw(InternalGrant *istmt)
 		CatalogUpdateIndexes(relation, newtuple);
 
 		/* Update the shared dependency ACL info */
-		updateAclDependencies(ForeignDataWrapperRelationId, HeapTupleGetOid(tuple),
+		updateAclDependencies(ForeignDataWrapperRelationId,
+							  HeapTupleGetOid(tuple), 0,
 							  ownerId, istmt->is_grant,
 							  noldmembers, oldmembers,
 							  nnewmembers, newmembers);
@@ -1066,7 +1451,8 @@ static void ExecGrant_ForeignServer(InternalGrant *istmt)
 			restrict_and_check_grant(istmt->is_grant, avail_goptions,
 									 istmt->all_privs, istmt->privileges,
 									 srvid, grantorId, ACL_KIND_FOREIGN_SERVER,
-									 NameStr(pg_server_tuple->srvname));
+									 NameStr(pg_server_tuple->srvname),
+									 0, NULL);
 
 		/*
 		 * Generate new ACL.
@@ -1100,7 +1486,8 @@ static void ExecGrant_ForeignServer(InternalGrant *istmt)
 		CatalogUpdateIndexes(relation, newtuple);
 
 		/* Update the shared dependency ACL info */
-		updateAclDependencies(ForeignServerRelationId, HeapTupleGetOid(tuple),
+		updateAclDependencies(ForeignServerRelationId,
+							  HeapTupleGetOid(tuple), 0,
 							  ownerId, istmt->is_grant,
 							  noldmembers, oldmembers,
 							  nnewmembers, newmembers);
@@ -1182,7 +1569,8 @@ ExecGrant_Function(InternalGrant *istmt)
 			restrict_and_check_grant(istmt->is_grant, avail_goptions,
 									 istmt->all_privs, istmt->privileges,
 									 funcId, grantorId, ACL_KIND_PROC,
-									 NameStr(pg_proc_tuple->proname));
+									 NameStr(pg_proc_tuple->proname),
+									 0, NULL);
 
 		/*
 		 * Generate new ACL.
@@ -1216,7 +1604,7 @@ ExecGrant_Function(InternalGrant *istmt)
 		CatalogUpdateIndexes(relation, newtuple);
 
 		/* Update the shared dependency ACL info */
-		updateAclDependencies(ProcedureRelationId, funcId,
+		updateAclDependencies(ProcedureRelationId, funcId, 0,
 							  ownerId, istmt->is_grant,
 							  noldmembers, oldmembers,
 							  nnewmembers, newmembers);
@@ -1305,7 +1693,8 @@ ExecGrant_Language(InternalGrant *istmt)
 			restrict_and_check_grant(istmt->is_grant, avail_goptions,
 									 istmt->all_privs, istmt->privileges,
 									 langId, grantorId, ACL_KIND_LANGUAGE,
-									 NameStr(pg_language_tuple->lanname));
+									 NameStr(pg_language_tuple->lanname),
+									 0, NULL);
 
 		/*
 		 * Generate new ACL.
@@ -1339,7 +1728,7 @@ ExecGrant_Language(InternalGrant *istmt)
 		CatalogUpdateIndexes(relation, newtuple);
 
 		/* Update the shared dependency ACL info */
-		updateAclDependencies(LanguageRelationId, HeapTupleGetOid(tuple),
+		updateAclDependencies(LanguageRelationId, HeapTupleGetOid(tuple), 0,
 							  ownerId, istmt->is_grant,
 							  noldmembers, oldmembers,
 							  nnewmembers, newmembers);
@@ -1422,7 +1811,8 @@ ExecGrant_Namespace(InternalGrant *istmt)
 			restrict_and_check_grant(istmt->is_grant, avail_goptions,
 									 istmt->all_privs, istmt->privileges,
 									 nspid, grantorId, ACL_KIND_NAMESPACE,
-									 NameStr(pg_namespace_tuple->nspname));
+									 NameStr(pg_namespace_tuple->nspname),
+									 0, NULL);
 
 		/*
 		 * Generate new ACL.
@@ -1456,7 +1846,7 @@ ExecGrant_Namespace(InternalGrant *istmt)
 		CatalogUpdateIndexes(relation, newtuple);
 
 		/* Update the shared dependency ACL info */
-		updateAclDependencies(NamespaceRelationId, HeapTupleGetOid(tuple),
+		updateAclDependencies(NamespaceRelationId, HeapTupleGetOid(tuple), 0,
 							  ownerId, istmt->is_grant,
 							  noldmembers, oldmembers,
 							  nnewmembers, newmembers);
@@ -1545,7 +1935,8 @@ ExecGrant_Tablespace(InternalGrant *istmt)
 			restrict_and_check_grant(istmt->is_grant, avail_goptions,
 									 istmt->all_privs, istmt->privileges,
 									 tblId, grantorId, ACL_KIND_TABLESPACE,
-									 NameStr(pg_tablespace_tuple->spcname));
+									 NameStr(pg_tablespace_tuple->spcname),
+									 0, NULL);
 
 		/*
 		 * Generate new ACL.
@@ -1579,7 +1970,7 @@ ExecGrant_Tablespace(InternalGrant *istmt)
 		CatalogUpdateIndexes(relation, newtuple);
 
 		/* Update the shared dependency ACL info */
-		updateAclDependencies(TableSpaceRelationId, tblId,
+		updateAclDependencies(TableSpaceRelationId, tblId, 0,
 							  ownerId, istmt->is_grant,
 							  noldmembers, oldmembers,
 							  nnewmembers, newmembers);
@@ -1677,6 +2068,8 @@ privilege_to_string(AclMode privilege)
 
 static const char *const no_priv_msg[MAX_ACL_KIND] =
 {
+	/* ACL_KIND_COLUMN */
+	gettext_noop("permission denied for column %s"),
 	/* ACL_KIND_CLASS */
 	gettext_noop("permission denied for relation %s"),
 	/* ACL_KIND_SEQUENCE */
@@ -1713,6 +2106,8 @@ static const char *const no_priv_msg[MAX_ACL_KIND] =
 
 static const char *const not_owner_msg[MAX_ACL_KIND] =
 {
+	/* ACL_KIND_COLUMN */
+	gettext_noop("must be owner of relation %s"),
 	/* ACL_KIND_CLASS */
 	gettext_noop("must be owner of relation %s"),
 	/* ACL_KIND_SEQUENCE */
@@ -1774,6 +2169,34 @@ aclcheck_error(AclResult aclerr, AclObjectKind objectkind,
 }
 
 
+void
+aclcheck_error_col(AclResult aclerr, AclObjectKind objectkind,
+				   const char *objectname, const char *colname)
+{
+	switch (aclerr)
+	{
+		case ACLCHECK_OK:
+			/* no error, so return to caller */
+			break;
+		case ACLCHECK_NO_PRIV:
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("permission denied for column %s of relation %s",
+							colname, objectname)));
+			break;
+		case ACLCHECK_NOT_OWNER:
+			/* relation msg is OK since columns don't have separate owners */
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg(not_owner_msg[objectkind], objectname)));
+			break;
+		default:
+			elog(ERROR, "unrecognized AclResult: %d", (int) aclerr);
+			break;
+	}
+}
+
+
 /* Check if given user has rolcatupdate privilege according to pg_authid */
 static bool
 has_rolcatupdate(Oid roleid)
@@ -1800,11 +2223,15 @@ has_rolcatupdate(Oid roleid)
  * Relay for the various pg_*_mask routines depending on object kind
  */
 static AclMode
-pg_aclmask(AclObjectKind objkind, Oid table_oid, Oid roleid,
+pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum, Oid roleid,
 		   AclMode mask, AclMaskHow how)
 {
 	switch (objkind)
 	{
+		case ACL_KIND_COLUMN:
+			return
+				pg_class_aclmask(table_oid, roleid, mask, how) |
+				pg_attribute_aclmask(table_oid, attnum, roleid, mask, how);
 		case ACL_KIND_CLASS:
 		case ACL_KIND_SEQUENCE:
 			return pg_class_aclmask(table_oid, roleid, mask, how);
@@ -1830,15 +2257,105 @@ pg_aclmask(AclObjectKind objkind, Oid table_oid, Oid roleid,
 	}
 }
 
-/*
- * Exported routine for examining a user's privileges for a table
+
+/* ****************************************************************
+ * Exported routines for examining a user's privileges for various objects
  *
- * See aclmask() for a description of the API.
+ * See aclmask() for a description of the common API for these functions.
  *
  * Note: we give lookup failure the full ereport treatment because the
- * has_table_privilege() family of functions allow users to pass
- * any random OID to this function.  Likewise for the sibling functions
- * below.
+ * has_xxx_privilege() family of functions allow users to pass any random
+ * OID to these functions.
+ * ****************************************************************
+ */
+
+/*
+ * Exported routine for examining a user's privileges for a column
+ *
+ * Note: this considers only privileges granted specifically on the column.
+ * It is caller's responsibility to take relation-level privileges into account
+ * as appropriate.  (For the same reason, we have no special case for
+ * superuser-ness here.)
+ */
+AclMode
+pg_attribute_aclmask(Oid table_oid, AttrNumber attnum, Oid roleid,
+					 AclMode mask, AclMaskHow how)
+{
+	AclMode		result;
+	HeapTuple	classTuple;
+	HeapTuple	attTuple;
+	Form_pg_class classForm;
+	Form_pg_attribute attributeForm;
+	Datum		aclDatum;
+	bool		isNull;
+	Acl		   *acl;
+	Oid			ownerId;
+
+	/*
+	 * Must get the relation's tuple from pg_class (only needed for ownerId)
+	 */
+	classTuple = SearchSysCache(RELOID,
+								ObjectIdGetDatum(table_oid),
+								0, 0, 0);
+	if (!HeapTupleIsValid(classTuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_TABLE),
+				 errmsg("relation with OID %u does not exist",
+						table_oid)));
+	classForm = (Form_pg_class) GETSTRUCT(classTuple);
+
+	ownerId = classForm->relowner;
+
+	/*
+	 * Next, get the column's ACL from pg_attribute
+	 */
+	attTuple = SearchSysCache(ATTNUM,
+							  ObjectIdGetDatum(table_oid),
+							  Int16GetDatum(attnum),
+							  0, 0);
+	if (!HeapTupleIsValid(attTuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("attribute %d of relation with OID %u does not exist",
+						attnum, table_oid)));
+	attributeForm = (Form_pg_attribute) GETSTRUCT(attTuple);
+
+	/* Throw error on dropped columns, too */
+	if (attributeForm->attisdropped)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("attribute %d of relation with OID %u does not exist",
+						attnum, table_oid)));
+
+	aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+							   &isNull);
+
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(ACL_OBJECT_COLUMN, ownerId);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast column's ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, ownerId, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(attTuple);
+	ReleaseSysCache(classTuple);
+
+	return result;
+}
+
+/*
+ * Exported routine for examining a user's privileges for a table
  */
 AclMode
 pg_class_aclmask(Oid table_oid, Oid roleid,
@@ -2376,6 +2893,115 @@ pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
 	return result;
 }
 
+/*
+ * Exported routine for checking a user's access privileges to a column
+ *
+ * Returns ACLCHECK_OK if the user has any of the privileges identified by
+ * 'mode'; otherwise returns a suitable error code (in practice, always
+ * ACLCHECK_NO_PRIV).
+ *
+ * As with pg_attribute_aclmask, only privileges granted directly on the
+ * column are considered here.
+ */
+AclResult
+pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
+					  Oid roleid, AclMode mode)
+{
+	if (pg_attribute_aclmask(table_oid, attnum, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
+/*
+ * Exported routine for checking a user's access privileges to any/all columns
+ *
+ * If 'how' is ACLMASK_ANY, then returns ACLCHECK_OK if user has any of the
+ * privileges identified by 'mode' on any non-dropped column in the relation;
+ * otherwise returns a suitable error code (in practice, always
+ * ACLCHECK_NO_PRIV).
+ *
+ * If 'how' is ACLMASK_ALL, then returns ACLCHECK_OK if user has any of the
+ * privileges identified by 'mode' on all non-dropped columns in the relation
+ * (and there must be at least one such column); otherwise returns a suitable
+ * error code (in practice, always ACLCHECK_NO_PRIV).
+ *
+ * As with pg_attribute_aclmask, only privileges granted directly on the
+ * column(s) are considered here.
+ *
+ * Note: system columns are not considered here; there are cases where that
+ * might be appropriate but there are also cases where it wouldn't.
+ */
+AclResult
+pg_attribute_aclcheck_all(Oid table_oid, Oid roleid, AclMode mode,
+						  AclMaskHow how)
+{
+	AclResult		result;
+	HeapTuple		classTuple;
+	Form_pg_class	classForm;
+	AttrNumber		nattrs;
+	AttrNumber		curr_att;
+
+	/* Must fetch pg_class row to check number of attributes */
+	classTuple = SearchSysCache(RELOID,
+								ObjectIdGetDatum(table_oid),
+								0, 0, 0);
+	if (!HeapTupleIsValid(classTuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_TABLE),
+				 errmsg("relation with OID %u does not exist",
+						table_oid)));
+	classForm = (Form_pg_class) GETSTRUCT(classTuple);
+
+	nattrs = classForm->relnatts;
+
+	ReleaseSysCache(classTuple);
+
+	/*
+	 * Initialize result in case there are no non-dropped columns.  We want
+	 * to report failure in such cases for either value of 'how'.
+	 */
+	result = ACLCHECK_NO_PRIV;
+
+	for (curr_att = 1; curr_att <= nattrs; curr_att++)
+	{
+		HeapTuple	attTuple;
+		bool		isdropped;
+
+		attTuple = SearchSysCache(ATTNUM,
+								  ObjectIdGetDatum(table_oid),
+								  Int16GetDatum(curr_att),
+								  0, 0);
+		if (!HeapTupleIsValid(attTuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 curr_att, table_oid);
+
+		isdropped = ((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped;
+
+		ReleaseSysCache(attTuple);
+
+		/* ignore dropped columns */
+		if (isdropped)
+			continue;
+
+		if (pg_attribute_aclmask(table_oid, curr_att, roleid,
+								 mode, ACLMASK_ANY) != 0)
+		{
+			result = ACLCHECK_OK;
+			if (how == ACLMASK_ANY)
+				break;			/* succeed on any success */
+		}
+		else
+		{
+			result = ACLCHECK_NO_PRIV;
+			if (how == ACLMASK_ALL)
+				break;			/* fail on any failure */
+		}
+	}
+
+	return result;
+}
+
 /*
  * Exported routine for checking a user's access privileges to a table
  *
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6f63232ff54..70c43cdec02 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.85 2009/01/01 17:23:36 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.86 2009/01/22 20:16:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -976,6 +976,13 @@ deleteOneObject(const ObjectAddress *object, Relation depRel)
 
 	systable_endscan(scan);
 
+	/*
+	 * Delete shared dependency references related to this object.  Again,
+	 * if subId = 0, remove records for sub-objects too.
+	 */
+	deleteSharedDependencyRecordsFor(object->classId, object->objectId,
+									 object->objectSubId);
+
 	/*
 	 * Now delete the object itself, in an object-type-dependent way.
 	 */
@@ -987,13 +994,6 @@ deleteOneObject(const ObjectAddress *object, Relation depRel)
 	 */
 	DeleteComments(object->objectId, object->classId, object->objectSubId);
 
-	/*
-	 * Delete shared dependency references related to this object. Sub-objects
-	 * (columns) don't have dependencies on global objects, so skip them.
-	 */
-	if (object->objectSubId == 0)
-		deleteSharedDependencyRecordsFor(object->classId, object->objectId);
-
 	/*
 	 * CommandCounterIncrement here to ensure that preceding changes are all
 	 * visible to the next deletion step.
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 462a1aac614..b01edbe0170 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.349 2009/01/01 17:23:36 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.350 2009/01/22 20:16:01 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -112,37 +112,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum);
 static FormData_pg_attribute a1 = {
 	0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData),
 	SelfItemPointerAttributeNumber, 0, -1, -1,
-	false, 'p', 's', true, false, false, true, 0
+	false, 'p', 's', true, false, false, true, 0, { 0 }
 };
 
 static FormData_pg_attribute a2 = {
 	0, {"oid"}, OIDOID, 0, sizeof(Oid),
 	ObjectIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, true, 0, { 0 }
 };
 
 static FormData_pg_attribute a3 = {
 	0, {"xmin"}, XIDOID, 0, sizeof(TransactionId),
 	MinTransactionIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, true, 0, { 0 }
 };
 
 static FormData_pg_attribute a4 = {
 	0, {"cmin"}, CIDOID, 0, sizeof(CommandId),
 	MinCommandIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, true, 0, { 0 }
 };
 
 static FormData_pg_attribute a5 = {
 	0, {"xmax"}, XIDOID, 0, sizeof(TransactionId),
 	MaxTransactionIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, true, 0, { 0 }
 };
 
 static FormData_pg_attribute a6 = {
 	0, {"cmax"}, CIDOID, 0, sizeof(CommandId),
 	MaxCommandIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, true, 0, { 0 }
 };
 
 /*
@@ -154,7 +154,7 @@ static FormData_pg_attribute a6 = {
 static FormData_pg_attribute a7 = {
 	0, {"tableoid"}, OIDOID, 0, sizeof(Oid),
 	TableOidAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, true, 0, { 0 }
 };
 
 static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7};
@@ -475,11 +475,13 @@ CheckAttributeType(const char *attname, Oid atttypid)
  *		Construct and insert a new tuple in pg_attribute.
  *
  * Caller has already opened and locked pg_attribute.  new_attribute is the
- * attribute to insert.
+ * attribute to insert (but we ignore its attacl, if indeed it has one).
  *
  * indstate is the index state for CatalogIndexInsert.  It can be passed as
  * NULL, in which case we'll fetch the necessary info.  (Don't do this when
  * inserting multiple attributes, because it's a tad more expensive.)
+ *
+ * We always initialize attacl to NULL (i.e., default permissions).
  */
 void
 InsertPgAttributeTuple(Relation pg_attribute_rel,
@@ -512,6 +514,9 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
 	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
 	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
 
+	/* start out with empty permissions */
+	nulls[Anum_pg_attribute_attacl - 1] = true;
+
 	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
 
 	/* finally insert the new tuple, update the indexes, and clean up */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index dee85b67161..e53f4f52dcf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.311 2009/01/01 17:23:37 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.312 2009/01/22 20:16:01 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -178,7 +178,7 @@ ConstructTupleDescriptor(Relation heapRelation,
 			 * now that we've determined the "from", let's copy the tuple desc
 			 * data...
 			 */
-			memcpy(to, from, ATTRIBUTE_TUPLE_SIZE);
+			memcpy(to, from, ATTRIBUTE_FIXED_PART_SIZE);
 
 			/*
 			 * Fix the stuff that should not be the same as the underlying
@@ -198,7 +198,7 @@ ConstructTupleDescriptor(Relation heapRelation,
 			/* Expressional index */
 			Node	   *indexkey;
 
-			MemSet(to, 0, ATTRIBUTE_TUPLE_SIZE);
+			MemSet(to, 0, ATTRIBUTE_FIXED_PART_SIZE);
 
 			if (indexpr_item == NULL)	/* shouldn't happen */
 				elog(ERROR, "too few entries in indexprs list");
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 989f2470af1..12ffd742566 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/pg_operator.c,v 1.107 2009/01/01 17:23:37 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/pg_operator.c,v 1.108 2009/01/22 20:16:01 tgl Exp $
  *
  * NOTES
  *	  these routines moved here from commands/define.c and somewhat cleaned up.
@@ -774,7 +774,7 @@ makeOperatorDependencies(HeapTuple tuple)
 
 	/* In case we are updating a shell, delete any existing entries */
 	deleteDependencyRecordsFor(myself.classId, myself.objectId);
-	deleteSharedDependencyRecordsFor(myself.classId, myself.objectId);
+	deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
 
 	/* Dependency on namespace */
 	if (OidIsValid(oper->oprnamespace))
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 191b8b8a9e4..f9b2716b9f7 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.160 2009/01/01 17:23:37 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.161 2009/01/22 20:16:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -499,7 +499,7 @@ ProcedureCreate(const char *procedureName,
 	if (is_update)
 	{
 		deleteDependencyRecordsFor(ProcedureRelationId, retval);
-		deleteSharedDependencyRecordsFor(ProcedureRelationId, retval);
+		deleteSharedDependencyRecordsFor(ProcedureRelationId, retval, 0);
 	}
 
 	myself.classId = ProcedureRelationId;
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 113c72f3245..ee5329b1d70 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.30 2009/01/01 17:23:37 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.31 2009/01/22 20:16:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -55,15 +55,19 @@ static int getOidListDiff(Oid *list1, int nlist1, Oid *list2, int nlist2,
 			   Oid **diff);
 static Oid	classIdGetDbId(Oid classId);
 static void shdepLockAndCheckObject(Oid classId, Oid objectId);
-static void shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
-			   Oid refclassid, Oid refobjid,
-			   SharedDependencyType deptype);
-static void shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
-				   Oid refclassId, Oid refobjId,
-				   SharedDependencyType deptype);
-static void shdepDropDependency(Relation sdepRel, Oid classId, Oid objectId,
-					Oid refclassId, Oid refobjId,
-					SharedDependencyType deptype);
+static void shdepChangeDep(Relation sdepRel,
+						   Oid classid, Oid objid, int32 objsubid,
+						   Oid refclassid, Oid refobjid,
+						   SharedDependencyType deptype);
+static void shdepAddDependency(Relation sdepRel,
+							   Oid classId, Oid objectId, int32 objsubId,
+							   Oid refclassId, Oid refobjId,
+							   SharedDependencyType deptype);
+static void shdepDropDependency(Relation sdepRel,
+								Oid classId, Oid objectId, int32 objsubId,
+								bool drop_subobjects,
+								Oid refclassId, Oid refobjId,
+								SharedDependencyType deptype);
 static void storeObjectDescription(StringInfo descs, objectType type,
 					   ObjectAddress *object,
 					   SharedDependencyType deptype,
@@ -111,6 +115,7 @@ recordSharedDependencyOn(ObjectAddress *depender,
 							  sdepRel))
 	{
 		shdepAddDependency(sdepRel, depender->classId, depender->objectId,
+						   depender->objectSubId,
 						   referenced->classId, referenced->objectId,
 						   deptype);
 	}
@@ -163,14 +168,15 @@ recordDependencyOnOwner(Oid classId, Oid objectId, Oid owner)
  * locked.
  */
 static void
-shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
+shdepChangeDep(Relation sdepRel,
+			   Oid classid, Oid objid, int32 objsubid,
 			   Oid refclassid, Oid refobjid,
 			   SharedDependencyType deptype)
 {
 	Oid			dbid = classIdGetDbId(classid);
 	HeapTuple	oldtup = NULL;
 	HeapTuple	scantup;
-	ScanKeyData key[3];
+	ScanKeyData key[4];
 	SysScanDesc scan;
 
 	/*
@@ -194,9 +200,13 @@ shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
 				Anum_pg_shdepend_objid,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(objid));
+	ScanKeyInit(&key[3],
+				Anum_pg_shdepend_objsubid,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(objsubid));
 
 	scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
-							  SnapshotNow, 3, key);
+							  SnapshotNow, 4, key);
 
 	while ((scantup = systable_getnext(scan)) != NULL)
 	{
@@ -206,8 +216,8 @@ shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
 		/* Caller screwed up if multiple matches */
 		if (oldtup)
 			elog(ERROR,
-				 "multiple pg_shdepend entries for object %u/%u deptype %c",
-				 classid, objid, deptype);
+				 "multiple pg_shdepend entries for object %u/%u/%d deptype %c",
+				 classid, objid, objsubid, deptype);
 		oldtup = heap_copytuple(scantup);
 	}
 
@@ -244,6 +254,7 @@ shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
 		values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(dbid);
 		values[Anum_pg_shdepend_classid - 1] = ObjectIdGetDatum(classid);
 		values[Anum_pg_shdepend_objid - 1] = ObjectIdGetDatum(objid);
+		values[Anum_pg_shdepend_objsubid - 1] = Int32GetDatum(objsubid);
 
 		values[Anum_pg_shdepend_refclassid - 1] = ObjectIdGetDatum(refclassid);
 		values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(refobjid);
@@ -268,6 +279,9 @@ shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
  * changeDependencyOnOwner
  *
  * Update the shared dependencies to account for the new owner.
+ *
+ * Note: we don't need an objsubid argument because only whole objects
+ * have owners.
  */
 void
 changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
@@ -277,7 +291,8 @@ changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
 	sdepRel = heap_open(SharedDependRelationId, RowExclusiveLock);
 
 	/* Adjust the SHARED_DEPENDENCY_OWNER entry */
-	shdepChangeDep(sdepRel, classId, objectId,
+	shdepChangeDep(sdepRel,
+				   classId, objectId, 0,
 				   AuthIdRelationId, newOwnerId,
 				   SHARED_DEPENDENCY_OWNER);
 
@@ -300,7 +315,7 @@ changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
 	 * to make the various ALTER OWNER routines each know about it.
 	 *----------
 	 */
-	shdepDropDependency(sdepRel, classId, objectId,
+	shdepDropDependency(sdepRel, classId, objectId, 0, true,
 						AuthIdRelationId, newOwnerId,
 						SHARED_DEPENDENCY_ACL);
 
@@ -368,7 +383,7 @@ getOidListDiff(Oid *list1, int nlist1, Oid *list2, int nlist2, Oid **diff)
  * updateAclDependencies
  *		Update the pg_shdepend info for an object's ACL during GRANT/REVOKE.
  *
- * classId, objectId: identify the object whose ACL this is
+ * classId, objectId, objsubId: identify the object whose ACL this is
  * ownerId: role owning the object
  * isGrant: are we adding or removing ACL entries?
  * noldmembers, oldmembers: array of roleids appearing in old ACL
@@ -388,7 +403,8 @@ getOidListDiff(Oid *list1, int nlist1, Oid *list2, int nlist2, Oid **diff)
  * before return.
  */
 void
-updateAclDependencies(Oid classId, Oid objectId, Oid ownerId, bool isGrant,
+updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
+					  Oid ownerId, bool isGrant,
 					  int noldmembers, Oid *oldmembers,
 					  int nnewmembers, Oid *newmembers)
 {
@@ -429,11 +445,12 @@ updateAclDependencies(Oid classId, Oid objectId, Oid ownerId, bool isGrant,
 				continue;
 
 			if (isGrant)
-				shdepAddDependency(sdepRel, classId, objectId,
+				shdepAddDependency(sdepRel, classId, objectId, objsubId,
 								   AuthIdRelationId, roleid,
 								   SHARED_DEPENDENCY_ACL);
 			else
-				shdepDropDependency(sdepRel, classId, objectId,
+				shdepDropDependency(sdepRel, classId, objectId, objsubId,
+									false, /* exact match on objsubId */
 									AuthIdRelationId, roleid,
 									SHARED_DEPENDENCY_ACL);
 		}
@@ -533,7 +550,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 
 		object.classId = sdepForm->classid;
 		object.objectId = sdepForm->objid;
-		object.objectSubId = 0;
+		object.objectSubId = sdepForm->objsubid;
 
 		/*
 		 * If it's a dependency local to this database or it's a shared
@@ -755,7 +772,7 @@ dropDatabaseDependencies(Oid databaseId)
 	systable_endscan(scan);
 
 	/* Now delete all entries corresponding to the database itself */
-	shdepDropDependency(sdepRel, DatabaseRelationId, databaseId,
+	shdepDropDependency(sdepRel, DatabaseRelationId, databaseId, 0, true,
 						InvalidOid, InvalidOid,
 						SHARED_DEPENDENCY_INVALID);
 
@@ -768,15 +785,19 @@ dropDatabaseDependencies(Oid databaseId)
  * Delete all pg_shdepend entries corresponding to an object that's being
  * dropped or modified.  The object is assumed to be either a shared object
  * or local to the current database (the classId tells us which).
+ *
+ * If objectSubId is zero, we are deleting a whole object, so get rid of
+ * pg_shdepend entries for subobjects as well.
  */
 void
-deleteSharedDependencyRecordsFor(Oid classId, Oid objectId)
+deleteSharedDependencyRecordsFor(Oid classId, Oid objectId, int32 objectSubId)
 {
 	Relation	sdepRel;
 
 	sdepRel = heap_open(SharedDependRelationId, RowExclusiveLock);
 
-	shdepDropDependency(sdepRel, classId, objectId,
+	shdepDropDependency(sdepRel, classId, objectId, objectSubId,
+						(objectSubId == 0),
 						InvalidOid, InvalidOid,
 						SHARED_DEPENDENCY_INVALID);
 
@@ -791,7 +812,8 @@ deleteSharedDependencyRecordsFor(Oid classId, Oid objectId)
  * locked.
  */
 static void
-shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
+shdepAddDependency(Relation sdepRel,
+				   Oid classId, Oid objectId, int32 objsubId,
 				   Oid refclassId, Oid refobjId,
 				   SharedDependencyType deptype)
 {
@@ -814,6 +836,7 @@ shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
 	values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(classIdGetDbId(classId));
 	values[Anum_pg_shdepend_classid - 1] = ObjectIdGetDatum(classId);
 	values[Anum_pg_shdepend_objid - 1] = ObjectIdGetDatum(objectId);
+	values[Anum_pg_shdepend_objsubid - 1] = Int32GetDatum(objsubId);
 
 	values[Anum_pg_shdepend_refclassid - 1] = ObjectIdGetDatum(refclassId);
 	values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(refobjId);
@@ -835,20 +858,26 @@ shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
  *		Internal workhorse for deleting entries from pg_shdepend.
  *
  * We drop entries having the following properties:
- *	dependent object is the one identified by classId/objectId
+ *	dependent object is the one identified by classId/objectId/objsubId
  *	if refclassId isn't InvalidOid, it must match the entry's refclassid
  *	if refobjId isn't InvalidOid, it must match the entry's refobjid
  *	if deptype isn't SHARED_DEPENDENCY_INVALID, it must match entry's deptype
  *
+ * If drop_subobjects is true, we ignore objsubId and consider all entries
+ * matching classId/objectId.
+ *
  * sdepRel must be the pg_shdepend relation, already opened and suitably
  * locked.
  */
 static void
-shdepDropDependency(Relation sdepRel, Oid classId, Oid objectId,
+shdepDropDependency(Relation sdepRel,
+					Oid classId, Oid objectId, int32 objsubId,
+					bool drop_subobjects,
 					Oid refclassId, Oid refobjId,
 					SharedDependencyType deptype)
 {
-	ScanKeyData key[3];
+	ScanKeyData key[4];
+	int			nkeys;
 	SysScanDesc scan;
 	HeapTuple	tup;
 
@@ -865,9 +894,19 @@ shdepDropDependency(Relation sdepRel, Oid classId, Oid objectId,
 				Anum_pg_shdepend_objid,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(objectId));
+	if (drop_subobjects)
+		nkeys = 3;
+	else
+	{
+		ScanKeyInit(&key[3],
+					Anum_pg_shdepend_objsubid,
+					BTEqualStrategyNumber, F_INT4EQ,
+					Int32GetDatum(objsubId));
+		nkeys = 4;
+	}
 
 	scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
-							  SnapshotNow, 3, key);
+							  SnapshotNow, nkeys, key);
 
 	while (HeapTupleIsValid(tup = systable_getnext(scan)))
 	{
@@ -1067,7 +1106,7 @@ isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel)
  *
  * Drop the objects owned by any one of the given RoleIds.	If a role has
  * access to an object, the grant will be removed as well (but the object
- * will not, of course.)
+ * will not, of course).
  *
  * We can revoke grants immediately while doing the scan, but drops are
  * saved up and done all at once with performMultipleDeletions.  This
@@ -1131,10 +1170,9 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 
 		while ((tuple = systable_getnext(scan)) != NULL)
 		{
-			ObjectAddress obj;
-			GrantObjectType objtype;
-			InternalGrant istmt;
 			Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tuple);
+			InternalGrant istmt;
+			ObjectAddress obj;
 
 			/* We only operate on objects in the current database */
 			if (sdepForm->dbid != MyDatabaseId)
@@ -1172,14 +1210,13 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 						default:
 							elog(ERROR, "unexpected object type %d",
 								 sdepForm->classid);
-							/* keep compiler quiet */
-							objtype = (GrantObjectType) 0;
 							break;
 					}
 					istmt.is_grant = false;
 					istmt.objects = list_make1_oid(sdepForm->objid);
 					istmt.all_privs = true;
 					istmt.privileges = ACL_NO_RIGHTS;
+					istmt.col_privs = NIL;
 					istmt.grantees = list_make1_oid(roleid);
 					istmt.grant_option = false;
 					istmt.behavior = DROP_CASCADE;
@@ -1190,7 +1227,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					/* Save it for deletion below */
 					obj.classId = sdepForm->classid;
 					obj.objectId = sdepForm->objid;
-					obj.objectSubId = 0;
+					obj.objectSubId = sdepForm->objsubid;
 					add_exact_object_address(&obj, deleteobjs);
 					break;
 			}
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index d39a66b58d2..6d28b1df2fd 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/pg_type.c,v 1.123 2009/01/01 17:23:37 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/pg_type.c,v 1.124 2009/01/22 20:16:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -482,7 +482,7 @@ GenerateTypeDependencies(Oid typeNamespace,
 	if (rebuild)
 	{
 		deleteDependencyRecordsFor(TypeRelationId, typeObjectId);
-		deleteSharedDependencyRecordsFor(TypeRelationId, typeObjectId);
+		deleteSharedDependencyRecordsFor(TypeRelationId, typeObjectId, 0);
 	}
 
 	myself.classId = TypeRelationId;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 5f6a2c42de6..33447b671f1 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.132 2009/01/06 23:46:06 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.133 2009/01/22 20:16:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -705,11 +705,12 @@ examine_attribute(Relation onerel, int attnum)
 		return NULL;
 
 	/*
-	 * Create the VacAttrStats struct.
+	 * Create the VacAttrStats struct.  Note that we only have a copy of
+	 * the fixed fields of the pg_attribute tuple.
 	 */
 	stats = (VacAttrStats *) palloc0(sizeof(VacAttrStats));
-	stats->attr = (Form_pg_attribute) palloc(ATTRIBUTE_TUPLE_SIZE);
-	memcpy(stats->attr, attr, ATTRIBUTE_TUPLE_SIZE);
+	stats->attr = (Form_pg_attribute) palloc(ATTRIBUTE_FIXED_PART_SIZE);
+	memcpy(stats->attr, attr, ATTRIBUTE_FIXED_PART_SIZE);
 	typtuple = SearchSysCache(TYPEOID,
 							  ObjectIdGetDatum(attr->atttypid),
 							  0, 0, 0);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 061d45c30ac..397e010acaf 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.277 2009/01/12 08:54:26 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.278 2009/01/22 20:16:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -246,6 +246,7 @@ static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 static Oid transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
 						Oid *opclasses);
+static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
 static void validateForeignKeyConstraint(FkConstraint *fkconstraint,
 							 Relation rel, Relation pkrel, Oid constraintOid);
 static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
@@ -3589,6 +3590,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
 	attribute.attisdropped = false;
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
+	/* attribute.attacl is handled by InsertPgAttributeTuple */
 
 	ReleaseSysCache(typeTuple);
 
@@ -4473,15 +4475,14 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
  * Add a foreign-key constraint to a single table
  *
  * Subroutine for ATExecAddConstraint.	Must already hold exclusive
- * lock on the rel, and have done appropriate validity/permissions checks
- * for it.
+ * lock on the rel, and have done appropriate validity checks for it.
+ * We do permissions checks here, however.
  */
 static void
 ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 						  FkConstraint *fkconstraint)
 {
 	Relation	pkrel;
-	AclResult	aclresult;
 	int16		pkattnum[INDEX_MAX_KEYS];
 	int16		fkattnum[INDEX_MAX_KEYS];
 	Oid			pktypoid[INDEX_MAX_KEYS];
@@ -4506,10 +4507,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
 
 	/*
-	 * Validity and permissions checks
-	 *
-	 * Note: REFERENCES permissions checks are redundant with CREATE TRIGGER,
-	 * but we may as well error out sooner instead of later.
+	 * Validity checks (permission checks wait till we have the column numbers)
 	 */
 	if (pkrel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
@@ -4517,24 +4515,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 				 errmsg("referenced relation \"%s\" is not a table",
 						RelationGetRelationName(pkrel))));
 
-	aclresult = pg_class_aclcheck(RelationGetRelid(pkrel), GetUserId(),
-								  ACL_REFERENCES);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error(aclresult, ACL_KIND_CLASS,
-					   RelationGetRelationName(pkrel));
-
 	if (!allowSystemTableMods && IsSystemRelation(pkrel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("permission denied: \"%s\" is a system catalog",
 						RelationGetRelationName(pkrel))));
 
-	aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
-								  ACL_REFERENCES);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error(aclresult, ACL_KIND_CLASS,
-					   RelationGetRelationName(rel));
-
 	/*
 	 * Disallow reference from permanent table to temp table or vice versa.
 	 * (The ban on perm->temp is for fairly obvious reasons.  The ban on
@@ -4598,6 +4584,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 										   opclasses);
 	}
 
+	/*
+	 * Now we can check permissions.
+	 */
+	checkFkeyPermissions(pkrel, pkattnum, numpks);
+	checkFkeyPermissions(rel, fkattnum, numfks);
+
 	/*
 	 * Look up the equality operators to use in the constraint.
 	 *
@@ -5016,6 +5008,30 @@ transformFkeyCheckAttrs(Relation pkrel,
 	return indexoid;
 }
 
+/* Permissions checks for ADD FOREIGN KEY */
+static void
+checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
+{
+	Oid			roleid = GetUserId();
+	AclResult	aclresult;
+	int			i;
+
+	/* Okay if we have relation-level REFERENCES permission */
+	aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid,
+								  ACL_REFERENCES);
+	if (aclresult == ACLCHECK_OK)
+		return;
+	/* Else we must have REFERENCES on each column */
+	for (i = 0; i < natts; i++)
+	{
+		aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i],
+										  roleid, ACL_REFERENCES);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error(aclresult, ACL_KIND_CLASS,
+						   RelationGetRelationName(rel));
+	}
+}
+
 /*
  * Scan the existing rows in a table to verify they meet a proposed FK
  * constraint.
@@ -5123,7 +5139,7 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
 	fk_trigger->constrrel = fkconstraint->pktable;
 	fk_trigger->args = NIL;
 
-	(void) CreateTrigger(fk_trigger, constraintOid);
+	(void) CreateTrigger(fk_trigger, constraintOid, false);
 
 	/* Make changes-so-far visible */
 	CommandCounterIncrement();
@@ -5204,7 +5220,7 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
 	}
 	fk_trigger->args = NIL;
 
-	(void) CreateTrigger(fk_trigger, constraintOid);
+	(void) CreateTrigger(fk_trigger, constraintOid, false);
 
 	/* Make changes-so-far visible */
 	CommandCounterIncrement();
@@ -5256,7 +5272,7 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
 	}
 	fk_trigger->args = NIL;
 
-	(void) CreateTrigger(fk_trigger, constraintOid);
+	(void) CreateTrigger(fk_trigger, constraintOid, false);
 }
 
 /*
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 54818ff34d7..b81381a6ea8 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -37,7 +37,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.60 2009/01/20 18:59:37 heikki Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.61 2009/01/22 20:16:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -455,7 +455,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 	/*
 	 * Remove dependency on owner.
 	 */
-	deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid);
+	deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid, 0);
 
 	/*
 	 * Acquire TablespaceCreateLock to ensure that no TablespaceCreateDbspace
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 699493c3350..ce276e5fe55 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.245 2009/01/22 19:16:31 heikki Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.246 2009/01/22 20:16:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -74,11 +74,16 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
  * be made to link the trigger to that constraint.	constraintOid is zero when
  * executing a user-entered CREATE TRIGGER command.
  *
+ * If checkPermissions is true we require ACL_TRIGGER permissions on the
+ * relation.  If not, the caller already checked permissions.  (This is
+ * currently redundant with constraintOid being zero, but it's clearer to
+ * have a separate argument.)
+ *
  * Note: can return InvalidOid if we decided to not create a trigger at all,
  * but a foreign-key constraint.  This is a kluge for backwards compatibility.
  */
 Oid
-CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid)
+CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid, bool checkPermissions)
 {
 	int16		tgtype;
 	int2vector *tgattr;
@@ -117,37 +122,27 @@ CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid)
 				 errmsg("permission denied: \"%s\" is a system catalog",
 						RelationGetRelationName(rel))));
 
-	/* permission checks */
+	if (stmt->isconstraint && stmt->constrrel != NULL)
+		constrrelid = RangeVarGetRelid(stmt->constrrel, false);
 
-	if (stmt->isconstraint)
+	/* permission checks */
+	if (checkPermissions)
 	{
-		/* constraint trigger */
 		aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
-									  ACL_REFERENCES);
+									  ACL_TRIGGER);
 		if (aclresult != ACLCHECK_OK)
 			aclcheck_error(aclresult, ACL_KIND_CLASS,
 						   RelationGetRelationName(rel));
 
-		if (stmt->constrrel != NULL)
+		if (OidIsValid(constrrelid))
 		{
-			constrrelid = RangeVarGetRelid(stmt->constrrel, false);
-
 			aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
-										  ACL_REFERENCES);
+										  ACL_TRIGGER);
 			if (aclresult != ACLCHECK_OK)
 				aclcheck_error(aclresult, ACL_KIND_CLASS,
 							   get_rel_name(constrrelid));
 		}
 	}
-	else
-	{
-		/* regular trigger */
-		aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
-									  ACL_TRIGGER);
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult, ACL_KIND_CLASS,
-						   RelationGetRelationName(rel));
-	}
 
 	/* Compute tgtype */
 	TRIGGER_CLEAR_TYPE(tgtype);
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index f9248a2b674..7276cd50d40 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.15 2009/01/01 17:23:40 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.16 2009/01/22 20:16:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1255,7 +1255,7 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
 	if (removeOld)
 	{
 		deleteDependencyRecordsFor(myself.classId, myself.objectId);
-		deleteSharedDependencyRecordsFor(myself.classId, myself.objectId);
+		deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
 	}
 
 	/*
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index a013e80ac94..7c1da42bc3e 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.184 2009/01/01 17:23:40 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.185 2009/01/22 20:16:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1123,9 +1123,17 @@ GrantRole(GrantRoleStmt *stmt)
 	 */
 	foreach(item, stmt->granted_roles)
 	{
-		char	   *rolename = strVal(lfirst(item));
-		Oid			roleid = get_roleid_checked(rolename);
+		AccessPriv *priv = (AccessPriv *) lfirst(item);
+		char	   *rolename = priv->priv_name;
+		Oid			roleid;
+
+		/* Must reject priv(columns) and ALL PRIVILEGES(columns) */
+		if (rolename == NULL || priv->cols != NIL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_GRANT_OPERATION),
+					 errmsg("column names cannot be included in GRANT/REVOKE ROLE")));
 
+		roleid = get_roleid_checked(rolename);
 		if (stmt->is_grant)
 			AddRoleMems(rolename, roleid,
 						stmt->grantee_roles, grantee_ids,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index bb05b5d4828..0352d9a5e43 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.320 2009/01/01 17:23:41 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.321 2009/01/22 20:16:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,6 +34,7 @@
 
 #include "access/heapam.h"
 #include "access/reloptions.h"
+#include "access/sysattr.h"
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/heap.h"
@@ -453,8 +454,12 @@ static void
 ExecCheckRTEPerms(RangeTblEntry *rte)
 {
 	AclMode		requiredPerms;
+	AclMode		relPerms;
+	AclMode		remainingPerms;
 	Oid			relOid;
 	Oid			userid;
+	Bitmapset  *tmpset;
+	int			col;
 
 	/*
 	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
@@ -484,12 +489,110 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/*
-	 * We must have *all* the requiredPerms bits, so use aclmask not aclcheck.
+	 * We must have *all* the requiredPerms bits, but some of the bits can be
+	 * satisfied from column-level rather than relation-level permissions.
+	 * First, remove any bits that are satisfied by relation permissions.
 	 */
-	if (pg_class_aclmask(relOid, userid, requiredPerms, ACLMASK_ALL)
-		!= requiredPerms)
-		aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
-					   get_rel_name(relOid));
+	relPerms = pg_class_aclmask(relOid, userid, requiredPerms, ACLMASK_ALL);
+	remainingPerms = requiredPerms & ~relPerms;
+	if (remainingPerms != 0)
+	{
+		/*
+		 * If we lack any permissions that exist only as relation permissions,
+		 * we can fail straight away.
+		 */
+		if (remainingPerms & ~(ACL_SELECT | ACL_INSERT | ACL_UPDATE))
+			aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+						   get_rel_name(relOid));
+
+		/*
+		 * Check to see if we have the needed privileges at column level.
+		 *
+		 * Note: failures just report a table-level error; it would be nicer
+		 * to report a column-level error if we have some but not all of the
+		 * column privileges.
+		 */
+		if (remainingPerms & ACL_SELECT)
+		{
+			/*
+			 * When the query doesn't explicitly reference any columns (for
+			 * example, SELECT COUNT(*) FROM table), allow the query if we
+			 * have SELECT on any column of the rel, as per SQL spec.
+			 */
+			if (bms_is_empty(rte->selectedCols))
+			{
+				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
+											  ACLMASK_ANY) != ACLCHECK_OK)
+					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+								   get_rel_name(relOid));
+			}
+
+			tmpset = bms_copy(rte->selectedCols);
+			while ((col = bms_first_member(tmpset)) >= 0)
+			{
+				/* remove the column number offset */
+				col += FirstLowInvalidHeapAttributeNumber;
+				if (col == InvalidAttrNumber)
+				{
+					/* Whole-row reference, must have priv on all cols */
+					if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
+												  ACLMASK_ALL) != ACLCHECK_OK)
+						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+									   get_rel_name(relOid));
+				}
+				else
+				{
+					if (pg_attribute_aclcheck(relOid, col, userid, ACL_SELECT)
+						!= ACLCHECK_OK)
+						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+									   get_rel_name(relOid));
+				}
+			}
+			bms_free(tmpset);
+		}
+
+		/*
+		 * Basically the same for the mod columns, with either INSERT or UPDATE
+		 * privilege as specified by remainingPerms.
+		 */
+		remainingPerms &= ~ACL_SELECT;
+		if (remainingPerms != 0)
+		{
+			/*
+			 * When the query doesn't explicitly change any columns, allow
+			 * the query if we have permission on any column of the rel.  This
+			 * is to handle SELECT FOR UPDATE as well as possible corner cases
+			 * in INSERT and UPDATE.
+			 */
+			if (bms_is_empty(rte->modifiedCols))
+			{
+				if (pg_attribute_aclcheck_all(relOid, userid, remainingPerms,
+											  ACLMASK_ANY) != ACLCHECK_OK)
+					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+								   get_rel_name(relOid));
+			}
+
+			tmpset = bms_copy(rte->modifiedCols);
+			while ((col = bms_first_member(tmpset)) >= 0)
+			{
+				/* remove the column number offset */
+				col += FirstLowInvalidHeapAttributeNumber;
+				if (col == InvalidAttrNumber)
+				{
+					/* whole-row reference can't happen here */
+					elog(ERROR, "whole-row update is not implemented");
+				}
+				else
+				{
+					if (pg_attribute_aclcheck(relOid, col, userid, remainingPerms)
+						!= ACLCHECK_OK)
+						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+									   get_rel_name(relOid));
+				}
+			}
+			bms_free(tmpset);
+		}
+	}
 }
 
 /*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da1c65cfcca..06f89b16a30 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.420 2009/01/16 13:27:23 heikki Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.421 2009/01/22 20:16:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1740,6 +1740,8 @@ _copyRangeTblEntry(RangeTblEntry *from)
 	COPY_SCALAR_FIELD(inFromCl);
 	COPY_SCALAR_FIELD(requiredPerms);
 	COPY_SCALAR_FIELD(checkAsUser);
+	COPY_BITMAPSET_FIELD(selectedCols);
+	COPY_BITMAPSET_FIELD(modifiedCols);
 
 	return newnode;
 }
@@ -2342,6 +2344,17 @@ _copyFuncWithArgs(FuncWithArgs *from)
 	return newnode;
 }
 
+static AccessPriv *
+_copyAccessPriv(AccessPriv *from)
+{
+	AccessPriv *newnode = makeNode(AccessPriv);
+
+	COPY_STRING_FIELD(priv_name);
+	COPY_NODE_FIELD(cols);
+
+	return newnode;
+}
+
 static GrantRoleStmt *
 _copyGrantRoleStmt(GrantRoleStmt *from)
 {
@@ -4096,6 +4109,9 @@ copyObject(void *from)
 		case T_FuncWithArgs:
 			retval = _copyFuncWithArgs(from);
 			break;
+		case T_AccessPriv:
+			retval = _copyAccessPriv(from);
+			break;
 		case T_XmlSerialize:
 			retval = _copyXmlSerialize(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 190750f2e19..4e101dd23b8 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -22,7 +22,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.345 2009/01/16 13:27:23 heikki Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.346 2009/01/22 20:16:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1013,6 +1013,15 @@ _equalFuncWithArgs(FuncWithArgs *a, FuncWithArgs *b)
 	return true;
 }
 
+static bool
+_equalAccessPriv(AccessPriv *a, AccessPriv *b)
+{
+	COMPARE_STRING_FIELD(priv_name);
+	COMPARE_NODE_FIELD(cols);
+
+	return true;
+}
+
 static bool
 _equalGrantRoleStmt(GrantRoleStmt *a, GrantRoleStmt *b)
 {
@@ -2122,6 +2131,8 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(inFromCl);
 	COMPARE_SCALAR_FIELD(requiredPerms);
 	COMPARE_SCALAR_FIELD(checkAsUser);
+	COMPARE_BITMAPSET_FIELD(selectedCols);
+	COMPARE_BITMAPSET_FIELD(modifiedCols);
 
 	return true;
 }
@@ -2874,6 +2885,9 @@ equal(void *a, void *b)
 		case T_FuncWithArgs:
 			retval = _equalFuncWithArgs(a, b);
 			break;
+		case T_AccessPriv:
+			retval = _equalAccessPriv(a, b);
+			break;
 		case T_XmlSerialize:
 			retval = _equalXmlSerialize(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index af427fe7e6c..0efd84dae8d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.349 2009/01/01 17:23:43 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.350 2009/01/22 20:16:04 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -180,8 +180,6 @@ _outList(StringInfo str, List *node)
  *	   converts a bitmap set of integers
  *
  * Note: the output format is "(b int int ...)", similar to an integer List.
- * Currently bitmapsets do not appear in any node type that is stored in
- * rules, so there is no support in readfuncs.c for reading this format.
  */
 static void
 _outBitmapset(StringInfo str, Bitmapset *bms)
@@ -2060,6 +2058,8 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
 	WRITE_BOOL_FIELD(inFromCl);
 	WRITE_UINT_FIELD(requiredPerms);
 	WRITE_OID_FIELD(checkAsUser);
+	WRITE_BITMAPSET_FIELD(selectedCols);
+	WRITE_BITMAPSET_FIELD(modifiedCols);
 }
 
 static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d5f4677c2a3..de62e53d1df 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.220 2009/01/01 17:23:43 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.221 2009/01/22 20:16:04 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -114,6 +114,11 @@
 	token = pg_strtok(&length);		/* skip :fldname */ \
 	local_node->fldname = nodeRead(NULL, 0)
 
+/* Read a bitmapset field */
+#define READ_BITMAPSET_FIELD(fldname) \
+	token = pg_strtok(&length);		/* skip :fldname */ \
+	local_node->fldname = _readBitmapset()
+
 /* Routine exit */
 #define READ_DONE() \
 	return local_node
@@ -137,6 +142,46 @@
 
 static Datum readDatum(bool typbyval);
 
+/*
+ * _readBitmapset
+ */
+static Bitmapset *
+_readBitmapset(void)
+{
+	Bitmapset *result = NULL;
+	READ_TEMP_LOCALS();
+
+	token = pg_strtok(&length);
+	if (token == NULL)
+		elog(ERROR, "incomplete Bitmapset structure");
+	if (length != 1 || token[0] != '(')
+		elog(ERROR, "unrecognized token: \"%.*s\"", length, token);
+
+	token = pg_strtok(&length);
+	if (token == NULL)
+		elog(ERROR, "incomplete Bitmapset structure");
+	if (length != 1 || token[0] != 'b')
+		elog(ERROR, "unrecognized token: \"%.*s\"", length, token);
+
+	for (;;)
+	{
+		int		val;
+		char   *endptr;
+
+		token = pg_strtok(&length);
+		if (token == NULL)
+			elog(ERROR, "unterminated Bitmapset structure");
+		if (length == 1 && token[0] == ')')
+			break;
+		val = (int) strtol(token, &endptr, 10);
+		if (endptr != token + length)
+			elog(ERROR, "unrecognized integer: \"%.*s\"", length, token);
+		result = bms_add_member(result, val);
+	}
+
+	return result;
+}
+
 
 /*
  * _readQuery
@@ -1102,6 +1147,8 @@ _readRangeTblEntry(void)
 	READ_BOOL_FIELD(inFromCl);
 	READ_UINT_FIELD(requiredPerms);
 	READ_OID_FIELD(checkAsUser);
+	READ_BITMAPSET_FIELD(selectedCols);
+	READ_BITMAPSET_FIELD(modifiedCols);
 
 	READ_DONE();
 }
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1a64a7742d5..17016d5f3bf 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.148 2009/01/01 17:23:44 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.149 2009/01/22 20:16:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -189,7 +189,8 @@ set_plan_references(PlannerGlobal *glob, Plan *plan, List *rtable)
 	 * In the flat rangetable, we zero out substructure pointers that are not
 	 * needed by the executor; this reduces the storage space and copying cost
 	 * for cached plans.  We keep only the alias and eref Alias fields, which
-	 * are needed by EXPLAIN.
+	 * are needed by EXPLAIN, and the selectedCols and modifiedCols bitmaps,
+	 * which are needed for executor-startup permissions checking.
 	 */
 	foreach(lc, rtable)
 	{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b54e9ba173..397e951c71b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -17,13 +17,14 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.387 2009/01/08 13:42:33 tgl Exp $
+ *	$PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.388 2009/01/22 20:16:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "access/sysattr.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -422,6 +423,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * bugs of just that nature...)
 		 */
 		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_joinexprs = NIL;			/* sub_rtable has no joins */
 		sub_pstate->p_relnamespace = sub_relnamespace;
 		sub_pstate->p_varnamespace = sub_varnamespace;
 
@@ -629,7 +631,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 
 	/*
 	 * Generate query's target list using the computed list of expressions.
+	 * Also, mark all the target columns as needing insert permissions.
 	 */
+	rte = pstate->p_target_rangetblentry;
 	qry->targetList = NIL;
 	icols = list_head(icolumns);
 	attnos = list_head(attrnos);
@@ -637,17 +641,22 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	{
 		Expr	   *expr = (Expr *) lfirst(lc);
 		ResTarget  *col;
+		AttrNumber	attr_num;
 		TargetEntry *tle;
 
 		col = (ResTarget *) lfirst(icols);
 		Assert(IsA(col, ResTarget));
+		attr_num = (AttrNumber) lfirst_int(attnos);
 
 		tle = makeTargetEntry(expr,
-							  (AttrNumber) lfirst_int(attnos),
+							  attr_num,
 							  col->name,
 							  false);
 		qry->targetList = lappend(qry->targetList, tle);
 
+		rte->modifiedCols = bms_add_member(rte->modifiedCols,
+								attr_num - FirstLowInvalidHeapAttributeNumber);
+
 		icols = lnext(icols);
 		attnos = lnext(attnos);
 	}
@@ -1129,8 +1138,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	List	   *targetvars,
 			   *targetnames,
 			   *sv_relnamespace,
-			   *sv_varnamespace,
-			   *sv_rtable;
+			   *sv_varnamespace;
+	int			sv_rtable_length;
 	RangeTblEntry *jrte;
 	int			tllen;
 
@@ -1254,16 +1263,15 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	 * "ORDER BY upper(foo)" will draw the right error message rather than
 	 * "foo not found".
 	 */
-	jrte = addRangeTableEntryForJoin(NULL,
+	sv_rtable_length = list_length(pstate->p_rtable);
+
+	jrte = addRangeTableEntryForJoin(pstate,
 									 targetnames,
 									 JOIN_INNER,
 									 targetvars,
 									 NULL,
 									 false);
 
-	sv_rtable = pstate->p_rtable;
-	pstate->p_rtable = list_make1(jrte);
-
 	sv_relnamespace = pstate->p_relnamespace;
 	pstate->p_relnamespace = NIL;		/* no qualified names allowed */
 
@@ -1283,7 +1291,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 										  &qry->targetList,
 										  false /* no unknowns expected */ );
 
-	pstate->p_rtable = sv_rtable;
+	pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
 	pstate->p_relnamespace = sv_relnamespace;
 	pstate->p_varnamespace = sv_varnamespace;
 
@@ -1618,6 +1626,7 @@ static Query *
 transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
+	RangeTblEntry *target_rte;
 	Node	   *qual;
 	ListCell   *origTargetList;
 	ListCell   *tl;
@@ -1675,6 +1684,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 		pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
+	target_rte = pstate->p_target_rangetblentry;
 	origTargetList = list_head(stmt->targetList);
 
 	foreach(tl, qry->targetList)
@@ -1715,6 +1725,10 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 							  origTarget->indirection,
 							  origTarget->location);
 
+		/* Mark the target column as requiring update permissions */
+		target_rte->modifiedCols = bms_add_member(target_rte->modifiedCols,
+								attrno - FirstLowInvalidHeapAttributeNumber);
+
 		origTargetList = lnext(origTargetList);
 	}
 	if (origTargetList != NULL)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index cc1f812bd98..7f9e5e5b983 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.655 2009/01/16 13:27:23 heikki Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.656 2009/01/22 20:16:05 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -94,6 +94,13 @@ extern List *parsetree;			/* final parse result is delivered here */
 
 static bool QueryIsRule = FALSE;
 
+/* Private struct for the result of privilege_target production */
+typedef struct PrivTarget
+{
+	GrantObjectType objtype;
+	List	   *objs;
+} PrivTarget;
+
 /*
  * If you need access to certain yacc-generated variables and find that
  * they're static by default, uncomment the next line.  (this is not a
@@ -167,7 +174,8 @@ static TypeName *TableFuncTypeName(List *columns);
 	WithClause			*with;
 	A_Indices			*aind;
 	ResTarget			*target;
-	PrivTarget			*privtarget;
+	struct PrivTarget	*privtarget;
+	AccessPriv			*accesspriv;
 
 	InsertStmt			*istmt;
 	VariableSetStmt		*vsetstmt;
@@ -254,7 +262,7 @@ static TypeName *TableFuncTypeName(List *columns);
 %type <str>		iso_level opt_encoding
 %type <node>	grantee
 %type <list>	grantee_list
-%type <str>		privilege
+%type <accesspriv> privilege
 %type <list>	privileges privilege_list
 %type <privtarget> privilege_target
 %type <funwithargs> function_with_argtypes
@@ -4210,12 +4218,11 @@ RevokeStmt:
 
 
 /*
- * A privilege list is represented as a list of strings; the validity of
- * the privilege names gets checked at execution.  This is a bit annoying
- * but we have little choice because of the syntactic conflict with lists
- * of role names in GRANT/REVOKE.  What's more, we have to call out in
- * the "privilege" production any reserved keywords that need to be usable
- * as privilege names.
+ * Privilege names are represented as strings; the validity of the privilege
+ * names gets checked at execution.  This is a bit annoying but we have little
+ * choice because of the syntactic conflict with lists of role names in
+ * GRANT/REVOKE.  What's more, we have to call out in the "privilege"
+ * production any reserved keywords that need to be usable as privilege names.
  */
 
 /* either ALL [PRIVILEGES] or a list of individual privileges */
@@ -4225,18 +4232,54 @@ privileges: privilege_list
 				{ $$ = NIL; }
 			| ALL PRIVILEGES
 				{ $$ = NIL; }
+			| ALL '(' columnList ')'
+				{
+					AccessPriv *n = makeNode(AccessPriv);
+					n->priv_name = NULL;
+					n->cols = $3;
+					$$ = list_make1(n);
+				}
+			| ALL PRIVILEGES '(' columnList ')'
+				{
+					AccessPriv *n = makeNode(AccessPriv);
+					n->priv_name = NULL;
+					n->cols = $4;
+					$$ = list_make1(n);
+				}
 		;
 
-privilege_list:	privilege
-					{ $$ = list_make1(makeString($1)); }
-			| privilege_list ',' privilege
-					{ $$ = lappend($1, makeString($3)); }
+privilege_list:	privilege							{ $$ = list_make1($1); }
+			| privilege_list ',' privilege			{ $$ = lappend($1, $3); }
 		;
 
-privilege:	SELECT									{ $$ = pstrdup($1); }
-			| REFERENCES							{ $$ = pstrdup($1); }
-			| CREATE								{ $$ = pstrdup($1); }
-			| ColId									{ $$ = $1; }
+privilege:	SELECT opt_column_list
+			{
+				AccessPriv *n = makeNode(AccessPriv);
+				n->priv_name = pstrdup($1);
+				n->cols = $2;
+				$$ = n;
+			}
+		| REFERENCES opt_column_list
+			{
+				AccessPriv *n = makeNode(AccessPriv);
+				n->priv_name = pstrdup($1);
+				n->cols = $2;
+				$$ = n;
+			}
+		| CREATE opt_column_list
+			{
+				AccessPriv *n = makeNode(AccessPriv);
+				n->priv_name = pstrdup($1);
+				n->cols = $2;
+				$$ = n;
+			}
+		| ColId opt_column_list
+			{
+				AccessPriv *n = makeNode(AccessPriv);
+				n->priv_name = $1;
+				n->cols = $2;
+				$$ = n;
+			}
 		;
 
 
@@ -4246,70 +4289,70 @@ privilege:	SELECT									{ $$ = pstrdup($1); }
 privilege_target:
 			qualified_name_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_RELATION;
 					n->objs = $1;
 					$$ = n;
 				}
 			| TABLE qualified_name_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_RELATION;
 					n->objs = $2;
 					$$ = n;
 				}
 			| SEQUENCE qualified_name_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_SEQUENCE;
 					n->objs = $2;
 					$$ = n;
 				}
 			| FOREIGN DATA_P WRAPPER name_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_FDW;
 					n->objs = $4;
 					$$ = n;
 				}
 			| FOREIGN SERVER name_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_FOREIGN_SERVER;
 					n->objs = $3;
 					$$ = n;
 				}
 			| FUNCTION function_with_argtypes_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_FUNCTION;
 					n->objs = $2;
 					$$ = n;
 				}
 			| DATABASE name_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_DATABASE;
 					n->objs = $2;
 					$$ = n;
 				}
 			| LANGUAGE name_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_LANGUAGE;
 					n->objs = $2;
 					$$ = n;
 				}
 			| SCHEMA name_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_NAMESPACE;
 					n->objs = $2;
 					$$ = n;
 				}
 			| TABLESPACE name_list
 				{
-					PrivTarget *n = makeNode(PrivTarget);
+					PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
 					n->objtype = ACL_OBJECT_TABLESPACE;
 					n->objs = $2;
 					$$ = n;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 93e393f8379..7e9fb9c071a 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.185 2009/01/01 17:23:45 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.186 2009/01/22 20:16:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,7 @@ static void extractRemainingColumns(List *common_colnames,
 						List *src_colnames, List *src_colvars,
 						List **res_colnames, List **res_colvars);
 static Node *transformJoinUsingClause(ParseState *pstate,
+						 RangeTblEntry *leftRTE, RangeTblEntry *rightRTE,
 						 List *leftVars, List *rightVars);
 static Node *transformJoinOnClause(ParseState *pstate, JoinExpr *j,
 					  RangeTblEntry *l_rte,
@@ -194,8 +195,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 *
 	 * If we find an explicit reference to the rel later during parse
 	 * analysis, we will add the ACL_SELECT bit back again; see
-	 * scanRTEForColumn (for simple field references), ExpandColumnRefStar
-	 * (for foo.*) and ExpandAllTables (for *).
+	 * markVarForSelectPriv and its callers.
 	 */
 	rte->requiredPerms = requiredPerms;
 
@@ -305,7 +305,9 @@ extractRemainingColumns(List *common_colnames,
  *	  Result is a transformed qualification expression.
  */
 static Node *
-transformJoinUsingClause(ParseState *pstate, List *leftVars, List *rightVars)
+transformJoinUsingClause(ParseState *pstate,
+						 RangeTblEntry *leftRTE, RangeTblEntry *rightRTE,
+						 List *leftVars, List *rightVars)
 {
 	Node	   *result = NULL;
 	ListCell   *lvars,
@@ -315,17 +317,25 @@ transformJoinUsingClause(ParseState *pstate, List *leftVars, List *rightVars)
 	 * We cheat a little bit here by building an untransformed operator tree
 	 * whose leaves are the already-transformed Vars.  This is OK because
 	 * transformExpr() won't complain about already-transformed subnodes.
+	 * However, this does mean that we have to mark the columns as requiring
+	 * SELECT privilege for ourselves; transformExpr() won't do it.
 	 */
 	forboth(lvars, leftVars, rvars, rightVars)
 	{
-		Node	   *lvar = (Node *) lfirst(lvars);
-		Node	   *rvar = (Node *) lfirst(rvars);
+		Var		   *lvar = (Var *) lfirst(lvars);
+		Var		   *rvar = (Var *) lfirst(rvars);
 		A_Expr	   *e;
 
+		/* Require read access to the join variables */
+		markVarForSelectPriv(pstate, lvar, leftRTE);
+		markVarForSelectPriv(pstate, rvar, rightRTE);
+
+		/* Now create the lvar = rvar join condition */
 		e = makeSimpleA_Expr(AEXPR_OP, "=",
 							 copyObject(lvar), copyObject(rvar),
 							 -1);
 
+		/* And combine into an AND clause, if multiple join columns */
 		if (result == NULL)
 			result = (Node *) e;
 		else
@@ -728,6 +738,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 				   *r_colvars,
 				   *res_colvars;
 		RangeTblEntry *rte;
+		int			k;
 
 		/*
 		 * Recursively process the left and right subtrees
@@ -912,6 +923,8 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 			}
 
 			j->quals = transformJoinUsingClause(pstate,
+												l_rte,
+												r_rte,
 												l_usingvars,
 												r_usingvars);
 		}
@@ -972,6 +985,12 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		*top_rte = rte;
 		*top_rti = j->rtindex;
 
+		/* make a matching link to the JoinExpr for later use */
+		for (k = list_length(pstate->p_joinexprs) + 1; k < j->rtindex; k++)
+			pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL);
+		pstate->p_joinexprs = lappend(pstate->p_joinexprs, j);
+		Assert(list_length(pstate->p_joinexprs) == j->rtindex);
+
 		/*
 		 * Prepare returned namespace list.  If the JOIN has an alias then it
 		 * hides the contained RTEs as far as the relnamespace goes;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 1d0f77a5b35..2bf6174866c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.239 2009/01/01 17:23:45 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.240 2009/01/22 20:16:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1977,6 +1977,9 @@ transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname,
 	/* location is not filled in by makeVar */
 	result->location = location;
 
+	/* mark relation as requiring whole-row SELECT access */
+	markVarForSelectPriv(pstate, result, rte);
+
 	return (Node *) result;
 }
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 49ad0240397..eb98f470ee3 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.140 2009/01/01 17:23:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.141 2009/01/22 20:16:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,6 +39,8 @@ static RangeTblEntry *scanNameSpaceForRefname(ParseState *pstate,
 						const char *refname, int location);
 static RangeTblEntry *scanNameSpaceForRelid(ParseState *pstate, Oid relid,
 											int location);
+static void markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
+								 int rtindex, AttrNumber col);
 static bool isLockedRel(ParseState *pstate, char *refname);
 static void expandRelation(Oid relid, Alias *eref,
 			   int rtindex, int sublevels_up,
@@ -435,14 +437,8 @@ GetCTEForRTE(ParseState *pstate, RangeTblEntry *rte, int rtelevelsup)
  *	  If found, return an appropriate Var node, else return NULL.
  *	  If the name proves ambiguous within this RTE, raise error.
  *
- * Side effect: if we find a match, mark the RTE as requiring read access.
- * See comments in setTargetTable().
- *
- * NOTE: if the RTE is for a join, marking it as requiring read access does
- * nothing.  It might seem that we need to propagate the mark to all the
- * contained RTEs, but that is not necessary.  This is so because a join
- * expression can only appear in a FROM clause, and any table named in
- * FROM will be marked as requiring read access from the beginning.
+ * Side effect: if we find a match, mark the RTE as requiring read access
+ * for the column.
  */
 Node *
 scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
@@ -450,6 +446,7 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
 {
 	Node	   *result = NULL;
 	int			attnum = 0;
+	Var		   *var;
 	ListCell   *c;
 
 	/*
@@ -476,9 +473,10 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
 						 errmsg("column reference \"%s\" is ambiguous",
 								colname),
 						 parser_errposition(pstate, location)));
-			result = (Node *) make_var(pstate, rte, attnum, location);
-			/* Require read access */
-			rte->requiredPerms |= ACL_SELECT;
+			var = make_var(pstate, rte, attnum, location);
+			/* Require read access to the column */
+			markVarForSelectPriv(pstate, var, rte);
+			result = (Node *) var;
 		}
 	}
 
@@ -504,9 +502,10 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
 									 Int16GetDatum(attnum),
 									 0, 0))
 			{
-				result = (Node *) make_var(pstate, rte, attnum, location);
-				/* Require read access */
-				rte->requiredPerms |= ACL_SELECT;
+				var = make_var(pstate, rte, attnum, location);
+				/* Require read access to the column */
+				markVarForSelectPriv(pstate, var, rte);
+				result = (Node *) var;
 			}
 		}
 	}
@@ -594,6 +593,122 @@ qualifiedNameToVar(ParseState *pstate,
 	return scanRTEForColumn(pstate, rte, colname, location);
 }
 
+/*
+ * markRTEForSelectPriv
+ *	   Mark the specified column of an RTE as requiring SELECT privilege
+ *
+ * col == InvalidAttrNumber means a "whole row" reference
+ *
+ * The caller should pass the actual RTE if it has it handy; otherwise pass
+ * NULL, and we'll look it up here.  (This uglification of the API is
+ * worthwhile because nearly all external callers have the RTE at hand.)
+ */
+static void
+markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
+					 int rtindex, AttrNumber col)
+{
+	if (rte == NULL)
+		rte = rt_fetch(rtindex, pstate->p_rtable);
+
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/* Make sure the rel as a whole is marked for SELECT access */
+		rte->requiredPerms |= ACL_SELECT;
+		/* Must offset the attnum to fit in a bitmapset */
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+									col - FirstLowInvalidHeapAttributeNumber);
+	}
+	else if (rte->rtekind == RTE_JOIN)
+	{
+		if (col == InvalidAttrNumber)
+		{
+			/*
+			 * A whole-row reference to a join has to be treated as
+			 * whole-row references to the two inputs.
+			 */
+			JoinExpr   *j;
+
+			if (rtindex > 0 && rtindex <= list_length(pstate->p_joinexprs))
+				j = (JoinExpr *) list_nth(pstate->p_joinexprs, rtindex - 1);
+			else
+				j = NULL;
+			if (j == NULL)
+				elog(ERROR, "could not find JoinExpr for whole-row reference");
+			Assert(IsA(j, JoinExpr));
+
+			/* Note: we can't see FromExpr here */
+			if (IsA(j->larg, RangeTblRef))
+			{
+				int		varno = ((RangeTblRef *) j->larg)->rtindex;
+
+				markRTEForSelectPriv(pstate, NULL, varno, InvalidAttrNumber);
+			}
+			else if (IsA(j->larg, JoinExpr))
+			{
+				int		varno = ((JoinExpr *) j->larg)->rtindex;
+
+				markRTEForSelectPriv(pstate, NULL, varno, InvalidAttrNumber);
+			}
+			else
+				elog(ERROR, "unrecognized node type: %d",
+					 (int) nodeTag(j->larg));
+			if (IsA(j->rarg, RangeTblRef))
+			{
+				int		varno = ((RangeTblRef *) j->rarg)->rtindex;
+
+				markRTEForSelectPriv(pstate, NULL, varno, InvalidAttrNumber);
+			}
+			else if (IsA(j->rarg, JoinExpr))
+			{
+				int		varno = ((JoinExpr *) j->rarg)->rtindex;
+
+				markRTEForSelectPriv(pstate, NULL, varno, InvalidAttrNumber);
+			}
+			else
+				elog(ERROR, "unrecognized node type: %d",
+					 (int) nodeTag(j->rarg));
+		}
+		else
+		{
+			/*
+			 * Regular join attribute, look at the alias-variable list.
+			 *
+			 * The aliasvar could be either a Var or a COALESCE expression,
+			 * but in the latter case we should already have marked the two
+			 * referent variables as being selected, due to their use in the
+			 * JOIN clause.  So we need only be concerned with the simple
+			 * Var case.
+			 */
+			Var	   *aliasvar;
+
+			Assert(col > 0 && col <= list_length(rte->joinaliasvars));
+			aliasvar = (Var *) list_nth(rte->joinaliasvars, col - 1);
+			if (IsA(aliasvar, Var))
+				markVarForSelectPriv(pstate, aliasvar, NULL);
+		}
+	}
+	/* other RTE types don't require privilege marking */
+}
+
+/*
+ * markVarForSelectPriv
+ *	   Mark the RTE referenced by a Var as requiring SELECT privilege
+ *
+ * The caller should pass the Var's referenced RTE if it has it handy
+ * (nearly all do); otherwise pass NULL.
+ */
+void
+markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
+{
+	Index	lv;
+
+	Assert(IsA(var, Var));
+	/* Find the appropriate pstate if it's an uplevel Var */
+	for (lv = 0; lv < var->varlevelsup; lv++)
+		pstate = pstate->parentParseState;
+	markRTEForSelectPriv(pstate, rte, var->varno, var->varattno);
+}
+
 /*
  * buildRelationAliases
  *		Construct the eref column name list for a relation RTE.
@@ -838,6 +953,8 @@ addRangeTableEntry(ParseState *pstate,
 
 	rte->requiredPerms = ACL_SELECT;
 	rte->checkAsUser = InvalidOid;		/* not set-uid by default, either */
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join list
@@ -891,6 +1008,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
 
 	rte->requiredPerms = ACL_SELECT;
 	rte->checkAsUser = InvalidOid;		/* not set-uid by default, either */
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join list
@@ -969,6 +1088,8 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 
 	rte->requiredPerms = 0;
 	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join list
@@ -1101,6 +1222,8 @@ addRangeTableEntryForFunction(ParseState *pstate,
 
 	rte->requiredPerms = 0;
 	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join list
@@ -1168,8 +1291,11 @@ addRangeTableEntryForValues(ParseState *pstate,
 	 */
 	rte->inh = false;			/* never true for values RTEs */
 	rte->inFromCl = inFromCl;
+
 	rte->requiredPerms = 0;
 	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join list
@@ -1239,6 +1365,8 @@ addRangeTableEntryForJoin(ParseState *pstate,
 
 	rte->requiredPerms = 0;
 	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join list
@@ -1320,6 +1448,8 @@ addRangeTableEntryForCTE(ParseState *pstate,
 
 	rte->requiredPerms = 0;
 	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
 
 	/*
 	 * Add completed RTE to pstate's range table list, but not to join list
@@ -1803,6 +1933,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
  * As with expandRTE, rtindex/sublevels_up determine the varno/varlevelsup
  * fields of the Vars produced, and location sets their location.
  * pstate->p_next_resno determines the resnos assigned to the TLEs.
+ * The referenced columns are marked as requiring SELECT access.
  */
 List *
 expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
@@ -1817,10 +1948,17 @@ expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
 	expandRTE(rte, rtindex, sublevels_up, location, false,
 			  &names, &vars);
 
+	/*
+	 * Require read access to the table.  This is normally redundant with the
+	 * markVarForSelectPriv calls below, but not if the table has zero
+	 * columns.
+	 */
+	rte->requiredPerms |= ACL_SELECT;
+
 	forboth(name, names, var, vars)
 	{
 		char	   *label = strVal(lfirst(name));
-		Node	   *varnode = (Node *) lfirst(var);
+		Var		   *varnode = (Var *) lfirst(var);
 		TargetEntry *te;
 
 		te = makeTargetEntry((Expr *) varnode,
@@ -1828,6 +1966,9 @@ expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
 							 label,
 							 false);
 		te_list = lappend(te_list, te);
+
+		/* Require read access to each column */
+		markVarForSelectPriv(pstate, varnode, rte);
 	}
 
 	Assert(name == NULL && var == NULL);	/* lists not the same length? */
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2765751edb9..3f804472c77 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.169 2009/01/01 17:23:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.170 2009/01/22 20:16:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -850,6 +850,8 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
  * in a SELECT target list (where we want TargetEntry nodes in the result)
  * and foo.* in a ROW() or VALUES() construct (where we want just bare
  * expressions).
+ *
+ * The referenced columns are marked as requiring SELECT access.
  */
 static List *
 ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
@@ -929,20 +931,37 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
 								 makeRangeVar(schemaname, relname,
 											  cref->location));
 
-		/* Require read access --- see comments in setTargetTable() */
-		rte->requiredPerms |= ACL_SELECT;
-
 		rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up);
 
 		if (targetlist)
+		{
+			/* expandRelAttrs handles permissions marking */
 			return expandRelAttrs(pstate, rte, rtindex, sublevels_up,
 								  cref->location);
+		}
 		else
 		{
 			List	   *vars;
+			ListCell   *l;
 
 			expandRTE(rte, rtindex, sublevels_up, cref->location, false,
 					  NULL, &vars);
+
+			/*
+			 * Require read access to the table.  This is normally redundant
+			 * with the markVarForSelectPriv calls below, but not if the table
+			 * has zero columns.
+			 */
+			rte->requiredPerms |= ACL_SELECT;
+
+			/* Require read access to each column */
+			foreach(l, vars)
+			{
+				Var	   *var = (Var *) lfirst(l);
+
+				markVarForSelectPriv(pstate, var, rte);
+			}
+
 			return vars;
 		}
 	}
@@ -956,6 +975,8 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
  * varnamespace.  We do not consider relnamespace because that would include
  * input tables of aliasless JOINs, NEW/OLD pseudo-entries, implicit RTEs,
  * etc.
+ *
+ * The referenced relations/columns are marked as requiring SELECT access.
  */
 static List *
 ExpandAllTables(ParseState *pstate, int location)
@@ -975,9 +996,6 @@ ExpandAllTables(ParseState *pstate, int location)
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
 		int			rtindex = RTERangeTablePosn(pstate, rte, NULL);
 
-		/* Require read access --- see comments in setTargetTable() */
-		rte->requiredPerms |= ACL_SELECT;
-
 		target = list_concat(target,
 							 expandRelAttrs(pstate, rte, rtindex, 0,
 											location));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 56549a55095..7cfeacb7305 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.183 2009/01/22 17:27:54 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.184 2009/01/22 20:16:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1178,9 +1178,13 @@ ApplyRetrieveRule(Query *parsetree,
 	Assert(subrte->relid == relation->rd_id);
 	subrte->requiredPerms = rte->requiredPerms;
 	subrte->checkAsUser = rte->checkAsUser;
+	subrte->selectedCols = rte->selectedCols;
+	subrte->modifiedCols = rte->modifiedCols;
 
 	rte->requiredPerms = 0;		/* no permission check on subquery itself */
 	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
 
 	/*
 	 * FOR UPDATE/SHARE of view?
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 61f23213c44..7e7d07e4f03 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.304 2009/01/01 17:23:48 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.305 2009/01/22 20:16:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -910,7 +910,7 @@ ProcessUtility(Node *parsetree,
 			break;
 
 		case T_CreateTrigStmt:
-			CreateTrigger((CreateTrigStmt *) parsetree, InvalidOid);
+			CreateTrigger((CreateTrigStmt *) parsetree, InvalidOid, true);
 			break;
 
 		case T_DropPropertyStmt:
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index b270077e8d8..cb0ebf46941 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.145 2009/01/01 17:23:48 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.146 2009/01/22 20:16:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -366,6 +366,47 @@ allocacl(int n)
 	return new_acl;
 }
 
+/*
+ * Copy an ACL
+ */
+Acl *
+aclcopy(const Acl *orig_acl)
+{
+	Acl *result_acl;
+
+	result_acl = allocacl(ACL_NUM(orig_acl));
+
+	memcpy(ACL_DAT(result_acl),
+		   ACL_DAT(orig_acl),
+		   ACL_NUM(orig_acl) * sizeof(AclItem));
+
+	return result_acl;
+}
+
+/*
+ * Concatenate two ACLs
+ *
+ * This is a bit cheesy, since we may produce an ACL with redundant entries.
+ * Be careful what the result is used for!
+ */
+Acl *
+aclconcat(const Acl *left_acl, const Acl *right_acl)
+{
+	Acl *result_acl;
+
+	result_acl = allocacl(ACL_NUM(left_acl) + ACL_NUM(right_acl));
+
+	memcpy(ACL_DAT(result_acl),
+		   ACL_DAT(left_acl),
+		   ACL_NUM(left_acl) * sizeof(AclItem));
+
+	memcpy(ACL_DAT(result_acl) + ACL_NUM(left_acl),
+		   ACL_DAT(right_acl),
+		   ACL_NUM(right_acl) * sizeof(AclItem));
+
+	return result_acl;
+}
+
 /*
  * Verify that an ACL array is acceptable (one-dimensional and has no nulls)
  */
@@ -542,11 +583,17 @@ acldefault(GrantObjectType objtype, Oid ownerId)
 {
 	AclMode		world_default;
 	AclMode		owner_default;
+	int			nacl;
 	Acl		   *acl;
 	AclItem    *aip;
 
 	switch (objtype)
 	{
+		case ACL_OBJECT_COLUMN:
+			/* by default, columns have no extra privileges */
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_NO_RIGHTS;
+			break;
 		case ACL_OBJECT_RELATION:
 			world_default = ACL_NO_RIGHTS;
 			owner_default = ACL_ALL_RIGHTS_RELATION;
@@ -593,7 +640,13 @@ acldefault(GrantObjectType objtype, Oid ownerId)
 			break;
 	}
 
-	acl = allocacl((world_default != ACL_NO_RIGHTS) ? 2 : 1);
+	nacl = 0;
+	if (world_default != ACL_NO_RIGHTS)
+		nacl++;
+	if (owner_default != ACL_NO_RIGHTS)
+		nacl++;
+
+	acl = allocacl(nacl);
 	aip = ACL_DAT(acl);
 
 	if (world_default != ACL_NO_RIGHTS)
@@ -614,9 +667,12 @@ acldefault(GrantObjectType objtype, Oid ownerId)
 	 * "_SYSTEM"-like ACL entry, by internally special-casing the owner
 	 * whereever we are testing grant options.
 	 */
-	aip->ai_grantee = ownerId;
-	aip->ai_grantor = ownerId;
-	ACLITEM_SET_PRIVS_GOPTIONS(*aip, owner_default, ACL_NO_RIGHTS);
+	if (owner_default != ACL_NO_RIGHTS)
+	{
+		aip->ai_grantee = ownerId;
+		aip->ai_grantor = ownerId;
+		ACLITEM_SET_PRIVS_GOPTIONS(*aip, owner_default, ACL_NO_RIGHTS);
+	}
 
 	return acl;
 }
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 4be6e08606b..ecfe59e3a8c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.281 2009/01/22 17:27:54 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.282 2009/01/22 20:16:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -480,7 +480,7 @@ RelationBuildTupleDesc(Relation relation)
 
 		memcpy(relation->rd_att->attrs[attp->attnum - 1],
 			   attp,
-			   ATTRIBUTE_TUPLE_SIZE);
+			   ATTRIBUTE_FIXED_PART_SIZE);
 
 		/* Update constraint/default info */
 		if (attp->attnotnull)
@@ -1449,7 +1449,7 @@ formrdesc(const char *relationName, Oid relationReltype,
 	{
 		memcpy(relation->rd_att->attrs[i],
 			   &att[i],
-			   ATTRIBUTE_TUPLE_SIZE);
+			   ATTRIBUTE_FIXED_PART_SIZE);
 		has_not_null |= att[i].attnotnull;
 		/* make sure attcacheoff is valid */
 		relation->rd_att->attrs[i]->attcacheoff = -1;
@@ -2714,7 +2714,7 @@ BuildHardcodedDescriptor(int natts, Form_pg_attribute attrs, bool hasoids)
 
 	for (i = 0; i < natts; i++)
 	{
-		memcpy(result->attrs[i], &attrs[i], ATTRIBUTE_TUPLE_SIZE);
+		memcpy(result->attrs[i], &attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
 		/* make sure attcacheoff is valid */
 		result->attrs[i]->attcacheoff = -1;
 	}
@@ -3441,7 +3441,7 @@ load_relcache_init_file(void)
 		{
 			if ((nread = fread(&len, 1, sizeof(len), fp)) != sizeof(len))
 				goto read_failed;
-			if (len != ATTRIBUTE_TUPLE_SIZE)
+			if (len != ATTRIBUTE_FIXED_PART_SIZE)
 				goto read_failed;
 			if ((nread = fread(rel->rd_att->attrs[i], 1, len, fp)) != len)
 				goto read_failed;
@@ -3751,7 +3751,7 @@ write_relcache_init_file(void)
 		/* next, do all the attribute tuple form data entries */
 		for (i = 0; i < relform->relnatts; i++)
 		{
-			write_item(rel->rd_att->attrs[i], ATTRIBUTE_TUPLE_SIZE, fp);
+			write_item(rel->rd_att->attrs[i], ATTRIBUTE_FIXED_PART_SIZE, fp);
 		}
 
 		/* next, do the access method specific field */
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index dbd9be4657f..15f66cd4938 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -42,7 +42,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  * Portions taken from FreeBSD.
  *
- * $PostgreSQL: pgsql/src/bin/initdb/initdb.c,v 1.166 2009/01/01 17:23:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/initdb/initdb.c,v 1.167 2009/01/22 20:16:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1601,7 +1601,7 @@ setup_depend(void)
 		" FROM pg_ts_template;\n",
 		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
 		" FROM pg_ts_config;\n",
-		"INSERT INTO pg_shdepend SELECT 0, 0, 0, tableoid, oid, 'p' "
+		"INSERT INTO pg_shdepend SELECT 0,0,0,0, tableoid,oid, 'p' "
 		" FROM pg_authid;\n",
 		NULL
 	};
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 819bb4d2e61..85b3373c535 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.c,v 1.43 2009/01/01 17:23:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.c,v 1.44 2009/01/22 20:16:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,11 +23,13 @@
 
 #define supports_grant_options(version) ((version) >= 70400)
 
-static bool parseAclItem(const char *item, const char *type, const char *name,
-			 int remoteVersion, PQExpBuffer grantee, PQExpBuffer grantor,
+static bool parseAclItem(const char *item, const char *type,
+			 const char *name, const char *subname, int remoteVersion,
+			 PQExpBuffer grantee, PQExpBuffer grantor,
 			 PQExpBuffer privs, PQExpBuffer privswgo);
 static char *copyAclUserName(PQExpBuffer output, char *input);
-static void AddAcl(PQExpBuffer aclbuf, const char *keyword);
+static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
+				   const char *subname);
 
 
 /*
@@ -384,6 +386,7 @@ parsePGArray(const char *atext, char ***itemarray, int *nitems)
  * Build GRANT/REVOKE command(s) for an object.
  *
  *	name: the object name, in the form to use in the commands (already quoted)
+ *	subname: the sub-object name, if any (already quoted); NULL if none
  *	type: the object type (as seen in GRANT command: must be one of
  *		TABLE, SEQUENCE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, or TABLESPACE)
  *	acls: the ACL string fetched from the database
@@ -394,12 +397,12 @@ parsePGArray(const char *atext, char ***itemarray, int *nitems)
  * Returns TRUE if okay, FALSE if could not parse the acl string.
  * The resulting commands (if any) are appended to the contents of 'sql'.
  *
- * Note: beware of passing fmtId() result as 'name', since this routine
- * uses fmtId() internally.
+ * Note: beware of passing a fmtId() result directly as 'name' or 'subname',
+ * since this routine uses fmtId() internally.
  */
 bool
-buildACLCommands(const char *name, const char *type,
-				 const char *acls, const char *owner,
+buildACLCommands(const char *name, const char *subname,
+				 const char *type, const char *acls, const char *owner,
 				 int remoteVersion,
 				 PQExpBuffer sql)
 {
@@ -448,8 +451,10 @@ buildACLCommands(const char *name, const char *type,
 	 * wire-in knowledge about the default public privileges for different
 	 * kinds of objects.
 	 */
-	appendPQExpBuffer(firstsql, "REVOKE ALL ON %s %s FROM PUBLIC;\n",
-					  type, name);
+	appendPQExpBuffer(firstsql, "REVOKE ALL");
+	if (subname)
+		appendPQExpBuffer(firstsql, "(%s)", subname);
+	appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name);
 
 	/*
 	 * We still need some hacking though to cover the case where new default
@@ -468,7 +473,7 @@ buildACLCommands(const char *name, const char *type,
 	/* Scan individual ACL items */
 	for (i = 0; i < naclitems; i++)
 	{
-		if (!parseAclItem(aclitems[i], type, name, remoteVersion,
+		if (!parseAclItem(aclitems[i], type, name, subname, remoteVersion,
 						  grantee, grantor, privs, privswgo))
 			return false;
 
@@ -491,15 +496,19 @@ buildACLCommands(const char *name, const char *type,
 					? strcmp(privswgo->data, "ALL") != 0
 					: strcmp(privs->data, "ALL") != 0)
 				{
-					appendPQExpBuffer(firstsql, "REVOKE ALL ON %s %s FROM %s;\n",
-									  type, name,
-									  fmtId(grantee->data));
+					appendPQExpBuffer(firstsql, "REVOKE ALL");
+					if (subname)
+						appendPQExpBuffer(firstsql, "(%s)", subname);
+					appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
+									  type, name, fmtId(grantee->data));
 					if (privs->len > 0)
-						appendPQExpBuffer(firstsql, "GRANT %s ON %s %s TO %s;\n",
+						appendPQExpBuffer(firstsql,
+										  "GRANT %s ON %s %s TO %s;\n",
 										  privs->data, type, name,
 										  fmtId(grantee->data));
 					if (privswgo->len > 0)
-						appendPQExpBuffer(firstsql, "GRANT %s ON %s %s TO %s WITH GRANT OPTION;\n",
+						appendPQExpBuffer(firstsql,
+							"GRANT %s ON %s %s TO %s WITH GRANT OPTION;\n",
 										  privswgo->data, type, name,
 										  fmtId(grantee->data));
 				}
@@ -553,8 +562,13 @@ buildACLCommands(const char *name, const char *type,
 	 * If we didn't find any owner privs, the owner must have revoked 'em all
 	 */
 	if (!found_owner_privs && owner)
-		appendPQExpBuffer(firstsql, "REVOKE ALL ON %s %s FROM %s;\n",
+	{
+		appendPQExpBuffer(firstsql, "REVOKE ALL");
+		if (subname)
+			appendPQExpBuffer(firstsql, "(%s)", subname);
+		appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n",
 						  type, name, fmtId(owner));
+	}
 
 	destroyPQExpBuffer(grantee);
 	destroyPQExpBuffer(grantor);
@@ -587,8 +601,9 @@ buildACLCommands(const char *name, const char *type,
  * appropriate.
  */
 static bool
-parseAclItem(const char *item, const char *type, const char *name,
-			 int remoteVersion, PQExpBuffer grantee, PQExpBuffer grantor,
+parseAclItem(const char *item, const char *type,
+			 const char *name, const char *subname, int remoteVersion,
+			 PQExpBuffer grantee, PQExpBuffer grantor,
 			 PQExpBuffer privs, PQExpBuffer privswgo)
 {
 	char	   *buf;
@@ -626,12 +641,12 @@ do { \
 	{ \
 		if (*(pos + 1) == '*') \
 		{ \
-			AddAcl(privswgo, keywd); \
+			AddAcl(privswgo, keywd, subname); \
 			all_without_go = false; \
 		} \
 		else \
 		{ \
-			AddAcl(privs, keywd); \
+			AddAcl(privs, keywd, subname); \
 			all_with_go = false; \
 		} \
 	} \
@@ -654,13 +669,18 @@ do { \
 			/* table only */
 			CONVERT_PRIV('a', "INSERT");
 			if (remoteVersion >= 70200)
-			{
-				CONVERT_PRIV('d', "DELETE");
 				CONVERT_PRIV('x', "REFERENCES");
-				CONVERT_PRIV('t', "TRIGGER");
+			/* rest are not applicable to columns */
+			if (subname == NULL)
+			{
+				if (remoteVersion >= 70200)
+				{
+					CONVERT_PRIV('d', "DELETE");
+					CONVERT_PRIV('t', "TRIGGER");
+				}
+				if (remoteVersion >= 80400)
+					CONVERT_PRIV('D', "TRUNCATE");
 			}
-			if (remoteVersion >= 80400)
-				CONVERT_PRIV('D', "TRUNCATE");
 		}
 
 		/* UPDATE */
@@ -700,11 +720,15 @@ do { \
 	{
 		resetPQExpBuffer(privs);
 		printfPQExpBuffer(privswgo, "ALL");
+		if (subname)
+			appendPQExpBuffer(privswgo, "(%s)", subname);
 	}
 	else if (all_without_go)
 	{
 		resetPQExpBuffer(privswgo);
 		printfPQExpBuffer(privs, "ALL");
+		if (subname)
+			appendPQExpBuffer(privs, "(%s)", subname);
 	}
 
 	free(buf);
@@ -757,11 +781,13 @@ copyAclUserName(PQExpBuffer output, char *input)
  * Append a privilege keyword to a keyword list, inserting comma if needed.
  */
 static void
-AddAcl(PQExpBuffer aclbuf, const char *keyword)
+AddAcl(PQExpBuffer aclbuf, const char *keyword, const char *subname)
 {
 	if (aclbuf->len > 0)
 		appendPQExpBufferChar(aclbuf, ',');
 	appendPQExpBuffer(aclbuf, "%s", keyword);
+	if (subname)
+		appendPQExpBuffer(aclbuf, "(%s)", subname);
 }
 
 
diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h
index 13341d77a8a..b8a3532fa88 100644
--- a/src/bin/pg_dump/dumputils.h
+++ b/src/bin/pg_dump/dumputils.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.h,v 1.22 2009/01/01 17:23:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/dumputils.h,v 1.23 2009/01/22 20:16:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,8 +28,8 @@ extern void appendStringLiteralDQ(PQExpBuffer buf, const char *str,
 					  const char *dqprefix);
 extern int	parse_version(const char *versionString);
 extern bool parsePGArray(const char *atext, char ***itemarray, int *nitems);
-extern bool buildACLCommands(const char *name, const char *type,
-				 const char *acls, const char *owner,
+extern bool buildACLCommands(const char *name, const char *subname,
+				 const char *type, const char *acls, const char *owner,
 				 int remoteVersion,
 				 PQExpBuffer sql);
 extern void processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 76558856e21..f47333c6051 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12,7 +12,7 @@
  *	by PostgreSQL
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.515 2009/01/22 17:27:54 petere Exp $
+ *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.516 2009/01/22 20:16:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -165,7 +165,7 @@ static void dumpUserMappings(Archive *fout, const char *target,
 			const char *owner, CatalogId catalogId, DumpId dumpId);
 
 static void dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
-		const char *type, const char *name,
+		const char *type, const char *name, const char *subname,
 		const char *tag, const char *nspname, const char *owner,
 		const char *acls);
 
@@ -5929,7 +5929,7 @@ dumpNamespace(Archive *fout, NamespaceInfo *nspinfo)
 				nspinfo->dobj.catId, 0, nspinfo->dobj.dumpId);
 
 	dumpACL(fout, nspinfo->dobj.catId, nspinfo->dobj.dumpId, "SCHEMA",
-			qnspname, nspinfo->dobj.name, NULL,
+			qnspname, NULL, nspinfo->dobj.name, NULL,
 			nspinfo->rolname, nspinfo->nspacl);
 
 	free(qnspname);
@@ -6786,7 +6786,7 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
 
 	if (plang->lanpltrusted)
 		dumpACL(fout, plang->dobj.catId, plang->dobj.dumpId, "LANGUAGE",
-				qlanname, plang->dobj.name,
+				qlanname, NULL, plang->dobj.name,
 				lanschema,
 				plang->lanowner, plang->lanacl);
 
@@ -7342,7 +7342,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
 				finfo->dobj.catId, 0, finfo->dobj.dumpId);
 
 	dumpACL(fout, finfo->dobj.catId, finfo->dobj.dumpId, "FUNCTION",
-			funcsig, funcsig_tag,
+			funcsig, NULL, funcsig_tag,
 			finfo->dobj.namespace->dobj.name,
 			finfo->rolname, finfo->proacl);
 
@@ -8828,7 +8828,7 @@ dumpAgg(Archive *fout, AggInfo *agginfo)
 
 	dumpACL(fout, agginfo->aggfn.dobj.catId, agginfo->aggfn.dobj.dumpId,
 			"FUNCTION",
-			aggsig, aggsig_tag,
+			aggsig, NULL, aggsig_tag,
 			agginfo->aggfn.dobj.namespace->dobj.name,
 			agginfo->aggfn.rolname, agginfo->aggfn.proacl);
 
@@ -9227,7 +9227,7 @@ dumpForeignDataWrapper(Archive *fout, FdwInfo *fdwinfo)
 	namecopy = strdup(fmtId(fdwinfo->dobj.name));
 	dumpACL(fout, fdwinfo->dobj.catId, fdwinfo->dobj.dumpId,
 			"FOREIGN DATA WRAPPER",
-			namecopy, fdwinfo->dobj.name,
+			namecopy, NULL, fdwinfo->dobj.name,
 			NULL, fdwinfo->rolname,
 			fdwinfo->fdwacl);
 	free(namecopy);
@@ -9305,7 +9305,7 @@ dumpForeignServer(Archive *fout, ForeignServerInfo *srvinfo)
 	namecopy = strdup(fmtId(srvinfo->dobj.name));
 	dumpACL(fout, srvinfo->dobj.catId, srvinfo->dobj.dumpId,
 			"SERVER",
-			namecopy, srvinfo->dobj.name,
+			namecopy, NULL, srvinfo->dobj.name,
 			NULL, srvinfo->rolname,
 			srvinfo->srvacl);
 	free(namecopy);
@@ -9412,6 +9412,7 @@ dumpUserMappings(Archive *fout, const char *target,
  * 'objDumpId' is the dump ID of the underlying object.
  * 'type' must be TABLE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, or TABLESPACE.
  * 'name' is the formatted name of the object.	Must be quoted etc. already.
+ * 'subname' is the formatted name of the sub-object, if any.  Must be quoted.
  * 'tag' is the tag for the archive entry (typ. unquoted name of object).
  * 'nspname' is the namespace the object is in (NULL if none).
  * 'owner' is the owner, NULL if there is no owner (for languages).
@@ -9421,7 +9422,7 @@ dumpUserMappings(Archive *fout, const char *target,
  */
 static void
 dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
-		const char *type, const char *name,
+		const char *type, const char *name, const char *subname,
 		const char *tag, const char *nspname, const char *owner,
 		const char *acls)
 {
@@ -9433,7 +9434,7 @@ dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
 
 	sql = createPQExpBuffer();
 
-	if (!buildACLCommands(name, type, acls, owner, fout->remoteVersion, sql))
+	if (!buildACLCommands(name, subname, type, acls, owner, fout->remoteVersion, sql))
 	{
 		write_msg(NULL, "could not parse ACL list (%s) for object \"%s\" (%s)\n",
 				  acls, name, type);
@@ -9459,10 +9460,10 @@ dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
 static void
 dumpTable(Archive *fout, TableInfo *tbinfo)
 {
-	char	   *namecopy;
-
 	if (tbinfo->dobj.dump)
 	{
+		char   *namecopy;
+
 		if (tbinfo->relkind == RELKIND_SEQUENCE)
 			dumpSequence(fout, tbinfo);
 		else if (!dataOnly)
@@ -9472,9 +9473,51 @@ dumpTable(Archive *fout, TableInfo *tbinfo)
 		namecopy = strdup(fmtId(tbinfo->dobj.name));
 		dumpACL(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId,
 				(tbinfo->relkind == RELKIND_SEQUENCE) ? "SEQUENCE" : "TABLE",
-				namecopy, tbinfo->dobj.name,
+				namecopy, NULL, tbinfo->dobj.name,
 				tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
 				tbinfo->relacl);
+
+		/*
+		 * Handle column ACLs, if any.  Note: we pull these with a separate
+		 * query rather than trying to fetch them during getTableAttrs, so
+		 * that we won't miss ACLs on system columns.
+		 */
+		if (g_fout->remoteVersion >= 80400)
+		{
+			PQExpBuffer query = createPQExpBuffer();
+			PGresult *res;
+			int		i;
+
+			appendPQExpBuffer(query,
+							  "SELECT attname, attacl FROM pg_catalog.pg_attribute "
+							  "WHERE attrelid = '%u' AND NOT attisdropped AND attacl IS NOT NULL "
+							  "ORDER BY attnum",
+							  tbinfo->dobj.catId.oid);
+			res = PQexec(g_conn, query->data);
+			check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
+
+			for (i = 0; i < PQntuples(res); i++)
+			{
+				char   *attname = PQgetvalue(res, i, 0);
+				char   *attacl = PQgetvalue(res, i, 1);
+				char   *attnamecopy;
+				char   *acltag;
+
+				attnamecopy = strdup(fmtId(attname));
+				acltag = malloc(strlen(tbinfo->dobj.name) + strlen(attname) + 2);
+				sprintf(acltag, "%s.%s", tbinfo->dobj.name, attname);
+				/* Column's GRANT type is always TABLE */
+				dumpACL(fout, tbinfo->dobj.catId, tbinfo->dobj.dumpId, "TABLE",
+						namecopy, attnamecopy, acltag,
+						tbinfo->dobj.namespace->dobj.name, tbinfo->rolname,
+						attacl);
+				free(attnamecopy);
+				free(acltag);
+			}
+			PQclear(res);
+			destroyPQExpBuffer(query);
+		}
+
 		free(namecopy);
 	}
 }
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 1be955b3e72..f5a08eb834f 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dumpall.c,v 1.112 2009/01/06 18:01:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dumpall.c,v 1.113 2009/01/22 20:16:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -882,7 +882,7 @@ dumpTablespaces(PGconn *conn)
 		appendPQExpBuffer(buf, ";\n");
 
 		if (!skip_acls &&
-			!buildACLCommands(fspcname, "TABLESPACE", spcacl, spcowner,
+			!buildACLCommands(fspcname, NULL, "TABLESPACE", spcacl, spcowner,
 							  server_version, buf))
 		{
 			fprintf(stderr, _("%s: could not parse ACL list (%s) for tablespace \"%s\"\n"),
@@ -1075,7 +1075,7 @@ dumpCreateDB(PGconn *conn)
 		}
 
 		if (!skip_acls &&
-			!buildACLCommands(fdbname, "DATABASE", dbacl, dbowner,
+			!buildACLCommands(fdbname, NULL, "DATABASE", dbacl, dbowner,
 							  server_version, buf))
 		{
 			fprintf(stderr, _("%s: could not parse ACL list (%s) for database \"%s\"\n"),
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 01cb9bee4be..8414d1bff4d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -8,7 +8,7 @@
  *
  * Copyright (c) 2000-2009, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.197 2009/01/20 02:13:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.198 2009/01/22 20:16:08 tgl Exp $
  */
 #include "postgres_fe.h"
 
@@ -519,7 +519,7 @@ listAllDbs(bool verbose)
 
 
 /*
- * List Tables Grant/Revoke Permissions
+ * List Tables' Grant/Revoke Permissions
  * \z (now also \dp -- perhaps more mnemonic)
  */
 bool
@@ -528,7 +528,7 @@ permissionsList(const char *pattern)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, true, false};
+	static const bool translate_columns[] = {false, false, true, false, false};
 
 	initPQExpBuffer(&buf);
 
@@ -544,7 +544,18 @@ permissionsList(const char *pattern)
 					  gettext_noop("Name"),
 					  gettext_noop("table"), gettext_noop("view"), gettext_noop("sequence"),
 					  gettext_noop("Type"));
+
 	printACLColumn(&buf, "c.relacl");
+
+	if (pset.sversion >= 80400)
+		appendPQExpBuffer(&buf,
+						  ",\n  pg_catalog.array_to_string(ARRAY(\n"
+						  "    SELECT attname || E':\\n  ' || pg_catalog.array_to_string(attacl, E'\\n  ')\n"
+						  "    FROM pg_catalog.pg_attribute a\n"
+						  "    WHERE attrelid = c.oid AND NOT attisdropped AND attacl IS NOT NULL\n"
+						  "  ), E'\\n') AS \"%s\"",
+						  gettext_noop("Column access privileges"));
+
 	appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
 					  "WHERE c.relkind IN ('r', 'v', 'S')\n");
@@ -1907,7 +1918,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, true, false, false, false};
+	static const bool translate_columns[] = {false, false, true, false, false, false, false};
 
 	if (!(showTables || showIndexes || showViews || showSeq))
 		showTables = showViews = showSeq = true;
@@ -1965,7 +1976,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 	if (showSeq)
 		appendPQExpBuffer(&buf, "'S',");
 	if (showSystem)
-		appendPQExpBuffer(&buf, "'s',");	/* was RELKIND_SPECIAL in <= 8.1.X */
+		appendPQExpBuffer(&buf, "'s',");	/* was RELKIND_SPECIAL in <= 8.1 */
 	appendPQExpBuffer(&buf, "''");		/* dummy */
 	appendPQExpBuffer(&buf, ")\n");
 
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 64a05e738d5..11349023d94 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.518 2009/01/16 13:27:24 heikki Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.519 2009/01/22 20:16:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200901161
+#define CATALOG_VERSION_NO	200901221
 
 #endif
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index f615d45011e..99380da566e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.38 2009/01/01 17:23:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.39 2009/01/22 20:16:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -108,7 +108,7 @@ typedef struct ObjectAddress
 {
 	Oid			classId;		/* Class Id from pg_class */
 	Oid			objectId;		/* OID of the object */
-	int32		objectSubId;	/* Subitem within the object (column of table) */
+	int32		objectSubId;	/* Subitem within object (eg column), or 0 */
 } ObjectAddress;
 
 /* expansible list of ObjectAddresses (private in dependency.c) */
@@ -221,14 +221,15 @@ extern void recordSharedDependencyOn(ObjectAddress *depender,
 						 ObjectAddress *referenced,
 						 SharedDependencyType deptype);
 
-extern void deleteSharedDependencyRecordsFor(Oid classId, Oid objectId);
+extern void deleteSharedDependencyRecordsFor(Oid classId, Oid objectId,
+											 int32 objectSubId);
 
 extern void recordDependencyOnOwner(Oid classId, Oid objectId, Oid owner);
 
 extern void changeDependencyOnOwner(Oid classId, Oid objectId,
 						Oid newOwnerId);
 
-extern void updateAclDependencies(Oid classId, Oid objectId,
+extern void updateAclDependencies(Oid classId, Oid objectId, int32 objectSubId,
 					  Oid ownerId, bool isGrant,
 					  int noldmembers, Oid *oldmembers,
 					  int nnewmembers, Oid *newmembers);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index e2c10efe779..1c517c7f3b6 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.105 2009/01/01 17:23:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.106 2009/01/22 20:16:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -202,7 +202,7 @@ DECLARE_UNIQUE_INDEX(pg_rewrite_rel_rulename_index, 2693, on pg_rewrite using bt
 #define RewriteRelRulenameIndexId  2693
 
 /* This following index is not used for a cache and is not unique */
-DECLARE_INDEX(pg_shdepend_depender_index, 1232, on pg_shdepend using btree(dbid oid_ops, classid oid_ops, objid oid_ops));
+DECLARE_INDEX(pg_shdepend_depender_index, 1232, on pg_shdepend using btree(dbid oid_ops, classid oid_ops, objid oid_ops, objsubid int4_ops));
 #define SharedDependDependerIndexId		1232
 /* This following index is not used for a cache and is not unique */
 DECLARE_INDEX(pg_shdepend_reference_index, 1233, on pg_shdepend using btree(refclassid oid_ops, refobjid oid_ops));
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 5411019ab14..3fddf7e9227 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.145 2009/01/01 17:23:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.146 2009/01/22 20:16:08 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -144,14 +144,24 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS
 
 	/* Number of times inherited from direct parent relation(s) */
 	int4		attinhcount;
+
+	/*
+	 * VARIABLE LENGTH FIELDS start here.  These fields may be NULL, too.
+	 *
+	 * NOTE: the following fields are not present in tuple descriptors!
+	 */
+
+	/* Column-level access permissions */
+	aclitem		attacl[1];
 } FormData_pg_attribute;
 
 /*
- * someone should figure out how to do this properly. (The problem is
- * the size of the C struct is not the same as the size of the tuple
- * because of alignment padding at the end of the struct.)
+ * ATTRIBUTE_FIXED_PART_SIZE is the size of the fixed-layout,
+ * guaranteed-not-null part of a pg_attribute row.  This is in fact as much
+ * of the row as gets copied into tuple descriptors, so don't expect you
+ * can access fields beyond attinhcount except in a real tuple!
  */
-#define ATTRIBUTE_TUPLE_SIZE \
+#define ATTRIBUTE_FIXED_PART_SIZE \
 	(offsetof(FormData_pg_attribute,attinhcount) + sizeof(int4))
 
 /* ----------------
@@ -166,7 +176,7 @@ typedef FormData_pg_attribute *Form_pg_attribute;
  * ----------------
  */
 
-#define Natts_pg_attribute				17
+#define Natts_pg_attribute				18
 #define Anum_pg_attribute_attrelid		1
 #define Anum_pg_attribute_attname		2
 #define Anum_pg_attribute_atttypid		3
@@ -184,24 +194,9 @@ typedef FormData_pg_attribute *Form_pg_attribute;
 #define Anum_pg_attribute_attisdropped	15
 #define Anum_pg_attribute_attislocal	16
 #define Anum_pg_attribute_attinhcount	17
+#define Anum_pg_attribute_attacl		18
 
 
-
-/* ----------------
- *		SCHEMA_ macros for declaring hardcoded tuple descriptors.
- *		these are used in utils/cache/relcache.c
- * ----------------
-#define SCHEMA_NAME(x) CppConcat(Name_,x)
-#define SCHEMA_DESC(x) CppConcat(Desc_,x)
-#define SCHEMA_NATTS(x) CppConcat(Natts_,x)
-#define SCHEMA_DEF(x) \
-	FormData_pg_attribute \
-	SCHEMA_DESC(x) [ SCHEMA_NATTS(x) ] = \
-	{ \
-		CppConcat(Schema_,x) \
-	}
- */
-
 /* ----------------
  *		initial contents of pg_attribute
  *
@@ -217,244 +212,246 @@ typedef FormData_pg_attribute *Form_pg_attribute;
  * ----------------
  */
 #define Schema_pg_type \
-{ 1247, {"typname"},	   19, -1, NAMEDATALEN, 1, 0, -1, -1, false, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typnamespace"},  26, -1,	4,	2, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typowner"},	   26, -1,	4,	3, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typlen"},		   21, -1,	2,	4, 0, -1, -1, true, 'p', 's', true, false, false, true, 0 }, \
-{ 1247, {"typbyval"},	   16, -1,	1,	5, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typtype"},	   18, -1,	1,	6, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typcategory"},   18, -1,	1,	7, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typispreferred"},16, -1,	1,	8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typisdefined"},  16, -1,	1,	9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typdelim"},	   18, -1,	1, 10, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typrelid"},	   26, -1,	4, 11, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typelem"},	   26, -1,	4, 12, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typarray"},	   26, -1,	4, 13, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typinput"},	   24, -1,	4, 14, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typoutput"},	   24, -1,	4, 15, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typreceive"},    24, -1,	4, 16, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typsend"},	   24, -1,	4, 17, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typmodin"},	   24, -1,	4, 18, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typmodout"},	   24, -1,	4, 19, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typanalyze"},    24, -1,	4, 20, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typalign"},	   18, -1,	1, 21, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typstorage"},    18, -1,	1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typnotnull"},    16, -1,	1, 23, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1247, {"typbasetype"},   26, -1,	4, 24, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typtypmod"},	   23, -1,	4, 25, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typndims"},	   23, -1,	4, 26, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1247, {"typdefaultbin"}, 25, -1, -1, 27, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1247, {"typdefault"},    25, -1, -1, 28, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0 }
-
-DATA(insert ( 1247 typname			19 -1 NAMEDATALEN	1 0 -1 -1 f p c t f f t 0));
-DATA(insert ( 1247 typnamespace		26 -1 4   2 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typowner			26 -1 4   3 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typlen			21 -1 2   4 0 -1 -1 t p s t f f t 0));
-DATA(insert ( 1247 typbyval			16 -1 1   5 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1247 typtype			18 -1 1   6 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1247 typcategory		18 -1 1   7 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1247 typispreferred	16 -1 1   8 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1247 typisdefined		16 -1 1   9 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1247 typdelim			18 -1 1  10 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1247 typrelid			26 -1 4  11 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typelem			26 -1 4  12 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typarray			26 -1 4  13 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typinput			24 -1 4  14 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typoutput		24 -1 4  15 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typreceive		24 -1 4  16 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typsend			24 -1 4  17 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typmodin			24 -1 4  18 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typmodout		24 -1 4  19 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typanalyze		24 -1 4  20 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typalign			18 -1 1  21 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1247 typstorage		18 -1 1  22 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1247 typnotnull		16 -1 1  23 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1247 typbasetype		26 -1 4  24 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typtypmod		23 -1 4  25 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typndims			23 -1 4  26 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 typdefaultbin	25 -1 -1 27 0 -1 -1 f x i f f f t 0));
-DATA(insert ( 1247 typdefault		25 -1 -1 28 0 -1 -1 f x i f f f t 0));
-DATA(insert ( 1247 ctid				27 0  6  -1 0 -1 -1 f p s t f f t 0));
-DATA(insert ( 1247 oid				26 0  4  -2 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 xmin				28 0  4  -3 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 cmin				29 0  4  -4 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 xmax				28 0  4  -5 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 cmax				29 0  4  -6 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1247 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0));
+{ 1247, {"typname"},	   19, -1, NAMEDATALEN, 1, 0, -1, -1, false, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typnamespace"},  26, -1,	4,	2, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typowner"},	   26, -1,	4,	3, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typlen"},		   21, -1,	2,	4, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typbyval"},	   16, -1,	1,	5, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typtype"},	   18, -1,	1,	6, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typcategory"},   18, -1,	1,	7, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typispreferred"},16, -1,	1,	8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typisdefined"},  16, -1,	1,	9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typdelim"},	   18, -1,	1, 10, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typrelid"},	   26, -1,	4, 11, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typelem"},	   26, -1,	4, 12, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typarray"},	   26, -1,	4, 13, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typinput"},	   24, -1,	4, 14, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typoutput"},	   24, -1,	4, 15, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typreceive"},    24, -1,	4, 16, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typsend"},	   24, -1,	4, 17, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typmodin"},	   24, -1,	4, 18, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typmodout"},	   24, -1,	4, 19, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typanalyze"},    24, -1,	4, 20, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typalign"},	   18, -1,	1, 21, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typstorage"},    18, -1,	1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typnotnull"},    16, -1,	1, 23, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typbasetype"},   26, -1,	4, 24, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typtypmod"},	   23, -1,	4, 25, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typndims"},	   23, -1,	4, 26, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1247, {"typdefaultbin"}, 25, -1, -1, 27, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1247, {"typdefault"},    25, -1, -1, 28, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
+
+DATA(insert ( 1247 typname			19 -1 NAMEDATALEN	1 0 -1 -1 f p c t f f t 0 _null_));
+DATA(insert ( 1247 typnamespace		26 -1 4   2 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typowner			26 -1 4   3 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typlen			21 -1 2   4 0 -1 -1 t p s t f f t 0 _null_));
+DATA(insert ( 1247 typbyval			16 -1 1   5 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1247 typtype			18 -1 1   6 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1247 typcategory		18 -1 1   7 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1247 typispreferred	16 -1 1   8 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1247 typisdefined		16 -1 1   9 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1247 typdelim			18 -1 1  10 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1247 typrelid			26 -1 4  11 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typelem			26 -1 4  12 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typarray			26 -1 4  13 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typinput			24 -1 4  14 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typoutput		24 -1 4  15 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typreceive		24 -1 4  16 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typsend			24 -1 4  17 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typmodin			24 -1 4  18 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typmodout		24 -1 4  19 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typanalyze		24 -1 4  20 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typalign			18 -1 1  21 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1247 typstorage		18 -1 1  22 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1247 typnotnull		16 -1 1  23 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1247 typbasetype		26 -1 4  24 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typtypmod		23 -1 4  25 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typndims			23 -1 4  26 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 typdefaultbin	25 -1 -1 27 0 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1247 typdefault		25 -1 -1 28 0 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1247 ctid				27 0  6  -1 0 -1 -1 f p s t f f t 0 _null_));
+DATA(insert ( 1247 oid				26 0  4  -2 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 xmin				28 0  4  -3 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 cmin				29 0  4  -4 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 xmax				28 0  4  -5 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 cmax				29 0  4  -6 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1247 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
 
 /* ----------------
  *		pg_proc
  * ----------------
  */
 #define Schema_pg_proc \
-{ 1255, {"proname"},			19, -1, NAMEDATALEN,  1, 0, -1, -1, false, 'p', 'c', true, false, false, true, 0 }, \
-{ 1255, {"pronamespace"},		26, -1, 4,	2, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1255, {"proowner"},			26, -1, 4,	3, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1255, {"prolang"},			26, -1, 4,	4, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1255, {"procost"},		   700, -1, 4,	5, 0, -1, -1, FLOAT4PASSBYVAL, 'p', 'i', true, false, false, true, 0 }, \
-{ 1255, {"prorows"},		   700, -1, 4,	6, 0, -1, -1, FLOAT4PASSBYVAL, 'p', 'i', true, false, false, true, 0 }, \
-{ 1255, {"provariadic"},		26, -1, 4,	7, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1255, {"proisagg"},			16, -1, 1,	8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1255, {"proiswindow"},		16, -1, 1,	9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1255, {"prosecdef"},			16, -1, 1, 10, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1255, {"proisstrict"},		16, -1, 1, 11, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1255, {"proretset"},			16, -1, 1, 12, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1255, {"provolatile"},		18, -1, 1, 13, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1255, {"pronargs"},			21, -1, 2, 14, 0, -1, -1, true, 'p', 's', true, false, false, true, 0 }, \
-{ 1255, {"pronargdefaults"},	21, -1, 2, 15, 0, -1, -1, true, 'p', 's', true, false, false, true, 0 }, \
-{ 1255, {"prorettype"},			26, -1, 4, 16, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1255, {"proargtypes"},		30, -1, -1, 17, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0 }, \
-{ 1255, {"proallargtypes"},   1028, -1, -1, 18, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1255, {"proargmodes"},	  1002, -1, -1, 19, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1255, {"proargnames"},	  1009, -1, -1, 20, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1255, {"proargdefaults"},		25, -1, -1, 21, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1255, {"prosrc"},				25, -1, -1, 22, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1255, {"probin"},				17, -1, -1, 23, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1255, {"proconfig"},		  1009, -1, -1, 24, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1255, {"proacl"},			  1034, -1, -1, 25, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }
-
-DATA(insert ( 1255 proname			19 -1 NAMEDATALEN	1 0 -1 -1 f p c t f f t 0));
-DATA(insert ( 1255 pronamespace		26 -1 4   2 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 proowner			26 -1 4   3 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 prolang			26 -1 4   4 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 procost		   700 -1 4   5 0 -1 -1 FLOAT4PASSBYVAL p i t f f t 0));
-DATA(insert ( 1255 prorows		   700 -1 4   6 0 -1 -1 FLOAT4PASSBYVAL p i t f f t 0));
-DATA(insert ( 1255 provariadic		26 -1 4   7 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 proisagg			16 -1 1   8 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1255 proiswindow		16 -1 1   9 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1255 prosecdef		16 -1 1  10 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1255 proisstrict		16 -1 1  11 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1255 proretset		16 -1 1  12 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1255 provolatile		18 -1 1  13 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1255 pronargs			21 -1 2  14 0 -1 -1 t p s t f f t 0));
-DATA(insert ( 1255 pronargdefaults	21 -1 2  15 0 -1 -1 t p s t f f t 0));
-DATA(insert ( 1255 prorettype		26 -1 4  16 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 proargtypes		30 -1 -1 17 1 -1 -1 f p i t f f t 0));
-DATA(insert ( 1255 proallargtypes 1028 -1 -1 18 1 -1 -1 f x i f f f t 0));
-DATA(insert ( 1255 proargmodes	  1002 -1 -1 19 1 -1 -1 f x i f f f t 0));
-DATA(insert ( 1255 proargnames	  1009 -1 -1 20 1 -1 -1 f x i f f f t 0));
-DATA(insert ( 1255 proargdefaults	25 -1 -1 21 0 -1 -1 f x i f f f t 0));
-DATA(insert ( 1255 prosrc			25 -1 -1 22 0 -1 -1 f x i f f f t 0));
-DATA(insert ( 1255 probin			17 -1 -1 23 0 -1 -1 f x i f f f t 0));
-DATA(insert ( 1255 proconfig	  1009 -1 -1 24 1 -1 -1 f x i f f f t 0));
-DATA(insert ( 1255 proacl		  1034 -1 -1 25 1 -1 -1 f x i f f f t 0));
-DATA(insert ( 1255 ctid				27 0  6  -1 0 -1 -1 f p s t f f t 0));
-DATA(insert ( 1255 oid				26 0  4  -2 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 xmin				28 0  4  -3 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 cmin				29 0  4  -4 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 xmax				28 0  4  -5 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 cmax				29 0  4  -6 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1255 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0));
+{ 1255, {"proname"},			19, -1, NAMEDATALEN,  1, 0, -1, -1, false, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"pronamespace"},		26, -1, 4,	2, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"proowner"},			26, -1, 4,	3, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"prolang"},			26, -1, 4,	4, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"procost"},		   700, -1, 4,	5, 0, -1, -1, FLOAT4PASSBYVAL, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"prorows"},		   700, -1, 4,	6, 0, -1, -1, FLOAT4PASSBYVAL, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"provariadic"},		26, -1, 4,	7, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"proisagg"},			16, -1, 1,	8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"proiswindow"},		16, -1, 1,	9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"prosecdef"},			16, -1, 1, 10, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"proisstrict"},		16, -1, 1, 11, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"proretset"},			16, -1, 1, 12, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"provolatile"},		18, -1, 1, 13, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"pronargs"},			21, -1, 2, 14, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"pronargdefaults"},	21, -1, 2, 15, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"prorettype"},			26, -1, 4, 16, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"proargtypes"},		30, -1, -1, 17, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1255, {"proallargtypes"},   1028, -1, -1, 18, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1255, {"proargmodes"},	  1002, -1, -1, 19, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1255, {"proargnames"},	  1009, -1, -1, 20, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1255, {"proargdefaults"},		25, -1, -1, 21, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1255, {"prosrc"},				25, -1, -1, 22, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1255, {"probin"},				17, -1, -1, 23, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1255, {"proconfig"},		  1009, -1, -1, 24, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1255, {"proacl"},			  1034, -1, -1, 25, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
+
+DATA(insert ( 1255 proname			19 -1 NAMEDATALEN	1 0 -1 -1 f p c t f f t 0 _null_));
+DATA(insert ( 1255 pronamespace		26 -1 4   2 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 proowner			26 -1 4   3 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 prolang			26 -1 4   4 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 procost		   700 -1 4   5 0 -1 -1 FLOAT4PASSBYVAL p i t f f t 0 _null_));
+DATA(insert ( 1255 prorows		   700 -1 4   6 0 -1 -1 FLOAT4PASSBYVAL p i t f f t 0 _null_));
+DATA(insert ( 1255 provariadic		26 -1 4   7 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 proisagg			16 -1 1   8 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1255 proiswindow		16 -1 1   9 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1255 prosecdef		16 -1 1  10 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1255 proisstrict		16 -1 1  11 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1255 proretset		16 -1 1  12 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1255 provolatile		18 -1 1  13 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1255 pronargs			21 -1 2  14 0 -1 -1 t p s t f f t 0 _null_));
+DATA(insert ( 1255 pronargdefaults	21 -1 2  15 0 -1 -1 t p s t f f t 0 _null_));
+DATA(insert ( 1255 prorettype		26 -1 4  16 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 proargtypes		30 -1 -1 17 1 -1 -1 f p i t f f t 0 _null_));
+DATA(insert ( 1255 proallargtypes 1028 -1 -1 18 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1255 proargmodes	  1002 -1 -1 19 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1255 proargnames	  1009 -1 -1 20 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1255 proargdefaults	25 -1 -1 21 0 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1255 prosrc			25 -1 -1 22 0 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1255 probin			17 -1 -1 23 0 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1255 proconfig	  1009 -1 -1 24 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1255 proacl		  1034 -1 -1 25 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1255 ctid				27 0  6  -1 0 -1 -1 f p s t f f t 0 _null_));
+DATA(insert ( 1255 oid				26 0  4  -2 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 xmin				28 0  4  -3 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 cmin				29 0  4  -4 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 xmax				28 0  4  -5 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 cmax				29 0  4  -6 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1255 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
 
 /* ----------------
  *		pg_attribute
  * ----------------
  */
 #define Schema_pg_attribute \
-{ 1249, {"attrelid"},	  26, -1,	4,	1, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1249, {"attname"},	  19, -1, NAMEDATALEN,	2, 0, -1, -1, false, 'p', 'c', true, false, false, true, 0 }, \
-{ 1249, {"atttypid"},	  26, -1,	4,	3, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1249, {"attstattarget"}, 23, -1,	4,	4, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1249, {"attlen"},		  21, -1,	2,	5, 0, -1, -1, true, 'p', 's', true, false, false, true, 0 }, \
-{ 1249, {"attnum"},		  21, -1,	2,	6, 0, -1, -1, true, 'p', 's', true, false, false, true, 0 }, \
-{ 1249, {"attndims"},	  23, -1,	4,	7, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1249, {"attcacheoff"},  23, -1,	4,	8, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1249, {"atttypmod"},	  23, -1,	4,	9, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1249, {"attbyval"},	  16, -1,	1, 10, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1249, {"attstorage"},   18, -1,	1, 11, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1249, {"attalign"},	  18, -1,	1, 12, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1249, {"attnotnull"},   16, -1,	1, 13, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1249, {"atthasdef"},	  16, -1,	1, 14, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1249, {"attisdropped"}, 16, -1,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1249, {"attislocal"},   16, -1,	1, 16, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1249, {"attinhcount"},  23, -1,	4, 17, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }
-
-DATA(insert ( 1249 attrelid			26 -1  4   1 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 attname			19 -1 NAMEDATALEN  2 0 -1 -1 f p c t f f t 0));
-DATA(insert ( 1249 atttypid			26 -1  4   3 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 attstattarget	23 -1  4   4 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 attlen			21 -1  2   5 0 -1 -1 t p s t f f t 0));
-DATA(insert ( 1249 attnum			21 -1  2   6 0 -1 -1 t p s t f f t 0));
-DATA(insert ( 1249 attndims			23 -1  4   7 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 attcacheoff		23 -1  4   8 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 atttypmod		23 -1  4   9 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 attbyval			16 -1  1  10 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1249 attstorage		18 -1  1  11 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1249 attalign			18 -1  1  12 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1249 attnotnull		16 -1  1  13 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1249 atthasdef		16 -1  1  14 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1249 attisdropped		16 -1  1  15 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1249 attislocal		16 -1  1  16 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1249 attinhcount		23 -1  4  17 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 ctid				27 0  6  -1 0 -1 -1 f p s t f f t 0));
+{ 1249, {"attrelid"},	  26, -1,	4,	1, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attname"},	  19, -1, NAMEDATALEN,	2, 0, -1, -1, false, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"atttypid"},	  26, -1,	4,	3, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attstattarget"}, 23, -1,	4,	4, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attlen"},		  21, -1,	2,	5, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attnum"},		  21, -1,	2,	6, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attndims"},	  23, -1,	4,	7, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attcacheoff"},  23, -1,	4,	8, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"atttypmod"},	  23, -1,	4,	9, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attbyval"},	  16, -1,	1, 10, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attstorage"},   18, -1,	1, 11, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attalign"},	  18, -1,	1, 12, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attnotnull"},   16, -1,	1, 13, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"atthasdef"},	  16, -1,	1, 14, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attisdropped"}, 16, -1,	1, 15, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attislocal"},   16, -1,	1, 16, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attinhcount"},  23, -1,	4, 17, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1249, {"attacl"},     1034, -1,  -1, 18, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
+
+DATA(insert ( 1249 attrelid			26 -1  4   1 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 attname			19 -1 NAMEDATALEN  2 0 -1 -1 f p c t f f t 0 _null_));
+DATA(insert ( 1249 atttypid			26 -1  4   3 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 attstattarget	23 -1  4   4 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 attlen			21 -1  2   5 0 -1 -1 t p s t f f t 0 _null_));
+DATA(insert ( 1249 attnum			21 -1  2   6 0 -1 -1 t p s t f f t 0 _null_));
+DATA(insert ( 1249 attndims			23 -1  4   7 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 attcacheoff		23 -1  4   8 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 atttypmod		23 -1  4   9 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 attbyval			16 -1  1  10 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1249 attstorage		18 -1  1  11 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1249 attalign			18 -1  1  12 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1249 attnotnull		16 -1  1  13 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1249 atthasdef		16 -1  1  14 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1249 attisdropped		16 -1  1  15 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1249 attislocal		16 -1  1  16 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1249 attinhcount		23 -1  4  17 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 attacl		  1034 -1 -1  18 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1249 ctid				27 0  6  -1 0 -1 -1 f p s t f f t 0 _null_));
 /* no OIDs in pg_attribute */
-DATA(insert ( 1249 xmin				28 0  4  -3 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 cmin				29 0  4  -4 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 xmax				28 0  4  -5 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 cmax				29 0  4  -6 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1249 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0));
+DATA(insert ( 1249 xmin				28 0  4  -3 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 cmin				29 0  4  -4 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 xmax				28 0  4  -5 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 cmax				29 0  4  -6 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1249 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
 
 /* ----------------
  *		pg_class
  * ----------------
  */
 #define Schema_pg_class \
-{ 1259, {"relname"},	   19, -1, NAMEDATALEN, 1, 0, -1, -1, false, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relnamespace"},  26, -1,	4,	2, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"reltype"},	   26, -1,	4,	3, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"relowner"},	   26, -1,	4,	4, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"relam"},		   26, -1,	4,	5, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"relfilenode"},   26, -1,	4,	6, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"reltablespace"}, 26, -1,	4,	7, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"relpages"},	   23, -1,	4,	8, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"reltuples"},	   700, -1, 4,	9, 0, -1, -1, FLOAT4PASSBYVAL, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"reltoastrelid"}, 26, -1,	4, 10, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"reltoastidxid"}, 26, -1,	4, 11, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"relhasindex"},   16, -1,	1, 12, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relisshared"},   16, -1,	1, 13, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relkind"},	   18, -1,	1, 14, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relnatts"},	   21, -1,	2, 15, 0, -1, -1, true, 'p', 's', true, false, false, true, 0 }, \
-{ 1259, {"relchecks"},	   21, -1,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0 }, \
-{ 1259, {"relhasoids"},    16, -1,	1, 17, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relhaspkey"},    16, -1,	1, 18, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relhasrules"},   16, -1,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relhastriggers"},16, -1,	1, 20, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relhassubclass"},16, -1,	1, 21, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 1259, {"relfrozenxid"},  28, -1,	4, 22, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 1259, {"relacl"},		 1034, -1, -1, 23, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 1259, {"reloptions"},  1009, -1, -1, 24, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }
-
-DATA(insert ( 1259 relname			19 -1 NAMEDATALEN	1 0 -1 -1 f p c t f f t 0));
-DATA(insert ( 1259 relnamespace		26 -1 4   2 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 reltype			26 -1 4   3 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 relowner			26 -1 4   4 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 relam			26 -1 4   5 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 relfilenode		26 -1 4   6 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 reltablespace	26 -1 4   7 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 relpages			23 -1 4   8 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 reltuples	   700 -1 4   9 0 -1 -1 FLOAT4PASSBYVAL p i t f f t 0));
-DATA(insert ( 1259 reltoastrelid	26 -1 4  10 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 reltoastidxid	26 -1 4  11 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 relhasindex		16 -1 1  12 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1259 relisshared		16 -1 1  13 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1259 relkind			18 -1 1  14 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1259 relnatts			21 -1 2  15 0 -1 -1 t p s t f f t 0));
-DATA(insert ( 1259 relchecks		21 -1 2  16 0 -1 -1 t p s t f f t 0));
-DATA(insert ( 1259 relhasoids		16 -1 1  17 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1259 relhaspkey		16 -1 1  18 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1259 relhasrules		16 -1 1  19 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1259 relhastriggers	16 -1 1  20 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1259 relhassubclass	16 -1 1  21 0 -1 -1 t p c t f f t 0));
-DATA(insert ( 1259 relfrozenxid		28 -1 4  22 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 relacl		  1034 -1 -1 23 1 -1 -1 f x i f f f t 0));
-DATA(insert ( 1259 reloptions	  1009 -1 -1 24 1 -1 -1 f x i f f f t 0));
-DATA(insert ( 1259 ctid				27 0  6  -1 0 -1 -1 f p s t f f t 0));
-DATA(insert ( 1259 oid				26 0  4  -2 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 xmin				28 0  4  -3 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 cmin				29 0  4  -4 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 xmax				28 0  4  -5 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 cmax				29 0  4  -6 0 -1 -1 t p i t f f t 0));
-DATA(insert ( 1259 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0));
+{ 1259, {"relname"},	   19, -1, NAMEDATALEN, 1, 0, -1, -1, false, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relnamespace"},  26, -1,	4,	2, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"reltype"},	   26, -1,	4,	3, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relowner"},	   26, -1,	4,	4, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relam"},		   26, -1,	4,	5, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relfilenode"},   26, -1,	4,	6, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"reltablespace"}, 26, -1,	4,	7, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relpages"},	   23, -1,	4,	8, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"reltuples"},	   700, -1, 4,	9, 0, -1, -1, FLOAT4PASSBYVAL, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"reltoastrelid"}, 26, -1,	4, 10, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"reltoastidxid"}, 26, -1,	4, 11, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relhasindex"},   16, -1,	1, 12, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relisshared"},   16, -1,	1, 13, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relkind"},	   18, -1,	1, 14, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relnatts"},	   21, -1,	2, 15, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relchecks"},	   21, -1,	2, 16, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relhasoids"},    16, -1,	1, 17, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relhaspkey"},    16, -1,	1, 18, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relhasrules"},   16, -1,	1, 19, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relhastriggers"},16, -1,	1, 20, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relhassubclass"},16, -1,	1, 21, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relfrozenxid"},  28, -1,	4, 22, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 1259, {"relacl"},		 1034, -1, -1, 23, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 1259, {"reloptions"},  1009, -1, -1, 24, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
+
+DATA(insert ( 1259 relname			19 -1 NAMEDATALEN	1 0 -1 -1 f p c t f f t 0 _null_));
+DATA(insert ( 1259 relnamespace		26 -1 4   2 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 reltype			26 -1 4   3 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 relowner			26 -1 4   4 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 relam			26 -1 4   5 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 relfilenode		26 -1 4   6 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 reltablespace	26 -1 4   7 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 relpages			23 -1 4   8 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 reltuples	   700 -1 4   9 0 -1 -1 FLOAT4PASSBYVAL p i t f f t 0 _null_));
+DATA(insert ( 1259 reltoastrelid	26 -1 4  10 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 reltoastidxid	26 -1 4  11 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 relhasindex		16 -1 1  12 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relisshared		16 -1 1  13 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relkind			18 -1 1  14 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relnatts			21 -1 2  15 0 -1 -1 t p s t f f t 0 _null_));
+DATA(insert ( 1259 relchecks		21 -1 2  16 0 -1 -1 t p s t f f t 0 _null_));
+DATA(insert ( 1259 relhasoids		16 -1 1  17 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relhaspkey		16 -1 1  18 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relhasrules		16 -1 1  19 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relhastriggers	16 -1 1  20 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relhassubclass	16 -1 1  21 0 -1 -1 t p c t f f t 0 _null_));
+DATA(insert ( 1259 relfrozenxid		28 -1 4  22 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 relacl		  1034 -1 -1 23 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1259 reloptions	  1009 -1 -1 24 1 -1 -1 f x i f f f t 0 _null_));
+DATA(insert ( 1259 ctid				27 0  6  -1 0 -1 -1 f p s t f f t 0 _null_));
+DATA(insert ( 1259 oid				26 0  4  -2 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 xmin				28 0  4  -3 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 cmin				29 0  4  -4 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 xmax				28 0  4  -5 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 cmax				29 0  4  -6 0 -1 -1 t p i t f f t 0 _null_));
+DATA(insert ( 1259 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0 _null_));
 
 /* ----------------
  *		pg_index
@@ -465,19 +462,19 @@ DATA(insert ( 1259 tableoid			26 0  4  -7 0 -1 -1 t p i t f f t 0));
  * ----------------
  */
 #define Schema_pg_index \
-{ 0, {"indexrelid"},		26, -1, 4, 1, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 0, {"indrelid"},			26, -1, 4, 2, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \
-{ 0, {"indnatts"},			21, -1, 2, 3, 0, -1, -1, true, 'p', 's', true, false, false, true, 0 }, \
-{ 0, {"indisunique"},		16, -1, 1, 4, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 0, {"indisprimary"},		16, -1, 1, 5, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 0, {"indisclustered"},	16, -1, 1, 6, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 0, {"indisvalid"},		16, -1, 1, 7, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 0, {"indcheckxmin"},		16, -1, 1, 8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 0, {"indisready"},		16, -1, 1, 9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \
-{ 0, {"indkey"},			22, -1, -1, 10, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0 }, \
-{ 0, {"indclass"},			30, -1, -1, 11, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0 }, \
-{ 0, {"indoption"},			22, -1, -1, 12, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0 }, \
-{ 0, {"indexprs"},			25, -1, -1, 13, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \
-{ 0, {"indpred"},			25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0 }
+{ 0, {"indexrelid"},		26, -1, 4, 1, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indrelid"},			26, -1, 4, 2, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indnatts"},			21, -1, 2, 3, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indisunique"},		16, -1, 1, 4, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indisprimary"},		16, -1, 1, 5, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indisclustered"},	16, -1, 1, 6, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indisvalid"},		16, -1, 1, 7, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indcheckxmin"},		16, -1, 1, 8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indisready"},		16, -1, 1, 9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indkey"},			22, -1, -1, 10, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indclass"},			30, -1, -1, 11, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indoption"},			22, -1, -1, 12, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indexprs"},			25, -1, -1, 13, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 0, {"indpred"},			25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
 
 #endif   /* PG_ATTRIBUTE_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 97ea79544ba..776158805d4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.111 2009/01/01 17:23:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.112 2009/01/22 20:16:09 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -123,7 +123,7 @@ typedef FormData_pg_class *Form_pg_class;
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
 DATA(insert OID = 1247 (  pg_type		PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f r 28 0 t f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f r 17 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f r 18 0 f f f f f 3 _null_ _null_ ));
 DESCR("");
 DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f r 25 0 t f f f f 3 _null_ _null_ ));
 DESCR("");
diff --git a/src/include/catalog/pg_shdepend.h b/src/include/catalog/pg_shdepend.h
index f741333914d..97f01df4c15 100644
--- a/src/include/catalog/pg_shdepend.h
+++ b/src/include/catalog/pg_shdepend.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_shdepend.h,v 1.8 2009/01/01 17:23:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_shdepend.h,v 1.9 2009/01/22 20:16:09 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -39,10 +39,12 @@ CATALOG(pg_shdepend,1214) BKI_SHARED_RELATION BKI_WITHOUT_OIDS
 	Oid			dbid;			/* OID of database containing object */
 	Oid			classid;		/* OID of table containing object */
 	Oid			objid;			/* OID of object itself */
+	int4		objsubid;		/* column number, or 0 if not used */
 
 	/*
 	 * Identification of the independent (referenced) object.  This is always
-	 * a shared object, so we need no database ID field.
+	 * a shared object, so we need no database ID field.  We don't bother
+	 * with a sub-object ID either.
 	 */
 	Oid			refclassid;		/* OID of table containing object */
 	Oid			refobjid;		/* OID of object itself */
@@ -65,13 +67,14 @@ typedef FormData_pg_shdepend *Form_pg_shdepend;
  *		compiler constants for pg_shdepend
  * ----------------
  */
-#define Natts_pg_shdepend			6
+#define Natts_pg_shdepend			7
 #define Anum_pg_shdepend_dbid		1
 #define Anum_pg_shdepend_classid	2
 #define Anum_pg_shdepend_objid		3
-#define Anum_pg_shdepend_refclassid 4
-#define Anum_pg_shdepend_refobjid	5
-#define Anum_pg_shdepend_deptype	6
+#define Anum_pg_shdepend_objsubid	4
+#define Anum_pg_shdepend_refclassid	5
+#define Anum_pg_shdepend_refobjid	6
+#define Anum_pg_shdepend_deptype	7
 
 
 /*
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 0b0c6c22323..123159e59a6 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.71 2009/01/22 19:16:31 heikki Exp $
+ * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.72 2009/01/22 20:16:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -104,7 +104,8 @@ extern PGDLLIMPORT int	SessionReplicationRole;
 #define TRIGGER_FIRES_ON_REPLICA			'R'
 #define TRIGGER_DISABLED					'D'
 
-extern Oid	CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid);
+extern Oid	CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid,
+						  bool checkPermissions);
 
 extern void DropTrigger(Oid relid, const char *trigname,
 			DropBehavior behavior, bool missing_ok);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 270760527b6..91efce94623 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.218 2009/01/01 17:24:00 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.219 2009/01/22 20:16:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -369,7 +369,7 @@ typedef enum NodeTag
 	T_FkConstraint,
 	T_PrivGrantee,
 	T_FuncWithArgs,
-	T_PrivTarget,
+	T_AccessPriv,
 	T_CreateOpClassItem,
 	T_InhRelation,
 	T_FunctionParameter,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ce225f801eb..b73e9f2cda6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -13,13 +13,14 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.388 2009/01/16 13:27:24 heikki Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.389 2009/01/22 20:16:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef PARSENODES_H
 #define PARSENODES_H
 
+#include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 #include "nodes/value.h"
 
@@ -622,6 +623,15 @@ typedef struct XmlSerialize
  *	  then do the permissions checks using the access rights of that user,
  *	  not the current effective user ID.  (This allows rules to act as
  *	  setuid gateways.)
+ *
+ *	  For SELECT/INSERT/UPDATE permissions, if the user doesn't have
+ *	  table-wide permissions then it is sufficient to have the permissions
+ *	  on all columns identified in selectedCols (for SELECT) and/or
+ *	  modifiedCols (for INSERT/UPDATE; we can tell which from the query type).
+ *	  selectedCols and modifiedCols are bitmapsets, which cannot have negative
+ *	  integer members, so we subtract FirstLowInvalidHeapAttributeNumber from
+ *	  column numbers before storing them in these fields.  A whole-row Var
+ *	  reference is represented by setting the bit for InvalidAttrNumber.
  *--------------------
  */
 typedef enum RTEKind
@@ -644,7 +654,7 @@ typedef struct RangeTblEntry
 	/*
 	 * XXX the fields applicable to only some rte kinds should be merged into
 	 * a union.  I didn't do this yet because the diffs would impact a lot of
-	 * code that is being actively worked on.  FIXME later.
+	 * code that is being actively worked on.  FIXME someday.
 	 */
 
 	/*
@@ -705,6 +715,8 @@ typedef struct RangeTblEntry
 	bool		inFromCl;		/* present in FROM clause? */
 	AclMode		requiredPerms;	/* bitmask of required access permissions */
 	Oid			checkAsUser;	/* if valid, check access as this role */
+	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
+	Bitmapset  *modifiedCols;	/* columns needing INSERT/UPDATE permission */
 } RangeTblEntry;
 
 /*
@@ -1168,6 +1180,7 @@ typedef struct AlterDomainStmt
  */
 typedef enum GrantObjectType
 {
+	ACL_OBJECT_COLUMN,			/* column */
 	ACL_OBJECT_RELATION,		/* table, view */
 	ACL_OBJECT_SEQUENCE,		/* sequence */
 	ACL_OBJECT_DATABASE,		/* database */
@@ -1186,8 +1199,8 @@ typedef struct GrantStmt
 	GrantObjectType objtype;	/* kind of object being operated on */
 	List	   *objects;		/* list of RangeVar nodes, FuncWithArgs nodes,
 								 * or plain names (as Value strings) */
-	List	   *privileges;		/* list of privilege names (as Strings) */
-	/* privileges == NIL denotes "all privileges" */
+	List	   *privileges;		/* list of AccessPriv nodes */
+	/* privileges == NIL denotes ALL PRIVILEGES */
 	List	   *grantees;		/* list of PrivGrantee nodes */
 	bool		grant_option;	/* grant or revoke grant option */
 	DropBehavior behavior;		/* drop behavior (for REVOKE) */
@@ -1211,18 +1224,27 @@ typedef struct FuncWithArgs
 	List	   *funcargs;		/* list of Typename nodes */
 } FuncWithArgs;
 
-/* This is only used internally in gram.y. */
-typedef struct PrivTarget
+/*
+ * An access privilege, with optional list of column names
+ * priv_name == NULL denotes ALL PRIVILEGES (only used with a column list)
+ * cols == NIL denotes "all columns"
+ * Note that simple "ALL PRIVILEGES" is represented as a NIL list, not
+ * an AccessPriv with both fields null.
+ */
+typedef struct AccessPriv
 {
 	NodeTag		type;
-	GrantObjectType objtype;
-	List	   *objs;
-} PrivTarget;
+	char	   *priv_name;		/* string name of privilege */
+	List	   *cols;			/* list of Value strings */
+} AccessPriv;
 
 /* ----------------------
  *		Grant/Revoke Role Statement
  *
- * Note: the lists of roles are lists of names, as Value strings
+ * Note: because of the parsing ambiguity with the GRANT <privileges>
+ * statement, granted_roles is a list of AccessPriv; the execution code
+ * should complain if any column lists appear.  grantee_roles is a list
+ * of role names, as Value strings.
  * ----------------------
  */
 typedef struct GrantRoleStmt
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index fb5e1fcf451..5e49b1a3dd8 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.60 2009/01/01 17:24:00 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.61 2009/01/22 20:16:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -33,6 +33,10 @@
  * Note that neither relname nor refname of these entries are necessarily
  * unique; searching the rtable by name is a bad idea.
  *
+ * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
+ * This is one-for-one with p_rtable, but contains NULLs for non-join
+ * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
+ *
  * p_joinlist: list of join items (RangeTblRef and JoinExpr nodes) that
  * will become the fromlist of the query's top-level FromExpr node.
  *
@@ -77,6 +81,7 @@ typedef struct ParseState
 	struct ParseState *parentParseState;		/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
+	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
 	List	   *p_joinlist;		/* join items so far (will become FromExpr
 								 * node's fromlist) */
 	List	   *p_relnamespace; /* current namespace for relations */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 76622af25d3..cb133fced66 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.62 2009/01/01 17:24:00 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.63 2009/01/22 20:16:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,6 +46,8 @@ extern Node *qualifiedNameToVar(ParseState *pstate,
 				   char *colname,
 				   bool implicitRTEOK,
 				   int location);
+extern void markVarForSelectPriv(ParseState *pstate, Var *var,
+								 RangeTblEntry *rte);
 extern Relation parserOpenTable(ParseState *pstate, const RangeVar *relation,
 								int lockmode);
 extern RangeTblEntry *addRangeTableEntry(ParseState *pstate,
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 02cf425619e..fed2de5b74e 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.106 2009/01/01 17:24:02 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.107 2009/01/22 20:16:09 tgl Exp $
  *
  * NOTES
  *	  An ACL array is simply an array of AclItems, representing the union
@@ -143,6 +143,7 @@ typedef ArrayType Acl;
 /*
  * Bitmasks defining "all rights" for each supported object type
  */
+#define ACL_ALL_RIGHTS_COLUMN		(ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_REFERENCES)
 #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)
@@ -172,6 +173,7 @@ typedef enum
 /* currently it's only used to tell aclcheck_error what to say */
 typedef enum AclObjectKind
 {
+	ACL_KIND_COLUMN,			/* pg_attribute */
 	ACL_KIND_CLASS,				/* pg_class */
 	ACL_KIND_SEQUENCE,			/* pg_sequence */
 	ACL_KIND_DATABASE,			/* pg_database */
@@ -195,9 +197,14 @@ typedef enum AclObjectKind
  * The information about one Grant/Revoke statement, in internal format: object
  * and grantees names have been turned into Oids, the privilege list is an
  * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, it will be internally turned into the right kind of
+ * all_privs is true, 'privileges' will be internally set to the right kind of
  * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
  * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
  */
 typedef struct
 {
@@ -206,6 +213,7 @@ typedef struct
 	List	   *objects;
 	bool		all_privs;
 	AclMode		privileges;
+	List	   *col_privs;
 	List	   *grantees;
 	bool		grant_option;
 	DropBehavior behavior;
@@ -218,6 +226,8 @@ extern Acl *acldefault(GrantObjectType objtype, Oid ownerId);
 extern Acl *aclupdate(const Acl *old_acl, const AclItem *mod_aip,
 		  int modechg, Oid ownerId, DropBehavior behavior);
 extern Acl *aclnewowner(const Acl *old_acl, Oid oldOwnerId, Oid newOwnerId);
+extern Acl *aclcopy(const Acl *orig_acl);
+extern Acl *aclconcat(const Acl *left_acl, const Acl *right_acl);
 
 extern AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId,
 		AclMode mask, AclMaskHow how);
@@ -253,6 +263,8 @@ extern Datum hash_aclitem(PG_FUNCTION_ARGS);
 extern void ExecuteGrantStmt(GrantStmt *stmt);
 extern void ExecGrantStmt_oids(InternalGrant *istmt);
 
+extern AclMode pg_attribute_aclmask(Oid table_oid, AttrNumber attnum,
+				Oid roleid, AclMode mask, AclMaskHow how);
 extern AclMode pg_class_aclmask(Oid table_oid, Oid roleid,
 				 AclMode mask, AclMaskHow how);
 extern AclMode pg_database_aclmask(Oid db_oid, Oid roleid,
@@ -270,6 +282,10 @@ extern AclMode pg_foreign_data_wrapper_aclmask(Oid fdw_oid, Oid roleid,
 extern AclMode pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
 					  AclMode mask, AclMaskHow how);
 
+extern AclResult pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
+									   Oid roleid, AclMode mode);
+extern AclResult pg_attribute_aclcheck_all(Oid table_oid, Oid roleid,
+										   AclMode mode, AclMaskHow how);
 extern AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode);
 extern AclResult pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode);
 extern AclResult pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode);
@@ -282,6 +298,9 @@ extern AclResult pg_foreign_server_aclcheck(Oid srv_oid, Oid roleid, AclMode mod
 extern void aclcheck_error(AclResult aclerr, AclObjectKind objectkind,
 			   const char *objectname);
 
+extern void aclcheck_error_col(AclResult aclerr, AclObjectKind objectkind,
+			   const char *objectname, const char *colname);
+
 /* ownercheck routines just return true (owner) or false (not) */
 extern bool pg_class_ownercheck(Oid class_oid, Oid roleid);
 extern bool pg_type_ownercheck(Oid type_oid, Oid roleid);
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 178f4221fca..6eb851a378c 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -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=arwdDxt/regression_user0
-                           : regression_user1=a*r*w*d*D*x*t*/regression_user0
-                           : regression_user2=arwdDxt/regression_user1
+                                            Access privileges
+ Schema |   Name   | Type  |                Access privileges                 | Column 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=arwdDxt/regression_user0
+                                        Access privileges
+ Schema |   Name   | Type  |             Access privileges             | Column 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 f4a2bd8d8f5..88d1ab3b78f 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -246,6 +246,147 @@ SELECT * FROM atest2; -- ok
 
 SELECT * FROM atestv2; -- fail (even though regressuser2 can access underlying atest2)
 ERROR:  permission denied for relation atest2
+-- Test column level permissions
+SET SESSION AUTHORIZATION regressuser1;
+CREATE TABLE atest5 (one int, two int, three int);
+CREATE TABLE atest6 (one int, two int, blue int);
+GRANT SELECT (one), INSERT (two), UPDATE (three) ON atest5 TO regressuser4;
+GRANT ALL (one) ON atest5 TO regressuser3;
+INSERT INTO atest5 VALUES (1,2,3);
+SET SESSION AUTHORIZATION regressuser4;
+SELECT * FROM atest5; -- fail
+ERROR:  permission denied for relation atest5
+SELECT one FROM atest5; -- ok
+ one 
+-----
+   1
+(1 row)
+
+SELECT two FROM atest5; -- fail
+ERROR:  permission denied for relation atest5
+SELECT atest5 FROM atest5; -- fail
+ERROR:  permission denied for relation atest5
+SELECT 1 FROM atest5; -- ok
+ ?column? 
+----------
+        1
+(1 row)
+
+SELECT 1 FROM atest5 a JOIN atest5 b USING (one); -- ok
+ ?column? 
+----------
+        1
+(1 row)
+
+SELECT 1 FROM atest5 a JOIN atest5 b USING (two); -- fail
+ERROR:  permission denied for relation atest5
+SELECT 1 FROM atest5 a NATURAL JOIN atest5 b; -- fail
+ERROR:  permission denied for relation atest5
+SELECT (j.*) IS NULL FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail
+ERROR:  permission denied for relation atest5
+SELECT 1 FROM atest5 WHERE two = 2; -- fail
+ERROR:  permission denied for relation atest5
+SELECT * FROM atest1, atest5; -- fail
+ERROR:  permission denied for relation atest5
+SELECT atest1.* FROM atest1, atest5; -- ok
+ a |  b  
+---+-----
+ 1 | two
+ 1 | two
+(2 rows)
+
+SELECT atest1.*,atest5.one FROM atest1, atest5; -- ok
+ a |  b  | one 
+---+-----+-----
+ 1 | two |   1
+ 1 | two |   1
+(2 rows)
+
+SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.two); -- fail
+ERROR:  permission denied for relation atest5
+SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.one); -- ok
+ a |  b  | one 
+---+-----+-----
+ 1 | two |   1
+ 1 | two |   1
+(2 rows)
+
+SELECT one, two FROM atest5; -- fail
+ERROR:  permission denied for relation atest5
+SET SESSION AUTHORIZATION regressuser1;
+GRANT SELECT (one,two) ON atest6 TO regressuser4;
+SET SESSION AUTHORIZATION regressuser4;
+SELECT one, two FROM atest5 NATURAL JOIN atest6; -- fail still
+ERROR:  permission denied for relation atest5
+SET SESSION AUTHORIZATION regressuser1;
+GRANT SELECT (two) ON atest5 TO regressuser4;
+SET SESSION AUTHORIZATION regressuser4;
+SELECT one, two FROM atest5 NATURAL JOIN atest6; -- ok now
+ one | two 
+-----+-----
+(0 rows)
+
+-- test column-level privileges for INSERT and UPDATE
+INSERT INTO atest5 (two) VALUES (3); -- ok
+INSERT INTO atest5 (three) VALUES (4); -- fail
+ERROR:  permission denied for relation atest5
+INSERT INTO atest5 VALUES (5,5,5); -- fail
+ERROR:  permission denied for relation atest5
+UPDATE atest5 SET three = 10; -- ok
+UPDATE atest5 SET one = 8; -- fail
+ERROR:  permission denied for relation atest5
+UPDATE atest5 SET three = 5, one = 2; -- fail
+ERROR:  permission denied for relation atest5
+SET SESSION AUTHORIZATION regressuser1;
+REVOKE ALL (one) ON atest5 FROM regressuser4;
+GRANT SELECT (one,two,blue) ON atest6 TO regressuser4;
+SET SESSION AUTHORIZATION regressuser4;
+SELECT one FROM atest5; -- fail
+ERROR:  permission denied for relation atest5
+UPDATE atest5 SET one = 1; -- fail
+ERROR:  permission denied for relation atest5
+SELECT atest6 FROM atest6; -- ok
+ atest6 
+--------
+(0 rows)
+
+-- test column-level privileges when involved with DELETE
+SET SESSION AUTHORIZATION regressuser1;
+ALTER TABLE atest6 ADD COLUMN three integer;
+GRANT DELETE ON atest5 TO regressuser3;
+GRANT SELECT (two) ON atest5 TO regressuser3;
+REVOKE ALL (one) ON atest5 FROM regressuser3;
+GRANT SELECT (one) ON atest5 TO regressuser4;
+SET SESSION AUTHORIZATION regressuser4;
+SELECT atest6 FROM atest6; -- fail
+ERROR:  permission denied for relation atest6
+SELECT one FROM atest5 NATURAL JOIN atest6; -- fail
+ERROR:  permission denied for relation atest5
+SET SESSION AUTHORIZATION regressuser1;
+ALTER TABLE atest6 DROP COLUMN three;
+SET SESSION AUTHORIZATION regressuser4;
+SELECT atest6 FROM atest6; -- ok
+ atest6 
+--------
+(0 rows)
+
+SELECT one FROM atest5 NATURAL JOIN atest6; -- ok
+ one 
+-----
+(0 rows)
+
+SET SESSION AUTHORIZATION regressuser1;
+ALTER TABLE atest6 DROP COLUMN two;
+REVOKE SELECT (one,blue) ON atest6 FROM regressuser4;
+SET SESSION AUTHORIZATION regressuser4;
+SELECT * FROM atest6; -- fail
+ERROR:  permission denied for relation atest6
+SELECT 1 FROM atest6; -- fail
+ERROR:  permission denied for relation atest6
+SET SESSION AUTHORIZATION regressuser3;
+DELETE FROM atest5 WHERE one = 1; -- fail
+ERROR:  permission denied for relation atest5
+DELETE FROM atest5 WHERE two = 2; -- ok
 -- privileges on functions, languages
 -- switch to superuser
 \c -
@@ -642,6 +783,8 @@ DROP TABLE atest1;
 DROP TABLE atest2;
 DROP TABLE atest3;
 DROP TABLE atest4;
+DROP TABLE atest5;
+DROP TABLE atest6;
 DROP GROUP regressgroup1;
 DROP GROUP regressgroup2;
 REVOKE USAGE ON LANGUAGE sql FROM regressuser1;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 63532f7e095..dda20db8556 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -171,6 +171,93 @@ SELECT * FROM atestv4; -- ok (even though regressuser2 cannot access underlying
 SELECT * FROM atest2; -- ok
 SELECT * FROM atestv2; -- fail (even though regressuser2 can access underlying atest2)
 
+-- Test column level permissions
+
+SET SESSION AUTHORIZATION regressuser1;
+CREATE TABLE atest5 (one int, two int, three int);
+CREATE TABLE atest6 (one int, two int, blue int);
+GRANT SELECT (one), INSERT (two), UPDATE (three) ON atest5 TO regressuser4;
+GRANT ALL (one) ON atest5 TO regressuser3;
+
+INSERT INTO atest5 VALUES (1,2,3);
+
+SET SESSION AUTHORIZATION regressuser4;
+SELECT * FROM atest5; -- fail
+SELECT one FROM atest5; -- ok
+SELECT two FROM atest5; -- fail
+SELECT atest5 FROM atest5; -- fail
+SELECT 1 FROM atest5; -- ok
+SELECT 1 FROM atest5 a JOIN atest5 b USING (one); -- ok
+SELECT 1 FROM atest5 a JOIN atest5 b USING (two); -- fail
+SELECT 1 FROM atest5 a NATURAL JOIN atest5 b; -- fail
+SELECT (j.*) IS NULL FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail
+SELECT 1 FROM atest5 WHERE two = 2; -- fail
+SELECT * FROM atest1, atest5; -- fail
+SELECT atest1.* FROM atest1, atest5; -- ok
+SELECT atest1.*,atest5.one FROM atest1, atest5; -- ok
+SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.two); -- fail
+SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.one); -- ok
+SELECT one, two FROM atest5; -- fail
+
+SET SESSION AUTHORIZATION regressuser1;
+GRANT SELECT (one,two) ON atest6 TO regressuser4;
+
+SET SESSION AUTHORIZATION regressuser4;
+SELECT one, two FROM atest5 NATURAL JOIN atest6; -- fail still
+
+SET SESSION AUTHORIZATION regressuser1;
+GRANT SELECT (two) ON atest5 TO regressuser4;
+
+SET SESSION AUTHORIZATION regressuser4;
+SELECT one, two FROM atest5 NATURAL JOIN atest6; -- ok now
+
+-- test column-level privileges for INSERT and UPDATE
+INSERT INTO atest5 (two) VALUES (3); -- ok
+INSERT INTO atest5 (three) VALUES (4); -- fail
+INSERT INTO atest5 VALUES (5,5,5); -- fail
+UPDATE atest5 SET three = 10; -- ok
+UPDATE atest5 SET one = 8; -- fail
+UPDATE atest5 SET three = 5, one = 2; -- fail
+
+SET SESSION AUTHORIZATION regressuser1;
+REVOKE ALL (one) ON atest5 FROM regressuser4;
+GRANT SELECT (one,two,blue) ON atest6 TO regressuser4;
+
+SET SESSION AUTHORIZATION regressuser4;
+SELECT one FROM atest5; -- fail
+UPDATE atest5 SET one = 1; -- fail
+SELECT atest6 FROM atest6; -- ok
+
+-- test column-level privileges when involved with DELETE
+SET SESSION AUTHORIZATION regressuser1;
+ALTER TABLE atest6 ADD COLUMN three integer;
+GRANT DELETE ON atest5 TO regressuser3;
+GRANT SELECT (two) ON atest5 TO regressuser3;
+REVOKE ALL (one) ON atest5 FROM regressuser3;
+GRANT SELECT (one) ON atest5 TO regressuser4;
+
+SET SESSION AUTHORIZATION regressuser4;
+SELECT atest6 FROM atest6; -- fail
+SELECT one FROM atest5 NATURAL JOIN atest6; -- fail
+
+SET SESSION AUTHORIZATION regressuser1;
+ALTER TABLE atest6 DROP COLUMN three;
+
+SET SESSION AUTHORIZATION regressuser4;
+SELECT atest6 FROM atest6; -- ok
+SELECT one FROM atest5 NATURAL JOIN atest6; -- ok
+
+SET SESSION AUTHORIZATION regressuser1;
+ALTER TABLE atest6 DROP COLUMN two;
+REVOKE SELECT (one,blue) ON atest6 FROM regressuser4;
+
+SET SESSION AUTHORIZATION regressuser4;
+SELECT * FROM atest6; -- fail
+SELECT 1 FROM atest6; -- fail
+
+SET SESSION AUTHORIZATION regressuser3;
+DELETE FROM atest5 WHERE one = 1; -- fail
+DELETE FROM atest5 WHERE two = 2; -- ok
 
 -- privileges on functions, languages
 
@@ -369,6 +456,8 @@ DROP TABLE atest1;
 DROP TABLE atest2;
 DROP TABLE atest3;
 DROP TABLE atest4;
+DROP TABLE atest5;
+DROP TABLE atest6;
 
 DROP GROUP regressgroup1;
 DROP GROUP regressgroup2;
-- 
GitLab