Skip to content

Commit 279a34b

Browse files
authored
Revert recent AutoGeneratedTimestamp change (#6412)
* Revert "Support AutoGeneratedTimestamp and UpdateBehavior annotation in nested objects (#6109)" This reverts commit 356f65d. * Add changelog entry
1 parent 59e3a00 commit 279a34b

File tree

13 files changed

+174
-1133
lines changed

13 files changed

+174
-1133
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": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Revert recent AutoGeneratedTimestampRecordExtension change released in `2.33.4` that may break users with manually configured table schema. See [#6410](https://github.com/aws/aws-sdk-java-v2/issues/6410)"
6+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedTimestampRecordExtension.java

Lines changed: 9 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,13 @@
1515

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

18-
import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.getNestedSchema;
19-
import static software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.getTableSchemaForListElement;
20-
import static software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.reconstructCompositeKey;
21-
import static software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.resolveSchemasPerPath;
22-
2318
import java.time.Clock;
2419
import java.time.Instant;
2520
import java.util.Collection;
2621
import java.util.Collections;
2722
import java.util.HashMap;
28-
import java.util.List;
2923
import java.util.Map;
30-
import java.util.Optional;
3124
import java.util.function.Consumer;
32-
import java.util.stream.Collectors;
3325
import software.amazon.awssdk.annotations.NotThreadSafe;
3426
import software.amazon.awssdk.annotations.SdkPublicApi;
3527
import software.amazon.awssdk.annotations.ThreadSafe;
@@ -38,7 +30,6 @@
3830
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
3931
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbExtensionContext;
4032
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
41-
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
4233
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
4334
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
4435
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
@@ -73,10 +64,6 @@
7364
* <p>
7465
* Every time a new update of the record is successfully written to the database, the timestamp at which it was modified will
7566
* be automatically updated. This extension applies the conversions as defined in the attribute convertor.
76-
* The implementation handles both flattened nested parameters (identified by keys separated with
77-
* {@code "_NESTED_ATTR_UPDATE_"}) and entire nested maps or lists, ensuring consistent behavior across both representations.
78-
* If a nested object or list is {@code null}, no timestamp values will be generated for any of its annotated fields.
79-
* The same timestamp value is used for both top-level attributes and all applicable nested fields.
8067
*/
8168
@SdkPublicApi
8269
@ThreadSafe
@@ -139,109 +126,26 @@ public static AutoGeneratedTimestampRecordExtension create() {
139126
*/
140127
@Override
141128
public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
142-
Map<String, AttributeValue> itemToTransform = new HashMap<>(context.items());
143-
144-
Map<String, AttributeValue> updatedItems = new HashMap<>();
145-
Instant currentInstant = clock.instant();
146-
147-
itemToTransform.forEach((key, value) -> {
148-
if (value.hasM() && value.m() != null) {
149-
Optional<? extends TableSchema<?>> nestedSchema = getNestedSchema(context.tableSchema(), key);
150-
if (nestedSchema.isPresent()) {
151-
Map<String, AttributeValue> processed = processNestedObject(value.m(), nestedSchema.get(), currentInstant);
152-
updatedItems.put(key, AttributeValue.builder().m(processed).build());
153-
}
154-
} else if (value.hasL() && !value.l().isEmpty() && value.l().get(0).hasM()) {
155-
TableSchema<?> elementListSchema = getTableSchemaForListElement(context.tableSchema(), key);
156-
157-
List<AttributeValue> updatedList = value.l()
158-
.stream()
159-
.map(listItem -> listItem.hasM() ?
160-
AttributeValue.builder()
161-
.m(processNestedObject(listItem.m(),
162-
elementListSchema,
163-
currentInstant))
164-
.build() : listItem)
165-
.collect(Collectors.toList());
166-
updatedItems.put(key, AttributeValue.builder().l(updatedList).build());
167-
}
168-
});
169-
170-
Map<String, TableSchema<?>> stringTableSchemaMap = resolveSchemasPerPath(itemToTransform, context.tableSchema());
171129

172-
stringTableSchemaMap.forEach((path, schema) -> {
173-
Collection<String> customMetadataObject = schema.tableMetadata()
174-
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class)
175-
.orElse(null);
130+
Collection<String> customMetadataObject = context.tableMetadata()
131+
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class).orElse(null);
176132

177-
if (customMetadataObject != null) {
178-
customMetadataObject.forEach(
179-
key -> insertTimestampInItemToTransform(updatedItems, reconstructCompositeKey(path, key),
180-
schema.converterForAttribute(key), currentInstant));
181-
}
182-
});
183-
184-
if (updatedItems.isEmpty()) {
133+
if (customMetadataObject == null) {
185134
return WriteModification.builder().build();
186135
}
187-
188-
itemToTransform.putAll(updatedItems);
189-
136+
Map<String, AttributeValue> itemToTransform = new HashMap<>(context.items());
137+
customMetadataObject.forEach(
138+
key -> insertTimestampInItemToTransform(itemToTransform, key,
139+
context.tableSchema().converterForAttribute(key)));
190140
return WriteModification.builder()
191141
.transformedItem(Collections.unmodifiableMap(itemToTransform))
192142
.build();
193143
}
194144

