Skip to content

Commit 60e6f64

Browse files
robfranktae898
authored andcommitted
ArcadeData#4000 fix(cypher): SET propagates to all aliases bound to the same node (ArcadeData#4004)
1 parent b7c4069 commit 60e6f64

2 files changed

Lines changed: 77 additions & 6 deletions

File tree

engine/src/main/java/com/arcadedb/query/opencypher/executor/steps/SetStep.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020

2121
import com.arcadedb.database.Document;
2222
import com.arcadedb.database.MutableDocument;
23+
import com.arcadedb.database.RID;
2324
import com.arcadedb.exception.TimeoutException;
2425
import com.arcadedb.graph.Edge;
2526
import com.arcadedb.graph.MutableVertex;
2627
import com.arcadedb.graph.Vertex;
2728
import com.arcadedb.query.opencypher.Labels;
28-
import com.arcadedb.query.opencypher.ast.Expression;
2929
import com.arcadedb.query.opencypher.ast.SetClause;
3030
import com.arcadedb.query.opencypher.executor.CypherFunctionFactory;
3131
import com.arcadedb.query.opencypher.executor.ExpressionEvaluator;
@@ -178,8 +178,9 @@ private void applyPropertySet(final SetClause.SetItem item, final Result result)
178178
mutableDoc.set(item.getProperty(), value);
179179
}
180180
mutableDoc.save();
181-
182-
if (variableToUpdate != null)
181+
propagateUpdateToSameNodeAliases(result, doc, mutableDoc);
182+
// Fallback: ensure the named variable is updated even when doc has no identity yet
183+
if (variableToUpdate != null && doc.getIdentity() == null)
183184
((ResultInternal) result).setProperty(variableToUpdate, mutableDoc);
184185
}
185186

@@ -210,7 +211,9 @@ private void applyReplaceMap(final SetClause.SetItem item, final Result result)
210211
}
211212

212213
mutableDoc.save();
213-
((ResultInternal) result).setProperty(item.getVariable(), mutableDoc);
214+
propagateUpdateToSameNodeAliases(result, doc, mutableDoc);
215+
if (doc.getIdentity() == null)
216+
((ResultInternal) result).setProperty(item.getVariable(), mutableDoc);
214217
}
215218

216219
@SuppressWarnings("unchecked")
@@ -235,7 +238,24 @@ private void applyMergeMap(final SetClause.SetItem item, final Result result) {
235238
}
236239

237240
mutableDoc.save();
238-
((ResultInternal) result).setProperty(item.getVariable(), mutableDoc);
241+
propagateUpdateToSameNodeAliases(result, doc, mutableDoc);
242+
if (doc.getIdentity() == null)
243+
((ResultInternal) result).setProperty(item.getVariable(), mutableDoc);
244+
}
245+
246+
/**
247+
* After mutating a document, update every alias in the result row that points to the same node
248+
* (identified by RID) so all aliases observe the new state within the same query.
249+
*/
250+
private void propagateUpdateToSameNodeAliases(final Result result, final Document originalDoc, final Document updatedDoc) {
251+
final RID originalRid = originalDoc.getIdentity();
252+
if (originalRid == null)
253+
return;
254+
for (final String propName : result.getPropertyNames()) {
255+
final Object prop = result.getProperty(propName);
256+
if (prop instanceof Document other && other != updatedDoc && originalRid.equals(other.getIdentity()))
257+
((ResultInternal) result).setProperty(propName, updatedDoc);
258+
}
239259
}
240260

241261
private void applyLabels(final SetClause.SetItem item, final Result result) {
@@ -273,7 +293,7 @@ private void applyLabels(final SetClause.SetItem item, final Result result) {
273293
// Delete old vertex
274294
vertex.delete();
275295

276-
((ResultInternal) result).setProperty(item.getVariable(), newVertex);
296+
propagateUpdateToSameNodeAliases(result, vertex, newVertex);
277297
}
278298

279299
private void validatePropertyValue(final Object value) {

engine/src/test/java/com/arcadedb/query/opencypher/OpenCypherSetTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,57 @@ void setCaseSubclauseWithUnwind() {
374374
assertThat(vb.get("propB")).isNull();
375375
}
376376

377+
/**
378+
* Issue #4000: SET through one alias must be visible through other aliases bound to the same node in the same row.
379+
*/
380+
@Test
381+
void setThroughOneAliasPropagatestoOtherAliasOnSameNode() {
382+
database.transaction(() -> {
383+
database.command("opencypher", "CREATE (:Person {name: 'Alice', age: 30}), (:Person {name: 'Bob', age: 25})");
384+
});
385+
386+
final ResultSet result = database.command("opencypher",
387+
"MATCH (n:Person {name: 'Alice'}), (m:Person {name: 'Alice'}) SET n.age = 35 RETURN n.age AS n_age, m.age AS m_age");
388+
389+
assertThat(result.hasNext()).isTrue();
390+
final Result row = result.next();
391+
assertThat(((Number) row.getProperty("n_age")).intValue()).isEqualTo(35);
392+
assertThat(((Number) row.getProperty("m_age")).intValue()).isEqualTo(35);
393+
}
394+
395+
/**
396+
* Issue #4000: stronger reproducer — full node aliases bound via WITH+MATCH to the same node must reflect a SET on the other alias.
397+
*/
398+
@Test
399+
void setThroughOneAliasPropagatestoOtherAliasOnSameNodeFullNode() {
400+
database.transaction(() -> {
401+
database.command("opencypher", "CREATE (:Person {name: 'Alice', age: 30})");
402+
});
403+
404+
final ResultSet result = database.command("opencypher",
405+
"MATCH (n:Person {name: 'Alice'}) WITH n MATCH (m:Person {name: 'Alice'}) SET n.age = 35 RETURN n.age AS n_age, m.age AS m_age");
406+
407+
assertThat(result.hasNext()).isTrue();
408+
final Result row = result.next();
409+
assertThat(((Number) row.getProperty("n_age")).intValue()).isEqualTo(35);
410+
assertThat(((Number) row.getProperty("m_age")).intValue()).isEqualTo(35);
411+
}
412+
413+
@Test
414+
void setLabelThroughOneAliasPropagatestoOtherAliasOnSameNode() {
415+
database.transaction(() -> {
416+
database.command("opencypher", "CREATE (:Person {name: 'Alice', age: 30})");
417+
});
418+
419+
final ResultSet result = database.command("opencypher",
420+
"MATCH (n:Person {name: 'Alice'}), (m:Person {name: 'Alice'}) SET n:Employee RETURN n.name AS n_name, m.name AS m_name");
421+
422+
assertThat(result.hasNext()).isTrue();
423+
final Result row = result.next();
424+
assertThat((String) row.getProperty("n_name")).isEqualTo("Alice");
425+
assertThat((String) row.getProperty("m_name")).isEqualTo("Alice");
426+
}
427+
377428
@Test
378429
void setAfterCreate() {
379430
// CREATE and SET in same query

0 commit comments

Comments
 (0)