Thread: Replacing pg_depend PIN entries with a fixed range check

Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
In [1] Andres and I speculated about whether we really need all
those PIN entries in pg_depend.  Here is a draft patch that gets
rid of them.

It turns out to be no big problem to replace the PIN entries
with an OID range check, because there's a well-defined point
in initdb where it wants to pin (almost) all existing objects,
and then no objects created after that are pinned.  In the earlier
thread I'd imagined having initdb record the OID counter at that
point in pg_control, and then we could look at the recorded counter
value to make is-it-pinned decisions.  However, that idea has a
fatal problem: what shall pg_resetwal fill into that field when
it has to gin up a pg_control file from scratch?  There's no
good way to reconstruct the value.

Hence, what this patch does is to establish a manually-managed cutoff
point akin to FirstBootstrapObjectId, and make initdb push the OID
counter up to that once it's made the small number of pinned objects
it's responsible for.  With the value I used here, a couple hundred
OIDs are wasted, but there seems to be a reasonable amount of headroom
still beyond that.  On my build, the OID counter at the end of initdb
is 15485 (with a reasonable number of glibc and ICU locales loaded).
So we still have about 900 free OIDs there; and there are 500 or so
free just below FirstBootstrapObjectId, too.  So this approach does
hasten the day when we're going to run out of free OIDs below 16384,
but not by all that much.

There are a couple of objects, namely template1 and the public
schema, that are in the catalog .dat files but are not supposed
to be pinned.  The existing code accomplishes that by excluding them
(in two different ways :-() while filling pg_depend.  This patch
just hard-wires exceptions for them in IsPinnedObject(), which seems
to me not much uglier than what we had before.  The existing code
also handles pinning of the standard tablespaces in an idiosyncratic
way; I just dropped that and made them be treated as pinned.

One interesting point about doing things this way is that
IsPinnedObject() will give correct answers throughout initdb, whereas
before the backend couldn't tell what was supposed to be pinned until
after initdb loaded pg_depend.  This means we don't need the hacky
truncation of pg_depend and pg_shdepend that initdb used to do,
because now the backend will correctly not make entries relating to
objects it now knows are pinned.  Aside from saving a few cycles,
this is more correct.  For example, if some object that initdb made
after bootstrap but before truncating pg_depend had a dependency on
the public schema, the existing coding would lose track of that fact.
(There's no live issue of that sort, I hasten to say, and really it
would be a bug to set things up that way because then you couldn't
drop the public schema.  But the existing coding would make things
worse by not detecting the mistake.)

Anyway, as to concrete results:

* pg_depend's total relation size, in a freshly made database,
drops from 1269760 bytes to 368640 bytes.

* There seems to be a small but noticeable reduction in the time
to run check-world.  I compared runtimes on a not-particularly-modern
machine with spinning-rust storage, using -j4 parallelism:

HEAD
real    5m4.248s
user    2m59.390s
sys     1m21.473s

+ patch
real    5m2.924s
user    2m36.196s
sys     1m19.724s

These top-line numbers don't look too impressive, but the CPU-time
reduction seems quite significant.  Probably on a different hardware
platform that would translate more directly to runtime savings.

I didn't try to reproduce the original performance bottleneck
that was complained of in [1], but that might be fun to check.

Anyway, I'll stick this in the next CF so we don't lose track
of it.

            regards, tom lane

[1] https://www.postgresql.org/message-id/flat/947172.1617684433%40sss.pgh.pa.us#6a3d250a9c4a994cb3a26c87384fc823

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2656786d1e..83a391eed1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3249,8 +3249,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
       </para>
       <para>
-       The OID of the system catalog the dependent object is in,
-       or zero for a <symbol>DEPENDENCY_PIN</symbol> entry
+       The OID of the system catalog the dependent object is in
       </para></entry>
      </row>

@@ -3260,8 +3259,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references any OID column)
       </para>
       <para>
-       The OID of the specific dependent object,
-       or zero for a <symbol>DEPENDENCY_PIN</symbol> entry
+       The OID of the specific dependent object
       </para></entry>
      </row>

@@ -3464,19 +3462,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
-
-    <varlistentry>
-     <term><symbol>DEPENDENCY_PIN</symbol> (<literal>p</literal>)</term>
-     <listitem>
-      <para>
-       There is no dependent object; this type of entry is a signal
-       that the system itself depends on the referenced object, and so
-       that object must never be deleted.  Entries of this type are
-       created only by <application>initdb</application>.  The columns for the
-       dependent object contain zeroes.
-      </para>
-     </listitem>
-    </varlistentry>
    </variablelist>

    Other dependency flavors might be needed in future.
@@ -6769,7 +6754,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
       <para>
        The OID of the database the dependent object is in,
        or zero for a shared object
-       or a <symbol>SHARED_DEPENDENCY_PIN</symbol> entry
       </para></entry>
      </row>

@@ -6779,8 +6763,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
       </para>
       <para>
-       The OID of the system catalog the dependent object is in,
-       or zero for a <symbol>SHARED_DEPENDENCY_PIN</symbol> entry
+       The OID of the system catalog the dependent object is in
       </para></entry>
      </row>

@@ -6790,8 +6773,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references any OID column)
       </para>
       <para>
-       The OID of the specific dependent object,
-       or zero for a <symbol>SHARED_DEPENDENCY_PIN</symbol> entry
+       The OID of the specific dependent object
       </para></entry>
      </row>

@@ -6879,19 +6861,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      </listitem>
     </varlistentry>

-    <varlistentry>
-     <term><symbol>SHARED_DEPENDENCY_PIN</symbol> (<literal>p</literal>)</term>
-     <listitem>
-      <para>
-       There is no dependent object; this type of entry is a signal
-       that the system itself depends on the referenced object, and so
-       that object must never be deleted.  Entries of this type are
-       created only by <application>initdb</application>.  The columns for the
-       dependent object contain zeroes.
-      </para>
-     </listitem>
-    </varlistentry>
-
     <varlistentry>
      <term><symbol>SHARED_DEPENDENCY_TABLESPACE</symbol> (<literal>t</literal>)</term>
      <listitem>
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index 142da4aaff..95e8df5212 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -586,6 +586,35 @@ GetNewObjectId(void)
     return result;
 }

+/*
+ * StopGeneratingPinnedObjectIds
+ *
+ * This is called once during initdb to force the OID counter up to
+ * FirstUnpinnedObjectId.  This supports letting initdb's post-bootstrap
+ * processing create some pinned objects early on.  Once it's done doing
+ * so, it calls this (via pg_stop_making_pinned_objects()) so that the
+ * remaining objects it makes will be considered un-pinned.
+ */
+void
+StopGeneratingPinnedObjectIds(void)
+{
+    /* Safety check, this is only allowable during initdb */
+    if (IsPostmasterEnvironment)
+        elog(ERROR, "cannot change object pinned-ness anymore");
+
+    /* Taking the lock is, therefore, just pro forma; but do it anyway */
+    LWLockAcquire(OidGenLock, LW_EXCLUSIVE);
+
+    if (ShmemVariableCache->nextOid > (Oid) FirstUnpinnedObjectId)
+        elog(ERROR, "too late to change pinned-ness, %u such OIDs were already used",
+             ShmemVariableCache->nextOid - (Oid) FirstUnpinnedObjectId);
+
+    ShmemVariableCache->nextOid = FirstUnpinnedObjectId;
+    ShmemVariableCache->oidCount = 0;
+
+    LWLockRelease(OidGenLock);
+}
+

 #ifdef USE_ASSERT_CHECKING

diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 245d536372..5d0dd3f481 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_largeobject.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
@@ -294,6 +295,64 @@ IsSharedRelation(Oid relationId)
     return false;
 }

+/*
+ * IsPinnedObject
+ *        Given the class + OID identity of a database object, report whether
+ *        it is "pinned", that is not droppable because the system requires it.
+ *
+ * We used to represent this explicitly in pg_depend, but that proved to be
+ * an undesirable amount of overhead, so now we rely on an OID range test.
+ */
+bool
+IsPinnedObject(Oid classId, Oid objectId)
+{
+    /*
+     * Objects with OIDs above FirstUnpinnedObjectId are never pinned.  Since
+     * the OID generator skips this range when wrapping around, this check
+     * guarantees that user-defined objects are never considered pinned.
+     */
+    if (objectId >= FirstUnpinnedObjectId)
+        return false;
+
+    /*
+     * Large objects are never pinned.  We need this special case because
+     * their OIDs can be user-assigned.
+     */
+    if (classId == LargeObjectRelationId)
+        return false;
+
+    /*
+     * There are a few objects defined in the catalog .dat files that, as a
+     * matter of policy, we prefer not to treat as pinned.  We used to handle
+     * that by excluding them from pg_depend, but it's just as easy to
+     * hard-wire their OIDs here.  (If the user does indeed drop and recreate
+     * them, they'll have new but certainly-unpinned OIDs, so no problem.)
+     *
+     * Checking both classId and objectId is overkill, since OIDs below
+     * FirstUnpinnedObjectId should be globally unique, but do it anyway for
+     * robustness.
+     */
+
+    /* template1 is not pinned */
+    if (classId == DatabaseRelationId &&
+        objectId == TemplateDbOid)
+        return false;
+
+    /* the public namespace is not pinned */
+    if (classId == NamespaceRelationId &&
+        objectId == PG_PUBLIC_NAMESPACE)
+        return false;
+
+    /*
+     * All other initdb-created objects are pinned.  This is overkill (the
+     * system doesn't really depend on having every last weird datatype, for
+     * instance) but generating only the minimum required set of dependencies
+     * seems hard, and enforcing an accurate list would be much more expensive
+     * than the simple range test used here.
+     */
+    return true;
+}
+

 /*
  * GetNewOidWithIndex
@@ -529,7 +588,8 @@ pg_nextoid(PG_FUNCTION_ARGS)
     if (!superuser())
         ereport(ERROR,
                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                 errmsg("must be superuser to call pg_nextoid()")));
+                 errmsg("must be superuser to call %s()",
+                        "pg_nextoid")));

     rel = table_open(reloid, RowExclusiveLock);
     idx = index_open(idxoid, RowExclusiveLock);
@@ -576,5 +636,29 @@ pg_nextoid(PG_FUNCTION_ARGS)
     table_close(rel, RowExclusiveLock);
     index_close(idx, RowExclusiveLock);

-    return newoid;
+    PG_RETURN_OID(newoid);
+}
+
+/*
+ * SQL callable interface for StopGeneratingPinnedObjectIds().
+ *
+ * This is only to be used by initdb, so it's intentionally not documented in
+ * the user facing docs.
+ */
+Datum
+pg_stop_making_pinned_objects(PG_FUNCTION_ARGS)
+{
+    /*
+     * Belt-and-suspenders check, since StopGeneratingPinnedObjectIds will
+     * fail anyway in non-single-user mode.
+     */
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("must be superuser to call %s()",
+                        "pg_stop_making_pinned_objects")));
+
+    StopGeneratingPinnedObjectIds();
+
+    PG_RETURN_VOID();
 }
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 8d8e926c21..5d70a6cb55 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -18,6 +18,7 @@
 #include "access/htup_details.h"
 #include "access/table.h"
 #include "access/xact.h"
