Re: DROP OWNED BY fails to clean out pg_init_privs grants - Mailing list pgsql-hackers

From Tom Lane
Subject Re: DROP OWNED BY fails to clean out pg_init_privs grants
Date
Msg-id 772627.1714330360@sss.pgh.pa.us
Whole thread Raw
In response to Re: DROP OWNED BY fails to clean out pg_init_privs grants  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: DROP OWNED BY fails to clean out pg_init_privs grants
List pgsql-hackers
I wrote:
> Here's a draft patch that attacks that.  It seems to fix the
> problem with test_pg_dump: no dangling pg_init_privs grants
> are left behind.

Here's a v2 that attempts to add some queries to test_pg_dump.sql
to provide visual verification that pg_shdepend and pg_init_privs
are updated correctly during DROP OWNED BY.  It's a little bit
nasty to look at the ACL column of pg_init_privs, because that text
involves the bootstrap superuser's name which is site-dependent.
What I did to try to make the test stable is

  replace(initprivs::text, current_user, 'postgres') AS initprivs

This is of course not bulletproof: with a sufficiently weird
bootstrap superuser name, we could get false matches to parts
of "regress_dump_test_role" or to privilege strings.  That
seems unlikely enough to live with, but I wonder if anybody has
a better idea.

            regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2907079e2a..c8cb46c5b9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7184,6 +7184,20 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      </listitem>
     </varlistentry>

+    <varlistentry>
+     <term><symbol>SHARED_DEPENDENCY_INITACL</symbol> (<literal>i</literal>)</term>
+     <listitem>
+      <para>
+       The referenced object (which must be a role) is mentioned in a
+       <link linkend="catalog-pg-init-privs"><structname>pg_init_privs</structname></link>
+       entry for the dependent object.
+       (A <symbol>SHARED_DEPENDENCY_INITACL</symbol> entry is not made for
+       the owner of the object, since the owner will have
+       a <symbol>SHARED_DEPENDENCY_OWNER</symbol> entry anyway.)
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><symbol>SHARED_DEPENDENCY_POLICY</symbol> (<literal>r</literal>)</term>
      <listitem>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 7abf3c2a74..04c41c0c14 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -165,9 +165,9 @@ static AclMode pg_type_aclmask_ext(Oid type_oid, Oid roleid,
                                    AclMode mask, AclMaskHow how,
                                    bool *is_missing);
 static void recordExtensionInitPriv(Oid objoid, Oid classoid, int objsubid,
-                                    Acl *new_acl);
+                                    Oid ownerId, Acl *new_acl);
 static void recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid,
-                                          Acl *new_acl);
+                                          Oid ownerId, Acl *new_acl);


 /*
@@ -1790,7 +1790,7 @@ ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname,
         CatalogTupleUpdate(attRelation, &newtuple->t_self, newtuple);

         /* Update initial privileges for extensions */
-        recordExtensionInitPriv(relOid, RelationRelationId, attnum,
+        recordExtensionInitPriv(relOid, RelationRelationId, attnum, ownerId,
                                 ACL_NUM(new_acl) > 0 ? new_acl : NULL);

         /* Update the shared dependency ACL info */
@@ -2050,7 +2050,8 @@ ExecGrant_Relation(InternalGrant *istmt)
             CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);

             /* Update initial privileges for extensions */
