ALTER TABLE OWNER must change the ownership of the table's rowtype too.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 4 Aug 2005 01:09:29 +0000 (01:09 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 4 Aug 2005 01:09:29 +0000 (01:09 +0000)
This was not especially critical before, but it is now that we track
ownership dependencies --- the dependency for the rowtype *must* shift
to the new owner.  Spotted by Bernd Helmle.
Also fix a problem introduced by recent change to allow non-superusers
to do ALTER OWNER in some cases: if the table had a toast table, ALTER
OWNER failed *even for superusers*, because the test being applied would
conclude that the new would-be owner had no create rights on pg_toast.
A side-effect of the fix is to disallow changing the ownership of indexes
or toast tables separately from their parent table, which seems a good
idea on the whole.

doc/src/sgml/ref/alter_table.sgml
src/backend/commands/tablecmds.c
src/backend/commands/typecmds.c
src/include/commands/typecmds.h
src/test/regress/expected/dependency.out
src/test/regress/sql/dependency.sql

index 01a3e1e619afb691b440e58bf39027420ce6bc86..57130cb57e1b684a50305ea60be9514175208b25 100644 (file)
@@ -235,7 +235,7 @@ where <replaceable class="PARAMETER">action</replaceable> is one of:
     <term><literal>OWNER</literal></term>
     <listitem>
      <para>
-      This form changes the owner of the table, index, sequence, or view to the
+      This form changes the owner of the table, sequence, or view to the
       specified user.
      </para>
     </listitem>
index 05423c43c09267567f174584d56f344e984fc145..5bf6220a9ccb96bcfb74b8bb673b48acc18d2faf 100644 (file)
@@ -238,7 +238,7 @@ static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                          const char *colName, TypeName *typename);
 static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab);
 static void ATPostAlterTypeParse(char *cmd, List **wqueue);
-static void ATExecChangeOwner(Oid relationOid, Oid newOwnerId);
+static void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing);
 static void change_owner_recurse_to_sequences(Oid relationOid,
                                                                                          Oid newOwnerId);
 static void ATExecClusterOn(Relation rel, const char *indexName);
@@ -2141,7 +2141,8 @@ ATExecCmd(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd)
                        break;
                case AT_ChangeOwner:    /* ALTER OWNER */
                        ATExecChangeOwner(RelationGetRelid(rel),
-                                                         get_roleid_checked(cmd->name));
+                                                         get_roleid_checked(cmd->name),
+                                                         false);
                        break;
                case AT_ClusterOn:              /* CLUSTER ON */
                        ATExecClusterOn(rel, cmd->name);
@@ -5238,9 +5239,15 @@ ATPostAlterTypeParse(char *cmd, List **wqueue)
 
 /*
  * ALTER TABLE OWNER
+ *
+ * recursing is true if we are recursing from a table to its indexes or
+ * toast table.  We don't allow the ownership of those things to be
+ * changed separately from the parent table.  Also, we can skip permission
+ * checks (this is necessary not just an optimization, else we'd fail to
+ * handle toast tables properly).
  */
 static void
-ATExecChangeOwner(Oid relationOid, Oid newOwnerId)
+ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing)
 {
        Relation        target_rel;
        Relation        class_rel;
@@ -5267,16 +5274,19 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId)
        switch (tuple_class->relkind)
        {
                case RELKIND_RELATION:
-               case RELKIND_INDEX:
                case RELKIND_VIEW:
                case RELKIND_SEQUENCE:
-               case RELKIND_TOASTVALUE:
                        /* ok to change owner */
                        break;
+               case RELKIND_INDEX:
+               case RELKIND_TOASTVALUE:
+                       if (recursing)
+                               break;
+                       /* FALL THRU */
                default:
                        ereport(ERROR,
                                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                                        errmsg("\"%s\" is not a table, TOAST table, index, view, or sequence",
+                                        errmsg("\"%s\" is not a table, view, or sequence",
                                                        NameStr(tuple_class->relname))));
        }
 
@@ -5293,23 +5303,28 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId)
                Datum           aclDatum;
                bool            isNull;
                HeapTuple       newtuple;