+#include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
@@ -599,6 +600,16 @@ findDependentObjects(const ObjectAddress *object,
     if (object_address_present_add_flags(object, objflags, targetObjects))
         return;

+    /*
+     * If the target object is pinned, we can just error out immediately; it
+     * won't have any objects recorded as depending on it.
+     */
+    if (IsPinnedObject(object->classId, object->objectId))
+        ereport(ERROR,
+                (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+                 errmsg("cannot drop %s because it is required by the database system",
+                        getObjectDescription(object, false))));
+
     /*
      * The target object might be internally dependent on some other object
      * (its "owner"), and/or be a member of an extension (also considered its
@@ -862,15 +873,6 @@ findDependentObjects(const ObjectAddress *object,
                 objflags |= DEPFLAG_IS_PART;
                 break;

-            case DEPENDENCY_PIN:
-
-                /*
-                 * Should not happen; PIN dependencies should have zeroes in
-                 * the depender fields...
-                 */
-                elog(ERROR, "incorrect use of PIN dependency with %s",
-                     getObjectDescription(object, false));
-                break;
             default:
                 elog(ERROR, "unrecognized dependency type '%c' for %s",
                      foundDep->deptype, getObjectDescription(object, false));
@@ -999,18 +1001,6 @@ findDependentObjects(const ObjectAddress *object,
             case DEPENDENCY_EXTENSION:
                 subflags = DEPFLAG_EXTENSION;
                 break;
-            case DEPENDENCY_PIN:
-
-                /*
-                 * For a PIN dependency we just ereport immediately; there
-                 * won't be any others to report.
-                 */
-                ereport(ERROR,
-                        (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
-                         errmsg("cannot drop %s because it is required by the database system",
-                                getObjectDescription(object, false))));
-                subflags = 0;    /* keep compiler quiet */
-                break;
             default:
                 elog(ERROR, "unrecognized dependency type '%c' for %s",
                      foundDep->deptype, getObjectDescription(object, false));
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 362db7fe91..c5626e4087 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_collation.h"
@@ -32,7 +33,7 @@
 #include "utils/rel.h"


-static bool isObjectPinned(const ObjectAddress *object, Relation rel);
+static bool isObjectPinned(const ObjectAddress *object);


 /*
@@ -136,7 +137,7 @@ recordMultipleDependencies(const ObjectAddress *depender,
          * version.  This saves lots of space in pg_depend, so it's worth the
          * time taken to check.
          */
-        if (!ignore_systempin && isObjectPinned(referenced, dependDesc))
+        if (!ignore_systempin && isObjectPinned(referenced))
             continue;

         if (slot_init_count < max_slots)
@@ -440,8 +441,6 @@ changeDependencyFor(Oid classId, Oid objectId,
     bool        oldIsPinned;
     bool        newIsPinned;

-    depRel = table_open(DependRelationId, RowExclusiveLock);
-
     /*
      * Check to see if either oldRefObjectId or newRefObjectId is pinned.
      * Pinned objects should not have any dependency entries pointing to them,
@@ -452,16 +451,14 @@ changeDependencyFor(Oid classId, Oid objectId,
     objAddr.objectId = oldRefObjectId;
     objAddr.objectSubId = 0;

-    oldIsPinned = isObjectPinned(&objAddr, depRel);
+    oldIsPinned = isObjectPinned(&objAddr);

     objAddr.objectId = newRefObjectId;

-    newIsPinned = isObjectPinned(&objAddr, depRel);
+    newIsPinned = isObjectPinned(&objAddr);

     if (oldIsPinned)
     {
-        table_close(depRel, RowExclusiveLock);
-
         /*
          * If both are pinned, we need do nothing.  However, return 1 not 0,
          * else callers will think this is an error case.
@@ -481,6 +478,8 @@ changeDependencyFor(Oid classId, Oid objectId,
         return 1;
     }

+    depRel = table_open(DependRelationId, RowExclusiveLock);
+
     /* There should be existing dependency record(s), so search. */
     ScanKeyInit(&key[0],
                 Anum_pg_depend_classid,
@@ -615,7 +614,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
     objAddr.objectId = oldRefObjectId;
     objAddr.objectSubId = 0;

-    if (isObjectPinned(&objAddr, depRel))
+    if (isObjectPinned(&objAddr))
         ereport(ERROR,
                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                  errmsg("cannot remove dependency on %s because it is a system object",
@@ -627,7 +626,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
      */
     objAddr.objectId = newRefObjectId;

-    newIsPinned = isObjectPinned(&objAddr, depRel);
+    newIsPinned = isObjectPinned(&objAddr);

     /* Now search for dependency records */
     ScanKeyInit(&key[0],
@@ -675,50 +674,14 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
  * isObjectPinned()
  *
  * Test if an object is required for basic database functionality.
- * Caller must already have opened pg_depend.
  *
  * The passed subId, if any, is ignored; we assume that only whole objects
  * are pinned (and that this implies pinning their components).
  */
 static bool
-isObjectPinned(const ObjectAddress *object, Relation rel)
+isObjectPinned(const ObjectAddress *object)
 {
-    bool        ret = false;
-    SysScanDesc scan;
-    HeapTuple    tup;
-    ScanKeyData key[2];
-
-    ScanKeyInit(&key[0],
-                Anum_pg_depend_refclassid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(object->classId));
-
-    ScanKeyInit(&key[1],
-                Anum_pg_depend_refobjid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(object->objectId));
-
-    scan = systable_beginscan(rel, DependReferenceIndexId, true,
-                              NULL, 2, key);
-
-    /*
-     * Since we won't generate additional pg_depend entries for pinned
-     * objects, there can be at most one entry referencing a pinned object.
-     * Hence, it's sufficient to look at the first returned tuple; we don't
-     * need to loop.
-     */
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
-
-        if (foundDep->deptype == DEPENDENCY_PIN)
-            ret = true;
-    }
-
-    systable_endscan(scan);
-
-    return ret;
+    return IsPinnedObject(object->classId, object->objectId);
 }


diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 90b7a5de29..1df7241a52 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -101,7 +101,7 @@ static void storeObjectDescription(StringInfo descs,
                                    ObjectAddress *object,
                                    SharedDependencyType deptype,
                                    int count);
-static bool isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel);
+static bool isSharedObjectPinned(Oid classId, Oid objectId);


 /*
@@ -140,8 +140,7 @@ recordSharedDependencyOn(ObjectAddress *depender,
     sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);

     /* If the referenced object is pinned, do nothing. */
-    if (!isSharedObjectPinned(referenced->classId, referenced->objectId,
-                              sdepRel))
+    if (!isSharedObjectPinned(referenced->classId, referenced->objectId))
     {
         shdepAddDependency(sdepRel, depender->classId, depender->objectId,
                            depender->objectSubId,
@@ -255,7 +254,7 @@ shdepChangeDep(Relation sdepRel,

     systable_endscan(scan);

-    if (isSharedObjectPinned(refclassid, refobjid, sdepRel))
+    if (isSharedObjectPinned(refclassid, refobjid))
     {
         /* No new entry needed, so just delete existing entry if any */
         if (oldtup)
@@ -513,7 +512,7 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
                 continue;

             /* Skip pinned roles; they don't need dependency entries */
-            if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+            if (isSharedObjectPinned(AuthIdRelationId, roleid))
                 continue;

             shdepAddDependency(sdepRel, classId, objectId, objsubId,
@@ -531,7 +530,7 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
                 continue;

             /* Skip pinned roles */
-            if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+            if (isSharedObjectPinned(AuthIdRelationId, roleid))
                 continue;

             shdepDropDependency(sdepRel, classId, objectId, objsubId,
@@ -626,8 +625,6 @@ shared_dependency_comparator(const void *a, const void *b)
  * on objects local to other databases.  We can (and do) provide descriptions
  * of the two former kinds of objects, but we can't do that for "remote"
  * objects, so we just provide a count of them.
- *
- * If we find a SHARED_DEPENDENCY_PIN entry, we can error out early.
  */
 bool
 checkSharedDependencies(Oid classId, Oid objectId,
@@ -649,6 +646,18 @@ checkSharedDependencies(Oid classId, Oid objectId,
     StringInfoData descs;
     StringInfoData alldescs;

+    /* This case can be dispatched quickly */
+    if (isSharedObjectPinned(classId, objectId))
+    {
+        object.classId = classId;
+        object.objectId = objectId;
+        object.objectSubId = 0;
+        ereport(ERROR,
+                (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+                 errmsg("cannot drop %s because it is required by the database system",
+                        getObjectDescription(&object, false))));
+    }
+
     /*
      * We limit the number of dependencies reported to the client to
      * MAX_REPORTED_DEPS, since client software may not deal well with
@@ -685,18 +694,6 @@ checkSharedDependencies(Oid classId, Oid objectId,
     {
         Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tup);

-        /* This case can be dispatched quickly */
-        if (sdepForm->deptype == SHARED_DEPENDENCY_PIN)
-        {
-            object.classId = classId;
-            object.objectId = objectId;
-            object.objectSubId = 0;
-            ereport(ERROR,
-                    (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
-                     errmsg("cannot drop %s because it is required by the database system",
-                            getObjectDescription(&object, false))));
-        }
-
         object.classId = sdepForm->classid;
         object.objectId = sdepForm->objid;
         object.objectSubId = sdepForm->objsubid;
@@ -1274,49 +1271,15 @@ storeObjectDescription(StringInfo descs,

 /*
  * isSharedObjectPinned
- *        Return whether a given shared object has a SHARED_DEPENDENCY_PIN entry.
- *
- * sdepRel must be the pg_shdepend relation, already opened and suitably
- * locked.
+ *        Return true if a shared object is pinned.
  */
 static bool
-isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel)
+isSharedObjectPinned(Oid classId, Oid objectId)
 {
-    bool        result = false;
-    ScanKeyData key[2];
-    SysScanDesc scan;
-    HeapTuple    tup;
-
-    ScanKeyInit(&key[0],
-                Anum_pg_shdepend_refclassid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(classId));
-    ScanKeyInit(&key[1],
-                Anum_pg_shdepend_refobjid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(objectId));
-
-    scan = systable_beginscan(sdepRel, SharedDependReferenceIndexId, true,
-                              NULL, 2, key);
-
     /*
-     * Since we won't generate additional pg_shdepend entries for pinned
-     * objects, there can be at most one entry referencing a pinned object.
-     * Hence, it's sufficient to look at the first returned tuple; we don't
-     * need to loop.
+     * This is currently just the same as the normal pin test.
      */
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_shdepend shdepForm = (Form_pg_shdepend) GETSTRUCT(tup);
-
-        if (shdepForm->deptype == SHARED_DEPENDENCY_PIN)
-            result = true;
-    }
-
-    systable_endscan(scan);
-
-    return result;
+    return IsPinnedObject(classId, objectId);
 }

 /*
@@ -1359,7 +1322,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
         HeapTuple    tuple;

         /* Doesn't work for pinned objects */
-        if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+        if (isSharedObjectPinned(AuthIdRelationId, roleid))
         {
             ObjectAddress obj;

@@ -1402,7 +1365,6 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
             switch (sdepForm->deptype)
             {
                     /* Shouldn't happen */
-                case SHARED_DEPENDENCY_PIN:
                 case SHARED_DEPENDENCY_INVALID:
                     elog(ERROR, "unexpected dependency type");
                     break;
@@ -1506,7 +1468,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
         Oid            roleid = lfirst_oid(cell);

         /* Refuse to work on pinned roles */
-        if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+        if (isSharedObjectPinned(AuthIdRelationId, roleid))
         {
             ObjectAddress obj;

@@ -1549,10 +1511,6 @@ shdepReassignOwned(List *roleids, Oid newrole)
                 sdepForm->dbid != InvalidOid)
                 continue;

-            /* Unexpected because we checked for pins above */
-            if (sdepForm->deptype == SHARED_DEPENDENCY_PIN)
-                elog(ERROR, "unexpected shared pin");
-
             /* We leave non-owner dependencies alone */
             if (sdepForm->deptype != SHARED_DEPENDENCY_OWNER)
                 continue;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 096a6f2891..0411934480 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11914,10 +11914,6 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
         Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
         ObjectAddress foundObject;

-        /* We don't expect any PIN dependencies on columns */
-        if (foundDep->deptype == DEPENDENCY_PIN)
-            elog(ERROR, "cannot alter type of a pinned column");
-
         foundObject.classId = foundDep->classid;
         foundObject.objectId = foundDep->objid;
         foundObject.objectSubId = foundDep->objsubid;
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 69ea155d50..0385fd6121 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -449,7 +449,6 @@ DropTableSpace(DropTableSpaceStmt *stmt)
             ereport(NOTICE,
                     (errmsg("tablespace \"%s\" does not exist, skipping",
                             tablespacename)));
-            /* XXX I assume I need one or both of these next two calls */
             table_endscan(scandesc);
             table_close(rel, NoLock);
         }
@@ -465,8 +464,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
                        tablespacename);

     /* Disallow drop of the standard tablespaces, even by superuser */
-    if (tablespaceoid == GLOBALTABLESPACE_OID ||
-        tablespaceoid == DEFAULTTABLESPACE_OID)
+    if (IsPinnedObject(TableSpaceRelationId, tablespaceoid))
         aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_TABLESPACE,
                        tablespacename);

diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 4500df6fc8..ced981b902 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1521,83 +1521,10 @@ setup_depend(FILE *cmdfd)
     const char *const *line;
     static const char *const pg_depend_setup[] = {
         /*
-         * Make PIN entries in pg_depend for all objects made so far in the
-         * tables that the dependency code handles.  This is overkill (the
-         * system doesn't really depend on having every last weird datatype,
-         * for instance) but generating only the minimum required set of
-         * dependencies seems hard.
-         *
-         * Catalogs that are intentionally not scanned here are:
-         *
-         * pg_database: it's a feature, not a bug, that template1 is not
+         * Advance the OID counter so that subsequently-created objects aren't
          * pinned.
-         *
-         * pg_extension: a pinned extension isn't really an extension, hmm?
-         *
-         * pg_tablespace: tablespaces don't participate in the dependency
-         * code, and DropTableSpace() explicitly protects the built-in
-         * tablespaces.
-         *
-         * First delete any already-made entries; PINs override all else, and
-         * must be the only entries for their objects.
-         */
-        "DELETE FROM pg_depend;\n\n",
-        "VACUUM pg_depend;\n\n",
-        "DELETE FROM pg_shdepend;\n\n",
-        "VACUUM pg_shdepend;\n\n",
-
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_class;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_proc;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_type;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_cast;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_constraint;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_conversion;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_attrdef;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_language;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_operator;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_opclass;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_opfamily;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_am;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_amop;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_amproc;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_rewrite;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_trigger;\n\n",
-
-        /*
-         * restriction here to avoid pinning the public namespace
          */
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_namespace "
-        "    WHERE nspname LIKE 'pg%';\n\n",
-
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_parser;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_dict;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_template;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_config;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_collation;\n\n",
-        "INSERT INTO pg_shdepend SELECT 0,0,0,0, tableoid,oid, 'p' "
-        " FROM pg_authid;\n\n",
+        "SELECT pg_stop_making_pinned_objects();\n\n",
         NULL
     };

diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 2f7338ee82..575daaa87f 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -161,18 +161,21 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  *        development purposes (such as in-progress patches and forks);
  *        they should not appear in released versions.
  *
- *        OIDs 10000-12999 are reserved for assignment by genbki.pl, for use
+ *        OIDs 10000-12499 are reserved for assignment by genbki.pl, for use
  *        when the .dat files in src/include/catalog/ do not specify an OID
  *        for a catalog entry that requires one.
  *
- *        OIDS 13000-16383 are reserved for assignment during initdb
- *        using the OID generator.  (We start the generator at 13000.)
+ *        OIDs 12500-16383 are reserved for assignment during initdb
+ *        using the OID generator.  (We start the generator at 12500.)
+ *        Of these, OIDs below 13000 are considered to be pinned, that is
+ *        not droppable after initdb.  initdb forces the OID generator up to
+ *        13000 as soon as it's made the pinned objects it's responsible for.
  *
  *        OIDs beginning at 16384 are assigned from the OID generator
  *        during normal multiuser operation.  (We force the generator up to
  *        16384 as soon as we are in normal operation.)
  *
- * The choices of 8000, 10000 and 13000 are completely arbitrary, and can be
+ * The choices of 8000/10000/12500/13000 are completely arbitrary, and can be
  * moved if we run low on OIDs in any category.  Changing the macros below,
  * and updating relevant documentation (see bki.sgml and RELEASE_CHANGES),
  * should be sufficient to do this.  Moving the 16384 boundary between
@@ -182,11 +185,13 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  *
  * NOTE: if the OID generator wraps around, we skip over OIDs 0-16383
  * and resume with 16384.  This minimizes the odds of OID conflict, by not
- * reassigning OIDs that might have been assigned during initdb.
+ * reassigning OIDs that might have been assigned during initdb.  Critically,
+ * it also ensures that no user-created object will be considered pinned.
  * ----------
  */
 #define FirstGenbkiObjectId        10000
-#define FirstBootstrapObjectId    13000
+#define FirstBootstrapObjectId    12500
+#define FirstUnpinnedObjectId    13000
 #define FirstNormalObjectId        16384

 /*
@@ -287,6 +292,7 @@ extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
 extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid);
 extern bool ForceTransactionIdLimitUpdate(void);
 extern Oid    GetNewObjectId(void);
+extern void StopGeneratingPinnedObjectIds(void);

 #ifdef USE_ASSERT_CHECKING
 extern void AssertTransactionIdInAllowableRange(TransactionId xid);
diff --git a/src/include/catalog/catalog.h b/src/include/catalog/catalog.h
index f247be50b4..ef2e88fe45 100644
--- a/src/include/catalog/catalog.h
+++ b/src/include/catalog/catalog.h
@@ -34,6 +34,8 @@ extern bool IsReservedName(const char *name);

 extern bool IsSharedRelation(Oid relationId);

+extern bool IsPinnedObject(Oid classId, Oid objectId);
+
 extern Oid    GetNewOidWithIndex(Relation relation, Oid indexId,
                                AttrNumber oidcolumn);
 extern Oid    GetNewRelFileNode(Oid reltablespace, Relation pg_class,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index f272e2c99f..83af8ff08c 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -36,8 +36,7 @@ typedef enum DependencyType
     DEPENDENCY_PARTITION_PRI = 'P',
     DEPENDENCY_PARTITION_SEC = 'S',
     DEPENDENCY_EXTENSION = 'e',
-    DEPENDENCY_AUTO_EXTENSION = 'x',
-    DEPENDENCY_PIN = 'p'
+    DEPENDENCY_AUTO_EXTENSION = 'x'
 } DependencyType;

 /*
@@ -47,27 +46,21 @@ typedef enum DependencyType
  * unless the dependent object is dropped at the same time.  There are some
  * additional rules however:
  *
- * (a) For a SHARED_DEPENDENCY_PIN entry, there is no dependent object --
- * rather, the referenced object is an essential part of the system.  This
- * applies to the initdb-created superuser.  Entries of this type are only
- * created by initdb; objects in this category don't need further pg_shdepend
- * entries if more objects come to depend on them.
- *
- * (b) a SHARED_DEPENDENCY_OWNER entry means that the referenced object is
+ * (a) a SHARED_DEPENDENCY_OWNER entry means that the referenced object is
  * the role owning the dependent object.  The referenced object must be
  * a pg_authid entry.
  *
- * (c) a SHARED_DEPENDENCY_ACL entry means that the referenced object is
+ * (b) a SHARED_DEPENDENCY_ACL entry means that the referenced object is
  * a role mentioned in the ACL field of the dependent object.  The referenced
  * object must be a pg_authid entry.  (SHARED_DEPENDENCY_ACL 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
+ * (c) 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.
  *
- * (e) a SHARED_DEPENDENCY_TABLESPACE entry means that the referenced
+ * (d) 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
@@ -78,7 +71,6 @@ typedef enum DependencyType
  */
 typedef enum SharedDependencyType
 {
-    SHARED_DEPENDENCY_PIN = 'p',
     SHARED_DEPENDENCY_OWNER = 'o',
     SHARED_DEPENDENCY_ACL = 'a',
     SHARED_DEPENDENCY_POLICY = 'r',
diff --git a/src/include/catalog/pg_depend.h b/src/include/catalog/pg_depend.h
index 606a2a8e19..55edbabb78 100644
--- a/src/include/catalog/pg_depend.h
+++ b/src/include/catalog/pg_depend.h
@@ -4,8 +4,9 @@
  *      definition of the "dependency" system catalog (pg_depend)
  *
  * pg_depend has no preloaded contents, so there is no pg_depend.dat
- * file; system-defined dependencies are loaded into it during a late stage
- * of the initdb process.
+ * file; dependencies for system-defined objects are loaded into it
+ * on-the-fly during initdb.  Most built-in objects are pinned anyway,
+ * and hence need no explicit entries in pg_depend.
  *
  * NOTE: we do not represent all possible dependency pairs in pg_depend;
  * for example, there's not much value in creating an explicit dependency
@@ -42,11 +43,9 @@ CATALOG(pg_depend,2608,DependRelationId)
 {
     /*
      * Identification of the dependent (referencing) object.
-     *
-     * These fields are all zeroes for a DEPENDENCY_PIN entry.
      */
-    Oid            classid BKI_LOOKUP_OPT(pg_class);    /* OID of table containing
-                                                     * object */
+    Oid            classid BKI_LOOKUP(pg_class);    /* OID of table containing
+                                                 * object */
     Oid            objid;            /* OID of object itself */
     int32        objsubid;        /* column number, or 0 if not used */

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f4957653ae..df712af5ef 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3296,6 +3296,10 @@
   proname => 'pg_nextoid', provolatile => 'v', proparallel => 'u',
   prorettype => 'oid', proargtypes => 'regclass name regclass',
   prosrc => 'pg_nextoid' },
+{ oid => '8922', descr => 'stop making pinned objects during initdb',
+  proname => 'pg_stop_making_pinned_objects', provolatile => 'v',
+  proparallel => 'u', prorettype => 'void', proargtypes => '',
+  prosrc => 'pg_stop_making_pinned_objects' },

 { oid => '1579', descr => 'I/O',
   proname => 'varbit_in', prorettype => 'varbit',
diff --git a/src/include/catalog/pg_shdepend.h b/src/include/catalog/pg_shdepend.h
index 4faa95794d..4223717805 100644
--- a/src/include/catalog/pg_shdepend.h
+++ b/src/include/catalog/pg_shdepend.h
@@ -4,8 +4,9 @@
  *      definition of the "shared dependency" system catalog (pg_shdepend)
  *
  * pg_shdepend has no preloaded contents, so there is no pg_shdepend.dat
- * file; system-defined dependencies are loaded into it during a late stage
- * of the initdb process.
+ * file; dependencies for system-defined objects are loaded into it
+ * on-the-fly during initdb.  Most built-in objects are pinned anyway,
+ * and hence need no explicit entries in pg_shdepend.
  *
  * NOTE: we do not represent all possible dependency pairs in pg_shdepend;
  * for example, there's not much value in creating an explicit dependency
@@ -39,13 +40,12 @@ CATALOG(pg_shdepend,1214,SharedDependRelationId) BKI_SHARED_RELATION
     /*
      * Identification of the dependent (referencing) object.
      *
-     * These fields are all zeroes for a DEPENDENCY_PIN entry.  Also, dbid can
-     * be zero to denote a shared object.
+     * Note that dbid can be zero to denote a shared object.
      */
     Oid            dbid BKI_LOOKUP_OPT(pg_database);    /* OID of database
                                                      * containing object */
-    Oid            classid BKI_LOOKUP_OPT(pg_class);    /* OID of table containing
-                                                     * object */
+    Oid            classid BKI_LOOKUP(pg_class);    /* OID of table containing
+                                                 * object */
     Oid            objid;            /* OID of object itself */
     int32        objsubid;        /* column number, or 0 if not used */

diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index 9ebe28a78d..9637c0f085 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -11,75 +11,36 @@
 -- NB: run this test early, because some later tests create bogus entries.
 -- **************** pg_depend ****************
 -- Look for illegal values in pg_depend fields.
--- classid/objid can be zero, but only in 'p' entries
 SELECT *
 FROM pg_depend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'e', 'i', 'n', 'p') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (classid != 0 OR objid != 0 OR objsubid != 0));
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'e', 'i', 'n', 'x', 'P', 'S');
+ classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype | refobjversion
+---------+-------+----------+------------+----------+-------------+---------+---------------
+(0 rows)
+
+-- refobjversion should only appear in normal dependencies on collations.
+SELECT *
+FROM pg_depend as d1
+WHERE refobjversion IS NOT NULL AND
+      (refclassid != 'pg_collation'::regclass OR
+       deptype != 'n');
  classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype | refobjversion
 ---------+-------+----------+------------+----------+-------------+---------+---------------
 (0 rows)

 -- **************** pg_shdepend ****************
 -- Look for illegal values in pg_shdepend fields.
--- classid/objid can be zero, but only in 'p' entries
 SELECT *
 FROM pg_shdepend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'o', 'p', 'r') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (dbid != 0 OR classid != 0 OR objid != 0 OR objsubid != 0));
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'o', 'r', 't');
  dbid | classid | objid | objsubid | refclassid | refobjid | deptype
 ------+---------+-------+----------+------------+----------+---------
 (0 rows)

--- Check each OID-containing system catalog to see if its lowest-numbered OID
--- is pinned.  If not, and if that OID was generated during initdb, then
--- perhaps initdb forgot to scan that catalog for pinnable entries.
--- Generally, it's okay for a catalog to be listed in the output of this
--- test if that catalog is scanned by initdb.c's setup_depend() function;
--- whatever OID the test is complaining about must have been added later
--- in initdb, where it intentionally isn't pinned.  Legitimate exceptions
--- to that rule are listed in the comments in setup_depend().
--- Currently, pg_rewrite is also listed by this check, even though it is
--- covered by setup_depend().  That happens because there are no rules in
--- the pinned data, but initdb creates some intentionally-not-pinned views.
-do $$
-declare relnm text;
-  reloid oid;
-  shared bool;
-  lowoid oid;
-  pinned bool;
-begin
-for relnm, reloid, shared in
-  select relname, oid, relisshared from pg_class
-  where EXISTS(
-      SELECT * FROM pg_attribute
-      WHERE attrelid = pg_class.oid AND attname = 'oid')
-    and relkind = 'r' and oid < 16384 order by 1
-loop
-  execute 'select min(oid) from ' || relnm into lowoid;
-  continue when lowoid is null or lowoid >= 16384;
-  if shared then
-    pinned := exists(select 1 from pg_shdepend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  else
-    pinned := exists(select 1 from pg_depend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  end if;
-  if not pinned then
-    raise notice '% contains unpinned initdb-created object(s)', relnm;
-  end if;
-end loop;
-end$$;
-NOTICE:  pg_database contains unpinned initdb-created object(s)
-NOTICE:  pg_extension contains unpinned initdb-created object(s)
-NOTICE:  pg_rewrite contains unpinned initdb-created object(s)
-NOTICE:  pg_tablespace contains unpinned initdb-created object(s)
 -- **************** pg_class ****************
 -- Look for system tables with varlena columns but no toast table. All
 -- system tables with toastable columns should have toast tables, with
diff --git a/src/test/regress/sql/misc_sanity.sql b/src/test/regress/sql/misc_sanity.sql
index 9699f5cc3b..6f55c9ef10 100644
--- a/src/test/regress/sql/misc_sanity.sql
+++ b/src/test/regress/sql/misc_sanity.sql
@@ -14,70 +14,32 @@
 -- **************** pg_depend ****************

 -- Look for illegal values in pg_depend fields.
--- classid/objid can be zero, but only in 'p' entries

 SELECT *
 FROM pg_depend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'e', 'i', 'n', 'p') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (classid != 0 OR objid != 0 OR objsubid != 0));
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'e', 'i', 'n', 'x', 'P', 'S');
+
+-- refobjversion should only appear in normal dependencies on collations.
+
+SELECT *
+FROM pg_depend as d1
+WHERE refobjversion IS NOT NULL AND
+      (refclassid != 'pg_collation'::regclass OR
+       deptype != 'n');
+

 -- **************** pg_shdepend ****************

 -- Look for illegal values in pg_shdepend fields.
--- classid/objid can be zero, but only in 'p' entries

 SELECT *
 FROM pg_shdepend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'o', 'p', 'r') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (dbid != 0 OR classid != 0 OR objid != 0 OR objsubid != 0));
-
-
--- Check each OID-containing system catalog to see if its lowest-numbered OID
--- is pinned.  If not, and if that OID was generated during initdb, then
--- perhaps initdb forgot to scan that catalog for pinnable entries.
--- Generally, it's okay for a catalog to be listed in the output of this
--- test if that catalog is scanned by initdb.c's setup_depend() function;
--- whatever OID the test is complaining about must have been added later
--- in initdb, where it intentionally isn't pinned.  Legitimate exceptions
--- to that rule are listed in the comments in setup_depend().
--- Currently, pg_rewrite is also listed by this check, even though it is
--- covered by setup_depend().  That happens because there are no rules in
--- the pinned data, but initdb creates some intentionally-not-pinned views.
-
-do $$
-declare relnm text;
-  reloid oid;
-  shared bool;
-  lowoid oid;
-  pinned bool;
-begin
-for relnm, reloid, shared in
-  select relname, oid, relisshared from pg_class
-  where EXISTS(
-      SELECT * FROM pg_attribute
-      WHERE attrelid = pg_class.oid AND attname = 'oid')
-    and relkind = 'r' and oid < 16384 order by 1
-loop
-  execute 'select min(oid) from ' || relnm into lowoid;
-  continue when lowoid is null or lowoid >= 16384;
-  if shared then
-    pinned := exists(select 1 from pg_shdepend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  else
-    pinned := exists(select 1 from pg_depend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  end if;
-  if not pinned then
-    raise notice '% contains unpinned initdb-created object(s)', relnm;
-  end if;
-end loop;
-end$$;
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'o', 'r', 't');
+

 -- **************** pg_class ****************


Re: Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
[ connecting up two threads here ]

I wrote:
> Hence, what this patch does is to establish a manually-managed cutoff
> point akin to FirstBootstrapObjectId, and make initdb push the OID
> counter up to that once it's made the small number of pinned objects
> it's responsible for.  With the value I used here, a couple hundred
> OIDs are wasted, but there seems to be a reasonable amount of headroom
> still beyond that.  On my build, the OID counter at the end of initdb
> is 15485 (with a reasonable number of glibc and ICU locales loaded).
> So we still have about 900 free OIDs there; and there are 500 or so
> free just below FirstBootstrapObjectId, too.  So this approach does
> hasten the day when we're going to run out of free OIDs below 16384,
> but not by all that much.

In view of the discussion at [1], there's more pressure on the OID supply
above 10K than I'd realized.  While I don't have any good ideas about
eliminating the problem altogether, I did have a thought that would remove
the extra buffer zone created by my first-draft patch in this thread.
Namely, let's have genbki.pl write out its final OID assignment counter
value in a command in the postgres.bki file, say "set_next_oid 12036".
This would cause the bootstrap backend to set the server's OID counter to
that value.  Then the initial part of initdb's post-bootstrap processing
could assign pinned OIDs working forward from there, with no gap.  We'd
still need a gap before FirstBootstrapObjectId (which we might as well
rename to FirstUnpinnedObjectId), but we don't need two gaps, and so this
patch wouldn't make things any worse than they are today.

I'm not planning to put more effort into this patch right now, but
I'll revise it along these lines once v15 development opens.

            regards, tom lane

[1] https://www.postgresql.org/message-id/flat/CAGPqQf3JYTrTB1E1fu_zOGj%2BrG_kwTfa3UcUYPfNZL9o1bcYNw%40mail.gmail.com



Re: Replacing pg_depend PIN entries with a fixed range check

From
Andres Freund
Date:
Hi,

On 2021-04-14 21:43:28 -0400, Tom Lane wrote:
> In [1] Andres and I speculated about whether we really need all
> those PIN entries in pg_depend.  Here is a draft patch that gets
> rid of them.

Yay.

> There are a couple of objects, namely template1 and the public
> schema, that are in the catalog .dat files but are not supposed
> to be pinned.  The existing code accomplishes that by excluding them
> (in two different ways :-() while filling pg_depend.  This patch
> just hard-wires exceptions for them in IsPinnedObject(), which seems
> to me not much uglier than what we had before.  The existing code
> also handles pinning of the standard tablespaces in an idiosyncratic
> way; I just dropped that and made them be treated as pinned.

Hm, maybe we ought to swap template0 and template1 instead? I.e. have
template0 be in pg_database.dat and thus get a pinned oid, and then
create template1, postgres etc from that?

I guess we could also just create public in initdb.

Not that it matters much, having those exceptions doesn't seem too bad.



> Anyway, as to concrete results:
> 
> * pg_depend's total relation size, in a freshly made database,
> drops from 1269760 bytes to 368640 bytes.

Nice!



> I didn't try to reproduce the original performance bottleneck
> that was complained of in [1], but that might be fun to check.

I hope it's not reproducible as is, because I hopefully did fix the bug
leading to it ;)

> +bool
> +IsPinnedObject(Oid classId, Oid objectId)
> +{
> +    /*
> +     * Objects with OIDs above FirstUnpinnedObjectId are never pinned.  Since
> +     * the OID generator skips this range when wrapping around, this check
> +     * guarantees that user-defined objects are never considered pinned.
> +     */
> +    if (objectId >= FirstUnpinnedObjectId)
> +        return false;
> +
> +    /*
> +     * Large objects are never pinned.  We need this special case because
> +     * their OIDs can be user-assigned.
> +     */
> +    if (classId == LargeObjectRelationId)
> +        return false;
> +

Huh, shouldn't we reject that when creating them? IIRC we already use
oid range checks in a bunch of places? I guess you didn't because of
dump/restore concerns?

Greetings,

Andres Freund



Re: Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> Hm, maybe we ought to swap template0 and template1 instead? I.e. have
> template0 be in pg_database.dat and thus get a pinned oid, and then
> create template1, postgres etc from that?

No, *neither* of them are pinned, and we don't want them to be.
It's something of a historical artifact that template1 has a low OID.

>> +    /*
>> +     * Large objects are never pinned.  We need this special case because
>> +     * their OIDs can be user-assigned.
>> +     */
>> +    if (classId == LargeObjectRelationId)
>> +        return false;

> Huh, shouldn't we reject that when creating them?

We've got regression tests that create blobs with small OIDs :-(.
We could change those tests of course, but they're pretty ancient
and I'm hesitant to move those goal posts.

> I guess you didn't because of dump/restore concerns?

That too.

In short, I'm really skeptical of changing any of these pin-or-not
decisions to save one or two comparisons in IsPinnedObject.  That
function is already orders of magnitude faster than what it replaces;
we don't need to sweat over making it faster yet.

            regards, tom lane



Re: Replacing pg_depend PIN entries with a fixed range check

From
Andres Freund
Date:
Hi,

On 2021-04-15 19:59:24 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > Hm, maybe we ought to swap template0 and template1 instead? I.e. have
> > template0 be in pg_database.dat and thus get a pinned oid, and then
> > create template1, postgres etc from that?
> 
> No, *neither* of them are pinned, and we don't want them to be.
> It's something of a historical artifact that template1 has a low OID.

Hm, it makes sense for template1 not to be pinned, but it doesn't seem
as obvious why that should be the case for template0.


> In short, I'm really skeptical of changing any of these pin-or-not
> decisions to save one or two comparisons in IsPinnedObject.  That
> function is already orders of magnitude faster than what it replaces;
> we don't need to sweat over making it faster yet.

I'm not at all concerned about the speed after the change - it just
seems cleaner and easier to understand not to have exceptions.

Greetings,

Andres Freund



Re: Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> On 2021-04-15 19:59:24 -0400, Tom Lane wrote:
>> No, *neither* of them are pinned, and we don't want them to be.
>> It's something of a historical artifact that template1 has a low OID.

> Hm, it makes sense for template1 not to be pinned, but it doesn't seem
> as obvious why that should be the case for template0.

IIRC, the docs suggest that in an emergency you could recreate either
of them from the other.  Admittedly, if you've put stuff in template1
then this might cause problems later, but I think relatively few
people do that.

> I'm not at all concerned about the speed after the change - it just
> seems cleaner and easier to understand not to have exceptions.

We had these exceptions already, they were just implemented in initdb
rather than the backend.

            regards, tom lane



Re: Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
I wrote:
> In view of the discussion at [1], there's more pressure on the OID supply
> above 10K than I'd realized.  While I don't have any good ideas about
> eliminating the problem altogether, I did have a thought that would remove
> the extra buffer zone created by my first-draft patch in this thread.
> Namely, let's have genbki.pl write out its final OID assignment counter
> value in a command in the postgres.bki file, say "set_next_oid 12036".
> This would cause the bootstrap backend to set the server's OID counter to
> that value.  Then the initial part of initdb's post-bootstrap processing
> could assign pinned OIDs working forward from there, with no gap.  We'd
> still need a gap before FirstBootstrapObjectId (which we might as well
> rename to FirstUnpinnedObjectId), but we don't need two gaps, and so this
> patch wouldn't make things any worse than they are today.

Here's a v2 that does things that way (and is rebased up to HEAD).
I did some more documentation cleanup, too.

            regards, tom lane

diff --git a/doc/src/sgml/bki.sgml b/doc/src/sgml/bki.sgml
index b33e59d5e4..8dcd6e783c 100644
--- a/doc/src/sgml/bki.sgml
+++ b/doc/src/sgml/bki.sgml
@@ -418,11 +418,12 @@
    <para>
     If <filename>genbki.pl</filename> needs to assign an OID to a catalog
     entry that does not have a manually-assigned OID, it will use a value in
-    the range 10000—12999.  The server's OID counter is set to 13000
-    at the start of a bootstrap run.  Thus objects created by regular SQL
-    commands during the later phases of bootstrap, such as objects created
-    while running the <filename>information_schema.sql</filename> script,
-    receive OIDs of 13000 or above.
+    the range 10000—12999.  Then, during bootstrap, the server's OID
+    counter is set to the first OID not so allocated.  Thus objects created
+    by regular SQL commands during the later phases of bootstrap, such as
+    objects created while running
+    the <filename>information_schema.sql</filename> script, receive
+    non-conflicting OIDs.
    </para>

    <para>
@@ -432,6 +433,17 @@
     during bootstrap.  These automatically-assigned OIDs are not considered
     stable, and may change from one installation to another.
    </para>
+
+   <para>
+    An additional thing that is useful to know is that objects with OIDs
+    below <symbol>FirstUnpinnedObjectId</symbol> (13000) are considered
+    <quote>pinned</quote>, preventing them from being deleted.
+    (There are a small number of exceptions, which are hard-wired
+    into <function>IsPinnedObject()</function>.)
+    <application>initdb</application> forces the OID counter up
+    to <symbol>FirstUnpinnedObjectId</symbol> as soon as it's ready to
+    create unpinned objects.
+   </para>
   </sect2>

   <sect2 id="system-catalog-oid-references">
@@ -952,6 +964,20 @@ $ perl  rewrite_dat_with_prokind.pl  pg_proc.dat
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>set_next_oid</literal> <replaceable class="parameter">nextoid</replaceable></term>
+
+    <listitem>
+     <para>
+      Set the backend's OID generation counter
+      to <replaceable class="parameter">nextoid</replaceable>, so that future
+      OID assignments begin there.  (It is advisable to issue this command as
+      soon as possible, preferably before building indices, in case any
+      processing invoked by that allocates an OID.)
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>

  </sect1>
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6d06ad22b9..ec51d26491 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3260,8 +3260,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
       </para>
       <para>
-       The OID of the system catalog the dependent object is in,
-       or zero for a <symbol>DEPENDENCY_PIN</symbol> entry
+       The OID of the system catalog the dependent object is in
       </para></entry>
      </row>

@@ -3271,8 +3270,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references any OID column)
       </para>
       <para>
-       The OID of the specific dependent object,
-       or zero for a <symbol>DEPENDENCY_PIN</symbol> entry
+       The OID of the specific dependent object
       </para></entry>
      </row>

@@ -3463,19 +3461,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
-
-    <varlistentry>
-     <term><symbol>DEPENDENCY_PIN</symbol> (<literal>p</literal>)</term>
-     <listitem>
-      <para>
-       There is no dependent object; this type of entry is a signal
-       that the system itself depends on the referenced object, and so
-       that object must never be deleted.  Entries of this type are
-       created only by <application>initdb</application>.  The columns for the
-       dependent object contain zeroes.
-      </para>
-     </listitem>
-    </varlistentry>
    </variablelist>

    Other dependency flavors might be needed in future.
@@ -3494,6 +3479,19 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
    must be satisfied.
   </para>

+  <para>
+   Most objects created during <application>initdb</application> are
+   considered <quote>pinned</quote>, which means that the system itself
+   depends on them.  Therefore, they are never allowed to be dropped.
+   Also, knowing that pinned objects will not be dropped, the dependency
+   mechanism doesn't bother to make <structname>pg_depend</structname>
+   entries showing dependencies on them.  Thus, for example, a table
+   column of type <type>numeric</type> notionally has
+   a <literal>NORMAL</literal> dependency on the <type>numeric</type>
+   data type, but no such entry actually appears
+   in <structname>pg_depend</structname>.
+  </para>
+
  </sect1>


@@ -6776,7 +6774,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
       <para>
        The OID of the database the dependent object is in,
        or zero for a shared object
-       or a <symbol>SHARED_DEPENDENCY_PIN</symbol> entry
       </para></entry>
      </row>

@@ -6786,8 +6783,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
       </para>
       <para>
-       The OID of the system catalog the dependent object is in,
-       or zero for a <symbol>SHARED_DEPENDENCY_PIN</symbol> entry
+       The OID of the system catalog the dependent object is in
       </para></entry>
      </row>

@@ -6797,8 +6793,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references any OID column)
       </para>
       <para>
-       The OID of the specific dependent object,
-       or zero for a <symbol>SHARED_DEPENDENCY_PIN</symbol> entry
+       The OID of the specific dependent object
       </para></entry>
      </row>

@@ -6886,19 +6881,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      </listitem>
     </varlistentry>

-    <varlistentry>
-     <term><symbol>SHARED_DEPENDENCY_PIN</symbol> (<literal>p</literal>)</term>
-     <listitem>
-      <para>
-       There is no dependent object; this type of entry is a signal
-       that the system itself depends on the referenced object, and so
-       that object must never be deleted.  Entries of this type are
-       created only by <application>initdb</application>.  The columns for the
-       dependent object contain zeroes.
-      </para>
-     </listitem>
-    </varlistentry>
-
     <varlistentry>
      <term><symbol>SHARED_DEPENDENCY_TABLESPACE</symbol> (<literal>t</literal>)</term>
      <listitem>
@@ -6915,6 +6897,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
    objects.
   </para>

+  <para>
+   As in the <structname>pg_depend</structname> catalog, most objects
+   created during <application>initdb</application> are
+   considered <quote>pinned</quote>.  No entries are made
+   in <structname>pg_shdepend</structname> that would have a pinned
+   object as either referenced or dependent object.
+  </para>
+
  </sect1>

  <sect1 id="catalog-pg-shdescription">
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index a22bf375f8..f335d91c17 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -541,11 +541,11 @@ GetNewObjectId(void)
      * FirstNormalObjectId since that range is reserved for initdb (see
      * IsCatalogRelationOid()).  Note we are relying on unsigned comparison.
      *
-     * During initdb, we start the OID generator at FirstBootstrapObjectId, so
+     * During initdb, we start the OID generator at FirstGenbkiObjectId, so
      * we only wrap if before that point when in bootstrap or standalone mode.
      * The first time through this routine after normal postmaster start, the
      * counter will be forced up to FirstNormalObjectId.  This mechanism
-     * leaves the OIDs between FirstBootstrapObjectId and FirstNormalObjectId
+     * leaves the OIDs between FirstGenbkiObjectId and FirstNormalObjectId
      * available for automatic assignment during initdb, while ensuring they
      * will never conflict with user-assigned OIDs.
      */
@@ -560,7 +560,7 @@ GetNewObjectId(void)
         else
         {
             /* we may be bootstrapping, so don't enforce the full range */
-            if (ShmemVariableCache->nextOid < ((Oid) FirstBootstrapObjectId))
+            if (ShmemVariableCache->nextOid < ((Oid) FirstGenbkiObjectId))
             {
                 /* wraparound in standalone mode (unlikely but possible) */
                 ShmemVariableCache->nextOid = FirstNormalObjectId;
@@ -586,6 +586,47 @@ GetNewObjectId(void)
     return result;
 }

+/*
+ * SetNextObjectId
+ *
+ * This may only be called during initdb; it advances the OID counter
+ * to the specified value.
+ */
+void
+SetNextObjectId(Oid nextOid)
+{
+    /* Safety check, this is only allowable during initdb */
+    if (IsPostmasterEnvironment)
+        elog(ERROR, "cannot advance OID counter anymore");
+
+    /* Taking the lock is, therefore, just pro forma; but do it anyway */
+    LWLockAcquire(OidGenLock, LW_EXCLUSIVE);
+
+    if (ShmemVariableCache->nextOid > nextOid)
+        elog(ERROR, "too late to advance OID counter to %u, it is now %u",
+             nextOid, ShmemVariableCache->nextOid);
+
+    ShmemVariableCache->nextOid = nextOid;
+    ShmemVariableCache->oidCount = 0;
+
+    LWLockRelease(OidGenLock);
+}
+
+/*
+ * StopGeneratingPinnedObjectIds
+ *
+ * This is called once during initdb to force the OID counter up to
+ * FirstUnpinnedObjectId.  This supports letting initdb's post-bootstrap
+ * processing create some pinned objects early on.  Once it's done doing
+ * so, it calls this (via pg_stop_making_pinned_objects()) so that the
+ * remaining objects it makes will be considered un-pinned.
+ */
+void
+StopGeneratingPinnedObjectIds(void)
+{
+    SetNextObjectId(FirstUnpinnedObjectId);
+}
+

 #ifdef USE_ASSERT_CHECKING

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8d163f190f..d2e1f38537 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -5288,7 +5288,7 @@ BootStrapXLOG(void)
     checkPoint.fullPageWrites = fullPageWrites;
     checkPoint.nextXid =
         FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
-    checkPoint.nextOid = FirstBootstrapObjectId;
+    checkPoint.nextOid = FirstGenbkiObjectId;
     checkPoint.nextMulti = FirstMultiXactId;
     checkPoint.nextMultiOffset = 0;
     checkPoint.oldestXid = FirstNormalTransactionId;
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004e1b..01609100c4 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -103,7 +103,7 @@ static int num_columns_read = 0;
 %token NULLVAL
 /* All the rest are unreserved, and should be handled in boot_ident! */
 %token <kw> OPEN XCLOSE XCREATE INSERT_TUPLE
-%token <kw> XDECLARE INDEX ON USING XBUILD INDICES UNIQUE XTOAST
+%token <kw> XDECLARE INDEX ON USING XBUILD INDICES XSET_NEXT_OID UNIQUE XTOAST
 %token <kw> OBJ_ID XBOOTSTRAP XSHARED_RELATION XROWTYPE_OID
 %token <kw> XFORCE XNOT XNULL

@@ -130,6 +130,7 @@ Boot_Query :
         | Boot_DeclareUniqueIndexStmt
         | Boot_DeclareToastStmt
         | Boot_BuildIndsStmt
+        | Boot_SetNextOidStmt
         ;

 Boot_OpenStmt:
@@ -390,6 +391,15 @@ Boot_BuildIndsStmt:
                 }
         ;

+Boot_SetNextOidStmt:
+          XSET_NEXT_OID oidspec
+                {
+                    do_start();
+                    set_next_oid($2);
+                    do_end();
+                }
+        ;
+

 boot_index_params:
         boot_index_params COMMA boot_index_param    { $$ = lappend($1, $3); }
@@ -475,6 +485,7 @@ boot_ident:
         | USING            { $$ = pstrdup($1); }
         | XBUILD        { $$ = pstrdup($1); }
         | INDICES        { $$ = pstrdup($1); }
+        | XSET_NEXT_OID    { $$ = pstrdup($1); }
         | UNIQUE        { $$ = pstrdup($1); }
         | XTOAST        { $$ = pstrdup($1); }
         | OBJ_ID        { $$ = pstrdup($1); }
diff --git a/src/backend/bootstrap/bootscanner.l b/src/backend/bootstrap/bootscanner.l
index 7aecd895fb..515a7636b8 100644
--- a/src/backend/bootstrap/bootscanner.l
+++ b/src/backend/bootstrap/bootscanner.l
@@ -93,6 +93,7 @@ _null_            { return NULLVAL; }
 declare            { yylval.kw = "declare"; return XDECLARE; }
 build            { yylval.kw = "build"; return XBUILD; }
 indices            { yylval.kw = "indices"; return INDICES; }
+set_next_oid    { yylval.kw = "set_next_oid"; return XSET_NEXT_OID; }
 unique            { yylval.kw = "unique"; return UNIQUE; }
 index            { yylval.kw = "index"; return INDEX; }
 on                { yylval.kw = "on"; return ON; }
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index b155237488..e2aba938a1 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -1155,3 +1155,13 @@ build_indices(void)
         table_close(heap, NoLock);
     }
 }
+
+
+/*
+ * set_next_oid -- set the backend's OID counter
+ */
+void
+set_next_oid(Oid nextOid)
+{
+    SetNextObjectId(nextOid);
+}
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 245d536372..55cc4881e1 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_largeobject.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
@@ -120,9 +121,9 @@ bool
 IsCatalogRelationOid(Oid relid)
 {
     /*
-     * We consider a relation to be a system catalog if it has an OID that was
-     * manually assigned or assigned by genbki.pl.  This includes all the
-     * defined catalogs, their indexes, and their TOAST tables and indexes.
+     * We consider a relation to be a system catalog if it has a pinned OID.
+     * This includes all the defined catalogs, their indexes, and their TOAST
+     * tables and indexes.
      *
      * This rule excludes the relations in information_schema, which are not
      * integral to the system and can be treated the same as user relations.
@@ -132,7 +133,7 @@ IsCatalogRelationOid(Oid relid)
      * This test is reliable since an OID wraparound will skip this range of
      * OIDs; see GetNewObjectId().
      */
-    return (relid < (Oid) FirstBootstrapObjectId);
+    return (relid < (Oid) FirstUnpinnedObjectId);
 }

 /*
@@ -294,6 +295,64 @@ IsSharedRelation(Oid relationId)
     return false;
 }

+/*
+ * IsPinnedObject
+ *        Given the class + OID identity of a database object, report whether
+ *        it is "pinned", that is not droppable because the system requires it.
+ *
+ * We used to represent this explicitly in pg_depend, but that proved to be
+ * an undesirable amount of overhead, so now we rely on an OID range test.
+ */
+bool
+IsPinnedObject(Oid classId, Oid objectId)
+{
+    /*
+     * Objects with OIDs above FirstUnpinnedObjectId are never pinned.  Since
+     * the OID generator skips this range when wrapping around, this check
+     * guarantees that user-defined objects are never considered pinned.
+     */
+    if (objectId >= FirstUnpinnedObjectId)
+        return false;
+
+    /*
+     * Large objects are never pinned.  We need this special case because
+     * their OIDs can be user-assigned.
+     */
+    if (classId == LargeObjectRelationId)
+        return false;
+
+    /*
+     * There are a few objects defined in the catalog .dat files that, as a
+     * matter of policy, we prefer not to treat as pinned.  We used to handle
+     * that by excluding them from pg_depend, but it's just as easy to
+     * hard-wire their OIDs here.  (If the user does indeed drop and recreate
+     * them, they'll have new but certainly-unpinned OIDs, so no problem.)
+     *
+     * Checking both classId and objectId is overkill, since OIDs below
+     * FirstUnpinnedObjectId should be globally unique, but do it anyway for
+     * robustness.
+     */
+
+    /* template1 is not pinned */
+    if (classId == DatabaseRelationId &&
+        objectId == TemplateDbOid)
+        return false;
+
+    /* the public namespace is not pinned */
+    if (classId == NamespaceRelationId &&
+        objectId == PG_PUBLIC_NAMESPACE)
+        return false;
+
+    /*
+     * All other initdb-created objects are pinned.  This is overkill (the
+     * system doesn't really depend on having every last weird datatype, for
+     * instance) but generating only the minimum required set of dependencies
+     * seems hard, and enforcing an accurate list would be much more expensive
+     * than the simple range test used here.
+     */
+    return true;
+}
+

 /*
  * GetNewOidWithIndex
@@ -529,7 +588,8 @@ pg_nextoid(PG_FUNCTION_ARGS)
     if (!superuser())
         ereport(ERROR,
                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                 errmsg("must be superuser to call pg_nextoid()")));
+                 errmsg("must be superuser to call %s()",
+                        "pg_nextoid")));

     rel = table_open(reloid, RowExclusiveLock);
     idx = index_open(idxoid, RowExclusiveLock);
@@ -576,5 +636,29 @@ pg_nextoid(PG_FUNCTION_ARGS)
     table_close(rel, RowExclusiveLock);
     index_close(idx, RowExclusiveLock);

-    return newoid;
+    PG_RETURN_OID(newoid);
+}
+
+/*
+ * SQL callable interface for StopGeneratingPinnedObjectIds().
+ *
+ * This is only to be used by initdb, so it's intentionally not documented in
+ * the user facing docs.
+ */
+Datum
+pg_stop_making_pinned_objects(PG_FUNCTION_ARGS)
+{
+    /*
+     * Belt-and-suspenders check, since StopGeneratingPinnedObjectIds will
+     * fail anyway in non-single-user mode.
+     */
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("must be superuser to call %s()",
+                        "pg_stop_making_pinned_objects")));
+
+    StopGeneratingPinnedObjectIds();
+
+    PG_RETURN_VOID();
 }
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..76b65e39c4 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -18,6 +18,7 @@
 #include "access/htup_details.h"
 #include "access/table.h"
 #include "access/xact.h"