-            recordExtensionInitPriv(relOid, RelationRelationId, 0, new_acl);
+            recordExtensionInitPriv(relOid, RelationRelationId, 0,
+                                    ownerId, new_acl);

             /* Update the shared dependency ACL info */
             updateAclDependencies(RelationRelationId, relOid, 0,
@@ -2251,7 +2252,7 @@ ExecGrant_common(InternalGrant *istmt, Oid classid, AclMode default_privs,
         CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);

         /* Update initial privileges for extensions */
-        recordExtensionInitPriv(objectid, classid, 0, new_acl);
+        recordExtensionInitPriv(objectid, classid, 0, ownerId, new_acl);

         /* Update the shared dependency ACL info */
         updateAclDependencies(classid,
@@ -2403,7 +2404,8 @@ ExecGrant_Largeobject(InternalGrant *istmt)
         CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);

         /* Update initial privileges for extensions */
-        recordExtensionInitPriv(loid, LargeObjectRelationId, 0, new_acl);
+        recordExtensionInitPriv(loid, LargeObjectRelationId, 0,
+                                ownerId, new_acl);

         /* Update the shared dependency ACL info */
         updateAclDependencies(LargeObjectRelationId,
@@ -2575,7 +2577,7 @@ ExecGrant_Parameter(InternalGrant *istmt)

         /* Update initial privileges for extensions */
         recordExtensionInitPriv(parameterId, ParameterAclRelationId, 0,
-                                new_acl);
+                                ownerId, new_acl);

         /* Update the shared dependency ACL info */
         updateAclDependencies(ParameterAclRelationId, parameterId, 0,
@@ -4463,6 +4465,7 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
                 }

                 recordExtensionInitPrivWorker(objoid, classoid, curr_att,
+                                              pg_class_tuple->relowner,
                                               DatumGetAclP(attaclDatum));

                 ReleaseSysCache(attTuple);
@@ -4475,6 +4478,7 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
         /* Add the record, if any, for the top-level object */
         if (!isNull)
             recordExtensionInitPrivWorker(objoid, classoid, 0,
+                                          pg_class_tuple->relowner,
                                           DatumGetAclP(aclDatum));

         ReleaseSysCache(tuple);
@@ -4485,6 +4489,7 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
         Datum        aclDatum;
         bool        isNull;
         HeapTuple    tuple;
+        Form_pg_largeobject_metadata form_lo_meta;
         ScanKeyData entry[1];
         SysScanDesc scan;
         Relation    relation;
@@ -4509,6 +4514,7 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
         tuple = systable_getnext(scan);
         if (!HeapTupleIsValid(tuple))
             elog(ERROR, "could not find tuple for large object %u", objoid);
+        form_lo_meta = (Form_pg_largeobject_metadata) GETSTRUCT(tuple);

         aclDatum = heap_getattr(tuple,
                                 Anum_pg_largeobject_metadata_lomacl,
@@ -4517,6 +4523,7 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
         /* Add the record, if any, for the top-level object */
         if (!isNull)
             recordExtensionInitPrivWorker(objoid, classoid, 0,
+                                          form_lo_meta->lomowner,
                                           DatumGetAclP(aclDatum));

         systable_endscan(scan);
@@ -4524,24 +4531,29 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
     /* This will error on unsupported classoid. */
     else if (get_object_attnum_acl(classoid) != InvalidAttrNumber)
     {
+        int            cacheid;
+        Oid            ownerId;
         Datum        aclDatum;
         bool        isNull;
         HeapTuple    tuple;

-        tuple = SearchSysCache1(get_object_catcache_oid(classoid),
-                                ObjectIdGetDatum(objoid));
+        cacheid = get_object_catcache_oid(classoid);
+        tuple = SearchSysCache1(cacheid, ObjectIdGetDatum(objoid));
         if (!HeapTupleIsValid(tuple))
             elog(ERROR, "cache lookup failed for %s %u",
                  get_object_class_descr(classoid), objoid);

-        aclDatum = SysCacheGetAttr(get_object_catcache_oid(classoid), tuple,
+        ownerId = DatumGetObjectId(SysCacheGetAttrNotNull(cacheid,
+                                                          tuple,
+                                                          get_object_attnum_owner(classoid)));
+        aclDatum = SysCacheGetAttr(cacheid, tuple,
                                    get_object_attnum_acl(classoid),
                                    &isNull);

         /* Add the record, if any, for the top-level object */
         if (!isNull)
             recordExtensionInitPrivWorker(objoid, classoid, 0,
-                                          DatumGetAclP(aclDatum));
+                                          ownerId, DatumGetAclP(aclDatum));

         ReleaseSysCache(tuple);
     }
@@ -4554,6 +4566,8 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
 void
 removeExtObjInitPriv(Oid objoid, Oid classoid)
 {
+    Oid            ownerId;
+
     /*
      * If this is a relation then we need to see if there are any sub-objects
      * (eg: columns) for it and, if so, be sure to call
@@ -4568,6 +4582,7 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)
         if (!HeapTupleIsValid(tuple))
             elog(ERROR, "cache lookup failed for relation %u", objoid);
         pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
+        ownerId = pg_class_tuple->relowner;

         /*
          * Indexes don't have permissions, neither do the pg_class rows for
@@ -4604,7 +4619,8 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)

                 /* when removing, remove all entries, even dropped columns */

-                recordExtensionInitPrivWorker(objoid, classoid, curr_att, NULL);
+                recordExtensionInitPrivWorker(objoid, classoid, curr_att,
+                                              ownerId, NULL);

                 ReleaseSysCache(attTuple);
             }
@@ -4612,9 +4628,35 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)

         ReleaseSysCache(tuple);
     }
