Re: Fundamental scheduling bug in parallel restore of partitioned tables - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Fundamental scheduling bug in parallel restore of partitioned tables
Date
Msg-id 729065.1744330102@sss.pgh.pa.us
Whole thread Raw
In response to Fundamental scheduling bug in parallel restore of partitioned tables  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Fundamental scheduling bug in parallel restore of partitioned tables
List pgsql-hackers
I wrote:
> I think that the most intellectually rigorous solution is to
> generate dummy TABLE DATA objects for partitioned tables, which
> don't actually contain data but merely carry dependencies on
> each of the child tables' TABLE DATA objects.

Here's a draft patch for this.  It seems to fix the problem in
light testing.  Some notes:

* Quite a lot of the patch is concerned with making various places
treat the new PARTITIONED DATA TOC entry type the same as TABLE DATA.
I considered removing that distinction and representing a partitioned
table's data object as TABLE DATA with no dataDumper, but it seems to
me this way is clearer.  Maybe others will think differently though;
it'd make for a smaller patch.

* It's annoying that we have to touch _tocEntryRequired's "Special
Case" logic for deciding whether an entry is schema or data, because
that means that old copies of pg_restore will think these entries are
schema and thus ignore them in a data-only restore.  But I think it
doesn't matter too much, because in a data-only restore we'd not be
creating indexes or foreign keys, so the scheduling bug isn't really
problematic.

* I'm not quite certain whether identify_locking_dependencies() needs
to treat PARTITIONED DATA dependencies as lockable.  I assumed here
that it does, but maybe we don't take out exclusive locks on
partitioned tables during restore?

* I noticed that a --data-only dump of the regression database now
complains:

$ pg_dump --data-only regression >r.dump
pg_dump: warning: there are circular foreign-key constraints on this table:
pg_dump: detail: parted_self_fk
pg_dump: hint: You might not be able to restore the dump without using --disable-triggers or temporarily dropping the
constraints.
pg_dump: hint: Consider using a full dump instead of a --data-only dump to avoid this problem.

The existing code does not produce this warning, but I think doing so
is correct.  The reason we missed the issue before is that
getTableDataFKConstraints ignores tables without a dataObj, so before
this patch it ignored partitioned tables altogether.

Comments?

            regards, tom lane

diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index f961162f365..a05ff716fe6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -1988,12 +1988,14 @@ buildTocEntryArrays(ArchiveHandle *AH)
         AH->tocsByDumpId[te->dumpId] = te;

         /*
-         * tableDataId provides the TABLE DATA item's dump ID for each TABLE
-         * TOC entry that has a DATA item.  We compute this by reversing the
-         * TABLE DATA item's dependency, knowing that a TABLE DATA item has
-         * just one dependency and it is the TABLE item.
+         * tableDataId provides the DATA item's dump ID for each TABLE TOC
+         * entry that has a TABLE DATA or PARTITIONED DATA item.  We compute
+         * this by reversing the DATA item's dependency, knowing that its
+         * first dependency is the TABLE item.
          */
-        if (strcmp(te->desc, "TABLE DATA") == 0 && te->nDeps > 0)
+        if (te->nDeps > 0 &&
+            (strcmp(te->desc, "TABLE DATA") == 0 ||
+             strcmp(te->desc, "PARTITIONED DATA") == 0))
         {
             DumpId        tableId = te->dependencies[0];

@@ -2003,7 +2005,7 @@ buildTocEntryArrays(ArchiveHandle *AH)
              * item's dump ID, so there should be a place for it in the array.
              */
             if (tableId <= 0 || tableId > maxDumpId)
-                pg_fatal("bad table dumpId for TABLE DATA item");
+                pg_fatal("bad table dumpId for %s item", te->desc);

             AH->tableDataId[tableId] = te->dumpId;
         }
