aws / aws/aws-sdk-java-v2

@DynamoDbVersionAttribute Annotation Does not work if used in conjunction with @DynamoDbSortKey for subsequent PUT operations.

Open
#5,316 1 comment 0 reactions 0 assignees View on GitHub
bug dynamodb-enhanced p2
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.