+    else
+    {
+        /* Must find out the owner's OID the hard way */
+        AttrNumber    ownerattnum;
+        int            cacheid;
+        HeapTuple    tuple;
+
+        /*
+         * If the object is of a type that has no owner, it should not have
+         * any pg_init_privs entry either.
+         */
+        ownerattnum = get_object_attnum_owner(classoid);
+        if (ownerattnum == InvalidAttrNumber)
+            return;
+        cacheid = get_object_catcache_oid(classoid);
+        tuple = SearchSysCache1(cacheid, ObjectIdGetDatum(objoid));
+        if (!HeapTupleIsValid(tuple))
+            elog(ERROR, "cache lookup failed for %s %u",
+                 get_object_class_descr(classoid), objoid);
+
+        ownerId = DatumGetObjectId(SysCacheGetAttrNotNull(cacheid,
+                                                          tuple,
+                                                          ownerattnum));
+
+        ReleaseSysCache(tuple);
+    }

     /* Remove the record, if any, for the top-level object */
-    recordExtensionInitPrivWorker(objoid, classoid, 0, NULL);
+    recordExtensionInitPrivWorker(objoid, classoid, 0, ownerId, NULL);
 }

 /*
@@ -4626,7 +4668,8 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)
  * Pass in the object OID, the OID of the class (the OID of the table which
  * the object is defined in) and the 'sub' id of the object (objsubid), if
  * any.  If there is no 'sub' id (they are currently only used for columns of
- * tables) then pass in '0'.  Finally, pass in the complete ACL to store.
+ * tables) then pass in '0'.  Also pass the OID of the object's owner.
+ * Finally, pass in the complete ACL to store.
  *
  * If an ACL already exists for this object/sub-object then we will replace
  * it with what is passed in.
@@ -4635,7 +4678,8 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)
  * removed, if one is found.
  */
 static void
-recordExtensionInitPriv(Oid objoid, Oid classoid, int objsubid, Acl *new_acl)
+recordExtensionInitPriv(Oid objoid, Oid classoid, int objsubid,
+                        Oid ownerId, Acl *new_acl)
 {
     /*
      * Generally, we only record the initial privileges when an extension is
@@ -4648,7 +4692,7 @@ recordExtensionInitPriv(Oid objoid, Oid classoid, int objsubid, Acl *new_acl)
     if (!creating_extension && !binary_upgrade_record_init_privs)
         return;

-    recordExtensionInitPrivWorker(objoid, classoid, objsubid, new_acl);
+    recordExtensionInitPrivWorker(objoid, classoid, objsubid, ownerId, new_acl);
 }

 /*
@@ -4664,14 +4708,23 @@ recordExtensionInitPriv(Oid objoid, Oid classoid, int objsubid, Acl *new_acl)
  * EXTENSION ... ADD/DROP.
  */
 static void
-recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid, Acl *new_acl)
+recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid,
+                              Oid ownerId, Acl *new_acl)
 {
     Relation    relation;
     ScanKeyData key[3];
     SysScanDesc scan;
     HeapTuple    tuple;
     HeapTuple    oldtuple;
+    int            noldmembers;
+    int            nnewmembers;
+    Oid           *oldmembers;
+    Oid           *newmembers;

+    /* We'll need the role membership of the new ACL. */
+    nnewmembers = aclmembers(new_acl, &newmembers);
+
+    /* Search pg_init_privs for an existing entry. */
     relation = table_open(InitPrivsRelationId, RowExclusiveLock);

     ScanKeyInit(&key[0],
@@ -4699,6 +4752,23 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid, Acl *new_a
         Datum        values[Natts_pg_init_privs] = {0};
         bool        nulls[Natts_pg_init_privs] = {0};
         bool        replace[Natts_pg_init_privs] = {0};
+        Datum        oldAclDatum;
+        bool        isNull;
+        Acl           *old_acl;
+
+        /* Update pg_shdepend for roles mentioned in the old/new ACLs. */
+        oldAclDatum = heap_getattr(oldtuple, Anum_pg_init_privs_initprivs,
+                                   RelationGetDescr(relation), &isNull);
+        if (!isNull)
+            old_acl = DatumGetAclP(oldAclDatum);
+        else
+            old_acl = NULL;        /* this case shouldn't happen, probably */
+        noldmembers = aclmembers(old_acl, &oldmembers);
+
+        updateInitAclDependencies(classoid, objoid, objsubid,
+                                  ownerId,
+                                  noldmembers, oldmembers,
+                                  nnewmembers, newmembers);

         /* If we have a new ACL to set, then update the row with it. */
         if (new_acl)
@@ -4744,6 +4814,15 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid, Acl *new_a
             tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);

             CatalogTupleInsert(relation, tuple);
+
+            /* Update pg_shdepend, too. */
+            noldmembers = 0;
+            oldmembers = NULL;
+
+            updateInitAclDependencies(classoid, objoid, objsubid,
+                                      ownerId,
+                                      noldmembers, oldmembers,
+                                      nnewmembers, newmembers);
         }
     }