-               Oid                 namespaceOid = tuple_class->relnamespace;
-               AclResult       aclresult;
-
-               /* Otherwise, must be owner of the existing object */
-               if (!pg_class_ownercheck(relationOid,GetUserId()))
-                       aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
-                                                  RelationGetRelationName(target_rel));
 
-               /* Must be able to become new owner */
-               check_is_member_of_role(GetUserId(), newOwnerId);
-
-               /* New owner must have CREATE privilege on namespace */
-               aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId,
-                                                                                 ACL_CREATE);
-               if (aclresult != ACLCHECK_OK)
-                       aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
-                                                  get_namespace_name(namespaceOid));
+               /* skip permission checks when recursing to index or toast table */
+               if (!recursing)
+               {
+                       Oid                 namespaceOid = tuple_class->relnamespace;
+                       AclResult       aclresult;
+
+                       /* Otherwise, must be owner of the existing object */
+                       if (!pg_class_ownercheck(relationOid,GetUserId()))
+                               aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
+                                                          RelationGetRelationName(target_rel));
+
+                       /* Must be able to become new owner */
+                       check_is_member_of_role(GetUserId(), newOwnerId);
+
+                       /* New owner must have CREATE privilege on namespace */
+                       aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId,
+                                                                                         ACL_CREATE);
+                       if (aclresult != ACLCHECK_OK)
+                               aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+                                                          get_namespace_name(namespaceOid));
+               }
 
                memset(repl_null, ' ', sizeof(repl_null));
                memset(repl_repl, ' ', sizeof(repl_repl));
@@ -5342,6 +5357,12 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId)
                /* Update owner dependency reference */
                changeDependencyOnOwner(RelationRelationId, relationOid, newOwnerId);
 
+               /*
+                * Also change the ownership of the table's rowtype, if it has one
+                */
+               if (tuple_class->relkind != RELKIND_INDEX)
+                       AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId);
+
                /*
                 * If we are operating on a table, also change the ownership of
                 * any indexes and sequences that belong to the table, as well as
@@ -5358,7 +5379,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId)
 
                        /* For each index, recursively change its ownership */
                        foreach(i, index_oid_list)
-                               ATExecChangeOwner(lfirst_oid(i), newOwnerId);
+                               ATExecChangeOwner(lfirst_oid(i), newOwnerId, true);
 
                        list_free(index_oid_list);
                }
@@ -5367,7 +5388,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId)
                {
                        /* If it has a toast table, recurse to change its ownership */
                        if (tuple_class->reltoastrelid != InvalidOid)
-                               ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId);
+                               ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerId,
+                                                                 true);
 
                        /* If it has dependent sequences, recurse to change them too */
                        change_owner_recurse_to_sequences(relationOid, newOwnerId);
@@ -5437,7 +5459,7 @@ change_owner_recurse_to_sequences(Oid relationOid, Oid newOwnerId)
                }
 
                /* We don't need to close the sequence while we alter it. */
-               ATExecChangeOwner(depForm->objid, newOwnerId);
+               ATExecChangeOwner(depForm->objid, newOwnerId, false);
 
                /* Now we can close it.  Keep the lock till end of transaction. */
                relation_close(seqRel, NoLock);
index 02574de18515165a1c193b7a001afc90932a554d..ec48347784f3c2e1cdded86332c02f3983e5d6f8 100644 (file)
@@ -2057,7 +2057,8 @@ AlterTypeOwner(List *names, Oid newOwnerId)
         * free-standing composite type, and not a table's underlying type. We
         * want people to use ALTER TABLE not ALTER TYPE for that case.
         */
