Skip to content

Commit 2ef2193

Browse files
Merge pull request #69 from sirixdb/feature/sdb-select-item-delete-support
Fix the UpdateList
2 parents 1e7aba4 + 777665d commit 2ef2193

2 files changed

Lines changed: 91 additions & 7 deletions

File tree

src/main/java/io/brackit/query/update/UpdateList.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ public final class UpdateList {
5353
OpType.REPLACE_VALUE,
5454
OpType.REPLACE_ELEMENT_CONTENT);
5555

56+
// Property update operations that should be skipped for deleted nodes
57+
// Per XQuery Update Facility 1.0 section 3.2.1:
58+
// "If a node is marked for deletion, updates to its properties have no effect."
59+
// Note: Insert operations should NOT be skipped - they create new sibling/child
60+
// nodes that persist after the target is deleted.
61+
private static final EnumSet<OpType> propertyUpdateOps = EnumSet.of(OpType.RENAME,
62+
OpType.REPLACE_NODE,
63+
OpType.REPLACE_VALUE,
64+
OpType.REPLACE_ELEMENT_CONTENT);
65+
5666
private final List<UpdateOp> ops;
5767

5868
public UpdateList() {
@@ -87,21 +97,20 @@ public void apply() throws QueryException {
8797
}
8898

8999
// Collect all nodes marked for deletion
90-
// Per XQuery Update Facility 1.0 section 3.2.1:
91-
// "If a node is marked for deletion, updates to its properties have no effect."
92100
final Set<Item> deletedNodes = new HashSet<>();
93101
for (final UpdateOp op : ops) {
94102
if (op.getType() == OpType.DELETE) {
95103
deletedNodes.add(op.getTarget());
96104
}
97105
}
98106

99-
// Apply all updates, skipping those targeting deleted nodes
107+
// Apply all updates, skipping only property updates to deleted nodes
100108
for (final UpdateOp op : ops) {
101-
// Skip non-delete updates to nodes that will be deleted
102-
if (op.getType() != OpType.DELETE && deletedNodes.contains(op.getTarget())) {
109+
// Skip property updates to nodes that will be deleted
110+
// Insert operations should NOT be skipped - they create new nodes
111+
if (propertyUpdateOps.contains(op.getType()) && deletedNodes.contains(op.getTarget())) {
103112
if (log.isDebugEnabled()) {
104-
log.debug(String.format("Skipping update %s - target node is marked for deletion", op));
113+
log.debug(String.format("Skipping property update %s - target node is marked for deletion", op));
105114
}
106115
continue;
107116
}
@@ -167,4 +176,4 @@ private void checkCompatibility(final UpdateOp op1, final UpdateOp op2) throws Q
167176
public List<UpdateOp> list() {
168177
return ops;
169178
}
170-
}
179+
}

src/test/java/io/brackit/query/update/UpdateListTest.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,81 @@ public void updateAndDeleteDifferentNodesAppliesBoth() {
125125
assertEquals("Both operations should be applied", 2, appliedOps.size());
126126
}
127127

128+
/**
129+
* Per XQuery Update Facility 1.0 section 3.2.2:
130+
* Insert operations are applied before delete operations.
131+
* An INSERT_BEFORE targeting a node that will be deleted should NOT be skipped.
132+
* The inserted content becomes a sibling and persists after the delete.
133+
*/
134+
@Test
135+
public void insertBeforeDeletedNodeIsNotSkipped() {
136+
final List<String> appliedOps = new ArrayList<>();
137+
final StructuredItem targetItem = new ArrayObject(new QNm[] { new QNm("name") },
138+
new Sequence[] { new Str("test") });
139+
140+
final UpdateList updateList = new UpdateList();
141+
142+
// Add an INSERT_BEFORE operation targeting a node
143+
updateList.append(new TestUpdateOp(OpType.INSERT_BEFORE, targetItem, () -> appliedOps.add("INSERT_BEFORE")));
144+
145+
// Add a DELETE operation for the same target
146+
updateList.append(new TestUpdateOp(OpType.DELETE, targetItem, () -> appliedOps.add("DELETE")));
147+
148+
// Apply updates
149+
updateList.apply();
150+
151+
// Both should be applied - INSERT_BEFORE creates a sibling, then DELETE removes the target
152+
assertEquals("Both INSERT_BEFORE and DELETE should be applied", List.of("INSERT_BEFORE", "DELETE"), appliedOps);
153+
}
154+
155+
/**
156+
* INSERT_INTO targeting a deleted node should also be applied.
157+
*/
158+
@Test
159+
public void insertIntoDeletedNodeIsNotSkipped() {
160+
final List<String> appliedOps = new ArrayList<>();
161+
final StructuredItem targetItem = new ArrayObject(new QNm[] { new QNm("name") },
162+
new Sequence[] { new Str("test") });
163+
164+
final UpdateList updateList = new UpdateList();
165+
166+
// Add an INSERT_INTO operation
167+
updateList.append(new TestUpdateOp(OpType.INSERT_INTO, targetItem, () -> appliedOps.add("INSERT_INTO")));
168+
169+
// Add a DELETE operation for the same target
170+
updateList.append(new TestUpdateOp(OpType.DELETE, targetItem, () -> appliedOps.add("DELETE")));
171+
172+
// Apply updates
173+
updateList.apply();
174+
175+
// Both should be applied
176+
assertEquals("Both INSERT_INTO and DELETE should be applied", List.of("INSERT_INTO", "DELETE"), appliedOps);
177+
}
178+
179+
/**
180+
* INSERT_AFTER targeting a deleted node should also be applied.
181+
*/
182+
@Test
183+
public void insertAfterDeletedNodeIsNotSkipped() {
184+
final List<String> appliedOps = new ArrayList<>();
185+
final StructuredItem targetItem = new ArrayObject(new QNm[] { new QNm("name") },
186+
new Sequence[] { new Str("test") });
187+
188+
final UpdateList updateList = new UpdateList();
189+
190+
// Add an INSERT_AFTER operation
191+
updateList.append(new TestUpdateOp(OpType.INSERT_AFTER, targetItem, () -> appliedOps.add("INSERT_AFTER")));
192+
193+
// Add a DELETE operation for the same target
194+
updateList.append(new TestUpdateOp(OpType.DELETE, targetItem, () -> appliedOps.add("DELETE")));
195+
196+
// Apply updates
197+
updateList.apply();
198+
199+
// Both should be applied
200+
assertEquals("Both INSERT_AFTER and DELETE should be applied", List.of("INSERT_AFTER", "DELETE"), appliedOps);
201+
}
202+
128203
/**
129204
* Simple test implementation of UpdateOp for testing purposes.
130205
*/

0 commit comments

Comments
 (0)