@@ -4754,3 +4833,138 @@ recordExtensionInitPrivWorker(Oid objoid, Oid classoid, int objsubid, Acl *new_a

     table_close(relation, RowExclusiveLock);
 }
+
+/*
+ * RemoveRoleFromInitPriv
+ *
+ * Used by shdepDropOwned to remove mentions of a role in pg_init_privs.
+ */
+void
+RemoveRoleFromInitPriv(Oid roleid, Oid classid, Oid objid, int32 objsubid)
+{
+    Relation    rel;
+    ScanKeyData key[3];
+    SysScanDesc scan;
+    HeapTuple    oldtuple;
+    int            cacheid;
+    HeapTuple    objtuple;
+    Oid            ownerId;
+    Datum        oldAclDatum;
+    bool        isNull;
+    Acl           *old_acl;
+    Acl           *new_acl;
+    HeapTuple    newtuple;
+    int            noldmembers;
+    int            nnewmembers;
+    Oid           *oldmembers;
+    Oid           *newmembers;
+
+    /* Search for existing pg_init_privs entry for the target object. */
+    rel = table_open(InitPrivsRelationId, RowExclusiveLock);
+
+    ScanKeyInit(&key[0],
+                Anum_pg_init_privs_objoid,
+                BTEqualStrategyNumber, F_OIDEQ,
+                ObjectIdGetDatum(objid));
+    ScanKeyInit(&key[1],
+                Anum_pg_init_privs_classoid,
+                BTEqualStrategyNumber, F_OIDEQ,
+                ObjectIdGetDatum(classid));
+    ScanKeyInit(&key[2],
+                Anum_pg_init_privs_objsubid,
+                BTEqualStrategyNumber, F_INT4EQ,
+                Int32GetDatum(objsubid));
+
+    scan = systable_beginscan(rel, InitPrivsObjIndexId, true,
+                              NULL, 3, key);
+
+    /* There should exist only one entry or none. */
+    oldtuple = systable_getnext(scan);
+
+    if (!HeapTupleIsValid(oldtuple))
+    {
+        /*
+         * Hmm, why are we here if there's no entry?  But pack up and go away
+         * quietly.
+         */
+        systable_endscan(scan);
+        table_close(rel, RowExclusiveLock);
+        return;
+    }
+
+    /* Get a writable copy of the existing ACL. */
+    oldAclDatum = heap_getattr(oldtuple, Anum_pg_init_privs_initprivs,
+                               RelationGetDescr(rel), &isNull);
+    if (!isNull)
+        old_acl = DatumGetAclPCopy(oldAclDatum);
+    else
+        old_acl = NULL;            /* this case shouldn't happen, probably */
+
+    /*
+     * We need the members of both old and new ACLs so we can correct the
+     * shared dependency information.  Collect data before
+     * merge_acl_with_grant throws away old_acl.
+     */
+    noldmembers = aclmembers(old_acl, &oldmembers);
+
+    /* Must find out the owner's OID the hard way. */
+    cacheid = get_object_catcache_oid(classid);
+    objtuple = SearchSysCache1(cacheid, ObjectIdGetDatum(objid));
+    if (!HeapTupleIsValid(objtuple))
+        elog(ERROR, "cache lookup failed for %s %u",
+             get_object_class_descr(classid), objid);
+
+    ownerId = DatumGetObjectId(SysCacheGetAttrNotNull(cacheid,
+                                                      objtuple,
+                                                      get_object_attnum_owner(classid)));
+    ReleaseSysCache(objtuple);
+
+    /*
+     * Generate new ACL.  Grantor of rights is always the same as the owner.
+     */
+    new_acl = merge_acl_with_grant(old_acl,
+                                   false,    /* is_grant */
+                                   false,    /* grant_option */
+                                   DROP_RESTRICT,
+                                   list_make1_oid(roleid),
+                                   ACLITEM_ALL_PRIV_BITS,
+                                   ownerId,
+                                   ownerId);
+
+    /* If we end with an empty ACL, delete the pg_init_privs entry. */
+    if (new_acl == NULL || ACL_NUM(new_acl) == 0)
+    {
+        CatalogTupleDelete(rel, &oldtuple->t_self);
+    }
+    else
+    {
+        Datum        values[Natts_pg_init_privs] = {0};
+        bool        nulls[Natts_pg_init_privs] = {0};
+        bool        replaces[Natts_pg_init_privs] = {0};
+
+        /* Update existing entry. */
+        values[Anum_pg_init_privs_initprivs - 1] = PointerGetDatum(new_acl);
+        replaces[Anum_pg_init_privs_initprivs - 1] = true;
+
+        newtuple = heap_modify_tuple(oldtuple, RelationGetDescr(rel),
+                                     values, nulls, replaces);
+        CatalogTupleUpdate(rel, &newtuple->t_self, newtuple);
+    }
+
+    /*
+     * Update the shared dependency ACL info.
+     */
+    nnewmembers = aclmembers(new_acl, &newmembers);
+
+    updateInitAclDependencies(classid, objid, objsubid,
+                              ownerId,
+                              noldmembers, oldmembers,
+                              nnewmembers, newmembers);
+
+    systable_endscan(scan);
+
+    /* prevent error when processing objects multiple times */
+    CommandCounterIncrement();
+
+    table_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index cb31590339..20bcfd779b 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -84,6 +84,11 @@ static void shdepChangeDep(Relation sdepRel,
                            Oid classid, Oid objid, int32 objsubid,
                            Oid refclassid, Oid refobjid,
                            SharedDependencyType deptype);
+static void updateAclDependenciesWorker(Oid classId, Oid objectId,
+                                        int32 objsubId, Oid ownerId,
+                                        SharedDependencyType deptype,
+                                        int noldmembers, Oid *oldmembers,
+                                        int nnewmembers, Oid *newmembers);
 static void shdepAddDependency(Relation sdepRel,
                                Oid classId, Oid objectId, int32 objsubId,
                                Oid refclassId, Oid refobjId,
@@ -340,6 +345,11 @@ changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
                         AuthIdRelationId, newOwnerId,
                         SHARED_DEPENDENCY_ACL);

+    /* The same applies to SHARED_DEPENDENCY_INITACL */
+    shdepDropDependency(sdepRel, classId, objectId, 0, true,
+                        AuthIdRelationId, newOwnerId,
+                        SHARED_DEPENDENCY_INITACL);
+
     table_close(sdepRel, RowExclusiveLock);
 }