@@ -3140,6 +3142,7 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
         {
             if (strcmp(te->desc, "TABLE") == 0 ||
                 strcmp(te->desc, "TABLE DATA") == 0 ||
+                strcmp(te->desc, "PARTITIONED DATA") == 0 ||
                 strcmp(te->desc, "VIEW") == 0 ||
                 strcmp(te->desc, "FOREIGN TABLE") == 0 ||
                 strcmp(te->desc, "MATERIALIZED VIEW") == 0 ||
@@ -3194,13 +3197,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
     if (!te->hadDumper)
     {
         /*
-         * Special Case: If 'SEQUENCE SET' or anything to do with LOs, then it
-         * is considered a data entry.  We don't need to check for BLOBS or
-         * old-style BLOB COMMENTS entries, because they will have hadDumper =
-         * true ... but we do need to check new-style BLOB ACLs, comments,
-         * etc.
+         * Special Case: If 'PARTITIONED DATA', 'SEQUENCE SET' or anything to
+         * do with LOs, then it is considered a data entry.  We don't need to
+         * check for BLOBS or old-style BLOB COMMENTS entries, because they
+         * will have hadDumper = true ... but we do need to check new-style
+         * BLOB ACLs, comments, etc.
          */
-        if (strcmp(te->desc, "SEQUENCE SET") == 0 ||
+        if (strcmp(te->desc, "PARTITIONED DATA") == 0 ||
+            strcmp(te->desc, "SEQUENCE SET") == 0 ||
             strcmp(te->desc, "BLOB") == 0 ||
             strcmp(te->desc, "BLOB METADATA") == 0 ||
             (strcmp(te->desc, "ACL") == 0 &&
@@ -4996,14 +5000,14 @@ identify_locking_dependencies(ArchiveHandle *AH, TocEntry *te)
         return;

     /*
-     * We assume the entry requires exclusive lock on each TABLE or TABLE DATA
-     * item listed among its dependencies.  Originally all of these would have
-     * been TABLE items, but repoint_table_dependencies would have repointed
-     * them to the TABLE DATA items if those are present (which they might not
-     * be, eg in a schema-only dump).  Note that all of the entries we are
-     * processing here are POST_DATA; otherwise there might be a significant
-     * difference between a dependency on a table and a dependency on its
-     * data, so that closer analysis would be needed here.
+     * We assume the entry requires exclusive lock on each TABLE, TABLE DATA,
+     * or PARTITIONED DATA item listed among its dependencies.  Originally all
+     * of these would have been TABLE items, but repoint_table_dependencies
+     * would have repointed them to the DATA items if those are present (which
+     * they might not be, eg in a schema-only dump).  Note that all of the
+     * entries we are processing here are POST_DATA; otherwise there might be
+     * a significant difference between a dependency on a table and a
+     * dependency on its data, so that closer analysis would be needed here.
      */
     lockids = (DumpId *) pg_malloc(te->nDeps * sizeof(DumpId));
     nlockids = 0;
@@ -5012,8 +5016,9 @@ identify_locking_dependencies(ArchiveHandle *AH, TocEntry *te)
         DumpId        depid = te->dependencies[i];

         if (depid <= AH->maxDumpId && AH->tocsByDumpId[depid] != NULL &&
-            ((strcmp(AH->tocsByDumpId[depid]->desc, "TABLE DATA") == 0) ||
-             strcmp(AH->tocsByDumpId[depid]->desc, "TABLE") == 0))
+            (strcmp(AH->tocsByDumpId[depid]->desc, "TABLE") == 0 ||
+             strcmp(AH->tocsByDumpId[depid]->desc, "TABLE DATA") == 0 ||
+             strcmp(AH->tocsByDumpId[depid]->desc, "PARTITIONED DATA") == 0))
             lockids[nlockids++] = depid;
     }

diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 365073b3eae..5e2eaf2d4c6 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -310,7 +310,8 @@ struct _archiveHandle

     /* arrays created after the TOC list is complete: */
     struct _tocEntry **tocsByDumpId;    /* TOCs indexed by dumpId */
-    DumpId       *tableDataId;    /* TABLE DATA ids, indexed by table dumpId */
+    DumpId       *tableDataId;    /* TABLE DATA and PARTITIONED DATA dumpIds,
+                                 * indexed by table dumpId */

     struct _tocEntry *currToc;    /* Used when dumping data */
     pg_compress_specification compression_spec; /* Requested specification for
diff --git a/src/bin/pg_dump/pg_backup_custom.c b/src/bin/pg_dump/pg_backup_custom.c
index f7c3af56304..42dda2c4220 100644
--- a/src/bin/pg_dump/pg_backup_custom.c
+++ b/src/bin/pg_dump/pg_backup_custom.c
@@ -25,6 +25,8 @@
  */
 #include "postgres_fe.h"

+#include <limits.h>
+
 #include "common/file_utils.h"
 #include "compress_io.h"
 #include "pg_backup_utils.h"
@@ -819,10 +821,10 @@ _ReopenArchive(ArchiveHandle *AH)
 /*
  * Prepare for parallel restore.
  *
- * The main thing that needs to happen here is to fill in TABLE DATA and BLOBS
- * TOC entries' dataLength fields with appropriate values to guide the
- * ordering of restore jobs.  The source of said data is format-dependent,
- * as is the exact meaning of the values.
+ * The main thing that needs to happen here is to fill in TABLE DATA,
+ * PARTITIONED_DATA, and BLOBS TOC entries' dataLength fields with appropriate
+ * values to guide the ordering of restore jobs.  The source of said data is
+ * format-dependent, as is the exact meaning of the values.
  *
  * A format module might also choose to do other setup here.
  */
@@ -830,6 +832,7 @@ static void
 _PrepParallelRestore(ArchiveHandle *AH)
 {
     lclContext *ctx = (lclContext *) AH->formatData;
+    bool        have_partitioned_data = false;
     TocEntry   *prev_te = NULL;
     lclTocEntry *prev_tctx = NULL;
     TocEntry   *te;
@@ -843,6 +846,10 @@ _PrepParallelRestore(ArchiveHandle *AH)
     {
         lclTocEntry *tctx = (lclTocEntry *) te->formatData;

+        /* Track whether there are any PARTITIONED_DATA items */
+        if (!have_partitioned_data && strcmp(te->desc, "PARTITIONED DATA") == 0)
+            have_partitioned_data = true;
+
         /*
          * Ignore entries without a known data offset; if we were unable to
          * seek to rewrite the TOC when creating the archive, this'll be all
@@ -873,6 +880,38 @@ _PrepParallelRestore(ArchiveHandle *AH)
         if (endpos > prev_tctx->dataPos)
             prev_te->dataLength = endpos - prev_tctx->dataPos;
     }
+
+    /*
+     * For PARTITIONED DATA items, add up the sizes of their child objects.
+     * (We couldn't do this earlier, since when we encounter a PARTITIONED
+     * DATA item in the first loop we typically don't know the dataLength of
+     * its last child yet.)
+     */
+    if (have_partitioned_data)
+    {
+        for (te = AH->toc->next; te != AH->toc; te = te->next)
+        {
+            if (strcmp(te->desc, "PARTITIONED DATA") != 0)
+                continue;
+            for (int i = 0; i < te->nDeps; i++)
+            {
+                DumpId        depid = te->dependencies[i];
+
+                if (depid <= AH->maxDumpId && AH->tocsByDumpId[depid] != NULL)
+                {
+                    pgoff_t        childLength = AH->tocsByDumpId[depid]->dataLength;
+
+                    te->dataLength += childLength;
+                    /* handle overflow -- unlikely except with 32-bit pgoff_t */
+                    if (unlikely(te->dataLength < 0))
+                    {
+                        te->dataLength = INT_MAX;
+                        break;
+                    }
+                }
+            }
+        }
+    }
 }

 /*
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 21b00792a8a..dcb7dfc2ee7 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -37,6 +37,7 @@
 #include "postgres_fe.h"

 #include <dirent.h>
+#include <limits.h>
 #include <sys/stat.h>

 #include "common/file_utils.h"
@@ -722,16 +723,17 @@ setFilePath(ArchiveHandle *AH, char *buf, const char *relativeFilename)
 /*
  * Prepare for parallel restore.
  *
- * The main thing that needs to happen here is to fill in TABLE DATA and BLOBS
- * TOC entries' dataLength fields with appropriate values to guide the
- * ordering of restore jobs.  The source of said data is format-dependent,
- * as is the exact meaning of the values.
+ * The main thing that needs to happen here is to fill in TABLE DATA,
+ * PARTITIONED_DATA, and BLOBS TOC entries' dataLength fields with appropriate
+ * values to guide the ordering of restore jobs.  The source of said data is
+ * format-dependent, as is the exact meaning of the values.
  *
  * A format module might also choose to do other setup here.
  */
 static void
 _PrepParallelRestore(ArchiveHandle *AH)
 {
+    bool        have_partitioned_data = false;
     TocEntry   *te;

     for (te = AH->toc->next; te != AH->toc; te = te->next)
@@ -740,6 +742,10 @@ _PrepParallelRestore(ArchiveHandle *AH)
         char        fname[MAXPGPATH];
         struct stat st;

+        /* Track whether there are any PARTITIONED_DATA items */
+        if (!have_partitioned_data && strcmp(te->desc, "PARTITIONED DATA") == 0)
+            have_partitioned_data = true;
+
         /*
          * A dumpable object has set tctx->filename, any other object has not.
          * (see _ArchiveEntry).
@@ -784,6 +790,38 @@ _PrepParallelRestore(ArchiveHandle *AH)
         if (strcmp(te->desc, "BLOBS") == 0)
             te->dataLength *= 1024;
     }
+
+    /*
+     * For PARTITIONED DATA items, add up the sizes of their child objects.
+     * (Unlike pg_backup_custom.c, we could theoretically do this within the
+     * previous loop, but it seems best to keep the logic looking the same in
+     * both functions.)
+     */
+    if (have_partitioned_data)
+    {
+        for (te = AH->toc->next; te != AH->toc; te = te->next)
+        {
+            if (strcmp(te->desc, "PARTITIONED DATA") != 0)
+                continue;
+            for (int i = 0; i < te->nDeps; i++)
+            {
+                DumpId        depid = te->dependencies[i];
+
+                if (depid <= AH->maxDumpId && AH->tocsByDumpId[depid] != NULL)
+                {
+                    pgoff_t        childLength = AH->tocsByDumpId[depid]->dataLength;
+
+                    te->dataLength += childLength;
+                    /* handle overflow -- unlikely except with 32-bit pgoff_t */
+                    if (unlikely(te->dataLength < 0))
+                    {
+                        te->dataLength = INT_MAX;
+                        break;
+                    }
+                }
+            }
+        }
+    }
 }

 /*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c6e6d3b2b86..3c174924770 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -258,6 +258,7 @@ static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,

 static NamespaceInfo *findNamespace(Oid nsoid);
 static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
+static void dumpPartitionedData(Archive *fout, const TableDataInfo *tdinfo);
 static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
 static const char *getRoleName(const char *roleoid_str);
 static void collectRoleNames(Archive *fout);
@@ -347,6 +348,7 @@ static void getDomainConstraints(Archive *fout, TypeInfo *tyinfo);
 static void getTableData(DumpOptions *dopt, TableInfo *tblinfo, int numTables, char relkind);
 static void makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo);
 static void buildMatViewRefreshDependencies(Archive *fout);
+static void buildPartitionedDataDependencies(Archive *fout);
 static void getTableDataFKConstraints(void);
 static void determineNotNullFlags(Archive *fout, PGresult *res, int r,
                                   TableInfo *tbinfo, int j,
@@ -1076,6 +1078,7 @@ main(int argc, char **argv)
     {
         getTableData(&dopt, tblinfo, numTables, 0);
         buildMatViewRefreshDependencies(fout);
+        buildPartitionedDataDependencies(fout);
         if (!dopt.dumpSchema)
             getTableDataFKConstraints();
     }
@@ -2883,6 +2886,33 @@ dumpTableData(Archive *fout, const TableDataInfo *tdinfo)
     destroyPQExpBuffer(clistBuf);
 }

+/*
+ * dumpPartitionedData -
+ *      dump the contents of a partitioned table
+ *
+ * Actually, this just makes an ArchiveEntry for the table contents.
+ * Furthermore, the ArchiveEntry doesn't really carry any data itself.
+ * What it carries is dependencies on the table data objects for the
+ * partitioned table's immediate children.  In that way, a dependency
+ * on the PARTITIONED DATA TOC entry represents a dependency on all
+ * data within the partition hierarchy.
+ */
+static void
+dumpPartitionedData(Archive *fout, const TableDataInfo *tdinfo)
+{
+    TableInfo  *tbinfo = tdinfo->tdtable;
+
+    if (tdinfo->dobj.dump & DUMP_COMPONENT_DATA)
+        ArchiveEntry(fout,
+                     tdinfo->dobj.catId,    /* catalog ID */
+                     tdinfo->dobj.dumpId,    /* dump ID */
+                     ARCHIVE_OPTS(.tag = tbinfo->dobj.name,
+                                  .namespace = tbinfo->dobj.namespace->dobj.name,
+                                  .owner = tbinfo->rolname,
+                                  .description = "PARTITIONED DATA",
+                                  .section = SECTION_DATA));
+}
+
 /*
  * refreshMatViewData -
  *      load or refresh the contents of a single materialized view
@@ -2965,9 +2995,6 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
          !simple_oid_list_member(&foreign_servers_include_oids,
                                  tbinfo->foreign_server)))
         return;
-    /* Skip partitioned tables (data in partitions) */
-    if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
-        return;

     /* Don't dump data in unlogged tables, if so requested */
     if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -2986,6 +3013,8 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
         tdinfo->dobj.objType = DO_REFRESH_MATVIEW;
     else if (tbinfo->relkind == RELKIND_SEQUENCE)
         tdinfo->dobj.objType = DO_SEQUENCE_SET;
+    else if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+        tdinfo->dobj.objType = DO_PARTITIONED_DATA;
     else
         tdinfo->dobj.objType = DO_TABLE_DATA;

@@ -3000,6 +3029,12 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
     tdinfo->dobj.namespace = tbinfo->dobj.namespace;
     tdinfo->tdtable = tbinfo;
     tdinfo->filtercond = NULL;    /* might get set later */
+
+    /*
+     * The first dependency of any of these objTypes must be their table.  We
+     * may add more dependencies later, for example between PARTITIONED DATA
+     * objects and their children, or to enforce foreign key dump order.
+     */
     addObjectDependency(&tdinfo->dobj, tbinfo->dobj.dumpId);

     /* A TableDataInfo contains data, of course */
@@ -3134,6 +3169,52 @@ buildMatViewRefreshDependencies(Archive *fout)
     destroyPQExpBuffer(query);
 }

+/*
+ * buildPartitionedDataDependencies -
+ *      add dump-order dependencies for partitioned tables' data
+ *
+ * We make all PARTITIONED_DATA objects depend on the TABLE_DATA or
+ * PARTITIONED_DATA objects of the immediate children of the partitioned
+ * table.  This might seem like the wrong direction for the dependencies
+ * to run, but it's what we want for controlling restore order.  The
+ * PARTITIONED_DATA object will not be considered restorable until after
+ * all the child data objects are restored, and thus a dependency on it
+ * from another object such as an FK constraint will block that object from
+ * being restored until all the data in the partition hierarchy is loaded.
+ */
+static void
+buildPartitionedDataDependencies(Archive *fout)
+{
+    DumpableObject **dobjs;
+    int            numObjs;
+    int            i;
+
+    /* Search through all the dumpable objects for TableAttachInfos */
+    getDumpableObjects(&dobjs, &numObjs);
+    for (i = 0; i < numObjs; i++)
+    {
+        if (dobjs[i]->objType == DO_TABLE_ATTACH)
+        {
+            TableAttachInfo *attachinfo = (TableAttachInfo *) dobjs[i];
+            TableInfo  *parentTbl = attachinfo->parentTbl;
+            TableInfo  *partitionTbl = attachinfo->partitionTbl;
+
+            /* Not interesting unless both tables are to be dumped */
+            if (parentTbl->dataObj == NULL ||
+                partitionTbl->dataObj == NULL)
+                continue;
+
+            /*
+             * Okay, make parent table's PARTITIONED_DATA object depend on the
+             * child table's TABLE_DATA or PARTITIONED_DATA object.
+             */
+            addObjectDependency(&parentTbl->dataObj->dobj,
+                                partitionTbl->dataObj->dobj.dumpId);
+        }
+    }
+    free(dobjs);
+}
+
 /*
  * getTableDataFKConstraints -
  *      add dump-order dependencies reflecting foreign key constraints
@@ -3173,7 +3254,8 @@ getTableDataFKConstraints(void)

             /*
              * Okay, make referencing table's TABLE_DATA object depend on the
-             * referenced table's TABLE_DATA object.
+             * referenced table's TABLE_DATA object.  (Either one could be a
+             * PARTITIONED_DATA object, too.)
              */
             addObjectDependency(&cinfo->contable->dataObj->dobj,
                                 ftable->dataObj->dobj.dumpId);
@@ -11448,6 +11530,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
         case DO_TABLE_DATA:
             dumpTableData(fout, (const TableDataInfo *) dobj);
             break;
+        case DO_PARTITIONED_DATA:
+            dumpPartitionedData(fout, (const TableDataInfo *) dobj);
+            break;
         case DO_DUMMY_TYPE:
             /* table rowtypes and array types are never dumped separately */
             break;
@@ -19723,6 +19808,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
                 addObjectDependency(preDataBound, dobj->dumpId);
                 break;
             case DO_TABLE_DATA:
+            case DO_PARTITIONED_DATA:
             case DO_SEQUENCE_SET:
             case DO_LARGE_OBJECT:
             case DO_LARGE_OBJECT_DATA:
@@ -19792,8 +19878,8 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
  *
  * Just to make things more complicated, there are also "special" dependencies
  * such as the dependency of a TABLE DATA item on its TABLE, which we must
- * not rearrange because pg_restore knows that TABLE DATA only depends on
- * its table.  In these cases we must leave the dependencies strictly as-is
+ * not rearrange because pg_restore knows that TABLE DATA's first dependency
+ * is its table.  In these cases we must leave the dependencies strictly as-is
  * even if they refer to not-to-be-dumped objects.
  *
  * To handle this, the convention is that "special" dependencies are created
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index b426b5e4736..49c0e489ccf 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -63,6 +63,7 @@ typedef enum
     DO_PROCLANG,
     DO_CAST,
     DO_TABLE_DATA,
+    DO_PARTITIONED_DATA,
     DO_SEQUENCE_SET,
     DO_DUMMY_TYPE,
     DO_TSPARSER,
@@ -399,6 +400,10 @@ typedef struct _attrDefInfo
     bool        separate;        /* true if must dump as separate item */
 } AttrDefInfo;

+/*
+ * TableDataInfo is used for DO_TABLE_DATA, DO_PARTITIONED_DATA,
+ * DO_REFRESH_MATVIEW, and DO_SEQUENCE_SET objTypes.
+ */
 typedef struct _tableDataInfo
 {
     DumpableObject dobj;
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 0b0977788f1..927cf7f7daa 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -129,6 +129,7 @@ static const int dbObjectTypePriority[] =
     [DO_PROCLANG] = PRIO_PROCLANG,
     [DO_CAST] = PRIO_CAST,
     [DO_TABLE_DATA] = PRIO_TABLE_DATA,
+    [DO_PARTITIONED_DATA] = PRIO_TABLE_DATA,
     [DO_SEQUENCE_SET] = PRIO_SEQUENCE_SET,
     [DO_DUMMY_TYPE] = PRIO_DUMMY_TYPE,
     [DO_TSPARSER] = PRIO_TSPARSER,
@@ -1233,13 +1234,15 @@ repairDependencyLoop(DumpableObject **loop,
     }

     /*
-     * If all the objects are TABLE_DATA items, what we must have is a
-     * circular set of foreign key constraints (or a single self-referential
-     * table).  Print an appropriate complaint and break the loop arbitrarily.
+     * If all the objects are TABLE_DATA or PARTITIONED_DATA items, what we
+     * must have is a circular set of foreign key constraints (or a single
+     * self-referential table).  Print an appropriate complaint and break the
+     * loop arbitrarily.
      */
     for (i = 0; i < nLoop; i++)
     {
-        if (loop[i]->objType != DO_TABLE_DATA)
+        if (loop[i]->objType != DO_TABLE_DATA &&
+            loop[i]->objType != DO_PARTITIONED_DATA)
             break;
     }
     if (i >= nLoop)
@@ -1433,6 +1436,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
                      "TABLE DATA %s  (ID %d OID %u)",
                      obj->name, obj->dumpId, obj->catId.oid);
             return;
+        case DO_PARTITIONED_DATA:
+            snprintf(buf, bufsize,
+                     "PARTITIONED DATA %s  (ID %d OID %u)",
+                     obj->name, obj->dumpId, obj->catId.oid);
+            return;
         case DO_SEQUENCE_SET:
             snprintf(buf, bufsize,
                      "SEQUENCE SET %s  (ID %d OID %u)",

pgsql-hackers by date:

Previous
From: David Rowley
Date:
Subject: Re: n_ins_since_vacuum stats for aborted transactions
Next
From: Jacob Champion
Date:
Subject: Re: [PoC] Federated Authn/z with OAUTHBEARER