From 2d6266aaf6b9765546c746ce45cf4836147e3d31 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat Date: Mon, 23 Mar 2026 19:29:21 +0530 Subject: [PATCH v20260324 2/3] Implicit vertex patterns When a path pattern contains two consecutive edge patterns without a vertex pattern in between, we implicitly add a vertex pattern between them in accordance with the syntax rule 13, subclause 10.6 of SQL/PGQ standard. On the other hand, consecutive vertex patterns without an edge pattern in between is not supported. Author: Ashutosh Bapat Reported by: Alexander Lakhin Investigation by: Henson Choi Reviewed by: Henson Choi --- src/backend/parser/parse_graphtable.c | 66 +++++++++++++++++++++-- src/backend/rewrite/rewriteGraphTable.c | 10 ++++ src/backend/utils/adt/ruleutils.c | 4 ++ src/include/nodes/parsenodes.h | 3 ++ src/test/regress/expected/graph_table.out | 28 ++++++++++ src/test/regress/sql/graph_table.sql | 6 +++ 6 files changed, 112 insertions(+), 5 deletions(-) diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c index bf805b4beb6..1590f4f9cde 100644 --- a/src/backend/parser/parse_graphtable.c +++ b/src/backend/parser/parse_graphtable.c @@ -225,11 +225,6 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep) { GraphTableParseState *gpstate = pstate->p_graph_table_pstate; - if (gep->kind != VERTEX_PATTERN && !IS_EDGE_PATTERN(gep->kind)) - ereport(ERROR, - errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("unsupported element pattern kind: \"%s\"", get_gep_kind_name(gep->kind))); - if (gep->quantifier) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -246,6 +241,23 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep) return (Node *) gep; } +/* + * Create an implicit element patterns of the given kind. + * + * An implicit element pattern is an empty element pattern of given kind. + */ +static GraphElementPattern * +createImplicitElementPattern(ParseState *pstate, GraphElementPatternKind kind) +{ + GraphElementPattern *gep = makeNode(GraphElementPattern); + + gep->kind = kind; + gep->implicit = true; + gep->location = -1; + + return gep; +} + /* * Transform a path term (list of GraphElementPattern's). */ @@ -253,10 +265,54 @@ static Node * transformPathTerm(ParseState *pstate, List *path_term) { List *result = NIL; + GraphElementPattern *prev_gep = NULL; foreach_node(GraphElementPattern, gep, path_term) + { + if (gep->kind != VERTEX_PATTERN && !IS_EDGE_PATTERN(gep->kind)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported element pattern kind: \"%s\"", get_gep_kind_name(gep->kind)), + parser_errposition(pstate, gep->location))); + + if (IS_EDGE_PATTERN(gep->kind)) + { + /* + * Add an implicit vertex pattern at the beginning if the given + * path starts with an edge or add an implicit vertex pattern + * between two consecutive edge patterns. + */ + if (!prev_gep || prev_gep->kind != VERTEX_PATTERN) + { + GraphElementPattern *empty_vertex_pattern = createImplicitElementPattern(pstate, VERTEX_PATTERN); + + result = lappend(result, transformGraphElementPattern(pstate, empty_vertex_pattern)); + } + } + else + { + if (prev_gep && !IS_EDGE_PATTERN(prev_gep->kind)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("path pattern with two consecutive vertex patterns without an edge pattern in between is not supported"), + parser_errposition(pstate, gep->location))); + } + result = lappend(result, transformGraphElementPattern(pstate, gep)); + prev_gep = gep; + } + + /* + * Add an implicit vertex pattern at the end if the last element is an + * edge pattern. + */ + if (prev_gep && IS_EDGE_PATTERN(prev_gep->kind)) + { + GraphElementPattern *empty_vertex_pattern = createImplicitElementPattern(pstate, VERTEX_PATTERN); + + result = lappend(result, transformGraphElementPattern(pstate, empty_vertex_pattern)); + } return (Node *) result; } diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c index d3f382573fd..028b1841531 100644 --- a/src/backend/rewrite/rewriteGraphTable.c +++ b/src/backend/rewrite/rewriteGraphTable.c @@ -303,6 +303,11 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern) errmsg("an edge cannot connect more than two vertexes even in a cyclic pattern")); prev_pf->src_pf = pf; } + else + { + Assert(prev_pf->kind == VERTEX_PATTERN); + Assert(IS_EDGE_PATTERN(pf->kind)); + } if (pf->kind == EDGE_PATTERN_RIGHT || pf->kind == EDGE_PATTERN_ANY) { @@ -322,6 +327,11 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern) errmsg("an edge cannot connect more than two vertexes even in a cyclic pattern")); pf->dest_pf = prev_pf; } + else + { + Assert(pf->kind == VERTEX_PATTERN); + Assert(IS_EDGE_PATTERN(prev_pf->kind)); + } } prev_pf = pf; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 7bc12589e40..99e7cf4406c 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8030,6 +8030,10 @@ get_path_pattern_expr_def(List *path_pattern_expr, deparse_context *context) GraphElementPattern *gep = lfirst_node(GraphElementPattern, lc); const char *sep = ""; + /* Ignore implicitly added elements */ + if (gep->implicit) + continue; + switch (gep->kind) { case VERTEX_PATTERN: diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index df431220ac5..95f7cce1812 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1049,6 +1049,9 @@ typedef struct GraphElementPattern List *subexpr; Node *whereClause; List *quantifier; + bool implicit pg_node_attr(query_jumble_ignore); /* True if implicitly + * added during + * transformation. */ ParseLoc location; } GraphElementPattern; diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out index eeaf1b85121..4f951b15d0f 100644 --- a/src/test/regress/expected/graph_table.out +++ b/src/test/regress/expected/graph_table.out @@ -103,6 +103,8 @@ SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers)->{1,2}(o IS ERROR: element pattern quantifier is not supported SELECT * FROM GRAPH_TABLE (myshop MATCH ((c IS customers)->(o IS orders)) COLUMNS (c.name)); ERROR: unsupported element pattern kind: "nested path pattern" +LINE 1: SELECT * FROM GRAPH_TABLE (myshop MATCH ((c IS customers)->(... + ^ -- a property graph can be referenced only from within GRAPH_TABLE clause. SELECT * FROM myshop; -- error ERROR: cannot open relation "myshop" @@ -434,6 +436,10 @@ SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[ ERROR: "*" not allowed here LINE 1: ...M GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT... ^ +SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 WHERE a.vprop1 in (10, 30))(b IS vl2 WHERE b.vprop2 >= 1200) COLUMNS (a.vname AS aname, b.vname AS bname)); +ERROR: path pattern with two consecutive vertex patterns without an edge pattern in between is not supported +LINE 1: ...LE (g1 MATCH (a IS vl1 WHERE a.vprop1 in (10, 30))(b IS vl2 ... + ^ -- select all the properties across all the labels associated with a given type -- of graph element SELECT * FROM GRAPH_TABLE (g1 MATCH (src)-[conn]->(dest) COLUMNS (src.vname AS svname, conn.ename AS cename, dest.vname AS dvname, src.vprop1 AS svp1, src.vprop2 AS svp2, src.lprop1 AS slp1, dest.vprop1 AS dvp1, dest.vprop2 AS dvp2, dest.lprop1 AS dlp1, conn.eprop1 AS cep1, conn.lprop2 AS clp2)); @@ -580,6 +586,20 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a IS vl1) COLUMNS (a.vname AS sel v13 | v23 | 30 | 1030 (2 rows) +-- implicitly added vertex pattern +SELECT * FROM GRAPH_TABLE (g1 MATCH -[c IS el2 ]-> COLUMNS (c.ename AS cname)); + cname +------- + e231 + e321 +(2 rows) + +SELECT count(*) FROM GRAPH_TABLE (g1 MATCH (a IS vl2 WHERE a.vname = 'v22')-[]- COLUMNS (1 AS one)); + count +------- + 3 +(1 row) + -- add loop to test edge patterns with same variable name CREATE TABLE e3_3 ( src_id int, @@ -897,6 +917,14 @@ SELECT pg_get_viewdef('customers_us'::regclass); ORDER BY customer_name, product_name; (1 row) +CREATE VIEW implicit_vertex AS SELECT * FROM GRAPH_TABLE (g1 MATCH -[c is el1]-> COLUMNS (c.ename AS cname)); +SELECT pg_get_viewdef('implicit_vertex'::regclass); + pg_get_viewdef +-------------------------------------------------------------------------- + SELECT cname + + FROM GRAPH_TABLE (g1 MATCH -[c IS el1]-> COLUMNS (c.ename AS cname)); +(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 c3a35b69e92..ed4eeee534b 100644 --- a/src/test/regress/sql/graph_table.sql +++ b/src/test/regress/sql/graph_table.sql @@ -293,6 +293,7 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (src IS el1 | vl1)-[conn]->(dest) COLUMNS (c SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders]->(o IS orders) COLUMNS (c.*)); -- star anywhere else is not allowed as a property reference SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.* IS NOT NULL)-[IS customer_orders]->(o IS orders) COLUMNS (c.name)); +SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1 WHERE a.vprop1 in (10, 30))(b IS vl2 WHERE b.vprop2 >= 1200) COLUMNS (a.vname AS aname, b.vname AS bname)); -- select all the properties across all the labels associated with a given type -- of graph element @@ -355,6 +356,9 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS l1)-[a IS l1]->(b IS l1) COLUMNS (a.en SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a IS vl2) WHERE a.vname <> b.vname COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through; -- error SELECT * FROM GRAPH_TABLE (g1 MATCH (a IS vl1)->(b)->(a) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through; SELECT * FROM GRAPH_TABLE (g1 MATCH (a)->(b)->(a IS vl1) COLUMNS (a.vname AS self, b.vname AS through, a.vprop1 AS self_p1, b.vprop1 AS through_p1)) ORDER BY self, through; +-- implicitly added vertex pattern +SELECT * FROM GRAPH_TABLE (g1 MATCH -[c IS el2 ]-> COLUMNS (c.ename AS cname)); +SELECT count(*) FROM GRAPH_TABLE (g1 MATCH (a IS vl2 WHERE a.vname = 'v22')-[]- COLUMNS (1 AS one)); -- add loop to test edge patterns with same variable name CREATE TABLE e3_3 ( @@ -513,6 +517,8 @@ SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e -- ruleutils reverse parsing CREATE VIEW customers_us AS SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[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)) ORDER BY customer_name, product_name; SELECT pg_get_viewdef('customers_us'::regclass); +CREATE VIEW implicit_vertex AS SELECT * FROM GRAPH_TABLE (g1 MATCH -[c is el1]-> COLUMNS (c.ename AS cname)); +SELECT pg_get_viewdef('implicit_vertex'::regclass); -- test view/graph nesting -- 2.34.1