@@ -478,6 +488,38 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
                       Oid ownerId,
                       int noldmembers, Oid *oldmembers,
                       int nnewmembers, Oid *newmembers)
+{
+    updateAclDependenciesWorker(classId, objectId, objsubId,
+                                ownerId, SHARED_DEPENDENCY_ACL,
+                                noldmembers, oldmembers,
+                                nnewmembers, newmembers);
+}
+
+/*
+ * updateInitAclDependencies
+ *        Update the pg_shdepend info for a pg_init_privs entry.
+ *
+ * Exactly like updateAclDependencies, except we are considering a
+ * pg_init_privs ACL for the specified object.
+ */
+void
+updateInitAclDependencies(Oid classId, Oid objectId, int32 objsubId,
+                          Oid ownerId,
+                          int noldmembers, Oid *oldmembers,
+                          int nnewmembers, Oid *newmembers)
+{
+    updateAclDependenciesWorker(classId, objectId, objsubId,
+                                ownerId, SHARED_DEPENDENCY_INITACL,
+                                noldmembers, oldmembers,
+                                nnewmembers, newmembers);
+}
+
+/* Common code for the above two functions */
+static void
+updateAclDependenciesWorker(Oid classId, Oid objectId, int32 objsubId,
+                            Oid ownerId, SharedDependencyType deptype,
+                            int noldmembers, Oid *oldmembers,
+                            int nnewmembers, Oid *newmembers)
 {
     Relation    sdepRel;
     int            i;
@@ -513,7 +555,7 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,

             shdepAddDependency(sdepRel, classId, objectId, objsubId,
                                AuthIdRelationId, roleid,
-                               SHARED_DEPENDENCY_ACL);
+                               deptype);
         }

         /* Drop no-longer-used old dependencies */
@@ -532,7 +574,7 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
             shdepDropDependency(sdepRel, classId, objectId, objsubId,
                                 false,    /* exact match on objsubId */
                                 AuthIdRelationId, roleid,
-                                SHARED_DEPENDENCY_ACL);
+                                deptype);
         }

         table_close(sdepRel, RowExclusiveLock);
@@ -1249,6 +1291,8 @@ storeObjectDescription(StringInfo descs,
                 appendStringInfo(descs, _("owner of %s"), objdesc);
             else if (deptype == SHARED_DEPENDENCY_ACL)
                 appendStringInfo(descs, _("privileges for %s"), objdesc);
+            else if (deptype == SHARED_DEPENDENCY_INITACL)
+                appendStringInfo(descs, _("initial privileges for %s"), objdesc);
             else if (deptype == SHARED_DEPENDENCY_POLICY)
                 appendStringInfo(descs, _("target of %s"), objdesc);
             else if (deptype == SHARED_DEPENDENCY_TABLESPACE)
@@ -1431,6 +1475,14 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
                         add_exact_object_address(&obj, deleteobjs);
                     }
                     break;
