]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix pg_dump's failure to honor dependencies of SQL functions.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 4 Jun 2023 17:05:54 +0000 (13:05 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 4 Jun 2023 17:05:54 +0000 (13:05 -0400)
A new-style SQL function can contain a parse-time dependency
on a unique index, much as views and matviews can (such cases
arise from GROUP BY and ON CONFLICT clauses, for example).
To dump and restore such a function successfully, pg_dump must
postpone the function until after the unique index is created,
which will happen in the post-data part of the dump.  Therefore
we have to remove the normal constraint that functions are
dumped in pre-data.  Add code similar to the existing logic
that handles this for matviews.  I added test cases for both
as well, since code coverage tests showed that we weren't
testing the matview logic.

Per report from Sami Imseih.  Back-patch to v14 where
new-style SQL functions came in.

Discussion: https://postgr.es/m/2C1933AB-C2F8-499B-9D18-4AC1882256A0@amazon.com

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

index 3f63b34395fb6f8360a44a38ffe22ed6b7a1e4a1..312acd1c10498316abe809b3d93bc2d7628e3017 100644 (file)
@@ -6073,6 +6073,7 @@ getAggregates(Archive *fout, int *numAggs)
                                                  agginfo[i].aggfn.argtypes,
                                                  agginfo[i].aggfn.nargs);
                }
+               agginfo[i].aggfn.postponed_def = false; /* might get set during sort */
 
                /* Decide whether we want to dump it */
                selectDumpableObject(&(agginfo[i].aggfn.dobj), fout);
@@ -6303,6 +6304,7 @@ getFuncs(Archive *fout, int *numFuncs)
                        parseOidArray(PQgetvalue(res, i, i_proargtypes),
                                                  finfo[i].argtypes, finfo[i].nargs);
                }
+               finfo[i].postponed_def = false; /* might get set during sort */
 
                /* Decide whether we want to dump it */
                selectDumpableObject(&(finfo[i].dobj), fout);
@@ -12691,7 +12693,8 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
                                                                  .namespace = finfo->dobj.namespace->dobj.name,
                                                                  .owner = finfo->rolname,
                                                                  .description = keyword,
-                                                                 .section = SECTION_PRE_DATA,
+                                                                 .section = finfo->postponed_def ?
+                                                                 SECTION_POST_DATA : SECTION_PRE_DATA,
                                                                  .createStmt = q->data,
                                                                  .dropStmt = delqry->data));
 
index 55eecc6c94c73f8838ab48f84a73de717d80ed43..986782584ace44262b3550955cc2db1908c94991 100644 (file)
@@ -207,6 +207,7 @@ typedef struct _funcInfo
        char       *rproacl;
        char       *initproacl;
        char       *initrproacl;
+       bool            postponed_def;  /* function must be postponed into post-data */
 } FuncInfo;
 
 /* AggInfo is a superset of FuncInfo */
index 48b5a2a84cb0842141d60f28a5db0fec3c137f92..8e6026df67f8531eeeef80bb6e4a85922b429aa1 100644 (file)
@@ -866,6 +866,28 @@ repairMatViewBoundaryMultiLoop(DumpableObject *boundaryobj,
        }
 }
 
