Fix pg_dump's handling of dependencies for custom opclasses.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 31 Jul 2019 19:42:50 +0000 (15:42 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 31 Jul 2019 19:42:50 +0000 (15:42 -0400)
Since pg_dump doesn't treat the member operators and functions of operator
classes/families (that is, the pg_amop and pg_amproc entries, not the
underlying operators/functions) as separate dumpable objects, it missed
their dependency information.  I think this was safe when the code was
designed, because the default object sorting rule emits operators and
functions before opclasses, and there were no dependency types that could
mess that up.  However, the introduction of range types in 9.2 broke it:
now a type can have a dependency on an opclass, allowing dependency rules
to push the opclass before the type and hence before custom operators.
Lacking any information showing that it shouldn't do so, pg_dump emitted
the objects in the wrong order.

Fix by teaching getDependencies() to translate pg_depend entries for
pg_amop/amproc rows to look like dependencies for their parent opfamily.

I added a regression test for this in HEAD/v12, but not further back;
life is too short to fight with 002_pg_dump.pl.

Per bug #15934 from Tom Gottfried.  Back-patch to all supported branches.

Discussion: https://postgr.es/m/15934-58b8c8ab7a09ea15@postgresql.org

src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/t/002_pg_dump.pl

index 1cd25adb6cf310a64585fabd213d6abf39f1910e..b092b14bedca3bfaf8959a3359016124d4be903f 100644 (file)
@@ -17905,14 +17905,45 @@ getDependencies(Archive *fout)
    query = createPQExpBuffer();
 
    /*
+    * Messy query to collect the dependency data we need.  Note that we
+    * ignore the sub-object column, so that dependencies of or on a column
+    * look the same as dependencies of or on a whole table.
+    *
     * PIN dependencies aren't interesting, and EXTENSION dependencies were
     * already processed by getExtensionMembership.
     */
    appendPQExpBufferStr(query, "SELECT "
                         "classid, objid, refclassid, refobjid, deptype "
                         "FROM pg_depend "
-                        "WHERE deptype != 'p' AND deptype != 'e' "
-                        "ORDER BY 1,2");
+                        "WHERE deptype != 'p' AND deptype != 'e'\n");
+
+   /*
+    * Since we don't treat pg_amop entries as separate DumpableObjects, we
+    * have to translate their dependencies into dependencies of their parent
+    * opfamily.  Ignore internal dependencies though, as those will point to
+    * their parent opclass, which we needn't consider here (and if we did,
+    * it'd just result in circular dependencies).  Also, "loose" opfamily
+    * entries will have dependencies on their parent opfamily, which we
+    * should drop since they'd likewise become useless self-dependencies.
+    * (But be sure to keep deps on *other* opfamilies; see amopsortfamily.)
+    */
+   appendPQExpBufferStr(query, "UNION ALL\n"
+                        "SELECT 'pg_opfamily'::regclass AS classid, amopfamily AS objid, refclassid, refobjid, deptype "
+                        "FROM pg_depend d, pg_amop o "
+                        "WHERE deptype NOT IN ('p', 'e', 'i') AND "
+                        "classid = 'pg_amop'::regclass AND objid = o.oid "
+                        "AND NOT (refclassid = 'pg_opfamily'::regclass AND amopfamily = refobjid)\n");
+
+   /* Likewise for pg_amproc entries */
+   appendPQExpBufferStr(query, "UNION ALL\n"
+                        "SELECT 'pg_opfamily'::regclass AS classid, amprocfamily AS objid, refclassid, refobjid, deptype "
+                        "FROM pg_depend d, pg_amproc p "
+                        "WHERE deptype NOT IN ('p', 'e', 'i') AND "
+                        "classid = 'pg_amproc'::regclass AND objid = p.oid "
+                        "AND NOT (refclassid = 'pg_opfamily'::regclass AND amprocfamily = refobjid)\n");
+
+   /* Sort the output for efficiency below */
+   appendPQExpBufferStr(query, "ORDER BY 1,2");
 
    res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
index c56bf00e4b40e2b914b284d3ad682a06b03608bc..713fb7a459edc76f9ef08a4ad5e664504a5d2db0 100644 (file)
@@ -1562,6 +1562,35 @@ my %tests = (
        unlike => { exclude_dump_test_schema => 1, },
    },
 
+    # verify that a custom operator/opclass/range type is dumped in right order
+   'CREATE OPERATOR CLASS dump_test.op_class_custom' => {
+       create_order => 74,
+       create_sql   => 'CREATE OPERATOR dump_test.~~ (
+                            PROCEDURE = int4eq,
+                            LEFTARG = int,
+                            RIGHTARG = int);
+                        CREATE OPERATOR CLASS dump_test.op_class_custom
+                            FOR TYPE int USING btree AS
+                            OPERATOR 3 dump_test.~~;
+                        CREATE TYPE dump_test.range_type_custom AS RANGE (
+                            subtype = int,
+                            subtype_opclass = dump_test.op_class_custom);',
+       regexp => qr/^
+           \QCREATE OPERATOR dump_test.~~ (\E\n.+
+           \QCREATE OPERATOR FAMILY dump_test.op_class_custom USING btree;\E\n.+
+           \QCREATE OPERATOR CLASS dump_test.op_class_custom\E\n\s+
+           \QFOR TYPE integer USING btree FAMILY dump_test.op_class_custom AS\E\n\s+
+           \QOPERATOR 3 dump_test.~~(integer,integer);\E\n.+
+           \QCREATE TYPE dump_test.range_type_custom AS RANGE (\E\n\s+
+           \Qsubtype = integer,\E\n\s+
+           \Qsubtype_opclass = dump_test.op_class_custom\E\n
+           \Q);\E
+           /xms,
+       like =>
+         { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+       unlike => { exclude_dump_test_schema => 1, },
+   },
+
    'CREATE OPERATOR CLASS dump_test.op_class_empty' => {
        create_order => 89,
        create_sql   => 'CREATE OPERATOR CLASS dump_test.op_class_empty