Преглед на файлове

Delete/require (target) conflict

Joeri Exelmans преди 3 години
родител
ревизия
0c81871739
променени са 2 файла, в които са добавени 105 реда и са изтрити 6 реда
  1. 35 1
      src/onion/micro_op.test.ts
  2. 70 5
      src/onion/micro_op.ts

+ 35 - 1
src/onion/micro_op.test.ts

@@ -90,7 +90,7 @@ describe("Delta", () => {
     assert(sourceDeletion.getConflicts().length === 1, "expected require/delete conflict, because edge source is concurrently deleted");
   });
 
-  it("Delete depends on deleted edge (no conflict)", () => {
+  it("Require (edge source), then delete (no conflict)", () => {
     // proper way of deleting the source of an edge: the deletion must depend on the edgeCreation
     const sourceCreation = new NodeCreation(getId());
     const targetCreation = new NodeCreation(getId());
@@ -133,6 +133,40 @@ describe("Delta", () => {
     assert(sourceDeletion.getConflicts().length === 1, "expected conflict between edgeUpdate and sourceDeletion");
   });
 
+  it("Delete/require (edge target) conflict", () => {
+    const sourceCreation = new NodeCreation(getId());
+    const targetCreation = new NodeCreation(getId());
+
+    const targetDeletion = new NodeDeletion(targetCreation, []);
+
+    const edgeCreation = new EdgeCreation(sourceCreation, "label", targetCreation);
+    assert(edgeCreation.getConflicts().length === 1, "expected require/delete conflict");
+    assert(targetDeletion.getConflicts().length === 1, "expected require/delete conflict");
+  })
+
+  it("Delete/require (edge target) conflict (2)", () => {
+    const sourceCreation = new NodeCreation(getId());
+    const targetCreation = new NodeCreation(getId());
+    const edgeCreation = new EdgeCreation(sourceCreation, "label", targetCreation);
+
+    // delete target of edge, unaware that edge exists:
+    const targetDeletion = new NodeDeletion(targetCreation, []);
+
+    assert(edgeCreation.getConflicts().length === 1, "expected require/delete conflict");
+    assert(targetDeletion.getConflicts().length === 1, "expected require/delete conflict");
+  })
+
+  it("Require (edge target), then delete (no conflict)", () => {
+    const sourceCreation = new NodeCreation(getId());
+    const targetCreation = new NodeCreation(getId());
+    const edgeCreation = new EdgeCreation(sourceCreation, "label", targetCreation);
+
+    const targetDeletion = new NodeDeletion(targetCreation, [edgeCreation]);
+
+    assert(edgeCreation.getConflicts().length === 0, "expected no require/delete conflict");
+    assert(targetDeletion.getConflicts().length === 0, "expected no require/delete conflict");
+  })
+
 
 
   // it("Delete/delete conflict", () => {

+ 70 - 5
src/onion/micro_op.ts

@@ -13,6 +13,9 @@ export class NodeCreation implements Delta {
   // Inverse dependency: Creation outgoing edges.
   outgoingEdges: Array<EdgeCreation> = []; // append-only
 
+  // Inverse dependency: Creation/update of incoming edges.
+  incomingEdges: Array<EdgeCreation | EdgeUpdate> = []; // append-only
+
   constructor(id: UUID) {
     this.id = id;
   }
@@ -39,6 +42,9 @@ export class NodeDeletion implements Delta {
   // Conflicts: Concurrent creation of an edge with as source the deleted node.
   edgeSourceConflicts: Array<EdgeCreation> = [];
 
+  // Conflicts: Concurrent creation/update of an edge with as target the deleted node.
+  edgeTargetConflicts: Array<EdgeCreation | EdgeUpdate> = [];
+
   // Conflicts: Concurrent update of an edge, when the edge is also deleted.
   updateConflicts: Array<EdgeUpdate | NodeDeletion> = [];
 
@@ -64,6 +70,21 @@ export class NodeDeletion implements Delta {
       }
     }
 
+    // Concurrently created incoming edges of this node
+    for (const incomingEdge of this.creation.incomingEdges) {
+      if (!this.deletedEdges.some(edge => {
+        while (true) {
+          if (edge === incomingEdge) return true;
+          if (edge instanceof EdgeUpdate) edge = edge.overwrites;
+          else return false;
+        }
+      })) {
+        // Symmetric
+        this.edgeTargetConflicts.push(incomingEdge);
+        incomingEdge.deleteTargetConflicts.push(this);
+      }
+    }
+
     // Concurrent edge updates
     for (const deletedEdge of this.deletedEdges) {
       for (const concurrentEdgeUpdate of deletedEdge.overwrittenBy) {
@@ -91,6 +112,7 @@ export class NodeDeletion implements Delta {
     return Array<Delta>().concat(
       this.deleteConflicts,
       this.edgeSourceConflicts,
+      this.edgeTargetConflicts,
       this.updateConflicts,
     );
   }
@@ -105,7 +127,6 @@ export class EdgeCreation implements Delta {
   // Inverse dependency
   // NodeDeletion if source of edge is deleted.
   overwrittenBy: Array<EdgeUpdate | NodeDeletion> = []; // append-only
-  // overwrittenBy: Array<EdgeUpdate> = []; // append-only
 
   // Conflicts: Concurrent creations of the same edge.
   createConflicts: Array<EdgeCreation> = []; // append-only
@@ -113,6 +134,9 @@ export class EdgeCreation implements Delta {
   // Conflicts: Concurrent deletions of source node.
   deleteSourceConflicts: Array<NodeDeletion> = []; // append-only
 
+  // Conflicts: Concurrent deletion of target node.
+  deleteTargetConflicts: Array<NodeDeletion> = []; // append-only
+
   constructor(source: NodeCreation, label: string, target: NodeCreation) {
     this.source = source;
     this.label = label;
@@ -132,14 +156,31 @@ export class EdgeCreation implements Delta {
     // Concurrent deletions of source node
     for (const sourceDeletion of this.source.deletions) {
       if (sourceDeletion.deletedEdges.some(edge => edge.getCreation() === this))
-        throw new Error("Assertion failed - did not expect existing deletion to be aware of a new edge creation")
+        throw new Error("Assertion failed - did not expect existing deletion to be aware of a new edge creation");
       // Symmetric
       this.deleteSourceConflicts.push(sourceDeletion);
       sourceDeletion.edgeSourceConflicts.push(this);
     }
 
+    // Concurrent deletion of target node
+    for (const targetDeletion of this.target.deletions) {
+      if (targetDeletion.deletedEdges.some(edge => {
+        while (true) {
+          if (edge === this) return true;
+          if (edge instanceof EdgeUpdate) edge = edge.overwrites;
+          else return false;
+        }
+      })) {
+        throw new Error("Assertion failed - did not expect existing deletion to be aware of a new edge update");
+      }
+      // Symmetric
+      this.deleteTargetConflicts.push(targetDeletion);
+      targetDeletion.edgeTargetConflicts.push(this);
+    }
+
     // Create inverse dependency
     this.source.outgoingEdges.push(this);
+    this.target.incomingEdges.push(this);
   }
 
   // Helper
@@ -155,6 +196,7 @@ export class EdgeCreation implements Delta {
     return Array<Delta>().concat(
       this.createConflicts,
       this.deleteSourceConflicts,
+      this.deleteTargetConflicts,
     );
   }
 }
@@ -167,25 +209,45 @@ export class EdgeUpdate implements Delta {
   // Inverse dependency
   // NodeDeletion if source of edge is deleted.
   overwrittenBy: Array<EdgeUpdate | NodeDeletion> = []; // append-only
-  // overwrittenBy: Array<EdgeUpdate> = []; // append-only
 
   // Conflicts: Concurrent updates
-  // updateConflicts: Array<EdgeUpdate | NodeDeletion> = []; // append-only
   updateConflicts: Array<EdgeUpdate | NodeDeletion> = []; // append-only
 
+  // Conflicts: Concurrent deletion of target node.
+  deleteTargetConflicts: Array<NodeDeletion> = []; // append-only
+
   constructor(overwrites: EdgeCreation | EdgeUpdate, newTarget: NodeCreation) {
     this.overwrites = overwrites;
     this.newTarget = newTarget;
 
     // Detect conflicts
+
+    // Concurrent updates (by EdgeUpdate or NodeDeletion)
     for (const concurrentUpdate of this.overwrites.overwrittenBy) {
       // Symmetric
       this.updateConflicts.push(concurrentUpdate);
       concurrentUpdate.updateConflicts.push(this);
     }
 
+    // Concurrent deletion of target node
+    for (const targetDeletion of this.newTarget.deletions) {
+      if (targetDeletion.deletedEdges.some(edge => {
+        while (true) {
+          if (edge === this) return true;
+          if (edge instanceof EdgeUpdate) edge = edge.overwrites;
+          else return false;
+        }
+      })) {
+        throw new Error("Assertion failed - did not expect existing deletion to be aware of a new edge update");
+      }
+      // Symmetric
+      this.deleteTargetConflicts.push(targetDeletion);
+      targetDeletion.edgeTargetConflicts.push(this);
+    }
+
     // Create inverse dependency
     this.overwrites.overwrittenBy.push(this);
+    this.newTarget.incomingEdges.push(this);
   }
 
   // Helper
@@ -198,7 +260,10 @@ export class EdgeUpdate implements Delta {
   }
 
   getConflicts(): Array<Delta> {
-    return this.updateConflicts;
+    return Array<Delta>().concat(
+      this.updateConflicts,
+      this.deleteTargetConflicts,
+    );
   }
 }