+/*
+ * If a function is involved in a multi-object loop, we can't currently fix
+ * that by splitting it into two DumpableObjects.  As a stopgap, we try to fix
+ * it by dropping the constraint that the function be dumped in the pre-data
+ * section.  This is sufficient to handle cases where a function depends on
+ * some unique index, as can happen if it has a GROUP BY for example.
+ */
+static void
+repairFunctionBoundaryMultiLoop(DumpableObject *boundaryobj,
+                                                               DumpableObject *nextobj)
+{
+       /* remove boundary's dependency on object after it in loop */
+       removeObjectDependency(boundaryobj, nextobj->dumpId);
+       /* if that object is a function, mark it as postponed into post-data */
+       if (nextobj->objType == DO_FUNC)
+       {
+               FuncInfo   *nextinfo = (FuncInfo *) nextobj;
+
+               nextinfo->postponed_def = true;
+       }
+}
+
 /*
  * Because we make tables depend on their CHECK constraints, while there
  * will be an automatic dependency in the other direction, we need to break
@@ -1060,6 +1082,28 @@ repairDependencyLoop(DumpableObject **loop,
                }
        }
 
+       /* Indirect loop involving function and data boundary */
+       if (nLoop > 2)
+       {
+               for (i = 0; i < nLoop; i++)
+               {
+                       if (loop[i]->objType == DO_FUNC)
+                       {
+                               for (j = 0; j < nLoop; j++)
+                               {
+                                       if (loop[j]->objType == DO_PRE_DATA_BOUNDARY)
+                                       {
+                                               DumpableObject *nextobj;
+
+                                               nextobj = (j < nLoop - 1) ? loop[j + 1] : loop[0];
+                                               repairFunctionBoundaryMultiLoop(loop[j], nextobj);
+                                               return;
+                                       }
+                               }
+                       }
+               }
+       }
+
        /* Table and CHECK constraint */
        if (nLoop == 2 &&
                loop[0]->objType == DO_TABLE &&
index 7b0ac9ca421691874996859572b67b0acd151686..976b3a33d0b18ba689af3fa2ddd4e39600faa658 100644 (file)
@@ -1957,6 +1957,27 @@ my %tests = (
                unlike => { exclude_dump_test_schema => 1, },
        },
 
+       'Check ordering of a function that depends on a primary key' => {
+               create_order => 41,
+               create_sql => '
+                       CREATE TABLE dump_test.ordering_table (id int primary key, data int);
+                       CREATE FUNCTION dump_test.ordering_func ()
+                       RETURNS SETOF dump_test.ordering_table
+                       LANGUAGE sql BEGIN ATOMIC
+                       SELECT * FROM dump_test.ordering_table GROUP BY id; END;',
+               regexp => qr/^
+                       \QALTER TABLE ONLY dump_test.ordering_table\E
+                       \n\s+\QADD CONSTRAINT ordering_table_pkey PRIMARY KEY (id);\E
+                       .*^
+                       \QCREATE FUNCTION dump_test.ordering_func\E/xms,
+               like =>
+                 { %full_runs, %dump_test_schema_runs, section_post_data => 1, },
+               unlike => {
+                       exclude_dump_test_schema => 1,
+                       only_dump_measurement => 1,
+               },
+       },
+
        'CREATE PROCEDURE dump_test.ptest1' => {
                create_order => 41,
                create_sql   => 'CREATE PROCEDURE dump_test.ptest1(a int)
@@ -2169,6 +2190,25 @@ my %tests = (
                  { exclude_dump_test_schema => 1, no_toast_compression => 1, },
        },
 
+       'Check ordering of a matview that depends on a primary key' => {
+               create_order => 42,
+               create_sql => '
+                       CREATE MATERIALIZED VIEW dump_test.ordering_view AS
+                               SELECT * FROM dump_test.ordering_table GROUP BY id;',
+               regexp => qr/^
+                       \QALTER TABLE ONLY dump_test.ordering_table\E
+                       \n\s+\QADD CONSTRAINT ordering_table_pkey PRIMARY KEY (id);\E
+                       .*^
+                       \QCREATE MATERIALIZED VIEW dump_test.ordering_view AS\E
+                       \n\s+\QSELECT ordering_table.id,\E/xms,
+               like =>
+                 { %full_runs, %dump_test_schema_runs, section_post_data => 1, },
+               unlike => {
+                       exclude_dump_test_schema => 1,
+                       only_dump_measurement => 1,
+               },
+       },
+
        'CREATE POLICY p1 ON test_table' => {
                create_order => 22,
                create_sql   => 'CREATE POLICY p1 ON dump_test.test_table