Skip to content

Commit 904caa9

Browse files
committed
[POC] Optimistic Locking for Delete Operations
1 parent 4ed59ff commit 904caa9

File tree

6 files changed

+91
-74
lines changed

6 files changed

+91
-74
lines changed

services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncCrudWithResponseIntegrationTest.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -458,9 +458,10 @@ public void deleteItemWithHelper_versionMatch_shouldSucceed() {
458458
versionedRecordTable.putItem(item).join();
459459
VersionedRecord savedItem = versionedRecordTable.getItem(r -> r.key(recordKey)).join();
460460

461-
DeleteItemEnhancedRequest baseRequest = DeleteItemEnhancedRequest.builder().key(recordKey).build();
462-
DeleteItemEnhancedRequest requestWithLocking = DeleteItemEnhancedRequest.withOptimisticLocking(
463-
baseRequest, AttributeValue.builder().n(savedItem.getVersion().toString()).build(), "version");
461+
DeleteItemEnhancedRequest requestWithLocking = DeleteItemEnhancedRequest.builder()
462+
.key(recordKey)
463+
.withOptimisticLocking(AttributeValue.builder().n(savedItem.getVersion().toString()).build(), "version")
464+
.build();
464465

465466
versionedRecordTable.deleteItem(requestWithLocking).join();
466467

@@ -476,9 +477,10 @@ public void deleteItemWithHelper_versionMismatch_shouldFail() {
476477

477478
versionedRecordTable.putItem(item).join();
478479

479-
DeleteItemEnhancedRequest baseRequest = DeleteItemEnhancedRequest.builder().key(recordKey).build();
480-
DeleteItemEnhancedRequest requestWithLocking = DeleteItemEnhancedRequest.withOptimisticLocking(
481-
baseRequest, AttributeValue.builder().n("999").build(), "version");
480+
DeleteItemEnhancedRequest requestWithLocking = DeleteItemEnhancedRequest.builder()
481+
.key(recordKey)
482+
.withOptimisticLocking(AttributeValue.builder().n("999").build(), "version")
483+
.build();
482484

483485
assertThatThrownBy(() -> versionedRecordTable.deleteItem(requestWithLocking).join())
484486
.isInstanceOf(CompletionException.class)
@@ -529,9 +531,10 @@ public void transactDeleteItemWithHelper_versionMatch_shouldSucceed() {
529531
versionedRecordTable.putItem(item).join();
530532
VersionedRecord savedItem = versionedRecordTable.getItem(r -> r.key(recordKey)).join();
531533

532-
TransactDeleteItemEnhancedRequest baseRequest = TransactDeleteItemEnhancedRequest.builder().key(recordKey).build();
533-
TransactDeleteItemEnhancedRequest requestWithLocking = TransactDeleteItemEnhancedRequest.withOptimisticLocking(
534-
baseRequest, AttributeValue.builder().n(savedItem.getVersion().toString()).build(), "version");
534+
TransactDeleteItemEnhancedRequest requestWithLocking = TransactDeleteItemEnhancedRequest.builder()
535+
.key(recordKey)
536+
.withOptimisticLocking(AttributeValue.builder().n(savedItem.getVersion().toString()).build(), "version")
537+
.build();
535538

536539
enhancedClient.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()
537540
.addDeleteItem(versionedRecordTable, requestWithLocking)
@@ -549,9 +552,10 @@ public void transactDeleteItemWithHelper_versionMismatch_shouldFail() {
549552

550553
versionedRecordTable.putItem(item).join();
551554

552-
TransactDeleteItemEnhancedRequest baseRequest = TransactDeleteItemEnhancedRequest.builder().key(recordKey).build();
553-
TransactDeleteItemEnhancedRequest requestWithLocking = TransactDeleteItemEnhancedRequest.withOptimisticLocking(
554-
baseRequest, AttributeValue.builder().n("999").build(), "version");
555+
TransactDeleteItemEnhancedRequest requestWithLocking = TransactDeleteItemEnhancedRequest.builder()
556+
.key(recordKey)
557+
.withOptimisticLocking(AttributeValue.builder().n("999").build(), "version")
558+
.build();
555559

556560
assertThatThrownBy(() -> enhancedClient.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()
557561
.addDeleteItem(versionedRecordTable, requestWithLocking)

services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/CrudWithResponseIntegrationTest.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -441,9 +441,10 @@ public void deleteItemWithHelper_versionMatch_shouldSucceed() {
441441
versionedRecordTable.putItem(item);
442442
VersionedRecord savedItem = versionedRecordTable.getItem(r -> r.key(recordKey));
443443

444-
DeleteItemEnhancedRequest baseRequest = DeleteItemEnhancedRequest.builder().key(recordKey).build();
445-
DeleteItemEnhancedRequest requestWithLocking = DeleteItemEnhancedRequest.withOptimisticLocking(
446-
baseRequest, AttributeValue.builder().n(savedItem.getVersion().toString()).build(), "version");
444+
DeleteItemEnhancedRequest requestWithLocking = DeleteItemEnhancedRequest.builder()
445+
.key(recordKey)
446+
.withOptimisticLocking(AttributeValue.builder().n(savedItem.getVersion().toString()).build(), "version")
447+
.build();
447448

448449
versionedRecordTable.deleteItem(requestWithLocking);
449450

@@ -459,9 +460,10 @@ public void deleteItemWithHelper_versionMismatch_shouldFail() {
459460

460461
versionedRecordTable.putItem(item);
461462

462-
DeleteItemEnhancedRequest baseRequest = DeleteItemEnhancedRequest.builder().key(recordKey).build();
463-
DeleteItemEnhancedRequest requestWithLocking = DeleteItemEnhancedRequest.withOptimisticLocking(
464-
baseRequest, AttributeValue.builder().n("999").build(), "version");
463+
DeleteItemEnhancedRequest requestWithLocking = DeleteItemEnhancedRequest.builder()
464+
.key(recordKey)
465+
.withOptimisticLocking(AttributeValue.builder().n("999").build(), "version")
466+
.build();
465467

466468
assertThatThrownBy(() -> versionedRecordTable.deleteItem(requestWithLocking))
467469
.isInstanceOf(ConditionalCheckFailedException.class)
@@ -513,9 +515,10 @@ public void transactDeleteItemWithHelper_versionMatch_shouldSucceed() {
513515
versionedRecordTable.putItem(item);
514516
VersionedRecord savedItem = versionedRecordTable.getItem(r -> r.key(recordKey));
515517

516-
TransactDeleteItemEnhancedRequest baseRequest = TransactDeleteItemEnhancedRequest.builder().key(recordKey).build();
517-
TransactDeleteItemEnhancedRequest requestWithLocking = TransactDeleteItemEnhancedRequest.withOptimisticLocking(
518-
baseRequest, AttributeValue.builder().n(savedItem.getVersion().toString()).build(), "version");
518+
TransactDeleteItemEnhancedRequest requestWithLocking = TransactDeleteItemEnhancedRequest.builder()
519+
.key(recordKey)
520+
.withOptimisticLocking(AttributeValue.builder().n(savedItem.getVersion().toString()).build(), "version")
521+
.build();
519522

520523
enhancedClient.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()
521524
.addDeleteItem(versionedRecordTable,
@@ -534,9 +537,10 @@ public void transactDeleteItemWithHelper_versionMismatch_shouldFail() {
534537

535538
versionedRecordTable.putItem(item);
536539

537-
TransactDeleteItemEnhancedRequest baseRequest = TransactDeleteItemEnhancedRequest.builder().key(recordKey).build();
538-
TransactDeleteItemEnhancedRequest requestWithLocking = TransactDeleteItemEnhancedRequest.withOptimisticLocking(
539-
baseRequest, AttributeValue.builder().n("999").build(), "version");
540+
TransactDeleteItemEnhancedRequest requestWithLocking = TransactDeleteItemEnhancedRequest.builder()
541+
.key(recordKey)
542+
.withOptimisticLocking(AttributeValue.builder().n("999").build(), "version")
543+
.build();
540544

541545
TransactionCanceledException ex = assertThrows(TransactionCanceledException.class,
542546
() -> enhancedClient.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/DeleteItemEnhancedRequest.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.awssdk.enhanced.dynamodb.model;
1717

18+
import static software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper.createVersionCondition;
19+
1820
import java.util.Objects;
1921
import java.util.function.Consumer;
2022
import software.amazon.awssdk.annotations.NotThreadSafe;
@@ -146,13 +148,6 @@ public String returnValuesOnConditionCheckFailureAsString() {
146148
return returnValuesOnConditionCheckFailure;
147149
}
148150

149-
public static DeleteItemEnhancedRequest withOptimisticLocking(
150-
DeleteItemEnhancedRequest request,
151-
AttributeValue oldVersionValue,
152-
String versionAttributeName) {
153-
return OptimisticLockingHelper.withOptimisticLocking(request, oldVersionValue, versionAttributeName);
154-
}
155-
156151
@Override
157152
public boolean equals(Object o) {
158153
if (this == o) {
@@ -297,6 +292,17 @@ public Builder returnValuesOnConditionCheckFailure(String returnValuesOnConditio
297292
return this;
298293
}
299294

295+
/**
296+
* Adds optimistic locking condition to the delete request.
297+
*
298+
* @param versionValue the expected version value
299+
* @param versionAttributeName the name of the version attribute
300+
* @return a builder of this type
301+
*/
302+
public Builder withOptimisticLocking(AttributeValue versionValue, String versionAttributeName) {
303+
return conditionExpression(createVersionCondition(versionValue, versionAttributeName));
304+
}
305+
300306
public DeleteItemEnhancedRequest build() {
301307
return new DeleteItemEnhancedRequest(this);
302308
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/OptimisticLockingHelper.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@ private OptimisticLockingHelper() {
3030
}
3131

3232
public static DeleteItemEnhancedRequest withOptimisticLocking(
33-
DeleteItemEnhancedRequest request, AttributeValue oldVersionValue, String versionAttributeName) {
33+
DeleteItemEnhancedRequest request, AttributeValue versionValue, String versionAttributeName) {
3434

35-
Expression conditionExpression = createVersionCondition(oldVersionValue, versionAttributeName);
35+
Expression conditionExpression = createVersionCondition(versionValue, versionAttributeName);
3636
return request.toBuilder()
3737
.conditionExpression(conditionExpression)
3838
.build();
3939
}
4040

4141
public static TransactDeleteItemEnhancedRequest withOptimisticLocking(
42-
TransactDeleteItemEnhancedRequest request, AttributeValue oldVersionValue, String versionAttributeName) {
42+
TransactDeleteItemEnhancedRequest request, AttributeValue versionValue, String versionAttributeName) {
4343

44-
Expression conditionExpression = createVersionCondition(oldVersionValue, versionAttributeName);
44+
Expression conditionExpression = createVersionCondition(versionValue, versionAttributeName);
4545
return request.toBuilder()
4646
.conditionExpression(conditionExpression)
4747
.build();
@@ -65,10 +65,10 @@ public static <T> Optional<String> getVersionAttributeName(
6565
return tableSchema.tableMetadata().customMetadataObject(CUSTOM_METADATA_KEY, String.class);
6666
}
6767

68-
private static Expression createVersionCondition(AttributeValue oldVersionValue, String versionAttributeName) {
68+
public static Expression createVersionCondition(AttributeValue versionValue, String versionAttributeName) {
6969
return Expression.builder()
7070
.expression(versionAttributeName + " = :version_value")
71-
.putExpressionValue(":version_value", oldVersionValue)
71+
.putExpressionValue(":version_value", versionValue)
7272
.build();
7373
}
7474
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/TransactDeleteItemEnhancedRequest.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package software.amazon.awssdk.enhanced.dynamodb.model;
1717

18+
import static software.amazon.awssdk.enhanced.dynamodb.model.OptimisticLockingHelper.createVersionCondition;
19+
1820
import java.util.Objects;
1921
import java.util.function.Consumer;
2022
import software.amazon.awssdk.annotations.NotThreadSafe;
@@ -108,13 +110,6 @@ public String returnValuesOnConditionCheckFailureAsString() {
108110
return returnValuesOnConditionCheckFailure;
109111
}
110112

111-
public static TransactDeleteItemEnhancedRequest withOptimisticLocking(
112-
TransactDeleteItemEnhancedRequest request,
113-
AttributeValue oldVersionValue,
114-
String versionAttributeName) {
115-
return OptimisticLockingHelper.withOptimisticLocking(request, oldVersionValue, versionAttributeName);
116-
}
117-
118113
@Override
119114
public boolean equals(Object o) {
120115
if (this == o) {
@@ -223,6 +218,16 @@ public Builder returnValuesOnConditionCheckFailure(String returnValuesOnConditio
223218
return this;
224219
}
225220

221+
/**
222+
* Adds optimistic locking condition to the delete request.
223+
*
224+
* @param versionValue the expected version value
225+
* @param versionAttributeName the name of the version attribute
226+
* @return a builder of this type
227+
*/
228+
public Builder withOptimisticLocking(AttributeValue versionValue, String versionAttributeName) {
229+
return conditionExpression(createVersionCondition(versionValue, versionAttributeName));
230+
}
226231

227232
public TransactDeleteItemEnhancedRequest build() {
228233
return new TransactDeleteItemEnhancedRequest(this);

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/model/OptimisticLockingHelperTest.java

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,34 @@
2424
public class OptimisticLockingHelperTest {
2525

2626
@Test
27-
public void withOptimisticLocking_deleteItemEnhancedRequest_shouldAddCondition() {
27+
public void builderWithOptimisticLocking_deleteItemEnhancedRequest_shouldAddCondition() {
2828
Key key = Key.builder().partitionValue("test-id").build();
29-
DeleteItemEnhancedRequest originalRequest = DeleteItemEnhancedRequest.builder()
30-
.key(key)
31-
.build();
32-
3329
AttributeValue versionValue = AttributeValue.builder().n("5").build();
3430
String versionAttributeName = "version";
3531

36-
DeleteItemEnhancedRequest result = DeleteItemEnhancedRequest.withOptimisticLocking(
37-
originalRequest, versionValue, versionAttributeName);
32+
DeleteItemEnhancedRequest result = DeleteItemEnhancedRequest.builder()
33+
.key(key)
34+
.withOptimisticLocking(versionValue, versionAttributeName)
35+
.build();
3836

3937
assertThat(result.key()).isEqualTo(key);
4038
assertThat(result.conditionExpression()).isNotNull();
4139
assertThat(result.conditionExpression().expression()).isEqualTo("version = :version_value");
4240
assertThat(result.conditionExpression().expressionValues()).containsEntry(":version_value", versionValue);
4341
}
4442

43+
44+
4545
@Test
46-
public void withOptimisticLocking_transactDeleteItemEnhancedRequest_shouldAddCondition() {
46+
public void builderWithOptimisticLocking_transactDeleteItemEnhancedRequest_shouldAddCondition() {
4747
Key key = Key.builder().partitionValue("test-id").build();
48-
TransactDeleteItemEnhancedRequest originalRequest = TransactDeleteItemEnhancedRequest.builder()
49-
.key(key)
50-
.build();
51-
5248
AttributeValue versionValue = AttributeValue.builder().n("10").build();
5349
String versionAttributeName = "recordVersion";
5450

55-
TransactDeleteItemEnhancedRequest result = TransactDeleteItemEnhancedRequest.withOptimisticLocking(
56-
originalRequest, versionValue, versionAttributeName);
51+
TransactDeleteItemEnhancedRequest result = TransactDeleteItemEnhancedRequest.builder()
52+
.key(key)
53+
.withOptimisticLocking(versionValue, versionAttributeName)
54+
.build();
5755

5856
assertThat(result.key()).isEqualTo(key);
5957
assertThat(result.conditionExpression()).isNotNull();
@@ -62,45 +60,43 @@ public void withOptimisticLocking_transactDeleteItemEnhancedRequest_shouldAddCon
6260
}
6361

6462
@Test
65-
public void withOptimisticLocking_preservesExistingRequestProperties() {
63+
public void builderWithOptimisticLocking_preservesExistingRequestProperties() {
6664
Key key = Key.builder().partitionValue("test-id").build();
67-
DeleteItemEnhancedRequest originalRequest = DeleteItemEnhancedRequest.builder()
68-
.key(key)
69-
.returnConsumedCapacity("TOTAL")
70-
.build();
71-
7265
AttributeValue versionValue = AttributeValue.builder().n("3").build();
7366

74-
DeleteItemEnhancedRequest result = DeleteItemEnhancedRequest.withOptimisticLocking(
75-
originalRequest, versionValue, "version");
67+
DeleteItemEnhancedRequest result = DeleteItemEnhancedRequest.builder()
68+
.key(key)
69+
.returnConsumedCapacity("TOTAL")
70+
.withOptimisticLocking(versionValue, "version")
71+
.build();
7672

7773
assertThat(result.key()).isEqualTo(key);
7874
assertThat(result.returnConsumedCapacityAsString()).isEqualTo("TOTAL");
7975
assertThat(result.conditionExpression()).isNotNull();
8076
}
8177

8278
@Test
83-
public void withOptimisticLocking_differentVersionAttributeNames_shouldWork() {
79+
public void builderWithOptimisticLocking_differentVersionAttributeNames_shouldWork() {
8480
Key key = Key.builder().partitionValue("test-id").build();
85-
DeleteItemEnhancedRequest originalRequest = DeleteItemEnhancedRequest.builder().key(key).build();
8681
AttributeValue versionValue = AttributeValue.builder().n("1").build();
8782

8883
// Test with different attribute names
8984
String[] attributeNames = {"version", "recordVersion", "itemVersion", "v"};
9085

9186
for (String attributeName : attributeNames) {
92-
DeleteItemEnhancedRequest result = DeleteItemEnhancedRequest.withOptimisticLocking(
93-
originalRequest, versionValue, attributeName);
87+
DeleteItemEnhancedRequest result = DeleteItemEnhancedRequest.builder()
88+
.key(key)
89+
.withOptimisticLocking(versionValue, attributeName)
90+
.build();
9491

9592
assertThat(result.conditionExpression().expression()).isEqualTo(attributeName + " = :version_value");
9693
assertThat(result.conditionExpression().expressionValues()).containsEntry(":version_value", versionValue);
9794
}
9895
}
9996

10097
@Test
101-
public void withOptimisticLocking_differentVersionValues_shouldWork() {
98+
public void builderWithOptimisticLocking_differentVersionValues_shouldWork() {
10299
Key key = Key.builder().partitionValue("test-id").build();
103-
DeleteItemEnhancedRequest originalRequest = DeleteItemEnhancedRequest.builder().key(key).build();
104100

105101
// Test with different version values
106102
AttributeValue[] versionValues = {
@@ -111,8 +107,10 @@ public void withOptimisticLocking_differentVersionValues_shouldWork() {
111107
};
112108

113109
for (AttributeValue versionValue : versionValues) {
114-
DeleteItemEnhancedRequest result = DeleteItemEnhancedRequest.withOptimisticLocking(
115-
originalRequest, versionValue, "version");
110+
DeleteItemEnhancedRequest result = DeleteItemEnhancedRequest.builder()
111+
.key(key)
112+
.withOptimisticLocking(versionValue, "version")
113+
.build();
116114

117115
assertThat(result.conditionExpression().expressionValues()).containsEntry(":version_value", versionValue);
118116
}

0 commit comments

Comments
 (0)