diff --git a/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-96aff9e.json b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-96aff9e.json new file mode 100644 index 000000000000..8ed0b2c84583 --- /dev/null +++ b/.changes/next-release/feature-AmazonDynamoDBEnhancedClient-96aff9e.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "Amazon DynamoDB Enhanced Client", + "contributor": "", + "description": "Added support for @DynamoDbAutoGeneratedTimestampAttribute and @DynamoDbUpdateBehavior on attributes within nested objects. The @DynamoDbUpdateBehavior annotation will only take effect for nested attributes when using IgnoreNullsMode.SCALAR_ONLY." +} diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedTimestampRecordExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedTimestampRecordExtension.java index 2ac27d918202..27bf8065a015 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedTimestampRecordExtension.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedTimestampRecordExtension.java @@ -15,13 +15,21 @@ package software.amazon.awssdk.enhanced.dynamodb.extensions; +import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.getNestedSchema; +import static software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.getTableSchemaForListElement; +import static software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.reconstructCompositeKey; +import static software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.resolveSchemasPerPath; + import java.time.Clock; import java.time.Instant; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Consumer; +import java.util.stream.Collectors; import software.amazon.awssdk.annotations.NotThreadSafe; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.ThreadSafe; @@ -30,6 +38,7 @@ import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbExtensionContext; import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag; import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; @@ -64,6 +73,10 @@ *