195-
private Map<String, AttributeValue> processNestedObject(Map<String, AttributeValue> nestedMap, TableSchema<?> nestedSchema,
196-
Instant currentInstant) {
197-
Map<String, AttributeValue> updatedNestedMap = new HashMap<>(nestedMap);
198-
Collection<String> customMetadataObject = nestedSchema.tableMetadata()
199-
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class).orElse(null);
200-
201-
if (customMetadataObject != null) {
202-
customMetadataObject.forEach(
203-
key -> insertTimestampInItemToTransform(updatedNestedMap, String.valueOf(key),
204-
nestedSchema.converterForAttribute(key), currentInstant));
205-
}
206-
207-
nestedMap.forEach((nestedKey, nestedValue) -> {
208-
if (nestedValue.hasM()) {
209-
Optional<? extends TableSchema<?>> childSchemaOptional = getNestedSchema(nestedSchema, nestedKey);
210-
TableSchema<?> schemaToUse = childSchemaOptional.isPresent() ? childSchemaOptional.get() : nestedSchema;
211-
updatedNestedMap.put(nestedKey,
212-
AttributeValue.builder()
213-
.m(processNestedObject(nestedValue.m(), schemaToUse, currentInstant))
214-
.build());
215-
216-
} else if (nestedValue.hasL() && !nestedValue.l().isEmpty()
217-
&& nestedValue.l().get(0).hasM()) {
218-
try {
219-
TableSchema<?> listElementSchema = TableSchema.fromClass(
220-
Class.forName(nestedSchema.converterForAttribute(nestedKey)
221-
.type().rawClassParameters().get(0).rawClass().getName()));
222-
List<AttributeValue> updatedList = nestedValue
223-
.l()
224-
.stream()
225-
.map(listItem -> listItem.hasM() ?
226-
AttributeValue.builder()
227-
.m(processNestedObject(listItem.m(),
228-
listElementSchema,
229-
currentInstant)).build() : listItem)
230-
.collect(Collectors.toList());
231-
updatedNestedMap.put(nestedKey, AttributeValue.builder().l(updatedList).build());
232-
} catch (ClassNotFoundException e) {
233-
throw new IllegalArgumentException("Class not found for field name: " + nestedKey, e);
234-
}
235-
}
236-
});
237-
return updatedNestedMap;
238-
}
239-
240145
private void insertTimestampInItemToTransform(Map<String, AttributeValue> itemToTransform,
241146
String key,
242-
AttributeConverter converter,
243-
Instant instant) {
244-
itemToTransform.put(key, converter.transformFrom(instant));
147+
AttributeConverter converter) {
148+
itemToTransform.put(key, converter.transformFrom(clock.instant()));
245149
}
246150

247151
/**

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -204,15 +204,4 @@ public static <T> List<T> getItemsFromSupplier(List<Supplier<T>> itemSupplierLis
204204
public static boolean isNullAttributeValue(AttributeValue attributeValue) {
205205
return attributeValue.nul() != null && attributeValue.nul();
206206
}
207-
208-
/**
209-
* Retrieves the {@link TableSchema} for a nested attribute within the given parent schema.
210-
*
211-
* @param parentSchema the schema of the parent bean class
212-
* @param attributeName the name of the nested attribute
213-
* @return an {@link Optional} containing the nested attribute's {@link TableSchema}, or empty if unavailable
214-
*/
215-
public static Optional<? extends TableSchema<?>> getNestedSchema(TableSchema<?> parentSchema, String attributeName) {
216-
return parentSchema.converterForAttribute(attributeName).type().tableSchema();
217-
}
218207
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/utility/NestedRecordUtils.java

Lines changed: 0 additions & 135 deletions
This file was deleted.

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ public UpdateItemRequest generateRequest(TableSchema<T> tableSchema,
131131

132132
Map<String, AttributeValue> keyAttributes = filterMap(itemMap, entry -> primaryKeys.contains(entry.getKey()));
133133
Map<String, AttributeValue> nonKeyAttributes = filterMap(itemMap, entry -> !primaryKeys.contains(entry.getKey()));
134-
Expression updateExpression = generateUpdateExpressionIfExist(tableSchema, transformation, nonKeyAttributes);
134+
135+
Expression updateExpression = generateUpdateExpressionIfExist(tableMetadata, transformation, nonKeyAttributes);
135136
Expression conditionExpression = generateConditionExpressionIfExist(transformation, request);
136137

137138
Map<String, String> expressionNames = coalesceExpressionNames(updateExpression, conditionExpression);
@@ -274,7 +275,7 @@ public TransactWriteItem generateTransactWriteItem(TableSchema<T> tableSchema, O
274275
* if there are attributes to be updated (most likely). If both exist, they are merged and the code generates a final
275276
* Expression that represent the result.
276277
*/
277-
private Expression generateUpdateExpressionIfExist(TableSchema<T> tableSchema,
278+
private Expression generateUpdateExpressionIfExist(TableMetadata tableMetadata,
278279
WriteModification transformation,
279280
Map<String, AttributeValue> attributes) {
280281
UpdateExpression updateExpression = null;
@@ -283,7 +284,7 @@ private Expression generateUpdateExpressionIfExist(TableSchema<T> tableSchema,
283284
}
284285
if (!attributes.isEmpty()) {
285286
List<String> nonRemoveAttributes = UpdateExpressionConverter.findAttributeNames(updateExpression);
286-
UpdateExpression operationUpdateExpression = operationExpression(attributes, tableSchema, nonRemoveAttributes);
287+
UpdateExpression operationUpdateExpression = operationExpression(attributes, tableMetadata, nonRemoveAttributes);
287288
if (updateExpression == null) {
288289
updateExpression = operationUpdateExpression;
289290
} else {

0 commit comments

Comments
 (0)