From f9a51582349fd7db467aa7862f98f7291924b08f Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Date: Mon, 23 Mar 2026 19:29:21 +0530
Subject: [PATCH v20260323 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 <ashutosh.bapat.oss@gmail.com>
Reported by: Alexander Lakhin <exclusion@gmail.com>
Investigation by: 	Henson Choi <assam258@gmail.com>
---
 src/backend/parser/parse_graphtable.c     | 66 +++++++++++++++++++++--
 src/backend/rewrite/rewriteGraphTable.c   | 19 +++++++
 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, 121 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..8b472a07845 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -179,6 +179,9 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 	int			factorpos = 0;
 	List	   *path_factors = NIL;
 	struct path_factor *prev_pf = NULL;
+#ifdef USE_ASSERT_CHECKING
+	GraphElementPattern *prev_gep = NULL;
+#endif
 
 	Assert(list_length(path_pattern) > 0);
 
@@ -201,6 +204,12 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
 		Assert(gep->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(gep->kind));
 		Assert(!gep->quantifier);
 
+		/*
+		 * Addition of an implicit element pattern shouldn't lead to addition
+		 * of another, thus leading to infinite path pattern.
+		 */
+		Assert(!prev_gep || prev_gep->implicit != gep->implicit);
+
 		foreach_ptr(struct path_factor, other, path_factors)
 		{
 			if (gep->variable && other->variable &&
@@ -303,6 +312,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 +336,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

