On Sat, Mar 21, 2026 at 6:19 PM Henson Choi <[email protected]> wrote:
>
> Hi Alexander,
>
>> I've discovered an assertion failure produced with the following script:
>> CREATE TABLE v1 (id int primary key, t text);
>> CREATE TABLE v2 (id int primary key, t text);
>> CREATE TABLE e1_2 (id_1 int, id_2 int, t text);
>> CREATE PROPERTY GRAPH g1
>> VERTEX TABLES (v1, v2)
>> EDGE TABLES (
>> e1_2 key (id_1, id_2)
>> SOURCE KEY (id_1) REFERENCES v1 (id)
>> DESTINATION KEY (id_2) REFERENCES v2 (id)
>> );
>>
>> SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()-> COLUMNS (1 AS one));
>
Thanks for the report.
>
> I looked into this. The pattern `()->` causes an assertion failure
> at rewriteGraphTable.c:783 because the edge's dest_pf is NULL -- there
> is no destination vertex following the edge pattern.
>
> Per SQL/PGQ standard (ISO/IEC 9075-16:2023), Section 10.6 Syntax
> Rules 13c and 13d, implicit empty vertex patterns should be inserted
> when an edge pattern is not preceded or followed by a vertex pattern
> at the same depth of graph pattern matching. Specifically:
>
> SR 13c: If an edge pattern EP contained in a <path term> PST at
> the same depth of graph pattern matching is not preceded
> by a <vertex pattern> contained in PST at the same depth
> of graph pattern matching, then an implicit empty
> <vertex pattern> VP is inserted in PST immediately
> prior to EP.
>
> SR 13d: If an edge pattern EP contained in a <path term> PST at
> the same depth of graph pattern matching is not followed
> by a <vertex pattern> contained in PST at the same depth
> of graph pattern matching, then an implicit empty
> <vertex pattern> VP is inserted in PST immediately
> after EP.
>
> So `()->` should be treated as `()->()`, and `->` as `()->()`.
> This also applies to left-pointing edges (`<-`, `()<-`) and
> any-direction edges (`-[]-`).
>
> The crash affects all patterns where a vertex is missing adjacent
> to an edge:
>
> -- trailing edge (SR 13d)
> MATCH ()-> MATCH ()<- MATCH ()-[]-
>
> -- leading edge (SR 13c)
> MATCH ->() MATCH <-()
>
> -- edge only (SR 13c + 13d)
> MATCH -> MATCH <- MATCH -[]-
>
> -- consecutive edges (SR 13b, with separator to avoid SR 3)
> MATCH ()-> ->()
>
> In a non-assert build, this results in a segfault (NULL pointer
> dereference at pf->dest_pf->factorpos).
>
> I have a fix that inserts implicit empty vertex patterns in the
> rewrite stage (not the parser), so that ruleutils can still
> reconstruct the original user-written pattern from the parse tree.
> The fix handles all three cases: SR 13b (consecutive edges),
> SR 13c (leading edge), and SR 13d (trailing edge).
>
> The change is in generate_queries_for_path_pattern(), before the
> path factor linking loop:
>
> /*
> * Per SQL standard 10.6 SR 13b/c/d, insert implicit empty vertex
> * patterns where needed:
> * SR 13b: between two consecutive edge patterns
> * SR 13c: before a leading edge pattern
> * SR 13d: after a trailing edge pattern
> *
> * We do this in the rewrite stage (not the parser) so that ruleutils
> * can reconstruct the original user-written pattern from the parse tree.
> */
> {
> List *expanded = NIL;
> GraphElementPattern *prev_gep = NULL;
>
> foreach_node(GraphElementPattern, gep, path_pattern)
> {
> /* SR 13c: leading edge needs a vertex before it */
> /* SR 13b: consecutive edges need a vertex between them */
> if (IS_EDGE_PATTERN(gep->kind) &&
> (prev_gep == NULL || IS_EDGE_PATTERN(prev_gep->kind)))
> {
> GraphElementPattern *implicit_vp =
> makeNode(GraphElementPattern);
>
> implicit_vp->kind = VERTEX_PATTERN;
> expanded = lappend(expanded, implicit_vp);
> }
>
> expanded = lappend(expanded, gep);
> prev_gep = gep;
> }
>
> /* SR 13d: trailing edge needs a vertex after it */
> if (prev_gep && IS_EDGE_PATTERN(prev_gep->kind))
> {
> GraphElementPattern *implicit_vp = makeNode(GraphElementPattern);
>
> implicit_vp->kind = VERTEX_PATTERN;
> expanded = lappend(expanded, implicit_vp);
> }
>
> path_pattern = expanded;
> }
An empty pattern has no label expression which is equivalent to
disjunction of all applicable labels. If we add an empty pattern in
the rewrite phase, we will require its label expression to be resolved
in the rewrite phase. Looking at the proposed changes for all
properties reference [1], I am moving towards resolving the label
expression in the transformation stage itself. So I think it's better
to add the implicit vertex patterns during the transformation stage to
be future proof. Rewriter should just then rewrite the patterns into
joins and unions. Your point is correct that the implicit vertex
should not be visible in view definition or deparsed query. For that I
have added a flag implicit to GraphElementPattern.
GraphElementPatterns with implicit = true are ignored by ruleutils.
Implemented it that way in the attached patch.
Vertex patterns without an edge pattern in between are not supported
OTOH. Added an ereport for the same.
I have also added a few tests. I didn't add queries with all the
patterns you mentioned above. I tested a few by hand and all of them
worked as expected. Can you please check?
>
> Separately, while testing various edge patterns I noticed that
> `<-[]->` (full edge left or right) produces a syntax error:
>
> SELECT count(*) FROM GRAPH_TABLE (g1 MATCH ()<-[]->() COLUMNS (1 AS one));
> ERROR: syntax error at or near "->"
>
> Per the standard BNF, this should be valid:
>
> <full edge left or right> ::=
> <left arrow bracket> <element pattern filler> <bracket right arrow>
>
> i.e., `<-[]->` = `<-[` + `]->`. This is a separate parser issue,
> not related to the implicit vertex insertion above.
Yes. That's a grammar issue. gram.y doesn't support it. Peter, do you
remember or know the reason why we don't support full edge left or
right? In fact, I am wondering what's the difference between full edge
left or right and full edge any direction?
Here's cumulative patchset
0001 - Fixes the segmentation fault reported in [2]. There is some
difference of opinion about supporting non-local variable references
[3].
0002 - implicit vertex patterns as discussed in this email
0003: Cleanup patch proposed by Man Zeng [4]. Added some cosmetic
changes to ereports in parse_graphtable.c. It can wait to collect more
such cleanups before committing this.
[1]
https://www.postgresql.org/message-id/CAExHW5tYCE9QyCvVraKUeesKW5RTR%2Bmrzsg3u64qSps-RPJR5A%40mail.gmail.com
[2]
https://www.postgresql.org/message-id/[email protected]
[3]
https://www.postgresql.org/message-id/CAAAe_zCA_9=JWBf4S5o_C2=EO=6dBZ6-=t0tuh+z5cvjkrt...@mail.gmail.com
[4]
https://www.postgresql.org/message-id/[email protected]
--
Best Wishes,
Ashutosh Bapat
From f9a51582349fd7db467aa7862f98f7291924b08f Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
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 <[email protected]>
Reported by: Alexander Lakhin <[email protected]>
Investigation by: Henson Choi <[email protected]>
---
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
From 60b77ed42bfd2d99fb2ee9bc28306a357569526e Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Tue, 17 Mar 2026 14:30:16 +0530
Subject: [PATCH v20260323 3/3] Cleanup and other cosmetic fixes
... collected over time
TODO: provide a proper commit message
Author: Man Zeng <[email protected]>
Author: Ashutosh Bapat <[email protected]>
---
src/backend/parser/parse_graphtable.c | 21 ++++++++--------
src/backend/rewrite/rewriteGraphTable.c | 32 ++++++++++++-------------
2 files changed, 26 insertions(+), 27 deletions(-)
diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c
index 1590f4f9cde..50e279335c9 100644
--- a/src/backend/parser/parse_graphtable.c
+++ b/src/backend/parser/parse_graphtable.c
@@ -94,14 +94,14 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
{
if (pstate->p_expr_kind == EXPR_KIND_SELECT_TARGET)
ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("\"*\" is not supported here"),
- parser_errposition(pstate, cref->location));
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("\"*\" is not supported here"),
+ parser_errposition(pstate, cref->location)));
else
ereport(ERROR,
- errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("\"*\" not allowed here"),
- parser_errposition(pstate, cref->location));
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"*\" not allowed here"),
+ parser_errposition(pstate, cref->location)));
}
elvarname = strVal(field1);
@@ -116,8 +116,8 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref)
pgptup = SearchSysCache2(PROPGRAPHPROPNAME, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(propname));
if (!HeapTupleIsValid(pgptup))
ereport(ERROR,
- errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("property \"%s\" does not exist", propname));
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("property \"%s\" does not exist", propname)));
pgpform = (Form_pg_propgraph_property) GETSTRUCT(pgptup);
gpr->location = cref->location;
@@ -172,8 +172,9 @@ transformLabelExpr(GraphTableParseState *gpstate, Node *labelexpr)
labelid = GetSysCacheOid2(PROPGRAPHLABELNAME, Anum_pg_propgraph_label_oid, ObjectIdGetDatum(gpstate->graphid), CStringGetDatum(labelname));
if (!labelid)
ereport(ERROR,
- errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("label \"%s\" does not exist in property graph \"%s\"", labelname, get_rel_name(gpstate->graphid)));
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("label \"%s\" does not exist in property graph \"%s\"",
+ labelname, get_rel_name(gpstate->graphid))));
lref = makeNode(GraphLabelRef);
lref->labelid = labelid;
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 8b472a07845..343be18f14a 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -33,7 +33,6 @@
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parsetree.h"
-#include "parser/parse_relation.h"
#include "parser/parse_graphtable.h"
#include "rewrite/rewriteGraphTable.h"
#include "rewrite/rewriteHandler.h"
@@ -256,16 +255,14 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
if (!pf)
{
- {
- pf = palloc0_object(struct path_factor);
- pf->factorpos = factorpos++;
- pf->kind = gep->kind;
- pf->labelexpr = gep->labelexpr;
- pf->variable = gep->variable;
- pf->whereClause = gep->whereClause;
-
- path_factors = lappend(path_factors, pf);
- }
+ pf = palloc0_object(struct path_factor);
+ pf->factorpos = factorpos++;
+ pf->kind = gep->kind;
+ pf->labelexpr = gep->labelexpr;
+ pf->variable = gep->variable;
+ pf->whereClause = gep->whereClause;
+
+ path_factors = lappend(path_factors, pf);
}
/*
@@ -289,7 +286,7 @@ generate_queries_for_path_pattern(RangeTblEntry *rte, List *path_pattern)
* be merged even though they have different variables. Such element
* patterns form a walk of graph where vertex and edges are repeated.
* For example, in (a)-[b]->(c)<-[b]-(d), (a) and (d) represent the
- * same vertex element. This is slighly harder to implement and
+ * same vertex element. This is slightly harder to implement and
* probably less useful. Hence not supported for now.
*/
if (prev_pf)
@@ -442,7 +439,7 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
Assert(pf->kind == VERTEX_PATTERN || IS_EDGE_PATTERN(pf->kind));
- /* Add conditions representing edge connnections. */
+ /* Add conditions representing edge connections. */
if (IS_EDGE_PATTERN(pf->kind))
{
struct path_element *src_pe;
@@ -755,7 +752,7 @@ generate_setop_from_pathqueries(List *pathqueries, List **rtable, List **targetl
/*
* Construct a path_element object for the graph element given by `elemoid`
- * statisfied by the path factor `pf`.
+ * satisfied by the path factor `pf`.
*
* If the type of graph element does not fit the element pattern kind, the
* function returns NULL.
@@ -955,7 +952,7 @@ get_path_elements_for_path_factor(Oid propgraphid, struct path_factor *pf)
}
/*
- * Rememeber qualified and unqualified elements processed so
+ * Remember qualified and unqualified elements processed so
* far to avoid processing already processed elements again.
*/
elem_oids_seen = lappend_oid(elem_oids_seen, label_elem->pgelelid);
@@ -1124,8 +1121,9 @@ replace_property_refs_mutator(Node *node, struct replace_property_refs_context *
* The property is associated with at least one of the labels
* that satisfy given element pattern. If it's associated with
* the given element (through some other label), use
- * correspondig value expression. Otherwise NULL. Ref. SQL/PGQ
- * standard section 6.5 Property Reference, General Rule 2.b.
+ * corresponding value expression. Otherwise NULL. Ref.
+ * SQL/PGQ standard section 6.5 Property Reference, General
+ * Rule 2.b.
*/
n = get_element_property_expr(found_mapping->elemoid, gpr->propid,
mapping_factor->factorpos + 1);
--
2.34.1
From 8a1c02b8ba0c476bd4134d0e2db4d395e639f21f Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Wed, 18 Mar 2026 19:27:38 +0530
Subject: [PATCH v20260323 1/3] Cross variable references in graph pattern
causes segfault
When converting the WHERE clause in an element pattern,
generate_query_for_graph_path() calls replace_property_refs() to replace
the property references in it. Only the current graph element pattern
is passed as the context for replacement. If there are references to
variables from other element patterns, it causes a segmentation fault
(an assertion failure in an Assert enabled build) since it does not find
path_element object corresponding to those variables. Fix the issue by
passing all the path elements in the graph pattern as context for
replacement.
Author: Ashutosh Bapat <[email protected]>
Reported by: Man Zeng <[email protected]>
Investigated by: Henson Choi <[email protected]>
Reviewed by: Junwang Zhao <[email protected]>
---
src/backend/rewrite/rewriteGraphTable.c | 2 +-
src/test/regress/expected/graph_table.out | 14 ++++++++++++++
src/test/regress/sql/graph_table.sql | 4 ++++
3 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/backend/rewrite/rewriteGraphTable.c b/src/backend/rewrite/rewriteGraphTable.c
index 06f2f3442d8..d3f382573fd 100644
--- a/src/backend/rewrite/rewriteGraphTable.c
+++ b/src/backend/rewrite/rewriteGraphTable.c
@@ -519,7 +519,7 @@ generate_query_for_graph_path(RangeTblEntry *rte, List *graph_path)
{
Node *tr;
- tr = replace_property_refs(rte->relid, pf->whereClause, list_make1(pe));
+ tr = replace_property_refs(rte->relid, pf->whereClause, graph_path);
qual_exprs = lappend(qual_exprs, tr);
}
diff --git a/src/test/regress/expected/graph_table.out b/src/test/regress/expected/graph_table.out
index 27c81ec6e42..eeaf1b85121 100644
--- a/src/test/regress/expected/graph_table.out
+++ b/src/test/regress/expected/graph_table.out
@@ -395,6 +395,20 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-(v2) COLUMNS (v1.vname AS v1name
v22 | v32
(3 rows)
+-- cross variable references
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b WHERE a.vprop1 = 10]->(c) COLUMNS (a.vname AS aname, a.vprop1 AS ap1, b.ename AS bname, b.eprop1 AS bp1, c.vname AS cname, c.vprop1 AS cp1)) ORDER BY aname, bname, cname;
+ aname | ap1 | bname | bp1 | cname | cp1
+-------+-----+-------+-------+-------+------
+ v11 | 10 | e121 | 10001 | v22 | 1020
+ v11 | 10 | e131 | 10003 | v33 | 2030
+ v11 | 10 | e132 | 10004 | v31 | 2010
+(3 rows)
+
+-- forward variable reference, not supported
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE b.eprop1 = 10001)-[b]->(c) COLUMNS (a.vname AS aname, a.vprop1 AS ap1, b.ename AS bname, b.eprop1 AS bp1, c.vname AS cname, c.vprop1 AS cp1)) ORDER BY aname, bname, cname;
+ERROR: missing FROM-clause entry for table "b"
+LINE 1: SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE b.eprop1 = 1000...
+ ^
-- Errors
-- vl1 is not associated with property vprop2
SELECT src, src_vprop2, conn, dest FROM GRAPH_TABLE (g1 MATCH (a IS vl1)-[b IS el1]->(c IS vl2 | vl3) COLUMNS (a.vname AS src, a.vprop2 AS src_vprop2, b.ename AS conn, c.vname AS dest));
diff --git a/src/test/regress/sql/graph_table.sql b/src/test/regress/sql/graph_table.sql
index 6d2b08b997b..c3a35b69e92 100644
--- a/src/test/regress/sql/graph_table.sql
+++ b/src/test/regress/sql/graph_table.sql
@@ -274,6 +274,10 @@ SELECT src, conn, dest, lprop1, vprop2, vprop1 FROM GRAPH_TABLE (g1 MATCH (a IS
-- edges directed in both ways - to and from v2
SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-[conn]-(v2) COLUMNS (v1.vname AS v1name, conn.ename AS cname, v2.vname AS v2name));
SELECT * FROM GRAPH_TABLE (g1 MATCH (v1 IS vl2)-(v2) COLUMNS (v1.vname AS v1name, v2.vname AS v2name));
+-- cross variable references
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b WHERE a.vprop1 = 10]->(c) COLUMNS (a.vname AS aname, a.vprop1 AS ap1, b.ename AS bname, b.eprop1 AS bp1, c.vname AS cname, c.vprop1 AS cp1)) ORDER BY aname, bname, cname;
+-- forward variable reference, not supported
+SELECT * FROM GRAPH_TABLE (g1 MATCH (a WHERE b.eprop1 = 10001)-[b]->(c) COLUMNS (a.vname AS aname, a.vprop1 AS ap1, b.ename AS bname, b.eprop1 AS bp1, c.vname AS cname, c.vprop1 AS cp1)) ORDER BY aname, bname, cname;
-- Errors
-- vl1 is not associated with property vprop2
base-commit: 93b76db0ace674a29a5d2146a139d6dd87b99659
--
2.34.1