On Tue, Jun 9, 2026 at 1:45 AM Andres Freund <[email protected]> wrote: > > Hi, > > On 2026-06-04 19:21:48 +0800, Ewan Young wrote: > > The patch is against master and passes make check. On top of your > > v20260601 series only the test-file hunks conflict (both append to > > graph_table.sql); the code hunk applies cleanly. > > Unfortunately this fails in CI: > https://github.com/postgresql-cfbot/postgresql/actions/runs/27163049887 > > #4 0x598104c3 in ExceptionalCondition (conditionName=0x59adcb6c > "var->varlevelsup > 0 && var->varlevelsup < root->query_level", > fileName=0x59adcb40 "../src/backend/optimizer/util/paramassign.c", > lineNumber=126) at ../src/backend/utils/error/assert.c:65 > No locals. > #5 0x592911f8 in replace_outer_var (root=0x5c92f418, var=0x5c92f708) at > ../src/backend/optimizer/util/paramassign.c:126 > retval = <optimized out> > i = <optimized out> > #6 0x59258569 in replace_correlation_vars_mutator (node=0x5c92f708, > root=0x5c92f418) at ../src/backend/optimizer/plan/subselect.c:2168 > No locals. > #7 0x59191152 in expression_tree_mutator_impl (node=<optimized out>, > mutator=0x592584b0 <replace_correlation_vars_mutator>, context=0x5c92f418) at > ../src/backend/nodes/nodeFuncs.c:3556 > targetentry = 0x5c92f6dc > newnode = 0x5c92fa60 > __func__ = "expression_tree_mutator_impl" > #8 0x59258541 in replace_correlation_vars_mutator (node=0x5c92f6dc, > root=0x5c92f418) at ../src/backend/optimizer/plan/subselect.c:2198 > No locals. > > For a full backtrace, see "Core backtraces" e.g. on > https://github.com/postgresql-cfbot/postgresql/actions/runs/27163049887/job/80183542083
That failure is the result of posting the wrong patch to the wrong thread. Here's the correct set of patches. -- Best Wishes, Ashutosh Bapat
From e72095baf87d7e28c162c0835e0f5f03854eeb63 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <[email protected]> Date: Thu, 28 May 2026 02:47:33 +0530 Subject: [PATCH v20260601 7/7] View referencing labels shared by vertex and edge tables While at it add a test for view containing labels which are shared by both vertex and edge tables. When such a label is dropped from only vertex tables or only edge tables, the view may be rendered invalid because properties only associated with that label can not be resolved. The fix will need to wait for the SQL/PGQ standard to specify the behaviour in such a case. Author: Ashutosh Bapat <[email protected]> --- src/test/regress/expected/graph_table.out | 20 ++++++++++++++++++++ src/test/regress/sql/graph_table.sql | 13 +++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out index cfee0626fdb..475ac20d376 100644 --- a/src/test/regress/expected/graph_table.out +++ b/src/test/regress/expected/graph_table.out @@ -973,6 +973,19 @@ ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL vl2; -- error ERROR: cannot drop label vl2 of property graph g1 because other objects depend on it DETAIL: view v_empty_label depends on label vl2 of property graph g1 HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- l1 is shared by all vertex tables and edge tables. Dropping it from all +-- vertex tables only renders a view unusable. This is because the standard +-- differentiates between a vertex label and an edge label even though they +-- share the same name. Waiting for the standard to clarify the expected +-- behavior in this case. +CREATE VIEW v_shared_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v IS l1) COLUMNS (v.elname)); +BEGIN; +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL l1; +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL l1; +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v3 DROP LABEL l1; +SELECT * FROM v_shared_label; +ERROR: no property graph element of type "vertex" has label "l1" associated with it in property graph "g1" +ROLLBACK; -- ruleutils reverse parsing SELECT pg_get_viewdef('customers_us'::regclass); pg_get_viewdef @@ -993,6 +1006,13 @@ SELECT pg_get_viewdef('v_empty_label'::regclass); FROM GRAPH_TABLE (g1 MATCH (v IS l1|vl1|vl2|vl3 WHERE (v.vprop1 = 10)) COLUMNS (v.elname AS elname)); (1 row) +SELECT pg_get_viewdef('v_shared_label'::regclass); + pg_get_viewdef +------------------------------------------------------------------------ + SELECT elname + + FROM GRAPH_TABLE (g1 MATCH (v IS l1) COLUMNS (v.elname AS elname)); +(1 row) + -- test view/graph nesting CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers; SELECT * FROM customers; diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql index c4cb7999857..5412f10ac3c 100644 --- a/src/test/regress/sql/graph_table.sql +++ b/src/test/regress/sql/graph_table.sql @@ -554,9 +554,22 @@ ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE products CREATE VIEW v_empty_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v WHERE v.vprop1 = 10) COLUMNS (v.elname)); ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL vl1; -- error ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL vl2; -- error +-- l1 is shared by all vertex tables and edge tables. Dropping it from all +-- vertex tables only renders a view unusable. This is because the standard +-- differentiates between a vertex label and an edge label even though they +-- share the same name. Waiting for the standard to clarify the expected +-- behavior in this case. +CREATE VIEW v_shared_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v IS l1) COLUMNS (v.elname)); +BEGIN; +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL l1; +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL l1; +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v3 DROP LABEL l1; +SELECT * FROM v_shared_label; +ROLLBACK; -- ruleutils reverse parsing SELECT pg_get_viewdef('customers_us'::regclass); SELECT pg_get_viewdef('v_empty_label'::regclass); +SELECT pg_get_viewdef('v_shared_label'::regclass); -- test view/graph nesting -- 2.34.1
From db93ea38da965fa2503ef528520076378da70cc7 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <[email protected]> Date: Thu, 28 May 2026 02:45:58 +0530 Subject: [PATCH v20260601 6/7] Empty label expression in view definition A view definition depends on all the graph labels that are explicitly mentioned in the graph patterns in it so as to avoid it being rendered invalid when any of the labels is dropped. But when a view definition contains an empty label expression, we do not create any dependency between the view and the labels that the empty label expression resolves to. Resolve an empty label expression during transformation phase so that we can create dependency between those labels and the view. This will further help to avoid invalidation of a view containing all-properties references when we support it. An empty label expression may resolve to an empty set of labels if there are not labels associated with the elements matching the kind of element pattern containing the label expression. We do not have a syntax level support for a label expression containing no labels. Hence we can not dump a view containing such an empty label expression. Throw an error when a query contains such an empty label expression. There are possibly no real usecases which use such queries. Author: Ashutosh Bapat <[email protected]> Discussion: https://www.postgresql.org/message-id/CAExHW5twGP5Zuk4Zch4kz8XDrSpckWQipMs=ysaj8gmqna2...@mail.gmail.com --- src/backend/parser/parse_graphtable.c | 110 +++++++++++++++++- src/backend/rewrite/rewriteGraphTable.c | 78 ++++--------- src/include/nodes/parsenodes.h | 8 ++ .../expected/create_property_graph.out | 1 + src/test/regress/expected/graph_table.out | 29 ++++- .../regress/sql/create_property_graph.sql | 1 + src/test/regress/sql/graph_table.sql | 16 ++- 7 files changed, 178 insertions(+), 65 deletions(-) diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c index 73fbfb541f7..5c9da75c8cf 100644 --- a/src/backend/parser/parse_graphtable.c +++ b/src/backend/parser/parse_graphtable.c @@ -18,6 +18,8 @@ #include "access/genam.h" #include "access/htup_details.h" #include "access/table.h" +#include "catalog/pg_propgraph_element.h" +#include "catalog/pg_propgraph_element_label.h" #include "catalog/pg_propgraph_label.h" #include "catalog/pg_propgraph_property.h" #include "miscadmin.h" @@ -151,6 +153,51 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref) return NULL; } +/* + * Given the OID of a label and the kind of graph element pattern, return true if + * there exists at least one element matching the given kind associated with the + * label. Otherwise return false. + */ +static bool +label_has_elements_of_kind(Oid labelid, GraphElementPatternKind gepkind) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tup; + bool result = false; + + rel = table_open(PropgraphElementLabelRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_element_label_pgellabelid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(labelid)); + scan = systable_beginscan(rel, PropgraphElementLabelLabelIndexId, + true, NULL, 1, key); + while (!result && HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_propgraph_element_label element_label = (Form_pg_propgraph_element_label) GETSTRUCT(tup); + Oid element_oid = element_label->pgelelid; + HeapTuple element_tup = SearchSysCache1(PROPGRAPHELOID, ObjectIdGetDatum(element_oid)); + Form_pg_propgraph_element element_form; + + if (!HeapTupleIsValid(element_tup)) + elog(ERROR, "cache lookup failed for property graph element %u", element_oid); + + element_form = (Form_pg_propgraph_element) GETSTRUCT(element_tup); + + if ((element_form->pgekind == PGEKIND_VERTEX && gepkind == VERTEX_PATTERN) || + (element_form->pgekind == PGEKIND_EDGE && IS_EDGE_PATTERN(gepkind))) + result = true; + + ReleaseSysCache(element_tup); + } + + systable_endscan(scan); + table_close(rel, AccessShareLock); + return result; +} + /* * Transform a label expression. * @@ -161,14 +208,66 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref) * GraphLabelRef nodes corresponding to the names of the labels appearing in the * expression. If any label name cannot be resolved to a label in the property * graph, an error is raised. + * + * An empty label expression is treated as a special case. According to section + * 9.2 "Contextual inference of a set of labels" subclause 2.a.ii of SQL/PGQ + * standard, element pattern which does not have a label expression is + * considered to have label expression equivalent to '%|!%' which is set of all + * labels which have at least one element of the given element kind associated with it. */ static Node * -transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr) +transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr, GraphElementPatternKind gepkind) { Node *result; - if (labelexpr == NULL) - return NULL; + if (!labelexpr) + { + Relation rel; + SysScanDesc scan; + ScanKeyData key[1]; + HeapTuple tup; + List *args = NIL; + + rel = table_open(PropgraphLabelRelationId, AccessShareLock); + ScanKeyInit(&key[0], + Anum_pg_propgraph_label_pglpgid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(gpstate->graphid)); + scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, + true, NULL, 1, key); + while (HeapTupleIsValid(tup = systable_getnext(scan))) + { + Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup); + GraphLabelRef *lref; + + if (!label_has_elements_of_kind(label->oid, gepkind)) + continue; + + lref = makeNode(GraphLabelRef); + lref->labelid = label->oid; + lref->location = -1; + args = lappend(args, lref); + } + systable_endscan(scan); + table_close(rel, AccessShareLock); + + /* + * If there are no labels with elements of the given kind, the set of + * labels that this label expression resolves to is empty. There is no + * way to represent an empty set of labels as a label expression since + * we do not support label conjunction as well as negation. So we can + * not dump a view containing such a label expression. Hence prohibit + * it for now. + */ + if (!args) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("empty label expression does not resolve to any label")); + + result = (Node *) makeBoolExpr(OR_EXPR, args, -1); + return result; + + } check_stack_depth(); @@ -208,7 +307,7 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr) { Node *arg = (Node *) lfirst(lc); - arg = transformLabelExpr(gpstate, arg); + arg = transformLabelExpr(gpstate, arg, gepkind); args = lappend(args, arg); } @@ -249,7 +348,8 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep) gpstate->cur_gep = gep; - gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr); + gep->has_empty_labelexpr = !gep->labelexpr; + gep->labelexpr = transformLabelExpr(gpstate, gep->labelexpr, gep->kind); gep->whereClause = transformExpr(pstate, gep->whereClause, EXPR_KIND_WHERE); diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c index 33d4e866d74..2292ca6dcee 100644 --- a/src/backend/rewrite/rewriteGraphTable.c +++ b/src/backend/rewrite/rewriteGraphTable.c @@ -58,6 +58,8 @@ struct path_factor { GraphElementPatternKind kind; const char *variable; + bool has_empty_labelexpr; /* Copied from the corresponding + * GraphElementPattern */ Node *labelexpr; Node *whereClause; int factorpos; /* Position of this path factor in the list of @@ -221,9 +223,12 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern) * expression itself. Hence if only one of the two element * patterns has a label expression use that expression. */ - if (!other->labelexpr) + if (other->has_empty_labelexpr) + { other->labelexpr = gep->labelexpr; - else if (gep->labelexpr && !equal(other->labelexpr, gep->labelexpr)) + other->has_empty_labelexpr = gep->has_empty_labelexpr; + } + else if (!gep->has_empty_labelexpr && !equal(other->labelexpr, gep->labelexpr)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("element patterns with same variable name \"%s\" but different label expressions are not supported", @@ -250,6 +255,7 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern) pf = palloc0_object(struct path_factor); pf->factorpos = factorpos++; pf->kind = gep->kind; + pf->has_empty_labelexpr = gep->has_empty_labelexpr; pf->labelexpr = gep->labelexpr; pf->variable = gep->variable; pf->whereClause = gep->whereClause; @@ -823,37 +829,9 @@ get_labels_for_expr(Oid propgraphid, Node *labelexpr) { List *label_oids; - if (!labelexpr) - { - Relation rel; - SysScanDesc scan; - ScanKeyData key[1]; - HeapTuple tup; - - /* - * According to section 9.2 "Contextual inference of a set of labels" - * subclause 2.a.ii of SQL/PGQ standard, element pattern which does - * not have a label expression is considered to have label expression - * equivalent to '%|!%' which is set of all labels. - */ - label_oids = NIL; - rel = table_open(PropgraphLabelRelationId, AccessShareLock); - ScanKeyInit(&key[0], - Anum_pg_propgraph_label_pglpgid, - BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(propgraphid)); - scan = systable_beginscan(rel, PropgraphLabelGraphNameIndexId, - true, NULL, 1, key); - while (HeapTupleIsValid(tup = systable_getnext(scan))) - { - Form_pg_propgraph_label label = (Form_pg_propgraph_label) GETSTRUCT(tup); + Assert(labelexpr); - label_oids = lappend_oid(label_oids, label->oid); - } - systable_endscan(scan); - table_close(rel, AccessShareLock); - } - else if (IsA(labelexpr, GraphLabelRef)) + if (IsA(labelexpr, GraphLabelRef)) { GraphLabelRef *glr = castNode(GraphLabelRef, labelexpr); @@ -903,7 +881,6 @@ get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf) List *elem_oids_seen = NIL; List *pf_elem_oids = NIL; List *path_elements = NIL; - List *unresolved_labels = NIL; Relation rel; SysScanDesc scan; ScanKeyData key[1]; @@ -970,33 +947,26 @@ get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf) { /* * We did not find any qualified element associated with this - * label. The label or its properties can not be associated with - * the given element pattern. Throw an error if the label was - * explicitly specified in the element pattern. Otherwise remember - * it for later use. + * label. Throw an error. + * + * An empty label expression is replaced by all labels that are + * associated with at least one element of the required kind. We + * should not reach here in that case. */ - if (!pf->labelexpr) - unresolved_labels = lappend_oid(unresolved_labels, labeloid); - else - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"", - pf->kind == VERTEX_PATTERN ? "vertex" : "edge", - get_propgraph_label_name(labeloid), - get_rel_name(propgraphid)))); + Assert(!pf->has_empty_labelexpr); + + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no property graph element of type \"%s\" has label \"%s\" associated with it in property graph \"%s\"", + pf->kind == VERTEX_PATTERN ? "vertex" : "edge", + get_propgraph_label_name(labeloid), + get_rel_name(propgraphid)))); } systable_endscan(scan); } table_close(rel, AccessShareLock); - - /* - * Remove the labels which were not explicitly mentioned in the label - * expression but do not have any qualified elements associated with them. - * Properties associated with such labels may not be referenced. See - * replace_property_refs_mutator() for more details. - */ - pf->labeloids = list_difference_oid(label_oids, unresolved_labels); + pf->labeloids = label_oids; return path_elements; } diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 91377a6cde3..71040261375 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1048,7 +1048,15 @@ typedef struct GraphElementPattern NodeTag type; GraphElementPatternKind kind; const char *variable; + + /* + * If no label expression is specified, we will replace it with a non-NULL + * expression in transformLabelExpr(). This flag indicates whether the + * label expression was originally empty. + */ + bool has_empty_labelexpr; Node *labelexpr; + List *subexpr; Node *whereClause; List *quantifier; diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out index 65508223f54..a1a5501dd52 100644 --- a/src/test/regress/expected/create_property_graph.out +++ b/src/test/regress/expected/create_property_graph.out @@ -939,6 +939,7 @@ DETAIL: Table "v2tmp" is a temporary table. DROP TABLE g2; -- error: wrong object type ERROR: "g2" is not a table HINT: Use DROP PROPERTY GRAPH to remove a property graph. +ALTER PROPERTY GRAPH g1 ADD VERTEX TABLES (t1 KEY (a)); -- to make a valid graph query CREATE VIEW vg1 AS SELECT * FROM GRAPH_TABLE(g1 MATCH () COLUMNS (1 AS one)); DROP PROPERTY GRAPH g1; -- error ERROR: cannot drop property graph g1 because other objects depend on it diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out index a5f9f0ac90a..cfee0626fdb 100644 --- a/src/test/regress/expected/graph_table.out +++ b/src/test/regress/expected/graph_table.out @@ -844,6 +844,8 @@ EXECUTE loopstmt; (2 rows) -- inheritance and partitioning +-- +-- Also test temporary property graphs, keywords NODE and RELATIONSHIP CREATE TABLE pv (id int, val int); CREATE TABLE cv1 () INHERITS (pv); CREATE TABLE cv2 () INHERITS (pv); @@ -873,7 +875,6 @@ SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.va 30 | 300 | 10 (3 rows) --- temporary property graph CREATE TEMPORARY PROPERTY GRAPH gtmp VERTEX TABLES ( pv KEY (id) @@ -900,8 +901,12 @@ CREATE TABLE ptne1 PARTITION OF ptne FOR VALUES IN (1, 2); CREATE TABLE ptne2 PARTITION OF ptne FOR VALUES IN (3); INSERT INTO ptne VALUES (1, 1, 2, 100), (2, 2, 3, 200), (3, 3, 1, 300); CREATE PROPERTY GRAPH g4 - VERTEX TABLES (ptnv) - EDGE TABLES ( + VERTEX TABLES (ptnv); +-- empty label expression which resolves to no labels +SELECT * FROM GRAPH_TABLE (g4 MATCH (s is ptnv)-[e]-(d is ptnv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; -- error +ERROR: empty label expression does not resolve to any label +ALTER PROPERTY GRAPH g4 + ADD EDGE TABLES ( ptne SOURCE KEY (src) REFERENCES ptnv(id) DESTINATION KEY (dest) REFERENCES ptnv(id) @@ -957,6 +962,17 @@ ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE products ERROR: cannot drop property price of property graph myshop because other objects depend on it DETAIL: view customers_us depends on property price of property graph myshop HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- Empty label expression creates a dependency between the view and the set of +-- labels it resolves to +CREATE VIEW v_empty_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v WHERE v.vprop1 = 10) COLUMNS (v.elname)); +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL vl1; -- error +ERROR: cannot drop label vl1 of property graph g1 because other objects depend on it +DETAIL: view v_empty_label depends on label vl1 of property graph g1 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL vl2; -- error +ERROR: cannot drop label vl2 of property graph g1 because other objects depend on it +DETAIL: view v_empty_label depends on label vl2 of property graph g1 +HINT: Use DROP ... CASCADE to drop the dependent objects too. -- ruleutils reverse parsing SELECT pg_get_viewdef('customers_us'::regclass); pg_get_viewdef @@ -970,6 +986,13 @@ SELECT pg_get_viewdef('customers_us'::regclass); ORDER BY g.customer_name, g.product_name; (1 row) +SELECT pg_get_viewdef('v_empty_label'::regclass); + pg_get_viewdef +---------------------------------------------------------------------------------------------------------- + SELECT elname + + FROM GRAPH_TABLE (g1 MATCH (v IS l1|vl1|vl2|vl3 WHERE (v.vprop1 = 10)) COLUMNS (v.elname AS elname)); +(1 row) + -- test view/graph nesting CREATE VIEW customers_view AS SELECT customer_id, 'redacted' || customer_id AS name_redacted, address FROM customers; SELECT * FROM customers; diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql index e8b3b3ff94c..0c3aa81e9ca 100644 --- a/src/test/regress/sql/create_property_graph.sql +++ b/src/test/regress/sql/create_property_graph.sql @@ -364,6 +364,7 @@ ALTER PROPERTY GRAPH g1 -- DROP, ALTER SET SCHEMA, ALTER PROPERTY GRAPH RENAME TO DROP TABLE g2; -- error: wrong object type +ALTER PROPERTY GRAPH g1 ADD VERTEX TABLES (t1 KEY (a)); -- to make a valid graph query CREATE VIEW vg1 AS SELECT * FROM GRAPH_TABLE(g1 MATCH () COLUMNS (1 AS one)); DROP PROPERTY GRAPH g1; -- error ALTER PROPERTY GRAPH g1 SET SCHEMA create_property_graph_tests_2; diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql index 80576b2f9f4..c4cb7999857 100644 --- a/src/test/regress/sql/graph_table.sql +++ b/src/test/regress/sql/graph_table.sql @@ -471,6 +471,8 @@ ALTER PROPERTY GRAPH g1 ALTER EDGE TABLE e3_3 ALTER LABEL l2 ADD PROPERTIES ((en EXECUTE loopstmt; -- inheritance and partitioning +-- +-- Also test temporary property graphs, keywords NODE and RELATIONSHIP CREATE TABLE pv (id int, val int); CREATE TABLE cv1 () INHERITS (pv); CREATE TABLE cv2 () INHERITS (pv); @@ -493,7 +495,6 @@ CREATE PROPERTY GRAPH g3 DESTINATION KEY(dest) REFERENCES pv(id) ); SELECT * FROM GRAPH_TABLE (g3 MATCH (s IS pv)-[e IS pe]->(d IS pv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; --- temporary property graph CREATE TEMPORARY PROPERTY GRAPH gtmp VERTEX TABLES ( pv KEY (id) @@ -514,8 +515,11 @@ CREATE TABLE ptne1 PARTITION OF ptne FOR VALUES IN (1, 2); CREATE TABLE ptne2 PARTITION OF ptne FOR VALUES IN (3); INSERT INTO ptne VALUES (1, 1, 2, 100), (2, 2, 3, 200), (3, 3, 1, 300); CREATE PROPERTY GRAPH g4 - VERTEX TABLES (ptnv) - EDGE TABLES ( + VERTEX TABLES (ptnv); +-- empty label expression which resolves to no labels +SELECT * FROM GRAPH_TABLE (g4 MATCH (s is ptnv)-[e]-(d is ptnv) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; -- error +ALTER PROPERTY GRAPH g4 + ADD EDGE TABLES ( ptne SOURCE KEY (src) REFERENCES ptnv(id) DESTINATION KEY (dest) REFERENCES ptnv(id) @@ -545,8 +549,14 @@ ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE customers ALTER LABEL customers DROP PROPERTIES (address); -- error ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE products ALTER LABEL products DROP PROPERTIES (price); -- error +-- Empty label expression creates a dependency between the view and the set of +-- labels it resolves to +CREATE VIEW v_empty_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v WHERE v.vprop1 = 10) COLUMNS (v.elname)); +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v1 DROP LABEL vl1; -- error +ALTER PROPERTY GRAPH g1 ALTER VERTEX TABLE v2 DROP LABEL vl2; -- error -- ruleutils reverse parsing SELECT pg_get_viewdef('customers_us'::regclass); +SELECT pg_get_viewdef('v_empty_label'::regclass); -- test view/graph nesting -- 2.34.1
From ba5614bcd38b96e0e455225bab3cb510ab7f151f Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <[email protected]> Date: Sun, 17 May 2026 12:21:13 +0530 Subject: [PATCH v20260601 5/7] Record dependencies on graph labels and properties A view definition with GRAPH_TABLE depends upon the property graph it references as well as the properties and labels referenced in it. We recorded the dependency on the property graph, but did not record dependency on labels and properties. This allowed properties or labels referenced by a view to be dropped resulting in a cache lookup error when such a view was accessed. Fix this bug by handling GraphPropertyRef and GraphLabelRef in find_expr_references_walker(). The dependency on the data type of property is not needed to be recorded separately as it is recorded indirectly via dependency on the property graph property itself. Please note that a property or a label associated with individual elements can still be dropped as long as there are other elements which are associated with that property or label since they do not lead to dropping the property or the label from the property graph altogether. Reported-by: Man Zeng <[email protected]> Author: Ayush Tiwari <[email protected]> Author: Ashutosh Bapat <[email protected]> Reviewed by: Junwang Zhao <[email protected]> Discussion: https://postgr.es/m/[email protected] --- src/backend/catalog/dependency.c | 19 ++++++++++++ src/test/regress/expected/graph_table.out | 37 +++++++++++++++++------ src/test/regress/sql/graph_table.sql | 14 +++++++-- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index fdb8e67e1f5..c54774b3275 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -2165,6 +2165,25 @@ find_expr_references_walker(Node *node, add_object_address(TypeRelationId, rowexpr->row_typeid, 0, context->addrs); } + else if (IsA(node, GraphLabelRef)) + { + GraphLabelRef *glr = (GraphLabelRef *) node; + + /* GRAPH_TABLE label reference depends on the property graph label */ + add_object_address(PropgraphLabelRelationId, glr->labelid, 0, + context->addrs); + } + else if (IsA(node, GraphPropertyRef)) + { + GraphPropertyRef *gpr = (GraphPropertyRef *) node; + + /* + * GRAPH_TABLE property reference depends on the property graph + * property + */ + add_object_address(PropgraphPropertyRelationId, gpr->propid, 0, + context->addrs); + } else if (IsA(node, RowCompareExpr)) { RowCompareExpr *rcexpr = (RowCompareExpr *) node; diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out index e8d49fd5cd4..a5f9f0ac90a 100644 --- a/src/test/regress/expected/graph_table.out +++ b/src/test/regress/expected/graph_table.out @@ -929,7 +929,7 @@ SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e 30 | 300 | 10 (2 rows) --- ruleutils reverse parsing +-- GRAPH_TABLE in views -- The query in the view definition is intentionally complex to test one view with many -- features like label disjunction, lateral references, WHERE clauses in graph -- patterns. @@ -938,16 +938,35 @@ SELECT g.* FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a) -[IS customer_orders | customer_wishlists ]-> (l IS orders | wishlists)-[ IS list_items]->(p IS products) - COLUMNS (c.name AS customer_name, p.name AS product_name, x1.a AS a)) g + COLUMNS (c.name AS customer_name, p.name AS product_name, p.price, x1.a AS a)) g ORDER BY customer_name, product_name; +-- Dropping properties or labels used by a view is not allowed +-- If these DDLs succeed, the pg_get_viewdef call below will throw cache lookup +-- error. +ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE orders DROP LABEL orders; -- error +ERROR: cannot drop label orders of property graph myshop because other objects depend on it +DETAIL: view customers_us depends on label orders of property graph myshop +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE customers + ALTER LABEL customers DROP PROPERTIES (address); -- error +ERROR: cannot drop property address of property graph myshop because other objects depend on it +DETAIL: view customers_us depends on property address of property graph myshop +HINT: Use DROP ... CASCADE to drop the dependent objects too. +ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE products + ALTER LABEL products DROP PROPERTIES (price); -- error +ERROR: cannot drop property price of property graph myshop because other objects depend on it +DETAIL: view customers_us depends on property price of property graph myshop +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- ruleutils reverse parsing SELECT pg_get_viewdef('customers_us'::regclass); - pg_get_viewdef ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - SELECT g.customer_name, + - g.product_name, + - g.a + - FROM x1, + - GRAPH_TABLE (myshop MATCH (c IS customers WHERE (((c.address)::text = 'US'::text) AND (c.customer_id = x1.a)))-[IS customer_orders|customer_wishlists]->(l IS orders|wishlists)-[IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name, x1.a AS a)) g+ + pg_get_viewdef +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + SELECT g.customer_name, + + g.product_name, + + g.price, + + g.a + + FROM x1, + + GRAPH_TABLE (myshop MATCH (c IS customers WHERE (((c.address)::text = 'US'::text) AND (c.customer_id = x1.a)))-[IS customer_orders|customer_wishlists]->(l IS orders|wishlists)-[IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name, p.price AS price, x1.a AS a)) g+ ORDER BY g.customer_name, g.product_name; (1 row) diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql index e761f09e057..80576b2f9f4 100644 --- a/src/test/regress/sql/graph_table.sql +++ b/src/test/regress/sql/graph_table.sql @@ -525,7 +525,8 @@ SELECT * FROM GRAPH_TABLE (g4 MATCH (s IS ptnv)-[e IS ptne]->(d IS ptnv) COLUMNS SELECT * FROM GRAPH_TABLE (g4 MATCH (s)-[e]-(d) WHERE s.id = 3 COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; --- ruleutils reverse parsing +-- GRAPH_TABLE in views + -- The query in the view definition is intentionally complex to test one view with many -- features like label disjunction, lateral references, WHERE clauses in graph -- patterns. @@ -534,8 +535,17 @@ SELECT g.* FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a) -[IS customer_orders | customer_wishlists ]-> (l IS orders | wishlists)-[ IS list_items]->(p IS products) - COLUMNS (c.name AS customer_name, p.name AS product_name, x1.a AS a)) g + COLUMNS (c.name AS customer_name, p.name AS product_name, p.price, x1.a AS a)) g ORDER BY customer_name, product_name; +-- Dropping properties or labels used by a view is not allowed +-- If these DDLs succeed, the pg_get_viewdef call below will throw cache lookup +-- error. +ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE orders DROP LABEL orders; -- error +ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE customers + ALTER LABEL customers DROP PROPERTIES (address); -- error +ALTER PROPERTY GRAPH myshop ALTER VERTEX TABLE products + ALTER LABEL products DROP PROPERTIES (price); -- error +-- ruleutils reverse parsing SELECT pg_get_viewdef('customers_us'::regclass); -- test view/graph nesting -- 2.34.1
