@DynamoDbVersionAttribute Annotation Does not work if used in conjunction with @DynamoDbSortKey for subsequent PUT operations.
- Dominant language
- Java
- Stars
- 2.6k
- Forks
- 1k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 51
Description
### Describe the bug
If you use the `@DynamoDbVersionAttribute` in conjunction with `@SortKey`, the initial `PutItem` request will work, and correctly store the item with `1` for Version, but subsequent `PutItems` will not auto increment to Version `2` (Note, it does work with `UpdateItem` if this is used on a normal attribute that's not a `@SortKey/@PrimaryKey`, like the `SomeLatestItem` table below)
Put Item Fails on a condition Check from this [VersionedRecordExtension](https://github.com/aws/aws-sdk-java-v2/blob/master/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java) whether or not the version passed exists(and is `== current version`) or does not exist (and is `== current version + 1` )
```java
@Value
@Builder
@AllArgsConstructor
@DynamoDbImmutable(builder = SomeVersionsItem.SomeVersionsItemBuilder.class)
public class SomeVersionsItem {
@Getter(onMethod_ = {@DynamoDbPartitionKey, @DynamoDbAttribute("Id")})
private String id;
// DDbVersionAttribute must be type (int, long)
@Getter(onMethod_ = {@DynamoDbSortKey, @DynamoDbVersionAttribute, @DynamoDbAttribute("Version")})
private Long version;
// Non PK/SK Additional fields
@Getter(onMethod_ = {@DynamoDbAttribute("SomeAttribute")})
private String someAttribute;
}
@Value
@Builder
@AllArgsConstructor
@DynamoDbImmutable(builder = SomeLatestItem.SomeLatestItemBuilder.class)
public class SomeLatestItem {
@Getter(onMethod_ = {@DynamoDbPartitionKey, @DynamoDbAttribute("Id")})
private String id;
// DDbVersionAttribute must be type (int, long)
@With(onMethod_ = {@DynamoDbIgnore})
@Getter(onMethod_ = {@DynamoDbVersionAttribute, @DynamoDbAttribute("Version")})
private Long version;
}
```
### Expected Behavior
To bump the version to N+1, and write the new item.
For an update item request when using the @DynamoDbVersionAttribute on a table where Version is not the sort key, it expects you to pass the current version and it auto increments for you.
With the current implementation, and using the ddb enhanced client, you can not write subsequent items to the table. This forces the user to now handle their own versioning logic, and remove the annotation. This is not called out in any docs, and if anything, many of the AWS articles on versioning lead you to make this mistake.
### Current Behavior
```
ERROR TransmutingContinuationHandler:157 - Internal Failure
software.amazon.awssdk.services.dynamodb.model.TransactionCanceledException: Transaction cancelled, please refer cancellation reasons for specific reasons [ConditionalCheckFailed, None] (Service: DynamoDb, Status Code: 400, Request ID: KNKSB2C6NH1T4U57EF26P6C2MNVV4KQNSO5AEMVJF66Q9ASUAAJG)
at software.amazon.awssdk.core.internal.http.CombinedResponseHandler.handleErrorResponse(CombinedResponseHandler.java:125) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.CombinedResponseHandler.handleResponse(CombinedResponseHandler.java:82) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.CombinedResponseHandler.handle(CombinedResponseHandler.java:60) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.CombinedResponseHandler.handle(CombinedResponseHandler.java:41) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.HandleResponseStage.execute(HandleResponseStage.java:50) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.HandleResponseStage.execute(HandleResponseStage.java:38) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptTimeoutTrackingStage.execute(ApiCallAttemptTimeoutTrackingStage.java:72) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptTimeoutTrackingStage.execute(ApiCallAttemptTimeoutTrackingStage.java:42) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage.execute(TimeoutExceptionHandlingStage.java:78) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage.execute(TimeoutExceptionHandlingStage.java:40) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptMetricCollectionStage.execute(ApiCallAttemptMetricCollectionStage.java:55) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptMetricCollectionStage.execute(ApiCallAttemptMetricCollectionStage.java:39) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:81) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage.execute(RetryableStage.java:36) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:56) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.StreamManagingStage.execute(StreamManagingStage.java:36) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.executeWithTimer(ApiCallTimeoutTrackingStage.java:80) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:60) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage.execute(ApiCallTimeoutTrackingStage.java:42) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:50) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage.execute(ApiCallMetricCollectionStage.java:32) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder$ComposingRequestPipelineStage.execute(RequestPipelineBuilder.java:206) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:37) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.pipeline.stages.ExecutionFailureExceptionReportingStage.execute(ExecutionFailureExceptionReportingStage.java:26) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient$RequestExecutionBuilderImpl.execute(AmazonSyncHttpClient.java:224) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.invoke(BaseSyncClientHandler.java:103) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.doExecute(BaseSyncClientHandler.java:173) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.lambda$execute$1(BaseSyncClientHandler.java:80) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.measureApiCallSuccess(BaseSyncClientHandler.java:182) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.internal.handler.BaseSyncClientHandler.execute(BaseSyncClientHandler.java:74) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.core.client.handler.SdkSyncClientHandler.execute(SdkSyncClientHandler.java:45) ~[AwsJavaSdk-Core-2.0.jar:?]
at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler.execute(AwsSyncClientHandler.java:53) ~[AwsJavaSdk-Core-AwsCore-2.0.jar:?]
at software.amazon.awssdk.services.dynamodb.DefaultDynamoDbClient.transactWriteItems(DefaultDynamoDbClient.java:6042) ~[AwsJavaSdk-DynamoDb-2.0.jar:?]
at software.amazon.awssdk.enhanced.dynamodb.internal.operations.DatabaseOperation.execute(DatabaseOperation.java:82) ~[AwsJavaSdk-DynamoDb-Enhanced-2.0.jar:?]
at software.amazon.awssdk.enhanced.dynamodb.internal.client.DefaultDynamoDbEnhancedClient.transactWriteItems(DefaultDynamoDbEnhancedClient.java:103) ~[AwsJavaSdk-DynamoDb-Enhanced-2.0.jar:?]
```
### Reproduction Steps
```java
public class DynamoDbDAO {
private final DynamoDbTable enhancedSomeLatestTable;
private final DynamoDbTable enhancedSomeVersionsTable;
private final DynamoDbEnhancedClient dynamoDbEnhancedClient;
...
public SomeLatestItem edit() {
// before edit is called, currentVersion = 1 and item "1234" exists.
final SomeLatestItem item = SomeLatestItem.builder().id("1234").version(currentVersion).build();
// SomeVersionsItem write fails for both currentVersion passed (Version = 1)
// also fails for current version + 1 passed (Version = 2)
// since i want a new item, I can't call addUpdateItem for this table.
final SomeVersionsItem item = SomeVersionsItem.builder().id("1234").version(currentVersion).build();
final TransactPutItemEnhancedRequest putRequestVersionItem =
TransactPutItemEnhancedRequest.builder(SomeVersionsItem.class)
.item(updatedVersionItemToWrite)
.build();
final TransactUpdateItemEnhancedRequest updateRequestLatestItem =
TransactUpdateItemEnhancedRequest.builder(SomeLatestItem.class)
.item(updatedLatestItem)
.ignoreNulls(true)
.build();
final TransactWriteItemsEnhancedRequest transactReq = TransactWriteItemsEnhancedRequest.builder()
.addPutItem(enhancedSomeVersionsTable, putRequestVersionItem)
.addUpdateItem(enhancedSomeLatestTable, updateRequestLatestItem)
.build();
dynamoDbEnhancedClient.transactWriteItems(transactReq);
return SomeLatestItem.builder().id("1234").build();
}
```
### Possible Solution
_No response_
### Additional Information/Context
_No response_
### AWS Java SDK version used
aws-sdk-java-v2
### JDK version used
JDK21
### Operating System and version
Amazon Linux 2023 (Lambda JDK 21)
Contributor guide
Assessment
This issue has not been assessed yet.