On Fri, May 22, 2026 at 12:07 AM Ashutosh Bapat <[email protected]> wrote: > > On Sun, May 17, 2026 at 11:26 PM Ayush Tiwari > <[email protected]> wrote: > > > > Hi, > > > > On Mon, 18 May 2026 at 07:37, Junwang Zhao <[email protected]> wrote: > >> > >> On Mon, May 18, 2026 at 8:22 AM Ashutosh Bapat > >> <[email protected]> wrote: > >> > >> > >> > >> > >> I shortened the commit message by taking essential elements from your > >> > >> commit message. > >> > >> > >> > >> Please review the attached patch. > >> > > > >> > > > >> > > The attached patch seems not for this thread. > >> > > >> > Thanks for noticing. Here's attached correct patch. > >> > >> The patch LGTM, thanks for taking care of this. > > > > > > +1, LGTM > > > > I've changed the status in CF entry status to Ready for Committer. > > https://commitfest.postgresql.org/patch/6760/ > > Thanks Ayush. > > While working on this, I found two other problems. Before we dive into > those, I think we should commit the current patch. It need not wait > for a solution to these problems. > > I was checking whether we should be expanding an empty label during > the transformation stage instead of rewrite phase so that, in case the > graph pattern is part of a view definition, the set of labels the > empty label expression resolves to is stored in the catalogs. That > way, we can create a dependency of view on the set of labels at the > time of view. After discussing it with Peter Eisentraut, we feel that > doing so will be good for future-proofing pg_dump of views containing > graph patterns with empty labels. We will definitely need it before > supporting all properties reference since the properties that the all > properties reference expands to depends upon the set of labels in the > label expression. We need to create dependencies between those > properties and the view and hence need dependencies between labels > (that the empty label expression resolves to) and the view. I will > provide a patch for the same soon.
While working on this I found another issue. An empty label expression may turn into no label which does not have any syntax level support. Hence we can't dump a view containing such an empty label expression. For now I have prohibited empty label expressions which do not resolve to any label as an unsupported feature. I doubt if there will be any valid use case for such a label expression. As a result a test which tests dependency between a view and the property graph in create_property_graph.sql failed. Failure is accidental as test uses an empty property graph for this purpose. I fixed it by adding a vertex table to the property graph being referenced in the view. The commit message just mentions the label dependency asymmetry between explicitly mentioned labels and labels resulting from empty label expression. I haven't gone into the details of all-properties-reference to keep it simple. The discussion link should lead a curious reader to the complex discussion here. I did reconsider whether to fix this right now or whether we can postpone it till we support all-properties reference. If we don't fix this, a query containing empty label expression will return different result as the set of labels in the property graph changes. When we will support all properties reference, we will have to fix this and then the behaviour will change breaking backward compatibility. So I think we have to fix this now. > > When trying different things which could lead to invalidation of view > if we don't add the dependency between labels (that the empty label > expression resolves to) and the view, I found another way to > invalidate the view. > > To reproduce it, run the following statements after running > graph_table.sql tests. > CREATE VIEW v_empty_label AS SELECT * FROM GRAPH_TABLE (g1 MATCH (v > WHERE v.vprop1 = 10) 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_empty_label; > ROLLBACK; > > The three ALTER TABLE statements leave the label behind since it's > associated with the edge tables. The SELECT statement still throws > "ERROR: property "elname" for element variable "v" not found". If we > expand the empty label reference in the transformation phase and > record dependencies between those labels and the view, we get a > different error "ERROR: no property graph element of type "vertex" > has label "l1" associated with it in property graph "g1"". The first > error is a bit obscure compared to the second one. So there's relative > improvement. Myself and Peter thought that the second error is an > artifact of the term "vertex labels" in the standard; a term which is > not properly defined in the standard. There are two solutions > > 1. We create vertex label and edge label as separate objects and > record depdencies accordingly > 2. We ignore the term "vertex/edge label" in the standard and handle > labels that exist in property graphs but have no associated element of > required type in the query. I think this will require some corrections > in standard to standardize the outcome of such queries. > > I will provide patches once we decide which approach to take. For now I have added a test case exhibiting this behaviour. If we commit the test, somebody who comes across this behaviour will know why the current behaviour is the way it is and what's the way forward. But if we don't commit it, which is ok, they will consider this as buggy behaviour and report it. I am fine either way. As I mentioned earlier, I am creating patches for all outstanding issues from the same branch. So the patches attached here do not start from 0001, but they apply with git am on the latest master for me without any problem. 0005 - fixes `cache lookup failed for label` 0006 - fixes empty label expression and view behaviour 0007 - issue with labels shared by vertex and edges -- 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 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
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
