From 22193d05c84a3a43e7475e1162f1bde5b2174d92 Mon Sep 17 00:00:00 2001 From: Henri Gasc Date: Tue, 8 Sep 2026 15:04:02 +0200 Subject: [PATCH 3/8] Parse grammar for VLE --- src/backend/parser/gram.y | 1 + src/backend/parser/parse_graphtable.c | 361 +++++++++++++++++++++++++- src/include/nodes/primnodes.h | 3 + src/include/parser/parse_graphtable.h | 10 + src/include/parser/parse_node.h | 5 + 5 files changed, 379 insertions(+), 1 deletion(-) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 200cbe35331..3826304f4b1 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -18377,6 +18377,7 @@ opt_graph_pattern_quantifier: '{' Iconst '}' { $$ = list_make2_int($2, $2); } | '{' ',' Iconst '}' { $$ = list_make2_int(0, $3); } | '{' Iconst ',' Iconst '}' { $$ = list_make2_int($2, $4); } + | '{' Iconst ',' '}' { $$ = list_make2_int($2, -1); } | /*EMPTY*/ { $$ = NULL; } ; diff --git a/src/backend/parser/parse_graphtable.c b/src/backend/parser/parse_graphtable.c index c5ca15b1977..983852ff076 100644 --- a/src/backend/parser/parse_graphtable.c +++ b/src/backend/parser/parse_graphtable.c @@ -18,10 +18,13 @@ #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" #include "nodes/makefuncs.h" +#include "optimizer/cost.h" #include "parser/parse_clause.h" #include "parser/parse_collate.h" #include "parser/parse_expr.h" @@ -143,6 +146,36 @@ transformGraphTablePropertyRef(ParseState *pstate, ColumnRef *cref) gpr->typmod = pgpform->pgptypmod; gpr->collation = pgpform->pgpcollation; + /* + * A property reference made outside any element pattern + * (COLUMNS clause or graph-level WHERE) to a variable bound to a + * quantified (variable-length) edge denotes the list of the + * property's values over every edge traversed along the matched + * path. Represent that as an array of the property's type. + * Inside the edge's own [e WHERE ...] clause cur_gep is set and + * e refers to the single candidate edge, so no array is built. + */ + if (gpstate->cur_gep == NULL) + { + foreach_node(GraphElementPattern, gep, gpstate->pattern_elements) + { + Oid arrtype; + + if (!gep->variable || + strcmp(gep->variable, elvarname) != 0) + continue; + if (!IS_EDGE_PATTERN(gep->kind) || !gep->quantifier) + break; + arrtype = get_array_type(pgpform->pgptypid); + if (arrtype == InvalidOid) + arrtype = pgpform->pgptypid; + gpr->vle_list = true; + gpr->typeId = arrtype; + gpr->typmod = -1; + break; + } + } + ReleaseSysCache(pgptup); return (Node *) gpr; @@ -241,7 +274,8 @@ transformGraphElementPattern(ParseState *pstate, GraphElementPattern *gep) { GraphTableParseState *gpstate = pstate->p_graph_table_pstate; - if (gep->quantifier) + /* Quantifiers are handled natively; the rewrite fallback cannot use them. */ + if (!enable_native_graphtable && gep->quantifier) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("element pattern quantifier is not supported"))); @@ -360,6 +394,7 @@ transformPathPatternList(ParseState *pstate, List *path_pattern) { if (gep->variable) gpstate->variables = list_append_unique(gpstate->variables, makeString(pstrdup(gep->variable))); + gpstate->pattern_elements = lappend(gpstate->pattern_elements, gep); } } @@ -369,6 +404,327 @@ transformPathPatternList(ParseState *pstate, List *path_pattern) return (Node *) result; } +/* + * Validate that no two element patterns in the same path have the same + * variable name with incompatible definitions. + * + * Cases: + * 1. Same variable, different element kinds (vertex vs edge) → error + * 2. Same variable, both vertex, different label expressions → error + * 3. Same variable, both vertex, compatible label expressions → allowed + */ +static void +validate_element_variable_names(GraphPattern *pattern) +{ + List *path; + int path_len; + int i; + + if (pattern == NULL || + pattern->path_pattern_list == NIL) + return; + + path = linitial(pattern->path_pattern_list); + if (path == NULL) + return; + path_len = list_length(path); + + for (i = 0; i < path_len; i++) + { + GraphElementPattern *gep_i = (GraphElementPattern *) + list_nth(path, i); + int j; + + if (gep_i == NULL || gep_i->variable == NULL) + continue; + + for (j = i + 1; j < path_len; j++) + { + GraphElementPattern *gep_j = (GraphElementPattern *) + list_nth(path, j); + + if (gep_j == NULL || gep_j->variable == NULL) + continue; + + if (strcmp(gep_i->variable, gep_j->variable) != 0) + continue; + + /* + * Same variable name. Check if element pattern types differ + * (vertex vs edge). + */ + if (gep_i->kind != gep_j->kind && + !(IS_EDGE_PATTERN(gep_i->kind) && + IS_EDGE_PATTERN(gep_j->kind))) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("element patterns with same variable " + "name \"%s\" but different element " + "pattern types", gep_i->variable))); + } + + /* + * Same kind (both vertex or both edge). Check label expressions. + * Error only if both have non-NULL label expressions that differ. + */ + if (gep_i->labelexpr != NULL && gep_j->labelexpr != NULL && + !equal(gep_i->labelexpr, gep_j->labelexpr)) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("element patterns with same " + "variable name \"%s\" but " + "different label expressions " + "are not supported", + gep_i->variable))); + } + } + } +} + +/* + * Validate that an edge variable appearing at multiple hop positions + * always connects the same pair of vertex variables. For example, in + * (a)->[e]->(b)<-[e]-(c), the edge e appears twice connecting a→b and + * c→b, which requires e to connect three vertices — an impossibility. + */ +static void +validate_edge_connectivity(GraphPattern *pattern) +{ + List *path; + int path_len; + int n_hops; + + if (pattern == NULL || + pattern->path_pattern_list == NIL) + return; + + path = linitial(pattern->path_pattern_list); + if (path == NULL) + return; + path_len = list_length(path); + if (path_len < 4) /* need at least (v)-[e]->(v)-[e]->(v) */ + return; + + n_hops = (path_len - 1) / 2; + { + int hi; + + for (hi = 0; hi < n_hops; hi++) + { + int edge_pi = 2 * hi + 1; + GraphElementPattern *edge_gep = (GraphElementPattern *) + list_nth(path, edge_pi); + int ji; + + if (edge_gep == NULL || edge_gep->variable == NULL || + !IS_EDGE_PATTERN(edge_gep->kind)) + continue; + + for (ji = hi + 1; ji < n_hops; ji++) + { + int je_pi = 2 * ji + 1; + GraphElementPattern *je_gep = (GraphElementPattern *) + list_nth(path, je_pi); + int src_pi_i; + int dst_pi_i; + int src_pi_j; + int dst_pi_j; + GraphElementPattern *src_gep_i; + GraphElementPattern *dst_gep_i; + GraphElementPattern *src_gep_j; + GraphElementPattern *dst_gep_j; + + if (je_gep == NULL || je_gep->variable == NULL || + !IS_EDGE_PATTERN(je_gep->kind)) + continue; + + if (strcmp(edge_gep->variable, je_gep->variable) != 0) + continue; + + /* Same edge variable at two hop positions */ + src_pi_i = 2 * hi; + dst_pi_i = 2 * hi + 2; + src_pi_j = 2 * ji; + dst_pi_j = 2 * ji + 2; + + src_gep_i = (GraphElementPattern *) list_nth(path, src_pi_i); + dst_gep_i = (GraphElementPattern *) list_nth(path, dst_pi_i); + src_gep_j = (GraphElementPattern *) list_nth(path, src_pi_j); + dst_gep_j = (GraphElementPattern *) list_nth(path, dst_pi_j); + + /* + * An edge variable connecting multiple hops must have the + * same source and destination vertex variables in each hop. + * If either side differs, the edge would need to connect more + * than two distinct vertices. + */ + if ((src_gep_i->variable == NULL || + dst_gep_i->variable == NULL || + src_gep_j->variable == NULL || + dst_gep_j->variable == NULL) || + strcmp(src_gep_i->variable, src_gep_j->variable) != 0 || + strcmp(dst_gep_i->variable, dst_gep_j->variable) != 0) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("an edge cannot connect more than two " + "vertices even in a cyclic pattern"))); + } + } + } + } +} + +/* + * Check that each label used in an element pattern matches the element's + * kind (vertex or edge). A label that only appears on vertex elements + * cannot be used in an edge pattern, and vice versa. + * + * Called at parse time, when the pattern is transformed. Unlike the label + * matching done later during planning/execution, this check runs once per + * query parse; DDL changes made after the query was parsed (e.g. a label + * dropped from all vertex tables after a prepared statement was created) + * are caught by the planner-side element resolution instead. + */ +void +validate_graph_element_label_kinds(GraphPattern *pattern, Oid graph_oid) +{ + List *path; + int path_len; + int pi; + + if (pattern == NULL || + pattern->path_pattern_list == NIL) + return; + + path = linitial(pattern->path_pattern_list); + if (path == NULL) + return; + path_len = list_length(path); + + for (pi = 0; pi < path_len; pi++) + { + GraphElementPattern *gep = (GraphElementPattern *) + list_nth(path, pi); + List *label_oids; + ListCell *lc; + char elem_kind; + const char *kind_str; + + if (gep == NULL || gep->labelexpr == NULL) + continue; + + /* + * Determine what kind this pattern element should be. + */ + if (!graph_element_kind_info(gep->kind, &elem_kind, &kind_str)) + continue; + + label_oids = get_label_oids_for_labelexpr(gep->labelexpr); + if (label_oids == NIL) + continue; + + /* + * For each label OID, scan pg_propgraph_element_label joined with + * pg_propgraph_element to find what element kinds this label is + * associated with. + */ + foreach(lc, label_oids) + { + Oid labelid = lfirst_oid(lc); + Relation el_label_rel; + SysScanDesc el_label_scan; + ScanKeyData el_label_key[1]; + HeapTuple el_label_tup; + bool has_vertex = false; + bool has_edge = false; + + el_label_rel = table_open(PropgraphElementLabelRelationId, + AccessShareLock); + ScanKeyInit(&el_label_key[0], + Anum_pg_propgraph_element_label_pgellabelid, + BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(labelid)); + el_label_scan = systable_beginscan(el_label_rel, + PropgraphElementLabelLabelIndexId, + true, NULL, 1, el_label_key); + + while (HeapTupleIsValid(el_label_tup = + systable_getnext(el_label_scan))) + { + Form_pg_propgraph_element_label el_form = + (Form_pg_propgraph_element_label) GETSTRUCT(el_label_tup); + + /* + * Look up the element to find its kind. + */ + { + HeapTuple elem_tup; + Form_pg_propgraph_element elem_form; + + elem_tup = SearchSysCache1(PROPGRAPHELOID, + ObjectIdGetDatum( + el_form->pgelelid)); + if (!HeapTupleIsValid(elem_tup)) + continue; + elem_form = (Form_pg_propgraph_element) + GETSTRUCT(elem_tup); + if (elem_form->pgepgid == graph_oid) + { + if (elem_form->pgekind == 'v') + has_vertex = true; + else if (elem_form->pgekind == 'e') + has_edge = true; + } + ReleaseSysCache(elem_tup); + } + + if (has_vertex && has_edge) + break; + } + + systable_endscan(el_label_scan); + table_close(el_label_rel, AccessShareLock); + + /* + * If the label is not associated with the required kind, produce + * the standard error. + */ + if ((elem_kind == 'v' && !has_vertex) || + (elem_kind == 'e' && !has_edge)) + { + const char *labelname; + + labelname = get_propgraph_label_name(labelid); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("no property graph element of type " + "\"%s\" has label \"%s\" associated " + "with it in property graph \"%s\"", + kind_str, labelname, + get_rel_name(graph_oid)))); + } + } + } +} + +/* + * Run all native-specific validations on a graph pattern. + * Called from transformGraphPattern when enable_native_graphtable is on. + */ +static void +validate_native_graph_query(ParseState *pstate, + GraphPattern *pattern) +{ + GraphTableParseState *gpstate = pstate->p_graph_table_pstate; + + validate_element_variable_names(pattern); + validate_edge_connectivity(pattern); + validate_graph_element_label_kinds(pattern, gpstate->graphid); +} + /* * Transform a GraphPattern. * @@ -393,6 +749,9 @@ transformGraphPattern(ParseState *pstate, GraphPattern *graph_pattern) EXPR_KIND_WHERE, "WHERE"); assign_expr_collations(pstate, graph_pattern->whereClause); + if (enable_native_graphtable) + validate_native_graph_query(pstate, graph_pattern); + return (Node *) graph_pattern; } diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 5ab8bad09c7..8729ac0b627 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -2205,6 +2205,9 @@ typedef struct GraphPropertyRef Oid typeId; int32 typmod; Oid collation; + bool vle_list; /* reference outside a quantified (VLE) edge's + * own WHERE: value is the array of the + * property's value over every traversed edge */ ParseLoc location; } GraphPropertyRef; diff --git a/src/include/parser/parse_graphtable.h b/src/include/parser/parse_graphtable.h index f53b4ff5f2d..e5c42264b88 100644 --- a/src/include/parser/parse_graphtable.h +++ b/src/include/parser/parse_graphtable.h @@ -48,6 +48,16 @@ extern List *get_graph_all_label_oids(Oid propgraphid); extern bool graph_element_kind_info(GraphElementPatternKind kind, char *element_kind, const char **kind_str); +/* + * Verify that every label explicitly referenced by the label expressions in + * the given graph pattern is associated with some element of the required + * kind (vertex or edge); error otherwise. Called at parse time; DDL made + * after parsing (cached plans) is handled by the planner-side element + * resolution. + */ +extern void validate_graph_element_label_kinds(GraphPattern *pattern, + Oid graph_oid); + /* * Callback used by graph_label_expr_matches(): does the element described * by arg carry the given label? diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index f7f4ba6c2a8..3ccf6f7b724 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -114,6 +114,11 @@ typedef struct GraphTableParseState GraphElementPattern *cur_gep; /* The element pattern being transformed. * NULL if no element pattern is being * transformed. */ + List *pattern_elements; /* flattened list of GraphElementPattern + * nodes of the path pattern, used to + * resolve references made outside an + * element pattern (COLUMNS, graph-level + * WHERE). */ } GraphTableParseState; /* -- 2.39.2