From d6f549d7a6275e16668faf8cddc95d6759506108 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 4 Jun 2023 13:05:54 -0400 Subject: [PATCH] Fix pg_dump's failure to honor dependencies of SQL functions. 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 | 5 +++- src/bin/pg_dump/pg_dump.h | 1 + src/bin/pg_dump/pg_dump_sort.c | 44 ++++++++++++++++++++++++++++++++ src/bin/pg_dump/t/002_pg_dump.pl | 40 +++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 3f63b34395f..312acd1c104 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -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)); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 55eecc6c94c..986782584ac 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -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 */ diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 48b5a2a84cb..8e6026df67f 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -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 && diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 7b0ac9ca421..976b3a33d0b 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -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 -- 2.39.5