Skip to content

Commit 023ebb4

Browse files
authored
Implement support for ReturnValuesOnConditionCheckFailure (#2688)
* Implement return values on TransactWriteItems This change gives customer the ability to specify ReturnValuesOnConditionCheckFailure on the TransactWriteItems operation, i.e. for PutItem, DeleteItem, UpdateItem, and ConditionCheck. In order to support this feature, new Transact*EnhancedRequest objects have been introduced because ReturnValuesOnConditionCheckFailure does not make any sense for the non-TransactWrite operations. Conversely, other options on the non-transact versions like ReturnConsumedCapacity do not make sense for the Transact* versions. As a result of "splitting" off the TransactWrite versions of the operations, the methods on TransactWriteItemsEnhancedRequest that take the non-transact versions have been deprecated. Fixes #2283 * Review comments
1 parent 985ef39 commit 023ebb4

19 files changed

+2113
-105
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "DynamoDB Enhanced Client",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Allow customers to specify `ReturnValuesOnConditionCheckFailure` for `TransactWriteItems`. Addresses [#2283](https://github.com/aws/aws-sdk-java-v2/issues/2283)."
6+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/DeleteItemOperation.java

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@
1616
package software.amazon.awssdk.enhanced.dynamodb.internal.operations;
1717

1818
import java.util.Map;
19+
import java.util.Optional;
1920
import java.util.concurrent.CompletableFuture;
2021
import java.util.function.Function;
2122
import software.amazon.awssdk.annotations.SdkInternalApi;
2223
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
24+
import software.amazon.awssdk.enhanced.dynamodb.Expression;
25+
import software.amazon.awssdk.enhanced.dynamodb.Key;
2326
import software.amazon.awssdk.enhanced.dynamodb.OperationContext;
2427
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
2528
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
2629
import software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils;
2730
import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest;
31+
import software.amazon.awssdk.enhanced.dynamodb.model.TransactDeleteItemEnhancedRequest;
2832
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
2933
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
3034
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
@@ -35,23 +39,32 @@
3539
import software.amazon.awssdk.services.dynamodb.model.ReturnValue;
3640
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItem;
3741
import software.amazon.awssdk.services.dynamodb.model.WriteRequest;
42+
import software.amazon.awssdk.utils.Either;
3843

3944
@SdkInternalApi
4045
public class DeleteItemOperation<T>
4146
implements TableOperation<T, DeleteItemRequest, DeleteItemResponse, T>,
4247
TransactableWriteOperation<T>,
4348
BatchableWriteOperation<T> {
4449

45-
private final DeleteItemEnhancedRequest request;
50+
private final Either<DeleteItemEnhancedRequest, TransactDeleteItemEnhancedRequest> request;
4651

4752
private DeleteItemOperation(DeleteItemEnhancedRequest request) {
48-
this.request = request;
53+
this.request = Either.left(request);
54+
}
55+
56+
private DeleteItemOperation(TransactDeleteItemEnhancedRequest request) {
57+
this.request = Either.right(request);
4958
}
5059

5160
public static <T> DeleteItemOperation<T> create(DeleteItemEnhancedRequest request) {
5261
return new DeleteItemOperation<>(request);
5362
}
5463

64+
public static <T> DeleteItemOperation<T> create(TransactDeleteItemEnhancedRequest request) {
65+
return new DeleteItemOperation<>(request);
66+
}
67+
5568
@Override
5669
public DeleteItemRequest generateRequest(TableSchema<T> tableSchema,
5770
OperationContext operationContext,
@@ -61,10 +74,12 @@ public DeleteItemRequest generateRequest(TableSchema<T> tableSchema,
6174
throw new IllegalArgumentException("DeleteItem cannot be executed against a secondary index.");
6275
}
6376

77+
Key key = request.map(DeleteItemEnhancedRequest::key, TransactDeleteItemEnhancedRequest::key);
78+
6479
DeleteItemRequest.Builder requestBuilder =
6580
DeleteItemRequest.builder()
6681
.tableName(operationContext.tableName())
67-
.key(this.request.key().keyMap(tableSchema, operationContext.indexName()))
82+
.key(key.keyMap(tableSchema, operationContext.indexName()))
6883
.returnValues(ReturnValue.ALL_OLD);
6984

7085
requestBuilder = addExpressionsIfExist(requestBuilder);
@@ -109,24 +124,31 @@ public TransactWriteItem generateTransactWriteItem(TableSchema<T> tableSchema,
109124
DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension) {
110125
DeleteItemRequest deleteItemRequest = generateRequest(tableSchema, operationContext, dynamoDbEnhancedClientExtension);
111126

112-
Delete delete = Delete.builder()
113-
.key(deleteItemRequest.key())
114-
.tableName(deleteItemRequest.tableName())
115-
.conditionExpression(deleteItemRequest.conditionExpression())
116-
.expressionAttributeValues(deleteItemRequest.expressionAttributeValues())
117-
.expressionAttributeNames(deleteItemRequest.expressionAttributeNames())
118-
.build();
127+
Delete.Builder builder = Delete.builder()
128+
.key(deleteItemRequest.key())
129+
.tableName(deleteItemRequest.tableName())
130+
.conditionExpression(deleteItemRequest.conditionExpression())
131+
.expressionAttributeValues(deleteItemRequest.expressionAttributeValues())
132+
.expressionAttributeNames(deleteItemRequest.expressionAttributeNames());
133+
134+
request.right()
135+
.map(TransactDeleteItemEnhancedRequest::returnValuesOnConditionCheckFailureAsString)
136+
.ifPresent(builder::returnValuesOnConditionCheckFailure);
119137

120138
return TransactWriteItem.builder()
121-
.delete(delete)
139+
.delete(builder.build())
122140
.build();
123141
}
124142

125143
private DeleteItemRequest.Builder addExpressionsIfExist(DeleteItemRequest.Builder requestBuilder) {
126-
if (this.request.conditionExpression() != null) {
127-
requestBuilder = requestBuilder.conditionExpression(this.request.conditionExpression().expression());
128-
Map<String, String> expressionNames = this.request.conditionExpression().expressionNames();
129-
Map<String, AttributeValue> expressionValues = this.request.conditionExpression().expressionValues();
144+
Expression conditionExpression = request.map(r -> Optional.ofNullable(r.conditionExpression()),
145+
r -> Optional.ofNullable(r.conditionExpression()))
146+
.orElse(null);
147+
148+
if (conditionExpression != null) {
149+
requestBuilder = requestBuilder.conditionExpression(conditionExpression.expression());
150+
Map<String, String> expressionNames = conditionExpression.expressionNames();
151+
Map<String, AttributeValue> expressionValues = conditionExpression.expressionValues();
130152

131153
// Avoiding adding empty collections that the low level SDK will propagate to DynamoDb where it causes error.
132154
if (expressionNames != null && !expressionNames.isEmpty()) {

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/PutItemOperation.java

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.enhanced.dynamodb.internal.operations;
1717

1818
import java.util.Map;
19+
import java.util.Optional;
1920
import java.util.concurrent.CompletableFuture;
2021
import java.util.function.Function;
2122
import software.amazon.awssdk.annotations.SdkInternalApi;
@@ -27,6 +28,7 @@
2728
import software.amazon.awssdk.enhanced.dynamodb.extensions.WriteModification;
2829
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
2930
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest;
31+
import software.amazon.awssdk.enhanced.dynamodb.model.TransactPutItemEnhancedRequest;
3032
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
3133
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
3234
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
@@ -36,23 +38,32 @@
3638
import software.amazon.awssdk.services.dynamodb.model.PutRequest;
3739
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItem;
3840
import software.amazon.awssdk.services.dynamodb.model.WriteRequest;
41+
import software.amazon.awssdk.utils.Either;
3942

4043
@SdkInternalApi
4144
public class PutItemOperation<T>
4245
implements BatchableWriteOperation<T>,
4346
TransactableWriteOperation<T>,
4447
TableOperation<T, PutItemRequest, PutItemResponse, Void> {
4548

46-
private final PutItemEnhancedRequest<T> request;
49+
private final Either<PutItemEnhancedRequest<T>, TransactPutItemEnhancedRequest<T>> request;
4750

4851
private PutItemOperation(PutItemEnhancedRequest<T> request) {
49-
this.request = request;
52+
this.request = Either.left(request);
53+
}
54+
55+
private PutItemOperation(TransactPutItemEnhancedRequest<T> request) {
56+
this.request = Either.right(request);
5057
}
5158

5259
public static <T> PutItemOperation<T> create(PutItemEnhancedRequest<T> request) {
5360
return new PutItemOperation<>(request);
5461
}
5562

63+
public static <T> PutItemOperation<T> create(TransactPutItemEnhancedRequest<T> request) {
64+
return new PutItemOperation<>(request);
65+
}
66+
5667
@Override
5768
public PutItemRequest generateRequest(TableSchema<T> tableSchema,
5869
OperationContext operationContext,
@@ -68,7 +79,8 @@ public PutItemRequest generateRequest(TableSchema<T> tableSchema,
6879
tableMetadata.primaryPartitionKey();
6980

7081
boolean alwaysIgnoreNulls = true;
71-
Map<String, AttributeValue> itemMap = tableSchema.itemToMap(this.request.item(), alwaysIgnoreNulls);
82+
T item = request.map(PutItemEnhancedRequest::item, TransactPutItemEnhancedRequest::item);
83+
Map<String, AttributeValue> itemMap = tableSchema.itemToMap(item, alwaysIgnoreNulls);
7284

7385
WriteModification transformation =
7486
extension != null ? extension.beforeWrite(
@@ -138,28 +150,35 @@ public TransactWriteItem generateTransactWriteItem(TableSchema<T> tableSchema,
138150
DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension) {
139151
PutItemRequest putItemRequest = generateRequest(tableSchema, operationContext, dynamoDbEnhancedClientExtension);
140152

141-
Put put = Put.builder()
142-
.item(putItemRequest.item())
143-
.tableName(putItemRequest.tableName())
144-
.conditionExpression(putItemRequest.conditionExpression())
145-
.expressionAttributeValues(putItemRequest.expressionAttributeValues())
146-
.expressionAttributeNames(putItemRequest.expressionAttributeNames())
147-
.build();
153+
Put.Builder builder = Put.builder()
154+
.item(putItemRequest.item())
155+
.tableName(putItemRequest.tableName())
156+
.conditionExpression(putItemRequest.conditionExpression())
157+
.expressionAttributeValues(putItemRequest.expressionAttributeValues())
158+
.expressionAttributeNames(putItemRequest.expressionAttributeNames());
159+
160+
request.right()
161+
.map(TransactPutItemEnhancedRequest::returnValuesOnConditionCheckFailureAsString)
162+
.ifPresent(builder::returnValuesOnConditionCheckFailure);
148163

149164
return TransactWriteItem.builder()
150-
.put(put)
165+
.put(builder.build())
151166
.build();
152167
}
153168

154169
private PutItemRequest.Builder addExpressionsIfExist(PutItemRequest.Builder requestBuilder,
155170
WriteModification transformation) {
171+
172+
Expression originalConditionExpression = request.map(r -> Optional.ofNullable(r.conditionExpression()),
173+
r -> Optional.ofNullable(r.conditionExpression()))
174+
.orElse(null);
156175
Expression mergedConditionExpression;
157176

158177
if (transformation != null && transformation.additionalConditionalExpression() != null) {
159-
mergedConditionExpression = Expression.join(this.request.conditionExpression(),
178+
mergedConditionExpression = Expression.join(originalConditionExpression,
160179
transformation.additionalConditionalExpression(), " AND ");
161180
} else {
162-
mergedConditionExpression = this.request.conditionExpression();
181+
mergedConditionExpression = originalConditionExpression;
163182
}
164183

165184
if (mergedConditionExpression != null) {
@@ -177,5 +196,4 @@ private PutItemRequest.Builder addExpressionsIfExist(PutItemRequest.Builder requ
177196
}
178197
return requestBuilder;
179198
}
180-
181199
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/UpdateItemOperation.java

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Collections;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Optional;
2627
import java.util.concurrent.CompletableFuture;
2728
import java.util.function.Function;
2829
import java.util.stream.Collectors;
@@ -37,6 +38,7 @@
3738
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
3839
import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.UpdateBehaviorTag;
3940
import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
41+
import software.amazon.awssdk.enhanced.dynamodb.model.TransactUpdateItemEnhancedRequest;
4042
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest;
4143
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
4244
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@@ -46,6 +48,7 @@
4648
import software.amazon.awssdk.services.dynamodb.model.Update;
4749
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
4850
import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse;
51+
import software.amazon.awssdk.utils.Either;
4952

5053
@SdkInternalApi
5154
public class UpdateItemOperation<T>
@@ -62,16 +65,24 @@ public class UpdateItemOperation<T>
6265
key -> "if_not_exists(" + EXPRESSION_KEY_MAPPER.apply(key) + ", " +
6366
EXPRESSION_VALUE_KEY_MAPPER.apply(key) + ")";
6467

65-
private final UpdateItemEnhancedRequest<T> request;
68+
private final Either<UpdateItemEnhancedRequest<T>, TransactUpdateItemEnhancedRequest<T>> request;
6669

6770
private UpdateItemOperation(UpdateItemEnhancedRequest<T> request) {
68-
this.request = request;
71+
this.request = Either.left(request);
72+
}
73+
74+
private UpdateItemOperation(TransactUpdateItemEnhancedRequest<T> request) {
75+
this.request = Either.right(request);
6976
}
7077

7178
public static <T> UpdateItemOperation<T> create(UpdateItemEnhancedRequest<T> request) {
7279
return new UpdateItemOperation<>(request);
7380
}
7481

82+
public static <T> UpdateItemOperation<T> create(TransactUpdateItemEnhancedRequest<T> request) {
83+
return new UpdateItemOperation<>(request);
84+
}
85+
7586
@Override
7687
public UpdateItemRequest generateRequest(TableSchema<T> tableSchema,
7788
OperationContext operationContext,
@@ -80,8 +91,12 @@ public UpdateItemRequest generateRequest(TableSchema<T> tableSchema,
8091
throw new IllegalArgumentException("UpdateItem cannot be executed against a secondary index.");
8192
}
8293

83-
Map<String, AttributeValue> itemMap = tableSchema.itemToMap(this.request.item(),
84-
Boolean.TRUE.equals(this.request.ignoreNulls()));
94+
T item = request.map(UpdateItemEnhancedRequest::item, TransactUpdateItemEnhancedRequest::item);
95+
Boolean ignoreNulls = request.map(r -> Optional.ofNullable(r.ignoreNulls()),
96+
r -> Optional.ofNullable(r.ignoreNulls()))
97+
.orElse(null);
98+
99+
Map<String, AttributeValue> itemMap = tableSchema.itemToMap(item, Boolean.TRUE.equals(ignoreNulls));
85100
TableMetadata tableMetadata = tableSchema.tableMetadata();
86101

87102
WriteModification transformation =
@@ -149,17 +164,20 @@ public TransactWriteItem generateTransactWriteItem(TableSchema<T> tableSchema, O
149164
DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension) {
150165
UpdateItemRequest updateItemRequest = generateRequest(tableSchema, operationContext, dynamoDbEnhancedClientExtension);
151166

152-
Update update = Update.builder()
153-
.key(updateItemRequest.key())
154-
.tableName(updateItemRequest.tableName())
155-
.updateExpression(updateItemRequest.updateExpression())
156-
.conditionExpression(updateItemRequest.conditionExpression())
157-
.expressionAttributeValues(updateItemRequest.expressionAttributeValues())
158-
.expressionAttributeNames(updateItemRequest.expressionAttributeNames())
159-
.build();
167+
Update.Builder builder = Update.builder()
168+
.key(updateItemRequest.key())
169+
.tableName(updateItemRequest.tableName())
170+
.updateExpression(updateItemRequest.updateExpression())
171+
.conditionExpression(updateItemRequest.conditionExpression())
172+
.expressionAttributeValues(updateItemRequest.expressionAttributeValues())
173+
.expressionAttributeNames(updateItemRequest.expressionAttributeNames());
174+
175+
request.right()
176+
.map(TransactUpdateItemEnhancedRequest::returnValuesOnConditionCheckFailureAsString)
177+
.ifPresent(builder::returnValuesOnConditionCheckFailure);
160178

161179
return TransactWriteItem.builder()
162-
.update(update)
180+
.update(builder.build())
163181
.build();
164182
}
165183

@@ -251,11 +269,14 @@ private UpdateItemRequest.Builder addExpressionsIfExist(WriteModification transf
251269
}
252270

253271
/* Merge in conditional expression from specified 'conditionExpression' if applicable */
254-
if (this.request.conditionExpression() != null) {
255-
expressionNames = Expression.joinNames(expressionNames, this.request.conditionExpression().expressionNames());
256-
expressionValues = Expression.joinValues(expressionValues, this.request.conditionExpression().expressionValues());
272+
Expression conditionExpression = request.map(r -> Optional.ofNullable(r.conditionExpression()),
273+
r -> Optional.ofNullable(r.conditionExpression()))
274+
.orElse(null);
275+
if (conditionExpression != null) {
276+
expressionNames = Expression.joinNames(expressionNames, conditionExpression.expressionNames());
277+
expressionValues = Expression.joinValues(expressionValues, conditionExpression.expressionValues());
257278
conditionExpressionString = Expression.joinExpressions(conditionExpressionString,
258-
this.request.conditionExpression().expression(), " AND ");
279+
conditionExpression.expression(), " AND ");
259280
}
260281

261282
// Avoiding adding empty collections that the low level SDK will propagate to DynamoDb where it causes error.

0 commit comments

Comments
 (0)