Skip to content

Commit 841d706

Browse files
committed
Optimistic locking for delete scenario with TransactWriteItemsEnhancedRequest
1 parent 4bf8e38 commit 841d706

File tree

15 files changed

+824
-6
lines changed

15 files changed

+824
-6
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "Amazon DynamoDB Enhanced Client",
4+
"contributor": "",
5+
"description": "Optimistic locking while using DynamoDbEnhancedClient - DeleteItem with TransactWriteItemsEnhancedRequest"
6+
}

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

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

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

18-
import static org.assertj.core.api.Assertions.as;
1918
import static org.assertj.core.api.Assertions.assertThat;
2019
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2120

@@ -31,6 +30,8 @@
3130
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest;
3231
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedResponse;
3332
import software.amazon.awssdk.enhanced.dynamodb.model.Record;
33+
import software.amazon.awssdk.enhanced.dynamodb.model.RecordWithVersion;
34+
import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
3435
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest;
3536
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedResponse;
3637
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
@@ -41,6 +42,7 @@
4142
import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics;
4243
import software.amazon.awssdk.services.dynamodb.model.ReturnValue;
4344
import software.amazon.awssdk.services.dynamodb.model.ReturnValuesOnConditionCheckFailure;
45+
import software.amazon.awssdk.services.dynamodb.model.TransactionCanceledException;
4446

4547
public class AsyncCrudWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase {
4648

@@ -56,13 +58,15 @@ public class AsyncCrudWithResponseIntegrationTest extends DynamoDbEnhancedIntegr
5658
private static DynamoDbAsyncClient dynamoDbClient;
5759
private static DynamoDbEnhancedAsyncClient enhancedClient;
5860
private static DynamoDbAsyncTable<Record> mappedTable;
61+
private static DynamoDbAsyncTable<RecordWithVersion> recordWithVersionMappedTable;
5962

6063
@BeforeClass
6164
public static void beforeClass() {
6265
dynamoDbClient = createAsyncDynamoDbClient();
6366
enhancedClient = DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(dynamoDbClient).build();
6467
mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA);
6568
mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)).join();
69+
recordWithVersionMappedTable = enhancedClient.table(TABLE_NAME, RECORD_WITH_VERSION_TABLE_SCHEMA);
6670
dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)).join();
6771
}
6872