+#include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
@@ -520,6 +521,16 @@ findDependentObjects(const ObjectAddress *object,
     if (object_address_present_add_flags(object, objflags, targetObjects))
         return;

+    /*
+     * If the target object is pinned, we can just error out immediately; it
+     * won't have any objects recorded as depending on it.
+     */
+    if (IsPinnedObject(object->classId, object->objectId))
+        ereport(ERROR,
+                (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+                 errmsg("cannot drop %s because it is required by the database system",
+                        getObjectDescription(object, false))));
+
     /*
      * The target object might be internally dependent on some other object
      * (its "owner"), and/or be a member of an extension (also considered its
@@ -783,15 +794,6 @@ findDependentObjects(const ObjectAddress *object,
                 objflags |= DEPFLAG_IS_PART;
                 break;

-            case DEPENDENCY_PIN:
-
-                /*
-                 * Should not happen; PIN dependencies should have zeroes in
-                 * the depender fields...
-                 */
-                elog(ERROR, "incorrect use of PIN dependency with %s",
-                     getObjectDescription(object, false));
-                break;
             default:
                 elog(ERROR, "unrecognized dependency type '%c' for %s",
                      foundDep->deptype, getObjectDescription(object, false));
@@ -920,18 +922,6 @@ findDependentObjects(const ObjectAddress *object,
             case DEPENDENCY_EXTENSION:
                 subflags = DEPFLAG_EXTENSION;
                 break;
-            case DEPENDENCY_PIN:
-
-                /*
-                 * For a PIN dependency we just ereport immediately; there
-                 * won't be any others to report.
-                 */
-                ereport(ERROR,
-                        (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
-                         errmsg("cannot drop %s because it is required by the database system",
-                                getObjectDescription(object, false))));
-                subflags = 0;    /* keep compiler quiet */
-                break;
             default:
                 elog(ERROR, "unrecognized dependency type '%c' for %s",
                      foundDep->deptype, getObjectDescription(object, false));
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index bf080b5f12..83dcf38c2d 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -168,13 +168,13 @@ die "found $found duplicate OID(s) in catalog data\n" if $found;


 # Oids not specified in the input files are automatically assigned,
-# starting at FirstGenbkiObjectId, extending up to FirstBootstrapObjectId.
+# starting at FirstGenbkiObjectId, extending up to FirstUnpinnedObjectId.
 my $FirstGenbkiObjectId =
   Catalog::FindDefinedSymbol('access/transam.h', $include_path,
     'FirstGenbkiObjectId');
-my $FirstBootstrapObjectId =
+my $FirstUnpinnedObjectId =
   Catalog::FindDefinedSymbol('access/transam.h', $include_path,
-    'FirstBootstrapObjectId');
+    'FirstUnpinnedObjectId');
 my $GenbkiNextOid = $FirstGenbkiObjectId;


@@ -655,6 +655,14 @@ EOM
 # Any information needed for the BKI that is not contained in a pg_*.h header
 # (i.e., not contained in a header with a CATALOG() statement) comes here

+# Check that we didn't overrun available OIDs, and write out the next OID
+# (do this before any index creation could consume OIDs)
+die
+  "genbki OID counter reached $GenbkiNextOid, overrunning FirstUnpinnedObjectId\n"
+  if $GenbkiNextOid > $FirstUnpinnedObjectId;
+
+print $bki "set_next_oid $GenbkiNextOid\n";
+
 # Write out declare toast/index statements
 foreach my $declaration (@toast_decls)
 {
@@ -669,11 +677,6 @@ foreach my $declaration (@index_decls)
 # last command in the BKI file: build the indexes declared above
 print $bki "build indices\n";

-# check that we didn't overrun available OIDs
-die
-  "genbki OID counter reached $GenbkiNextOid, overrunning FirstBootstrapObjectId\n"
-  if $GenbkiNextOid > $FirstBootstrapObjectId;
-
 # Now generate system_constraints.sql

 foreach my $c (@system_constraints)
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 54688094f5..10f3119670 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_constraint.h"
@@ -29,7 +30,7 @@
 #include "utils/rel.h"


-static bool isObjectPinned(const ObjectAddress *object, Relation rel);
+static bool isObjectPinned(const ObjectAddress *object);


 /*
@@ -69,8 +70,11 @@ recordMultipleDependencies(const ObjectAddress *depender,
         return;                    /* nothing to do */

     /*
-     * During bootstrap, do nothing since pg_depend may not exist yet. initdb
-     * will fill in appropriate pg_depend entries after bootstrap.
+     * During bootstrap, do nothing since pg_depend may not exist yet.
+     *
+     * Objects created during bootstrap are most likely pinned, and the few
+     * that are not do not have dependencies on each other, so that there
+     * would be no need to make a pg_depend entry anyway.
      */
     if (IsBootstrapProcessingMode())
         return;
@@ -99,7 +103,7 @@ recordMultipleDependencies(const ObjectAddress *depender,
          * need to record dependencies on it.  This saves lots of space in
          * pg_depend, so it's worth the time taken to check.
          */
-        if (isObjectPinned(referenced, dependDesc))
+        if (isObjectPinned(referenced))
             continue;

         if (slot_init_count < max_slots)
@@ -399,8 +403,6 @@ changeDependencyFor(Oid classId, Oid objectId,
     bool        oldIsPinned;
     bool        newIsPinned;

-    depRel = table_open(DependRelationId, RowExclusiveLock);
-
     /*
      * Check to see if either oldRefObjectId or newRefObjectId is pinned.
      * Pinned objects should not have any dependency entries pointing to them,
@@ -411,16 +413,14 @@ changeDependencyFor(Oid classId, Oid objectId,
     objAddr.objectId = oldRefObjectId;
     objAddr.objectSubId = 0;

-    oldIsPinned = isObjectPinned(&objAddr, depRel);
+    oldIsPinned = isObjectPinned(&objAddr);

     objAddr.objectId = newRefObjectId;

-    newIsPinned = isObjectPinned(&objAddr, depRel);
+    newIsPinned = isObjectPinned(&objAddr);

     if (oldIsPinned)
     {
-        table_close(depRel, RowExclusiveLock);
-
         /*
          * If both are pinned, we need do nothing.  However, return 1 not 0,
          * else callers will think this is an error case.
@@ -440,6 +440,8 @@ changeDependencyFor(Oid classId, Oid objectId,
         return 1;
     }

+    depRel = table_open(DependRelationId, RowExclusiveLock);
+
     /* There should be existing dependency record(s), so search. */
     ScanKeyInit(&key[0],
                 Anum_pg_depend_classid,
@@ -574,7 +576,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
     objAddr.objectId = oldRefObjectId;
     objAddr.objectSubId = 0;

-    if (isObjectPinned(&objAddr, depRel))
+    if (isObjectPinned(&objAddr))
         ereport(ERROR,
                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                  errmsg("cannot remove dependency on %s because it is a system object",
@@ -586,7 +588,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
      */
     objAddr.objectId = newRefObjectId;

-    newIsPinned = isObjectPinned(&objAddr, depRel);
+    newIsPinned = isObjectPinned(&objAddr);

     /* Now search for dependency records */
     ScanKeyInit(&key[0],
@@ -634,50 +636,14 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
  * isObjectPinned()
  *
  * Test if an object is required for basic database functionality.
- * Caller must already have opened pg_depend.
  *
  * The passed subId, if any, is ignored; we assume that only whole objects
  * are pinned (and that this implies pinning their components).
  */
 static bool
-isObjectPinned(const ObjectAddress *object, Relation rel)
+isObjectPinned(const ObjectAddress *object)
 {
-    bool        ret = false;
-    SysScanDesc scan;
-    HeapTuple    tup;
-    ScanKeyData key[2];
-
-    ScanKeyInit(&key[0],
-                Anum_pg_depend_refclassid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(object->classId));
-
-    ScanKeyInit(&key[1],
-                Anum_pg_depend_refobjid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(object->objectId));
-
-    scan = systable_beginscan(rel, DependReferenceIndexId, true,
-                              NULL, 2, key);
-
-    /*
-     * Since we won't generate additional pg_depend entries for pinned
-     * objects, there can be at most one entry referencing a pinned object.
-     * Hence, it's sufficient to look at the first returned tuple; we don't
-     * need to loop.
-     */
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
-
-        if (foundDep->deptype == DEPENDENCY_PIN)
-            ret = true;
-    }
-
-    systable_endscan(scan);
-
-    return ret;
+    return IsPinnedObject(object->classId, object->objectId);
 }


diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..36bfff9706 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -85,7 +85,7 @@ check_publication_add_relation(Relation targetrel)
  * XXX  This also excludes all tables with relid < FirstNormalObjectId,
  * ie all tables created during initdb.  This mainly affects the preinstalled
  * information_schema.  IsCatalogRelationOid() only excludes tables with
- * relid < FirstBootstrapObjectId, making that test rather redundant,
+ * relid < FirstUnpinnedObjectId, making that test rather redundant,
  * but really we should get rid of the FirstNormalObjectId test not
  * IsCatalogRelationOid.  We can't do so today because we don't want
  * information_schema tables to be considered publishable; but this test
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 420ad96565..94989119ed 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -101,7 +101,7 @@ static void storeObjectDescription(StringInfo descs,
                                    ObjectAddress *object,
                                    SharedDependencyType deptype,
                                    int count);
-static bool isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel);
+static bool isSharedObjectPinned(Oid classId, Oid objectId);


 /*
@@ -140,8 +140,7 @@ recordSharedDependencyOn(ObjectAddress *depender,
     sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);

     /* If the referenced object is pinned, do nothing. */
-    if (!isSharedObjectPinned(referenced->classId, referenced->objectId,
-                              sdepRel))
+    if (!isSharedObjectPinned(referenced->classId, referenced->objectId))
     {
         shdepAddDependency(sdepRel, depender->classId, depender->objectId,
                            depender->objectSubId,
@@ -255,7 +254,7 @@ shdepChangeDep(Relation sdepRel,

     systable_endscan(scan);

-    if (isSharedObjectPinned(refclassid, refobjid, sdepRel))
+    if (isSharedObjectPinned(refclassid, refobjid))
     {
         /* No new entry needed, so just delete existing entry if any */
         if (oldtup)
@@ -513,7 +512,7 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
                 continue;

             /* Skip pinned roles; they don't need dependency entries */
-            if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+            if (isSharedObjectPinned(AuthIdRelationId, roleid))
                 continue;

             shdepAddDependency(sdepRel, classId, objectId, objsubId,
@@ -531,7 +530,7 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
                 continue;

             /* Skip pinned roles */
-            if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+            if (isSharedObjectPinned(AuthIdRelationId, roleid))
                 continue;

             shdepDropDependency(sdepRel, classId, objectId, objsubId,
@@ -626,8 +625,6 @@ shared_dependency_comparator(const void *a, const void *b)
  * on objects local to other databases.  We can (and do) provide descriptions
  * of the two former kinds of objects, but we can't do that for "remote"
  * objects, so we just provide a count of them.
- *
- * If we find a SHARED_DEPENDENCY_PIN entry, we can error out early.
  */
 bool
 checkSharedDependencies(Oid classId, Oid objectId,
@@ -649,6 +646,18 @@ checkSharedDependencies(Oid classId, Oid objectId,
     StringInfoData descs;
     StringInfoData alldescs;

+    /* This case can be dispatched quickly */
+    if (isSharedObjectPinned(classId, objectId))
+    {
+        object.classId = classId;
+        object.objectId = objectId;
+        object.objectSubId = 0;
+        ereport(ERROR,
+                (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+                 errmsg("cannot drop %s because it is required by the database system",
+                        getObjectDescription(&object, false))));
+    }
+
     /*
      * We limit the number of dependencies reported to the client to
      * MAX_REPORTED_DEPS, since client software may not deal well with
@@ -685,18 +694,6 @@ checkSharedDependencies(Oid classId, Oid objectId,
     {
         Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tup);

-        /* This case can be dispatched quickly */
-        if (sdepForm->deptype == SHARED_DEPENDENCY_PIN)
-        {
-            object.classId = classId;
-            object.objectId = objectId;
-            object.objectSubId = 0;
-            ereport(ERROR,
-                    (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
-                     errmsg("cannot drop %s because it is required by the database system",
-                            getObjectDescription(&object, false))));
-        }
-
         object.classId = sdepForm->classid;
         object.objectId = sdepForm->objid;
         object.objectSubId = sdepForm->objsubid;
@@ -1274,49 +1271,15 @@ storeObjectDescription(StringInfo descs,

 /*
  * isSharedObjectPinned
- *        Return whether a given shared object has a SHARED_DEPENDENCY_PIN entry.
- *
- * sdepRel must be the pg_shdepend relation, already opened and suitably
- * locked.
+ *        Return true if a shared object is pinned.
  */
 static bool
-isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel)
+isSharedObjectPinned(Oid classId, Oid objectId)
 {
-    bool        result = false;
-    ScanKeyData key[2];
-    SysScanDesc scan;
-    HeapTuple    tup;
-
-    ScanKeyInit(&key[0],
-                Anum_pg_shdepend_refclassid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(classId));
-    ScanKeyInit(&key[1],
-                Anum_pg_shdepend_refobjid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(objectId));
-
-    scan = systable_beginscan(sdepRel, SharedDependReferenceIndexId, true,
-                              NULL, 2, key);
-
     /*
-     * Since we won't generate additional pg_shdepend entries for pinned
-     * objects, there can be at most one entry referencing a pinned object.
-     * Hence, it's sufficient to look at the first returned tuple; we don't
-     * need to loop.
+     * This is currently just the same as the normal pin test.
      */
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_shdepend shdepForm = (Form_pg_shdepend) GETSTRUCT(tup);
-
-        if (shdepForm->deptype == SHARED_DEPENDENCY_PIN)
-            result = true;
-    }
-
-    systable_endscan(scan);
-
-    return result;
+    return IsPinnedObject(classId, objectId);
 }

 /*
@@ -1359,7 +1322,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
         HeapTuple    tuple;

         /* Doesn't work for pinned objects */
-        if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+        if (isSharedObjectPinned(AuthIdRelationId, roleid))
         {
             ObjectAddress obj;

@@ -1402,7 +1365,6 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
             switch (sdepForm->deptype)
             {
                     /* Shouldn't happen */
-                case SHARED_DEPENDENCY_PIN:
                 case SHARED_DEPENDENCY_INVALID:
                     elog(ERROR, "unexpected dependency type");
                     break;
@@ -1506,7 +1468,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
         Oid            roleid = lfirst_oid(cell);

         /* Refuse to work on pinned roles */
-        if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+        if (isSharedObjectPinned(AuthIdRelationId, roleid))
         {
             ObjectAddress obj;

@@ -1549,10 +1511,6 @@ shdepReassignOwned(List *roleids, Oid newrole)
                 sdepForm->dbid != InvalidOid)
                 continue;

-            /* Unexpected because we checked for pins above */
-            if (sdepForm->deptype == SHARED_DEPENDENCY_PIN)
-                elog(ERROR, "unexpected shared pin");
-
             /* We leave non-owner dependencies alone */
             if (sdepForm->deptype != SHARED_DEPENDENCY_OWNER)
                 continue;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ebc62034d2..18d2ea7723 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12000,10 +12000,6 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
         Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
         ObjectAddress foundObject;

-        /* We don't expect any PIN dependencies on columns */
-        if (foundDep->deptype == DEPENDENCY_PIN)
-            elog(ERROR, "cannot alter type of a pinned column");
-
         foundObject.classId = foundDep->classid;
         foundObject.objectId = foundDep->objid;
         foundObject.objectSubId = foundDep->objsubid;
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 69ea155d50..0385fd6121 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -449,7 +449,6 @@ DropTableSpace(DropTableSpaceStmt *stmt)
             ereport(NOTICE,
                     (errmsg("tablespace \"%s\" does not exist, skipping",
                             tablespacename)));
-            /* XXX I assume I need one or both of these next two calls */
             table_endscan(scandesc);
             table_close(rel, NoLock);
         }
@@ -465,8 +464,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
                        tablespacename);

     /* Disallow drop of the standard tablespaces, even by superuser */
-    if (tablespaceoid == GLOBALTABLESPACE_OID ||
-        tablespaceoid == DEFAULTTABLESPACE_OID)
+    if (IsPinnedObject(TableSpaceRelationId, tablespaceoid))
         aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_TABLESPACE,
                        tablespacename);

diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 9f40ed77e6..7cb51563fa 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2946,11 +2946,11 @@ record_plan_function_dependency(PlannerInfo *root, Oid funcid)
      * For performance reasons, we don't bother to track built-in functions;
      * we just assume they'll never change (or at least not in ways that'd
      * invalidate plans using them).  For this purpose we can consider a
-     * built-in function to be one with OID less than FirstBootstrapObjectId.
+     * built-in function to be one with OID less than FirstUnpinnedObjectId.
      * Note that the OID generator guarantees never to generate such an OID
      * after startup, even at OID wraparound.
      */
-    if (funcid >= (Oid) FirstBootstrapObjectId)
+    if (funcid >= (Oid) FirstUnpinnedObjectId)
     {
         PlanInvalItem *inval_item = makeNode(PlanInvalItem);

@@ -2986,7 +2986,7 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
      * As in record_plan_function_dependency, ignore the possibility that
      * someone would change a built-in domain.
      */
-    if (typid >= (Oid) FirstBootstrapObjectId)
+    if (typid >= (Oid) FirstUnpinnedObjectId)
     {
         PlanInvalItem *inval_item = makeNode(PlanInvalItem);

diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index d493aeef0f..56267bdc3c 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -494,7 +494,7 @@ static void ReleasePredicateLocksLocal(void);
 static inline bool
 PredicateLockingNeededForRelation(Relation relation)
 {
-    return !(relation->rd_id < FirstBootstrapObjectId ||
+    return !(relation->rd_id < FirstUnpinnedObjectId ||
              RelationUsesLocalBuffers(relation) ||
              relation->rd_rel->relkind == RELKIND_MATVIEW);
 }
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 152d21e88b..39646ee88d 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1522,83 +1522,10 @@ setup_depend(FILE *cmdfd)
     const char *const *line;
     static const char *const pg_depend_setup[] = {
         /*
-         * Make PIN entries in pg_depend for all objects made so far in the
-         * tables that the dependency code handles.  This is overkill (the
-         * system doesn't really depend on having every last weird datatype,
-         * for instance) but generating only the minimum required set of
-         * dependencies seems hard.
-         *
-         * Catalogs that are intentionally not scanned here are:
-         *
-         * pg_database: it's a feature, not a bug, that template1 is not
+         * Advance the OID counter so that subsequently-created objects aren't
          * pinned.
-         *
-         * pg_extension: a pinned extension isn't really an extension, hmm?
-         *
-         * pg_tablespace: tablespaces don't participate in the dependency
-         * code, and DropTableSpace() explicitly protects the built-in
-         * tablespaces.
-         *
-         * First delete any already-made entries; PINs override all else, and
-         * must be the only entries for their objects.
-         */
-        "DELETE FROM pg_depend;\n\n",
-        "VACUUM pg_depend;\n\n",
-        "DELETE FROM pg_shdepend;\n\n",
-        "VACUUM pg_shdepend;\n\n",
-
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_class;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_proc;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_type;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_cast;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_constraint;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_conversion;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_attrdef;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_language;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_operator;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_opclass;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_opfamily;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_am;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_amop;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_amproc;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_rewrite;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_trigger;\n\n",
-
-        /*
-         * restriction here to avoid pinning the public namespace
          */
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_namespace "
-        "    WHERE nspname LIKE 'pg%';\n\n",
-
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_parser;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_dict;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_template;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_config;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_collation;\n\n",
-        "INSERT INTO pg_shdepend SELECT 0,0,0,0, tableoid,oid, 'p' "
-        " FROM pg_authid;\n\n",
+        "SELECT pg_stop_making_pinned_objects();\n\n",
         NULL
     };

diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 805dafef07..2601f70a04 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -686,7 +686,7 @@ GuessControlValues(void)
     ControlFile.checkPointCopy.fullPageWrites = false;
     ControlFile.checkPointCopy.nextXid =
         FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
-    ControlFile.checkPointCopy.nextOid = FirstBootstrapObjectId;
+    ControlFile.checkPointCopy.nextOid = FirstGenbkiObjectId;
     ControlFile.checkPointCopy.nextMulti = FirstMultiXactId;
     ControlFile.checkPointCopy.nextMultiOffset = 0;
     ControlFile.checkPointCopy.oldestXid = FirstNormalTransactionId;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 05c6fbffe4..5be7bb3f95 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -163,10 +163,15 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  *
  *        OIDs 10000-12999 are reserved for assignment by genbki.pl, for use
  *        when the .dat files in src/include/catalog/ do not specify an OID
- *        for a catalog entry that requires one.
+ *        for a catalog entry that requires one.  Furthermore, initdb's
+ *        post-bootstrap processing can also assign OIDs in this range,
+ *        for objects it makes that should be treated as pinned.  genbki.pl
+ *        records the first OID that it didn't use in postgres.bki, causing
+ *        the bootstrap backend's OID generator to be set to that value.
  *
- *        OIDS 13000-16383 are reserved for assignment during initdb
- *        using the OID generator.  (We start the generator at 13000.)
+ *        OIDs 13000-16383 are reserved for unpinned objects created by initdb's
+ *        post-bootstrap processing.  initdb forces the OID generator up to
+ *        13000 as soon as it's made the pinned objects it's responsible for.
  *
  *        OIDs beginning at 16384 are assigned from the OID generator
  *        during normal multiuser operation.  (We force the generator up to
@@ -182,11 +187,12 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  *
  * NOTE: if the OID generator wraps around, we skip over OIDs 0-16383
  * and resume with 16384.  This minimizes the odds of OID conflict, by not
- * reassigning OIDs that might have been assigned during initdb.
+ * reassigning OIDs that might have been assigned during initdb.  Critically,
+ * it also ensures that no user-created object will be considered pinned.
  * ----------
  */
 #define FirstGenbkiObjectId        10000
-#define FirstBootstrapObjectId    13000
+#define FirstUnpinnedObjectId    13000
 #define FirstNormalObjectId        16384

 /*
@@ -287,6 +293,8 @@ extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
 extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid);
 extern bool ForceTransactionIdLimitUpdate(void);
 extern Oid    GetNewObjectId(void);
+extern void SetNextObjectId(Oid nextOid);
+extern void StopGeneratingPinnedObjectIds(void);

 #ifdef USE_ASSERT_CHECKING
 extern void AssertTransactionIdInAllowableRange(TransactionId xid);
diff --git a/src/include/bootstrap/bootstrap.h b/src/include/bootstrap/bootstrap.h
index 8290d4c6c4..cc3370bfa3 100644
--- a/src/include/bootstrap/bootstrap.h
+++ b/src/include/bootstrap/bootstrap.h
@@ -45,6 +45,8 @@ extern void InsertOneNull(int i);
 extern void index_register(Oid heap, Oid ind, IndexInfo *indexInfo);
 extern void build_indices(void);

+extern void set_next_oid(Oid nextOid);
+
 extern void boot_get_type_io_data(Oid typid,
                                   int16 *typlen,
                                   bool *typbyval,
diff --git a/src/include/catalog/catalog.h b/src/include/catalog/catalog.h
index f247be50b4..ef2e88fe45 100644
--- a/src/include/catalog/catalog.h
+++ b/src/include/catalog/catalog.h
@@ -34,6 +34,8 @@ extern bool IsReservedName(const char *name);

 extern bool IsSharedRelation(Oid relationId);

+extern bool IsPinnedObject(Oid classId, Oid objectId);
+
 extern Oid    GetNewOidWithIndex(Relation relation, Oid indexId,
                                AttrNumber oidcolumn);
 extern Oid    GetNewRelFileNode(Oid reltablespace, Relation pg_class,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..2885f35ccd 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -36,8 +36,7 @@ typedef enum DependencyType
     DEPENDENCY_PARTITION_PRI = 'P',
     DEPENDENCY_PARTITION_SEC = 'S',
     DEPENDENCY_EXTENSION = 'e',
-    DEPENDENCY_AUTO_EXTENSION = 'x',
-    DEPENDENCY_PIN = 'p'
+    DEPENDENCY_AUTO_EXTENSION = 'x'
 } DependencyType;

 /*
@@ -47,27 +46,21 @@ typedef enum DependencyType
  * unless the dependent object is dropped at the same time.  There are some
  * additional rules however:
  *
- * (a) For a SHARED_DEPENDENCY_PIN entry, there is no dependent object --
- * rather, the referenced object is an essential part of the system.  This
- * applies to the initdb-created superuser.  Entries of this type are only
- * created by initdb; objects in this category don't need further pg_shdepend
- * entries if more objects come to depend on them.
- *
- * (b) a SHARED_DEPENDENCY_OWNER entry means that the referenced object is
+ * (a) a SHARED_DEPENDENCY_OWNER entry means that the referenced object is
  * the role owning the dependent object.  The referenced object must be
  * a pg_authid entry.
  *
- * (c) a SHARED_DEPENDENCY_ACL entry means that the referenced object is
+ * (b) a SHARED_DEPENDENCY_ACL entry means that the referenced object is
  * a role mentioned in the ACL field of the dependent object.  The referenced
  * object must be a pg_authid entry.  (SHARED_DEPENDENCY_ACL 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
+ * (c) 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.
  *
- * (e) a SHARED_DEPENDENCY_TABLESPACE entry means that the referenced
+ * (d) 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
@@ -78,7 +71,6 @@ typedef enum DependencyType
  */
 typedef enum SharedDependencyType
 {
-    SHARED_DEPENDENCY_PIN = 'p',
     SHARED_DEPENDENCY_OWNER = 'o',
     SHARED_DEPENDENCY_ACL = 'a',
     SHARED_DEPENDENCY_POLICY = 'r',
diff --git a/src/include/catalog/pg_depend.h b/src/include/catalog/pg_depend.h
index e0bc114145..555523697f 100644
--- a/src/include/catalog/pg_depend.h
+++ b/src/include/catalog/pg_depend.h
@@ -4,8 +4,9 @@
  *      definition of the "dependency" system catalog (pg_depend)
  *
  * pg_depend has no preloaded contents, so there is no pg_depend.dat
- * file; system-defined dependencies are loaded into it during a late stage
- * of the initdb process.
+ * file; dependencies for system-defined objects are loaded into it
+ * on-the-fly during initdb.  Most built-in objects are pinned anyway,
+ * and hence need no explicit entries in pg_depend.
  *
  * NOTE: we do not represent all possible dependency pairs in pg_depend;
  * for example, there's not much value in creating an explicit dependency
@@ -42,11 +43,9 @@ CATALOG(pg_depend,2608,DependRelationId)
 {
     /*
      * Identification of the dependent (referencing) object.
-     *
-     * These fields are all zeroes for a DEPENDENCY_PIN entry.
      */
-    Oid            classid BKI_LOOKUP_OPT(pg_class);    /* OID of table containing
-                                                     * object */
+    Oid            classid BKI_LOOKUP(pg_class);    /* OID of table containing
+                                                 * object */
     Oid            objid;            /* OID of object itself */
     int32        objsubid;        /* column number, or 0 if not used */

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4607..00748bd898 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3295,6 +3295,10 @@
   proname => 'pg_nextoid', provolatile => 'v', proparallel => 'u',
   prorettype => 'oid', proargtypes => 'regclass name regclass',
   prosrc => 'pg_nextoid' },
+{ oid => '8922', descr => 'stop making pinned objects during initdb',
+  proname => 'pg_stop_making_pinned_objects', provolatile => 'v',
+  proparallel => 'u', prorettype => 'void', proargtypes => '',
+  prosrc => 'pg_stop_making_pinned_objects' },

 { oid => '1579', descr => 'I/O',
   proname => 'varbit_in', prorettype => 'varbit',
diff --git a/src/include/catalog/pg_shdepend.h b/src/include/catalog/pg_shdepend.h
index 4faa95794d..4223717805 100644
--- a/src/include/catalog/pg_shdepend.h
+++ b/src/include/catalog/pg_shdepend.h
@@ -4,8 +4,9 @@
  *      definition of the "shared dependency" system catalog (pg_shdepend)
  *
  * pg_shdepend has no preloaded contents, so there is no pg_shdepend.dat
- * file; system-defined dependencies are loaded into it during a late stage
- * of the initdb process.
+ * file; dependencies for system-defined objects are loaded into it
+ * on-the-fly during initdb.  Most built-in objects are pinned anyway,
+ * and hence need no explicit entries in pg_shdepend.
  *
  * NOTE: we do not represent all possible dependency pairs in pg_shdepend;
  * for example, there's not much value in creating an explicit dependency
@@ -39,13 +40,12 @@ CATALOG(pg_shdepend,1214,SharedDependRelationId) BKI_SHARED_RELATION
     /*
      * Identification of the dependent (referencing) object.
      *
-     * These fields are all zeroes for a DEPENDENCY_PIN entry.  Also, dbid can
-     * be zero to denote a shared object.
+     * Note that dbid can be zero to denote a shared object.
      */
     Oid            dbid BKI_LOOKUP_OPT(pg_database);    /* OID of database
                                                      * containing object */
-    Oid            classid BKI_LOOKUP_OPT(pg_class);    /* OID of table containing
-                                                     * object */
+    Oid            classid BKI_LOOKUP(pg_class);    /* OID of table containing
+                                                 * object */
     Oid            objid;            /* OID of object itself */
     int32        objsubid;        /* column number, or 0 if not used */

diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index a67f40198a..a57fd142a9 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -11,75 +11,26 @@
 -- NB: run this test early, because some later tests create bogus entries.
 -- **************** pg_depend ****************
 -- Look for illegal values in pg_depend fields.
--- classid/objid can be zero, but only in 'p' entries
 SELECT *
 FROM pg_depend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'e', 'i', 'n', 'p') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (classid != 0 OR objid != 0 OR objsubid != 0));
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'e', 'i', 'n', 'x', 'P', 'S');
  classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype
 ---------+-------+----------+------------+----------+-------------+---------
 (0 rows)

 -- **************** pg_shdepend ****************
 -- Look for illegal values in pg_shdepend fields.
--- classid/objid can be zero, but only in 'p' entries
 SELECT *
 FROM pg_shdepend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'o', 'p', 'r') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (dbid != 0 OR classid != 0 OR objid != 0 OR objsubid != 0));
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'o', 'r', 't');
  dbid | classid | objid | objsubid | refclassid | refobjid | deptype
 ------+---------+-------+----------+------------+----------+---------
 (0 rows)

--- Check each OID-containing system catalog to see if its lowest-numbered OID
--- is pinned.  If not, and if that OID was generated during initdb, then
--- perhaps initdb forgot to scan that catalog for pinnable entries.
--- Generally, it's okay for a catalog to be listed in the output of this
--- test if that catalog is scanned by initdb.c's setup_depend() function;
--- whatever OID the test is complaining about must have been added later
--- in initdb, where it intentionally isn't pinned.  Legitimate exceptions
--- to that rule are listed in the comments in setup_depend().
--- Currently, pg_rewrite is also listed by this check, even though it is
--- covered by setup_depend().  That happens because there are no rules in
--- the pinned data, but initdb creates some intentionally-not-pinned views.
-do $$
-declare relnm text;
-  reloid oid;
-  shared bool;
-  lowoid oid;
-  pinned bool;
-begin
-for relnm, reloid, shared in
-  select relname, oid, relisshared from pg_class
-  where EXISTS(
-      SELECT * FROM pg_attribute
-      WHERE attrelid = pg_class.oid AND attname = 'oid')
-    and relkind = 'r' and oid < 16384 order by 1
-loop
-  execute 'select min(oid) from ' || relnm into lowoid;
-  continue when lowoid is null or lowoid >= 16384;
-  if shared then
-    pinned := exists(select 1 from pg_shdepend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  else
-    pinned := exists(select 1 from pg_depend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  end if;
-  if not pinned then
-    raise notice '% contains unpinned initdb-created object(s)', relnm;
-  end if;
-end loop;
-end$$;
-NOTICE:  pg_database contains unpinned initdb-created object(s)
-NOTICE:  pg_extension contains unpinned initdb-created object(s)
-NOTICE:  pg_rewrite contains unpinned initdb-created object(s)
-NOTICE:  pg_tablespace contains unpinned initdb-created object(s)
 -- **************** pg_class ****************
 -- Look for system tables with varlena columns but no toast table. All
 -- system tables with toastable columns should have toast tables, with
diff --git a/src/test/regress/sql/misc_sanity.sql b/src/test/regress/sql/misc_sanity.sql
index 9699f5cc3b..2c0f87a651 100644
--- a/src/test/regress/sql/misc_sanity.sql
+++ b/src/test/regress/sql/misc_sanity.sql
@@ -14,70 +14,24 @@
 -- **************** pg_depend ****************

 -- Look for illegal values in pg_depend fields.
--- classid/objid can be zero, but only in 'p' entries

 SELECT *
 FROM pg_depend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'e', 'i', 'n', 'p') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (classid != 0 OR objid != 0 OR objsubid != 0));
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'e', 'i', 'n', 'x', 'P', 'S');
+

 -- **************** pg_shdepend ****************

 -- Look for illegal values in pg_shdepend fields.
--- classid/objid can be zero, but only in 'p' entries

 SELECT *
 FROM pg_shdepend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'o', 'p', 'r') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (dbid != 0 OR classid != 0 OR objid != 0 OR objsubid != 0));
-
-
--- Check each OID-containing system catalog to see if its lowest-numbered OID
--- is pinned.  If not, and if that OID was generated during initdb, then
--- perhaps initdb forgot to scan that catalog for pinnable entries.
--- Generally, it's okay for a catalog to be listed in the output of this
--- test if that catalog is scanned by initdb.c's setup_depend() function;
--- whatever OID the test is complaining about must have been added later
--- in initdb, where it intentionally isn't pinned.  Legitimate exceptions
--- to that rule are listed in the comments in setup_depend().
--- Currently, pg_rewrite is also listed by this check, even though it is
--- covered by setup_depend().  That happens because there are no rules in
--- the pinned data, but initdb creates some intentionally-not-pinned views.
-
-do $$
-declare relnm text;
-  reloid oid;
-  shared bool;
-  lowoid oid;
-  pinned bool;
-begin
-for relnm, reloid, shared in
-  select relname, oid, relisshared from pg_class
-  where EXISTS(
-      SELECT * FROM pg_attribute
-      WHERE attrelid = pg_class.oid AND attname = 'oid')
-    and relkind = 'r' and oid < 16384 order by 1
-loop
-  execute 'select min(oid) from ' || relnm into lowoid;
-  continue when lowoid is null or lowoid >= 16384;
-  if shared then
-    pinned := exists(select 1 from pg_shdepend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  else
-    pinned := exists(select 1 from pg_depend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  end if;
-  if not pinned then
-    raise notice '% contains unpinned initdb-created object(s)', relnm;
-  end if;
-end loop;
-end$$;
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'o', 'r', 't');
+

 -- **************** pg_class ****************


Re: Replacing pg_depend PIN entries with a fixed range check

From
Robert Haas
Date:
On Wed, May 12, 2021 at 6:21 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Here's a v2 that does things that way (and is rebased up to HEAD).
> I did some more documentation cleanup, too.

The first hunk of the patch seems to back away from the idea that the
cutoff is 13000, but the second half of the patch says 13000 still
matters. Not sure I understand what's going on there exactly.

I suggest deleting the words "An additional thing that is useful to
know is that" because the rest of the sentence is fine without it.

I'm sort of wondering what we think the long term plan ought to be.
Are there some categories of things we should be looking to move out
of the reserved OID space to keep it from filling up? Can we
realistically think of moving the 16384 boundary?

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> The first hunk of the patch seems to back away from the idea that the
> cutoff is 13000, but the second half of the patch says 13000 still
> matters. Not sure I understand what's going on there exactly.

Not sure exactly what you're looking at, but IIRC there is a place
where the patch is cleaning up after ab596105b's failure to adjust
bki.sgml to match its change of FirstBootstrapObjectId from 12000
to 13000.  I hadn't bothered to fix that separately, but I guess
we should do so, else v14 is going to ship with incorrect docs.

> I'm sort of wondering what we think the long term plan ought to be.
> Are there some categories of things we should be looking to move out
> of the reserved OID space to keep it from filling up? Can we
> realistically think of moving the 16384 boundary?

I haven't got any wonderful ideas there.  I do not see how we can
move the 16384 boundary without breaking pg_upgrade'ing, because
pg_upgrade relies on preserving user object OIDs that are likely
to be not much above that value.  Probably, upping
FirstNormalObjectId ought to be high on our list of things to do
if we ever do force an on-disk compatibility break.  In the
meantime, we could decrease the 10000 boundary if things get
tight above that, but I fear that would annoy some extension
maintainers.

Another idea is to give up the principle that initdb-time OIDs
need to be globally unique.  They only really need to be
unique within their own catalogs, so we could buy a lot of space
by exploiting that.  The original reason for that policy was to
reduce the risk of mistakes in handwritten OID references in
the initial catalog data --- but now that numeric references
there are Not Done, it seems like we don't really need that.

An intermediate step, perhaps, could be to give up that
uniqueness only for OIDs assigned by genbki.pl itself, while
keeping it for OIDs below 10000.  This'd be appealing if we
find that we're getting tight between 10K and 13K.

In any case it doesn't seem like the issue is entirely pressing
yet.  Although ... maybe we should do that last bit now, so
that we can revert FirstBootstrapObjectId to 12K before v14
ships?  I've felt a little bit of worry that that change might
cause problems on machines with a boatload of locales.

            regards, tom lane



Re: Replacing pg_depend PIN entries with a fixed range check

From
Robert Haas
Date:
On Wed, May 26, 2021 at 11:37 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> In any case it doesn't seem like the issue is entirely pressing
> yet.  Although ... maybe we should do that last bit now, so
> that we can revert FirstBootstrapObjectId to 12K before v14
> ships?  I've felt a little bit of worry that that change might
> cause problems on machines with a boatload of locales.

I think that particular case is definitely worth worrying about. Most
of what we put into the system catalogs is our own hand-crafted
entries, but that's coming from the operating system and we have no
control over it whatever. It wouldn't be very nice to have to suggest
to users who get can't initdb that perhaps they should delete some
locales...

Honestly, it seems odd to me that these entries use reserved OIDs
rather than regular ones at all. Why does the first run of
pg_import_system_collations use special magic OIDs, and later runs use
regular OIDs? pg_type OIDs need to remain stable from release to
release since it's part of the on disk format for arrays, and pg_proc
OIDs have to be the same at compile time and initdb time because of
the fmgr hash table, and any other thing that has a constant that
might be used in the source code also has that issue. But none of this
applies to collations: they can't expected to have the same OID from
release to release, or even from one installation to another; the
source code can't be relying on the specific values; and we have no
idea how many there might be.

So I think your proposal of allowing genbki-assigned OIDs to be reused
in different catalogs is probably a pretty good one, but I wonder if
we could just rejigger things so that collations just get normal OIDs
> 16384.

-- 
Robert Haas
EDB: http://www.enterprisedb.com



Re: Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
Robert Haas <robertmhaas@gmail.com> writes:
> So I think your proposal of allowing genbki-assigned OIDs to be reused
> in different catalogs is probably a pretty good one, but I wonder if
> we could just rejigger things so that collations just get normal OIDs
> > 16384.

Hm.  I can't readily think of a non-hack way of making that happen.
It's also unclear to me how it'd interact with assignment of OIDs
to regular user objects.  Maybe we'll have to go there eventually,
but I'm not in a hurry to.

Meanwhile, I'll draft a patch for the other thing.

            regards, tom lane



Re: Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
I wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> The first hunk of the patch seems to back away from the idea that the
>> cutoff is 13000, but the second half of the patch says 13000 still
>> matters. Not sure I understand what's going on there exactly.

> Not sure exactly what you're looking at, but IIRC there is a place
> where the patch is cleaning up after ab596105b's failure to adjust
> bki.sgml to match its change of FirstBootstrapObjectId from 12000
> to 13000.  I hadn't bothered to fix that separately, but I guess
> we should do so, else v14 is going to ship with incorrect docs.

I take that back: I had committed that doc fix, in 1f9b0e693, so
I'm still unsure what was confusing you.  (But a4390abec just
reverted it, anyway.)

Attached is a rebase over a4390abec.  The decision in that commit
to not expect global uniqueness of OIDs above 10K frees us to use
a much simpler solution than before: we can just go ahead and start
the backend's OID counter at 10000, and not worry about conflicts,
because the OID generation logic can deal with any conflicts just
fine as long as you're okay with only having per-catalog uniqueness.
So this gets rid of the set_next_oid mechanism that I'd invented in
v2, and yet there's still no notable risk of running out of OIDs in
the 10K-12K range.

While testing this, I discovered something that I either never knew
or had forgotten: the bootstrap backend is itself assigning some
OIDs, specifically OIDs for the composite types associated with most
of the system catalogs (plus their array types).  I find this scary,
because it is happening before we've built the catalog indexes, so
it's impossible to ensure uniqueness.  (Of course, when we do build
the indexes, we'd notice any conflicts; but that's not a solution.)
I think it accidentally works because we don't ask genbki.pl to
assign any pg_type OIDs, but that seems fragile.  Seems like maybe
we should fix genbki.pl to assign those OIDs, and then change
GetNewOidWithIndex to error out in bootstrap mode.  However that's a
pre-existing issue, so I don't feel that this patch needs to be
the one to fix it.

            regards, tom lane

diff --git a/doc/src/sgml/bki.sgml b/doc/src/sgml/bki.sgml
index db1b3d5e9a..f32208b550 100644
--- a/doc/src/sgml/bki.sgml
+++ b/doc/src/sgml/bki.sgml
@@ -418,19 +418,33 @@
    <para>
     If <filename>genbki.pl</filename> needs to assign an OID to a catalog
     entry that does not have a manually-assigned OID, it will use a value in
-    the range 10000—11999.  The server's OID counter is set to 12000
-    at the start of a bootstrap run.  Thus objects created by regular SQL
-    commands during the later phases of bootstrap, such as objects created
-    while running the <filename>information_schema.sql</filename> script,
-    receive OIDs of 12000 or above.
+    the range 10000—11999.  The server's OID counter is set to 10000
+    at the start of a bootstrap run, so that any objects created on-the-fly
+    during bootstrap processing also receive OIDs in this range.  (The
+    usual OID assignment mechanism takes care of preventing any conflicts.)
+   </para>
+
+   <para>
+    Objects with OIDs below <symbol>FirstUnpinnedObjectId</symbol> (12000)
+    are considered <quote>pinned</quote>, preventing them from being
+    deleted.  (There are a small number of exceptions, which are
+    hard-wired into <function>IsPinnedObject()</function>.)
+    <application>initdb</application> forces the OID counter up
+    to <symbol>FirstUnpinnedObjectId</symbol> as soon as it's ready to
+    create unpinned objects.  Thus objects created during the later phases
+    of <application>initdb</application>, such as objects created while
+    running the <filename>information_schema.sql</filename> script, will
+    not be pinned, while all objects known
+    to <filename>genbki.pl</filename> will be.
    </para>

    <para>
     OIDs assigned during normal database operation are constrained to be
     16384 or higher.  This ensures that the range 10000—16383 is free
     for OIDs assigned automatically by <filename>genbki.pl</filename> or
-    during bootstrap.  These automatically-assigned OIDs are not considered
-    stable, and may change from one installation to another.
+    during <application>initdb</application>.  These
+    automatically-assigned OIDs are not considered stable, and may change
+    from one installation to another.
    </para>
   </sect2>

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 16493209c6..d4e1a13746 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3264,8 +3264,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
       </para>
       <para>
-       The OID of the system catalog the dependent object is in,
-       or zero for a <symbol>DEPENDENCY_PIN</symbol> entry
+       The OID of the system catalog the dependent object is in
       </para></entry>
      </row>

@@ -3275,8 +3274,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references any OID column)
       </para>
       <para>
-       The OID of the specific dependent object,
-       or zero for a <symbol>DEPENDENCY_PIN</symbol> entry
+       The OID of the specific dependent object
       </para></entry>
      </row>

@@ -3467,19 +3465,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
       </para>
      </listitem>
     </varlistentry>
-
-    <varlistentry>
-     <term><symbol>DEPENDENCY_PIN</symbol> (<literal>p</literal>)</term>
-     <listitem>
-      <para>
-       There is no dependent object; this type of entry is a signal
-       that the system itself depends on the referenced object, and so
-       that object must never be deleted.  Entries of this type are
-       created only by <application>initdb</application>.  The columns for the
-       dependent object contain zeroes.
-      </para>
-     </listitem>
-    </varlistentry>
    </variablelist>

    Other dependency flavors might be needed in future.
@@ -3498,6 +3483,19 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
    must be satisfied.
   </para>

+  <para>
+   Most objects created during <application>initdb</application> are
+   considered <quote>pinned</quote>, which means that the system itself
+   depends on them.  Therefore, they are never allowed to be dropped.
+   Also, knowing that pinned objects will not be dropped, the dependency
+   mechanism doesn't bother to make <structname>pg_depend</structname>
+   entries showing dependencies on them.  Thus, for example, a table
+   column of type <type>numeric</type> notionally has
+   a <literal>NORMAL</literal> dependency on the <type>numeric</type>
+   data type, but no such entry actually appears
+   in <structname>pg_depend</structname>.
+  </para>
+
  </sect1>


@@ -6780,7 +6778,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
       <para>
        The OID of the database the dependent object is in,
        or zero for a shared object
-       or a <symbol>SHARED_DEPENDENCY_PIN</symbol> entry
       </para></entry>
      </row>

@@ -6790,8 +6787,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.<structfield>oid</structfield>)
       </para>
       <para>
-       The OID of the system catalog the dependent object is in,
-       or zero for a <symbol>SHARED_DEPENDENCY_PIN</symbol> entry
+       The OID of the system catalog the dependent object is in
       </para></entry>
      </row>

@@ -6801,8 +6797,7 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
        (references any OID column)
       </para>
       <para>
-       The OID of the specific dependent object,
-       or zero for a <symbol>SHARED_DEPENDENCY_PIN</symbol> entry
+       The OID of the specific dependent object
       </para></entry>
      </row>

@@ -6890,19 +6885,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      </listitem>
     </varlistentry>

-    <varlistentry>
-     <term><symbol>SHARED_DEPENDENCY_PIN</symbol> (<literal>p</literal>)</term>
-     <listitem>
-      <para>
-       There is no dependent object; this type of entry is a signal
-       that the system itself depends on the referenced object, and so
-       that object must never be deleted.  Entries of this type are
-       created only by <application>initdb</application>.  The columns for the
-       dependent object contain zeroes.
-      </para>
-     </listitem>
-    </varlistentry>
-
     <varlistentry>
      <term><symbol>SHARED_DEPENDENCY_TABLESPACE</symbol> (<literal>t</literal>)</term>
      <listitem>
@@ -6919,6 +6901,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
    objects.
   </para>

+  <para>
+   As in the <structname>pg_depend</structname> catalog, most objects
+   created during <application>initdb</application> are
+   considered <quote>pinned</quote>.  No entries are made
+   in <structname>pg_shdepend</structname> that would have a pinned
+   object as either referenced or dependent object.
+  </para>
+
  </sect1>

  <sect1 id="catalog-pg-shdescription">
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index a22bf375f8..547199bb9c 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -541,11 +541,11 @@ GetNewObjectId(void)
      * FirstNormalObjectId since that range is reserved for initdb (see
      * IsCatalogRelationOid()).  Note we are relying on unsigned comparison.
      *
-     * During initdb, we start the OID generator at FirstBootstrapObjectId, so
+     * During initdb, we start the OID generator at FirstGenbkiObjectId, so
      * we only wrap if before that point when in bootstrap or standalone mode.
      * The first time through this routine after normal postmaster start, the
      * counter will be forced up to FirstNormalObjectId.  This mechanism
-     * leaves the OIDs between FirstBootstrapObjectId and FirstNormalObjectId
+     * leaves the OIDs between FirstGenbkiObjectId and FirstNormalObjectId
      * available for automatic assignment during initdb, while ensuring they
      * will never conflict with user-assigned OIDs.
      */
@@ -560,7 +560,7 @@ GetNewObjectId(void)
         else
         {
             /* we may be bootstrapping, so don't enforce the full range */
-            if (ShmemVariableCache->nextOid < ((Oid) FirstBootstrapObjectId))
+            if (ShmemVariableCache->nextOid < ((Oid) FirstGenbkiObjectId))
             {
                 /* wraparound in standalone mode (unlikely but possible) */
                 ShmemVariableCache->nextOid = FirstNormalObjectId;
@@ -586,6 +586,47 @@ GetNewObjectId(void)
     return result;
 }

+/*
+ * SetNextObjectId
+ *
+ * This may only be called during initdb; it advances the OID counter
+ * to the specified value.
+ */
+static void
+SetNextObjectId(Oid nextOid)
+{
+    /* Safety check, this is only allowable during initdb */
+    if (IsPostmasterEnvironment)
+        elog(ERROR, "cannot advance OID counter anymore");
+
+    /* Taking the lock is, therefore, just pro forma; but do it anyway */
+    LWLockAcquire(OidGenLock, LW_EXCLUSIVE);
+
+    if (ShmemVariableCache->nextOid > nextOid)
+        elog(ERROR, "too late to advance OID counter to %u, it is now %u",
+             nextOid, ShmemVariableCache->nextOid);
+
+    ShmemVariableCache->nextOid = nextOid;
+    ShmemVariableCache->oidCount = 0;
+
+    LWLockRelease(OidGenLock);
+}
+
+/*
+ * StopGeneratingPinnedObjectIds
+ *
+ * This is called once during initdb to force the OID counter up to
+ * FirstUnpinnedObjectId.  This supports letting initdb's post-bootstrap
+ * processing create some pinned objects early on.  Once it's done doing
+ * so, it calls this (via pg_stop_making_pinned_objects()) so that the
+ * remaining objects it makes will be considered un-pinned.
+ */
+void
+StopGeneratingPinnedObjectIds(void)
+{
+    SetNextObjectId(FirstUnpinnedObjectId);
+}
+

 #ifdef USE_ASSERT_CHECKING

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 441a9124cd..6b37550805 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -5288,7 +5288,7 @@ BootStrapXLOG(void)
     checkPoint.fullPageWrites = fullPageWrites;
     checkPoint.nextXid =
         FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
-    checkPoint.nextOid = FirstBootstrapObjectId;
+    checkPoint.nextOid = FirstGenbkiObjectId;
     checkPoint.nextMulti = FirstMultiXactId;
     checkPoint.nextMultiOffset = 0;
     checkPoint.oldestXid = FirstNormalTransactionId;
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 245d536372..7e0591539c 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -31,6 +31,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_largeobject.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
@@ -120,9 +121,9 @@ bool
 IsCatalogRelationOid(Oid relid)
 {
     /*
-     * We consider a relation to be a system catalog if it has an OID that was
-     * manually assigned or assigned by genbki.pl.  This includes all the
-     * defined catalogs, their indexes, and their TOAST tables and indexes.
+     * We consider a relation to be a system catalog if it has a pinned OID.
+     * This includes all the defined catalogs, their indexes, and their TOAST
+     * tables and indexes.
      *
      * This rule excludes the relations in information_schema, which are not
      * integral to the system and can be treated the same as user relations.
@@ -132,7 +133,7 @@ IsCatalogRelationOid(Oid relid)
      * This test is reliable since an OID wraparound will skip this range of
      * OIDs; see GetNewObjectId().
      */
-    return (relid < (Oid) FirstBootstrapObjectId);
+    return (relid < (Oid) FirstUnpinnedObjectId);
 }

 /*
@@ -294,6 +295,64 @@ IsSharedRelation(Oid relationId)
     return false;
 }

+/*
+ * IsPinnedObject
+ *        Given the class + OID identity of a database object, report whether
+ *        it is "pinned", that is not droppable because the system requires it.
+ *
+ * We used to represent this explicitly in pg_depend, but that proved to be
+ * an undesirable amount of overhead, so now we rely on an OID range test.
+ */
+bool
+IsPinnedObject(Oid classId, Oid objectId)
+{
+    /*
+     * Objects with OIDs above FirstUnpinnedObjectId are never pinned.  Since
+     * the OID generator skips this range when wrapping around, this check
+     * guarantees that user-defined objects are never considered pinned.
+     */
+    if (objectId >= FirstUnpinnedObjectId)
+        return false;
+
+    /*
+     * Large objects are never pinned.  We need this special case because
+     * their OIDs can be user-assigned.
+     */
+    if (classId == LargeObjectRelationId)
+        return false;
+
+    /*
+     * There are a few objects defined in the catalog .dat files that, as a
+     * matter of policy, we prefer not to treat as pinned.  We used to handle
+     * that by excluding them from pg_depend, but it's just as easy to
+     * hard-wire their OIDs here.  (If the user does indeed drop and recreate
+     * them, they'll have new but certainly-unpinned OIDs, so no problem.)
+     *
+     * Checking both classId and objectId is overkill, since OIDs below
+     * FirstGenbkiObjectId should be globally unique, but do it anyway for
+     * robustness.
+     */
+
+    /* template1 is not pinned */
+    if (classId == DatabaseRelationId &&
+        objectId == TemplateDbOid)
+        return false;
+
+    /* the public namespace is not pinned */
+    if (classId == NamespaceRelationId &&
+        objectId == PG_PUBLIC_NAMESPACE)
+        return false;
+
+    /*
+     * All other initdb-created objects are pinned.  This is overkill (the
+     * system doesn't really depend on having every last weird datatype, for
+     * instance) but generating only the minimum required set of dependencies
+     * seems hard, and enforcing an accurate list would be much more expensive
+     * than the simple range test used here.
+     */
+    return true;
+}
+

 /*
  * GetNewOidWithIndex
@@ -529,7 +588,8 @@ pg_nextoid(PG_FUNCTION_ARGS)
     if (!superuser())
         ereport(ERROR,
                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                 errmsg("must be superuser to call pg_nextoid()")));
+                 errmsg("must be superuser to call %s()",
+                        "pg_nextoid")));

     rel = table_open(reloid, RowExclusiveLock);
     idx = index_open(idxoid, RowExclusiveLock);
@@ -576,5 +636,29 @@ pg_nextoid(PG_FUNCTION_ARGS)
     table_close(rel, RowExclusiveLock);
     index_close(idx, RowExclusiveLock);

-    return newoid;
+    PG_RETURN_OID(newoid);
+}
+
+/*
+ * SQL callable interface for StopGeneratingPinnedObjectIds().
+ *
+ * This is only to be used by initdb, so it's intentionally not documented in
+ * the user facing docs.
+ */
+Datum
+pg_stop_making_pinned_objects(PG_FUNCTION_ARGS)
+{
+    /*
+     * Belt-and-suspenders check, since StopGeneratingPinnedObjectIds will
+     * fail anyway in non-single-user mode.
+     */
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("must be superuser to call %s()",
+                        "pg_stop_making_pinned_objects")));
+
+    StopGeneratingPinnedObjectIds();
+
+    PG_RETURN_VOID();
 }
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0c37fc1d53..76b65e39c4 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -18,6 +18,7 @@
 #include "access/htup_details.h"
 #include "access/table.h"
 #include "access/xact.h"
+#include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
@@ -520,6 +521,16 @@ findDependentObjects(const ObjectAddress *object,
     if (object_address_present_add_flags(object, objflags, targetObjects))
         return;

+    /*
+     * If the target object is pinned, we can just error out immediately; it
+     * won't have any objects recorded as depending on it.
+     */
+    if (IsPinnedObject(object->classId, object->objectId))
+        ereport(ERROR,
+                (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+                 errmsg("cannot drop %s because it is required by the database system",
+                        getObjectDescription(object, false))));
+
     /*
      * The target object might be internally dependent on some other object
      * (its "owner"), and/or be a member of an extension (also considered its
@@ -783,15 +794,6 @@ findDependentObjects(const ObjectAddress *object,
                 objflags |= DEPFLAG_IS_PART;
                 break;

-            case DEPENDENCY_PIN:
-
-                /*
-                 * Should not happen; PIN dependencies should have zeroes in
-                 * the depender fields...
-                 */
-                elog(ERROR, "incorrect use of PIN dependency with %s",
-                     getObjectDescription(object, false));
-                break;
             default:
                 elog(ERROR, "unrecognized dependency type '%c' for %s",
                      foundDep->deptype, getObjectDescription(object, false));
@@ -920,18 +922,6 @@ findDependentObjects(const ObjectAddress *object,
             case DEPENDENCY_EXTENSION:
                 subflags = DEPFLAG_EXTENSION;
                 break;
-            case DEPENDENCY_PIN:
-
-                /*
-                 * For a PIN dependency we just ereport immediately; there
-                 * won't be any others to report.
-                 */
-                ereport(ERROR,
-                        (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
-                         errmsg("cannot drop %s because it is required by the database system",
-                                getObjectDescription(object, false))));
-                subflags = 0;    /* keep compiler quiet */
-                break;
             default:
                 elog(ERROR, "unrecognized dependency type '%c' for %s",
                      foundDep->deptype, getObjectDescription(object, false));
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index 81363a0710..d22df056f1 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -168,14 +168,14 @@ die "found $found duplicate OID(s) in catalog data\n" if $found;


 # OIDs not specified in the input files are automatically assigned,
-# starting at FirstGenbkiObjectId, extending up to FirstBootstrapObjectId.
+# starting at FirstGenbkiObjectId, extending up to FirstUnpinnedObjectId.
 # We allow such OIDs to be assigned independently within each catalog.
 my $FirstGenbkiObjectId =
   Catalog::FindDefinedSymbol('access/transam.h', $include_path,
     'FirstGenbkiObjectId');
-my $FirstBootstrapObjectId =
+my $FirstUnpinnedObjectId =
   Catalog::FindDefinedSymbol('access/transam.h', $include_path,
-    'FirstBootstrapObjectId');
+    'FirstUnpinnedObjectId');
 # Hash of next available OID, indexed by catalog name.
 my %GenbkiNextOids;

@@ -1088,8 +1088,8 @@ sub assign_next_oid

     # Check that we didn't overrun available OIDs
     die
-      "genbki OID counter for $catname reached $result, overrunning FirstBootstrapObjectId\n"
-      if $result >= $FirstBootstrapObjectId;
+      "genbki OID counter for $catname reached $result, overrunning FirstUnpinnedObjectId\n"
+      if $result >= $FirstUnpinnedObjectId;

     return $result;
 }
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 54688094f5..10f3119670 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -17,6 +17,7 @@
 #include "access/genam.h"
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_constraint.h"
@@ -29,7 +30,7 @@
 #include "utils/rel.h"


-static bool isObjectPinned(const ObjectAddress *object, Relation rel);
+static bool isObjectPinned(const ObjectAddress *object);


 /*
@@ -69,8 +70,11 @@ recordMultipleDependencies(const ObjectAddress *depender,
         return;                    /* nothing to do */

     /*
-     * During bootstrap, do nothing since pg_depend may not exist yet. initdb
-     * will fill in appropriate pg_depend entries after bootstrap.
+     * During bootstrap, do nothing since pg_depend may not exist yet.
+     *
+     * Objects created during bootstrap are most likely pinned, and the few
+     * that are not do not have dependencies on each other, so that there
+     * would be no need to make a pg_depend entry anyway.
      */
     if (IsBootstrapProcessingMode())
         return;
@@ -99,7 +103,7 @@ recordMultipleDependencies(const ObjectAddress *depender,
          * need to record dependencies on it.  This saves lots of space in
          * pg_depend, so it's worth the time taken to check.
          */
-        if (isObjectPinned(referenced, dependDesc))
+        if (isObjectPinned(referenced))
             continue;

         if (slot_init_count < max_slots)
@@ -399,8 +403,6 @@ changeDependencyFor(Oid classId, Oid objectId,
     bool        oldIsPinned;
     bool        newIsPinned;

-    depRel = table_open(DependRelationId, RowExclusiveLock);
-
     /*
      * Check to see if either oldRefObjectId or newRefObjectId is pinned.
      * Pinned objects should not have any dependency entries pointing to them,
@@ -411,16 +413,14 @@ changeDependencyFor(Oid classId, Oid objectId,
     objAddr.objectId = oldRefObjectId;
     objAddr.objectSubId = 0;

-    oldIsPinned = isObjectPinned(&objAddr, depRel);
+    oldIsPinned = isObjectPinned(&objAddr);

     objAddr.objectId = newRefObjectId;

-    newIsPinned = isObjectPinned(&objAddr, depRel);
+    newIsPinned = isObjectPinned(&objAddr);

     if (oldIsPinned)
     {
-        table_close(depRel, RowExclusiveLock);
-
         /*
          * If both are pinned, we need do nothing.  However, return 1 not 0,
          * else callers will think this is an error case.
@@ -440,6 +440,8 @@ changeDependencyFor(Oid classId, Oid objectId,
         return 1;
     }

+    depRel = table_open(DependRelationId, RowExclusiveLock);
+
     /* There should be existing dependency record(s), so search. */
     ScanKeyInit(&key[0],
                 Anum_pg_depend_classid,
@@ -574,7 +576,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
     objAddr.objectId = oldRefObjectId;
     objAddr.objectSubId = 0;

-    if (isObjectPinned(&objAddr, depRel))
+    if (isObjectPinned(&objAddr))
         ereport(ERROR,
                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                  errmsg("cannot remove dependency on %s because it is a system object",
@@ -586,7 +588,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
      */
     objAddr.objectId = newRefObjectId;

-    newIsPinned = isObjectPinned(&objAddr, depRel);
+    newIsPinned = isObjectPinned(&objAddr);

     /* Now search for dependency records */
     ScanKeyInit(&key[0],
@@ -634,50 +636,14 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
  * isObjectPinned()
  *
  * Test if an object is required for basic database functionality.
- * Caller must already have opened pg_depend.
  *
  * The passed subId, if any, is ignored; we assume that only whole objects
  * are pinned (and that this implies pinning their components).
  */
 static bool
-isObjectPinned(const ObjectAddress *object, Relation rel)
+isObjectPinned(const ObjectAddress *object)
 {
-    bool        ret = false;
-    SysScanDesc scan;
-    HeapTuple    tup;
-    ScanKeyData key[2];
-
-    ScanKeyInit(&key[0],
-                Anum_pg_depend_refclassid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(object->classId));
-
-    ScanKeyInit(&key[1],
-                Anum_pg_depend_refobjid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(object->objectId));
-
-    scan = systable_beginscan(rel, DependReferenceIndexId, true,
-                              NULL, 2, key);
-
-    /*
-     * Since we won't generate additional pg_depend entries for pinned
-     * objects, there can be at most one entry referencing a pinned object.
-     * Hence, it's sufficient to look at the first returned tuple; we don't
-     * need to loop.
-     */
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
-
-        if (foundDep->deptype == DEPENDENCY_PIN)
-            ret = true;
-    }
-
-    systable_endscan(scan);
-
-    return ret;
+    return IsPinnedObject(object->classId, object->objectId);
 }


diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 86e415af89..36bfff9706 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -85,7 +85,7 @@ check_publication_add_relation(Relation targetrel)
  * XXX  This also excludes all tables with relid < FirstNormalObjectId,
  * ie all tables created during initdb.  This mainly affects the preinstalled
  * information_schema.  IsCatalogRelationOid() only excludes tables with
- * relid < FirstBootstrapObjectId, making that test rather redundant,
+ * relid < FirstUnpinnedObjectId, making that test rather redundant,
  * but really we should get rid of the FirstNormalObjectId test not
  * IsCatalogRelationOid.  We can't do so today because we don't want
  * information_schema tables to be considered publishable; but this test
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 420ad96565..94989119ed 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -101,7 +101,7 @@ static void storeObjectDescription(StringInfo descs,
                                    ObjectAddress *object,
                                    SharedDependencyType deptype,
                                    int count);
-static bool isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel);
+static bool isSharedObjectPinned(Oid classId, Oid objectId);


 /*
@@ -140,8 +140,7 @@ recordSharedDependencyOn(ObjectAddress *depender,
     sdepRel = table_open(SharedDependRelationId, RowExclusiveLock);

     /* If the referenced object is pinned, do nothing. */
-    if (!isSharedObjectPinned(referenced->classId, referenced->objectId,
-                              sdepRel))
+    if (!isSharedObjectPinned(referenced->classId, referenced->objectId))
     {
         shdepAddDependency(sdepRel, depender->classId, depender->objectId,
                            depender->objectSubId,
@@ -255,7 +254,7 @@ shdepChangeDep(Relation sdepRel,

     systable_endscan(scan);

-    if (isSharedObjectPinned(refclassid, refobjid, sdepRel))
+    if (isSharedObjectPinned(refclassid, refobjid))
     {
         /* No new entry needed, so just delete existing entry if any */
         if (oldtup)
@@ -513,7 +512,7 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
                 continue;

             /* Skip pinned roles; they don't need dependency entries */
-            if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+            if (isSharedObjectPinned(AuthIdRelationId, roleid))
                 continue;

             shdepAddDependency(sdepRel, classId, objectId, objsubId,
@@ -531,7 +530,7 @@ updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
                 continue;

             /* Skip pinned roles */
-            if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+            if (isSharedObjectPinned(AuthIdRelationId, roleid))
                 continue;

             shdepDropDependency(sdepRel, classId, objectId, objsubId,
@@ -626,8 +625,6 @@ shared_dependency_comparator(const void *a, const void *b)
  * on objects local to other databases.  We can (and do) provide descriptions
  * of the two former kinds of objects, but we can't do that for "remote"
  * objects, so we just provide a count of them.
- *
- * If we find a SHARED_DEPENDENCY_PIN entry, we can error out early.
  */
 bool
 checkSharedDependencies(Oid classId, Oid objectId,
@@ -649,6 +646,18 @@ checkSharedDependencies(Oid classId, Oid objectId,
     StringInfoData descs;
     StringInfoData alldescs;

+    /* This case can be dispatched quickly */
+    if (isSharedObjectPinned(classId, objectId))
+    {
+        object.classId = classId;
+        object.objectId = objectId;
+        object.objectSubId = 0;
+        ereport(ERROR,
+                (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+                 errmsg("cannot drop %s because it is required by the database system",
+                        getObjectDescription(&object, false))));
+    }
+
     /*
      * We limit the number of dependencies reported to the client to
      * MAX_REPORTED_DEPS, since client software may not deal well with
@@ -685,18 +694,6 @@ checkSharedDependencies(Oid classId, Oid objectId,
     {
         Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tup);

-        /* This case can be dispatched quickly */
-        if (sdepForm->deptype == SHARED_DEPENDENCY_PIN)
-        {
-            object.classId = classId;
-            object.objectId = objectId;
-            object.objectSubId = 0;
-            ereport(ERROR,
-                    (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
-                     errmsg("cannot drop %s because it is required by the database system",
-                            getObjectDescription(&object, false))));
-        }
-
         object.classId = sdepForm->classid;
         object.objectId = sdepForm->objid;
         object.objectSubId = sdepForm->objsubid;
@@ -1274,49 +1271,15 @@ storeObjectDescription(StringInfo descs,

 /*
  * isSharedObjectPinned
- *        Return whether a given shared object has a SHARED_DEPENDENCY_PIN entry.
- *
- * sdepRel must be the pg_shdepend relation, already opened and suitably
- * locked.
+ *        Return true if a shared object is pinned.
  */
 static bool
-isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel)
+isSharedObjectPinned(Oid classId, Oid objectId)
 {
-    bool        result = false;
-    ScanKeyData key[2];
-    SysScanDesc scan;
-    HeapTuple    tup;
-
-    ScanKeyInit(&key[0],
-                Anum_pg_shdepend_refclassid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(classId));
-    ScanKeyInit(&key[1],
-                Anum_pg_shdepend_refobjid,
-                BTEqualStrategyNumber, F_OIDEQ,
-                ObjectIdGetDatum(objectId));
-
-    scan = systable_beginscan(sdepRel, SharedDependReferenceIndexId, true,
-                              NULL, 2, key);
-
     /*
-     * Since we won't generate additional pg_shdepend entries for pinned
-     * objects, there can be at most one entry referencing a pinned object.
-     * Hence, it's sufficient to look at the first returned tuple; we don't
-     * need to loop.
+     * This is currently just the same as the normal pin test.
      */
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_shdepend shdepForm = (Form_pg_shdepend) GETSTRUCT(tup);
-
-        if (shdepForm->deptype == SHARED_DEPENDENCY_PIN)
-            result = true;
-    }
-
-    systable_endscan(scan);
-
-    return result;
+    return IsPinnedObject(classId, objectId);
 }

 /*
@@ -1359,7 +1322,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
         HeapTuple    tuple;

         /* Doesn't work for pinned objects */
-        if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+        if (isSharedObjectPinned(AuthIdRelationId, roleid))
         {
             ObjectAddress obj;

@@ -1402,7 +1365,6 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
             switch (sdepForm->deptype)
             {
                     /* Shouldn't happen */
-                case SHARED_DEPENDENCY_PIN:
                 case SHARED_DEPENDENCY_INVALID:
                     elog(ERROR, "unexpected dependency type");
                     break;
@@ -1506,7 +1468,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
         Oid            roleid = lfirst_oid(cell);

         /* Refuse to work on pinned roles */
-        if (isSharedObjectPinned(AuthIdRelationId, roleid, sdepRel))
+        if (isSharedObjectPinned(AuthIdRelationId, roleid))
         {
             ObjectAddress obj;

@@ -1549,10 +1511,6 @@ shdepReassignOwned(List *roleids, Oid newrole)
                 sdepForm->dbid != InvalidOid)
                 continue;

-            /* Unexpected because we checked for pins above */
-            if (sdepForm->deptype == SHARED_DEPENDENCY_PIN)
-                elog(ERROR, "unexpected shared pin");
-
             /* We leave non-owner dependencies alone */
             if (sdepForm->deptype != SHARED_DEPENDENCY_OWNER)
                 continue;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac46b..c4ff6962a7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11988,10 +11988,6 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
         Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
         ObjectAddress foundObject;

-        /* We don't expect any PIN dependencies on columns */
-        if (foundDep->deptype == DEPENDENCY_PIN)
-            elog(ERROR, "cannot alter type of a pinned column");
-
         foundObject.classId = foundDep->classid;
         foundObject.objectId = foundDep->objid;
         foundObject.objectSubId = foundDep->objsubid;
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 69ea155d50..0385fd6121 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -449,7 +449,6 @@ DropTableSpace(DropTableSpaceStmt *stmt)
             ereport(NOTICE,
                     (errmsg("tablespace \"%s\" does not exist, skipping",
                             tablespacename)));
-            /* XXX I assume I need one or both of these next two calls */
             table_endscan(scandesc);
             table_close(rel, NoLock);
         }
@@ -465,8 +464,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
                        tablespacename);

     /* Disallow drop of the standard tablespaces, even by superuser */
-    if (tablespaceoid == GLOBALTABLESPACE_OID ||
-        tablespaceoid == DEFAULTTABLESPACE_OID)
+    if (IsPinnedObject(TableSpaceRelationId, tablespaceoid))
         aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_TABLESPACE,
                        tablespacename);

diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 61ccfd300b..6ebae95ec4 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2952,11 +2952,11 @@ record_plan_function_dependency(PlannerInfo *root, Oid funcid)
      * For performance reasons, we don't bother to track built-in functions;
      * we just assume they'll never change (or at least not in ways that'd
      * invalidate plans using them).  For this purpose we can consider a
-     * built-in function to be one with OID less than FirstBootstrapObjectId.
+     * built-in function to be one with OID less than FirstUnpinnedObjectId.
      * Note that the OID generator guarantees never to generate such an OID
      * after startup, even at OID wraparound.
      */
-    if (funcid >= (Oid) FirstBootstrapObjectId)
+    if (funcid >= (Oid) FirstUnpinnedObjectId)
     {
         PlanInvalItem *inval_item = makeNode(PlanInvalItem);

@@ -2992,7 +2992,7 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
      * As in record_plan_function_dependency, ignore the possibility that
      * someone would change a built-in domain.
      */
-    if (typid >= (Oid) FirstBootstrapObjectId)
+    if (typid >= (Oid) FirstUnpinnedObjectId)
     {
         PlanInvalItem *inval_item = makeNode(PlanInvalItem);

diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index d493aeef0f..56267bdc3c 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -494,7 +494,7 @@ static void ReleasePredicateLocksLocal(void);
 static inline bool
 PredicateLockingNeededForRelation(Relation relation)
 {
-    return !(relation->rd_id < FirstBootstrapObjectId ||
+    return !(relation->rd_id < FirstUnpinnedObjectId ||
              RelationUsesLocalBuffers(relation) ||
              relation->rd_rel->relkind == RELKIND_MATVIEW);
 }
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 152d21e88b..39646ee88d 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1522,83 +1522,10 @@ setup_depend(FILE *cmdfd)
     const char *const *line;
     static const char *const pg_depend_setup[] = {
         /*
-         * Make PIN entries in pg_depend for all objects made so far in the
-         * tables that the dependency code handles.  This is overkill (the
-         * system doesn't really depend on having every last weird datatype,
-         * for instance) but generating only the minimum required set of
-         * dependencies seems hard.
-         *
-         * Catalogs that are intentionally not scanned here are:
-         *
-         * pg_database: it's a feature, not a bug, that template1 is not
+         * Advance the OID counter so that subsequently-created objects aren't
          * pinned.
-         *
-         * pg_extension: a pinned extension isn't really an extension, hmm?
-         *
-         * pg_tablespace: tablespaces don't participate in the dependency
-         * code, and DropTableSpace() explicitly protects the built-in
-         * tablespaces.
-         *
-         * First delete any already-made entries; PINs override all else, and
-         * must be the only entries for their objects.
-         */
-        "DELETE FROM pg_depend;\n\n",
-        "VACUUM pg_depend;\n\n",
-        "DELETE FROM pg_shdepend;\n\n",
-        "VACUUM pg_shdepend;\n\n",
-
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_class;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_proc;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_type;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_cast;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_constraint;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_conversion;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_attrdef;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_language;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_operator;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_opclass;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_opfamily;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_am;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_amop;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_amproc;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_rewrite;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_trigger;\n\n",
-
-        /*
-         * restriction here to avoid pinning the public namespace
          */
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_namespace "
-        "    WHERE nspname LIKE 'pg%';\n\n",
-
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_parser;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_dict;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_template;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_ts_config;\n\n",
-        "INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
-        " FROM pg_collation;\n\n",
-        "INSERT INTO pg_shdepend SELECT 0,0,0,0, tableoid,oid, 'p' "
-        " FROM pg_authid;\n\n",
+        "SELECT pg_stop_making_pinned_objects();\n\n",
         NULL
     };

diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c
index 805dafef07..2601f70a04 100644
--- a/src/bin/pg_resetwal/pg_resetwal.c
+++ b/src/bin/pg_resetwal/pg_resetwal.c
@@ -686,7 +686,7 @@ GuessControlValues(void)
     ControlFile.checkPointCopy.fullPageWrites = false;
     ControlFile.checkPointCopy.nextXid =
         FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
-    ControlFile.checkPointCopy.nextOid = FirstBootstrapObjectId;
+    ControlFile.checkPointCopy.nextOid = FirstGenbkiObjectId;
     ControlFile.checkPointCopy.nextMulti = FirstMultiXactId;
     ControlFile.checkPointCopy.nextMultiOffset = 0;
     ControlFile.checkPointCopy.oldestXid = FirstNormalTransactionId;
diff --git a/src/include/access/transam.h b/src/include/access/transam.h
index 2fe8a59110..d22de19c94 100644
--- a/src/include/access/transam.h
+++ b/src/include/access/transam.h
@@ -165,10 +165,14 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  *        when the .dat files in src/include/catalog/ do not specify an OID
  *        for a catalog entry that requires one.  Note that genbki.pl assigns
  *        these OIDs independently in each catalog, so they're not guaranteed
- *        to be globally unique.
+ *        to be globally unique.  Furthermore, the bootstrap backend and
+ *        initdb's post-bootstrap processing can also assign OIDs in this range.
+ *        The normal OID-generation logic takes care of any OID conflicts that
+ *        might arise from that.
  *
- *        OIDS 12000-16383 are reserved for assignment during initdb
- *        using the OID generator.  (We start the generator at 12000.)
+ *        OIDs 12000-16383 are reserved for unpinned objects created by initdb's
+ *        post-bootstrap processing.  initdb forces the OID generator up to
+ *        12000 as soon as it's made the pinned objects it's responsible for.
  *
  *        OIDs beginning at 16384 are assigned from the OID generator
  *        during normal multiuser operation.  (We force the generator up to
@@ -184,11 +188,12 @@ FullTransactionIdAdvance(FullTransactionId *dest)
  *
  * NOTE: if the OID generator wraps around, we skip over OIDs 0-16383
  * and resume with 16384.  This minimizes the odds of OID conflict, by not
- * reassigning OIDs that might have been assigned during initdb.
+ * reassigning OIDs that might have been assigned during initdb.  Critically,
+ * it also ensures that no user-created object will be considered pinned.
  * ----------
  */
 #define FirstGenbkiObjectId        10000
-#define FirstBootstrapObjectId    12000
+#define FirstUnpinnedObjectId    12000
 #define FirstNormalObjectId        16384

 /*
@@ -289,6 +294,7 @@ extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
 extern void AdvanceOldestClogXid(TransactionId oldest_datfrozenxid);
 extern bool ForceTransactionIdLimitUpdate(void);
 extern Oid    GetNewObjectId(void);
+extern void StopGeneratingPinnedObjectIds(void);

 #ifdef USE_ASSERT_CHECKING
 extern void AssertTransactionIdInAllowableRange(TransactionId xid);
diff --git a/src/include/catalog/catalog.h b/src/include/catalog/catalog.h
index f247be50b4..ef2e88fe45 100644
--- a/src/include/catalog/catalog.h
+++ b/src/include/catalog/catalog.h
@@ -34,6 +34,8 @@ extern bool IsReservedName(const char *name);

 extern bool IsSharedRelation(Oid relationId);

+extern bool IsPinnedObject(Oid classId, Oid objectId);
+
 extern Oid    GetNewOidWithIndex(Relation relation, Oid indexId,
                                AttrNumber oidcolumn);
 extern Oid    GetNewRelFileNode(Oid reltablespace, Relation pg_class,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index fd44081e74..2885f35ccd 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -36,8 +36,7 @@ typedef enum DependencyType
     DEPENDENCY_PARTITION_PRI = 'P',
     DEPENDENCY_PARTITION_SEC = 'S',
     DEPENDENCY_EXTENSION = 'e',
-    DEPENDENCY_AUTO_EXTENSION = 'x',
-    DEPENDENCY_PIN = 'p'
+    DEPENDENCY_AUTO_EXTENSION = 'x'
 } DependencyType;

 /*
@@ -47,27 +46,21 @@ typedef enum DependencyType
  * unless the dependent object is dropped at the same time.  There are some
  * additional rules however:
  *
- * (a) For a SHARED_DEPENDENCY_PIN entry, there is no dependent object --
- * rather, the referenced object is an essential part of the system.  This
- * applies to the initdb-created superuser.  Entries of this type are only
- * created by initdb; objects in this category don't need further pg_shdepend
- * entries if more objects come to depend on them.
- *
- * (b) a SHARED_DEPENDENCY_OWNER entry means that the referenced object is
+ * (a) a SHARED_DEPENDENCY_OWNER entry means that the referenced object is
  * the role owning the dependent object.  The referenced object must be
  * a pg_authid entry.
  *
- * (c) a SHARED_DEPENDENCY_ACL entry means that the referenced object is
+ * (b) a SHARED_DEPENDENCY_ACL entry means that the referenced object is
  * a role mentioned in the ACL field of the dependent object.  The referenced
  * object must be a pg_authid entry.  (SHARED_DEPENDENCY_ACL 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
+ * (c) 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.
  *
- * (e) a SHARED_DEPENDENCY_TABLESPACE entry means that the referenced
+ * (d) 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
@@ -78,7 +71,6 @@ typedef enum DependencyType
  */
 typedef enum SharedDependencyType
 {
-    SHARED_DEPENDENCY_PIN = 'p',
     SHARED_DEPENDENCY_OWNER = 'o',
     SHARED_DEPENDENCY_ACL = 'a',
     SHARED_DEPENDENCY_POLICY = 'r',
diff --git a/src/include/catalog/pg_depend.h b/src/include/catalog/pg_depend.h
index e0bc114145..555523697f 100644
--- a/src/include/catalog/pg_depend.h
+++ b/src/include/catalog/pg_depend.h
@@ -4,8 +4,9 @@
  *      definition of the "dependency" system catalog (pg_depend)
  *
  * pg_depend has no preloaded contents, so there is no pg_depend.dat
- * file; system-defined dependencies are loaded into it during a late stage
- * of the initdb process.
+ * file; dependencies for system-defined objects are loaded into it
+ * on-the-fly during initdb.  Most built-in objects are pinned anyway,
+ * and hence need no explicit entries in pg_depend.
  *
  * NOTE: we do not represent all possible dependency pairs in pg_depend;
  * for example, there's not much value in creating an explicit dependency
@@ -42,11 +43,9 @@ CATALOG(pg_depend,2608,DependRelationId)
 {
     /*
      * Identification of the dependent (referencing) object.
-     *
-     * These fields are all zeroes for a DEPENDENCY_PIN entry.
      */
-    Oid            classid BKI_LOOKUP_OPT(pg_class);    /* OID of table containing
-                                                     * object */
+    Oid            classid BKI_LOOKUP(pg_class);    /* OID of table containing
+                                                 * object */
     Oid            objid;            /* OID of object itself */
     int32        objsubid;        /* column number, or 0 if not used */

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index acbcae4607..00748bd898 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3295,6 +3295,10 @@
   proname => 'pg_nextoid', provolatile => 'v', proparallel => 'u',
   prorettype => 'oid', proargtypes => 'regclass name regclass',
   prosrc => 'pg_nextoid' },
+{ oid => '8922', descr => 'stop making pinned objects during initdb',
+  proname => 'pg_stop_making_pinned_objects', provolatile => 'v',
+  proparallel => 'u', prorettype => 'void', proargtypes => '',
+  prosrc => 'pg_stop_making_pinned_objects' },

 { oid => '1579', descr => 'I/O',
   proname => 'varbit_in', prorettype => 'varbit',
diff --git a/src/include/catalog/pg_shdepend.h b/src/include/catalog/pg_shdepend.h
index 4faa95794d..4223717805 100644
--- a/src/include/catalog/pg_shdepend.h
+++ b/src/include/catalog/pg_shdepend.h
@@ -4,8 +4,9 @@
  *      definition of the "shared dependency" system catalog (pg_shdepend)
  *
  * pg_shdepend has no preloaded contents, so there is no pg_shdepend.dat
- * file; system-defined dependencies are loaded into it during a late stage
- * of the initdb process.
+ * file; dependencies for system-defined objects are loaded into it
+ * on-the-fly during initdb.  Most built-in objects are pinned anyway,
+ * and hence need no explicit entries in pg_shdepend.
  *
  * NOTE: we do not represent all possible dependency pairs in pg_shdepend;
  * for example, there's not much value in creating an explicit dependency
@@ -39,13 +40,12 @@ CATALOG(pg_shdepend,1214,SharedDependRelationId) BKI_SHARED_RELATION
     /*
      * Identification of the dependent (referencing) object.
      *
-     * These fields are all zeroes for a DEPENDENCY_PIN entry.  Also, dbid can
-     * be zero to denote a shared object.
+     * Note that dbid can be zero to denote a shared object.
      */
     Oid            dbid BKI_LOOKUP_OPT(pg_database);    /* OID of database
                                                      * containing object */
-    Oid            classid BKI_LOOKUP_OPT(pg_class);    /* OID of table containing
-                                                     * object */
+    Oid            classid BKI_LOOKUP(pg_class);    /* OID of table containing
+                                                 * object */
     Oid            objid;            /* OID of object itself */
     int32        objsubid;        /* column number, or 0 if not used */

diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index a67f40198a..a57fd142a9 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -11,75 +11,26 @@
 -- NB: run this test early, because some later tests create bogus entries.
 -- **************** pg_depend ****************
 -- Look for illegal values in pg_depend fields.
--- classid/objid can be zero, but only in 'p' entries
 SELECT *
 FROM pg_depend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'e', 'i', 'n', 'p') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (classid != 0 OR objid != 0 OR objsubid != 0));
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'e', 'i', 'n', 'x', 'P', 'S');
  classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype
 ---------+-------+----------+------------+----------+-------------+---------
 (0 rows)

 -- **************** pg_shdepend ****************
 -- Look for illegal values in pg_shdepend fields.
--- classid/objid can be zero, but only in 'p' entries
 SELECT *
 FROM pg_shdepend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'o', 'p', 'r') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (dbid != 0 OR classid != 0 OR objid != 0 OR objsubid != 0));
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'o', 'r', 't');
  dbid | classid | objid | objsubid | refclassid | refobjid | deptype
 ------+---------+-------+----------+------------+----------+---------
 (0 rows)

--- Check each OID-containing system catalog to see if its lowest-numbered OID
--- is pinned.  If not, and if that OID was generated during initdb, then
--- perhaps initdb forgot to scan that catalog for pinnable entries.
--- Generally, it's okay for a catalog to be listed in the output of this
--- test if that catalog is scanned by initdb.c's setup_depend() function;
--- whatever OID the test is complaining about must have been added later
--- in initdb, where it intentionally isn't pinned.  Legitimate exceptions
--- to that rule are listed in the comments in setup_depend().
--- Currently, pg_rewrite is also listed by this check, even though it is
--- covered by setup_depend().  That happens because there are no rules in
--- the pinned data, but initdb creates some intentionally-not-pinned views.
-do $$
-declare relnm text;
-  reloid oid;
-  shared bool;
-  lowoid oid;
-  pinned bool;
-begin
-for relnm, reloid, shared in
-  select relname, oid, relisshared from pg_class
-  where EXISTS(
-      SELECT * FROM pg_attribute
-      WHERE attrelid = pg_class.oid AND attname = 'oid')
-    and relkind = 'r' and oid < 16384 order by 1
-loop
-  execute 'select min(oid) from ' || relnm into lowoid;
-  continue when lowoid is null or lowoid >= 16384;
-  if shared then
-    pinned := exists(select 1 from pg_shdepend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  else
-    pinned := exists(select 1 from pg_depend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  end if;
-  if not pinned then
-    raise notice '% contains unpinned initdb-created object(s)', relnm;
-  end if;
-end loop;
-end$$;
-NOTICE:  pg_database contains unpinned initdb-created object(s)
-NOTICE:  pg_extension contains unpinned initdb-created object(s)
-NOTICE:  pg_rewrite contains unpinned initdb-created object(s)
-NOTICE:  pg_tablespace contains unpinned initdb-created object(s)
 -- **************** pg_class ****************
 -- Look for system tables with varlena columns but no toast table. All
 -- system tables with toastable columns should have toast tables, with
diff --git a/src/test/regress/sql/misc_sanity.sql b/src/test/regress/sql/misc_sanity.sql
index 9699f5cc3b..2c0f87a651 100644
--- a/src/test/regress/sql/misc_sanity.sql
+++ b/src/test/regress/sql/misc_sanity.sql
@@ -14,70 +14,24 @@
 -- **************** pg_depend ****************

 -- Look for illegal values in pg_depend fields.
--- classid/objid can be zero, but only in 'p' entries

 SELECT *
 FROM pg_depend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'e', 'i', 'n', 'p') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (classid != 0 OR objid != 0 OR objsubid != 0));
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'e', 'i', 'n', 'x', 'P', 'S');
+

 -- **************** pg_shdepend ****************

 -- Look for illegal values in pg_shdepend fields.
--- classid/objid can be zero, but only in 'p' entries

 SELECT *
 FROM pg_shdepend as d1
 WHERE refclassid = 0 OR refobjid = 0 OR
-      deptype NOT IN ('a', 'o', 'p', 'r') OR
-      (deptype != 'p' AND (classid = 0 OR objid = 0)) OR
-      (deptype = 'p' AND (dbid != 0 OR classid != 0 OR objid != 0 OR objsubid != 0));
-
-
--- Check each OID-containing system catalog to see if its lowest-numbered OID
--- is pinned.  If not, and if that OID was generated during initdb, then
--- perhaps initdb forgot to scan that catalog for pinnable entries.
--- Generally, it's okay for a catalog to be listed in the output of this
--- test if that catalog is scanned by initdb.c's setup_depend() function;
--- whatever OID the test is complaining about must have been added later
--- in initdb, where it intentionally isn't pinned.  Legitimate exceptions
--- to that rule are listed in the comments in setup_depend().
--- Currently, pg_rewrite is also listed by this check, even though it is
--- covered by setup_depend().  That happens because there are no rules in
--- the pinned data, but initdb creates some intentionally-not-pinned views.
-
-do $$
-declare relnm text;
-  reloid oid;
-  shared bool;
-  lowoid oid;
-  pinned bool;
-begin
-for relnm, reloid, shared in
-  select relname, oid, relisshared from pg_class
-  where EXISTS(
-      SELECT * FROM pg_attribute
-      WHERE attrelid = pg_class.oid AND attname = 'oid')
-    and relkind = 'r' and oid < 16384 order by 1
-loop
-  execute 'select min(oid) from ' || relnm into lowoid;
-  continue when lowoid is null or lowoid >= 16384;
-  if shared then
-    pinned := exists(select 1 from pg_shdepend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  else
-    pinned := exists(select 1 from pg_depend
-                     where refclassid = reloid and refobjid = lowoid
-                     and deptype = 'p');
-  end if;
-  if not pinned then
-    raise notice '% contains unpinned initdb-created object(s)', relnm;
-  end if;
-end loop;
-end$$;
+      classid = 0 OR objid = 0 OR
+      deptype NOT IN ('a', 'o', 'r', 't');
+

 -- **************** pg_class ****************


Re: Replacing pg_depend PIN entries with a fixed range check

From
John Naylor
Date:
On Thu, May 27, 2021 at 6:53 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Attached is a rebase over a4390abec.

Looks good to me overall, I just had a couple questions/comments:

isObjectPinned and isSharedObjectPinned are now thin wrappers around IsPinnedObject. Is keeping those functions a matter of future-proofing in case something needs to be handled differently someday, or reducing unnecessary code churn?

setup_depend now doesn't really need to execute any SQL (unless third-party forks have extra steps here?), and could be replaced with a direct call to StopGeneratingPinnedObjectIds. That's a bit more self-documenting, and that would allow shortening this comment:

 /*
* Note that no objects created after setup_depend() will be "pinned".
* They are all droppable at the whim of the DBA.
*/

--
John Naylor
EDB: http://www.enterprisedb.com

Re: Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
John Naylor <john.naylor@enterprisedb.com> writes:
> On Thu, May 27, 2021 at 6:53 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Attached is a rebase over a4390abec.

> Looks good to me overall, I just had a couple questions/comments:

Thanks for looking!

> isObjectPinned and isSharedObjectPinned are now thin wrappers around
> IsPinnedObject. Is keeping those functions a matter of future-proofing in
> case something needs to be handled differently someday, or reducing
> unnecessary code churn?

Yeah, it was mostly a matter of reducing code churn.  We could probably
drop isSharedObjectPinned altogether, but isObjectPinned seems to have
some notational value in providing an API that takes an ObjectAddress.

> setup_depend now doesn't really need to execute any SQL (unless third-party
> forks have extra steps here?), and could be replaced with a direct call
> to StopGeneratingPinnedObjectIds. That's a bit more self-documenting, and
> that would allow shortening this comment:

Hm, I'm not following?  setup_depend runs in initdb, that is on the
client side.  It can't invoke backend-internal functions any other
way than via SQL, AFAICS.

            regards, tom lane



Re: Replacing pg_depend PIN entries with a fixed range check

From
John Naylor
Date:
On Wed, Jul 14, 2021 at 3:34 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

> Hm, I'm not following?  setup_depend runs in initdb, that is on the
> client side.  It can't invoke backend-internal functions any other
> way than via SQL, AFAICS.

Ah, brainfade on my part.

I was also curious about the test case where Andres fixed a regression in the parent thread [1], and there is a noticeable improvement (lowest of 10 measurements):

HEAD: 623ms
patch: 567ms

If no one else has anything, I think this is ready for commit.

[1] https://www.postgresql.org/message-id/20210406043521.lopeo7bbigad3n6t%40alap3.anarazel.de

--
John Naylor
EDB: http://www.enterprisedb.com

Re: Replacing pg_depend PIN entries with a fixed range check

From
Tom Lane
Date:
John Naylor <john.naylor@enterprisedb.com> writes:
> If no one else has anything, I think this is ready for commit.

Pushed, after adopting the suggestion to dispense with
isSharedObjectPinned.

            regards, tom lane