diff --git a/src/cypher/cypher.c b/src/cypher/cypher.c index db0c46976..e02cf07d8 100644 --- a/src/cypher/cypher.c +++ b/src/cypher/cypher.c @@ -3151,6 +3151,8 @@ static void expand_var_length(cbm_store_t *store, cbm_rel_pattern_t *rel, cbm_traverse_result_t tr = {0}; const char *dir = rel->direction ? rel->direction : "outbound"; cbm_store_bfs(store, src->id, dir, rel->types, rel->type_count, max_depth, CBM_PERCENT, &tr); + cbm_node_t *bound_to = binding_get(b, to_var); + int64_t bound_to_id = bound_to ? bound_to->id : 0; /* Same contract as process_edges: the budget caps materialisation, never * detection, so a saturated source cannot report match_count == 0 and get a * fabricated OPTIONAL "no match" row. */ @@ -3159,6 +3161,9 @@ static void expand_var_length(cbm_store_t *store, cbm_rel_pattern_t *rel, if (hop->hop < rel->min_hops) { continue; } + if (bound_to && hop->node.id != bound_to_id) { + continue; + } if (target_node->label && !label_alt_matches(hop->node.label, target_node->label)) { continue; } diff --git a/tests/test_cypher.c b/tests/test_cypher.c index 73a0455ec..9f2509b37 100644 --- a/tests/test_cypher.c +++ b/tests/test_cypher.c @@ -1371,6 +1371,22 @@ TEST(cypher_exec_variable_length) { PASS(); } +TEST(cypher_exec_variable_length_repeated_node_var_unifies) { + cbm_store_t *s = setup_cypher_store(); + cbm_cypher_result_t r = {0}; + + int rc = cbm_cypher_execute(s, + "MATCH (f:Function)-[:CALLS*1..2]->(f:Function) " + "RETURN f.name", + "test", 0, &r); + ASSERT_EQ(rc, 0); + ASSERT_EQ(r.row_count, 0); + + cbm_cypher_result_free(&r); + cbm_store_close(s); + PASS(); +} + /* Reproduce-first (#887): an EXPLICIT variable-length upper bound must still be * capped at the engine ceiling (cbm_cypher_max_depth(), default 10). On * origin/main, expand_var_length honoured an explicit `*1..N` verbatim (only the @@ -3612,6 +3628,7 @@ SUITE(cypher) { RUN_TEST(cypher_exec_limit); RUN_TEST(cypher_exec_order_by); RUN_TEST(cypher_exec_variable_length); + RUN_TEST(cypher_exec_variable_length_repeated_node_var_unifies); RUN_TEST(cypher_exec_var_length_explicit_bound_capped); RUN_TEST(cypher_exec_defines_edge); RUN_TEST(cypher_exec_no_results);