PGQ catalog representation and pg_dump support

Started by Andres Freund1 day ago5 messageshackers
Jump to latest
#1Andres Freund
andres@anarazel.de

Hi,

I don't know much about PGQ. So maybe I'm way off base with my questions
below.

I noticed some things that struck me as odd, when, while working on another
patch, I had occasion to look at dumpTableSchema(), which started me looking
into dump / dependency handling of PGQ:

- Why do property graphs have pg_attribute entries?

As far as I can tell, the system attributes don't make any sense for a
property graph as system attributes aren't ever referenced? Other objects
for which system attributes, like composite types, don't have pg_attribute
rows for system attributes?

And, IIUC, there aren't any other kind of attributes for property graph
relations?

- Pretty sure pg_dump's dependency handling for property graphs is
insufficient?

I see there's code to handle dependencies via pg_propgraph_element, but
there's also dependencies like pg_propgraph_property.pgtypid? I don't
immediately see such a dependency would be visible to pg_dump, as
getDependencies() only additionally queries dependencies via
pg_propgraph_element

There probably are unhandled dependencies other than
pg_propgraph_property.pgtypid.

- The prior UNION arms in getDependencies() prevent dependencies on itself -
but I don't think the pg_propgraph_element query does?

Compare with e.g. the amproc case which has
"AND NOT (refclassid = 'pg_opfamily'::regclass AND amprocfamily = refobjid)\n");

- Why are property graphs dumped as part of dumpTableSchema()?

I think it's already pretty weird that views are created as part of
dumpTableSchema(), but they at least share some infrastructure with
tables. I don't see any reason for propgraphs to not have been redirected in
dumpTable(), just like it's done for dumpSequence()?

- I am a bit worried that having something as complicated as
pg_get_propgraphdef() done purely server side will prevent some challenges
when we end up having to evolve any aspect of the property graph grammar
over time. But I guess the alternatives are also decidedly unattractive.

- More curiosity: Why do property graphs have pg_class entries at all? As far
as I can tell it doesn't use anything from it?

- Harmless, but it's a bit odd for the propgraph portion of getDependencies()
to filter deptype = 'p' away, given how long that has not existed.

Perhaps getDependencies() code should just have a comment about why the
queries include 'p', despite that being an unknown kind of dependency these
days.

Greetings,

Andres Freund

#2Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#1)
Re: PGQ catalog representation and pg_dump support

Hi,

On 2026-08-22 15:33:01 -0400, Andres Freund wrote:

- Pretty sure pg_dump's dependency handling for property graphs is
insufficient?

I see there's code to handle dependencies via pg_propgraph_element, but
there's also dependencies like pg_propgraph_property.pgtypid? I don't
immediately see such a dependency would be visible to pg_dump, as
getDependencies() only additionally queries dependencies via
pg_propgraph_element

There probably are unhandled dependencies other than
pg_propgraph_property.pgtypid.

Indeed:

DROP PROPERTY GRAPH IF EXISTS g_dump;
DROP TABLE IF EXISTS zz_rowtab CASCADE;
DROP TABLE IF EXISTS t_dump CASCADE;

CREATE TABLE zz_rowtab (a int, b int);
CREATE TABLE t_dump (id int PRIMARY KEY, txt text);

CREATE PROPERTY GRAPH g_dump
VERTEX TABLES (
t_dump KEY (id) LABEL l PROPERTIES (id, row(1,2)::zz_rowtab AS p)
);

dumps as

CREATE PROPERTY GRAPH public.g_dump
VERTEX TABLES (
public.t_dump KEY (id) LABEL l PROPERTIES (ROW(1, 2)::public.zz_rowtab AS p)
);
...
CREATE TABLE public.zz_rowtab (
a integer,
b integer
);

Which obviously can't work.

This example is the easy case, because they both g_dump and zz_rowtab have the
same priority. But I'm pretty sure that dependencies on other object types,
even if they are in a lower priority class, can still trigger problems when
reordered due to the topo sort.

Greetings,

Andres

