From 3182b6d9ae73b1845df7381877f46b5061a0b160 Mon Sep 17 00:00:00 2001 From: Satya Narlapuram Date: Sun, 10 May 2026 13:35:22 +0000 Subject: [PATCH] pg_dump: postpone PROPERTY GRAPH into POST-DATA when it depends on a unique constraint A property graph that references a vertex (or edge) table with a PRIMARY KEY (or any non-inline UNIQUE constraint) creates a circular dependency for pg_dump: CREATE PROPERTY GRAPH g VERTEX TABLES (persons KEY (id)); - the property graph depends on the unique index that backs KEY (id), - the unique index is implemented by a constraint dumped in POST-DATA, - the table itself is dumped in PRE-DATA, - and the property graph defaults to PRE-DATA along with its table. pg_dump emitted a 'could not resolve dependency loop' warning and then wrote the property graph in PRE-DATA before the ADD CONSTRAINT line that creates the required unique index. On a fresh restore, the CREATE PROPERTY GRAPH then errored out with 'there is no unique constraint matching the key for element ...', leaving the property graph silently absent from the restored database. Resolve it the same way as for materialized views: when a property graph is involved in a multi-object loop with the PRE-DATA boundary, break the boundary's dependency on it and postpone the property graph into POST-DATA via the existing TableInfo.postponed_def flag (already honoured by the section assignment in dumpTableSchema()). Add a TAP test mirroring the existing 'Check ordering of a matview that depends on a primary key' case to verify that the property graph appears after its supporting PRIMARY KEY constraint. --- src/bin/pg_dump/pg_dump.c | 2 +- src/bin/pg_dump/pg_dump_sort.c | 51 +++++++++++++++++++------------- src/bin/pg_dump/t/002_pg_dump.pl | 21 +++++++++++++ 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d56dcc701c..9d2d44cef4 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -7243,7 +7243,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages, * stats. REFRESH MATERIALIZED VIEW replaces the storage and resets * the stats, so the stats must be restored after the data. Also, the * materialized view definition may be postponed to SECTION_POST_DATA - * (see repairMatViewBoundaryMultiLoop()). + * (see repairPostponableBoundaryMultiLoop()). */ switch (info->relkind) { diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 03e5c1c111..314725b965 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -1008,36 +1008,39 @@ repairViewRuleMultiLoop(DumpableObject *viewobj, } /* - * If a matview is involved in a multi-object loop, we can't currently fix - * that by splitting off the rule. As a stopgap, we try to fix it by - * dropping the constraint that the matview be dumped in the pre-data section. - * This is sufficient to handle cases where a matview depends on some unique - * index, as can happen if it has a GROUP BY for example. + * If a matview or property graph is involved in a multi-object loop, we can't + * currently fix that by splitting off the rule (or, for property graphs, the + * element list). As a stopgap, we try to fix it by dropping the constraint + * that the object be dumped in the pre-data section. This is sufficient to + * handle cases where it depends on some unique index, as can happen for a + * matview with a GROUP BY, or a property graph whose element KEY / REFERENCES + * clauses point at a PRIMARY KEY constraint. * - * Note that the "next object" is not necessarily the matview itself; - * it could be the matview's rowtype, for example. We may come through here - * several times while removing all the pre-data linkages. In particular, - * if there are other matviews that depend on the one with the circularity - * problem, we'll come through here for each such matview and mark them all - * as postponed. (This works because all MVs have pre-data dependencies - * to begin with, so each of them will get visited.) + * Note that the "next object" is not necessarily the matview/property graph + * itself; it could be the matview's rowtype, for example. We may come through + * here several times while removing all the pre-data linkages. In particular, + * if there are other matviews or property graphs that depend on the one with + * the circularity problem, we'll come through here for each such object and + * mark them all as postponed. (This works because all such objects have + * pre-data dependencies to begin with, so each of them will get visited.) */ static void -repairMatViewBoundaryMultiLoop(DumpableObject *boundaryobj, - DumpableObject *nextobj) +repairPostponableBoundaryMultiLoop(DumpableObject *boundaryobj, + DumpableObject *nextobj) { /* remove boundary's dependency on object after it in loop */ removeObjectDependency(boundaryobj, nextobj->dumpId); /* - * If that object is a matview or matview stats, mark it as postponed into - * post-data. + * If that object is a matview, property graph, or matview stats entry, + * mark it as postponed into post-data. */ if (nextobj->objType == DO_TABLE) { TableInfo *nextinfo = (TableInfo *) nextobj; - if (nextinfo->relkind == RELKIND_MATVIEW) + if (nextinfo->relkind == RELKIND_MATVIEW || + nextinfo->relkind == RELKIND_PROPGRAPH) nextinfo->postponed_def = true; } else if (nextobj->objType == DO_REL_STATS) @@ -1242,13 +1245,19 @@ repairDependencyLoop(DumpableObject **loop, } } - /* Indirect loop involving matview and data boundary */ + /* + * Indirect loop involving matview or property graph and data boundary. + * Both relkinds default to PRE_DATA but can depend on POST_DATA objects + * (e.g. PRIMARY KEY constraints), and are handled identically by + * repairPostponableBoundaryMultiLoop(). + */ if (nLoop > 2) { for (i = 0; i < nLoop; i++) { if (loop[i]->objType == DO_TABLE && - ((TableInfo *) loop[i])->relkind == RELKIND_MATVIEW) + (((TableInfo *) loop[i])->relkind == RELKIND_MATVIEW || + ((TableInfo *) loop[i])->relkind == RELKIND_PROPGRAPH)) { for (j = 0; j < nLoop; j++) { @@ -1257,7 +1266,7 @@ repairDependencyLoop(DumpableObject **loop, DumpableObject *nextobj; nextobj = (j < nLoop - 1) ? loop[j + 1] : loop[0]; - repairMatViewBoundaryMultiLoop(loop[j], nextobj); + repairPostponableBoundaryMultiLoop(loop[j], nextobj); return; } } @@ -1272,7 +1281,7 @@ repairDependencyLoop(DumpableObject **loop, DumpableObject *nextobj; nextobj = (j < nLoop - 1) ? loop[j + 1] : loop[0]; - repairMatViewBoundaryMultiLoop(loop[j], nextobj); + repairPostponableBoundaryMultiLoop(loop[j], nextobj); return; } } diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 3bc8e51561..4ac665a5d9 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -3143,6 +3143,27 @@ my %tests = ( { exclude_dump_test_schema => 1, only_dump_measurement => 1, }, }, + # A property graph that references a vertex table's PRIMARY KEY must be + # dumped after the ADD CONSTRAINT line, not before it, or the restore + # fails with "no unique constraint matching the key". See + # repairPostponableBoundaryMultiLoop(). + 'Check ordering of a property graph that depends on a primary key' => { + create_order => 43, + create_sql => 'CREATE PROPERTY GRAPH dump_test.ordering_propgraph + VERTEX TABLES (dump_test.ordering_table);', + regexp => qr/^ + \QALTER TABLE ONLY dump_test.ordering_table\E + \n\s+\QADD CONSTRAINT ordering_table_pkey PRIMARY KEY (id);\E + .*^ + \QCREATE PROPERTY GRAPH dump_test.ordering_propgraph\E/xms, + like => + { %full_runs, %dump_test_schema_runs, section_post_data => 1, }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE PUBLICATION pub1' => { create_order => 50, create_sql => 'CREATE PUBLICATION pub1;', -- 2.43.0