aws / aws/aws-sdk-java-v2

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

Aperta
#5,316 1 commento 0 reazioni 0 assegnatari Vedi su GitHub
bug dynamodb-enhanced p2
Lingua principale
Java
Stelle
2.6k
Fork
1k
Merge medio
2g 9h
PR unite (30g)
51

Descrizione

### 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)

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Inizia con VersionedRecordExtension e con il punto di ingresso DynamoDbEnhancedClient.transactWriteItems mostrato nella riproduzione. Riproduci il PutItem transazionale usando un attributo di versione nella chiave di ordinamento, quindi traccia la gestione condizionale della versione. Il lavoro è completato quando le operazioni PutItem successive accettano l'elemento esistente e scrivono la versione successiva senza l'errore ConditionalCheckFailed.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
aws, java
Ambito
databases
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
45/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.