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: