Skip to content

Commit c503c9b

Browse files
author
Dylan Bobby Storey
committed
fix(transform): T-0306 follow-on — varlen MATCH with WITH-bound vars no longer emits column-ref as table alias
When a varlen pattern reuses variables bound via a prior WITH (e.g. \`MATCH (first)-[rs*]->(second)\` where first/second/rs came from a preceding WITH projection), the rel/target variable aliases are column references like \`_with_0.rs\` — using those as SQL table aliases produced 'CROSS JOIN _varlen_path_1 AS _with_0.rs' which fails parsing (SQLite treats . as a column accessor). Two coordinated changes in transform_match.c's varlen branch: 1. Detect alias-is-colref by substring '.' on edge_alias / target_alias. Either alias_is_id=true (id-from-WITH) or kind=PROJECTED scalar projection produces a dot in the alias. 2. When edge alias is colref: substitute a fresh internal alias (_vp_inner_N) for the CTE JOIN, keep all downstream WHERE constraints using that fresh alias. The bound rel column ref isn't referenced — for now the bound rs list isn't matched against the iterated path (that's a separate semantic issue, already filed under T-0306). 3. When target alias is colref: skip the 'CROSS JOIN nodes AS <colref>' emission entirely. The downstream WHERE constraint ('_vp_inner_N.end_id = <colref>') uses the column ref directly, so no fresh nodes table is needed. Match9 [6]/[7] / Match4 [8] no longer produce 'near .:' parse errors. TCK delta: pass=3466 → 3468 (+2), errors=77 → 74 (-3), fails=283 → 284 (+1). The one extra fail moves to result-mismatch territory (bound-list-vs-iterated-path semantic, separate). 944/944 unit, functional clean.
1 parent 536880b commit c503c9b

1 file changed

Lines changed: 36 additions & 0 deletions

File tree

src/backend/transform/transform_match.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,9 +1172,44 @@ static int generate_relationship_match(cypher_transform_context *ctx, cypher_rel
11721172
/* Empty interval `*M..N` with M > N must return zero rows. */
11731173
bool empty_interval = (max_hops >= 0 && min_hops > max_hops);
11741174

1175+
/* T-0306 follow-on: when the rel/target variables came from a prior
1176+
* WITH (alias_is_id), their "alias" is actually a column reference
1177+
* like `_with_0.rs` — using that as a SQL table alias produces
1178+
* `CROSS JOIN _varlen_path_1 AS _with_0.rs` which doesn't parse
1179+
* (SQLite sees `.` as a column accessor). Generate fresh internal
1180+
* aliases for the JOIN and constrain via WHERE using the column
1181+
* refs. */
1182+
/* A bound variable (via WITH) has an alias that's a column ref
1183+
* like `_with_0.rs` — either alias_is_id=true (node/edge id from
1184+
* WITH) or kind=VAR_KIND_PROJECTED (scalar projection). Either
1185+
* way, the alias contains a dot and can't be used as a SQL
1186+
* table alias. The simplest reliable detection: substring `.`. */
1187+
bool edge_alias_is_colref = (edge_alias && strchr(edge_alias, '.') != NULL);
1188+
bool target_alias_is_colref = (target_alias && strchr(target_alias, '.') != NULL);
1189+
char fresh_cte_alias[64];
1190+
char fresh_target_alias[64];
1191+
if (edge_alias_is_colref) {
1192+
snprintf(fresh_cte_alias, sizeof(fresh_cte_alias),
1193+
"_vp_inner_%d", rel_index);
1194+
edge_alias = fresh_cte_alias;
1195+
}
1196+
if (target_alias_is_colref) {
1197+
snprintf(fresh_target_alias, sizeof(fresh_target_alias),
1198+
"_vp_tgt_%d", rel_index);
1199+
}
1200+
11751201
/* Join the main query with the CTE result using unified builder */
11761202
sql_join(ctx->unified_builder, SQL_JOIN_CROSS, cte_name, edge_alias, NULL);
11771203

1204+
/* T-0306 follow-on: skip the target-node JOIN entirely when the
1205+
* target var is bound via WITH (its column-ref IS the id; no
1206+
* fresh `nodes AS _with_0.x` is needed and would be invalid SQL
1207+
* anyway). The WHERE constraint downstream uses the column-ref
1208+
* directly. */
1209+
if (target_alias_is_colref) {
1210+
goto skip_target_node_join;
1211+
}
1212+
11781213
/* Add target node to FROM clause - needed for the CTE join */
11791214
bool target_has_properties = (target_node->properties && target_node->properties->type == AST_NODE_MAP);
11801215
cypher_map *target_prop_map = target_has_properties ? (cypher_map*)target_node->properties : NULL;
@@ -1242,6 +1277,7 @@ static int generate_relationship_match(cypher_transform_context *ctx, cypher_rel
12421277
sql_join(ctx->unified_builder, SQL_JOIN_CROSS, get_graph_table(ctx, "nodes"), target_alias, NULL);
12431278
}
12441279

1280+
skip_target_node_join:
12451281
/* Add label constraints for target node if specified */
12461282
if (has_labels(target_node)) {
12471283
const char *target_id = get_node_id_ref(ctx, target_alias, target_node->variable);

0 commit comments

Comments
 (0)