Missing Dependency Tracking for SQL/PGQ Labels and Properties
Context: SQL/PGQ in PostgreSQL
SQL/PGQ (Property Graph Queries) is the ISO/IEC 9075-16 standard extension that adds property-graph querying semantics on top of SQL. PostgreSQL has been growing support for this feature via the CREATE PROPERTY GRAPH DDL, GRAPH_TABLE(...) table function, and the MATCH ... COLUMNS (...) pattern-matching syntax. Internally this introduces new catalogs — notably pg_propgraph_label (referenced here as PropgraphLabelRelationId) and pg_propgraph_property (PropgraphPropertyRelationId) — plus new parse/plan nodes GraphLabelRef, GraphPropertyRef, GraphElementPattern, and GraphPattern, along with a new RTE kind RTE_GRAPH_TABLE.
Because these are first-class schema objects, they must participate in PostgreSQL's dependency machinery (pg_depend) so that DROP/ALTER cascades behave correctly and views/rules holding references to them cannot be silently invalidated.
The Bug
The reporter (Zeng Man) demonstrates a clear-cut dependency-tracking gap:
- A property graph
gis created over base tablesvtandet, declaring two labels onvt(l1,l2). - A view
v1is defined whose body containsGRAPH_TABLE(g MATCH (a IS l2)-[e IS et]->(b IS l2) COLUMNS (...))— i.e., it references labell2explicitly. ALTER PROPERTY GRAPH g ALTER VERTEX TABLE vt DROP LABEL l2succeeds silently, even thoughv1referencesl2.- Executing
SELECT * FROM v1then fails withcache lookup failed for label 16472— the classic signature of a stale OID reference whose catalog row has been deleted without the dependent object being invalidated first.
This is a user-visible symptom of the deeper issue: when RecordDependencyOnExpr walks the stored view definition (via find_expr_references_walker in src/backend/catalog/dependency.c), it never descends into graph-specific node types, so no pg_depend rows connecting the view to the label/property objects are ever recorded.
Why find_expr_references_walker Is the Right Place
find_expr_references_walker is PostgreSQL's canonical traversal for extracting object references from a parsed/rewritten expression tree during view/rule/constraint creation. Every node kind that carries an OID pointing into the catalogs must have an arm here, otherwise the object will appear "rootless" to the dependency system. Historically, each new referenceable node (e.g., Const with a typmod collation, FuncExpr, OpExpr, RelabelType, NextValueExpr, etc.) has required a corresponding handler. The SQL/PGQ feature introduced GraphLabelRef and GraphPropertyRef but did not extend this walker — a straightforward oversight.
Additionally, RTE_GRAPH_TABLE is handled in the RTE switch but only records the graph relation itself. The MATCH pattern — stored on rte->graph_pattern as a list of path patterns, each a list of GraphElementPattern nodes with a labelexpr — is never traversed. The labelexpr is where the GraphLabelRef nodes actually live (label expressions like IS l2, IS l1|l2, etc.), so the walker must be taught to recurse into it.
The Proposed Patch
The diff makes three additions:
- Handle
GraphLabelRef: record a dependency on the label's row inpg_propgraph_labelviaadd_object_address(PropgraphLabelRelationId, lref->labelid, 0, ...)and return (leaf node). - Handle
GraphPropertyRef: analogously record a dependency inpg_propgraph_property. Note that the sample query doesn't obviously trigger this path (property access is viaa.name, which may be resolved through column mapping), but recording it is defensively correct — dropping a property that a view relies on should equally be blocked or cascaded. - Recurse into
RTE_GRAPH_TABLE's graph pattern: iterategp->path_pattern_list→path_term(aListofGraphElementPattern) → invoke the walker ongep->labelexpr. This is what actually surfaces theGraphLabelRefnodes to the newly added arm.
A minor cosmetic issue in the diff: the case RTE_RELATION: arm is duplicated (both the original fall-through and a new explicit break are produced) — likely an artifact of how the author separated the two cases to attach new logic only to RTE_GRAPH_TABLE. A committer would tidy this up.
What dependency type should be used?
The patch uses the default dependency class (implied by add_object_address without customization at this call site) — which for view→object is typically DEPENDENCY_NORMAL, producing the "cannot drop ... because other objects depend on it" error seen in the validated output. That matches the behavior of a view referencing a column or function, and is the desired semantics here.
Gaps the author hints at
The author explicitly flags uncertainty ("I'm not sure if anything is missing"). Things that likely still need consideration:
- Element-table references in
GraphElementPattern: patterns can also constrain the underlying vertex/edge table; dependencies on those may already be covered viarte->relidon the graph, but element-level dependencies could matter for more targeted DDL. - Properties referenced via
COLUMNS (a.name, ...): theCOLUMNSlist is likely a separate projection structure. Ifname/ageare lowered intoGraphPropertyRefnodes during parse analysis, the new handler covers them; if they remain as column references resolved at execution, a separate dependency path is needed. The author's result showing the view still returns correct data after the patch suggests the execution-time resolution still works, but it doesn't by itself prove that dropping propertynamewould now be blocked. - ALTER variants: e.g., renaming a label or changing its property set. These should invalidate relcache/plancache entries for dependent views — orthogonal to
pg_dependbut worth checking. pg_dumpround-tripping: dependencies also drive dump ordering; the fix should make dumps of property-graph-using views order correctly.
Why This Matters Architecturally
The cache lookup failed for X class of errors is always a symptom of a broken invariant between catalog rows and the objects that reference them. PostgreSQL's dependency system is the sole mechanism preventing this for user-visible objects. Every new catalog-backed concept must be wired in at three points:
- Creation path:
recordDependencyOn*orrecordDependencyOnExprcalls that know which classId/objId to emit. - Expression walker:
find_expr_references_walkerarms for any new Expr node carrying an OID. - Drop path:
doDeletion/getObjectDescription/getObjectClasssupport so cascades format and execute correctly (the validated output showsgetObjectDescriptionalready formats labels nicely as "label l2 of property graph g", so that wiring exists).
SQL/PGQ, being a large new feature committed relatively recently, is the kind of area where such integration gaps are expected. Catching this before a stable release is valuable: once shipped, users' stored views could be rendered unusable by an otherwise-innocuous ALTER, and fixing it after the fact requires either catalog upgrade logic or a forced re-parse of all dependent views to rebuild pg_depend.
Evaluation
The diagnosis is correct and the fix is on the right track. The mechanism (extending find_expr_references_walker) is idiomatic and matches how every other expression-carried OID reference is handled. The patch is minimal, localized, and the author has validated the end-to-end behavior: the DROP LABEL is now blocked with an appropriate error message identifying the dependent view, and the view continues to work if the label is left alone.
Remaining work for a committed fix would likely include a regression test (in src/test/regress/sql/sqljson_queries.sql or the dedicated property-graph test file), the small code-style cleanup of the duplicated RTE_RELATION case, and verification that property-level and cascade-drop paths are also covered.