Skip to content

Commit 905eb6c

Browse files
author
Dylan Bobby Storey
committed
validate: allow forward-reference variables in CREATE property maps
Per openCypher: variables bound in an earlier path of a CREATE clause are in scope for later paths in the same clause: CREATE (a:End {num: 42, id: 0}), (:End {num: 3}), (:Begin {num: a.id}) -- valid forward ref to `a` Previous validation rejected this with 'SyntaxError: UndefinedVariable' because it collected all pattern names AFTER validating. Now we interleave: validate each path's property maps using the cumulative local_bound set, then merge that path's bindings into local_bound before moving on. Backward references (path 1 referencing a variable from path 2) are still rejected — local_bound only grows as we go. TCK delta: 0 net (the syntactic block was removed, but the executor's CREATE-with-forward-ref evaluation still produces null for `a.id` — that's a runtime issue not addressed here). 944/944 unit, functional clean; no regressions. Affected (now no longer compile-error): With2 [1] Forwarding a property to express a join WithSkipLimit1 [1] Handle dependencies across WITH with SKIP WithSkipLimit2 [2] Handle dependencies across WITH with LIMIT (each still fails on result mismatch — separate executor work.)
1 parent 506ec0e commit 905eb6c

1 file changed

Lines changed: 35 additions & 8 deletions

File tree

src/backend/transform/transform_validate.c

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,20 +1614,31 @@ static int check_undef_in_expr(ast_node *expr, const name_set *bound,
16141614
}
16151615

16161616
/* Walk every property-map value in a CREATE/MERGE pattern checking for
1617-
* identifier references that aren't bound. Pattern variables being
1618-
* introduced by this same clause are NOT in `bound` yet — that matches
1619-
* Cypher scoping (you can't reference a sibling variable being
1620-
* introduced in the same write clause). */
1617+
* identifier references that aren't bound. Per openCypher: variables
1618+
* bound in an earlier path of the same CREATE are in scope for later
1619+
* paths. We process paths in order and merge each path's bindings
1620+
* into a local copy of `bound` before validating the next path —
1621+
* forward references work, backward references don't. (With2 [1],
1622+
* WithSkipLimit1 [1].) */
16211623
static int validate_write_undef_in_props(ast_list *patterns, const name_set *bound,
16221624
const char *kw, char **error_message)
16231625
{
16241626
if (!patterns) return 0;
1627+
/* Local name set seeded with the inherited bound vars, extended
1628+
* as we walk each path. */
1629+
name_set local_bound; nset_init(&local_bound);
1630+
if (bound) {
1631+
for (int i = 0; i < bound->count; i++) {
1632+
if (bound->names[i]) nset_add(&local_bound, bound->names[i]);
1633+
}
1634+
}
1635+
int rc = 0;
16251636
for (int pi = 0; pi < patterns->count; pi++) {
16261637
ast_node *pn = patterns->items[pi];
16271638
if (!pn || pn->type != AST_NODE_PATH) continue;
16281639
cypher_path *p = (cypher_path *)pn;
16291640
if (!p->elements) continue;
1630-
for (int ei = 0; ei < p->elements->count; ei++) {
1641+
for (int ei = 0; ei < p->elements->count && rc == 0; ei++) {
16311642
ast_node *el = p->elements->items[ei];
16321643
if (!el) continue;
16331644
ast_node *props = NULL;
@@ -1636,14 +1647,30 @@ static int validate_write_undef_in_props(ast_list *patterns, const name_set *bou
16361647
if (!props || props->type != AST_NODE_MAP) continue;
16371648
cypher_map *m = (cypher_map *)props;
16381649
if (!m->pairs) continue;
1639-
for (int i = 0; i < m->pairs->count; i++) {
1650+
for (int i = 0; i < m->pairs->count && rc == 0; i++) {
16401651
cypher_map_pair *pair = (cypher_map_pair *)m->pairs->items[i];
16411652
if (!pair || !pair->value) continue;
1642-
if (check_undef_in_expr(pair->value, bound, kw, error_message) < 0) return -1;
1653+
if (check_undef_in_expr(pair->value, &local_bound, kw, error_message) < 0) rc = -1;
1654+
}
1655+
}
1656+
/* Now add this path's bindings so later paths can reference them. */
1657+
if (rc == 0 && p->var_name) nset_add(&local_bound, p->var_name);
1658+
if (rc == 0) {
1659+
for (int ei = 0; p->elements && ei < p->elements->count; ei++) {
1660+
ast_node *el = p->elements->items[ei];
1661+
if (!el) continue;
1662+
if (el->type == AST_NODE_NODE_PATTERN) {
1663+
cypher_node_pattern *np = (cypher_node_pattern *)el;
1664+
if (np->variable) nset_add(&local_bound, np->variable);
1665+
} else if (el->type == AST_NODE_REL_PATTERN) {
1666+
cypher_rel_pattern *rp = (cypher_rel_pattern *)el;
1667+
if (rp->variable) nset_add(&local_bound, rp->variable);
1668+
}
16431669
}
16441670
}
16451671
}
1646-
return 0;
1672+
nset_free(&local_bound);
1673+
return rc;
16471674
}
16481675

16491676
/* Reject NULL literals in CREATE/MERGE property maps. openCypher

0 commit comments

Comments
 (0)