Skip to content

Commit ab6b2ab

Browse files
authored
fix: UPDATE EDGE SET @in/@out correctly rewires vertex edge lists (ArcadeData#4074)
1 parent bfafd9b commit ab6b2ab

3 files changed

Lines changed: 169 additions & 2 deletions

File tree

engine/src/main/java/com/arcadedb/graph/GraphEngine.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,33 @@ public void deleteEdge(final Edge edge) {
394394
}
395395
}
396396

397+
public void moveEdge(final MutableEdge edge, final Vertex.DIRECTION direction, final RID newVertexRID) {
398+
if (direction != Vertex.DIRECTION.IN && direction != Vertex.DIRECTION.OUT)
399+
throw new IllegalArgumentException("Unsupported direction for moveEdge: " + direction);
400+
401+
final String typeName = edge.getTypeName();
402+
final Map<String, Object> properties = edge.propertiesAsMap();
403+
final RID newOut = direction == Vertex.DIRECTION.OUT ? newVertexRID : edge.getOut();
404+
final RID newIn = direction == Vertex.DIRECTION.IN ? newVertexRID : edge.getIn();
405+
406+
deleteEdge(edge);
407+
408+
final EdgeType edgeType = (EdgeType) database.getSchema().getType(typeName);
409+
final VertexInternal fromVertex = (VertexInternal) database.lookupByRID(newOut, false);
410+
final Identifiable toVertex = database.lookupByRID(newIn, false);
411+
412+
final MutableEdge newEdge = new MutableEdge(database, edgeType, newOut, newIn);
413+
if (!properties.isEmpty())
414+
newEdge.set(properties);
415+
newEdge.save();
416+
417+
connectOutgoingEdge(fromVertex, toVertex, newEdge);
418+
if (edgeType.isBidirectional())
419+
connectIncomingEdge(toVertex, newOut, newEdge.getIdentity());
420+
421+
edge.updateIdentity(newEdge.getIdentity(), newOut, newIn);
422+
}
423+
397424
public void deleteVertex(final VertexInternal vertex) {
398425
// RETRIEVE ALL THE EDGES TO DELETE AT THE END
399426
final List<Identifiable> edgesToDelete = new ArrayList<>();

engine/src/main/java/com/arcadedb/graph/MutableEdge.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import com.arcadedb.database.Binary;
2222
import com.arcadedb.database.Database;
23+
import com.arcadedb.database.DatabaseInternal;
24+
import com.arcadedb.database.Identifiable;
2325
import com.arcadedb.database.MutableDocument;
2426
import com.arcadedb.database.RID;
2527
import com.arcadedb.database.Transaction;
@@ -130,8 +132,28 @@ public MutableEdge set(final Map<String, Object> properties) {
130132

131133
@Override
132134
public MutableEdge set(final String name, final Object value) {
133-
super.set(name, value);
134-
return this;
135+
if ("@in".equals(name)) {
136+
final RID newIn = toRID(value);
137+
if (newIn == null)
138+
throw new IllegalArgumentException("Cannot set @in to null: edges require both endpoints");
139+
if (rid != null && !newIn.equals(this.in) && database instanceof DatabaseInternal dbInt)
140+
dbInt.getGraphEngine().moveEdge(this, Vertex.DIRECTION.IN, newIn);
141+
else
142+
this.in = newIn;
143+
dirty = true;
144+
return this;
145+
} else if ("@out".equals(name)) {
146+
final RID newOut = toRID(value);
147+
if (newOut == null)
148+
throw new IllegalArgumentException("Cannot set @out to null: edges require both endpoints");
149+
if (rid != null && !newOut.equals(this.out) && database instanceof DatabaseInternal dbInt)
150+
dbInt.getGraphEngine().moveEdge(this, Vertex.DIRECTION.OUT, newOut);
151+
else
152+
this.out = newOut;
153+
dirty = true;
154+
return this;
155+
}
156+
return (MutableEdge) super.set(name, value);
135157
}
136158

137159
@Override
@@ -196,6 +218,19 @@ public void setIn(final RID in) {
196218
this.in = in;
197219
}
198220

221+
void updateIdentity(final RID newRid, final RID newOut, final RID newIn) {
222+
this.rid = newRid;
223+
this.out = newOut;
224+
this.in = newIn;
225+
this.buffer = null;
226+
}
227+
228+
private static RID toRID(final Object value) {
229+
if (value instanceof RID r) return r;
230+
if (value instanceof Identifiable i) return i.getIdentity();
231+
return null;
232+
}
233+
199234
@Override
200235
public Map<String, Object> toMap(final boolean includeMetadata) {
201236
final Map<String, Object> map = super.toMap(includeMetadata);
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.arcadedb.query.sql;
2+
3+
import com.arcadedb.TestHelper;
4+
import com.arcadedb.database.RID;
5+
import com.arcadedb.graph.Edge;
6+
import com.arcadedb.graph.Vertex;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.util.Iterator;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
public class UpdateEdgeTest extends TestHelper {
14+
@Override
15+
protected void beginTest() {
16+
database.transaction(() -> {
17+
database.command("sql", "CREATE VERTEX TYPE V");
18+
database.command("sql", "CREATE EDGE TYPE E");
19+
});
20+
}
21+
22+
@Test
23+
void testUpdateEdgeIn() {
24+
final RID[] rids = new RID[3];
25+
26+
database.transaction(() -> {
27+
rids[0] = database.command("sql", "CREATE VERTEX V SET name = 'Alice'").next().getIdentity().get();
28+
rids[1] = database.command("sql", "CREATE VERTEX V SET name = 'Bob'").next().getIdentity().get();
29+
rids[2] = database.command("sql", "CREATE VERTEX V SET name = 'Charlie'").next().getIdentity().get();
30+
database.command("sql",
31+
"CREATE EDGE E FROM (SELECT FROM V WHERE name = 'Alice') TO (SELECT FROM V WHERE name = 'Bob') SET weight = 5");
32+
database.command("sql", "UPDATE E SET @in = " + rids[2] + " WHERE @out = " + rids[0]);
33+
});
34+
35+
final Edge edge = database.query("sql", "SELECT FROM E").next().getEdge().get();
36+
37+
assertThat(edge.getOut().toString()).isEqualTo(rids[0].toString());
38+
assertThat(edge.getIn().toString()).isEqualTo(rids[2].toString());
39+
}
40+
41+
@Test
42+
void testUpdateEdgeOut() {
43+
final RID[] rids = new RID[3];
44+
45+
database.transaction(() -> {
46+
rids[0] = database.command("sql", "CREATE VERTEX V SET name = 'Alice'").next().getIdentity().get();
47+
rids[1] = database.command("sql", "CREATE VERTEX V SET name = 'Bob'").next().getIdentity().get();
48+
rids[2] = database.command("sql", "CREATE VERTEX V SET name = 'Charlie'").next().getIdentity().get();
49+
database.command("sql",
50+
"CREATE EDGE E FROM (SELECT FROM V WHERE name = 'Alice') TO (SELECT FROM V WHERE name = 'Bob') SET weight = 7");
51+
database.command("sql", "UPDATE E SET @out = " + rids[2] + " WHERE @in = " + rids[1]);
52+
});
53+
54+
final Edge edge = database.query("sql", "SELECT FROM E").next().getEdge().get();
55+
56+
assertThat(edge.getOut().toString()).isEqualTo(rids[2].toString());
57+
assertThat(edge.getIn().toString()).isEqualTo(rids[1].toString());
58+
}
59+
60+
@Test
61+
void testUpdateEdgePreservesProperties() {
62+
final RID[] rids = new RID[3];
63+
64+
database.transaction(() -> {
65+
rids[0] = database.command("sql", "CREATE VERTEX V SET name = 'Alice'").next().getIdentity().get();
66+
rids[1] = database.command("sql", "CREATE VERTEX V SET name = 'Bob'").next().getIdentity().get();
67+
rids[2] = database.command("sql", "CREATE VERTEX V SET name = 'Charlie'").next().getIdentity().get();
68+
database.command("sql",
69+
"CREATE EDGE E FROM (SELECT FROM V WHERE name = 'Alice') TO (SELECT FROM V WHERE name = 'Bob') SET weight = 42");
70+
database.command("sql", "UPDATE E SET @in = " + rids[2] + " WHERE @out = " + rids[0]);
71+
});
72+
73+
final Edge edge = database.query("sql", "SELECT FROM E").next().getEdge().get();
74+
75+
assertThat(edge.getInteger("weight")).isEqualTo(42);
76+
}
77+
78+
@Test
79+
void testUpdateEdgeInReflectedInTraversal() {
80+
final RID[] rids = new RID[3];
81+
82+
database.transaction(() -> {
83+
rids[0] = database.command("sql", "CREATE VERTEX V SET name = 'Alice'").next().getIdentity().get();
84+
rids[1] = database.command("sql", "CREATE VERTEX V SET name = 'Bob'").next().getIdentity().get();
85+
rids[2] = database.command("sql", "CREATE VERTEX V SET name = 'Charlie'").next().getIdentity().get();
86+
database.command("sql",
87+
"CREATE EDGE E FROM (SELECT FROM V WHERE name = 'Alice') TO (SELECT FROM V WHERE name = 'Bob') SET weight = 1");
88+
database.command("sql", "UPDATE E SET @in = " + rids[2] + " WHERE @out = " + rids[0]);
89+
});
90+
91+
final Vertex alice = (Vertex) database.lookupByRID(rids[0], true);
92+
final Vertex bob = (Vertex) database.lookupByRID(rids[1], true);
93+
final Vertex charlie = (Vertex) database.lookupByRID(rids[2], true);
94+
95+
final Iterator<Edge> aliceOut = alice.getEdges(Vertex.DIRECTION.OUT, "E").iterator();
96+
assertThat(aliceOut.hasNext()).isTrue();
97+
assertThat(aliceOut.next().getIn().toString()).isEqualTo(rids[2].toString());
98+
99+
final Iterator<Edge> charlieIn = charlie.getEdges(Vertex.DIRECTION.IN, "E").iterator();
100+
assertThat(charlieIn.hasNext()).isTrue();
101+
assertThat(charlieIn.next().getOut().toString()).isEqualTo(rids[0].toString());
102+
103+
assertThat(bob.getEdges(Vertex.DIRECTION.IN, "E").iterator().hasNext()).isFalse();
104+
}
105+
}

0 commit comments

Comments
 (0)