+                case SHARED_DEPENDENCY_INITACL:
+                    /* Shouldn't see a role grant here */
+                    Assert(sdepForm->classid != AuthMemRelationId);
+                    RemoveRoleFromInitPriv(roleid,
+                                           sdepForm->classid,
+                                           sdepForm->objid,
+                                           sdepForm->objsubid);
+                    break;
             }
         }

diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index ec654010d4..e0dcc0b069 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -56,11 +56,17 @@ typedef enum DependencyType
  * created for the owner of an object; hence two objects may be linked by
  * one or the other, but not both, of these dependency types.)
  *
- * (c) a SHARED_DEPENDENCY_POLICY entry means that the referenced object is
+ * (c) a SHARED_DEPENDENCY_INITACL entry means that the referenced object is
+ * a role mentioned in a pg_init_privs entry for the dependent object.  The
+ * referenced object must be a pg_authid entry.  (SHARED_DEPENDENCY_INITACL
+ * entries are not created for the owner of an object; hence two objects may
+ * be linked by one or the other, but not both, of these dependency types.)
+ *
+ * (d) a SHARED_DEPENDENCY_POLICY entry means that the referenced object is
  * a role mentioned in a policy object.  The referenced object must be a
  * pg_authid entry.
  *
- * (d) a SHARED_DEPENDENCY_TABLESPACE entry means that the referenced
+ * (e) a SHARED_DEPENDENCY_TABLESPACE entry means that the referenced
  * object is a tablespace mentioned in a relation without storage.  The
  * referenced object must be a pg_tablespace entry.  (Relations that have
  * storage don't need this: they are protected by the existence of a physical
@@ -73,6 +79,7 @@ typedef enum SharedDependencyType
 {
     SHARED_DEPENDENCY_OWNER = 'o',
     SHARED_DEPENDENCY_ACL = 'a',
+    SHARED_DEPENDENCY_INITACL = 'i',
     SHARED_DEPENDENCY_POLICY = 'r',
     SHARED_DEPENDENCY_TABLESPACE = 't',
     SHARED_DEPENDENCY_INVALID = 0,
@@ -201,6 +208,11 @@ extern void updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
                                   int noldmembers, Oid *oldmembers,
                                   int nnewmembers, Oid *newmembers);

+extern void updateInitAclDependencies(Oid classId, Oid objectId, int32 objsubId,
+                                      Oid ownerId,
+                                      int noldmembers, Oid *oldmembers,
+                                      int nnewmembers, Oid *newmembers);
+
 extern bool checkSharedDependencies(Oid classId, Oid objectId,
                                     char **detail_msg, char **detail_log_msg);

diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 3a0baf3039..1a554c6699 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -276,6 +276,8 @@ extern void aclcheck_error_type(AclResult aclerr, Oid typeOid);

 extern void recordExtObjInitPriv(Oid objoid, Oid classoid);
 extern void removeExtObjInitPriv(Oid objoid, Oid classoid);
+extern void RemoveRoleFromInitPriv(Oid roleid,
+                                   Oid classid, Oid objid, int32 objsubid);


 /* ownercheck routines just return true (owner) or false (not) */
diff --git a/src/test/modules/test_pg_dump/expected/test_pg_dump.out
b/src/test/modules/test_pg_dump/expected/test_pg_dump.out
index f57c96aeb9..be71822ece 100644
--- a/src/test/modules/test_pg_dump/expected/test_pg_dump.out
+++ b/src/test/modules/test_pg_dump/expected/test_pg_dump.out
@@ -62,6 +62,59 @@ GRANT SELECT ON ft1 TO regress_dump_test_role;
 GRANT UPDATE ON test_pg_dump_mv1 TO regress_dump_test_role;
 GRANT USAGE ON SCHEMA test_pg_dump_s1 TO regress_dump_test_role;
 GRANT USAGE ON TYPE test_pg_dump_e1 TO regress_dump_test_role;
+SELECT pg_describe_object(classoid,objoid,objsubid) AS obj,
+  replace(initprivs::text, current_user, 'postgres') AS initprivs
+  FROM pg_init_privs WHERE privtype = 'e' ORDER BY 1;
+                        obj                         |                              initprivs
   

+----------------------------------------------------+---------------------------------------------------------------------
+ column col1 of table regress_pg_dump_table         | {=r/postgres}
+ function regress_pg_dump_schema.test_agg(smallint) |
{=X/postgres,postgres=X/postgres,regress_dump_test_role=X/postgres}
+ function regress_pg_dump_schema.test_func()        |
{=X/postgres,postgres=X/postgres,regress_dump_test_role=X/postgres}
+ function wgo_then_no_access()                      | {=X/postgres,postgres=X/postgres,pg_signal_backend=X*/postgres}
+ sequence regress_pg_dump_schema.test_seq           | {postgres=rwU/postgres,regress_dump_test_role=U/postgres}
+ sequence regress_pg_dump_seq                       | {postgres=rwU/postgres,regress_dump_test_role=U/postgres}
+ sequence regress_seq_dumpable                      | {postgres=rwU/postgres,=r/postgres}
+ sequence wgo_then_regular                          | {postgres=rwU/postgres,pg_signal_backend=rw*U*/postgres}
+ table regress_pg_dump_schema.test_table            | {postgres=arwdDxtm/postgres,regress_dump_test_role=r/postgres}
+ table regress_pg_dump_table                        | {postgres=arwdDxtm/postgres,regress_dump_test_role=r/postgres}
+ table regress_table_dumpable                       | {postgres=arwdDxtm/postgres,=r/postgres}
+ type regress_pg_dump_schema.test_type              |
{=U/postgres,postgres=U/postgres,regress_dump_test_role=U/postgres}
+(12 rows)
+
+SELECT pg_describe_object(classid,objid,objsubid) AS obj,
+  pg_describe_object(refclassid,refobjid,0) AS refobj,
+  deptype
+  FROM pg_shdepend JOIN pg_database d ON dbid = d.oid
+  WHERE d.datname = current_database()
+  ORDER BY 1, 3;
+                        obj                         |           refobj            | deptype
+----------------------------------------------------+-----------------------------+---------
+ column c1 of foreign table ft1                     | role regress_dump_test_role | a
+ column c1 of table test_pg_dump_t1                 | role regress_dump_test_role | a
+ foreign table ft1                                  | role regress_dump_test_role | a
+ foreign-data wrapper dummy                         | role regress_dump_test_role | a
+ function regress_pg_dump_schema.test_agg(smallint) | role regress_dump_test_role | a
+ function regress_pg_dump_schema.test_agg(smallint) | role regress_dump_test_role | i
+ function regress_pg_dump_schema.test_func()        | role regress_dump_test_role | a
+ function regress_pg_dump_schema.test_func()        | role regress_dump_test_role | i
+ function test_pg_dump(integer)                     | role regress_dump_test_role | a
+ materialized view test_pg_dump_mv1                 | role regress_dump_test_role | a
+ schema test_pg_dump_s1                             | role regress_dump_test_role | a
+ sequence regress_pg_dump_schema.test_seq           | role regress_dump_test_role | a
+ sequence regress_pg_dump_schema.test_seq           | role regress_dump_test_role | i
+ sequence regress_pg_dump_seq                       | role regress_dump_test_role | a
+ sequence regress_pg_dump_seq                       | role regress_dump_test_role | i
+ server s0                                          | role regress_dump_test_role | a
+ table regress_pg_dump_schema.test_table            | role regress_dump_test_role | a
+ table regress_pg_dump_schema.test_table            | role regress_dump_test_role | i
+ table regress_pg_dump_table                        | role regress_dump_test_role | a
+ table regress_pg_dump_table                        | role regress_dump_test_role | i
+ type regress_pg_dump_schema.test_type              | role regress_dump_test_role | a
+ type regress_pg_dump_schema.test_type              | role regress_dump_test_role | i
+ type test_pg_dump_e1                               | role regress_dump_test_role | a
+ view test_pg_dump_v1                               | role regress_dump_test_role | a
+(24 rows)
+
 ALTER EXTENSION test_pg_dump ADD ACCESS METHOD gist2;
 ALTER EXTENSION test_pg_dump ADD AGGREGATE newavg(int4);
 ALTER EXTENSION test_pg_dump ADD CAST (text AS casttesttype);
@@ -92,4 +145,33 @@ ALTER EXTENSION test_pg_dump DROP TABLE test_pg_dump_t1;
 ALTER EXTENSION test_pg_dump DROP TYPE test_pg_dump_e1;
 ALTER EXTENSION test_pg_dump DROP VIEW test_pg_dump_v1;
 DROP OWNED BY regress_dump_test_role RESTRICT;
+SELECT pg_describe_object(classoid,objoid,objsubid) AS obj,
+  replace(initprivs::text, current_user, 'postgres') AS initprivs
+  FROM pg_init_privs WHERE privtype = 'e' ORDER BY 1;
+                        obj                         |                            initprivs
+----------------------------------------------------+-----------------------------------------------------------------
+ column col1 of table regress_pg_dump_table         | {=r/postgres}
+ function regress_pg_dump_schema.test_agg(smallint) | {=X/postgres,postgres=X/postgres}
+ function regress_pg_dump_schema.test_func()        | {=X/postgres,postgres=X/postgres}
+ function wgo_then_no_access()                      | {=X/postgres,postgres=X/postgres,pg_signal_backend=X*/postgres}
+ sequence regress_pg_dump_schema.test_seq           | {postgres=rwU/postgres}
+ sequence regress_pg_dump_seq                       | {postgres=rwU/postgres}
+ sequence regress_seq_dumpable                      | {postgres=rwU/postgres,=r/postgres}
+ sequence wgo_then_regular                          | {postgres=rwU/postgres,pg_signal_backend=rw*U*/postgres}
+ table regress_pg_dump_schema.test_table            | {postgres=arwdDxtm/postgres}
+ table regress_pg_dump_table                        | {postgres=arwdDxtm/postgres}
+ table regress_table_dumpable                       | {postgres=arwdDxtm/postgres,=r/postgres}
+ type regress_pg_dump_schema.test_type              | {=U/postgres,postgres=U/postgres}
+(12 rows)
+
+SELECT pg_describe_object(classid,objid,objsubid) AS obj,
+  pg_describe_object(refclassid,refobjid,0) AS refobj,
+  deptype
+  FROM pg_shdepend JOIN pg_database d ON dbid = d.oid
+  WHERE d.datname = current_database()
+  ORDER BY 1, 3;
+ obj | refobj | deptype
+-----+--------+---------
+(0 rows)
+
 DROP ROLE regress_dump_test_role;
diff --git a/src/test/modules/test_pg_dump/sql/test_pg_dump.sql b/src/test/modules/test_pg_dump/sql/test_pg_dump.sql
index 4f1eb9d429..0d48256883 100644
--- a/src/test/modules/test_pg_dump/sql/test_pg_dump.sql
+++ b/src/test/modules/test_pg_dump/sql/test_pg_dump.sql
@@ -75,6 +75,16 @@ GRANT UPDATE ON test_pg_dump_mv1 TO regress_dump_test_role;
 GRANT USAGE ON SCHEMA test_pg_dump_s1 TO regress_dump_test_role;
 GRANT USAGE ON TYPE test_pg_dump_e1 TO regress_dump_test_role;

+SELECT pg_describe_object(classoid,objoid,objsubid) AS obj,
+  replace(initprivs::text, current_user, 'postgres') AS initprivs
+  FROM pg_init_privs WHERE privtype = 'e' ORDER BY 1;
+SELECT pg_describe_object(classid,objid,objsubid) AS obj,
+  pg_describe_object(refclassid,refobjid,0) AS refobj,
+  deptype
+  FROM pg_shdepend JOIN pg_database d ON dbid = d.oid
+  WHERE d.datname = current_database()
+  ORDER BY 1, 3;
+
 ALTER EXTENSION test_pg_dump ADD ACCESS METHOD gist2;
 ALTER EXTENSION test_pg_dump ADD AGGREGATE newavg(int4);
 ALTER EXTENSION test_pg_dump ADD CAST (text AS casttesttype);
@@ -108,4 +118,15 @@ ALTER EXTENSION test_pg_dump DROP TYPE test_pg_dump_e1;
 ALTER EXTENSION test_pg_dump DROP VIEW test_pg_dump_v1;

 DROP OWNED BY regress_dump_test_role RESTRICT;
+
+SELECT pg_describe_object(classoid,objoid,objsubid) AS obj,
+  replace(initprivs::text, current_user, 'postgres') AS initprivs
+  FROM pg_init_privs WHERE privtype = 'e' ORDER BY 1;
+SELECT pg_describe_object(classid,objid,objsubid) AS obj,
+  pg_describe_object(refclassid,refobjid,0) AS refobj,
+  deptype
+  FROM pg_shdepend JOIN pg_database d ON dbid = d.oid
+  WHERE d.datname = current_database()
+  ORDER BY 1, 3;
+
 DROP ROLE regress_dump_test_role;

pgsql-hackers by date:

Previous
From: Tom Lane
Date:
Subject: Re: Fix overflow hazard in timestamp_pl_interval
Next
From: Alvaro Herrera
Date:
Subject: Re: Tarball builds in the new world order