@@ -341,4 +345,164 @@ public void getItem_withoutReturnConsumedCapacity() {
341345
GetItemEnhancedResponse<Record> response = mappedTable.getItemWithResponse(req -> req.key(key)).join();
342346
assertThat(response.consumedCapacity()).isNull();
343347
}
348+
349+
@Test
350+
public void deleteItemWithoutVersion_andOptimisticLockingEnabled_shouldSucceed() {
351+
Record originalItem = new Record().setId("123").setSort(10).setStringAttribute("Original Item");
352+
Key recordKey = Key.builder()
353+
.partitionValue(originalItem.getId())
354+
.sortValue(originalItem.getSort())
355+
.build();
356+
mappedTable.putItem(originalItem).join();
357+
358+
// Retrieve the item
359+
Record retrievedItem = mappedTable.getItem(r -> r.key(recordKey)).join();
360+
361+
// Delete the item using a transaction
362+
TransactWriteItemsEnhancedRequest request =
363+
TransactWriteItemsEnhancedRequest.builder()
364+
.addDeleteItem(mappedTable, retrievedItem, true)
365+
.build();
366+
367+
enhancedClient.transactWriteItems(request).join();
368+
369+
Record deletedItem = mappedTable.getItem(r -> r.key(recordKey)).join();
370+
assertThat(deletedItem).isNull();
371+
}
372+
373+
@Test
374+
public void deleteItemWithoutVersion_andOptimisticLockingDisabled_shouldSucceed() {
375+
Record originalItem = new Record().setId("123").setSort(10).setStringAttribute("Original Item");
376+
Key recordKey = Key.builder()
377+
.partitionValue(originalItem.getId())
378+
.sortValue(originalItem.getSort())
379+
.build();
380+
mappedTable.putItem(originalItem).join();
381+
382+
// Retrieve the item
383+
Record retrievedItem = mappedTable.getItem(r -> r.key(recordKey)).join();
384+
385+
// Delete the item using a transaction
386+
TransactWriteItemsEnhancedRequest request =
387+
TransactWriteItemsEnhancedRequest.builder()
388+
.addDeleteItem(mappedTable, retrievedItem, false)
389+
.build();
390+
391+
enhancedClient.transactWriteItems(request).join();
392+
393+
Record deletedItem = mappedTable.getItem(r -> r.key(recordKey)).join();
394+
assertThat(deletedItem).isNull();
395+
}
396+
397+
@Test
398+
public void deleteItemWithVersion_andOptimisticLockingEnabled_ifVersionMatch_shouldSucceed() {
399+
RecordWithVersion originalItem = new RecordWithVersion().setId("123").setSort(10).setStringAttribute("Original Item");
400+
Key recordKey = Key.builder()
401+
.partitionValue(originalItem.getId())
402+
.sortValue(originalItem.getSort())
403+
.build();
404+
recordWithVersionMappedTable.putItem(originalItem).join();
405+
406+
// Retrieve the item
407+
RecordWithVersion retrievedItem = recordWithVersionMappedTable.getItem(r -> r.key(recordKey)).join();
408+
409+
// Delete the item using a transaction
410+
TransactWriteItemsEnhancedRequest request =
411+
TransactWriteItemsEnhancedRequest.builder()
412+
.addDeleteItem(recordWithVersionMappedTable, retrievedItem, true)
413+
.build();
414+
415+
enhancedClient.transactWriteItems(request).join();
416+
417+
RecordWithVersion deletedItem = recordWithVersionMappedTable.getItem(r -> r.key(recordKey)).join();
418+
assertThat(deletedItem).isNull();
419+
}
420+
421+
@Test
422+
public void deleteItemWithVersion_andOptimisticLockingEnabled_ifVersionMismatch_shouldFail() {
423+
RecordWithVersion originalItem = new RecordWithVersion().setId("123").setSort(10).setStringAttribute("Original Item");
424+
Key recordKey = Key.builder()
425+
.partitionValue(originalItem.getId())
426+
.sortValue(originalItem.getSort())
427+
.build();
428+
429+
recordWithVersionMappedTable.putItem(originalItem).join();
430+
431+
// Retrieve the item and modify it separately
432+
RecordWithVersion modifiedItem = recordWithVersionMappedTable.getItem(r -> r.key(recordKey)).join();
433+
modifiedItem.setStringAttribute("Updated Item");
434+
435+
// Update the item, which will increment the version
436+
recordWithVersionMappedTable.updateItem(modifiedItem);
437+
438+
// Now attempt to delete the original item using a transaction
439+
TransactWriteItemsEnhancedRequest request =
440+
TransactWriteItemsEnhancedRequest.builder()
441+
.addDeleteItem(recordWithVersionMappedTable, modifiedItem, true)
442+
.build();
443+
444+
assertThatThrownBy(() -> enhancedClient.transactWriteItems(request).join())
445+
.isInstanceOf(CompletionException.class)
446+
.satisfies(e ->
447+
assertThat(((TransactionCanceledException) e.getCause())
448+
.cancellationReasons()
449+
.stream()
450+
.anyMatch(reason ->
451+
"ConditionalCheckFailed".equals(reason.code())
452+
&& "The conditional request failed".equals(reason.message())))
453+
.isTrue());
454+
}
455+
456+
@Test
457+
public void deleteItemWithVersion_andOptimisticLockingDisabled_ifVersionMatch_shouldSucceed() {
458+
RecordWithVersion originalItem = new RecordWithVersion().setId("123").setSort(10).setStringAttribute("Original Item");
459+
Key recordKey = Key.builder()
460+
.partitionValue(originalItem.getId())
461+
.sortValue(originalItem.getSort())
462+
.build();
463+
recordWithVersionMappedTable.putItem(originalItem).join();
464+
465+
// Retrieve the item
466+
RecordWithVersion retrievedItem = recordWithVersionMappedTable.getItem(r -> r.key(recordKey)).join();
467+
468+
// Delete the item using a transaction
469+
TransactWriteItemsEnhancedRequest request =
470+
TransactWriteItemsEnhancedRequest.builder()
471+
.addDeleteItem(recordWithVersionMappedTable, retrievedItem, false)
472+
.build();
473+
474+
enhancedClient.transactWriteItems(request).join();
475+
476+
RecordWithVersion deletedItem = recordWithVersionMappedTable.getItem(r -> r.key(recordKey)).join();
477+
assertThat(deletedItem).isNull();
478+
}
479+
480+
@Test
481+
public void deleteItemWithVersion_andOptimisticLockingDisabled_ifVersionMismatch_shouldSucceed() {
482+
RecordWithVersion originalItem = new RecordWithVersion().setId("123").setSort(10).setStringAttribute("Original Item");
483+
Key recordKey = Key.builder()
484+
.partitionValue(originalItem.getId())
485+
.sortValue(originalItem.getSort())
486+
.build();
487+
488+
recordWithVersionMappedTable.putItem(originalItem).join();
489+
490+
// Retrieve the item and modify it separately
491+
RecordWithVersion modifiedItem = recordWithVersionMappedTable.getItem(r -> r.key(recordKey)).join();
492+
modifiedItem.setStringAttribute("Updated Item");
493+
494+
// Update the item, which will increment the version
495+
recordWithVersionMappedTable.updateItem(modifiedItem);
496+
497+
// Now attempt to delete the original item using a transaction
498+
TransactWriteItemsEnhancedRequest request =
499+
TransactWriteItemsEnhancedRequest.builder()
500+
.addDeleteItem(recordWithVersionMappedTable, modifiedItem, false)
501+
.build();
502+
503+
enhancedClient.transactWriteItems(request).join();
504+
505+
RecordWithVersion deletedItem = recordWithVersionMappedTable.getItem(r -> r.key(recordKey)).join();
506+
assertThat(deletedItem).isNull();
507+
}
344508
}

0 commit comments

Comments
 (0)