diff --git a/engine/src/main/java/com/arcadedb/query/opencypher/executor/CypherExecutionPlan.java b/engine/src/main/java/com/arcadedb/query/opencypher/executor/CypherExecutionPlan.java index 5204db8967..6be159364d 100644 --- a/engine/src/main/java/com/arcadedb/query/opencypher/executor/CypherExecutionPlan.java +++ b/engine/src/main/java/com/arcadedb/query/opencypher/executor/CypherExecutionPlan.java @@ -1664,7 +1664,7 @@ private AbstractExecutionStep buildMatchStep(final MatchClause matchClause, Abst AbstractExecutionStep nextStep; if (relPattern.isVariableLength()) { nextStep = new ExpandPathStep(effectiveSourceVar, pathVariable, relVar, effectiveTargetVar, relPattern, - true, effectiveTargetNode, pathPattern.getEffectivePathMode(), new HashSet<>(boundVariables), context); + true, effectiveTargetNode, pathPattern.getEffectivePathMode(), computePrevVarsForVlp(pathPattern, i, boundVariables), context); } else { // Check if this hop requires IN traversal on a unidirectional edge. // Unidirectional edges don't store incoming links, so we must restructure: @@ -2028,7 +2028,7 @@ public String prettyPrint(final int depth, final int indent) { // Variable-length path - pass path variable, relationship variable, and target node for label // filtering. Snapshot previously bound variables for relationship-uniqueness scoping. nextStep = new ExpandPathStep(currentSourceVar, pathVariable, relVar, targetVar, relPattern, true, - targetNode, pathPattern.getEffectivePathMode(), new HashSet<>(legacyBoundVariables), context); + targetNode, pathPattern.getEffectivePathMode(), computePrevVarsForVlp(pathPattern, i, legacyBoundVariables), context); } else { // Fixed-length relationship - pass path variable, target node pattern, and bound variables nextStep = new MatchRelationshipStep(currentSourceVar, relVar, targetVar, relPattern, pathVariable, @@ -3511,6 +3511,29 @@ private boolean[] computeHopEdgeTrackingNeeds(final PathPattern pathPattern) { return needs; } + /** + * Computes the set of "previously bound" variables to pass to {@link ExpandPathStep} for a + * variable-length hop at {@code vlpHopIndex} within {@code pathPattern}. + *
+ * OpenCypher path isomorphism applies within a single path, not within a MATCH clause.
+ * A relationship variable bound by a prior MATCH that is also explicitly named in the current
+ * path pattern is a same-path co-participant and must be checked for edge conflicts even though
+ * it was introduced before this MATCH. We therefore remove those co-participants from the
+ * exclusion set before handing it to ExpandPathStep.
+ */
+ private static Set
+ * When a relationship variable {@code r} is bound in one MATCH clause and then referenced
+ * explicitly inside a path pattern containing variable-length segments in a subsequent MATCH,
+ * the variable-length segments must not re-traverse {@code r}. OpenCypher path isomorphism
+ * applies within a single path, not within a single MATCH clause.
+ *
+ * This corresponds to TCK scenario {@code Match4 [7]}:
+ * "Matching variable length patterns including a bound relationship".
+ */
+class Issue4006BoundRelVarPathIsomorphismTest {
+ private Database database;
+
+ @BeforeEach
+ void setUp() {
+ database = new DatabaseFactory("./target/databases/testopencypher-4006").create();
+ database.getSchema().createVertexType("Node4006");
+ database.getSchema().createEdgeType("EDGE4006");
+ database.transaction(() -> database.command("opencypher",
+ "CREATE (n0:Node4006)-[:EDGE4006]->(n1:Node4006)-[:EDGE4006]->(n2:Node4006)-[:EDGE4006]->(n3:Node4006)"));
+ }
+
+ @AfterEach
+ void tearDown() {
+ if (database != null) {
+ database.drop();
+ database = null;
+ }
+ }
+
+ /**
+ * TCK Match4 [7]: variable-length segments flanking a bound relationship must obey path
+ * isomorphism - they must not re-traverse the explicitly named {@code r}.
+ */
+ @Test
+ void vlpSegmentsDoNotReuseExplicitlyNamedBoundRelationship() {
+ final ResultSet result = database.query("opencypher",
+ "MATCH ()-[r:EDGE4006]-()"
+ + " MATCH p = (n)-[*0..1]-()-[r]-()-[*0..1]-(m)"
+ + " RETURN count(p) AS c");
+
+ final List