-       if (typTup->typtype == 'c' && get_rel_relkind(typTup->typrelid) != 'c')
+       if (typTup->typtype == 'c' &&
+               get_rel_relkind(typTup->typrelid) != RELKIND_COMPOSITE_TYPE)
                ereport(ERROR,
                                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                                 errmsg("\"%s\" is a table's row type",
@@ -2102,6 +2103,45 @@ AlterTypeOwner(List *names, Oid newOwnerId)
        heap_close(rel, RowExclusiveLock);
 }
 
+/*
+ * AlterTypeOwnerInternal - change type owner unconditionally
+ *
+ * This is currently only used to propagate ALTER TABLE OWNER to the
+ * table's rowtype.  It assumes the caller has done all needed checks.
+ */
+void
+AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId)
+{
+       Relation        rel;
+       HeapTuple       tup;
+       Form_pg_type typTup;
+
+       rel = heap_open(TypeRelationId, RowExclusiveLock);
+
+       tup = SearchSysCacheCopy(TYPEOID,
+                                                        ObjectIdGetDatum(typeOid),
+                                                        0, 0, 0);
+       if (!HeapTupleIsValid(tup))
+               elog(ERROR, "cache lookup failed for type %u", typeOid);
+       typTup = (Form_pg_type) GETSTRUCT(tup);
+
+       /*
+        * Modify the owner --- okay to scribble on typTup because it's a
+        * copy
+        */
+       typTup->typowner = newOwnerId;
+
+       simple_heap_update(rel, &tup->t_self, tup);
+
+       CatalogUpdateIndexes(rel, tup);
+
+       /* Update owner dependency reference */
+       changeDependencyOnOwner(TypeRelationId, typeOid, newOwnerId);
+
+       /* Clean up */
+       heap_close(rel, RowExclusiveLock);
+}
+
 /*
  * Execute ALTER TYPE SET SCHEMA
  */
index 283ce9851d0dd1f69096945042cc0f3cd2d13652..ef3c7c98d8307d38d46be276cddd6b041335558b 100644 (file)
@@ -35,6 +35,7 @@ extern void AlterDomainDropConstraint(List *names, const char *constrName,
 extern List *GetDomainConstraints(Oid typeOid);
 
 extern void AlterTypeOwner(List *names, Oid newOwnerId);
+extern void AlterTypeOwnerInternal(Oid typeOid, Oid newOwnerId);
 extern void AlterTypeNamespace(List *names, const char *newschema);
 extern void AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
                                                                           bool errorOnTableType);
index 4ee3e8b6a8ff23c56c1a53817987c8cc1248d7f9..2c31e581bfe84353c63dc1dbf2ec2d0269992f06 100644 (file)
@@ -5,7 +5,9 @@ CREATE USER regression_user;
 CREATE USER regression_user2;
 CREATE USER regression_user3;
 CREATE GROUP regression_group;
-CREATE TABLE deptest ();
+CREATE TABLE deptest (f1 serial primary key, f2 text);
+NOTICE:  CREATE TABLE will create implicit sequence "deptest_f1_seq" for serial column "deptest.f1"
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "deptest_pkey" for table "deptest"
 GRANT SELECT ON TABLE deptest TO GROUP regression_group;
 GRANT ALL ON TABLE deptest TO regression_user, regression_user2;
 -- can't drop neither because they have privileges somewhere
@@ -30,10 +32,12 @@ DROP USER regression_user;
 REVOKE ALL ON deptest FROM regression_user2;
 DROP USER regression_user2;
 -- can't drop the owner of an object
+-- the error message detail here would include a pg_toast_nnn name that
+-- is not constant, so suppress it
+\set VERBOSITY terse
 ALTER TABLE deptest OWNER TO regression_user3;
 DROP USER regression_user3;
 ERROR:  role "regression_user3" cannot be dropped because some objects depend on it
-DETAIL:  owner of table deptest
 -- if we drop the object, we can drop the user too
 DROP TABLE deptest;
 DROP USER regression_user3;
index 6d52b62dee188037b4f4d578cb7f5490712bd36f..3e4a232ea716654908b9e0d9eb1f50c250c92644 100644 (file)
@@ -7,7 +7,7 @@ CREATE USER regression_user2;
 CREATE USER regression_user3;
 CREATE GROUP regression_group;
 
-CREATE TABLE deptest ();
+CREATE TABLE deptest (f1 serial primary key, f2 text);
 
 GRANT SELECT ON TABLE deptest TO GROUP regression_group;
 GRANT ALL ON TABLE deptest TO regression_user, regression_user2;
@@ -33,6 +33,9 @@ REVOKE ALL ON deptest FROM regression_user2;
 DROP USER regression_user2;
 
 -- can't drop the owner of an object
+-- the error message detail here would include a pg_toast_nnn name that
+-- is not constant, so suppress it
+\set VERBOSITY terse
 ALTER TABLE deptest OWNER TO regression_user3;
 DROP USER regression_user3;