diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index c68b86bc766..59f00740bfb 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -17,6 +17,7 @@ #include +#include "catalog/pg_am_d.h" #include "catalog/pg_class_d.h" #include "fe_utils/string_utils.h" #include "pg_backup_archiver.h" @@ -49,6 +50,7 @@ static DumpableObject **tblinfoindex; static DumpableObject **typinfoindex; static DumpableObject **funinfoindex; static DumpableObject **oprinfoindex; +static DumpableObject **aminfoindex; static DumpableObject **collinfoindex; static DumpableObject **nspinfoindex; static DumpableObject **extinfoindex; @@ -57,6 +59,7 @@ static int numTables; static int numTypes; static int numFuncs; static int numOperators; +static int numAccessMethods; static int numCollations; static int numNamespaces; static int numExtensions; @@ -92,6 +95,7 @@ getSchemaData(Archive *fout, int *numTablesPtr) TypeInfo *typinfo; FuncInfo *funinfo; OprInfo *oprinfo; + AccessMethodInfo *aminfo; CollInfo *collinfo; NamespaceInfo *nspinfo; ExtensionInfo *extinfo; @@ -103,7 +107,6 @@ getSchemaData(Archive *fout, int *numTablesPtr) int numProcLangs; int numCasts; int numTransforms; - int numAccessMethods; int numOpclasses; int numOpfamilies; int numConversions; @@ -166,7 +169,8 @@ getSchemaData(Archive *fout, int *numTablesPtr) oprinfoindex = buildIndexArray(oprinfo, numOperators, sizeof(OprInfo)); pg_log_info("reading user-defined access methods"); - getAccessMethods(fout, &numAccessMethods); + aminfo = getAccessMethods(fout, &numAccessMethods); + aminfoindex = buildIndexArray(aminfo, numAccessMethods, sizeof(AccessMethodInfo)); pg_log_info("reading user-defined operator classes"); getOpclasses(fout, &numOpclasses); @@ -890,6 +894,17 @@ findOprByOid(Oid oid) return (OprInfo *) findObjectByOid(oid, oprinfoindex, numOperators); } +/* + * findAccessMethodByOid + * finds the DumpableObject for the access method with the given oid + * returns NULL if not found + */ +AccessMethodInfo * +findAccessMethodByOid(Oid oid) +{ + return (AccessMethodInfo *) findObjectByOid(oid, aminfoindex, numAccessMethods); +} + /* * findCollationByOid * finds the entry (in collinfo) of the collation with the given oid diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 4b13669f450..29d37158a38 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1767,6 +1767,13 @@ selectDumpableProcLang(ProcLangInfo *plang, Archive *fout) static void selectDumpableAccessMethod(AccessMethodInfo *method, Archive *fout) { + /* see getAccessMethods() comment about v9.6. */ + if (fout->remoteVersion < 90600) + { + method->dobj.dump = DUMP_COMPONENT_NONE; + return; + } + if (checkExtensionMembership(&method->dobj, fout)) return; /* extension membership overrides all else */ @@ -5325,6 +5332,8 @@ getOperators(Archive *fout, int *numOprs) int i_oprnamespace; int i_rolname; int i_oprkind; + int i_oprleft; + int i_oprright; int i_oprcode; /* @@ -5336,6 +5345,8 @@ getOperators(Archive *fout, int *numOprs) "oprnamespace, " "(%s oprowner) AS rolname, " "oprkind, " + "oprleft, " + "oprright, " "oprcode::oid AS oprcode " "FROM pg_operator", username_subquery); @@ -5353,6 +5364,8 @@ getOperators(Archive *fout, int *numOprs) i_oprnamespace = PQfnumber(res, "oprnamespace"); i_rolname = PQfnumber(res, "rolname"); i_oprkind = PQfnumber(res, "oprkind"); + i_oprleft = PQfnumber(res, "oprleft"); + i_oprright = PQfnumber(res, "oprright"); i_oprcode = PQfnumber(res, "oprcode"); for (i = 0; i < ntups; i++) @@ -5367,6 +5380,8 @@ getOperators(Archive *fout, int *numOprs) atooid(PQgetvalue(res, i, i_oprnamespace))); oprinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); oprinfo[i].oprkind = (PQgetvalue(res, i, i_oprkind))[0]; + oprinfo[i].oprleft = atooid(PQgetvalue(res, i, i_oprleft)); + oprinfo[i].oprright = atooid(PQgetvalue(res, i, i_oprright)); oprinfo[i].oprcode = atooid(PQgetvalue(res, i, i_oprcode)); /* Decide whether we want to dump it */ @@ -5407,6 +5422,7 @@ getCollations(Archive *fout, int *numCollations) int i_collname; int i_collnamespace; int i_rolname; + int i_collencoding; /* Collations didn't exist pre-9.1 */ if (fout->remoteVersion < 90100) @@ -5424,7 +5440,8 @@ getCollations(Archive *fout, int *numCollations) appendPQExpBuffer(query, "SELECT tableoid, oid, collname, " "collnamespace, " - "(%s collowner) AS rolname " + "(%s collowner) AS rolname, " + "collencoding " "FROM pg_collation", username_subquery); @@ -5440,6 +5457,7 @@ getCollations(Archive *fout, int *numCollations) i_collname = PQfnumber(res, "collname"); i_collnamespace = PQfnumber(res, "collnamespace"); i_rolname = PQfnumber(res, "rolname"); + i_collencoding = PQfnumber(res, "collencoding"); for (i = 0; i < ntups; i++) { @@ -5452,6 +5470,7 @@ getCollations(Archive *fout, int *numCollations) findNamespace(fout, atooid(PQgetvalue(res, i, i_collnamespace))); collinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); + collinfo[i].collencoding = atoi(PQgetvalue(res, i, i_collencoding)); /* Decide whether we want to dump it */ selectDumpableObject(&(collinfo[i].dobj), fout); @@ -5561,19 +5580,28 @@ getAccessMethods(Archive *fout, int *numAccessMethods) int i_amhandler; int i_amtype; - /* Before 9.6, there are no user-defined access methods */ - if (fout->remoteVersion < 90600) - { - *numAccessMethods = 0; - return NULL; - } - query = createPQExpBuffer(); - /* Select all access methods from pg_am table */ - appendPQExpBufferStr(query, "SELECT tableoid, oid, amname, amtype, " - "amhandler::pg_catalog.regproc AS amhandler " - "FROM pg_am"); + /* + * Select all access methods from pg_am table. v9.6 introduced CREATE + * ACCESS METHOD, so earlier versions usually have only built-in access + * methods. v9.6 also changed the access method API, replacing dozens of + * pg_am columns with amhandler. Even if a user created an access method + * by "INSERT INTO pg_am", we have no way to translate pre-v9.6 pg_am + * columns to a v9.6+ CREATE ACCESS METHOD. Hence, before v9.6, read + * pg_am just to facilitate findAccessMethodByOid() providing the + * OID-to-name mapping. + */ + appendPQExpBufferStr(query, "SELECT tableoid, oid, amname, "); + if (fout->remoteVersion >= 90600) + appendPQExpBufferStr(query, + "amtype, " + "amhandler::pg_catalog.regproc AS amhandler "); + else + appendPQExpBufferStr(query, + "'i'::pg_catalog.\"char\" AS amtype, " + "'-'::pg_catalog.regproc AS amhandler "); + appendPQExpBufferStr(query, "FROM pg_am"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -5631,6 +5659,7 @@ getOpclasses(Archive *fout, int *numOpclasses) OpclassInfo *opcinfo; int i_tableoid; int i_oid; + int i_opcmethod; int i_opcname; int i_opcnamespace; int i_rolname; @@ -5640,11 +5669,20 @@ getOpclasses(Archive *fout, int *numOpclasses) * system-defined opclasses at dump-out time. */ - appendPQExpBuffer(query, "SELECT tableoid, oid, opcname, " - "opcnamespace, " - "(%s opcowner) AS rolname " - "FROM pg_opclass", - username_subquery); + if (fout->remoteVersion >= 80300) + appendPQExpBuffer(query, "SELECT tableoid, oid, " + "opcmethod, opcname, " + "opcnamespace, " + "(%s opcowner) AS rolname " + "FROM pg_opclass", + username_subquery); + else + appendPQExpBuffer(query, "SELECT tableoid, oid, " + "opcamid AS opcmethod, opcname, " + "opcnamespace, " + "(%s opcowner) AS rolname " + "FROM pg_opclass", + username_subquery); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -5655,6 +5693,7 @@ getOpclasses(Archive *fout, int *numOpclasses) i_tableoid = PQfnumber(res, "tableoid"); i_oid = PQfnumber(res, "oid"); + i_opcmethod = PQfnumber(res, "opcmethod"); i_opcname = PQfnumber(res, "opcname"); i_opcnamespace = PQfnumber(res, "opcnamespace"); i_rolname = PQfnumber(res, "rolname"); @@ -5669,6 +5708,7 @@ getOpclasses(Archive *fout, int *numOpclasses) opcinfo[i].dobj.namespace = findNamespace(fout, atooid(PQgetvalue(res, i, i_opcnamespace))); + opcinfo[i].opcmethod = atooid(PQgetvalue(res, i, i_opcmethod)); opcinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); /* Decide whether we want to dump it */ @@ -5706,6 +5746,7 @@ getOpfamilies(Archive *fout, int *numOpfamilies) OpfamilyInfo *opfinfo; int i_tableoid; int i_oid; + int i_opfmethod; int i_opfname; int i_opfnamespace; int i_rolname; @@ -5724,7 +5765,7 @@ getOpfamilies(Archive *fout, int *numOpfamilies) * system-defined opfamilies at dump-out time. */ - appendPQExpBuffer(query, "SELECT tableoid, oid, opfname, " + appendPQExpBuffer(query, "SELECT tableoid, oid, opfmethod, opfname, " "opfnamespace, " "(%s opfowner) AS rolname " "FROM pg_opfamily", @@ -5740,6 +5781,7 @@ getOpfamilies(Archive *fout, int *numOpfamilies) i_tableoid = PQfnumber(res, "tableoid"); i_oid = PQfnumber(res, "oid"); i_opfname = PQfnumber(res, "opfname"); + i_opfmethod = PQfnumber(res, "opfmethod"); i_opfnamespace = PQfnumber(res, "opfnamespace"); i_rolname = PQfnumber(res, "rolname"); @@ -5753,6 +5795,7 @@ getOpfamilies(Archive *fout, int *numOpfamilies) opfinfo[i].dobj.namespace = findNamespace(fout, atooid(PQgetvalue(res, i, i_opfnamespace))); + opfinfo[i].opfmethod = atooid(PQgetvalue(res, i, i_opfmethod)); opfinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); /* Decide whether we want to dump it */ diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 552e9e1da16..7fcc544b2dc 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -219,6 +219,8 @@ typedef struct _oprInfo DumpableObject dobj; char *rolname; char oprkind; + Oid oprleft; + Oid oprright; Oid oprcode; } OprInfo; @@ -232,12 +234,14 @@ typedef struct _accessMethodInfo typedef struct _opclassInfo { DumpableObject dobj; + Oid opcmethod; char *rolname; } OpclassInfo; typedef struct _opfamilyInfo { DumpableObject dobj; + Oid opfmethod; char *rolname; } OpfamilyInfo; @@ -245,6 +249,7 @@ typedef struct _collInfo { DumpableObject dobj; char *rolname; + int collencoding; } CollInfo; typedef struct _convInfo @@ -662,6 +667,7 @@ extern TableInfo *findTableByOid(Oid oid); extern TypeInfo *findTypeByOid(Oid oid); extern FuncInfo *findFuncByOid(Oid oid); extern OprInfo *findOprByOid(Oid oid); +extern AccessMethodInfo *findAccessMethodByOid(Oid oid); extern CollInfo *findCollationByOid(Oid oid); extern NamespaceInfo *findNamespaceByOid(Oid oid); extern ExtensionInfo *findExtensionByOid(Oid oid); diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 51e6be90737..892eda73420 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -104,6 +104,8 @@ static DumpId postDataBoundId; static int DOTypeNameCompare(const void *p1, const void *p2); +static int pgTypeNameCompare(Oid typid1, Oid typid2); +static int accessMethodNameCompare(Oid am1, Oid am2); static bool TopoSort(DumpableObject **objs, int numObjs, DumpableObject **ordering, @@ -171,12 +173,39 @@ DOTypeNameCompare(const void *p1, const void *p2) else if (obj2->namespace) return 1; - /* Sort by name */ + /* + * Sort by name. With a few exceptions, names here are single catalog + * columns. To get a fuller picture, grep pg_dump.c for "dobj.name = ". + * Names here don't match "Name:" in plain format output, which is a + * _tocEntry.tag. For example, DumpableObject.name of a constraint is + * pg_constraint.conname, but _tocEntry.tag of a constraint is relname and + * conname joined with a space. + */ cmpval = strcmp(obj1->name, obj2->name); if (cmpval != 0) return cmpval; - /* To have a stable sort order, break ties for some object types */ + /* + * Sort by type. This helps types that share a type priority without + * sharing a unique name constraint, e.g. opclass and opfamily. + */ + cmpval = obj1->objType - obj2->objType; + if (cmpval != 0) + return cmpval; + + /* + * To have a stable sort order, break ties for some object types. Most + * catalogs have a natural key, e.g. pg_proc_proname_args_nsp_index. Where + * the above "namespace" and "name" comparisons don't cover all natural + * key columns, compare the rest here. + * + * The natural key usually refers to other catalogs by surrogate keys. + * Hence, this translates each of those references to the natural key of + * the referenced catalog. That may descend through multiple levels of + * catalog references. For example, to sort by pg_proc.proargtypes, + * descend to each pg_type and then further to its pg_namespace, for an + * overall sort by (nspname, typname). + */ if (obj1->objType == DO_FUNC || obj1->objType == DO_AGG) { FuncInfo *fobj1 = *(FuncInfo *const *) p1; @@ -189,22 +218,10 @@ DOTypeNameCompare(const void *p1, const void *p2) return cmpval; for (i = 0; i < fobj1->nargs; i++) { - TypeInfo *argtype1 = findTypeByOid(fobj1->argtypes[i]); - TypeInfo *argtype2 = findTypeByOid(fobj2->argtypes[i]); - - if (argtype1 && argtype2) - { - if (argtype1->dobj.namespace && argtype2->dobj.namespace) - { - cmpval = strcmp(argtype1->dobj.namespace->dobj.name, - argtype2->dobj.namespace->dobj.name); - if (cmpval != 0) - return cmpval; - } - cmpval = strcmp(argtype1->dobj.name, argtype2->dobj.name); - if (cmpval != 0) - return cmpval; - } + cmpval = pgTypeNameCompare(fobj1->argtypes[i], + fobj2->argtypes[i]); + if (cmpval != 0) + return cmpval; } } else if (obj1->objType == DO_OPERATOR) @@ -216,6 +233,57 @@ DOTypeNameCompare(const void *p1, const void *p2) cmpval = (oobj2->oprkind - oobj1->oprkind); if (cmpval != 0) return cmpval; + /* Within an oprkind, sort by argument type names */ + cmpval = pgTypeNameCompare(oobj1->oprleft, oobj2->oprleft); + if (cmpval != 0) + return cmpval; + cmpval = pgTypeNameCompare(oobj1->oprright, oobj2->oprright); + if (cmpval != 0) + return cmpval; + } + else if (obj1->objType == DO_OPCLASS) + { + OpclassInfo *opcobj1 = *(OpclassInfo *const *) p1; + OpclassInfo *opcobj2 = *(OpclassInfo *const *) p2; + + /* Sort by access method name, per pg_opclass_am_name_nsp_index */ + cmpval = accessMethodNameCompare(opcobj1->opcmethod, + opcobj2->opcmethod); + if (cmpval != 0) + return cmpval; + } + else if (obj1->objType == DO_OPFAMILY) + { + OpfamilyInfo *opfobj1 = *(OpfamilyInfo *const *) p1; + OpfamilyInfo *opfobj2 = *(OpfamilyInfo *const *) p2; + + /* Sort by access method name, per pg_opfamily_am_name_nsp_index */ + cmpval = accessMethodNameCompare(opfobj1->opfmethod, + opfobj2->opfmethod); + if (cmpval != 0) + return cmpval; + } + else if (obj1->objType == DO_COLLATION) + { + CollInfo *cobj1 = *(CollInfo *const *) p1; + CollInfo *cobj2 = *(CollInfo *const *) p2; + + /* + * Sort by encoding, per pg_collation_name_enc_nsp_index. Technically, + * this is not necessary, because wherever this changes dump order, + * restoring the dump fails anyway. CREATE COLLATION can't create a + * tie for this to break, because it imposes restrictions to make + * (nspname, collname) uniquely identify a collation within a given + * DatabaseEncoding. While pg_import_system_collations() can create a + * tie, pg_dump+restore fails after + * pg_import_system_collations('my_schema') does so. However, there's + * little to gain by ignoring one natural key column on the basis of + * those limitations elsewhere, so respect the full natural key like + * we do for other object types. + */ + cmpval = cobj1->collencoding - cobj2->collencoding; + if (cmpval != 0) + return cmpval; } else if (obj1->objType == DO_ATTRDEF) { @@ -260,11 +328,132 @@ DOTypeNameCompare(const void *p1, const void *p2) if (cmpval != 0) return cmpval; } + else if (obj1->objType == DO_CONSTRAINT) + { + ConstraintInfo *robj1 = *(ConstraintInfo *const *) p1; + ConstraintInfo *robj2 = *(ConstraintInfo *const *) p2; - /* Usually shouldn't get here, but if we do, sort by OID */ + /* + * Sort domain constraints before table constraints, for consistency + * with our decision to sort CREATE DOMAIN before CREATE TABLE. + */ + if (robj1->condomain) + { + if (robj2->condomain) + { + /* Sort by domain name (domain namespace was considered) */ + cmpval = strcmp(robj1->condomain->dobj.name, + robj2->condomain->dobj.name); + if (cmpval != 0) + return cmpval; + } + else + return dbObjectTypePriority[DO_TYPE] - dbObjectTypePriority[DO_TABLE]; + } + else if (robj2->condomain) + return dbObjectTypePriority[DO_TABLE] - dbObjectTypePriority[DO_TYPE]; + else + { + /* Sort by table name (table namespace was considered already) */ + cmpval = strcmp(robj1->contable->dobj.name, + robj2->contable->dobj.name); + if (cmpval != 0) + return cmpval; + } + } + else if (obj1->objType == DO_PUBLICATION_REL) + { + PublicationRelInfo *probj1 = *(PublicationRelInfo *const *) p1; + PublicationRelInfo *probj2 = *(PublicationRelInfo *const *) p2; + + /* Sort by publication name, since (namespace, name) match the rel */ + cmpval = strcmp(probj1->publication->dobj.name, + probj2->publication->dobj.name); + if (cmpval != 0) + return cmpval; + } + + /* + * Shouldn't get here except after catalog corruption, but if we do, sort + * by OID. This may make logically-identical databases differ in the + * order of objects in dump output. Users will get spurious schema diffs. + * Expect flaky failures of 002_pg_upgrade.pl test 'dump outputs from + * original and restored regression databases match' if the regression + * database contains objects allowing that test to reach here. That's a + * consequence of the test using "pg_restore -j", which doesn't fully + * constrain OID assignment order. + */ + Assert(false); return oidcmp(obj1->catId.oid, obj2->catId.oid); } +/* Compare two OID-identified pg_type values by nspname, then by typname. */ +static int +pgTypeNameCompare(Oid typid1, Oid typid2) +{ + TypeInfo *typobj1; + TypeInfo *typobj2; + int cmpval; + + if (typid1 == typid2) + return 0; + + typobj1 = findTypeByOid(typid1); + typobj2 = findTypeByOid(typid2); + + if (!typobj1 || !typobj2) + { + /* + * getTypes() didn't find some OID. Assume catalog corruption, e.g. + * an oprright value without the corresponding OID in a pg_type row. + * Report as "equal", so the caller uses the next available basis for + * comparison, e.g. the next function argument. + * + * Unary operators have InvalidOid in oprleft (if oprkind='r') or in + * oprright (if oprkind='l'). Caller already sorted by oprkind, + * calling us only for like-kind operators. Hence, "typid1 == typid2" + * took care of InvalidOid. (v14 removed postfix operator support. + * Hence, when dumping from v14+, only oprleft can be InvalidOid.) + */ + Assert(false); + return 0; + } + + if (!typobj1->dobj.namespace || !typobj2->dobj.namespace) + Assert(false); /* catalog corruption */ + else + { + cmpval = strcmp(typobj1->dobj.namespace->dobj.name, + typobj2->dobj.namespace->dobj.name); + if (cmpval != 0) + return cmpval; + } + return strcmp(typobj1->dobj.name, typobj2->dobj.name); +} + +/* Compare two OID-identified pg_am values by amname. */ +static int +accessMethodNameCompare(Oid am1, Oid am2) +{ + AccessMethodInfo *amobj1; + AccessMethodInfo *amobj2; + + if (am1 == am2) + return 0; + + amobj1 = findAccessMethodByOid(am1); + amobj2 = findAccessMethodByOid(am2); + + if (!amobj1 || !amobj2) + { + /* catalog corruption: handle like pgTypeNameCompare() does */ + Assert(false); + return 0; + } + + return strcmp(amobj1->dobj.name, amobj2->dobj.name); +} + /* * Sort the given objects into a safe dump order using dependency diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index b7ce080424f..35595fb1427 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -344,3 +344,11 @@ NOTICE: drop cascades to table pub_test.testpub_nopk RESET SESSION AUTHORIZATION; DROP ROLE regress_publication_user, regress_publication_user2; DROP ROLE regress_publication_user_dummy; +-- stage objects for pg_dump tests +CREATE SCHEMA pubme CREATE TABLE t0 (c int, d int) CREATE TABLE t1 (c int); +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION dump_pub_1ct FOR TABLE ONLY pubme.t0; +CREATE PUBLICATION dump_pub_2ct FOR TABLE ONLY pubme.t0, pubme.t1; +CREATE PUBLICATION dump_pub_all FOR TABLE ONLY pubme.t0, pubme.t1 + WITH (publish_via_partition_root = true); +RESET client_min_messages; diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index 7d5c9373845..021d4e2cb24 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -218,3 +218,12 @@ DROP SCHEMA pub_test CASCADE; RESET SESSION AUTHORIZATION; DROP ROLE regress_publication_user, regress_publication_user2; DROP ROLE regress_publication_user_dummy; + +-- stage objects for pg_dump tests +CREATE SCHEMA pubme CREATE TABLE t0 (c int, d int) CREATE TABLE t1 (c int); +SET client_min_messages = 'ERROR'; +CREATE PUBLICATION dump_pub_1ct FOR TABLE ONLY pubme.t0; +CREATE PUBLICATION dump_pub_2ct FOR TABLE ONLY pubme.t0, pubme.t1; +CREATE PUBLICATION dump_pub_all FOR TABLE ONLY pubme.t0, pubme.t1 + WITH (publish_via_partition_root = true); +RESET client_min_messages;