#3Andres Freund
andres@anarazel.de
In reply to: Andres Freund (#2)
Re: PGQ catalog representation and pg_dump support

Hi,

Given the things I already found manually, I did a short AI review (Opus 5 and
GPT 5.6 Sol) and they found quite a few more things, just looking at the
dependency stuff. I did briefly look over the reported issues, and they all
looked valid to me:

- External cascades leave orphan graph metadata

ALTER PROPERTY GRAPH explicitly removes unused labels/properties, but generic
dependency deletion bypasses that cleanup:

CREATE TABLE v (id int PRIMARY KEY);
CREATE PROPERTY GRAPH g
VERTEX TABLES (v LABEL l PROPERTIES (id AS p));
DROP TABLE v CASCADE;

g retains global label l and integer property p. Adding a new text property named p then incorrectly
reports a type mismatch.

SELECT count(*) AS elements
FROM pg_propgraph_element
WHERE pgepgid = 'g'::regclass;

SELECT count(*) AS labels
FROM pg_propgraph_label
WHERE pglpgid = 'g'::regclass;

SELECT count(*) AS properties
FROM pg_propgraph_property
WHERE pgppgid = 'g'::regclass;

CREATE TABLE v2 (id text PRIMARY KEY);

ALTER PROPERTY GRAPH g
ADD VERTEX TABLES (
v2 LABEL l PROPERTIES (id AS p)
);

ERROR: 42601: property "p" data type mismatch: integer vs. text
DETAIL: In a property graph, a property of the same name has to have the same data type in each label.

- Similarly, query plan caching is not handled correctly after a CASCADE style
dropping.

AlterPropGraph() calls CacheInvalidateRelcacheByRelid(), but that's not
invoked when done via performDeletion() -> DropObjectById().

- Views depend only on global pg_propgraph_label and pg_propgraph_property rows
not the specific label/property association.

CREATE TABLE v1 (id integer PRIMARY KEY, n integer);
CREATE TABLE v2 (id integer PRIMARY KEY, n integer);

CREATE PROPERTY GRAPH g
VERTEX TABLES (
v1 LABEL l1 PROPERTIES (n AS p) LABEL keep NO PROPERTIES,
v2 LABEL l2 PROPERTIES (n AS p)
);

CREATE VIEW gv AS
SELECT *
FROM GRAPH_TABLE (
g MATCH (x IS l1)
COLUMNS (x.p)
);

ALTER PROPERTY GRAPH g
ALTER VERTEX TABLE v1
ALTER LABEL l1 DROP PROPERTIES (p);

SELECT to_regclass('gv') AS view_still_exists;
SELECT * FROM gv;

results in:

ERROR: 42704: property "p" for element variable "x" not found

- Edge links omit implicit-cast dependencies

Edge creation may accept an implicit cast but records only the equality
operator. Rewrite reconstructs the cast later. DROP CAST (...) therefore
succeeds, after which GRAPH_TABLE fails.

- Graph/materialized-view cycles are unrestorable

A matview can query graph g, then be added as an element of g. pg_dump
reports an unresolved dependency loop and restore fails because either the
graph or matview must exist first. Property graphs lack the
staged/dummy-definition repair used for ordinary views.

- opclass/opfamily for edge key equality

propgraph_edge_get_ref_keys() uses get_opfamily_member() but only the
dependency on the resulting pg_operator is recorded, not the opfamily.

I suggest doing a broader review of the propgraph code yourselves, if there's
this much to find just around propgraph dependencies, there's probably more.

Greetings,

Andres

#4Taha Naveed
m.taha.naveed27@gmail.com
In reply to: Andres Freund (#3)
Re: PGQ catalog representation and pg_dump support

Hi,

I also encountered an issue with pg_get_propgraphdef() and pg_dump.

CREATE TABLE wr (
id int PRIMARY KEY,
x text
);

CREATE PROPERTY GRAPH gwr
VERTEX TABLES (
wr PROPERTIES (wr AS whole)
);

SELECT pg_get_propgraphdef('gwr'::regclass);
ERROR: cache lookup failed for attribute 0 of relation ...

The graph itself works, and the whole row property can be queried through
GRAPH_TABLE. However, pg_dump also fails because it calls
pg_get_propgraphdef().
Reproduced this on PG 19beta3 as well as current master.

Regards,
Taha

#5Andrew Dunstan
andrew@dunslane.net
In reply to: Taha Naveed (#4)
Re: PGQ catalog representation and pg_dump support

On 2026-08-23 Su 4:41 AM, Taha Naveed wrote:

Hi,

I also encountered an issue with pg_get_propgraphdef() and pg_dump.

CREATE TABLE wr (
    id int PRIMARY KEY,
    x text
);

CREATE PROPERTY GRAPH gwr
    VERTEX TABLES (
        wr PROPERTIES (wr AS whole)
    );

SELECT pg_get_propgraphdef('gwr'::regclass);
ERROR:  cache lookup failed for attribute 0 of relation ...

The graph itself works, and the whole row property can be queried
through GRAPH_TABLE. However, pg_dump also fails because it calls
pg_get_propgraphdef().
Reproduced this on PG 19beta3 as well as current master.

Hi Andres, Taha,

I ran a broader audit using Opus 5 off the back of this thread and can
confirm essentially everything reported here, including Taha's whole-row
crash (that one's a one-line guard — get_attname() needs varattno > 0 before
the shortcut, ruleutils.c:1951).

Two further issues, both more urgent than the dump problems since
neither needs any privilege on the underlying tables:

- AlterPropGraph() never checks its target is actually a property graph.
ALTER PROPERTY GRAPH <any table/view/index/sequence you own> ADD VERTEX
TABLES (...) succeeds and writes catalog rows nobody can read
  back.
- GRAPH_TABLE's rewriter enumerates the full cartesian product of
candidate paths with no bound on breadth. A short EXPLAIN over a
middling multi-element pattern — SELECT on someone else's graph is
enough, or
  just the default TEMP privilege — can OOM the backend bringing down
the postmaster. That one probably belongs first in the queue.

Repro for that one:

 CREATE TEMP TABLE z (id int primary key, s int, t int);
  ALTER TABLE z ADD CONSTRAINT zfk FOREIGN KEY (s) REFERENCES z(id);

  CREATE PROPERTY GRAPH gz
    VERTEX TABLES (z AS v1, z AS v2, z AS v3, z AS v4)
    EDGE TABLES (z AS e1 KEY (id) SOURCE KEY (s) REFERENCES v1 (id)
DESTINATION KEY (t) REFERENCES v1 (id),
                 z AS e2 KEY (id) SOURCE KEY (s) REFERENCES v2 (id)
DESTINATION KEY (t) REFERENCES v2 (id),
                 z AS e3 KEY (id) SOURCE KEY (s) REFERENCES v3 (id)
DESTINATION KEY (t) REFERENCES v3 (id),
                 z AS e4 KEY (id) SOURCE KEY (s) REFERENCES v4 (id)
DESTINATION KEY (t) REFERENCES v4 (id));

  EXPLAIN (COSTS OFF) SELECT count(*) FROM GRAPH_TABLE (gz MATCH
(a1)-[b1]->(a2)-[b2]->(a3)-[b3]->(a4)-[b4]->(a5)-[b5]->(a6) COLUMNS
(a1.id AS c1));

On the AI-assisted items in Andres' third message: the correctness bugs
behind all four hold up, but the privilege-escalation framing attached
to each doesn't survive inspection. No surviving orphan row
carries a relation OID; ExecCheckPermissions() reruns on every
execution, so the relcache-invalidation gap is a plan-staleness issue,
not an auth one; the cross-label lookup is keyed by (elemoid, propid) and
can't cross tables; and replacing a cast or operator already requires
owning the type. Real bugs, not exploitable ones.

On Andres' original question — the pg_attribute rows do look vestigial.
heap.c already carves the rowtype out for propgraphs (and toast tables,
sequences); nobody made the matching carve-out for attributes.
Keeping the pg_class representation overall seems right (the ACL and
relcache-invalidation reuse pays for itself), but three targeted fixes —
the attribute carve-out, a propgraph_open() along the lines
Ashutosh floated and dropped upthread, and giving pg_dump its own
dump-object type instead of riding on DO_TABLE — would close most of
what's turned up here, ordering bug included.

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com