* Every time a new update of the record is successfully written to the database, the timestamp at which it was modified will * be automatically updated. This extension applies the conversions as defined in the attribute convertor. + * The implementation handles both flattened nested parameters (identified by keys separated with + * {@code "_NESTED_ATTR_UPDATE_"}) and entire nested maps or lists, ensuring consistent behavior across both representations. + * If a nested object or list is {@code null}, no timestamp values will be generated for any of its annotated fields. + * The same timestamp value is used for both top-level attributes and all applicable nested fields. */ @SdkPublicApi @ThreadSafe @@ -126,26 +139,103 @@ public static AutoGeneratedTimestampRecordExtension create() { */ @Override public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) { + Map itemToTransform = new HashMap<>(context.items()); + + Map updatedItems = new HashMap<>(); + Instant currentInstant = clock.instant(); + + itemToTransform.forEach((key, value) -> { + if (value.hasM() && value.m() != null) { + Optional> nestedSchema = getNestedSchema(context.tableSchema(), key); + if (nestedSchema.isPresent()) { + Map processed = processNestedObject(value.m(), nestedSchema.get(), currentInstant); + updatedItems.put(key, AttributeValue.builder().m(processed).build()); + } + } else if (value.hasL() && !value.l().isEmpty() && value.l().get(0).hasM()) { + TableSchema elementListSchema = getTableSchemaForListElement(context.tableSchema(), key); + + List updatedList = value.l() + .stream() + .map(listItem -> listItem.hasM() ? + AttributeValue.builder() + .m(processNestedObject(listItem.m(), + elementListSchema, + currentInstant)) + .build() : listItem) + .collect(Collectors.toList()); + updatedItems.put(key, AttributeValue.builder().l(updatedList).build()); + } + }); + + Map> stringTableSchemaMap = resolveSchemasPerPath(itemToTransform, context.tableSchema()); - Collection customMetadataObject = context.tableMetadata() - .customMetadataObject(CUSTOM_METADATA_KEY, Collection.class).orElse(null); + stringTableSchemaMap.forEach((path, schema) -> { + Collection customMetadataObject = schema.tableMetadata() + .customMetadataObject(CUSTOM_METADATA_KEY, Collection.class) + .orElse(null); - if (customMetadataObject == null) { + if (customMetadataObject != null) { + customMetadataObject.forEach( + key -> insertTimestampInItemToTransform(updatedItems, reconstructCompositeKey(path, key), + schema.converterForAttribute(key), currentInstant)); + } + }); + + if (updatedItems.isEmpty()) { return WriteModification.builder().build(); } - Map itemToTransform = new HashMap<>(context.items()); - customMetadataObject.forEach( - key -> insertTimestampInItemToTransform(itemToTransform, key, - context.tableSchema().converterForAttribute(key))); + + itemToTransform.putAll(updatedItems); + return WriteModification.builder() .transformedItem(Collections.unmodifiableMap(itemToTransform)) .build(); } + private Map processNestedObject(Map nestedMap, TableSchema nestedSchema, + Instant currentInstant) { + Map updatedNestedMap = new HashMap<>(nestedMap); + Collection customMetadataObject = nestedSchema.tableMetadata() + .customMetadataObject(CUSTOM_METADATA_KEY, Collection.class) + .orElse(null); + + if (customMetadataObject != null) { + customMetadataObject.forEach( + key -> insertTimestampInItemToTransform(updatedNestedMap, String.valueOf(key), + nestedSchema.converterForAttribute(key), currentInstant)); + } + + nestedMap.forEach((nestedKey, nestedValue) -> { + if (nestedValue.hasM()) { + Optional> childSchemaOptional = getNestedSchema(nestedSchema, nestedKey); + TableSchema schemaToUse = childSchemaOptional.isPresent() ? childSchemaOptional.get() : nestedSchema; + updatedNestedMap.put(nestedKey, + AttributeValue.builder() + .m(processNestedObject(nestedValue.m(), schemaToUse, currentInstant)) + .build()); + + } else if (nestedValue.hasL() && !nestedValue.l().isEmpty() && nestedValue.l().get(0).hasM()) { + TableSchema listElementSchema = getTableSchemaForListElement(nestedSchema, nestedKey); + List updatedList = nestedValue + .l() + .stream() + .map(listItem -> listItem.hasM() ? + AttributeValue.builder() + .m(processNestedObject(listItem.m(), + listElementSchema, + currentInstant)).build() : listItem) + .collect(Collectors.toList()); + updatedNestedMap.put(nestedKey, AttributeValue.builder().l(updatedList).build()); + } + }); + return updatedNestedMap; + } + private void insertTimestampInItemToTransform(Map itemToTransform, String key, - AttributeConverter converter) { - itemToTransform.put(key, converter.transformFrom(clock.instant())); + AttributeConverter converter, + Instant instant) { + itemToTransform.put(key, converter.transformFrom(instant)); } /** diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtils.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtils.java index 61d750e98a7e..3787e12a9a34 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtils.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtils.java @@ -29,6 +29,7 @@ import java.util.stream.Stream; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; import software.amazon.awssdk.enhanced.dynamodb.Key; import software.amazon.awssdk.enhanced.dynamodb.OperationContext; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; @@ -204,4 +205,24 @@ public static List getItemsFromSupplier(List> itemSupplierLis public static boolean isNullAttributeValue(AttributeValue attributeValue) { return attributeValue.nul() != null && attributeValue.nul(); } + + /** + * Retrieves the {@link TableSchema} for a nested attribute within the given parent schema. When the attribute is a + * parameterized type (e.g., List), it retrieves the schema of the first type parameter. Otherwise, it retrieves the schema + * directly from the attribute's enhanced type. + * + * @param parentSchema the schema of the parent bean class + * @param attributeName the name of the nested attribute + * @return an {@link Optional} containing the nested attribute's {@link TableSchema}, or empty if unavailable + */ + public static Optional> getNestedSchema(TableSchema parentSchema, String attributeName) { + EnhancedType enhancedType = parentSchema.converterForAttribute(attributeName).type(); + List> rawClassParameters = enhancedType.rawClassParameters(); + + if (rawClassParameters != null && !rawClassParameters.isEmpty()) { + enhancedType = rawClassParameters.get(0); + } + + return enhancedType.tableSchema(); + } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/utility/NestedRecordUtils.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/utility/NestedRecordUtils.java new file mode 100644 index 000000000000..b63b561fad37 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/utility/NestedRecordUtils.java @@ -0,0 +1,140 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility; + +import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.getNestedSchema; +import static software.amazon.awssdk.enhanced.dynamodb.internal.operations.UpdateItemOperation.NESTED_OBJECT_UPDATE; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +@SdkInternalApi +public final class NestedRecordUtils { + + private static final Pattern NESTED_OBJECT_PATTERN = Pattern.compile(NESTED_OBJECT_UPDATE); + + private NestedRecordUtils() { + } + + /** + * Resolves and returns the {@link TableSchema} for the element type of a list attribute from the provided root schema. + *

+ * This method is useful when dealing with lists of nested objects in a DynamoDB-enhanced table schema, particularly in + * scenarios where the list is part of a flattened nested structure. + *

+ * If the provided key contains the nested object delimiter (e.g., {@code _NESTED_ATTR_UPDATE_}), the method traverses the + * nested hierarchy based on that path to locate the correct schema for the target attribute. Otherwise, it directly resolves + * the list element type from the root schema using reflection. + * + * @param rootSchema The root {@link TableSchema} representing the top-level entity. + * @param key The key representing the list attribute, either flat or nested (using a delimiter). + * @return The {@link TableSchema} representing the list element type of the specified attribute. + * @throws IllegalArgumentException If the list element class cannot be found via reflection. + */ + public static TableSchema getTableSchemaForListElement(TableSchema rootSchema, String key) { + TableSchema listElementSchema; + try { + if (!key.contains(NESTED_OBJECT_UPDATE)) { + Optional> staticSchema = getNestedSchema(rootSchema, key); + listElementSchema = + staticSchema.isPresent() + ? staticSchema.get() + : TableSchema.fromClass(Class.forName( + rootSchema.converterForAttribute(key).type().rawClassParameters().get(0).rawClass().getName())); + + } else { + String[] parts = NESTED_OBJECT_PATTERN.split(key); + TableSchema currentSchema = rootSchema; + + for (int i = 0; i < parts.length - 1; i++) { + Optional> nestedSchema = getNestedSchema(currentSchema, parts[i]); + if (nestedSchema.isPresent()) { + currentSchema = nestedSchema.get(); + } + } + String attributeName = parts[parts.length - 1]; + listElementSchema = TableSchema.fromClass( + Class.forName(currentSchema.converterForAttribute(attributeName) + .type().rawClassParameters().get(0).rawClass().getName())); + } + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Class not found for field name: " + key, e); + } + return listElementSchema; + } + + /** + * Traverses the attribute keys representing flattened nested structures and resolves the corresponding {@link TableSchema} + * for each nested path. + *

+ * The method constructs a mapping between each unique nested path (represented as dot-delimited strings) and the + * corresponding {@link TableSchema} object derived from the root schema. It supports resolving schemas for arbitrarily deep + * nesting, using the {@code _NESTED_ATTR_UPDATE_} pattern as a path delimiter. + *

+ * This is typically used in update or transformation flows where fields from nested objects are represented as flattened keys + * in the attribute map (e.g., {@code parent_NESTED_ATTR_UPDATE_child}). + * + * @param attributesToSet A map of flattened attribute keys to values, where keys may represent paths to nested attributes. + * @param rootSchema The root {@link TableSchema} of the top-level entity. + * @return A map where the key is the nested path (e.g., {@code "parent.child"}) and the value is the {@link TableSchema} + * corresponding to that level in the object hierarchy. + */ + public static Map> resolveSchemasPerPath(Map attributesToSet, + TableSchema rootSchema) { + Map> schemaMap = new HashMap<>(); + schemaMap.put("", rootSchema); + + for (String key : attributesToSet.keySet()) { + String[] parts = NESTED_OBJECT_PATTERN.split(key); + + StringBuilder pathBuilder = new StringBuilder(); + TableSchema currentSchema = rootSchema; + + for (int i = 0; i < parts.length - 1; i++) { + if (pathBuilder.length() > 0) { + pathBuilder.append("."); + } + pathBuilder.append(parts[i]); + + String path = pathBuilder.toString(); + + if (!schemaMap.containsKey(path)) { + Optional> nestedSchema = getNestedSchema(currentSchema, parts[i]); + if (nestedSchema.isPresent()) { + schemaMap.put(path, nestedSchema.get()); + currentSchema = nestedSchema.get(); + } + } else { + currentSchema = schemaMap.get(path); + } + } + } + return schemaMap; + } + + public static String reconstructCompositeKey(String path, String attributeName) { + if (path == null || path.isEmpty()) { + return attributeName; + } + return String.join(NESTED_OBJECT_UPDATE, path.split("\\.")) + + NESTED_OBJECT_UPDATE + attributeName; + } +} diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/UpdateItemOperation.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/UpdateItemOperation.java index 0ffe361b5aed..cd98db7417bc 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/UpdateItemOperation.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/UpdateItemOperation.java @@ -132,7 +132,7 @@ public UpdateItemRequest generateRequest(TableSchema tableSchema, Map keyAttributes = filterMap(itemMap, entry -> primaryKeys.contains(entry.getKey())); Map nonKeyAttributes = filterMap(itemMap, entry -> !primaryKeys.contains(entry.getKey())); - Expression updateExpression = generateUpdateExpressionIfExist(tableMetadata, transformation, nonKeyAttributes); + Expression updateExpression = generateUpdateExpressionIfExist(tableSchema, transformation, nonKeyAttributes); Expression conditionExpression = generateConditionExpressionIfExist(transformation, request); Map expressionNames = coalesceExpressionNames(updateExpression, conditionExpression); @@ -275,7 +275,7 @@ public TransactWriteItem generateTransactWriteItem(TableSchema tableSchema, O * if there are attributes to be updated (most likely). If both exist, they are merged and the code generates a final * Expression that represent the result. */ - private Expression generateUpdateExpressionIfExist(TableMetadata tableMetadata, + private Expression generateUpdateExpressionIfExist(TableSchema tableSchema, WriteModification transformation, Map attributes) { UpdateExpression updateExpression = null; @@ -284,7 +284,7 @@ private Expression generateUpdateExpressionIfExist(TableMetadata tableMetadata, } if (!attributes.isEmpty()) { List nonRemoveAttributes = UpdateExpressionConverter.findAttributeNames(updateExpression); - UpdateExpression operationUpdateExpression = operationExpression(attributes, tableMetadata, nonRemoveAttributes); + UpdateExpression operationUpdateExpression = operationExpression(attributes, tableSchema, nonRemoveAttributes); if (updateExpression == null) { updateExpression = operationUpdateExpression; } else { diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/update/UpdateExpressionUtils.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/update/UpdateExpressionUtils.java index 1d47400ab2e6..4ad1989d057d 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/update/UpdateExpressionUtils.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/update/UpdateExpressionUtils.java @@ -15,21 +15,24 @@ package software.amazon.awssdk.enhanced.dynamodb.internal.update; +import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.getNestedSchema; import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.isNullAttributeValue; import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.keyRef; import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.valueRef; import static software.amazon.awssdk.enhanced.dynamodb.internal.operations.UpdateItemOperation.NESTED_OBJECT_UPDATE; import static software.amazon.awssdk.utils.CollectionUtils.filterMap; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.enhanced.dynamodb.TableMetadata; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils; import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.UpdateBehaviorTag; import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior; @@ -57,12 +60,12 @@ public static String ifNotExists(String key, String initValue) { * Generates an UpdateExpression representing a POJO, with only SET and REMOVE actions. */ public static UpdateExpression operationExpression(Map itemMap, - TableMetadata tableMetadata, + TableSchema tableSchema, List nonRemoveAttributes) { Map setAttributes = filterMap(itemMap, e -> !isNullAttributeValue(e.getValue())); UpdateExpression setAttributeExpression = UpdateExpression.builder() - .actions(setActionsFor(setAttributes, tableMetadata)) + .actions(setActionsFor(setAttributes, tableSchema)) .build(); Map removeAttributes = @@ -78,13 +81,31 @@ public static UpdateExpression operationExpression(Map i /** * Creates a list of SET actions for all attributes supplied in the map. */ - private static List setActionsFor(Map attributesToSet, TableMetadata tableMetadata) { - return attributesToSet.entrySet() - .stream() - .map(entry -> setValue(entry.getKey(), - entry.getValue(), - UpdateBehaviorTag.resolveForAttribute(entry.getKey(), tableMetadata))) - .collect(Collectors.toList()); + private static List setActionsFor(Map attributesToSet, TableSchema tableSchema) { + List actions = new ArrayList<>(); + for (Map.Entry entry : attributesToSet.entrySet()) { + String key = entry.getKey(); + AttributeValue value = entry.getValue(); + + if (key.contains(NESTED_OBJECT_UPDATE)) { + TableSchema currentSchema = tableSchema; + List pathFieldNames = Arrays.asList(PATTERN.split(key)); + String attributeName = pathFieldNames.get(pathFieldNames.size() - 1); + + for (int i = 0; i < pathFieldNames.size() - 1; i++) { + Optional> nestedSchema = getNestedSchema(currentSchema, pathFieldNames.get(i)); + if (nestedSchema.isPresent()) { + currentSchema = nestedSchema.get(); + } + } + + actions.add(setValue(key, value, + UpdateBehaviorTag.resolveForAttribute(attributeName, currentSchema.tableMetadata()))); + } else { + actions.add(setValue(key, value, UpdateBehaviorTag.resolveForAttribute(key, tableSchema.tableMetadata()))); + } + } + return actions; } /** diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbUpdateBehavior.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbUpdateBehavior.java index fa161446c1a4..d14216b6a529 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbUpdateBehavior.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/DynamoDbUpdateBehavior.java @@ -22,10 +22,15 @@ import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.enhanced.dynamodb.internal.mapper.BeanTableSchemaAttributeTags; import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior; +import software.amazon.awssdk.enhanced.dynamodb.model.IgnoreNullsMode; /** * Specifies the behavior when this attribute is updated as part of an 'update' operation such as UpdateItem. See * documentation of {@link UpdateBehavior} for details on the different behaviors supported and the default behavior. + * For attributes within nested objects, this annotation is only respected when the request uses + * {@link IgnoreNullsMode#SCALAR_ONLY}. In {@link IgnoreNullsMode#MAPS_ONLY} or {@link IgnoreNullsMode#DEFAULT}, + * the annotation has no effect. When applied to a list of nested objects, the annotation is not supported, + * as individual elements cannot be updated — the entire list is replaced during an update operation. */ @SdkPublicApi @Target({ElementType.METHOD}) diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/NestedRecordUtilsTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/NestedRecordUtilsTest.java new file mode 100644 index 000000000000..c666b2644a8b --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/NestedRecordUtilsTest.java @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.enhanced.dynamodb.extensions; + +import static software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.getTableSchemaForListElement; +import static software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.resolveSchemasPerPath; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.NestedRecordListElement; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.NestedRecordWithUpdateBehavior; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.RecordWithUpdateBehaviors; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class NestedRecordUtilsTest { + + @Test + public void getTableSchemaForListElement_shouldReturnElementSchema() { + TableSchema parentSchema = TableSchema.fromBean(NestedRecordWithUpdateBehavior.class); + + TableSchema childSchema = getTableSchemaForListElement(parentSchema, "nestedRecordList"); + + Assertions.assertNotNull(childSchema); + Assertions.assertEquals(TableSchema.fromBean(NestedRecordListElement.class), childSchema); + } + + @Test + public void resolveSchemasPerPath_shouldResolveNestedPaths() { + TableSchema rootSchema = TableSchema.fromBean(RecordWithUpdateBehaviors.class); + + Map attributesToSet = new HashMap<>(); + attributesToSet.put("nestedRecord_NESTED_ATTR_UPDATE_nestedRecord_NESTED_ATTR_UPDATE_attribute", + AttributeValue.builder().s("attributeValue").build()); + + Map> result = resolveSchemasPerPath(attributesToSet, rootSchema); + + Assertions.assertEquals(3, result.size()); + Assertions.assertTrue(result.containsKey("")); + Assertions.assertTrue(result.containsKey("nestedRecord")); + Assertions.assertTrue(result.containsKey("nestedRecord.nestedRecord")); + } +} diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedTimestampRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedTimestampRecordTest.java index 5d5ccf4fdb4b..e1f9835ec1e3 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedTimestampRecordTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedTimestampRecordTest.java @@ -15,10 +15,14 @@ package software.amazon.awssdk.enhanced.dynamodb.functionaltests; -import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension.AttributeTags.autoGeneratedTimestampAttribute; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedStaticLevel2RecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedStaticLevel3RecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedStaticLevel4Record; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedStaticRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildStaticSchemaForNestedRecordWithList; import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue; import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.updateBehavior; @@ -27,11 +31,9 @@ import java.time.Instant; import java.time.ZoneOffset; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; -import java.util.stream.IntStream; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -40,15 +42,12 @@ import org.mockito.Mockito; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; import software.amazon.awssdk.enhanced.dynamodb.Expression; -import software.amazon.awssdk.enhanced.dynamodb.OperationContext; -import software.amazon.awssdk.enhanced.dynamodb.TableMetadata; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.enhanced.dynamodb.converters.EpochMillisFormatTestConverter; import software.amazon.awssdk.enhanced.dynamodb.converters.TimeFormatUpdateTestConverter; import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension; -import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext; -import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext; import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior; import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest; @@ -70,17 +69,13 @@ public class AutoGeneratedTimestampRecordTest extends LocalDynamoDbSyncTestBase public static final Instant MOCKED_INSTANT_UPDATE_TWO = Instant.now(Clock.fixed(Instant.parse("2019-01-15T14:00:00Z"), ZoneOffset.UTC)); - private static final String TABLE_NAME = "table-name"; - private static final OperationContext PRIMARY_CONTEXT = - DefaultOperationContext.create(TABLE_NAME, TableMetadata.primaryIndexName()); - private static final TableSchema FLATTENED_TABLE_SCHEMA = StaticTableSchema.builder(FlattenedRecord.class) .newItemSupplier(FlattenedRecord::new) .addAttribute(Instant.class, a -> a.name("generated") - .getter(FlattenedRecord::getGenerated) - .setter(FlattenedRecord::setGenerated) - .tags(autoGeneratedTimestampAttribute())) + .getter(FlattenedRecord::getGenerated) + .setter(FlattenedRecord::setGenerated) + .tags(autoGeneratedTimestampAttribute())) .build(); private static final TableSchema TABLE_SCHEMA = @@ -103,23 +98,23 @@ public class AutoGeneratedTimestampRecordTest extends LocalDynamoDbSyncTestBase .tags(autoGeneratedTimestampAttribute(), updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS))) .addAttribute(Instant.class, a -> a.name("lastUpdatedDateInEpochMillis") - .getter(Record::getLastUpdatedDateInEpochMillis) - .setter(Record::setLastUpdatedDateInEpochMillis) - .attributeConverter(EpochMillisFormatTestConverter.create()) - .tags(autoGeneratedTimestampAttribute())) + .getter(Record::getLastUpdatedDateInEpochMillis) + .setter(Record::setLastUpdatedDateInEpochMillis) + .attributeConverter(EpochMillisFormatTestConverter.create()) + .tags(autoGeneratedTimestampAttribute())) .addAttribute(Instant.class, a -> a.name("convertedLastUpdatedDate") .getter(Record::getConvertedLastUpdatedDate) .setter(Record::setConvertedLastUpdatedDate) .attributeConverter(TimeFormatUpdateTestConverter.create()) .tags(autoGeneratedTimestampAttribute())) .flatten(FLATTENED_TABLE_SCHEMA, Record::getFlattenedRecord, Record::setFlattenedRecord) + .addAttribute(EnhancedType.documentOf(NestedStaticRecordWithList.class, + buildStaticSchemaForNestedRecordWithList(), + b -> b.ignoreNulls(true)), + a -> a.name("nestedRecord").getter(Record::getNestedRecord) + .setter(Record::setNestedRecord)) .build(); - private final List> fakeItems = - IntStream.range(0, 4) - .mapToObj($ -> createUniqueFakeItem()) - .map(fakeItem -> TABLE_SCHEMA.itemToMap(fakeItem, true)) - .collect(toList()); private final DynamoDbTable mappedTable; private final Clock mockCLock = Mockito.mock(Clock.class); @@ -160,39 +155,86 @@ public void deleteTable() { } @Test - public void putNewRecordSetsInitialAutoGeneratedTimestamp() { - Record item = new Record().setId("id").setAttribute("one"); + public void putNewRecord_setsInitialTimestamps_onAllNestedBeanLevels() { + NestedStaticLevel4Record nestedLevel4 = new NestedStaticLevel4Record().setAttr("attrL4"); + NestedStaticLevel3RecordWithList nestedLevel3 = + new NestedStaticLevel3RecordWithList().setAttr("attrL3").setLevel4(nestedLevel4); + NestedStaticLevel2RecordWithList level2 = + new NestedStaticLevel2RecordWithList().setAttr("attrL2").setLevel3(nestedLevel3); + NestedStaticRecordWithList nestedLevel1 = new NestedStaticRecordWithList().setAttr("attrL1").setLevel2(level2); + + Record item = new Record() + .setId("id") + .setAttribute("one") + .setNestedRecord(nestedLevel1); + mappedTable.putItem(r -> r.item(item)); Record result = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id"))); - GetItemResponse itemAsStoredInDDB = getItemAsStoredFromDDB(); + GetItemResponse stored = getItemAsStoredFromDDB(); + + NestedStaticLevel4Record expL4 = new NestedStaticLevel4Record() + .setTime(MOCKED_INSTANT_NOW).setAttr("attrL4"); + NestedStaticLevel3RecordWithList expL3 = new NestedStaticLevel3RecordWithList() + .setTime(MOCKED_INSTANT_NOW).setAttr("attrL3").setLevel4(expL4); + NestedStaticLevel2RecordWithList expL2 = new NestedStaticLevel2RecordWithList() + .setTime(MOCKED_INSTANT_NOW).setAttr("attrL2").setLevel3(expL3); + NestedStaticRecordWithList expL1 = new NestedStaticRecordWithList() + .setTime(MOCKED_INSTANT_NOW).setAttr("attrL1").setLevel2(expL2); + FlattenedRecord flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_NOW); - Record expectedRecord = new Record().setId("id") - .setAttribute("one") - .setLastUpdatedDate(MOCKED_INSTANT_NOW) - .setConvertedLastUpdatedDate(MOCKED_INSTANT_NOW) - .setCreatedDate(MOCKED_INSTANT_NOW) - .setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW) - .setFlattenedRecord(flattenedRecord); + + Record expectedRecord = new Record() + .setId("id") + .setAttribute("one") + .setLastUpdatedDate(MOCKED_INSTANT_NOW) + .setConvertedLastUpdatedDate(MOCKED_INSTANT_NOW) + .setCreatedDate(MOCKED_INSTANT_NOW) + .setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW) + .setFlattenedRecord(flattenedRecord) + .setNestedRecord(expL1); + assertThat(result, is(expectedRecord)); - // The data in DDB is stored in converted time format - assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("13 01 2019 14:00:00")); + assertThat(stored.item().get("convertedLastUpdatedDate").s(), is("13 01 2019 14:00:00")); + + // nestedLevel1 assertions + Map lvl1Map = stored.item().get("nestedRecord").m(); + assertThat(lvl1Map.get("time").s(), is(MOCKED_INSTANT_NOW.toString())); + + // level2 assertions + Map lvl2Map = lvl1Map.get("level2").m(); + assertThat(lvl2Map.get("time").s(), is(MOCKED_INSTANT_NOW.toString())); + + // level3 assertions + Map lvl3Map = lvl2Map.get("level3").m(); + assertThat(lvl3Map.get("time").s(), is(MOCKED_INSTANT_NOW.toString())); + + // level4 assertions + Map lvl4Map = lvl3Map.get("level4").m(); + assertThat(lvl4Map.get("time").s(), is(MOCKED_INSTANT_NOW.toString())); } @Test public void updateNewRecordSetsAutoFormattedDate() { - Record result = mappedTable.updateItem(r -> r.item(new Record().setId("id").setAttribute("one"))); + Record result = mappedTable.updateItem(r -> r.item(new Record().setId("id").setAttribute("one") + .setNestedRecord(new NestedStaticRecordWithList() + .setAttr("attribute")))); GetItemResponse itemAsStoredInDDB = getItemAsStoredFromDDB(); FlattenedRecord flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_NOW); + NestedStaticRecordWithList expectednestedLevel1 = new NestedStaticRecordWithList().setTime(MOCKED_INSTANT_NOW) + .setAttr("attribute"); Record expectedRecord = new Record().setId("id") .setAttribute("one") .setLastUpdatedDate(MOCKED_INSTANT_NOW) .setConvertedLastUpdatedDate(MOCKED_INSTANT_NOW) .setCreatedDate(MOCKED_INSTANT_NOW) .setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW) - .setFlattenedRecord(flattenedRecord); + .setFlattenedRecord(flattenedRecord) + .setNestedRecord(expectednestedLevel1); assertThat(result, is(expectedRecord)); // The data in DDB is stored in converted time format assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("13 01 2019 14:00:00")); + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("time").s(), + is(MOCKED_INSTANT_NOW.toString())); } @Test @@ -226,7 +268,7 @@ public void putExistingRecordUpdatedWithAutoFormattedTimestamps() { .setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE) .setFlattenedRecord(flattenedRecord); - System.out.println("result "+result); + System.out.println("result " + result); assertThat(result, is(expectedRecord)); // The data in DDB is stored in converted time format assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("14 01 2019 14:00:00")); @@ -256,12 +298,12 @@ public void putItemFollowedByUpdates() { itemAsStoredInDDB = getItemAsStoredFromDDB(); flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_UPDATE_ONE); expectedRecord = new Record().setId("id") - .setAttribute("one") - .setLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE) - .setConvertedLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE) - .setCreatedDate(MOCKED_INSTANT_NOW) - .setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE) - .setFlattenedRecord(flattenedRecord); + .setAttribute("one") + .setLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE) + .setConvertedLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE) + .setCreatedDate(MOCKED_INSTANT_NOW) + .setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE) + .setFlattenedRecord(flattenedRecord); assertThat(result, is(expectedRecord)); // The data in DDB is stored in converted time format assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("14 01 2019 14:00:00")); @@ -350,12 +392,12 @@ public void updateExistingRecordWithConditionExpressions() { Record result = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id"))); FlattenedRecord flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_UPDATE_ONE); Record expectedRecord = new Record().setId("id") - .setAttribute("one") - .setLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE) - .setConvertedLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE) - .setCreatedDate(MOCKED_INSTANT_NOW) - .setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE) - .setFlattenedRecord(flattenedRecord); + .setAttribute("one") + .setLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE) + .setConvertedLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE) + .setCreatedDate(MOCKED_INSTANT_NOW) + .setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE) + .setFlattenedRecord(flattenedRecord); assertThat(result, is(expectedRecord)); } @@ -373,9 +415,9 @@ public void putItemConditionTestFailure() { thrown.expect(ConditionalCheckFailedException.class); mappedTable.putItem(PutItemEnhancedRequest.builder(Record.class) - .item(new Record().setId("id").setAttribute("one")) - .conditionExpression(conditionExpression) - .build()); + .item(new Record().setId("id").setAttribute("one")) + .conditionExpression(conditionExpression) + .build()); } @@ -396,7 +438,7 @@ public void updateItemConditionTestFailure() { } @Test - public void incorrectTypeForAutoUpdateTimestampThrowsException(){ + public void incorrectTypeForAutoUpdateTimestampThrowsException() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Attribute 'lastUpdatedDate' of Class type class java.lang.String is not a suitable " @@ -415,6 +457,76 @@ public void incorrectTypeForAutoUpdateTimestampThrowsException(){ .build(); } + @Test + public void putItemFollowedByUpdatesShouldGenerateTimestampsOnNestedFields() { + mappedTable.putItem(r -> r.item(new Record().setId("id").setAttribute("one") + .setNestedRecord(new NestedStaticRecordWithList().setAttr("attribute")))); + mappedTable.getItem(r -> r.key(k -> k.partitionValue("id"))); + GetItemResponse itemAsStoredInDDB = getItemAsStoredFromDDB(); + + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("attr").s(), is("attribute")); + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("time").s(), + is(MOCKED_INSTANT_NOW.toString())); + + //First Update + Mockito.when(mockCLock.instant()).thenReturn(MOCKED_INSTANT_UPDATE_ONE); + + mappedTable.updateItem(r -> r.item(new Record().setId("id").setAttribute("one") + .setNestedRecord(new NestedStaticRecordWithList().setAttr( + "attribute1")))); + itemAsStoredInDDB = getItemAsStoredFromDDB(); + + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("attr").s(), is("attribute1")); + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("time").s(), + is(MOCKED_INSTANT_UPDATE_ONE.toString())); + + //Second Update + Mockito.when(mockCLock.instant()).thenReturn(MOCKED_INSTANT_UPDATE_TWO); + mappedTable.updateItem(r -> r.item(new Record().setId("id").setAttribute("one") + .setNestedRecord(new NestedStaticRecordWithList().setAttr( + "attribute2")))); + itemAsStoredInDDB = getItemAsStoredFromDDB(); + + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("attr").s(), is("attribute2")); + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("time").s(), + is(MOCKED_INSTANT_UPDATE_TWO.toString())); + } + + @Test + public void putItemFollowedByUpdatesShouldGenerateTimestampsOnNestedFieldsList() { + mappedTable.putItem(r -> r.item(new Record().setId("id").setAttribute("one") + .setNestedRecord(new NestedStaticRecordWithList().setAttr("attribute")))); + mappedTable.getItem(r -> r.key(k -> k.partitionValue("id"))); + GetItemResponse itemAsStoredInDDB = getItemAsStoredFromDDB(); + + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("attr").s(), is("attribute")); + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("time").s(), + is(MOCKED_INSTANT_NOW.toString())); + + //First Update + Mockito.when(mockCLock.instant()).thenReturn(MOCKED_INSTANT_UPDATE_ONE); + + mappedTable.updateItem(r -> r.item(new Record().setId("id").setAttribute("one") + .setNestedRecord(new NestedStaticRecordWithList().setAttr( + "attribute1")))); + itemAsStoredInDDB = getItemAsStoredFromDDB(); + + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("attr").s(), is("attribute1")); + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("time").s(), + is(MOCKED_INSTANT_UPDATE_ONE.toString())); + + //Second Update + Mockito.when(mockCLock.instant()).thenReturn(MOCKED_INSTANT_UPDATE_TWO); + mappedTable.updateItem(r -> r.item(new Record().setId("id").setAttribute("one") + .setNestedRecord(new NestedStaticRecordWithList().setAttr( + "attribute2")))); + itemAsStoredInDDB = getItemAsStoredFromDDB(); + + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("attr").s(), is("attribute2")); + assertThat(itemAsStoredInDDB.item().get("nestedRecord").m().get("time").s(), + is(MOCKED_INSTANT_UPDATE_TWO.toString())); + } + private GetItemResponse getItemAsStoredFromDDB() { Map key = new HashMap<>(); key.put("id", AttributeValue.builder().s("id").build()); @@ -424,6 +536,43 @@ private GetItemResponse getItemAsStoredFromDDB() { .consistentRead(true).build()); } + private static class FlattenedRecord { + private Instant generated; + + public Instant getGenerated() { + return generated; + } + + public FlattenedRecord setGenerated(Instant generated) { + this.generated = generated; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FlattenedRecord that = (FlattenedRecord) o; + return Objects.equals(generated, that.generated); + } + + @Override + public int hashCode() { + return Objects.hash(generated); + } + + @Override + public String toString() { + return "FlattenedRecord{" + + "generated=" + generated + + '}'; + } + } + private static class Record { private String id; private String attribute; @@ -432,6 +581,7 @@ private static class Record { private Instant convertedLastUpdatedDate; private Instant lastUpdatedDateInEpochMillis; private FlattenedRecord flattenedRecord; + private NestedStaticRecordWithList nestedLevel1; private String getId() { return id; @@ -496,6 +646,15 @@ public Record setFlattenedRecord(FlattenedRecord flattenedRecord) { return this; } + public NestedStaticRecordWithList getNestedRecord() { + return nestedLevel1; + } + + public Record setNestedRecord(NestedStaticRecordWithList nestedLevel1) { + this.nestedLevel1 = nestedLevel1; + return this; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -511,13 +670,14 @@ public boolean equals(Object o) { Objects.equals(createdDate, record.createdDate) && Objects.equals(lastUpdatedDateInEpochMillis, record.lastUpdatedDateInEpochMillis) && Objects.equals(convertedLastUpdatedDate, record.convertedLastUpdatedDate) && - Objects.equals(flattenedRecord, record.flattenedRecord); + Objects.equals(flattenedRecord, record.flattenedRecord) && + Objects.equals(nestedLevel1, record.nestedLevel1); } @Override public int hashCode() { return Objects.hash(id, attribute, lastUpdatedDate, createdDate, lastUpdatedDateInEpochMillis, - convertedLastUpdatedDate, flattenedRecord); + convertedLastUpdatedDate, flattenedRecord, nestedLevel1); } @Override @@ -530,43 +690,7 @@ public String toString() { ", convertedLastUpdatedDate=" + convertedLastUpdatedDate + ", lastUpdatedDateInEpochMillis=" + lastUpdatedDateInEpochMillis + ", flattenedRecord=" + flattenedRecord + - '}'; - } - } - - private static class FlattenedRecord { - private Instant generated; - - public Instant getGenerated() { - return generated; - } - - public FlattenedRecord setGenerated(Instant generated) { - this.generated = generated; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FlattenedRecord that = (FlattenedRecord) o; - return Objects.equals(generated, that.generated); - } - - @Override - public int hashCode() { - return Objects.hash(generated); - } - - @Override - public String toString() { - return "FlattenedRecord{" + - "generated=" + generated + + ", nestedRecord=" + nestedLevel1 + '}'; } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutogeneratedTimestampTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutogeneratedTimestampTest.java new file mode 100644 index 000000000000..1395c7e5bf51 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutogeneratedTimestampTest.java @@ -0,0 +1,595 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.enhanced.dynamodb.functionaltests; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hamcrest.CoreMatchers.everyItem; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNotNull; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.ATTR_CHILD1; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.ATTR_CHILD2; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.ATTR_LEVEL1; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.ATTR_LEVEL2; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.ATTR_LEVEL3; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.ATTR_LEVEL4; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.CHILD1_KEY; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.CHILD2_KEY; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.ID_1; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.LEVEL2_KEY; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedImmutableRecordWithMap; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.SimpleImmutableRecordWithMap; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.TIME_ATTR; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildBeanSchemaForNestedRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildBeanSchemaForSimpleRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildImmutableSchemaForNestedRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildImmutableSchemaForSimpleRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildNestedBeanRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildNestedBeanRecordWithMap; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildNestedImmutableRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildNestedImmutableRecordWithMap; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildNestedStaticRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildSimpleBeanRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildSimpleBeanRecordWithMap; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildSimpleImmutableRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildSimpleImmutableRecordWithMap; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildSimpleStaticRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildStaticImmutableSchemaForNestedRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildStaticImmutableSchemaForSimpleRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildStaticSchemaForNestedRecordWithList; +import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.buildStaticSchemaForSimpleRecordWithList; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.Mockito; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedBeanLevel2RecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedBeanLevel3RecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedBeanLevel4Record; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedBeanRecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedBeanRecordWithMap; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedImmutableLevel2RecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedImmutableLevel3RecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedImmutableLevel4Record; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedImmutableRecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedStaticLevel2RecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedStaticLevel3RecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedStaticLevel4Record; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.NestedStaticRecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.SimpleBeanRecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.SimpleBeanRecordWithMap; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.SimpleImmutableRecordWithList; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.AutogeneratedTimestampModels.SimpleStaticRecordWithList; +import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException; + +@RunWith(Parameterized.class) +public class AutogeneratedTimestampTest extends LocalDynamoDbSyncTestBase { + + // Test configuration constants + private static final String TIMESTAMP_STRING = "2019-01-13T14:00:00Z"; + private static final String INVALID_SCHEMA_ERROR = "Invalid table schema"; + + // Table name suffixes + private static final String BASE_TABLE_NAME = "autogen-timestamp-test"; + private static final String SIMPLE_BEAN_TABLE_SUFFIX = "-simple-bean-"; + private static final String NESTED_BEAN_TABLE_SUFFIX = "-nested-bean-"; + private static final String SIMPLE_IMMUTABLE_TABLE_SUFFIX = "-simple-immutable-"; + private static final String NESTED_IMMUTABLE_TABLE_SUFFIX = "-nested-immutable-"; + private static final String SIMPLE_STATIC_TABLE_SUFFIX = "-simple-static-"; + private static final String NESTED_STATIC_TABLE_SUFFIX = "-nested-static-"; + private static final String SIMPLE_STATIC_IMMUTABLE_TABLE_SUFFIX = "-simple-static-immutable-"; + private static final String NESTED_STATIC_IMMUTABLE_TABLE_SUFFIX = "-nested-static-immutable-"; + + private static final Clock mockClock = Mockito.mock(Clock.class); + private static final Instant MOCKED_INSTANT_NOW = + Instant.now(Clock.fixed(Instant.parse(TIMESTAMP_STRING), ZoneOffset.UTC)); + private DynamoDbEnhancedClient enhancedClient; + private String currentTestTableName; + + private enum RecordLevel {SIMPLE, NESTED} + + private enum SchemaType {BEAN, IMMUTABLE, STATIC, STATIC_IMMUTABLE} + + @Parameterized.Parameters(name = "{0}-{1}") + public static Collection data() { + return Arrays.stream(SchemaType.values()) + .flatMap(schema -> Arrays.stream(RecordLevel.values()) + .map(level -> new Object[] {schema, level})) + .collect(Collectors.toList()); + } + + @Parameterized.Parameter(0) + public SchemaType schemaType; + + @Parameterized.Parameter(1) + public RecordLevel recordLevel; + + @Before + public void beforeClass() { + Mockito.when(mockClock.instant()).thenReturn(MOCKED_INSTANT_NOW); + enhancedClient = DynamoDbEnhancedClient.builder() + .dynamoDbClient(getDynamoDbClient()) + .extensions(AutoGeneratedTimestampRecordExtension.builder() + .baseClock(mockClock) + .build()) + .build(); + } + + @After + public void deleteTable() { + try { + if (currentTestTableName != null) { + getDynamoDbClient().deleteTable(r -> r.tableName(currentTestTableName)); + } + } catch (ResourceNotFoundException e) { + // Table didn't get created, ignore. + } + } + + + @Test + public void shouldPopulateTimestamps_forRecordWithList() { + getTestCaseForRecordWithList(schemaType, recordLevel).run(); + } + + @Test + public void shouldThrowException_forRecordWithSet() { + if (schemaType == SchemaType.BEAN || schemaType == SchemaType.IMMUTABLE) { + assertThatThrownBy(() -> getTestCaseForRecordWithSet(schemaType, recordLevel).run()) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Converter not found for EnhancedType(java.util.Set<"); + } else { + assertThatThrownBy(() -> getTestCaseForRecordWithSet(schemaType, recordLevel).run()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("SetAttributeConverter cannot be created with a parameterized type of '"); + } + } + + @Test + public void shouldThrowException_forRecordWithMap() { + if (schemaType == SchemaType.BEAN || schemaType == SchemaType.IMMUTABLE) { + assertThatThrownBy(() -> getTestCaseForRecordWithMap(schemaType, recordLevel).run()) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expected: is <2019-01-13T14:00:00Z>\n but: was null"); + } else { + assertThatThrownBy(() -> getTestCaseForRecordWithMap(schemaType, recordLevel).run()) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Converter not found for EnhancedType(java.util.Map { + throw new IllegalArgumentException(INVALID_SCHEMA_ERROR); + }; + } + } + + private Runnable getTestCaseForRecordWithSet(SchemaType schema, RecordLevel level) { + switch (schema) { + case BEAN: + return (level == RecordLevel.SIMPLE) + ? AutogeneratedTimestampModels::buildBeanSchemaForSimpleRecordWithSet + : AutogeneratedTimestampModels::buildBeanSchemaForNestedRecordWithSet; + case IMMUTABLE: + return (level == RecordLevel.SIMPLE) + ? AutogeneratedTimestampModels::buildImmutableSchemaForSimpleRecordWithSet + : AutogeneratedTimestampModels::buildImmutableSchemaForNestedRecordWithSet; + case STATIC: + return (level == RecordLevel.SIMPLE) + ? AutogeneratedTimestampModels::buildStaticSchemaForSimpleRecordWithSet + : AutogeneratedTimestampModels::buildStaticSchemaForNestedRecordWithSet; + case STATIC_IMMUTABLE: + return (level == RecordLevel.SIMPLE) + ? AutogeneratedTimestampModels::buildStaticImmutableSchemaForSimpleRecordWithSet + : AutogeneratedTimestampModels::buildStaticImmutableSchemaForNestedRecordWithSet; + default: + return () -> { + throw new IllegalArgumentException(INVALID_SCHEMA_ERROR); + }; + } + } + + private Runnable getTestCaseForRecordWithMap(SchemaType schema, RecordLevel level) { + switch (schema) { + case BEAN: + return (level == RecordLevel.SIMPLE) + ? this::testAutogeneratedTimestamp_givenBeanSchema_onSimpleRecordWithMap_throwsException + : this::testAutogeneratedTimestamp_givenBeanSchema_onNestedRecordWithMap_throwsException; + case IMMUTABLE: + return (level == RecordLevel.SIMPLE) + ? this::testAutogeneratedTimestamp_givenImmutableSchema_onSimpleRecordWithMap_throwsException + : this::testAutogeneratedTimestamp_givenImmutableSchema_onNestedRecordWithMap_throwsException; + case STATIC: + return (level == RecordLevel.SIMPLE) + ? AutogeneratedTimestampModels::buildStaticSchemaForSimpleRecordWithMap + : AutogeneratedTimestampModels::buildStaticSchemaForNestedRecordWithMap; + case STATIC_IMMUTABLE: + return (level == RecordLevel.SIMPLE) + ? AutogeneratedTimestampModels::buildStaticImmutableSchemaForSimpleRecordWithMap + : AutogeneratedTimestampModels::buildStaticImmutableSchemaForNestedRecordWithMap; + default: + return () -> { + throw new IllegalArgumentException(INVALID_SCHEMA_ERROR); + }; + } + } + + + // Bean table schema + Record with List + private void testAutogeneratedTimestamp_givenBeanSchema_onSimpleRecordWithList_populatesTimestamps() { + TableSchema schema = buildBeanSchemaForSimpleRecordWithList(); + DynamoDbTable table = createAndPut(SIMPLE_BEAN_TABLE_SUFFIX, + schema, + buildSimpleBeanRecordWithList()); + + SimpleBeanRecordWithList result = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(result.getAttr(), is(ATTR_LEVEL1)); + assertThat(result.getTime(), is(MOCKED_INSTANT_NOW)); + + assertNotNull(result.getChildList()); + assertThat(result.getChildList().get(0).getAttr(), is(ATTR_CHILD1)); + assertThat(result.getChildList().get(0).getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(result.getChildList().get(1).getAttr(), is(ATTR_CHILD2)); + assertThat(result.getChildList().get(1).getTime(), is(MOCKED_INSTANT_NOW)); + } + + private void testAutogeneratedTimestamp_givenBeanSchema_onNestedRecordWithList_populatesTimestamps() { + TableSchema schema = buildBeanSchemaForNestedRecordWithList(); + DynamoDbTable table = createAndPut(NESTED_BEAN_TABLE_SUFFIX, + schema, + buildNestedBeanRecordWithList()); + + NestedBeanRecordWithList level1 = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(level1, notNullValue()); + assertThat(level1.getAttr(), is(ATTR_LEVEL1)); + assertThat(level1.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2(), notNullValue()); + assertThat(level1.getLevel2().getAttr(), is(ATTR_LEVEL2)); + assertThat(level1.getLevel2().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2List(), notNullValue()); + assertThat(level1.getLevel2List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedBeanLevel2RecordWithList level2 = level1.getLevel2(); + assertThat(level2, notNullValue()); + assertThat(level2.getAttr(), is(ATTR_LEVEL2)); + assertThat(level2.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level2.getLevel3(), notNullValue()); + assertThat(level2.getLevel3().getAttr(), is(ATTR_LEVEL3)); + assertThat(level2.getLevel3().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level2.getLevel3List(), notNullValue()); + assertThat(level2.getLevel3List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedBeanLevel3RecordWithList level3 = level2.getLevel3(); + assertThat(level3, notNullValue()); + assertThat(level3.getAttr(), is(ATTR_LEVEL3)); + assertThat(level3.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level3.getLevel4(), notNullValue()); + assertThat(level3.getLevel4().getAttr(), is(ATTR_LEVEL4)); + assertThat(level3.getLevel4().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level3.getLevel4List(), notNullValue()); + assertThat(level3.getLevel4List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedBeanLevel4Record level4 = level3.getLevel4(); + assertThat(level4, notNullValue()); + assertThat(level4.getAttr(), is(ATTR_LEVEL4)); + assertThat(level4.getTime(), is(MOCKED_INSTANT_NOW)); + } + + // Bean table schema + Record with Map + private void testAutogeneratedTimestamp_givenBeanSchema_onSimpleRecordWithMap_throwsException() { + TableSchema schema = AutogeneratedTimestampModels.buildBeanSchemaForSimpleRecordWithMap(); + DynamoDbTable table = createAndPut(SIMPLE_BEAN_TABLE_SUFFIX, + schema, + buildSimpleBeanRecordWithMap()); + + SimpleBeanRecordWithMap result = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(result.getAttr(), is(ATTR_LEVEL1)); + assertThat(result.getTime(), is(MOCKED_INSTANT_NOW)); + + assertNotNull(result.getChildMap()); + assertThat(result.getChildMap().get(CHILD1_KEY).getAttr(), is(ATTR_CHILD1)); + assertThat(result.getChildMap().get(CHILD1_KEY).getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(result.getChildMap().get(CHILD2_KEY).getAttr(), is(ATTR_CHILD2)); + assertThat(result.getChildMap().get(CHILD2_KEY).getTime(), is(MOCKED_INSTANT_NOW)); + } + + private void testAutogeneratedTimestamp_givenBeanSchema_onNestedRecordWithMap_throwsException() { + TableSchema schema = AutogeneratedTimestampModels.buildBeanSchemaForNestedRecordWithMap(); + DynamoDbTable table = createAndPut(SIMPLE_BEAN_TABLE_SUFFIX, + schema, + buildNestedBeanRecordWithMap()); + + NestedBeanRecordWithMap level1 = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(level1, notNullValue()); + assertThat(level1.getAttr(), is(ATTR_LEVEL1)); + assertThat(level1.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2(), notNullValue()); + assertThat(level1.getLevel2().getAttr(), is(ATTR_LEVEL2)); + assertThat(level1.getLevel2().getTime(), is(MOCKED_INSTANT_NOW)); + assertNotNull(level1.getLevel2Map()); + assertThat(level1.getLevel2Map().get(LEVEL2_KEY).getAttr(), is(ATTR_LEVEL2)); + assertThat(level1.getLevel2Map().get(LEVEL2_KEY).getTime(), is(MOCKED_INSTANT_NOW)); + } + + // Immutable table schema + Record with Map + private void testAutogeneratedTimestamp_givenImmutableSchema_onSimpleRecordWithMap_throwsException() { + TableSchema schema = + AutogeneratedTimestampModels.buildImmutableSchemaForSimpleRecordWithMap(); + DynamoDbTable table = createAndPut(SIMPLE_IMMUTABLE_TABLE_SUFFIX, + schema, + buildSimpleImmutableRecordWithMap()); + + SimpleImmutableRecordWithMap result = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(result.getAttr(), is(ATTR_LEVEL1)); + assertThat(result.getTime(), is(MOCKED_INSTANT_NOW)); + + assertNotNull(result.getChildMap()); + assertThat(result.getChildMap().get(CHILD1_KEY).getAttr(), is(ATTR_CHILD1)); + assertThat(result.getChildMap().get(CHILD1_KEY).getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(result.getChildMap().get(CHILD2_KEY).getAttr(), is(ATTR_CHILD2)); + assertThat(result.getChildMap().get(CHILD2_KEY).getTime(), is(MOCKED_INSTANT_NOW)); + } + + private void testAutogeneratedTimestamp_givenImmutableSchema_onNestedRecordWithMap_throwsException() { + TableSchema schema = + AutogeneratedTimestampModels.buildImmutableSchemaForNestedRecordWithMap(); + DynamoDbTable table = createAndPut(NESTED_IMMUTABLE_TABLE_SUFFIX, + schema, + buildNestedImmutableRecordWithMap()); + + NestedImmutableRecordWithMap level1 = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(level1, notNullValue()); + assertThat(level1.getAttr(), is(ATTR_LEVEL1)); + assertThat(level1.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2(), notNullValue()); + assertThat(level1.getLevel2().getAttr(), is(ATTR_LEVEL2)); + assertThat(level1.getLevel2().getTime(), is(MOCKED_INSTANT_NOW)); + assertNotNull(level1.getLevel2Map()); + assertThat(level1.getLevel2Map().get(LEVEL2_KEY).getAttr(), is(ATTR_LEVEL2)); + assertThat(level1.getLevel2Map().get(LEVEL2_KEY).getTime(), is(MOCKED_INSTANT_NOW)); + } + + // Immutable table schema + Record with List + private void testAutogeneratedTimestamp_givenImmutableSchema_onSimpleRecordWithList_populatesTimestamps() { + TableSchema schema = buildImmutableSchemaForSimpleRecordWithList(); + DynamoDbTable table = createAndPut(SIMPLE_IMMUTABLE_TABLE_SUFFIX, + schema, + buildSimpleImmutableRecordWithList()); + + SimpleImmutableRecordWithList result = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(result.getAttr(), is(ATTR_LEVEL1)); + assertThat(result.getTime(), is(MOCKED_INSTANT_NOW)); + + assertNotNull(result.getChildList()); + assertThat(result.getChildList().get(0).getAttr(), is(ATTR_CHILD1)); + assertThat(result.getChildList().get(0).getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(result.getChildList().get(1).getAttr(), is(ATTR_CHILD2)); + assertThat(result.getChildList().get(1).getTime(), is(MOCKED_INSTANT_NOW)); + } + + private void testAutogeneratedTimestamp_givenImmutableSchema_onNestedRecordWithList_populatesTimestamps() { + TableSchema schema = buildImmutableSchemaForNestedRecordWithList(); + DynamoDbTable table = createAndPut(NESTED_IMMUTABLE_TABLE_SUFFIX, + schema, + buildNestedImmutableRecordWithList()); + + NestedImmutableRecordWithList level1 = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(level1, notNullValue()); + assertThat(level1.getAttr(), is(ATTR_LEVEL1)); + assertThat(level1.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2(), notNullValue()); + assertThat(level1.getLevel2().getAttr(), is(ATTR_LEVEL2)); + assertThat(level1.getLevel2().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2List(), notNullValue()); + assertThat(level1.getLevel2List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedImmutableLevel2RecordWithList level2 = level1.getLevel2(); + assertThat(level2, notNullValue()); + assertThat(level2.getAttr(), is(ATTR_LEVEL2)); + assertThat(level2.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level2.getLevel3(), notNullValue()); + assertThat(level2.getLevel3().getAttr(), is(ATTR_LEVEL3)); + assertThat(level2.getLevel3().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level2.getLevel3List(), notNullValue()); + assertThat(level2.getLevel3List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedImmutableLevel3RecordWithList level3 = level2.getLevel3(); + assertThat(level3, notNullValue()); + assertThat(level3.getAttr(), is(ATTR_LEVEL3)); + assertThat(level3.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level3.getLevel4(), notNullValue()); + assertThat(level3.getLevel4().getAttr(), is(ATTR_LEVEL4)); + assertThat(level3.getLevel4().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level3.getLevel4List(), notNullValue()); + assertThat(level3.getLevel4List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedImmutableLevel4Record level4 = level3.getLevel4(); + assertThat(level4, notNullValue()); + assertThat(level4.getAttr(), is(ATTR_LEVEL4)); + assertThat(level4.getTime(), is(MOCKED_INSTANT_NOW)); + } + + // Static table schema + Record with List + private void testAutogeneratedTimestamp_givenStaticSchema_onSimpleRecordWithList_populatesTimestamps() { + DynamoDbTable table = createAndPut(SIMPLE_STATIC_TABLE_SUFFIX, + buildStaticSchemaForSimpleRecordWithList(), + buildSimpleStaticRecordWithList()); + + SimpleStaticRecordWithList result = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(result.getAttr(), is(ATTR_LEVEL1)); + assertThat(result.getTime(), is(MOCKED_INSTANT_NOW)); + + assertNotNull(result.getChildList()); + assertThat(result.getChildList().get(0).getAttr(), is(ATTR_CHILD1)); + assertThat(result.getChildList().get(0).getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(result.getChildList().get(1).getAttr(), is(ATTR_CHILD2)); + assertThat(result.getChildList().get(1).getTime(), is(MOCKED_INSTANT_NOW)); + } + + private void testAutogeneratedTimestamp_givenStaticImmutableSchema_onSimpleRecordWithList_populatesTimestamps() { + DynamoDbTable table = createAndPut(SIMPLE_STATIC_IMMUTABLE_TABLE_SUFFIX, + buildStaticImmutableSchemaForSimpleRecordWithList(), + buildSimpleImmutableRecordWithList()); + + SimpleImmutableRecordWithList result = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(result.getAttr(), is(ATTR_LEVEL1)); + assertThat(result.getTime(), is(MOCKED_INSTANT_NOW)); + + assertNotNull(result.getChildList()); + assertThat(result.getChildList().get(0).getAttr(), is(ATTR_CHILD1)); + assertThat(result.getChildList().get(0).getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(result.getChildList().get(1).getAttr(), is(ATTR_CHILD2)); + assertThat(result.getChildList().get(1).getTime(), is(MOCKED_INSTANT_NOW)); + } + + private void testAutogeneratedTimestamp_givenStaticSchema_onNestedRecordWithList_populatesTimestamps() { + DynamoDbTable table = createAndPut(NESTED_STATIC_TABLE_SUFFIX, + buildStaticSchemaForNestedRecordWithList(), + buildNestedStaticRecordWithList()); + + NestedStaticRecordWithList level1 = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(level1, notNullValue()); + assertThat(level1.getAttr(), is(ATTR_LEVEL1)); + assertThat(level1.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2(), notNullValue()); + assertThat(level1.getLevel2().getAttr(), is(ATTR_LEVEL2)); + assertThat(level1.getLevel2().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2List(), notNullValue()); + assertThat(level1.getLevel2List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedStaticLevel2RecordWithList level2 = level1.getLevel2(); + assertThat(level2, notNullValue()); + assertThat(level2.getAttr(), is(ATTR_LEVEL2)); + assertThat(level2.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level2.getLevel3(), notNullValue()); + assertThat(level2.getLevel3().getAttr(), is(ATTR_LEVEL3)); + assertThat(level2.getLevel3().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level2.getLevel3List(), notNullValue()); + assertThat(level2.getLevel3List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedStaticLevel3RecordWithList level3 = level2.getLevel3(); + assertThat(level3, notNullValue()); + assertThat(level3.getAttr(), is(ATTR_LEVEL3)); + assertThat(level3.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level3.getLevel4(), notNullValue()); + assertThat(level3.getLevel4().getAttr(), is(ATTR_LEVEL4)); + assertThat(level3.getLevel4().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level3.getLevel4List(), notNullValue()); + assertThat(level3.getLevel4List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedStaticLevel4Record level4 = level3.getLevel4(); + assertThat(level4, notNullValue()); + assertThat(level4.getAttr(), is(ATTR_LEVEL4)); + assertThat(level4.getTime(), is(MOCKED_INSTANT_NOW)); + } + + private void testAutogeneratedTimestamp_givenStaticImmutableSchema_onNestedRecordWithList_populatesTimestamps() { + DynamoDbTable table = createAndPut(NESTED_STATIC_IMMUTABLE_TABLE_SUFFIX, + buildStaticImmutableSchemaForNestedRecordWithList(), + buildNestedImmutableRecordWithList()); + + NestedImmutableRecordWithList level1 = table.getItem(r -> r.key(k -> k.partitionValue(ID_1))); + + assertThat(level1, notNullValue()); + assertThat(level1.getAttr(), is(ATTR_LEVEL1)); + assertThat(level1.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2(), notNullValue()); + assertThat(level1.getLevel2().getAttr(), is(ATTR_LEVEL2)); + assertThat(level1.getLevel2().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level1.getLevel2List(), notNullValue()); + assertThat(level1.getLevel2List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedImmutableLevel2RecordWithList level2 = level1.getLevel2(); + assertThat(level2, notNullValue()); + assertThat(level2.getAttr(), is(ATTR_LEVEL2)); + assertThat(level2.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level2.getLevel3(), notNullValue()); + assertThat(level2.getLevel3().getAttr(), is(ATTR_LEVEL3)); + assertThat(level2.getLevel3().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level2.getLevel3List(), notNullValue()); + assertThat(level2.getLevel3List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedImmutableLevel3RecordWithList level3 = level2.getLevel3(); + assertThat(level3, notNullValue()); + assertThat(level3.getAttr(), is(ATTR_LEVEL3)); + assertThat(level3.getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level3.getLevel4(), notNullValue()); + assertThat(level3.getLevel4().getAttr(), is(ATTR_LEVEL4)); + assertThat(level3.getLevel4().getTime(), is(MOCKED_INSTANT_NOW)); + assertThat(level3.getLevel4List(), notNullValue()); + assertThat(level3.getLevel4List(), everyItem(hasProperty(TIME_ATTR, is(MOCKED_INSTANT_NOW)))); + + NestedImmutableLevel4Record level4 = level3.getLevel4(); + assertThat(level4, notNullValue()); + assertThat(level4.getAttr(), is(ATTR_LEVEL4)); + assertThat(level4.getTime(), is(MOCKED_INSTANT_NOW)); + } + + // Helper for table creation + item insert + private DynamoDbTable createAndPut(String tableSuffix, TableSchema schema, T item) { + currentTestTableName = BASE_TABLE_NAME + tableSuffix + System.nanoTime(); + DynamoDbTable table = enhancedClient.table(currentTestTableName, schema); + table.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput())); + table.putItem(item); + return table; + } +} diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/UpdateBehaviorTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/UpdateBehaviorTest.java index 196d38282277..15342c3f25e5 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/UpdateBehaviorTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/UpdateBehaviorTest.java @@ -2,9 +2,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.common.collect.ImmutableList; import java.time.Instant; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.After; @@ -16,6 +20,7 @@ import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension; import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.CompositeRecord; import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FlattenRecord; +import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.NestedRecordListElement; import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.NestedRecordWithUpdateBehavior; import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.RecordWithUpdateBehaviors; import software.amazon.awssdk.enhanced.dynamodb.internal.client.ExtensionResolver; @@ -62,11 +67,16 @@ public void deleteTable() { @Test public void updateBehaviors_firstUpdate() { - Instant currentTime = Instant.now(); + Instant currentTime = Instant.now().minusMillis(1); + NestedRecordWithUpdateBehavior nestedRecord = new NestedRecordWithUpdateBehavior(); + nestedRecord.setId("id167"); + nestedRecord.setNestedUpdateBehaviorAttribute(TEST_BEHAVIOUR_ATTRIBUTE); + RecordWithUpdateBehaviors record = new RecordWithUpdateBehaviors(); record.setId("id123"); record.setCreatedOn(INSTANT_1); record.setLastUpdatedOn(INSTANT_2); + record.setNestedRecord(nestedRecord); mappedTable.updateItem(record); RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(record); @@ -81,28 +91,57 @@ public void updateBehaviors_firstUpdate() { assertThat(persistedRecord.getLastAutoUpdatedOnMillis().getEpochSecond()).isGreaterThanOrEqualTo(currentTime.getEpochSecond()); assertThat(persistedRecord.getCreatedAutoUpdateOn()).isAfterOrEqualTo(currentTime); + + assertThat(persistedRecord.getNestedRecord().getId()).isEqualTo("id167"); + assertThat(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()).isAfterOrEqualTo(currentTime); + assertThat(persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute()).isAfterOrEqualTo(currentTime); + assertThat(persistedRecord.getCreatedAutoUpdateOn()).isAfterOrEqualTo(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()); + assertThat(persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute()).isEqualTo(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()); } @Test public void updateBehaviors_secondUpdate() { - Instant beforeUpdateInstant = Instant.now(); + Instant beforeUpdateInstant = Instant.now().minusMillis(1); + + NestedRecordWithUpdateBehavior secondNestedRecord = new NestedRecordWithUpdateBehavior(); + secondNestedRecord.setId("id199"); + secondNestedRecord.setNestedUpdateBehaviorAttribute(TEST_BEHAVIOUR_ATTRIBUTE); + + NestedRecordWithUpdateBehavior nestedRecord = new NestedRecordWithUpdateBehavior(); + nestedRecord.setId("id155"); + nestedRecord.setNestedUpdateBehaviorAttribute(TEST_BEHAVIOUR_ATTRIBUTE); + nestedRecord.setNestedRecord(secondNestedRecord); + RecordWithUpdateBehaviors record = new RecordWithUpdateBehaviors(); record.setId("id123"); record.setCreatedOn(INSTANT_1); record.setLastUpdatedOn(INSTANT_2); + record.setNestedRecord(nestedRecord); mappedTable.updateItem(record); RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(record); assertThat(persistedRecord.getVersion()).isEqualTo(1L); + Instant firstUpdatedTime = persistedRecord.getLastAutoUpdatedOn(); Instant createdAutoUpdateOn = persistedRecord.getCreatedAutoUpdateOn(); + assertThat(firstUpdatedTime).isAfterOrEqualTo(beforeUpdateInstant); assertThat(persistedRecord.getFormattedLastAutoUpdatedOn().getEpochSecond()) .isGreaterThanOrEqualTo(beforeUpdateInstant.getEpochSecond()); + assertThat(persistedRecord.getNestedRecord().getNestedUpdateBehaviorAttribute()).isNotNull(); + assertThat(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()) + .isEqualTo(firstUpdatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute()) + .isEqualTo(firstUpdatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedRecord().getNestedCreatedTimeAttribute()) + .isEqualTo(firstUpdatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedRecord().getNestedUpdatedTimeAttribute()) + .isEqualTo(firstUpdatedTime); record.setVersion(1L); record.setCreatedOn(INSTANT_2); record.setLastUpdatedOn(INSTANT_2); + record.setNestedRecord(nestedRecord); mappedTable.updateItem(record); persistedRecord = mappedTable.getItem(record); @@ -113,6 +152,14 @@ public void updateBehaviors_secondUpdate() { Instant secondUpdatedTime = persistedRecord.getLastAutoUpdatedOn(); assertThat(secondUpdatedTime).isAfterOrEqualTo(firstUpdatedTime); assertThat(persistedRecord.getCreatedAutoUpdateOn()).isEqualTo(createdAutoUpdateOn); + assertThat(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()) + .isEqualTo(secondUpdatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute()) + .isEqualTo(secondUpdatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedRecord().getNestedCreatedTimeAttribute()) + .isEqualTo(secondUpdatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedRecord().getNestedUpdatedTimeAttribute()) + .isEqualTo(secondUpdatedTime); } @Test @@ -164,7 +211,7 @@ public void updateBehaviors_transactWriteItems_secondUpdate() { @Test public void when_updatingNestedObjectWithSingleLevel_existingInformationIsPreserved_scalar_only_update() { - + Instant currentTime = Instant.now().minusMillis(1); NestedRecordWithUpdateBehavior nestedRecord = createNestedWithDefaults("id456", 5L); RecordWithUpdateBehaviors record = new RecordWithUpdateBehaviors(); @@ -173,26 +220,35 @@ public void when_updatingNestedObjectWithSingleLevel_existingInformationIsPreser mappedTable.putItem(record); + RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); + + Instant nestedCreatedTime = persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute(); + Instant nestedUpdatedTime = persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute(); + assertThat(nestedCreatedTime).isAfter(currentTime); + assertThat(nestedUpdatedTime).isEqualTo(nestedCreatedTime); + NestedRecordWithUpdateBehavior updatedNestedRecord = new NestedRecordWithUpdateBehavior(); long updatedNestedCounter = 10L; updatedNestedRecord.setNestedCounter(updatedNestedCounter); - RecordWithUpdateBehaviors update_record = new RecordWithUpdateBehaviors(); - update_record.setId("id123"); - update_record.setVersion(1L); - update_record.setNestedRecord(updatedNestedRecord); + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setNestedRecord(updatedNestedRecord); - mappedTable.updateItem(r -> r.item(update_record).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); + mappedTable.updateItem(r -> r.item(updateRecord).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); - RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); + persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); verifySingleLevelNestingTargetedUpdateBehavior(persistedRecord.getNestedRecord(), updatedNestedCounter, - TEST_BEHAVIOUR_ATTRIBUTE, INSTANT_1); + TEST_BEHAVIOUR_ATTRIBUTE, currentTime); + assertThat(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()).isEqualTo(nestedCreatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute()).isAfter(nestedUpdatedTime); } @Test public void when_updatingNestedObjectWithSingleLevel_default_mode_update_newMapCreated() { - + Instant currentTime = Instant.now().minusMillis(1); NestedRecordWithUpdateBehavior nestedRecord = createNestedWithDefaults("id456", 5L); RecordWithUpdateBehaviors record = new RecordWithUpdateBehaviors(); @@ -201,25 +257,34 @@ public void when_updatingNestedObjectWithSingleLevel_default_mode_update_newMapC mappedTable.putItem(record); + RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); + + Instant nestedCreatedTime = persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute(); + Instant nestedUpdatedTime = persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute(); + assertThat(nestedCreatedTime).isNotNull(); + assertThat(nestedUpdatedTime).isEqualTo(nestedCreatedTime); + NestedRecordWithUpdateBehavior updatedNestedRecord = new NestedRecordWithUpdateBehavior(); long updatedNestedCounter = 10L; updatedNestedRecord.setNestedCounter(updatedNestedCounter); - RecordWithUpdateBehaviors update_record = new RecordWithUpdateBehaviors(); - update_record.setId("id123"); - update_record.setVersion(1L); - update_record.setNestedRecord(updatedNestedRecord); + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setNestedRecord(updatedNestedRecord); - mappedTable.updateItem(r -> r.item(update_record).ignoreNullsMode(IgnoreNullsMode.DEFAULT)); + mappedTable.updateItem(r -> r.item(updateRecord).ignoreNullsMode(IgnoreNullsMode.DEFAULT)); - RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); + persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); - verifySingleLevelNestingTargetedUpdateBehavior(persistedRecord.getNestedRecord(), updatedNestedCounter, null, null); + verifySingleLevelNestingTargetedUpdateBehavior(persistedRecord.getNestedRecord(), updatedNestedCounter, null, currentTime); + assertThat(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()).isAfter(nestedCreatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute()).isAfter(nestedUpdatedTime); } @Test public void when_updatingNestedObjectWithSingleLevel_with_no_mode_update_newMapCreated() { - + Instant currentTime = Instant.now().minusMillis(1); NestedRecordWithUpdateBehavior nestedRecord = createNestedWithDefaults("id456", 5L); RecordWithUpdateBehaviors record = new RecordWithUpdateBehaviors(); @@ -232,16 +297,73 @@ public void when_updatingNestedObjectWithSingleLevel_with_no_mode_update_newMapC long updatedNestedCounter = 10L; updatedNestedRecord.setNestedCounter(updatedNestedCounter); - RecordWithUpdateBehaviors update_record = new RecordWithUpdateBehaviors(); - update_record.setId("id123"); - update_record.setVersion(1L); - update_record.setNestedRecord(updatedNestedRecord); + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setNestedRecord(updatedNestedRecord); - mappedTable.updateItem(r -> r.item(update_record)); + mappedTable.updateItem(r -> r.item(updateRecord)); RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); - verifySingleLevelNestingTargetedUpdateBehavior(persistedRecord.getNestedRecord(), updatedNestedCounter, null, null); + verifySingleLevelNestingTargetedUpdateBehavior(persistedRecord.getNestedRecord(), updatedNestedCounter, null, currentTime); + } + + @Test + public void when_updatingNestedObjectList_no_matter_mode_update_newListCreated_with_timestampGenerated() { + Instant currentTime = Instant.now().minusMillis(1); + NestedRecordWithUpdateBehavior nestedRecord = createNestedWithDefaults("id456", 5L); + nestedRecord.setNestedUpdatedTimeAttribute(null); + NestedRecordListElement firstElement = new NestedRecordListElement(); + firstElement.setId("id1"); + firstElement.setAttribute("attr1"); + NestedRecordListElement secondElement = new NestedRecordListElement(); + secondElement.setId("id2"); + secondElement.setAttribute("attr2"); + nestedRecord.setNestedRecordList(ImmutableList.of(firstElement, secondElement)); + + RecordWithUpdateBehaviors record = new RecordWithUpdateBehaviors(); + record.setId("id123"); + record.setNestedRecord(nestedRecord); + record.setNestedRecordList(ImmutableList.of(firstElement, secondElement)); + + mappedTable.putItem(record); + + RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); + + List nestedRecordList = persistedRecord.getNestedRecord().getNestedRecordList(); + Instant firstOperationTime = nestedRecordList.get(0).getTimeAttributeElement(); + + assertThat(persistedRecord.getNestedRecordList().get(0).getTimeAttributeElement()).isAfter(currentTime); + assertThat(persistedRecord.getNestedRecordList().get(1).getTimeAttributeElement()).isAfter(currentTime); + assertThat(nestedRecordList.get(0).getTimeAttributeElement()).isAfter(currentTime); + assertThat(nestedRecordList.get(1).getTimeAttributeElement()).isEqualTo(firstOperationTime); + + NestedRecordWithUpdateBehavior updatedNestedRecord = new NestedRecordWithUpdateBehavior(); + long updatedNestedCounter = 10L; + updatedNestedRecord.setNestedUpdatedTimeAttribute(null); + firstElement.setAttribute("attr44"); + secondElement.setAttribute("attr55"); + updatedNestedRecord.setNestedCounter(updatedNestedCounter); + updatedNestedRecord.setNestedRecordList(ImmutableList.of(firstElement, secondElement)); + + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setNestedRecord(updatedNestedRecord); + updateRecord.setNestedRecordList(ImmutableList.of(firstElement)); + + mappedTable.updateItem(r -> r.item(updateRecord).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); + + persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); + + nestedRecordList = persistedRecord.getNestedRecord().getNestedRecordList(); + + assertThat(persistedRecord.getNestedRecordList()).hasSize(1); + assertThat(persistedRecord.getNestedRecordList().get(0).getTimeAttributeElement()).isAfter(firstOperationTime); + assertThat(nestedRecordList).hasSize(2); + assertThat(nestedRecordList.get(0).getTimeAttributeElement()).isAfter(firstOperationTime); + assertThat(nestedRecordList.get(1).getTimeAttributeElement()).isAfter(firstOperationTime); } @Test @@ -258,15 +380,59 @@ public void when_updatingNestedObjectToEmptyWithSingleLevel_existingInformationI NestedRecordWithUpdateBehavior updatedNestedRecord = new NestedRecordWithUpdateBehavior(); - RecordWithUpdateBehaviors update_record = new RecordWithUpdateBehaviors(); - update_record.setId("id123"); - update_record.setVersion(1L); - update_record.setNestedRecord(updatedNestedRecord); + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setNestedRecord(updatedNestedRecord); + + mappedTable.updateItem(r -> r.item(updateRecord).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); + + RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); + assertThat(persistedRecord.getNestedRecord()).isNotNull(); + assertThat(persistedRecord.getNestedRecord().getId()).isNull(); + assertThat(persistedRecord.getNestedRecord().getNestedCounter()).isNull(); + assertThat(persistedRecord.getNestedRecord().getNestedUpdateBehaviorAttribute()).isNull(); + assertThat(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()).isNotNull(); + } + + @Test + public void when_updatingNestedObjectWithSingleLevel_updateBehaviorIsChecked_scalar_only_update() { + Instant currentTime = Instant.now().minusMillis(1); + NestedRecordWithUpdateBehavior nestedRecord = createNestedWithDefaults("id456", 5L); + + RecordWithUpdateBehaviors record = new RecordWithUpdateBehaviors(); + record.setId("id123"); + record.setNestedRecord(nestedRecord); - mappedTable.updateItem(r -> r.item(update_record).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); + mappedTable.putItem(record); RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); - assertThat(persistedRecord.getNestedRecord()).isNull(); + + Instant nestedCreatedTime = persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute(); + Instant nestedUpdatedTime = persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute(); + assertThat(nestedCreatedTime).isAfter(currentTime); + assertThat(nestedUpdatedTime).isEqualTo(nestedCreatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedUpdateBehaviorAttribute()).isEqualTo(TEST_BEHAVIOUR_ATTRIBUTE); + + NestedRecordWithUpdateBehavior updatedNestedRecord = new NestedRecordWithUpdateBehavior(); + long updatedNestedCounter = 10L; + updatedNestedRecord.setNestedCounter(updatedNestedCounter); + updatedNestedRecord.setNestedUpdateBehaviorAttribute(TEST_BEHAVIOUR_ATTRIBUTE + "updated"); + + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setNestedRecord(updatedNestedRecord); + + mappedTable.updateItem(r -> r.item(updateRecord).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); + + persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); + + //WRITE_IF_NOT_EXISTS detected on createdTimeAttribute and updateBehaviorAttribute -> not changed + assertThat(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()).isEqualTo(nestedCreatedTime); + assertThat(persistedRecord.getNestedRecord().getNestedUpdateBehaviorAttribute()).isEqualTo(TEST_BEHAVIOUR_ATTRIBUTE); + + assertThat(persistedRecord.getNestedRecord().getNestedUpdatedTimeAttribute()).isAfter(nestedUpdatedTime); } private NestedRecordWithUpdateBehavior createNestedWithDefaults(String id, Long counter) { @@ -274,7 +440,6 @@ private NestedRecordWithUpdateBehavior createNestedWithDefaults(String id, Long nestedRecordWithDefaults.setId(id); nestedRecordWithDefaults.setNestedCounter(counter); nestedRecordWithDefaults.setNestedUpdateBehaviorAttribute(TEST_BEHAVIOUR_ATTRIBUTE); - nestedRecordWithDefaults.setNestedTimeAttribute(INSTANT_1); return nestedRecordWithDefaults; } @@ -282,31 +447,34 @@ private NestedRecordWithUpdateBehavior createNestedWithDefaults(String id, Long private void verifyMultipleLevelNestingTargetedUpdateBehavior(NestedRecordWithUpdateBehavior nestedRecord, long updatedOuterNestedCounter, long updatedInnerNestedCounter, - String test_behav_attribute, - Instant expected_time) { + String testBehaviorAttribute, + Instant expectedTime) { assertThat(nestedRecord).isNotNull(); assertThat(nestedRecord.getNestedRecord()).isNotNull(); assertThat(nestedRecord.getNestedCounter()).isEqualTo(updatedOuterNestedCounter); + assertThat(nestedRecord.getNestedCreatedTimeAttribute()).isAfter(expectedTime); + assertThat(nestedRecord.getNestedUpdatedTimeAttribute()).isAfter(expectedTime); assertThat(nestedRecord.getNestedRecord()).isNotNull(); assertThat(nestedRecord.getNestedRecord().getNestedCounter()).isEqualTo(updatedInnerNestedCounter); assertThat(nestedRecord.getNestedRecord().getNestedUpdateBehaviorAttribute()).isEqualTo( - test_behav_attribute); - assertThat(nestedRecord.getNestedRecord().getNestedTimeAttribute()).isEqualTo(expected_time); + testBehaviorAttribute); + assertThat(nestedRecord.getNestedRecord().getNestedCreatedTimeAttribute()).isAfter(expectedTime); + assertThat(nestedRecord.getNestedRecord().getNestedUpdatedTimeAttribute()).isAfter(expectedTime); } private void verifySingleLevelNestingTargetedUpdateBehavior(NestedRecordWithUpdateBehavior nestedRecord, - long updatedNestedCounter, String expected_behav_attr, - Instant expected_time) { + long updatedNestedCounter, String expectedBehaviorAttr, + Instant expectedTime) { assertThat(nestedRecord).isNotNull(); assertThat(nestedRecord.getNestedCounter()).isEqualTo(updatedNestedCounter); - assertThat(nestedRecord.getNestedUpdateBehaviorAttribute()).isEqualTo(expected_behav_attr); - assertThat(nestedRecord.getNestedTimeAttribute()).isEqualTo(expected_time); + assertThat(nestedRecord.getNestedUpdateBehaviorAttribute()).isEqualTo(expectedBehaviorAttr); + assertThat(nestedRecord.getNestedCreatedTimeAttribute()).isAfter(expectedTime); + assertThat(nestedRecord.getNestedUpdatedTimeAttribute()).isAfter(expectedTime); } @Test public void when_updatingNestedObjectWithMultipleLevels_inScalarOnlyMode_existingInformationIsPreserved() { - NestedRecordWithUpdateBehavior nestedRecord1 = createNestedWithDefaults("id789", 50L); NestedRecordWithUpdateBehavior nestedRecord2 = createNestedWithDefaults("id456", 0L); @@ -327,12 +495,12 @@ public void when_updatingNestedObjectWithMultipleLevels_inScalarOnlyMode_existin long outerNestedCounter = 200L; updatedNestedRecord1.setNestedCounter(outerNestedCounter); - RecordWithUpdateBehaviors update_record = new RecordWithUpdateBehaviors(); - update_record.setId("id123"); - update_record.setVersion(1L); - update_record.setNestedRecord(updatedNestedRecord1); + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setNestedRecord(updatedNestedRecord1); - mappedTable.updateItem(r -> r.item(update_record).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); + mappedTable.updateItem(r -> r.item(updateRecord).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); @@ -342,7 +510,6 @@ public void when_updatingNestedObjectWithMultipleLevels_inScalarOnlyMode_existin @Test public void when_updatingNestedObjectWithMultipleLevels_inMapsOnlyMode_existingInformationIsPreserved() { - NestedRecordWithUpdateBehavior nestedRecord1 = createNestedWithDefaults("id789", 50L); NestedRecordWithUpdateBehavior nestedRecord2 = createNestedWithDefaults("id456", 0L); @@ -358,12 +525,12 @@ public void when_updatingNestedObjectWithMultipleLevels_inMapsOnlyMode_existingI long outerNestedCounter = 200L; updatedNestedRecord1.setNestedCounter(outerNestedCounter); - RecordWithUpdateBehaviors update_record = new RecordWithUpdateBehaviors(); - update_record.setId("id123"); - update_record.setVersion(1L); - update_record.setNestedRecord(updatedNestedRecord1); + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setNestedRecord(updatedNestedRecord1); - mappedTable.updateItem(r -> r.item(update_record).ignoreNullsMode(IgnoreNullsMode.MAPS_ONLY)); + mappedTable.updateItem(r -> r.item(updateRecord).ignoreNullsMode(IgnoreNullsMode.MAPS_ONLY)); RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); @@ -373,7 +540,7 @@ public void when_updatingNestedObjectWithMultipleLevels_inMapsOnlyMode_existingI @Test public void when_updatingNestedObjectWithMultipleLevels_default_mode_existingInformationIsErased() { - + Instant currentTime = Instant.now().minusMillis(1); NestedRecordWithUpdateBehavior nestedRecord1 = createNestedWithDefaults("id789", 50L); NestedRecordWithUpdateBehavior nestedRecord2 = createNestedWithDefaults("id456", 0L); @@ -394,22 +561,21 @@ public void when_updatingNestedObjectWithMultipleLevels_default_mode_existingInf long outerNestedCounter = 200L; updatedNestedRecord1.setNestedCounter(outerNestedCounter); - RecordWithUpdateBehaviors update_record = new RecordWithUpdateBehaviors(); - update_record.setId("id123"); - update_record.setVersion(1L); - update_record.setNestedRecord(updatedNestedRecord1); + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setNestedRecord(updatedNestedRecord1); - mappedTable.updateItem(r -> r.item(update_record)); + mappedTable.updateItem(r -> r.item(updateRecord)); RecordWithUpdateBehaviors persistedRecord = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id123"))); verifyMultipleLevelNestingTargetedUpdateBehavior(persistedRecord.getNestedRecord(), outerNestedCounter, innerNestedCounter, null, - null); + currentTime); } @Test public void when_updatingNestedNonScalarObject_scalar_only_update_throwsDynamoDBException() { - NestedRecordWithUpdateBehavior nestedRecord = createNestedWithDefaults("id456", 5L); nestedRecord.setAttribute(TEST_ATTRIBUTE); @@ -418,35 +584,34 @@ public void when_updatingNestedNonScalarObject_scalar_only_update_throwsDynamoDB mappedTable.putItem(record); - RecordWithUpdateBehaviors update_record = new RecordWithUpdateBehaviors(); - update_record.setId("id123"); - update_record.setVersion(1L); - update_record.setKey("abc"); - update_record.setNestedRecord(nestedRecord); + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setKey("abc"); + updateRecord.setNestedRecord(nestedRecord); - assertThatThrownBy(() -> mappedTable.updateItem(r -> r.item(update_record).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY))) + assertThatThrownBy(() -> mappedTable.updateItem(r -> r.item(updateRecord).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY))) .isInstanceOf(DynamoDbException.class); } @Test public void when_updatingNestedMap_mapsOnlyMode_newMapIsCreatedAndStored() { - RecordWithUpdateBehaviors record = new RecordWithUpdateBehaviors(); record.setId("id123"); mappedTable.putItem(record); - RecordWithUpdateBehaviors update_record = new RecordWithUpdateBehaviors(); - update_record.setId("id123"); - update_record.setVersion(1L); - update_record.setKey("abc"); + RecordWithUpdateBehaviors updateRecord = new RecordWithUpdateBehaviors(); + updateRecord.setId("id123"); + updateRecord.setVersion(1L); + updateRecord.setKey("abc"); NestedRecordWithUpdateBehavior nestedRecord = createNestedWithDefaults("id456", 5L); nestedRecord.setAttribute(TEST_ATTRIBUTE); - update_record.setNestedRecord(nestedRecord); + updateRecord.setNestedRecord(nestedRecord); RecordWithUpdateBehaviors persistedRecord = - mappedTable.updateItem(r -> r.item(update_record).ignoreNullsMode(IgnoreNullsMode.MAPS_ONLY)); + mappedTable.updateItem(r -> r.item(updateRecord).ignoreNullsMode(IgnoreNullsMode.MAPS_ONLY)); verifySingleLevelNestingTargetedUpdateBehavior(persistedRecord.getNestedRecord(), 5L, TEST_BEHAVIOUR_ATTRIBUTE, INSTANT_1); @@ -470,21 +635,20 @@ public void when_emptyNestedRecordIsSet_emptyMapIsStoredInTable() { .build()); assertThat(getItemResponse.item().get("nestedRecord")).isNotNull(); - assertThat(getItemResponse.item().get("nestedRecord").toString()).isEqualTo("AttributeValue(M={nestedTimeAttribute" - + "=AttributeValue(NUL=true), " - + "nestedRecord=AttributeValue(NUL=true), " - + "attribute=AttributeValue(NUL=true), " - + "id=AttributeValue(NUL=true), " - + "nestedUpdateBehaviorAttribute=AttributeValue" - + "(NUL=true), nestedCounter=AttributeValue" - + "(NUL=true), nestedVersionedAttribute" - + "=AttributeValue(NUL=true)})"); + Map nestedRecord = getItemResponse.item().get("nestedRecord").m(); + assertThat(nestedRecord.get("nestedCreatedTimeAttribute")).isNotNull(); + assertThat(nestedRecord.get("nestedUpdatedTimeAttribute")).isNotNull(); + assertTrue(nestedRecord.get("id").nul()); + assertTrue(nestedRecord.get("nestedRecord").nul()); + assertTrue(nestedRecord.get("attribute").nul()); + assertTrue(nestedRecord.get("nestedUpdateBehaviorAttribute").nul()); + assertTrue(nestedRecord.get("nestedCounter").nul()); + assertTrue(nestedRecord.get("nestedVersionedAttribute").nul()); } @Test public void when_updatingNestedObjectWithSingleLevelFlattened_existingInformationIsPreserved_scalar_only_update() { - NestedRecordWithUpdateBehavior nestedRecord = createNestedWithDefaults("id123", 10L); CompositeRecord compositeRecord = new CompositeRecord(); @@ -513,12 +677,9 @@ public void when_updatingNestedObjectWithSingleLevelFlattened_existingInformatio verifySingleLevelNestingTargetedUpdateBehavior(persistedFlattenedRecord.getCompositeRecord().getNestedRecord(), 100L, TEST_BEHAVIOUR_ATTRIBUTE, INSTANT_1); } - - @Test public void when_updatingNestedObjectWithMultipleLevelFlattened_existingInformationIsPreserved_scalar_only_update() { - NestedRecordWithUpdateBehavior outerNestedRecord = createNestedWithDefaults("id123", 10L); NestedRecordWithUpdateBehavior innerNestedRecord = createNestedWithDefaults("id456", 5L); outerNestedRecord.setNestedRecord(innerNestedRecord); @@ -555,10 +716,11 @@ public void when_updatingNestedObjectWithMultipleLevelFlattened_existingInformat 50L, TEST_BEHAVIOUR_ATTRIBUTE, INSTANT_1); assertThat(persistedFlattenedRecord.getCompositeRecord().getNestedRecord().getNestedCounter()).isEqualTo(100L); assertThat(persistedFlattenedRecord.getCompositeRecord().getNestedRecord().getNestedRecord().getNestedCounter()).isEqualTo(50L); + assertThat(persistedFlattenedRecord.getCompositeRecord().getNestedRecord().getNestedRecord().getNestedCreatedTimeAttribute()).isNotNull(); } /** - * Currently, nested records are not updated through extensions. + * Currently, nested records are not updated through extensions (only the timestamp). */ @Test public void updateBehaviors_nested() { @@ -579,6 +741,6 @@ public void updateBehaviors_nested() { assertThat(persistedRecord.getNestedRecord().getNestedVersionedAttribute()).isNull(); assertThat(persistedRecord.getNestedRecord().getNestedCounter()).isNull(); assertThat(persistedRecord.getNestedRecord().getNestedUpdateBehaviorAttribute()).isNull(); - assertThat(persistedRecord.getNestedRecord().getNestedTimeAttribute()).isNull(); + assertThat(persistedRecord.getNestedRecord().getNestedCreatedTimeAttribute()).isNotNull(); } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/AutogeneratedTimestampModels.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/AutogeneratedTimestampModels.java new file mode 100644 index 000000000000..fb8eb5b4837b --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/AutogeneratedTimestampModels.java @@ -0,0 +1,3602 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.enhanced.dynamodb.functionaltests.models; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension.AttributeTags.autoGeneratedTimestampAttribute; +import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import software.amazon.awssdk.enhanced.dynamodb.EnhancedType; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedTimestampAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema; +import software.amazon.awssdk.enhanced.dynamodb.mapper.ImmutableTableSchema; +import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema; +import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.utils.ImmutableMap; + +public final class AutogeneratedTimestampModels { + + private AutogeneratedTimestampModels() { + } + + // Constants + public static final String ID_1 = "1"; + public static final String ID_2 = "2"; + public static final String ID_ATTR = "id"; + public static final String TIME_ATTR = "time"; + + public static final String ATTR_LEVEL1 = "attr_level1"; + public static final String ATTR_LEVEL2 = "attr_level2"; + public static final String ATTR_LEVEL3 = "attr_level3"; + public static final String ATTR_LEVEL4 = "attr_level4"; + public static final String ATTR_CHILD1 = "attr_child1"; + public static final String ATTR_CHILD2 = "attr_child2"; + + public static final String CHILD1_KEY = "child1"; + public static final String CHILD2_KEY = "child2"; + public static final String LEVEL2_KEY = "level2"; + public static final String LEVEL3_KEY = "level3"; + public static final String LEVEL4_KEY = "level4"; + + + // Simple Bean Records + @DynamoDbBean + public static class SimpleBeanRecordWithList { + private String id; + private String attr; + private Instant time; + private List childList; + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public SimpleBeanRecordWithList setId(String id) { + this.id = id; + return this; + } + + + public String getAttr() { + return attr; + } + + public SimpleBeanRecordWithList setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public SimpleBeanRecordWithList setTime(Instant time) { + this.time = time; + return this; + } + + + public List getChildList() { + return childList == null ? null : Collections.unmodifiableList(childList); + } + + public SimpleBeanRecordWithList setChildList(List childList) { + this.childList = childList; + return this; + } + } + + @DynamoDbBean + public static class SimpleBeanRecordWithSet { + private String id; + private String attr; + private Instant time; + private Set childSet; + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public SimpleBeanRecordWithSet setId(String id) { + this.id = id; + return this; + } + + public String getAttr() { + return attr; + } + + public SimpleBeanRecordWithSet setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public SimpleBeanRecordWithSet setTime(Instant time) { + this.time = time; + return this; + } + + public Set getChildSet() { + return childSet == null ? null : Collections.unmodifiableSet(childSet); + } + + public SimpleBeanRecordWithSet setChildSet(Set childSet) { + this.childSet = childSet; + return this; + } + } + + @DynamoDbBean + public static class SimpleBeanRecordWithMap { + private String id; + private String attr; + private Instant time; + private Map childMap; + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public SimpleBeanRecordWithMap setId(String id) { + this.id = id; + return this; + } + + public String getAttr() { + return attr; + } + + public SimpleBeanRecordWithMap setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public SimpleBeanRecordWithMap setTime(Instant time) { + this.time = time; + return this; + } + + public Map getChildMap() { + return childMap == null ? null : Collections.unmodifiableMap(childMap); + } + + public SimpleBeanRecordWithMap setChildMap(Map childMap) { + this.childMap = childMap; + return this; + } + } + + // simple record used by list/set/map as the deepest nested level + @DynamoDbBean + public static class SimpleBeanChild { + private String id; + private String attr; + private Instant time; + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public SimpleBeanChild setId(String id) { + this.id = id; + return this; + } + + + public String getAttr() { + return attr; + } + + public SimpleBeanChild setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public SimpleBeanChild setTime(Instant time) { + this.time = time; + return this; + } + } + + + // Nested Bean Records + @DynamoDbBean + public static class NestedBeanRecordWithList { + private String id; + private String attr; + private Instant time; + private NestedBeanLevel2RecordWithList level2; + private List level2List; + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public NestedBeanRecordWithList setId(String id) { + this.id = id; + return this; + } + + + public String getAttr() { + return attr; + } + + public NestedBeanRecordWithList setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanRecordWithList setTime(Instant time) { + this.time = time; + return this; + } + + public NestedBeanLevel2RecordWithList getLevel2() { + return level2; + } + + public NestedBeanRecordWithList setLevel2(NestedBeanLevel2RecordWithList level2) { + this.level2 = level2; + return this; + } + + public List getLevel2List() { + return level2List == null ? null : Collections.unmodifiableList(level2List); + } + + public NestedBeanRecordWithList setLevel2List(List level2List) { + this.level2List = level2List; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedBeanRecordWithList)) { + return false; + } + + NestedBeanRecordWithList that = (NestedBeanRecordWithList) o; + return Objects.equals(id, that.id) && + Objects.equals(attr, that.attr) && + Objects.equals(time, that.time) && + Objects.equals(level2, that.level2) && + Objects.equals(level2List, that.level2List); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(id); + result = 31 * result + Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level2); + result = 31 * result + Objects.hashCode(level2List); + return result; + } + } + + @DynamoDbBean + public static class NestedBeanLevel2RecordWithList { + private String attr; + private Instant time; + private NestedBeanLevel3RecordWithList level3; + private List level3List; + + + public String getAttr() { + return attr; + } + + public NestedBeanLevel2RecordWithList setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanLevel2RecordWithList setTime(Instant time) { + this.time = time; + return this; + } + + public NestedBeanLevel3RecordWithList getLevel3() { + return level3; + } + + public NestedBeanLevel2RecordWithList setLevel3(NestedBeanLevel3RecordWithList level3) { + this.level3 = level3; + return this; + } + + public List getLevel3List() { + return level3List; + } + + public NestedBeanLevel2RecordWithList setLevel3List(List level3List) { + this.level3List = level3List; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedBeanLevel2RecordWithList)) { + return false; + } + + NestedBeanLevel2RecordWithList that = (NestedBeanLevel2RecordWithList) o; + return Objects.equals(attr, that.attr) && + Objects.equals(time, that.time) && + Objects.equals(level3, that.level3) && + Objects.equals(level3List, that.level3List); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level3); + result = 31 * result + Objects.hashCode(level3List); + return result; + } + } + + @DynamoDbBean + public static class NestedBeanLevel3RecordWithList { + private String attr; + private Instant time; + private NestedBeanLevel4Record level4; + private List level4List; + + + public String getAttr() { + return attr; + } + + public NestedBeanLevel3RecordWithList setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanLevel3RecordWithList setTime(Instant time) { + this.time = time; + return this; + } + + public NestedBeanLevel4Record getLevel4() { + return level4; + } + + public NestedBeanLevel3RecordWithList setLevel4(NestedBeanLevel4Record level4) { + this.level4 = level4; + return this; + } + + public List getLevel4List() { + return level4List; + } + + public NestedBeanLevel3RecordWithList setLevel4List(List level4List) { + this.level4List = level4List; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedBeanLevel3RecordWithList)) { + return false; + } + + NestedBeanLevel3RecordWithList that = (NestedBeanLevel3RecordWithList) o; + return Objects.equals(attr, that.attr) && + Objects.equals(time, that.time) && + Objects.equals(level4, that.level4) && + Objects.equals(level4List, that.level4List); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level4); + result = 31 * result + Objects.hashCode(level4List); + return result; + } + } + + @DynamoDbBean + public static class NestedBeanRecordWithSet { + private String id; + private String attr; + private Instant time; + private NestedBeanLevel2RecordWithSet level2; + private Set level2Set; + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public NestedBeanRecordWithSet setId(String v) { + id = v; + return this; + } + + public String getAttr() { + return attr; + } + + public NestedBeanRecordWithSet setAttr(String v) { + attr = v; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanRecordWithSet setTime(Instant v) { + time = v; + return this; + } + + public NestedBeanLevel2RecordWithSet getLevel2() { + return level2; + } + + public NestedBeanRecordWithSet setLevel2(NestedBeanLevel2RecordWithSet v) { + level2 = v; + return this; + } + + public Set getLevel2Set() { + return level2Set; + } + + public NestedBeanRecordWithSet setLevel2Set(Set v) { + level2Set = v; + return this; + } + } + + @DynamoDbBean + public static class NestedBeanLevel2RecordWithSet { + private String attr; + private Instant time; + private NestedBeanLevel3RecordWithSet level3; + private Set level3Set; + + public String getAttr() { + return attr; + } + + public NestedBeanLevel2RecordWithSet setAttr(String v) { + attr = v; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanLevel2RecordWithSet setTime(Instant v) { + time = v; + return this; + } + + public NestedBeanLevel3RecordWithSet getLevel3() { + return level3; + } + + public NestedBeanLevel2RecordWithSet setLevel3(NestedBeanLevel3RecordWithSet v) { + level3 = v; + return this; + } + + public Set getLevel3Set() { + return level3Set; + } + + public NestedBeanLevel2RecordWithSet setLevel3Set(Set v) { + level3Set = v; + return this; + } + } + + @DynamoDbBean + public static class NestedBeanLevel3RecordWithSet { + private String attr; + private Instant time; + private NestedBeanLevel4Record level4; + private Set level4Set; + + public String getAttr() { + return attr; + } + + public NestedBeanLevel3RecordWithSet setAttr(String v) { + attr = v; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanLevel3RecordWithSet setTime(Instant v) { + time = v; + return this; + } + + public NestedBeanLevel4Record getLevel4() { + return level4; + } + + public NestedBeanLevel3RecordWithSet setLevel4(NestedBeanLevel4Record v) { + level4 = v; + return this; + } + + public Set getLevel4Set() { + return level4Set; + } + + public NestedBeanLevel3RecordWithSet setLevel4Set(Set v) { + level4Set = v; + return this; + } + } + + @DynamoDbBean + public static class NestedBeanRecordWithMap { + private String id; + private String attr; + private Instant time; + private NestedBeanLevel2RecordWithMap level2; + private Map level2Map; + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public NestedBeanRecordWithMap setId(String id) { + this.id = id; + return this; + } + + public String getAttr() { + return attr; + } + + public NestedBeanRecordWithMap setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanRecordWithMap setTime(Instant time) { + this.time = time; + return this; + } + + public NestedBeanLevel2RecordWithMap getLevel2() { + return level2; + } + + public NestedBeanRecordWithMap setLevel2(NestedBeanLevel2RecordWithMap level2) { + this.level2 = level2; + return this; + } + + public Map getLevel2Map() { + return Collections.unmodifiableMap(level2Map); + } + + public NestedBeanRecordWithMap setLevel2Map(Map level2Map) { + this.level2Map = level2Map; + return this; + } + } + + @DynamoDbBean + public static class NestedBeanLevel2RecordWithMap { + private String attr; + private Instant time; + private NestedBeanLevel3RecordWithMap level3; + private Map level3Map; + + public String getAttr() { + return attr; + } + + public NestedBeanLevel2RecordWithMap setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanLevel2RecordWithMap setTime(Instant time) { + this.time = time; + return this; + } + + public NestedBeanLevel3RecordWithMap getLevel3() { + return level3; + } + + public NestedBeanLevel2RecordWithMap setLevel3(NestedBeanLevel3RecordWithMap level3) { + this.level3 = level3; + return this; + } + + public Map getLevel3Map() { + return Collections.unmodifiableMap(level3Map); + } + + public NestedBeanLevel2RecordWithMap setLevel3Map(Map level3Map) { + this.level3Map = level3Map; + return this; + } + } + + @DynamoDbBean + public static class NestedBeanLevel3RecordWithMap { + private String attr; + private Instant time; + private NestedBeanLevel4Record level4; + private Map level4Map; + + public String getAttr() { + return attr; + } + + public NestedBeanLevel3RecordWithMap setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanLevel3RecordWithMap setTime(Instant time) { + this.time = time; + return this; + } + + public NestedBeanLevel4Record getLevel4() { + return level4; + } + + public NestedBeanLevel3RecordWithMap setLevel4(NestedBeanLevel4Record level4) { + this.level4 = level4; + return this; + } + + public Map getLevel4Map() { + return Collections.unmodifiableMap(level4Map); + } + + public NestedBeanLevel3RecordWithMap setLevel4Map(Map level4Map) { + this.level4Map = level4Map; + return this; + } + } + + // nested record used by list/set/map as the deepest nested level + @DynamoDbBean + public static class NestedBeanLevel4Record { + private String id; + private String attr; + private Instant time; + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public NestedBeanLevel4Record setId(String id) { + this.id = id; + return this; + } + + public String getAttr() { + return attr; + } + + public NestedBeanLevel4Record setAttr(String attr) { + this.attr = attr; + return this; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedBeanLevel4Record setTime(Instant time) { + this.time = time; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedBeanLevel4Record)) { + return false; + } + + NestedBeanLevel4Record that = (NestedBeanLevel4Record) o; + return Objects.equals(attr, that.attr) && + Objects.equals(time, that.time); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + return result; + } + } + + + // Simple Immutable Records + @DynamoDbImmutable(builder = SimpleImmutableRecordWithList.Builder.class) + public static final class SimpleImmutableRecordWithList { + private final String id; + private final String attr; + private final Instant time; + private final List childList; + + private SimpleImmutableRecordWithList(Builder b) { + this.id = b.id; + this.attr = b.attr; + this.time = b.time; + this.childList = b.childList; + } + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + + public List getChildList() { + return Collections.unmodifiableList(childList); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String id; + private String attr; + private Instant time; + private List childList; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder attr(String attr) { + this.attr = attr; + return this; + } + + public Builder time(Instant time) { + this.time = time; + return this; + } + + public Builder childList(List childList) { + this.childList = childList; + return this; + } + + public SimpleImmutableRecordWithList build() { + return new SimpleImmutableRecordWithList(this); + } + } + } + + @DynamoDbImmutable(builder = SimpleImmutableRecordWithSet.Builder.class) + public static class SimpleImmutableRecordWithSet { + private final String id; + private final String attr; + private final Instant time; + private final Set childSet; + + private SimpleImmutableRecordWithSet(Builder b) { + this.id = b.id; + this.attr = b.attr; + this.time = b.time; + this.childSet = b.childSet; + } + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + + public Set getChildSet() { + return Collections.unmodifiableSet(childSet); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String id; + private String attr; + private Instant time; + private Set childSet; + + public Builder id(String v) { + this.id = v; + return this; + } + + public Builder attr(String v) { + this.attr = v; + return this; + } + + public Builder time(Instant v) { + this.time = v; + return this; + } + + public Builder childSet(Set v) { + this.childSet = v; + return this; + } + + public SimpleImmutableRecordWithSet build() { + return new SimpleImmutableRecordWithSet(this); + } + } + } + + @DynamoDbImmutable(builder = SimpleImmutableRecordWithMap.Builder.class) + public static class SimpleImmutableRecordWithMap { + private final String id; + private final String attr; + private final Instant time; + private final Map childMap; + + private SimpleImmutableRecordWithMap(Builder b) { + this.id = b.id; + this.attr = b.attr; + this.time = b.time; + this.childMap = b.childMap; + } + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public Map getChildMap() { + return Collections.unmodifiableMap(childMap); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String id; + private String attr; + private Instant time; + private Map childMap; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder attr(String attr) { + this.attr = attr; + return this; + } + + public Builder time(Instant time) { + this.time = time; + return this; + } + + public Builder childMap(Map childMap) { + this.childMap = childMap; + return this; + } + + public SimpleImmutableRecordWithMap build() { + return new SimpleImmutableRecordWithMap(this); + } + } + } + + // simple record used by list/set/map as the deepest nested level + @DynamoDbImmutable(builder = SimpleImmutableChild.Builder.class) + public static final class SimpleImmutableChild { + private final String id; + private final String attr; + private final Instant time; + + private SimpleImmutableChild(Builder b) { + this.id = b.id; + this.attr = b.attr; + this.time = b.time; + } + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String id; + private String attr; + private Instant time; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder attr(String attr) { + this.attr = attr; + return this; + } + + public Builder time(Instant time) { + this.time = time; + return this; + } + + public SimpleImmutableChild build() { + return new SimpleImmutableChild(this); + } + } + } + + + // Nested Immutable Records + @DynamoDbImmutable(builder = NestedImmutableRecordWithList.Builder.class) + public static final class NestedImmutableRecordWithList { + private final String id; + private final String attr; + private final Instant time; + private final NestedImmutableLevel2RecordWithList level2; + private final List level2List; + + private NestedImmutableRecordWithList(Builder b) { + this.id = b.id; + this.attr = b.attr; + this.time = b.time; + this.level2 = b.level2; + this.level2List = b.level2List; + } + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedImmutableLevel2RecordWithList getLevel2() { + return level2; + } + + public List getLevel2List() { + return level2List == null ? null : Collections.unmodifiableList(level2List); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String id; + private String attr; + private Instant time; + private NestedImmutableLevel2RecordWithList level2; + private List level2List; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder attr(String attr) { + this.attr = attr; + return this; + } + + public Builder time(Instant time) { + this.time = time; + return this; + } + + public Builder level2(NestedImmutableLevel2RecordWithList level2) { + this.level2 = level2; + return this; + } + + public Builder level2List(List level2List) { + this.level2List = level2List; + return this; + } + + public NestedImmutableRecordWithList build() { + return new NestedImmutableRecordWithList(this); + } + } + } + + @DynamoDbImmutable(builder = NestedImmutableLevel2RecordWithList.Builder.class) + public static final class NestedImmutableLevel2RecordWithList { + private final String attr; + private final Instant time; + private final NestedImmutableLevel3RecordWithList level3; + private final List level3List; + + private NestedImmutableLevel2RecordWithList(Builder b) { + this.attr = b.attr; + this.time = b.time; + this.level3 = b.level3; + this.level3List = b.level3List; + } + + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedImmutableLevel3RecordWithList getLevel3() { + return level3; + } + + public List getLevel3List() { + return level3List; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String attr; + private Instant time; + private NestedImmutableLevel3RecordWithList level3; + private List level3List; + + public Builder attr(String attr) { + this.attr = attr; + return this; + } + + public Builder time(Instant time) { + this.time = time; + return this; + } + + public Builder level3(NestedImmutableLevel3RecordWithList level3) { + this.level3 = level3; + return this; + } + + public Builder level3List(List level3List) { + this.level3List = level3List; + return this; + } + + public NestedImmutableLevel2RecordWithList build() { + return new NestedImmutableLevel2RecordWithList(this); + } + } + } + + @DynamoDbImmutable(builder = NestedImmutableLevel3RecordWithList.Builder.class) + public static final class NestedImmutableLevel3RecordWithList { + private final String attr; + private final Instant time; + private final NestedImmutableLevel4Record level4; + private final List level4List; + + private NestedImmutableLevel3RecordWithList(Builder b) { + this.attr = b.attr; + this.time = b.time; + this.level4 = b.level4; + this.level4List = b.level4List; + } + + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedImmutableLevel4Record getLevel4() { + return level4; + } + + public List getLevel4List() { + return level4List; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String attr; + private Instant time; + private NestedImmutableLevel4Record level4; + private List level4List; + + public Builder attr(String attr) { + this.attr = attr; + return this; + } + + public Builder time(Instant time) { + this.time = time; + return this; + } + + public Builder level4(NestedImmutableLevel4Record level4) { + this.level4 = level4; + return this; + } + + public Builder level4List(List level4List) { + this.level4List = level4List; + return this; + } + + public NestedImmutableLevel3RecordWithList build() { + return new NestedImmutableLevel3RecordWithList(this); + } + } + } + + @DynamoDbImmutable(builder = NestedImmutableRecordWithSet.Builder.class) + public static final class NestedImmutableRecordWithSet { + private final String id; + private final String attr; + private final Instant time; + private final NestedImmutableLevel2RecordWithSet level2; + private final Set level2Set; + + private NestedImmutableRecordWithSet(Builder b) { + id = b.id; + attr = b.attr; + time = b.time; + level2 = b.level2; + level2Set = b.level2Set; + } + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedImmutableLevel2RecordWithSet getLevel2() { + return level2; + } + + public Set getLevel2Set() { + return level2Set == null ? null : Collections.unmodifiableSet(level2Set); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String id; + private String attr; + private Instant time; + private NestedImmutableLevel2RecordWithSet level2; + private Set level2Set; + + public Builder id(String v) { + id = v; + return this; + } + + public Builder attr(String v) { + attr = v; + return this; + } + + public Builder time(Instant v) { + time = v; + return this; + } + + public Builder level2(NestedImmutableLevel2RecordWithSet v) { + level2 = v; + return this; + } + + public Builder level2Set(Set v) { + level2Set = v; + return this; + } + + public NestedImmutableRecordWithSet build() { + return new NestedImmutableRecordWithSet(this); + } + } + } + + @DynamoDbImmutable(builder = NestedImmutableLevel2RecordWithSet.Builder.class) + public static class NestedImmutableLevel2RecordWithSet { + private final String attr; + private final Instant time; + private final NestedImmutableLevel3RecordWithSet level3; + private final Set level3Set; + + private NestedImmutableLevel2RecordWithSet(Builder b) { + attr = b.attr; + time = b.time; + level3 = b.level3; + level3Set = b.level3Set; + } + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedImmutableLevel3RecordWithSet getLevel3() { + return level3; + } + + public Set getLevel3Set() { + return level3Set == null ? null : Collections.unmodifiableSet(level3Set); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String attr; + private Instant time; + private NestedImmutableLevel3RecordWithSet level3; + private Set level3Set; + + public Builder attr(String v) { + attr = v; + return this; + } + + public Builder time(Instant v) { + time = v; + return this; + } + + public Builder level3(NestedImmutableLevel3RecordWithSet v) { + level3 = v; + return this; + } + + public Builder level3Set(Set v) { + level3Set = v; + return this; + } + + public NestedImmutableLevel2RecordWithSet build() { + return new NestedImmutableLevel2RecordWithSet(this); + } + } + } + + @DynamoDbImmutable(builder = NestedImmutableLevel3RecordWithSet.Builder.class) + public static class NestedImmutableLevel3RecordWithSet { + private final String attr; + private final Instant time; + private final NestedImmutableLevel4Record level4; + private final Set level4Set; + + private NestedImmutableLevel3RecordWithSet(Builder b) { + attr = b.attr; + time = b.time; + level4 = b.level4; + level4Set = b.level4Set; + } + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedImmutableLevel4Record getLevel4() { + return level4; + } + + public Set getLevel4Set() { + return level4Set == null ? null : Collections.unmodifiableSet(level4Set); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String attr; + private Instant time; + private NestedImmutableLevel4Record level4; + private Set level4Set; + + public Builder attr(String v) { + attr = v; + return this; + } + + public Builder time(Instant v) { + time = v; + return this; + } + + public Builder level4(NestedImmutableLevel4Record v) { + level4 = v; + return this; + } + + public Builder level4Set(Set v) { + level4Set = v; + return this; + } + + public NestedImmutableLevel3RecordWithSet build() { + return new NestedImmutableLevel3RecordWithSet(this); + } + } + } + + @DynamoDbImmutable(builder = NestedImmutableRecordWithMap.Builder.class) + public static class NestedImmutableRecordWithMap { + private final String id; + private final String attr; + private final Instant time; + private final NestedImmutableLevel2RecordWithMap level2; + private final Map level2Map; + + private NestedImmutableRecordWithMap(Builder b) { + this.id = b.id; + this.attr = b.attr; + this.time = b.time; + this.level2 = b.level2; + this.level2Map = b.level2Map; + } + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedImmutableLevel2RecordWithMap getLevel2() { + return level2; + } + + public Map getLevel2Map() { + return level2Map == null ? null : Collections.unmodifiableMap(level2Map); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String id; + private String attr; + private Instant time; + private NestedImmutableLevel2RecordWithMap level2; + private Map level2Map; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder attr(String attr) { + this.attr = attr; + return this; + } + + public Builder time(Instant time) { + this.time = time; + return this; + } + + public Builder level2(NestedImmutableLevel2RecordWithMap v) { + this.level2 = v; + return this; + } + + public Builder level2Map(Map v) { + this.level2Map = v; + return this; + } + + public NestedImmutableRecordWithMap build() { + return new NestedImmutableRecordWithMap(this); + } + } + } + + @DynamoDbImmutable(builder = NestedImmutableLevel2RecordWithMap.Builder.class) + public static class NestedImmutableLevel2RecordWithMap { + private final String attr; + private final Instant time; + private final NestedImmutableLevel3RecordWithMap level3; + private final Map level3Map; + + private NestedImmutableLevel2RecordWithMap(Builder b) { + this.attr = b.attr; + this.time = b.time; + this.level3 = b.level3; + this.level3Map = b.level3Map; + } + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedImmutableLevel3RecordWithMap getLevel3() { + return level3; + } + + public Map getLevel3Map() { + return level3Map == null ? null : Collections.unmodifiableMap(level3Map); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String attr; + private Instant time; + private NestedImmutableLevel3RecordWithMap level3; + private Map level3Map; + + public Builder attr(String v) { + this.attr = v; + return this; + } + + public Builder time(Instant v) { + this.time = v; + return this; + } + + public Builder level3(NestedImmutableLevel3RecordWithMap v) { + this.level3 = v; + return this; + } + + public Builder level3Map(Map v) { + this.level3Map = v; + return this; + } + + public NestedImmutableLevel2RecordWithMap build() { + return new NestedImmutableLevel2RecordWithMap(this); + } + } + } + + @DynamoDbImmutable(builder = NestedImmutableLevel3RecordWithMap.Builder.class) + public static class NestedImmutableLevel3RecordWithMap { + private final String attr; + private final Instant time; + private final NestedImmutableLevel4Record level4; + private final Map level4Map; + + private NestedImmutableLevel3RecordWithMap(Builder b) { + this.attr = b.attr; + this.time = b.time; + this.level4 = b.level4; + this.level4Map = b.level4Map; + } + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public NestedImmutableLevel4Record getLevel4() { + return level4; + } + + public Map getLevel4Map() { + return level4Map == null ? null : Collections.unmodifiableMap(level4Map); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String attr; + private Instant time; + private NestedImmutableLevel4Record level4; + private Map level4Map; + + public Builder attr(String v) { + this.attr = v; + return this; + } + + public Builder time(Instant v) { + this.time = v; + return this; + } + + public Builder level4(NestedImmutableLevel4Record v) { + this.level4 = v; + return this; + } + + public Builder level4Map(Map v) { + this.level4Map = v; + return this; + } + + public NestedImmutableLevel3RecordWithMap build() { + return new NestedImmutableLevel3RecordWithMap(this); + } + } + } + + // nested record used by list/set/map as the deepest nested level + @DynamoDbImmutable(builder = NestedImmutableLevel4Record.Builder.class) + public static final class NestedImmutableLevel4Record { + private final String id; + private final String attr; + private final Instant time; + + private NestedImmutableLevel4Record(Builder b) { + this.id = b.id; + this.attr = b.attr; + this.time = b.time; + } + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + + public String getAttr() { + return attr; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTime() { + return time; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String id; + private String attr; + private Instant time; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder attr(String attr) { + this.attr = attr; + return this; + } + + public Builder time(Instant time) { + this.time = time; + return this; + } + + public NestedImmutableLevel4Record build() { + return new NestedImmutableLevel4Record(this); + } + } + } + + + // Simple Static Records + public static class SimpleStaticRecordWithSet { + private String id; + private String attr; + private Instant time; + private Set childSet; + + public String getId() { + return id; + } + + public SimpleStaticRecordWithSet setId(String id) { + this.id = id; + return this; + } + + public String getAttr() { + return attr; + } + + public SimpleStaticRecordWithSet setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public SimpleStaticRecordWithSet setTime(Instant time) { + this.time = time; + return this; + } + + public Set getChildSet() { + return Collections.unmodifiableSet(childSet); + } + + public SimpleStaticRecordWithSet setChildSet(Set childSet) { + this.childSet = childSet; + return this; + } + } + + public static class SimpleStaticRecordWithMap { + private String id; + private String attr; + private Instant time; + private Map childMap; + + public String getId() { + return id; + } + + public SimpleStaticRecordWithMap setId(String id) { + this.id = id; + return this; + } + + public String getAttr() { + return attr; + } + + public SimpleStaticRecordWithMap setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public SimpleStaticRecordWithMap setTime(Instant time) { + this.time = time; + return this; + } + + public Map getChildMap() { + return Collections.unmodifiableMap(childMap); + } + + public SimpleStaticRecordWithMap setChildMap(Map childMap) { + this.childMap = childMap; + return this; + } + } + + public static class SimpleStaticRecordWithList { + private String id; + private String attr; + private Instant time; + private List childList; + + public String getId() { + return id; + } + + public SimpleStaticRecordWithList setId(String id) { + this.id = id; + return this; + } + + + public String getAttr() { + return attr; + } + + public SimpleStaticRecordWithList setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public SimpleStaticRecordWithList setTime(Instant time) { + this.time = time; + return this; + } + + + public List getChildList() { + return childList == null ? null : Collections.unmodifiableList(childList); + } + + public SimpleStaticRecordWithList setChildList(List childList) { + this.childList = childList; + return this; + } + } + + // simple record used by list/set/map as the deepest nested level + public static class SimpleStaticChild { + private String id; + private String attr; + private Instant time; + + public String getId() { + return id; + } + + public SimpleStaticChild setId(String id) { + this.id = id; + return this; + } + + + public String getAttr() { + return attr; + } + + public SimpleStaticChild setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public SimpleStaticChild setTime(Instant time) { + this.time = time; + return this; + } + } + + + // Nested Static Records + public static class NestedStaticRecordWithList { + private String id; + private String attr; + private Instant time; + private NestedStaticLevel2RecordWithList level2; + private List level2List; + + public String getId() { + return id; + } + + public NestedStaticRecordWithList setId(String id) { + this.id = id; + return this; + } + + + public String getAttr() { + return attr; + } + + public NestedStaticRecordWithList setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticRecordWithList setTime(Instant time) { + this.time = time; + return this; + } + + public NestedStaticLevel2RecordWithList getLevel2() { + return level2; + } + + public NestedStaticRecordWithList setLevel2(NestedStaticLevel2RecordWithList level2) { + this.level2 = level2; + return this; + } + + public List getLevel2List() { + return level2List; + } + + public NestedStaticRecordWithList setLevel2List(List level2List) { + this.level2List = level2List; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticRecordWithList)) { + return false; + } + + NestedStaticRecordWithList that = (NestedStaticRecordWithList) o; + return Objects.equals(id, that.id) && Objects.equals(attr, that.attr) && Objects.equals(time, that.time) && Objects.equals(level2, that.level2) && Objects.equals(level2List, that.level2List); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(id); + result = 31 * result + Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level2); + result = 31 * result + Objects.hashCode(level2List); + return result; + } + } + + public static class NestedStaticLevel2RecordWithList { + private String attr; + private Instant time; + private NestedStaticLevel3RecordWithList level3; + private List level3List; + + + public String getAttr() { + return attr; + } + + public NestedStaticLevel2RecordWithList setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticLevel2RecordWithList setTime(Instant time) { + this.time = time; + return this; + } + + public NestedStaticLevel3RecordWithList getLevel3() { + return level3; + } + + public NestedStaticLevel2RecordWithList setLevel3(NestedStaticLevel3RecordWithList level3) { + this.level3 = level3; + return this; + } + + public List getLevel3List() { + return level3List; + } + + public NestedStaticLevel2RecordWithList setLevel3List(List level3List) { + this.level3List = level3List; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticLevel2RecordWithList)) { + return false; + } + + NestedStaticLevel2RecordWithList that = (NestedStaticLevel2RecordWithList) o; + return Objects.equals(attr, that.attr) && Objects.equals(time, that.time) && Objects.equals(level3, that.level3) && Objects.equals(level3List, that.level3List); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level3); + result = 31 * result + Objects.hashCode(level3List); + return result; + } + } + + public static class NestedStaticLevel3RecordWithList { + private String attr; + private Instant time; + private NestedStaticLevel4Record level4; + private List level4List; + + + public String getAttr() { + return attr; + } + + public NestedStaticLevel3RecordWithList setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticLevel3RecordWithList setTime(Instant time) { + this.time = time; + return this; + } + + public NestedStaticLevel4Record getLevel4() { + return level4; + } + + public NestedStaticLevel3RecordWithList setLevel4(NestedStaticLevel4Record level4) { + this.level4 = level4; + return this; + } + + public List getLevel4List() { + return level4List; + } + + public NestedStaticLevel3RecordWithList setLevel4List(List level4List) { + this.level4List = level4List; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticLevel3RecordWithList)) { + return false; + } + + NestedStaticLevel3RecordWithList that = (NestedStaticLevel3RecordWithList) o; + return Objects.equals(attr, that.attr) && Objects.equals(time, that.time) && Objects.equals(level4, that.level4) && Objects.equals(level4List, that.level4List); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level4); + result = 31 * result + Objects.hashCode(level4List); + return result; + } + } + + public static class NestedStaticRecordWithSet { + private String id; + private String attr; + private Instant time; + private NestedStaticLevel2RecordWithSet level2; + private Set level2List; + + public String getId() { + return id; + } + + public NestedStaticRecordWithSet setId(String id) { + this.id = id; + return this; + } + + + public String getAttr() { + return attr; + } + + public NestedStaticRecordWithSet setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticRecordWithSet setTime(Instant time) { + this.time = time; + return this; + } + + public NestedStaticLevel2RecordWithSet getLevel2() { + return level2; + } + + public NestedStaticRecordWithSet setLevel2(NestedStaticLevel2RecordWithSet level2) { + this.level2 = level2; + return this; + } + + public Set getLevel2List() { + return level2List; + } + + public NestedStaticRecordWithSet setLevel2List(Set level2List) { + this.level2List = level2List; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticRecordWithSet)) { + return false; + } + + NestedStaticRecordWithSet that = (NestedStaticRecordWithSet) o; + return Objects.equals(id, that.id) && Objects.equals(attr, that.attr) && Objects.equals(time, that.time) && Objects.equals(level2, that.level2) && Objects.equals(level2List, that.level2List); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(id); + result = 31 * result + Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level2); + result = 31 * result + Objects.hashCode(level2List); + return result; + } + } + + public static class NestedStaticLevel2RecordWithSet { + private String attr; + private Instant time; + private NestedStaticLevel3RecordWithSet level3; + private Set level3Set; + + + public String getAttr() { + return attr; + } + + public NestedStaticLevel2RecordWithSet setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticLevel2RecordWithSet setTime(Instant time) { + this.time = time; + return this; + } + + public NestedStaticLevel3RecordWithSet getLevel3() { + return level3; + } + + public NestedStaticLevel2RecordWithSet setLevel3(NestedStaticLevel3RecordWithSet level3) { + this.level3 = level3; + return this; + } + + public Set getLevel3Set() { + return level3Set; + } + + public NestedStaticLevel2RecordWithSet setLevel3Set(Set level3Set) { + this.level3Set = level3Set; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticLevel2RecordWithSet)) { + return false; + } + + NestedStaticLevel2RecordWithSet that = (NestedStaticLevel2RecordWithSet) o; + return Objects.equals(attr, that.attr) && Objects.equals(time, that.time) && Objects.equals(level3, that.level3) && Objects.equals(level3Set, that.level3Set); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level3); + result = 31 * result + Objects.hashCode(level3Set); + return result; + } + } + + public static class NestedStaticLevel3RecordWithSet { + private String attr; + private Instant time; + private NestedStaticLevel4Record level4; + private Set level4Set; + + + public String getAttr() { + return attr; + } + + public NestedStaticLevel3RecordWithSet setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticLevel3RecordWithSet setTime(Instant time) { + this.time = time; + return this; + } + + public NestedStaticLevel4Record getLevel4() { + return level4; + } + + public NestedStaticLevel3RecordWithSet setLevel4(NestedStaticLevel4Record level4) { + this.level4 = level4; + return this; + } + + public Set getLevel4Set() { + return level4Set; + } + + public NestedStaticLevel3RecordWithSet setLevel4Set(Set level4Set) { + this.level4Set = level4Set; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticLevel3RecordWithSet)) { + return false; + } + + NestedStaticLevel3RecordWithSet that = (NestedStaticLevel3RecordWithSet) o; + return Objects.equals(attr, that.attr) && Objects.equals(time, that.time) && Objects.equals(level4, that.level4) && Objects.equals(level4Set, that.level4Set); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level4); + result = 31 * result + Objects.hashCode(level4Set); + return result; + } + } + + public static class NestedStaticRecordWithMap { + private String id; + private String attr; + private Instant time; + private NestedStaticLevel2RecordWithMap level2; + private Map level2Map; + + public String getId() { + return id; + } + + public NestedStaticRecordWithMap setId(String id) { + this.id = id; + return this; + } + + + public String getAttr() { + return attr; + } + + public NestedStaticRecordWithMap setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticRecordWithMap setTime(Instant time) { + this.time = time; + return this; + } + + public NestedStaticLevel2RecordWithMap getLevel2() { + return level2; + } + + public NestedStaticRecordWithMap setLevel2(NestedStaticLevel2RecordWithMap level2) { + this.level2 = level2; + return this; + } + + public Map getLevel2Map() { + return level2Map; + } + + public NestedStaticRecordWithMap setLevel2Map(Map level2Map) { + this.level2Map = level2Map; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticRecordWithMap)) { + return false; + } + + NestedStaticRecordWithMap that = (NestedStaticRecordWithMap) o; + return Objects.equals(id, that.id) && Objects.equals(attr, that.attr) && Objects.equals(time, that.time) && Objects.equals(level2, that.level2) && Objects.equals(level2Map, that.level2Map); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(id); + result = 31 * result + Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level2); + result = 31 * result + Objects.hashCode(level2Map); + return result; + } + } + + public static class NestedStaticLevel2RecordWithMap { + private String attr; + private Instant time; + private NestedStaticLevel3RecordWithMap level3; + private Map level3Map; + + + public String getAttr() { + return attr; + } + + public NestedStaticLevel2RecordWithMap setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticLevel2RecordWithMap setTime(Instant time) { + this.time = time; + return this; + } + + public NestedStaticLevel3RecordWithMap getLevel3() { + return level3; + } + + public NestedStaticLevel2RecordWithMap setLevel3(NestedStaticLevel3RecordWithMap level3) { + this.level3 = level3; + return this; + } + + public Map getLevel3Map() { + return level3Map; + } + + public NestedStaticLevel2RecordWithMap setLevel3Map(Map level3Map) { + this.level3Map = level3Map; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticLevel2RecordWithMap)) { + return false; + } + + NestedStaticLevel2RecordWithMap that = (NestedStaticLevel2RecordWithMap) o; + return Objects.equals(attr, that.attr) && Objects.equals(time, that.time) && Objects.equals(level3, that.level3) && Objects.equals(level3Map, that.level3Map); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level3); + result = 31 * result + Objects.hashCode(level3Map); + return result; + } + } + + public static class NestedStaticLevel3RecordWithMap { + private String attr; + private Instant time; + private NestedStaticLevel4Record level4; + private Map level4Map; + + public String getAttr() { + return attr; + } + + public NestedStaticLevel3RecordWithMap setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticLevel3RecordWithMap setTime(Instant time) { + this.time = time; + return this; + } + + public NestedStaticLevel4Record getLevel4() { + return level4; + } + + public NestedStaticLevel3RecordWithMap setLevel4(NestedStaticLevel4Record level4) { + this.level4 = level4; + return this; + } + + public Map getLevel4Map() { + return level4Map; + } + + public NestedStaticLevel3RecordWithMap setLevel4Map(Map level4Map) { + this.level4Map = level4Map; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticLevel3RecordWithMap)) { + return false; + } + + NestedStaticLevel3RecordWithMap that = (NestedStaticLevel3RecordWithMap) o; + return Objects.equals(attr, that.attr) && Objects.equals(time, that.time) && Objects.equals(level4, that.level4) && Objects.equals(level4Map, that.level4Map); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + result = 31 * result + Objects.hashCode(level4); + result = 31 * result + Objects.hashCode(level4Map); + return result; + } + } + + // nested record used by list/set/map as the deepest nested level + public static class NestedStaticLevel4Record { + private String attr; + private Instant time; + + public String getAttr() { + return attr; + } + + public NestedStaticLevel4Record setAttr(String attr) { + this.attr = attr; + return this; + } + + public Instant getTime() { + return time; + } + + public NestedStaticLevel4Record setTime(Instant time) { + this.time = time; + return this; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof NestedStaticLevel4Record)) { + return false; + } + + NestedStaticLevel4Record that = (NestedStaticLevel4Record) o; + return Objects.equals(attr, that.attr) && Objects.equals(time, that.time); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(attr); + result = 31 * result + Objects.hashCode(time); + return result; + } + } + + + // Bean Table Schemas for Simple Records + public static TableSchema buildBeanSchemaForSimpleRecordWithList() { + return BeanTableSchema.create(SimpleBeanRecordWithList.class); + } + + public static TableSchema buildBeanSchemaForSimpleRecordWithSet() { + return BeanTableSchema.create(SimpleBeanRecordWithSet.class); + } + + public static TableSchema buildBeanSchemaForSimpleRecordWithMap() { + return BeanTableSchema.create(SimpleBeanRecordWithMap.class); + } + + // Bean Table Schemas for Nested Records + public static TableSchema buildBeanSchemaForNestedRecordWithList() { + return BeanTableSchema.create(NestedBeanRecordWithList.class); + } + + public static TableSchema buildBeanSchemaForNestedRecordWithSet() { + return BeanTableSchema.create(NestedBeanRecordWithSet.class); + } + + public static TableSchema buildBeanSchemaForNestedRecordWithMap() { + return BeanTableSchema.create(NestedBeanRecordWithMap.class); + } + + + // Immutable Table Schemas for Simple Records + public static TableSchema buildImmutableSchemaForSimpleRecordWithList() { + return ImmutableTableSchema.create(SimpleImmutableRecordWithList.class); + } + + public static TableSchema buildImmutableSchemaForSimpleRecordWithSet() { + return ImmutableTableSchema.create(SimpleImmutableRecordWithSet.class); + } + + public static TableSchema buildImmutableSchemaForSimpleRecordWithMap() { + return ImmutableTableSchema.create(SimpleImmutableRecordWithMap.class); + } + + + // Immutable Table Schemas for Nested Records + public static TableSchema buildImmutableSchemaForNestedRecordWithList() { + return ImmutableTableSchema.create(NestedImmutableRecordWithList.class); + } + + public static TableSchema buildImmutableSchemaForNestedRecordWithSet() { + return ImmutableTableSchema.create(NestedImmutableRecordWithSet.class); + } + + public static TableSchema buildImmutableSchemaForNestedRecordWithMap() { + return ImmutableTableSchema.create(NestedImmutableRecordWithMap.class); + } + + + // Static Table Schemas for Simple Records + public static TableSchema buildStaticSchemaForSimpleRecordWithList() { + return StaticTableSchema.builder(SimpleStaticRecordWithList.class) + .newItemSupplier(SimpleStaticRecordWithList::new) + .addAttribute(String.class, + a -> a.name("id") + .getter(SimpleStaticRecordWithList::getId) + .setter(SimpleStaticRecordWithList::setId) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(SimpleStaticRecordWithList::getAttr) + .setter(SimpleStaticRecordWithList::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(SimpleStaticRecordWithList::getTime) + .setter(SimpleStaticRecordWithList::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.listOf(EnhancedType.documentOf( + SimpleStaticChild.class, + buildStaticSchemaForSimpleChildRecord())), + a -> a.name("childList") + .getter(SimpleStaticRecordWithList::getChildList) + .setter(SimpleStaticRecordWithList::setChildList)) + .build(); + } + + public static StaticTableSchema buildStaticSchemaForSimpleRecordWithSet() { + return StaticTableSchema.builder(SimpleStaticRecordWithSet.class) + .newItemSupplier(SimpleStaticRecordWithSet::new) + .addAttribute(String.class, + a -> a.name("id") + .getter(SimpleStaticRecordWithSet::getId) + .setter(SimpleStaticRecordWithSet::setId) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(SimpleStaticRecordWithSet::getAttr) + .setter(SimpleStaticRecordWithSet::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(SimpleStaticRecordWithSet::getTime) + .setter(SimpleStaticRecordWithSet::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.setOf( + EnhancedType.documentOf( + SimpleStaticChild.class, + buildStaticSchemaForSimpleChildRecord())), + a -> a.name("childSet") + .getter(SimpleStaticRecordWithSet::getChildSet) + .setter(SimpleStaticRecordWithSet::setChildSet)) + .build(); + } + + public static TableSchema buildStaticSchemaForSimpleRecordWithMap() { + return StaticTableSchema.builder(SimpleStaticRecordWithMap.class) + .newItemSupplier(SimpleStaticRecordWithMap::new) + .addAttribute(String.class, + a -> a.name("id") + .getter(SimpleStaticRecordWithMap::getId) + .setter(SimpleStaticRecordWithMap::setId) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(SimpleStaticRecordWithMap::getAttr) + .setter(SimpleStaticRecordWithMap::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(SimpleStaticRecordWithMap::getTime) + .setter(SimpleStaticRecordWithMap::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.mapOf( + String.class, SimpleStaticChild.class), + a -> a.name("childMap") + .getter(SimpleStaticRecordWithMap::getChildMap) + .setter(SimpleStaticRecordWithMap::setChildMap)) + .build(); + } + + // schema of the simple record used by list/set/map as the deepest simple level + public static TableSchema buildStaticSchemaForSimpleChildRecord() { + return StaticTableSchema.builder(SimpleStaticChild.class) + .newItemSupplier(SimpleStaticChild::new) + .addAttribute(String.class, + a -> a.name("id") + .getter(SimpleStaticChild::getId) + .setter(SimpleStaticChild::setId) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(SimpleStaticChild::getAttr) + .setter(SimpleStaticChild::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(SimpleStaticChild::getTime) + .setter(SimpleStaticChild::setTime) + .tags(autoGeneratedTimestampAttribute())) + .build(); + } + + + // Static Table Schemas for Nested Records + public static TableSchema buildStaticSchemaForNestedRecordWithList() { + return StaticTableSchema.builder(NestedStaticRecordWithList.class) + .newItemSupplier(NestedStaticRecordWithList::new) + .addAttribute(String.class, + a -> a.name("id") + .getter(NestedStaticRecordWithList::getId) + .setter(NestedStaticRecordWithList::setId) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedStaticRecordWithList::getAttr) + .setter(NestedStaticRecordWithList::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedStaticRecordWithList::getTime) + .setter(NestedStaticRecordWithList::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedStaticLevel2RecordWithList.class, + buildStaticSchemaForNestedLevel2RecordWithList()), + a -> a.name("level2") + .getter(NestedStaticRecordWithList::getLevel2) + .setter(NestedStaticRecordWithList::setLevel2)) + .addAttribute(EnhancedType.listOf(EnhancedType.documentOf( + NestedStaticLevel2RecordWithList.class, + buildStaticSchemaForNestedLevel2RecordWithList())), + a -> a.name("level2List") + .getter(NestedStaticRecordWithList::getLevel2List) + .setter(NestedStaticRecordWithList::setLevel2List)) + .build(); + } + + public static TableSchema buildStaticSchemaForNestedLevel2RecordWithList() { + return StaticTableSchema.builder(NestedStaticLevel2RecordWithList.class) + .newItemSupplier(NestedStaticLevel2RecordWithList::new) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedStaticLevel2RecordWithList::getAttr) + .setter(NestedStaticLevel2RecordWithList::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedStaticLevel2RecordWithList::getTime) + .setter(NestedStaticLevel2RecordWithList::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedStaticLevel3RecordWithList.class, + buildStaticSchemaForNestedLevel3RecordWithList()), + a -> a.name("level3") + .getter(NestedStaticLevel2RecordWithList::getLevel3) + .setter(NestedStaticLevel2RecordWithList::setLevel3)) + .addAttribute(EnhancedType.listOf(EnhancedType.documentOf( + NestedStaticLevel3RecordWithList.class, + buildStaticSchemaForNestedLevel3RecordWithList())), + a -> a.name("level3List") + .getter(NestedStaticLevel2RecordWithList::getLevel3List) + .setter(NestedStaticLevel2RecordWithList::setLevel3List)) + .build(); + } + + public static TableSchema buildStaticSchemaForNestedLevel3RecordWithList() { + return StaticTableSchema.builder(NestedStaticLevel3RecordWithList.class) + .newItemSupplier(NestedStaticLevel3RecordWithList::new) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedStaticLevel3RecordWithList::getAttr) + .setter(NestedStaticLevel3RecordWithList::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedStaticLevel3RecordWithList::getTime) + .setter(NestedStaticLevel3RecordWithList::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedStaticLevel4Record.class, + buildStaticSchemaForNestedLevel4Record()), + a -> a.name("level4") + .getter(NestedStaticLevel3RecordWithList::getLevel4) + .setter(NestedStaticLevel3RecordWithList::setLevel4)) + .addAttribute(EnhancedType.listOf( + EnhancedType.documentOf( + NestedStaticLevel4Record.class, + buildStaticSchemaForNestedLevel4Record())), + a -> a.name("level4List") + .getter(NestedStaticLevel3RecordWithList::getLevel4List) + .setter(NestedStaticLevel3RecordWithList::setLevel4List)) + .build(); + } + + public static TableSchema buildStaticSchemaForNestedRecordWithSet() { + return StaticTableSchema.builder(NestedStaticRecordWithSet.class) + .newItemSupplier(NestedStaticRecordWithSet::new) + .addAttribute(String.class, + a -> a.name("id") + .getter(NestedStaticRecordWithSet::getId) + .setter(NestedStaticRecordWithSet::setId) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedStaticRecordWithSet::getAttr) + .setter(NestedStaticRecordWithSet::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedStaticRecordWithSet::getTime) + .setter(NestedStaticRecordWithSet::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedStaticLevel2RecordWithSet.class, + buildStaticSchemaForNestedLevel2RecordWithSet()), + a -> a.name("level2") + .getter(NestedStaticRecordWithSet::getLevel2) + .setter(NestedStaticRecordWithSet::setLevel2)) + .addAttribute(EnhancedType.setOf(EnhancedType.documentOf( + NestedStaticLevel2RecordWithSet.class, + buildStaticSchemaForNestedLevel2RecordWithSet())), + a -> a.name("level2Set") + .getter(NestedStaticRecordWithSet::getLevel2List) + .setter(NestedStaticRecordWithSet::setLevel2List)) + .build(); + } + + public static TableSchema buildStaticSchemaForNestedLevel2RecordWithSet() { + return StaticTableSchema.builder(NestedStaticLevel2RecordWithSet.class) + .newItemSupplier(NestedStaticLevel2RecordWithSet::new) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedStaticLevel2RecordWithSet::getAttr) + .setter(NestedStaticLevel2RecordWithSet::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedStaticLevel2RecordWithSet::getTime) + .setter(NestedStaticLevel2RecordWithSet::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedStaticLevel3RecordWithSet.class, + buildStaticSchemaForNestedLevel3RecordWithSet()), + a -> a.name("level3") + .getter(NestedStaticLevel2RecordWithSet::getLevel3) + .setter(NestedStaticLevel2RecordWithSet::setLevel3)) + .addAttribute(EnhancedType.setOf(EnhancedType.documentOf( + NestedStaticLevel3RecordWithSet.class, + buildStaticSchemaForNestedLevel3RecordWithSet())), + a -> a.name("level3Set") + .getter(NestedStaticLevel2RecordWithSet::getLevel3Set) + .setter(NestedStaticLevel2RecordWithSet::setLevel3Set)) + .build(); + } + + public static TableSchema buildStaticSchemaForNestedLevel3RecordWithSet() { + return StaticTableSchema.builder(NestedStaticLevel3RecordWithSet.class) + .newItemSupplier(NestedStaticLevel3RecordWithSet::new) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedStaticLevel3RecordWithSet::getAttr) + .setter(NestedStaticLevel3RecordWithSet::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedStaticLevel3RecordWithSet::getTime) + .setter(NestedStaticLevel3RecordWithSet::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedStaticLevel4Record.class, + buildStaticSchemaForNestedLevel4Record()), + a -> a.name("level4") + .getter(NestedStaticLevel3RecordWithSet::getLevel4) + .setter(NestedStaticLevel3RecordWithSet::setLevel4)) + .addAttribute(EnhancedType.setOf( + EnhancedType.documentOf( + NestedStaticLevel4Record.class, + buildStaticSchemaForNestedLevel4Record())), + a -> a.name("level4List") + .getter(NestedStaticLevel3RecordWithSet::getLevel4Set) + .setter(NestedStaticLevel3RecordWithSet::setLevel4Set)) + .build(); + } + + public static TableSchema buildStaticSchemaForNestedRecordWithMap() { + return StaticTableSchema.builder(NestedStaticRecordWithMap.class) + .newItemSupplier(NestedStaticRecordWithMap::new) + .addAttribute(String.class, + a -> a.name("id") + .getter(NestedStaticRecordWithMap::getId) + .setter(NestedStaticRecordWithMap::setId) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedStaticRecordWithMap::getAttr) + .setter(NestedStaticRecordWithMap::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedStaticRecordWithMap::getTime) + .setter(NestedStaticRecordWithMap::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedStaticLevel2RecordWithMap.class, + buildStaticSchemaForNestedLevel2RecordWithMap()), + a -> a.name("level2") + .getter(NestedStaticRecordWithMap::getLevel2) + .setter(NestedStaticRecordWithMap::setLevel2)) + .addAttribute(EnhancedType.mapOf( + String.class, NestedStaticLevel2RecordWithMap.class), + a -> a.name("level2Map") + .getter(NestedStaticRecordWithMap::getLevel2Map) + .setter(NestedStaticRecordWithMap::setLevel2Map)) + .build(); + } + + public static TableSchema buildStaticSchemaForNestedLevel2RecordWithMap() { + return StaticTableSchema.builder(NestedStaticLevel2RecordWithMap.class) + .newItemSupplier(NestedStaticLevel2RecordWithMap::new) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedStaticLevel2RecordWithMap::getAttr) + .setter(NestedStaticLevel2RecordWithMap::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedStaticLevel2RecordWithMap::getTime) + .setter(NestedStaticLevel2RecordWithMap::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedStaticLevel3RecordWithMap.class, + buildStaticSchemaForNestedLevel3RecordWithMap()), + a -> a.name("level3") + .getter(NestedStaticLevel2RecordWithMap::getLevel3) + .setter(NestedStaticLevel2RecordWithMap::setLevel3)) + .addAttribute(EnhancedType.mapOf( + String.class, NestedStaticLevel3RecordWithMap.class), + a -> a.name("level3Map") + .getter(NestedStaticLevel2RecordWithMap::getLevel3Map) + .setter(NestedStaticLevel2RecordWithMap::setLevel3Map)) + .build(); + } + + public static TableSchema buildStaticSchemaForNestedLevel3RecordWithMap() { + return StaticTableSchema.builder(NestedStaticLevel3RecordWithMap.class) + .newItemSupplier(NestedStaticLevel3RecordWithMap::new) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedStaticLevel3RecordWithMap::getAttr) + .setter(NestedStaticLevel3RecordWithMap::setAttr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedStaticLevel3RecordWithMap::getTime) + .setter(NestedStaticLevel3RecordWithMap::setTime) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedStaticLevel4Record.class, + buildStaticSchemaForNestedLevel4Record()), + a -> a.name("level4") + .getter(NestedStaticLevel3RecordWithMap::getLevel4) + .setter(NestedStaticLevel3RecordWithMap::setLevel4)) + .addAttribute(EnhancedType.mapOf( + String.class, NestedStaticLevel4Record.class), + a -> a.name("level4Map") + .getter(NestedStaticLevel3RecordWithMap::getLevel4Map) + .setter(NestedStaticLevel3RecordWithMap::setLevel4Map)) + .build(); + } + + // schema of the nested record used by list/set/map as the deepest nested level + public static TableSchema buildStaticSchemaForNestedLevel4Record() { + return StaticTableSchema.builder(NestedStaticLevel4Record.class) + .newItemSupplier(NestedStaticLevel4Record::new) + .addAttribute(String.class, + x -> x.name("attr") + .getter(NestedStaticLevel4Record::getAttr) + .setter(NestedStaticLevel4Record::setAttr)) + .addAttribute(Instant.class, + x -> x.name("time") + .getter(NestedStaticLevel4Record::getTime) + .setter(NestedStaticLevel4Record::setTime) + .tags(autoGeneratedTimestampAttribute())) + .build(); + } + + + // Static Immutable Table Schemas for Simple Records + public static TableSchema buildStaticImmutableSchemaForSimpleChildRecord() { + return StaticImmutableTableSchema.builder(SimpleImmutableChild.class, SimpleImmutableChild.Builder.class) + .newItemBuilder(SimpleImmutableChild::builder, SimpleImmutableChild.Builder::build) + .addAttribute(String.class, + a -> a.name("id") + .getter(SimpleImmutableChild::getId) + .setter(SimpleImmutableChild.Builder::id) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(SimpleImmutableChild::getAttr) + .setter(SimpleImmutableChild.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(SimpleImmutableChild::getTime) + .setter(SimpleImmutableChild.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForSimpleRecordWithList() { + return StaticImmutableTableSchema.builder(SimpleImmutableRecordWithList.class, + SimpleImmutableRecordWithList.Builder.class) + .newItemBuilder(SimpleImmutableRecordWithList::builder, + SimpleImmutableRecordWithList.Builder::build) + .addAttribute(String.class, + a -> a.name("id") + .getter(SimpleImmutableRecordWithList::getId) + .setter(SimpleImmutableRecordWithList.Builder::id) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(SimpleImmutableRecordWithList::getAttr) + .setter(SimpleImmutableRecordWithList.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(SimpleImmutableRecordWithList::getTime) + .setter(SimpleImmutableRecordWithList.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.listOf( + EnhancedType.documentOf( + SimpleImmutableChild.class, + buildStaticImmutableSchemaForSimpleChildRecord())), + a -> a.name("childList") + .getter(SimpleImmutableRecordWithList::getChildList) + .setter(SimpleImmutableRecordWithList.Builder::childList)) + .build(); + } + + public static StaticImmutableTableSchema buildStaticImmutableSchemaForSimpleRecordWithSet() { + return StaticImmutableTableSchema.builder(SimpleImmutableRecordWithSet.class, SimpleImmutableRecordWithSet.Builder.class) + .newItemBuilder(SimpleImmutableRecordWithSet::builder, + SimpleImmutableRecordWithSet.Builder::build) + .addAttribute(String.class, + a -> a.name("id") + .getter(SimpleImmutableRecordWithSet::getId) + .setter(SimpleImmutableRecordWithSet.Builder::id) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(SimpleImmutableRecordWithSet::getAttr) + .setter(SimpleImmutableRecordWithSet.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(SimpleImmutableRecordWithSet::getTime) + .setter(SimpleImmutableRecordWithSet.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.setOf( + EnhancedType.documentOf( + SimpleImmutableChild.class, + buildStaticImmutableSchemaForSimpleChildRecord())), + a -> a.name("childSet") + .getter(SimpleImmutableRecordWithSet::getChildSet) + .setter(SimpleImmutableRecordWithSet.Builder::childSet)) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForSimpleRecordWithMap() { + return StaticImmutableTableSchema.builder(SimpleImmutableRecordWithMap.class, SimpleImmutableRecordWithMap.Builder.class) + .newItemBuilder(SimpleImmutableRecordWithMap::builder, + SimpleImmutableRecordWithMap.Builder::build) + .addAttribute(String.class, + a -> a.name("id") + .getter(SimpleImmutableRecordWithMap::getId) + .setter(SimpleImmutableRecordWithMap.Builder::id) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(SimpleImmutableRecordWithMap::getAttr) + .setter(SimpleImmutableRecordWithMap.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(SimpleImmutableRecordWithMap::getTime) + .setter(SimpleImmutableRecordWithMap.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.mapOf( + String.class, SimpleImmutableChild.class), + a -> a.name("childMap") + .getter(SimpleImmutableRecordWithMap::getChildMap) + .setter(SimpleImmutableRecordWithMap.Builder::childMap)) + .build(); + } + + + // Static Immutable Table Schemas for Nested Records + public static TableSchema buildStaticImmutableSchemaForNestedRecordWithList() { + return StaticImmutableTableSchema.builder(NestedImmutableRecordWithList.class, + NestedImmutableRecordWithList.Builder.class) + .newItemBuilder(NestedImmutableRecordWithList::builder, + NestedImmutableRecordWithList.Builder::build) + .addAttribute(String.class, + a -> a.name("id") + .getter(NestedImmutableRecordWithList::getId) + .setter(NestedImmutableRecordWithList.Builder::id) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableRecordWithList::getAttr) + .setter(NestedImmutableRecordWithList.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableRecordWithList::getTime) + .setter(NestedImmutableRecordWithList.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedImmutableLevel2RecordWithList.class, + buildStaticImmutableSchemaForNestedLevel2RecordWithList()), + a -> a.name("level2") + .getter(NestedImmutableRecordWithList::getLevel2) + .setter(NestedImmutableRecordWithList.Builder::level2)) + .addAttribute(EnhancedType.listOf( + EnhancedType.documentOf( + NestedImmutableLevel2RecordWithList.class, + buildStaticImmutableSchemaForNestedLevel2RecordWithList())), + a -> a.name("level2List") + .getter(NestedImmutableRecordWithList::getLevel2List) + .setter(NestedImmutableRecordWithList.Builder::level2List)) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForNestedLevel2RecordWithList() { + return StaticImmutableTableSchema.builder(NestedImmutableLevel2RecordWithList.class, + NestedImmutableLevel2RecordWithList.Builder.class) + .newItemBuilder(NestedImmutableLevel2RecordWithList::builder, + NestedImmutableLevel2RecordWithList.Builder::build) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableLevel2RecordWithList::getAttr) + .setter(NestedImmutableLevel2RecordWithList.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableLevel2RecordWithList::getTime) + .setter(NestedImmutableLevel2RecordWithList.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedImmutableLevel3RecordWithList.class, + buildStaticImmutableSchemaForNestedLevel3RecordWithList()), + a -> a.name("level3") + .getter(NestedImmutableLevel2RecordWithList::getLevel3) + .setter(NestedImmutableLevel2RecordWithList.Builder::level3)) + .addAttribute(EnhancedType.listOf( + EnhancedType.documentOf( + NestedImmutableLevel3RecordWithList.class, + buildStaticImmutableSchemaForNestedLevel3RecordWithList())), + a -> a.name("level3List") + .getter(NestedImmutableLevel2RecordWithList::getLevel3List) + .setter(NestedImmutableLevel2RecordWithList.Builder::level3List)) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForNestedLevel3RecordWithList() { + return StaticImmutableTableSchema.builder(NestedImmutableLevel3RecordWithList.class, + NestedImmutableLevel3RecordWithList.Builder.class) + .newItemBuilder(NestedImmutableLevel3RecordWithList::builder, + NestedImmutableLevel3RecordWithList.Builder::build) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableLevel3RecordWithList::getAttr) + .setter(NestedImmutableLevel3RecordWithList.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableLevel3RecordWithList::getTime) + .setter(NestedImmutableLevel3RecordWithList.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedImmutableLevel4Record.class, + buildStaticImmutableSchemaForNestedLevel4Record()), + a -> a.name("level4") + .getter(NestedImmutableLevel3RecordWithList::getLevel4) + .setter(NestedImmutableLevel3RecordWithList.Builder::level4)) + .addAttribute(EnhancedType.listOf( + EnhancedType.documentOf( + NestedImmutableLevel4Record.class, + buildStaticImmutableSchemaForNestedLevel4Record())), + a -> a.name("level4List") + .getter(NestedImmutableLevel3RecordWithList::getLevel4List) + .setter(NestedImmutableLevel3RecordWithList.Builder::level4List)) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForNestedRecordWithSet() { + return StaticImmutableTableSchema.builder(NestedImmutableRecordWithSet.class, + NestedImmutableRecordWithSet.Builder.class) + .newItemBuilder(NestedImmutableRecordWithSet::builder, + NestedImmutableRecordWithSet.Builder::build) + .addAttribute(String.class, + a -> a.name("id") + .getter(NestedImmutableRecordWithSet::getId) + .setter(NestedImmutableRecordWithSet.Builder::id) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableRecordWithSet::getAttr) + .setter(NestedImmutableRecordWithSet.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableRecordWithSet::getTime) + .setter(NestedImmutableRecordWithSet.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedImmutableLevel2RecordWithSet.class, + buildStaticImmutableSchemaForNestedLevel2RecordWithSet()), + a -> a.name("level2") + .getter(NestedImmutableRecordWithSet::getLevel2) + .setter(NestedImmutableRecordWithSet.Builder::level2)) + .addAttribute(EnhancedType.setOf( + EnhancedType.documentOf( + NestedImmutableLevel2RecordWithSet.class, + buildStaticImmutableSchemaForNestedLevel2RecordWithSet())), + a -> a.name("level2Set") + .getter(NestedImmutableRecordWithSet::getLevel2Set) + .setter(NestedImmutableRecordWithSet.Builder::level2Set)) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForNestedLevel2RecordWithSet() { + return StaticImmutableTableSchema.builder(NestedImmutableLevel2RecordWithSet.class, + NestedImmutableLevel2RecordWithSet.Builder.class) + .newItemBuilder(NestedImmutableLevel2RecordWithSet::builder, + NestedImmutableLevel2RecordWithSet.Builder::build) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableLevel2RecordWithSet::getAttr) + .setter(NestedImmutableLevel2RecordWithSet.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableLevel2RecordWithSet::getTime) + .setter(NestedImmutableLevel2RecordWithSet.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedImmutableLevel3RecordWithSet.class, + buildStaticImmutableSchemaForNestedLevel3RecordWithSet()), + a -> a.name("level3") + .getter(NestedImmutableLevel2RecordWithSet::getLevel3) + .setter(NestedImmutableLevel2RecordWithSet.Builder::level3)) + .addAttribute(EnhancedType.setOf( + EnhancedType.documentOf( + NestedImmutableLevel3RecordWithSet.class, + buildStaticImmutableSchemaForNestedLevel3RecordWithSet())), + a -> a.name("level3Set") + .getter(NestedImmutableLevel2RecordWithSet::getLevel3Set) + .setter(NestedImmutableLevel2RecordWithSet.Builder::level3Set)) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForNestedLevel3RecordWithSet() { + return StaticImmutableTableSchema.builder(NestedImmutableLevel3RecordWithSet.class, + NestedImmutableLevel3RecordWithSet.Builder.class) + .newItemBuilder(NestedImmutableLevel3RecordWithSet::builder, + NestedImmutableLevel3RecordWithSet.Builder::build) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableLevel3RecordWithSet::getAttr) + .setter(NestedImmutableLevel3RecordWithSet.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableLevel3RecordWithSet::getTime) + .setter(NestedImmutableLevel3RecordWithSet.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedImmutableLevel4Record.class, + buildStaticImmutableSchemaForNestedLevel4Record()), + a -> a.name("level4") + .getter(NestedImmutableLevel3RecordWithSet::getLevel4) + .setter(NestedImmutableLevel3RecordWithSet.Builder::level4)) + .addAttribute(EnhancedType.setOf(EnhancedType.documentOf(NestedImmutableLevel4Record.class, + buildStaticImmutableSchemaForNestedLevel4Record())), + a -> a.name("level4Set") + .getter(NestedImmutableLevel3RecordWithSet::getLevel4Set) + .setter(NestedImmutableLevel3RecordWithSet.Builder::level4Set)) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForNestedRecordWithMap() { + return StaticImmutableTableSchema.builder(NestedImmutableRecordWithMap.class, + NestedImmutableRecordWithMap.Builder.class) + .newItemBuilder(NestedImmutableRecordWithMap::builder, + NestedImmutableRecordWithMap.Builder::build) + .addAttribute(String.class, + a -> a.name("id") + .getter(NestedImmutableRecordWithMap::getId) + .setter(NestedImmutableRecordWithMap.Builder::id) + .tags(primaryPartitionKey())) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableRecordWithMap::getAttr) + .setter(NestedImmutableRecordWithMap.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableRecordWithMap::getTime) + .setter(NestedImmutableRecordWithMap.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedImmutableLevel2RecordWithMap.class, + buildStaticImmutableSchemaForNestedLevel2RecordWithMap()), + a -> a.name("level2") + .getter(NestedImmutableRecordWithMap::getLevel2) + .setter(NestedImmutableRecordWithMap.Builder::level2)) + .addAttribute(EnhancedType.mapOf( + String.class, NestedImmutableLevel2RecordWithMap.class), + a -> a.name("childMap") + .getter(NestedImmutableRecordWithMap::getLevel2Map) + .setter(NestedImmutableRecordWithMap.Builder::level2Map)) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForNestedLevel2RecordWithMap() { + return StaticImmutableTableSchema.builder(NestedImmutableLevel2RecordWithMap.class, + NestedImmutableLevel2RecordWithMap.Builder.class) + .newItemBuilder(NestedImmutableLevel2RecordWithMap::builder, + NestedImmutableLevel2RecordWithMap.Builder::build) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableLevel2RecordWithMap::getAttr) + .setter(NestedImmutableLevel2RecordWithMap.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableLevel2RecordWithMap::getTime) + .setter(NestedImmutableLevel2RecordWithMap.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedImmutableLevel3RecordWithMap.class, + buildStaticImmutableSchemaForNestedLevel3RecordWithMap()), + a -> a.name("level3") + .getter(NestedImmutableLevel2RecordWithMap::getLevel3) + .setter(NestedImmutableLevel2RecordWithMap.Builder::level3)) + .addAttribute(EnhancedType.mapOf( + String.class, NestedImmutableLevel3RecordWithMap.class), + a -> a.name("childMap") + .getter(NestedImmutableLevel2RecordWithMap::getLevel3Map) + .setter(NestedImmutableLevel2RecordWithMap.Builder::level3Map)) + .build(); + } + + public static TableSchema buildStaticImmutableSchemaForNestedLevel3RecordWithMap() { + return StaticImmutableTableSchema.builder(NestedImmutableLevel3RecordWithMap.class, + NestedImmutableLevel3RecordWithMap.Builder.class) + .newItemBuilder(NestedImmutableLevel3RecordWithMap::builder, + NestedImmutableLevel3RecordWithMap.Builder::build) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableLevel3RecordWithMap::getAttr) + .setter(NestedImmutableLevel3RecordWithMap.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableLevel3RecordWithMap::getTime) + .setter(NestedImmutableLevel3RecordWithMap.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .addAttribute(EnhancedType.documentOf( + NestedImmutableLevel4Record.class, + buildStaticImmutableSchemaForNestedLevel4Record()), + a -> a.name("level4") + .getter(NestedImmutableLevel3RecordWithMap::getLevel4) + .setter(NestedImmutableLevel3RecordWithMap.Builder::level4)) + .addAttribute(EnhancedType.mapOf( + String.class, NestedImmutableLevel4Record.class), + a -> a.name("level4Map") + .getter(NestedImmutableLevel3RecordWithMap::getLevel4Map) + .setter(NestedImmutableLevel3RecordWithMap.Builder::level4Map)) + .build(); + } + + // schema of the record used by list/set/map as the deepest nested level + public static TableSchema buildStaticImmutableSchemaForNestedLevel4Record() { + return StaticImmutableTableSchema.builder(NestedImmutableLevel4Record.class, NestedImmutableLevel4Record.Builder.class) + .newItemBuilder(NestedImmutableLevel4Record::builder, + NestedImmutableLevel4Record.Builder::build) + .addAttribute(String.class, + a -> a.name("attr") + .getter(NestedImmutableLevel4Record::getAttr) + .setter(NestedImmutableLevel4Record.Builder::attr)) + .addAttribute(Instant.class, + a -> a.name("time") + .getter(NestedImmutableLevel4Record::getTime) + .setter(NestedImmutableLevel4Record.Builder::time) + .tags(autoGeneratedTimestampAttribute())) + .build(); + } + + + // Object builder methods + public static SimpleBeanRecordWithList buildSimpleBeanRecordWithList() { + return new SimpleBeanRecordWithList() + .setId(ID_1).setAttr(ATTR_LEVEL1) + .setChildList(new ArrayList<>(Arrays.asList( + new SimpleBeanChild().setId(ID_1).setAttr(ATTR_CHILD1), + new SimpleBeanChild().setId(ID_2).setAttr(ATTR_CHILD2)))); + } + + public static SimpleStaticRecordWithList buildSimpleStaticRecordWithList() { + return new SimpleStaticRecordWithList() + .setId(ID_1).setAttr(ATTR_LEVEL1) + .setChildList(new ArrayList<>(Arrays.asList( + new SimpleStaticChild().setId(ID_1).setAttr(ATTR_CHILD1), + new SimpleStaticChild().setId(ID_2).setAttr(ATTR_CHILD2)))); + } + + public static SimpleImmutableRecordWithList buildSimpleImmutableRecordWithList() { + return SimpleImmutableRecordWithList.builder() + .id(ID_1).attr(ATTR_LEVEL1) + .childList(new ArrayList<>(Arrays.asList( + SimpleImmutableChild.builder().id(ID_1).attr(ATTR_CHILD1).build(), + SimpleImmutableChild.builder().id(ID_2).attr(ATTR_CHILD2).build()))) + .build(); + } + + public static NestedBeanRecordWithList buildNestedBeanRecordWithList() { + NestedBeanLevel4Record level4 = + new NestedBeanLevel4Record() + .setId(ID_1) + .setAttr(ATTR_LEVEL4); + + NestedBeanLevel3RecordWithList level3 = + new NestedBeanLevel3RecordWithList() + .setAttr(ATTR_LEVEL3) + .setLevel4(level4) + .setLevel4List(new ArrayList<>(singletonList(level4))); + + NestedBeanLevel2RecordWithList level2 = + new NestedBeanLevel2RecordWithList() + .setAttr(ATTR_LEVEL2).setLevel3(level3) + .setLevel3List(new ArrayList<>(singletonList(level3))); + + return new NestedBeanRecordWithList() + .setId(ID_1).setAttr(ATTR_LEVEL1) + .setLevel2(level2) + .setLevel2List(new ArrayList<>(singletonList(level2))); + } + + public static NestedStaticRecordWithList buildNestedStaticRecordWithList() { + NestedStaticLevel4Record level4 = + new NestedStaticLevel4Record() + .setAttr(ATTR_LEVEL4); + + NestedStaticLevel3RecordWithList level3 = + new NestedStaticLevel3RecordWithList() + .setAttr(ATTR_LEVEL3) + .setLevel4(level4) + .setLevel4List(new ArrayList<>(singletonList(level4))); + + NestedStaticLevel2RecordWithList level2 = + new NestedStaticLevel2RecordWithList() + .setAttr(ATTR_LEVEL2) + .setLevel3(level3) + .setLevel3List(new ArrayList<>(singletonList(level3))); + + return new NestedStaticRecordWithList() + .setId(ID_1) + .setAttr(ATTR_LEVEL1) + .setLevel2(level2) + .setLevel2List(new ArrayList<>(singletonList(level2))); + } + + public static NestedImmutableRecordWithList buildNestedImmutableRecordWithList() { + NestedImmutableLevel4Record level4 = + NestedImmutableLevel4Record.builder() + .id(ID_ATTR) + .attr(ATTR_LEVEL4) + .build(); + + NestedImmutableLevel3RecordWithList level3 = + NestedImmutableLevel3RecordWithList.builder() + .attr(ATTR_LEVEL3) + .level4(level4) + .level4List(new ArrayList<>(singletonList(level4))) + .build(); + + NestedImmutableLevel2RecordWithList level2 = + NestedImmutableLevel2RecordWithList.builder() + .attr(ATTR_LEVEL2) + .level3(level3) + .level3List(new ArrayList<>(singletonList(level3))) + .build(); + + return NestedImmutableRecordWithList.builder() + .id(ID_1) + .attr(ATTR_LEVEL1).level2(level2) + .level2List(new ArrayList<>(singletonList(level2))) + .build(); + } + + public static SimpleBeanRecordWithMap buildSimpleBeanRecordWithMap() { + return new SimpleBeanRecordWithMap() + .setId(ID_1) + .setAttr(ATTR_LEVEL1) + .setChildMap(ImmutableMap.of( + CHILD1_KEY, new SimpleBeanChild().setId(ID_1).setAttr(ATTR_CHILD1), + CHILD2_KEY, new SimpleBeanChild().setId(ID_2).setAttr(ATTR_CHILD2) + )); + } + + public static NestedBeanRecordWithMap buildNestedBeanRecordWithMap() { + NestedBeanLevel4Record level4 = + new NestedBeanLevel4Record() + .setId(ID_1) + .setAttr(ATTR_LEVEL4); + + NestedBeanLevel3RecordWithMap level3 = + new NestedBeanLevel3RecordWithMap() + .setAttr(ATTR_LEVEL3) + .setLevel4(level4) + .setLevel4Map(ImmutableMap.of(LEVEL4_KEY, level4)); + + NestedBeanLevel2RecordWithMap level2 = + new NestedBeanLevel2RecordWithMap() + .setAttr(ATTR_LEVEL2) + .setLevel3(level3) + .setLevel3Map(ImmutableMap.of(LEVEL3_KEY, level3)); + + return new NestedBeanRecordWithMap() + .setId(ID_1) + .setAttr(ATTR_LEVEL1) + .setLevel2(level2) + .setLevel2Map(ImmutableMap.of(LEVEL2_KEY, level2)); + } + + public static SimpleImmutableRecordWithMap buildSimpleImmutableRecordWithMap() { + return SimpleImmutableRecordWithMap.builder() + .id(ID_1).attr(ATTR_LEVEL1) + .childMap(ImmutableMap.of( + CHILD1_KEY, SimpleImmutableChild.builder().id(ID_1).attr(ATTR_CHILD1).build(), + CHILD2_KEY, SimpleImmutableChild.builder().id(ID_2).attr(ATTR_CHILD2).build())) + .build(); + } + + public static NestedImmutableRecordWithMap buildNestedImmutableRecordWithMap() { + NestedImmutableLevel4Record level4 = + NestedImmutableLevel4Record.builder() + .id(ID_1) + .attr(ATTR_LEVEL4) + .build(); + + NestedImmutableLevel3RecordWithMap level3 = + NestedImmutableLevel3RecordWithMap.builder() + .attr(ATTR_LEVEL3) + .level4(level4) + .level4Map(new HashMap<>(singletonMap(LEVEL4_KEY, level4))) + .build(); + + NestedImmutableLevel2RecordWithMap level2 = + NestedImmutableLevel2RecordWithMap.builder() + .attr(ATTR_LEVEL2) + .level3(level3) + .level3Map(new HashMap<>(singletonMap(LEVEL3_KEY, level3))) + .build(); + + return NestedImmutableRecordWithMap.builder() + .id(ID_1) + .attr(ATTR_LEVEL1) + .level2(level2) + .level2Map(new HashMap<>(singletonMap(LEVEL2_KEY, level2))) + .build(); + } +} \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedRecordListElement.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedRecordListElement.java new file mode 100644 index 000000000000..6cf9450f349c --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedRecordListElement.java @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.enhanced.dynamodb.functionaltests.models; + +import java.time.Instant; +import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedTimestampAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; + +@DynamoDbBean +public class NestedRecordListElement { + private String id; + private String attribute; + private Instant timeAttributeElement; + + @DynamoDbPartitionKey + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAttribute() { + return attribute; + } + + public void setAttribute(String attribute) { + this.attribute = attribute; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getTimeAttributeElement() { + return timeAttributeElement; + } + + public void setTimeAttributeElement(Instant timeAttributeElement) { + this.timeAttributeElement = timeAttributeElement; + } +} diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedRecordWithUpdateBehavior.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedRecordWithUpdateBehavior.java index 883a89813c1a..df2e92c57392 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedRecordWithUpdateBehavior.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedRecordWithUpdateBehavior.java @@ -18,6 +18,7 @@ import static software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior.WRITE_IF_NOT_EXISTS; import java.time.Instant; +import java.util.List; import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAtomicCounter; import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedTimestampAttribute; import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute; @@ -30,10 +31,12 @@ public class NestedRecordWithUpdateBehavior { private String id; private String nestedUpdateBehaviorAttribute; private Long nestedVersionedAttribute; - private Instant nestedTimeAttribute; + private Instant nestedCreatedTimeAttribute; + private Instant nestedUpdatedTimeAttribute; private Long nestedCounter; private NestedRecordWithUpdateBehavior nestedRecord; private String attribute; + private List nestedRecordList; @DynamoDbPartitionKey public String getId() { @@ -63,12 +66,22 @@ public void setNestedVersionedAttribute(Long nestedVersionedAttribute) { } @DynamoDbAutoGeneratedTimestampAttribute - public Instant getNestedTimeAttribute() { - return nestedTimeAttribute; + @DynamoDbUpdateBehavior(WRITE_IF_NOT_EXISTS) + public Instant getNestedCreatedTimeAttribute() { + return nestedCreatedTimeAttribute; } - public void setNestedTimeAttribute(Instant nestedTimeAttribute) { - this.nestedTimeAttribute = nestedTimeAttribute; + public void setNestedCreatedTimeAttribute(Instant nestedCreatedTimeAttribute) { + this.nestedCreatedTimeAttribute = nestedCreatedTimeAttribute; + } + + @DynamoDbAutoGeneratedTimestampAttribute + public Instant getNestedUpdatedTimeAttribute() { + return nestedUpdatedTimeAttribute; + } + + public void setNestedUpdatedTimeAttribute(Instant nestedUpdatedTimeAttribute) { + this.nestedUpdatedTimeAttribute = nestedUpdatedTimeAttribute; } @DynamoDbAtomicCounter @@ -95,4 +108,10 @@ public String getAttribute() { public void setAttribute(String attribute) { this.attribute = attribute; } + + public List getNestedRecordList() { return nestedRecordList;} + + public void setNestedRecordList(List nestedRecordList) { + this.nestedRecordList = nestedRecordList; + } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/RecordWithUpdateBehaviors.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/RecordWithUpdateBehaviors.java index 8bd874fee002..ad396ed28d00 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/RecordWithUpdateBehaviors.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/RecordWithUpdateBehaviors.java @@ -15,7 +15,10 @@ package software.amazon.awssdk.enhanced.dynamodb.functionaltests.models; +import static software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior.WRITE_IF_NOT_EXISTS; + import java.time.Instant; +import java.util.List; import software.amazon.awssdk.enhanced.dynamodb.converters.EpochMillisFormatTestConverter; import software.amazon.awssdk.enhanced.dynamodb.converters.TimeFormatUpdateTestConverter; import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedTimestampAttribute; @@ -26,8 +29,6 @@ import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior.WRITE_IF_NOT_EXISTS; - @DynamoDbBean public class RecordWithUpdateBehaviors { private String id; @@ -40,6 +41,7 @@ public class RecordWithUpdateBehaviors { private Instant formattedLastAutoUpdatedOn; private NestedRecordWithUpdateBehavior nestedRecord; private String key; + private List nestedRecordList; @DynamoDbPartitionKey public String getId() { @@ -133,4 +135,10 @@ public NestedRecordWithUpdateBehavior getNestedRecord() { public void setNestedRecord(NestedRecordWithUpdateBehavior nestedRecord) { this.nestedRecord = nestedRecord; } + + public List getNestedRecordList() { return nestedRecordList;} + + public void setNestedRecordList(List nestedRecordList) { + this.nestedRecordList = nestedRecordList; + } }