modifyMetadata(String attributeName,
AttributeValueType attributeValueType) {
- return metadata -> metadata.addCustomMetadataObject(CUSTOM_METADATA_KEY, Collections.singleton(attributeName))
+ return metadata -> metadata.addCustomMetadataObject(
+ CUSTOM_METADATA_KEY, Collections.singletonMap(attributeName, strategy))
.markAttributeAsKey(attributeName, attributeValueType);
}
}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGenerateStrategy.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGenerateStrategy.java
new file mode 100644
index 000000000000..fbed21c7d4c5
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGenerateStrategy.java
@@ -0,0 +1,34 @@
+/*
+ * 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.annotations;
+
+import software.amazon.awssdk.annotations.SdkPublicApi;
+
+/**
+ * Strategy used to decide when a new value is generated for an annotated attribute.
+ */
+@SdkPublicApi
+public enum DynamoDbAutoGenerateStrategy {
+ /**
+ * Generate a new value on every write operation.
+ */
+ ALWAYS,
+
+ /**
+ * Generate a value only when the current value is missing.
+ */
+ CREATE
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java
index 6df85903c20a..28eec8e676af 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbAutoGeneratedUuid.java
@@ -19,18 +19,32 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.util.UUID;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.AutoGeneratedUuidTag;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.BeanTableSchemaAttributeTag;
/**
- * Denotes this attribute as recording the auto generated UUID string for the record. Every time a record with this
- * attribute is written to the database it will update the attribute with a {@link UUID#randomUUID} string.
+ * Denotes this attribute as recording the auto generated UUID string for the record.
+ *
+ * The {@link #strategy()} controls whether UUID is generated on every write or only when missing.
+ * The default is {@link DynamoDbAutoGenerateStrategy#ALWAYS} for backward compatibility with existing
+ * {@code @DynamoDbAutoGeneratedUuid} usage.
+ * Use {@link DynamoDbAutoGenerateStrategy#CREATE} when you want to generate only if the value is missing
+ * (absent from the write item map or DynamoDB {@code NULL}).
+ *
+ * {@code CREATE} inspects the write item map after mapping, not the value stored in DynamoDB.
+ * With {@code updateItem} and {@code ignoreNulls(true)}, a null CREATE field is omitted from the map, so a new UUID
+ * is generated and silently overwrites any existing stored value.
*/
@SdkPublicApi
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@BeanTableSchemaAttributeTag(AutoGeneratedUuidTag.class)
public @interface DynamoDbAutoGeneratedUuid {
-}
\ No newline at end of file
+ /**
+ * Defines when a new UUID should be generated.
+ *
+ * Defaults to {@link DynamoDbAutoGenerateStrategy#ALWAYS} to preserve backward compatibility.
+ */
+ DynamoDbAutoGenerateStrategy strategy() default DynamoDbAutoGenerateStrategy.ALWAYS;
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedUuidTag.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedUuidTag.java
index 17872e71954b..499a1db737b8 100644
--- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedUuidTag.java
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/AutoGeneratedUuidTag.java
@@ -27,7 +27,7 @@ private AutoGeneratedUuidTag() {
}
public static StaticAttributeTag attributeTagFor(DynamoDbAutoGeneratedUuid annotation) {
- return AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute();
+ return AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(annotation.strategy());
}
-}
\ No newline at end of file
+}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/UuidTestUtils.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/UuidTestUtils.java
new file mode 100644
index 000000000000..6b4314bc5668
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/UuidTestUtils.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import java.util.UUID;
+
+public final class UuidTestUtils {
+
+ private UuidTestUtils() {
+ }
+
+ public static boolean isValidUuid(String uuid) {
+ try {
+ UUID.fromString(uuid);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
index 0a48d5f0ba55..d4568cc073d1 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedUuidExtensionTest.java
@@ -19,6 +19,7 @@
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@@ -26,6 +27,7 @@
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.enhanced.dynamodb.OperationContext;
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGenerateStrategy;
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext;
import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext;
import software.amazon.awssdk.enhanced.dynamodb.internal.operations.OperationName;
@@ -65,97 +67,127 @@ public class AutoGeneratedUuidExtensionTest {
.setter(ItemWithUuid::setSimpleString))
.build();
- @Test
- public void beforeWrite_schemaWithoutUuidAttribute_returnsEmptyWriteModification() {
- StaticTableSchema schemaWithoutUuidMetadata =
- StaticTableSchema.builder(ItemWithUuid.class)
- .newItemSupplier(ItemWithUuid::new)
- .addAttribute(String.class, a -> a.name("id")
- .getter(ItemWithUuid::getId)
- .setter(ItemWithUuid::setId)
- .addTag(primaryPartitionKey()))
- .build();
+ private static final StaticTableSchema ITEM_WITH_CREATE_UUID_MAPPER =
+ StaticTableSchema.builder(ItemWithUuid.class)
+ .newItemSupplier(ItemWithUuid::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithUuid::getId)
+ .setter(ItemWithUuid::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(String.class, a -> a.name("uuidAttribute")
+ .getter(ItemWithUuid::getUuidAttribute)
+ .setter(ItemWithUuid::setUuidAttribute)
+ .addTag(AutoGeneratedUuidExtension.AttributeTags
+ .autoGeneratedUuidAttribute(
+ DynamoDbAutoGenerateStrategy.CREATE)))
+ .build();
- ItemWithUuid item = new ItemWithUuid();
- item.setId(RECORD_ID);
- Map items = schemaWithoutUuidMetadata.itemToMap(item, true);
+ private static final StaticTableSchema ITEM_WITHOUT_UUID_MAPPER =
+ StaticTableSchema.builder(ItemWithUuid.class)
+ .newItemSupplier(ItemWithUuid::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(ItemWithUuid::getId)
+ .setter(ItemWithUuid::setId)
+ .addTag(primaryPartitionKey()))
+ .addAttribute(String.class, a -> a.name("simpleString")
+ .getter(ItemWithUuid::getSimpleString)
+ .setter(ItemWithUuid::setSimpleString))
+ .build();
- WriteModification result = uuidExtension.beforeWrite(
- DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(schemaWithoutUuidMetadata.tableMetadata())
- .operationName(OperationName.PUT_ITEM)
- .operationContext(PRIMARY_CONTEXT)
- .build());
+ @Test
+ public void beforeWrite_whenStrategyCreateAndAttributeAbsent_generatesUuid() {
+ Map rawItems = new HashMap<>();
+ rawItems.put("id", AttributeValue.fromS(RECORD_ID));
+
+ WriteModification result =
+ uuidExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(rawItems)
+ .tableMetadata(ITEM_WITH_CREATE_UUID_MAPPER.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
- assertThat(result).usingRecursiveComparison().isEqualTo(WriteModification.builder().build());
+ assertThat(isValidUuid(result.transformedItem().get("uuidAttribute").s())).isTrue();
}
@Test
- public void beforeWrite_updateItemOperation_hasUuidInItem_doesNotCreateUpdateExpressionAndFilters() {
- ItemWithUuid SimpleItem = new ItemWithUuid();
- SimpleItem.setId(RECORD_ID);
- String uuidAttribute = String.valueOf(UUID.randomUUID());
- SimpleItem.setUuidAttribute(uuidAttribute);
+ public void beforeWrite_updateItem_whenStrategyCreateAndValueExists_preservesUuid() {
+ assertUuidBehavior(DynamoDbAutoGenerateStrategy.CREATE, OperationName.UPDATE_ITEM, true, true);
+ }
- Map items = ITEM_WITH_UUID_MAPPER.itemToMap(SimpleItem, true);
- assertThat(items).hasSize(2);
+ @Test
+ public void beforeWrite_updateItem_whenStrategyCreateAndValueEmptyString_preservesEmptyString() {
+ Map rawItems = new HashMap<>();
+ rawItems.put("id", AttributeValue.fromS(RECORD_ID));
+ rawItems.put("uuidAttribute", AttributeValue.fromS(""));
WriteModification result =
- uuidExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_UUID_MAPPER.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT).build());
-
- Map transformedItem = result.transformedItem();
- assertThat(transformedItem).isNotNull().hasSize(2);
- assertThat(transformedItem).containsEntry("id", AttributeValue.fromS(RECORD_ID));
- isValidUuid(transformedItem.get("uuidAttribute").s());
- assertThat(result.updateExpression()).isNull();
-
+ uuidExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(rawItems)
+ .tableMetadata(ITEM_WITH_CREATE_UUID_MAPPER.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ assertThat(result.transformedItem().get("uuidAttribute").s()).isEmpty();
}
@Test
- public void beforeWrite_updateItemOperation_hasNoUuidInItem_doesNotCreatesUpdateExpressionAndFilters() {
- ItemWithUuid SimpleItem = new ItemWithUuid();
- SimpleItem.setId(RECORD_ID);
+ public void beforeWrite_updateItem_whenStrategyCreateAndValueIsDynamoDbNul_generatesUuid() {
+ Map rawItems = new HashMap<>();
+ rawItems.put("id", AttributeValue.fromS(RECORD_ID));
+ rawItems.put("uuidAttribute", AttributeValue.fromNul(true));
- Map items = ITEM_WITH_UUID_MAPPER.itemToMap(SimpleItem, true);
- assertThat(items).hasSize(1);
+ WriteModification result =
+ uuidExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(rawItems)
+ .tableMetadata(ITEM_WITH_CREATE_UUID_MAPPER.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ String actualUuid = result.transformedItem().get("uuidAttribute").s();
+ assertThat(isValidUuid(actualUuid)).isTrue();
+ }
+
+ @Test
+ public void beforeWrite_updateItem_whenStrategyAlwaysAndValueEmptyString_regeneratesUuid() {
+ Map rawItems = new HashMap<>();
+ rawItems.put("id", AttributeValue.fromS(RECORD_ID));
+ rawItems.put("uuidAttribute", AttributeValue.fromS(""));
WriteModification result =
- uuidExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_UUID_MAPPER.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT).build());
-
- Map transformedItem = result.transformedItem();
- assertThat(transformedItem).isNotNull().hasSize(2);
- assertThat(transformedItem).containsEntry("id", AttributeValue.fromS(RECORD_ID));
- isValidUuid(transformedItem.get("uuidAttribute").s());
- assertThat(result.updateExpression()).isNull();
+ uuidExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(rawItems)
+ .tableMetadata(ITEM_WITH_UUID_MAPPER.tableMetadata())
+ .operationName(OperationName.UPDATE_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
+
+ String actualUuid = result.transformedItem().get("uuidAttribute").s();
+ assertThat(isValidUuid(actualUuid)).isTrue();
}
@Test
- public void beforeWrite_updateItemOperation_UuidNotPresent_newUuidCreated() {
+ public void beforeWrite_whenNoAutoGeneratedUuidConfigured_returnsEmptyWriteModification() {
ItemWithUuid item = new ItemWithUuid();
item.setId(RECORD_ID);
+ item.setSimpleString("value");
- Map items = ITEM_WITH_UUID_MAPPER.itemToMap(item, true);
- assertThat(items).hasSize(1);
+ WriteModification result = uuidExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(ITEM_WITHOUT_UUID_MAPPER.itemToMap(item, true))
+ .tableMetadata(ITEM_WITHOUT_UUID_MAPPER.tableMetadata())
+ .operationName(OperationName.PUT_ITEM)
+ .operationContext(PRIMARY_CONTEXT)
+ .build());
- WriteModification result =
- uuidExtension.beforeWrite(DefaultDynamoDbExtensionContext.builder()
- .items(items)
- .tableMetadata(ITEM_WITH_UUID_MAPPER.tableMetadata())
- .operationName(OperationName.UPDATE_ITEM)
- .operationContext(PRIMARY_CONTEXT).build());
- assertThat(result.transformedItem()).isNotNull();
+ assertThat(result.transformedItem()).isNull();
assertThat(result.updateExpression()).isNull();
- assertThat(result.transformedItem()).hasSize(2);
- assertThat(isValidUuid(result.transformedItem().get("uuidAttribute").s())).isTrue();
}
@Test
@@ -182,6 +214,46 @@ void IllegalArgumentException_for_AutogeneratedUuid_withNonStringType() {
+ " to be used as a Auto Generated Uuid attribute. Only String Class type is supported.");
}
+ private void assertUuidBehavior(DynamoDbAutoGenerateStrategy strategy,
+ OperationName operationName,
+ boolean hasExistingValue,
+ boolean shouldPreserveExisting) {
+ ItemWithUuid item = new ItemWithUuid();
+ item.setId(RECORD_ID);
+ String existingUuid = null;
+ if (hasExistingValue) {
+ existingUuid = String.valueOf(UUID.randomUUID());
+ item.setUuidAttribute(existingUuid);
+ }
+
+ StaticTableSchema schema = strategy == DynamoDbAutoGenerateStrategy.CREATE
+ ? ITEM_WITH_CREATE_UUID_MAPPER
+ : ITEM_WITH_UUID_MAPPER;
+ Map items = schema.itemToMap(item, true);
+
+ WriteModification result =
+ uuidExtension.beforeWrite(
+ DefaultDynamoDbExtensionContext.builder()
+ .items(items)
+ .tableMetadata(schema.tableMetadata())
+ .operationName(operationName)
+ .operationContext(PRIMARY_CONTEXT).build());
+
+ assertThat(result.updateExpression()).isNull();
+ assertThat(result.transformedItem()).containsEntry("id", AttributeValue.fromS(RECORD_ID));
+ assertThat(result.transformedItem()).containsKey("uuidAttribute");
+ String actualUuid = result.transformedItem().get("uuidAttribute").s();
+
+ if (shouldPreserveExisting) {
+ assertThat(actualUuid).isEqualTo(existingUuid);
+ } else {
+ assertThat(isValidUuid(actualUuid)).isTrue();
+ if (hasExistingValue) {
+ assertThat(actualUuid).isNotEqualTo(existingUuid);
+ }
+ }
+ }
+
public static boolean isValidUuid(String uuid) {
return UUID_PATTERN.matcher(uuid).matches();
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AsyncAutoGeneratedUuidRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AsyncAutoGeneratedUuidRecordTest.java
index 0a134b54abf7..727395adc729 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AsyncAutoGeneratedUuidRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AsyncAutoGeneratedUuidRecordTest.java
@@ -11,11 +11,12 @@
* 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.assertThat;
+import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.updateBehavior;
@@ -24,10 +25,14 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbAsyncTable;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient;
+import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGenerateStrategy;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
@@ -36,14 +41,74 @@ private static class Record {
private String id;
private String writeAlwaysUuid;
private String writeIfNotExistsUuid;
+ private FlattenedRecord flattenedRecord;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getWriteAlwaysUuid() { return writeAlwaysUuid; }
public void setWriteAlwaysUuid(String writeAlwaysUuid) { this.writeAlwaysUuid = writeAlwaysUuid; }
public String getWriteIfNotExistsUuid() { return writeIfNotExistsUuid; }
public void setWriteIfNotExistsUuid(String writeIfNotExistsUuid) { this.writeIfNotExistsUuid = writeIfNotExistsUuid; }
+
+ public FlattenedRecord getFlattenedRecord() {
+ return flattenedRecord;
+ }
+
+ public void setFlattenedRecord(FlattenedRecord flattenedRecord) {
+ this.flattenedRecord = flattenedRecord;
+ }
+ }
+
+ private static class FlattenedRecord {
+ private String generated;
+
+ public String getGenerated() {
+ return generated;
+ }
+
+ public void setGenerated(String generated) {
+ this.generated = generated;
+ }
+ }
+
+ private static class CreateNonKeyRecord {
+ private String id;
+ private String payload;
+ private String createdUuid;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getPayload() {
+ return payload;
+ }
+
+ public void setPayload(String payload) {
+ this.payload = payload;
+ }
+
+ public String getCreatedUuid() {
+ return createdUuid;
+ }
+
+ public void setCreatedUuid(String createdUuid) {
+ this.createdUuid = createdUuid;
+ }
}
+ private static final TableSchema FLATTENED_SCHEMA =
+ StaticTableSchema.builder(FlattenedRecord.class)
+ .newItemSupplier(FlattenedRecord::new)
+ .addAttribute(String.class, a -> a.name("generated")
+ .getter(FlattenedRecord::getGenerated)
+ .setter(FlattenedRecord::setGenerated)
+ .tags(autoGeneratedUuidAttribute()))
+ .build();
+
private static final TableSchema TABLE_SCHEMA =
StaticTableSchema.builder(Record.class)
.newItemSupplier(Record::new)
@@ -58,6 +123,23 @@ private static class Record {
.setter(Record::setWriteIfNotExistsUuid)
.tags(autoGeneratedUuidAttribute(),
updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)))
+ .flatten(FLATTENED_SCHEMA, Record::getFlattenedRecord, Record::setFlattenedRecord)
+ .build();
+
+ private static final TableSchema CREATE_NON_KEY_SCHEMA =
+ StaticTableSchema.builder(CreateNonKeyRecord.class)
+ .newItemSupplier(CreateNonKeyRecord::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(CreateNonKeyRecord::getId)
+ .setter(CreateNonKeyRecord::setId)
+ .tags(primaryPartitionKey()))
+ .addAttribute(String.class, a -> a.name("payload")
+ .getter(CreateNonKeyRecord::getPayload)
+ .setter(CreateNonKeyRecord::setPayload))
+ .addAttribute(String.class, a -> a.name("createdUuid")
+ .getter(CreateNonKeyRecord::getCreatedUuid)
+ .setter(CreateNonKeyRecord::setCreatedUuid)
+ .tags(autoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy.CREATE)))
.build();
private final DynamoDbEnhancedAsyncClient enhancedAsyncClient =
@@ -69,14 +151,19 @@ private static class Record {
private final DynamoDbAsyncTable mappedTable = enhancedAsyncClient.table(getConcreteTableName("table-name"),
TABLE_SCHEMA);
+ private final DynamoDbAsyncTable createNonKeyTable =
+ enhancedAsyncClient.table(getConcreteTableName("non-key-create"), CREATE_NON_KEY_SCHEMA);
+
@Before
public void createTable() {
mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput())).join();
+ createNonKeyTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput())).join();
}
@After
public void deleteTable() {
getDynamoDbAsyncClient().deleteTable(r -> r.tableName(getConcreteTableName("table-name"))).join();
+ getDynamoDbAsyncClient().deleteTable(r -> r.tableName(getConcreteTableName("non-key-create"))).join();
}
@Test
@@ -98,4 +185,109 @@ public void putOverwrite_withExplicitUuidValues_shouldRegenerateWriteAlwaysAndNo
assertThat(persisted.getWriteIfNotExistsUuid()).isNotNull();
UUID.fromString(persisted.getWriteIfNotExistsUuid());
}
+
+ @Test
+ public void putItem_whenNonKeyCreateAttributeAbsent_generatesUuid() {
+ CreateNonKeyRecord record = new CreateNonKeyRecord();
+ record.setId("id-1");
+ record.setPayload("v1");
+ createNonKeyTable.putItem(record).join();
+
+ CreateNonKeyRecord inserted = createNonKeyTable.getItem(r -> r.key(k -> k.partitionValue("id-1"))).join();
+ assertThat(isValidUuid(inserted.getCreatedUuid())).isTrue();
+ }
+
+ @Test
+ public void updateItem_whenNonKeyCreateAttributePresent_preservesUuid() {
+ CreateNonKeyRecord record = new CreateNonKeyRecord();
+ record.setId("id-2");
+ record.setPayload("v1");
+ createNonKeyTable.putItem(record).join();
+
+ CreateNonKeyRecord inserted = createNonKeyTable.getItem(r -> r.key(k -> k.partitionValue("id-2"))).join();
+ String created = inserted.getCreatedUuid();
+ inserted.setPayload("v2");
+ CreateNonKeyRecord updated = createNonKeyTable.updateItem(inserted).join();
+
+ assertThat(updated.getCreatedUuid()).isEqualTo(created);
+ assertThat(updated.getPayload()).isEqualTo("v2");
+ }
+
+ @Test
+ public void updateItem_whenNonKeyCreateAttributeNull_generatesUuid() {
+ CreateNonKeyRecord record = new CreateNonKeyRecord();
+ record.setId("id-3");
+ record.setPayload("v1");
+ createNonKeyTable.putItem(record).join();
+ String created = createNonKeyTable.getItem(r -> r.key(k -> k.partitionValue("id-3"))).join().getCreatedUuid();
+
+ CreateNonKeyRecord update = new CreateNonKeyRecord();
+ update.setId("id-3");
+ update.setPayload("v2");
+ CreateNonKeyRecord updated = createNonKeyTable.updateItem(update).join();
+
+ assertThat(isValidUuid(updated.getCreatedUuid())).isTrue();
+ assertThat(updated.getCreatedUuid()).isNotEqualTo(created);
+ assertThat(updated.getPayload()).isEqualTo("v2");
+ }
+
+ @Test
+ public void updateItem_ignoreNullsTrue_whenNonKeyCreateAttributeNull_overwritesStoredUuid() {
+ CreateNonKeyRecord record = new CreateNonKeyRecord();
+ record.setId("id-4");
+ record.setPayload("v1");
+ createNonKeyTable.putItem(record).join();
+ String created = createNonKeyTable.getItem(r -> r.key(k -> k.partitionValue("id-4"))).join().getCreatedUuid();
+
+ CreateNonKeyRecord update = new CreateNonKeyRecord();
+ update.setId("id-4");
+ update.setPayload("v2");
+ CreateNonKeyRecord updated = createNonKeyTable.updateItem(r -> r.item(update).ignoreNulls(true)).join();
+
+ assertThat(isValidUuid(updated.getCreatedUuid())).isTrue();
+ assertThat(updated.getCreatedUuid()).isNotEqualTo(created);
+ assertThat(createNonKeyTable.getItem(r -> r.key(k -> k.partitionValue("id-4"))).join().getCreatedUuid())
+ .isEqualTo(updated.getCreatedUuid());
+ }
+
+ @Test
+ public void sharedClient_afterDocumentWrite_nonKeyCreateStillGeneratesUuid() {
+ DynamoDbAsyncTable documentTable =
+ enhancedAsyncClient.table(getConcreteTableName("document-no-metadata"),
+ TableSchema.documentSchemaBuilder()
+ .addIndexPartitionKey(TableMetadata.primaryIndexName(),
+ "id",
+ AttributeValueType.S)
+ .build());
+ documentTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput())).join();
+ try {
+ documentTable.putItem(EnhancedDocument.builder().putString("id", "doc-id").putString("payload", "v1").build()).join();
+
+ CreateNonKeyRecord record = new CreateNonKeyRecord();
+ record.setId("id-shared");
+ record.setPayload("v1");
+ createNonKeyTable.putItem(record).join();
+
+ CreateNonKeyRecord inserted = createNonKeyTable.getItem(r -> r.key(k -> k.partitionValue("id-shared"))).join();
+ assertThat(isValidUuid(inserted.getCreatedUuid())).isTrue();
+ assertThat(documentTable.getItem(r -> r.key(k -> k.partitionValue("doc-id"))).join().getString("payload"))
+ .isEqualTo("v1");
+ } finally {
+ getDynamoDbAsyncClient().deleteTable(r -> r.tableName(getConcreteTableName("document-no-metadata"))).join();
+ }
+ }
+
+ @Test
+ public void putAndUpdate_flattenedAlwaysUuid_generatesAndRegenerates() {
+ Record record = new Record();
+ record.setId("id-flat");
+ mappedTable.putItem(record).join();
+ Record inserted = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-flat"))).join();
+ assertThat(isValidUuid(inserted.getFlattenedRecord().getGenerated())).isTrue();
+ String generated = inserted.getFlattenedRecord().getGenerated();
+
+ Record updated = mappedTable.updateItem(inserted).join();
+ assertThat(isValidUuid(updated.getFlattenedRecord().getGenerated())).isTrue();
+ assertThat(updated.getFlattenedRecord().getGenerated()).isNotEqualTo(generated);
+ }
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
index a6c207e1be0e..5b0c7a55595e 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidRecordTest.java
@@ -16,19 +16,19 @@
package software.amazon.awssdk.enhanced.dynamodb.functionaltests;
import static java.util.stream.Collectors.toList;
+import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
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;
+import static software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional.keyEqualTo;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
-import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.assertj.core.api.Assertions;
import org.junit.After;
@@ -46,33 +46,24 @@
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGenerateStrategy;
import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
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.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
-import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest;
-import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.enhanced.dynamodb.model.ReadBatch;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
-import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
-import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
-import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
@RunWith(Parameterized.class)
public class AutoGeneratedUuidRecordTest extends LocalDynamoDbSyncTestBase{
- private static final String UUID_REGEX =
- "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
-
- private static final Pattern UUID_PATTERN = Pattern.compile(UUID_REGEX);
-
public static void assertValidUuid(String uuid) {
- Assertions.assertThat(UUID_PATTERN.matcher(uuid).matches()).isTrue();
+ Assertions.assertThat(isValidUuid(uuid)).isTrue();
}
private static final String TABLE_NAME = "table-name";
@@ -81,11 +72,11 @@ public static void assertValidUuid(String uuid) {
public AutoGeneratedUuidRecordTest(String testName, TableSchema recordTableSchema) {
- this.mappedTable = DynamoDbEnhancedClient.builder()
- .dynamoDbClient(getDynamoDbClient())
- .extensions(AutoGeneratedUuidExtension.create())
- .build().table(getConcreteTableName("table-name"),
- recordTableSchema);
+ this.enhancedClient = DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(AutoGeneratedUuidExtension.create())
+ .build();
+ this.mappedTable = enhancedClient.table(getConcreteTableName("table-name"), recordTableSchema);
this.testCaseName = testName;
}
@@ -115,8 +106,7 @@ public AutoGeneratedUuidRecordTest(String testName, TableSchema recordTa
.addAttribute(String.class, a -> a.name("createdUuid")
.getter(Record::getCreatedUuid)
.setter(Record::createdUuid)
- .tags(autoGeneratedUuidAttribute(),
- updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)))
+ .tags(autoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy.CREATE)))
.flatten(FLATTENED_TABLE_SCHEMA, Record::getFlattenedRecord, Record::flattenedRecord)
.build();
@@ -126,6 +116,7 @@ public AutoGeneratedUuidRecordTest(String testName, TableSchema recordTa
.mapToObj($ -> createUniqueFakeItem())
.map(fakeItem -> TABLE_SCHEMA.itemToMap(fakeItem, true))
.collect(toList());
+ private final DynamoDbEnhancedClient enhancedClient;
private DynamoDbTable mappedTable;
private final String concreteTableName;
@@ -201,8 +192,9 @@ public void putItemFollowedByUpdates() {
Assertions.assertThat(result.getCreatedUuid()).isNotEqualTo(result.lastUpdatedUuid);
Assertions.assertThat(result.getLastUpdatedUuid()).isNotEqualTo(result.flattenedRecord.getGenerated());
- // UPDATE
- mappedTable.updateItem(r -> r.item(new Record().id("id").attribute("UpdatedItem")));
+ Record fullItemForUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
+ fullItemForUpdate.attribute("UpdatedItem");
+ mappedTable.updateItem(r -> r.item(fullItemForUpdate));
Record afterUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
assertRecordHasValidUuid(afterUpdate);
@@ -211,10 +203,10 @@ public void putItemFollowedByUpdates() {
Assertions.assertThat(afterUpdate.getCreatedUuid()).isNotEqualTo(afterUpdate.lastUpdatedUuid);
Assertions.assertThat(afterUpdate.getLastUpdatedUuid()).isNotEqualTo(afterUpdate.flattenedRecord.getGenerated());
- // UpdateBehavior.WRITE_IF_NOT_EXISTS , the old UUID is not changed
+ // CREATE strategy preserves existing UUID because the field is present in the update payload.
Assertions.assertThat(afterUpdate.getCreatedUuid()).isEqualTo(createdUuidAfterPut);
- // UpdateBehavior.WRITE_ALWAYS
+ // ALWAYS strategy regenerates on each write.
Assertions.assertThat(afterUpdate.getLastUpdatedUuid()).isNotEqualTo(lastUpdatedUuiAfterPut);
Assertions.assertThat(afterUpdate.getFlattenedRecord().getGenerated()).isNotEqualTo(flattenedRecordAfterPut);
Assertions.assertThat(afterUpdate.getAttribute()).isEqualTo("UpdatedItem");
@@ -243,10 +235,10 @@ public void putExistingRecordWithConditionExpressions() {
Record afterUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("newId")));
- // UpdateBehavior.WRITE_IF_NOT_EXISTS , this gets changed because this is a put
+ // CREATE strategy generates on put when the value is missing from the request item.
Assertions.assertThat(afterUpdate.getCreatedUuid()).isNotEqualTo(createdUuidAfterPut);
- // UpdateBehavior.WRITE_ALWAYS
+ // ALWAYS strategy regenerates on each write.
Assertions.assertThat(afterUpdate.getLastUpdatedUuid()).isNotEqualTo(lastUpdatedUuiAfterPut);
Assertions.assertThat(afterUpdate.getFlattenedRecord().getGenerated()).isNotEqualTo(flattenedRecordAfterPut);
Assertions.assertThat(afterUpdate.getAttribute()).isEqualTo("conditionalUpdate");
@@ -268,12 +260,14 @@ public void updateExistingRecordWithConditionExpressions() {
.putExpressionValue(":v1", stringValue("wrong2"))
.build();
- mappedTable.updateItem(r -> r.item(new Record().id("id").attribute("conditionalUpdate"))
+ Record fullItemForConditionalUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
+ fullItemForConditionalUpdate.attribute("conditionalUpdate");
+ mappedTable.updateItem(r -> r.item(fullItemForConditionalUpdate)
.conditionExpression(conditionExpression));
Record afterUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
- // UpdateBehavior.WRITE_IF_NOT_EXISTS , this gets changed because this is a put
+ // CREATE strategy preserves when the existing value is supplied in the update payload.
Assertions.assertThat(afterUpdate.getCreatedUuid()).isEqualTo(createdUuidAfterPut);
- // UpdateBehavior.WRITE_ALWAYS
+ // ALWAYS strategy regenerates on each write.
Assertions.assertThat(afterUpdate.getLastUpdatedUuid()).isNotEqualTo(lastUpdatedUuiAfterPut);
Assertions.assertThat(afterUpdate.getFlattenedRecord().getGenerated()).isNotEqualTo(flattenedRecordAfterPut);
Assertions.assertThat(afterUpdate.getAttribute()).isEqualTo("conditionalUpdate");
@@ -315,7 +309,7 @@ public void updateItemConditionTestFailure() {
}
@Test
- public void putOverwrite_withExplicitUuidValues_shouldRegenerateAllUuidsAffectedByBehavior() {
+ public void putOverwrite_withExplicitUuidValues_preservesCreateAndRegeneratesAlways() {
mappedTable.putItem(r -> r.item(new Record().id("overwrite-id").attribute("one")));
Record first = mappedTable.getItem(r -> r.key(k -> k.partitionValue("overwrite-id")));
@@ -327,15 +321,117 @@ public void putOverwrite_withExplicitUuidValues_shouldRegenerateAllUuidsAffected
mappedTable.putItem(overwrite);
Record second = mappedTable.getItem(r -> r.key(k -> k.partitionValue("overwrite-id")));
- assertRecordHasValidUuid(second);
- Assertions.assertThat(second.getCreatedUuid()).isNotEqualTo(overwrite.getCreatedUuid());
+ Assertions.assertThat(second.getCreatedUuid()).isEqualTo(overwrite.getCreatedUuid());
Assertions.assertThat(second.getLastUpdatedUuid()).isNotEqualTo(overwrite.getLastUpdatedUuid());
Assertions.assertThat(second.getFlattenedRecord().getGenerated())
.isNotEqualTo(overwrite.getFlattenedRecord().getGenerated());
+ assertValidUuid(second.getLastUpdatedUuid());
+ assertValidUuid(second.getFlattenedRecord().getGenerated());
Assertions.assertThat(second.getCreatedUuid()).isNotEqualTo(first.getCreatedUuid());
Assertions.assertThat(second.getAttribute()).isEqualTo("two");
}
+ @Test
+ public void updateItem_whenNonKeyCreateAttributeNull_generatesUuid() {
+ mappedTable.putItem(new Record().id("id-3").attribute("v1"));
+ String created = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-3"))).getCreatedUuid();
+
+ Record updated = mappedTable.updateItem(new Record().id("id-3").attribute("v2"));
+
+ assertValidUuid(updated.getCreatedUuid());
+ Assertions.assertThat(updated.getCreatedUuid()).isNotEqualTo(created);
+ Assertions.assertThat(updated.getAttribute()).isEqualTo("v2");
+ }
+
+ @Test
+ public void updateItem_ignoreNullsTrue_whenNonKeyCreateAttributeNull_overwritesStoredUuid() {
+ mappedTable.putItem(new Record().id("id-4").attribute("v1"));
+ String created = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-4"))).getCreatedUuid();
+
+ Record updated = mappedTable.updateItem(r -> r.item(new Record().id("id-4").attribute("v2")).ignoreNulls(true));
+
+ assertValidUuid(updated.getCreatedUuid());
+ Assertions.assertThat(updated.getCreatedUuid()).isNotEqualTo(created);
+ Assertions.assertThat(mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-4"))).getCreatedUuid())
+ .isEqualTo(updated.getCreatedUuid());
+ }
+
+ @Test
+ public void query_whenNonKeyCreateGenerated_returnsItem() {
+ mappedTable.putItem(new Record().id("id-q").attribute("v1"));
+
+ List results = mappedTable.query(r -> r.queryConditional(keyEqualTo(k -> k.partitionValue("id-q"))))
+ .items()
+ .stream()
+ .collect(Collectors.toList());
+
+ Assertions.assertThat(results).hasSize(1);
+ assertValidUuid(results.get(0).getCreatedUuid());
+ Assertions.assertThat(results.get(0).getAttribute()).isEqualTo("v1");
+ }
+
+ @Test
+ public void deleteItem_whenItemExists_removesRecord() {
+ mappedTable.putItem(new Record().id("id-d").attribute("v1"));
+ mappedTable.deleteItem(r -> r.key(k -> k.partitionValue("id-d")));
+
+ Assertions.assertThat(mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-d")))).isNull();
+ }
+
+ @Test
+ public void putItem_whenCreateAttributeIsEmptyString_preservesEmptyString() {
+ mappedTable.putItem(new Record().id("id-e").attribute("v1").createdUuid(""));
+
+ Record inserted = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-e")));
+ Assertions.assertThat(inserted.getCreatedUuid()).isEmpty();
+ }
+
+ @Test
+ public void updateItem_whenCreateAttributeIsEmptyString_preservesEmptyString() {
+ mappedTable.putItem(new Record().id("id-eu").attribute("v1").createdUuid(""));
+ Record loaded = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-eu")));
+ loaded.attribute("v2");
+
+ Record updated = mappedTable.updateItem(loaded);
+
+ Assertions.assertThat(updated.getCreatedUuid()).isEmpty();
+ Assertions.assertThat(updated.getAttribute()).isEqualTo("v2");
+ }
+
+ @Test
+ public void flattenedUuid_acrossPutUpdateScanBatchGet_preservesCreateAndRegeneratesAlways() {
+ mappedTable.putItem(new Record().id("id-chain").attribute("one"));
+ Record afterPut = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-chain")));
+ String created = afterPut.getCreatedUuid();
+ String lastUpdated = afterPut.getLastUpdatedUuid();
+ String flattened = afterPut.getFlattenedRecord().getGenerated();
+
+ Record loaded = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-chain")));
+ loaded.attribute("two");
+ mappedTable.updateItem(loaded);
+
+ Record afterUpdate = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id-chain")));
+ Assertions.assertThat(afterUpdate.getCreatedUuid()).isEqualTo(created);
+ Assertions.assertThat(afterUpdate.getLastUpdatedUuid()).isNotEqualTo(lastUpdated);
+ Assertions.assertThat(afterUpdate.getFlattenedRecord().getGenerated()).isNotEqualTo(flattened);
+
+ List scanned = mappedTable.scan().items().stream().collect(Collectors.toList());
+ Assertions.assertThat(scanned).hasSize(1);
+ Assertions.assertThat(scanned.get(0).getCreatedUuid()).isEqualTo(created);
+
+ List batchRead = enhancedClient.batchGetItem(r -> r.readBatches(
+ ReadBatch.builder(Record.class)
+ .mappedTableResource(mappedTable)
+ .addGetItem(i -> i.key(k -> k.partitionValue("id-chain")))
+ .build()))
+ .resultsForTable(mappedTable)
+ .stream()
+ .collect(Collectors.toList());
+ Assertions.assertThat(batchRead).hasSize(1);
+ Assertions.assertThat(batchRead.get(0).getCreatedUuid()).isEqualTo(created);
+ Assertions.assertThat(batchRead.get(0).getAttribute()).isEqualTo("two");
+ }
+
public static Record createUniqueFakeItem() {
Record record = new Record();
record.setId(UUID.randomUUID().toString());
@@ -389,8 +485,7 @@ public Record lastUpdatedUuid(String lastUpdatedUuid) {
return this;
}
- @DynamoDbAutoGeneratedUuid
- @DynamoDbUpdateBehavior(value = UpdateBehavior.WRITE_IF_NOT_EXISTS)
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
public String getCreatedUuid() {
return createdUuid;
}
@@ -498,4 +593,5 @@ public String toString() {
'}';
}
}
+
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidStrategyCompositeGsiTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidStrategyCompositeGsiTest.java
new file mode 100644
index 000000000000..40c55cdb2efe
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidStrategyCompositeGsiTest.java
@@ -0,0 +1,442 @@
+/*
+ * 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.assertThat;
+import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.Order.FIRST;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.Order.SECOND;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+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.AutoGeneratedUuidExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGenerateStrategy;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.Order;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex;
+import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
+import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
+import software.amazon.awssdk.services.dynamodb.model.Projection;
+import software.amazon.awssdk.services.dynamodb.model.ProjectionType;
+
+public class AutoGeneratedUuidStrategyCompositeGsiTest extends LocalDynamoDbSyncTestBase {
+
+ private static final TableSchema TABLE_SCHEMA =
+ TableSchema.fromClass(BeanWithMixedCompositeGsi.class);
+
+ private final DynamoDbEnhancedClient enhancedClient =
+ DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(AutoGeneratedUuidExtension.create())
+ .build();
+
+ private final DynamoDbTable mappedTable =
+ enhancedClient.table(getConcreteTableName("mixed-gsi-autogenerated-uuid-strategy-table"), TABLE_SCHEMA);
+
+ private final DynamoDbTable lsiTable =
+ enhancedClient.table(getConcreteTableName("lsi-autogenerated-uuid-create-table"),
+ TableSchema.fromClass(BeanWithLsiCreate.class));
+
+ @Before
+ public void createTable() {
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+ lsiTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput())
+ .localSecondaryIndices(
+ EnhancedLocalSecondaryIndex.create(
+ "lsi1",
+ Projection.builder().projectionType(ProjectionType.ALL).build())));
+ }
+
+ @After
+ public void deleteTable() {
+ getDynamoDbClient().deleteTable(r -> r.tableName(getConcreteTableName("mixed-gsi-autogenerated-uuid-strategy-table")));
+ getDynamoDbClient().deleteTable(r -> r.tableName(getConcreteTableName("lsi-autogenerated-uuid-create-table")));
+ }
+
+ @Test
+ public void create_compositeGsi_whenKeysNotPopulated_generatesExpectedUuids() {
+ BeanWithMixedCompositeGsi result = putAndGetSingleRecord(new BeanWithMixedCompositeGsi());
+ assertAllKeysAreValidUuids(result);
+ }
+
+ @Test
+ public void create_compositeGsi_whenKeysAlreadyPopulatedAndStrategyCreate_preservesExistingValues() {
+ BeanWithMixedCompositeGsi record = buildBeanWithCompositeGsiAndKeysPopulated();
+ BeanWithMixedCompositeGsi result = putAndGetSingleRecord(record);
+
+ assertCompositeKeyValuesArePreserved(result);
+ }
+
+ @Test
+ public void update_compositeGsi_whenMixedStrategiesConfigured_appliesPreserveAndRegenerateBehavior() {
+ BeanWithMixedCompositeGsi record = buildBeanWithCompositeGsiAndKeysPopulated();
+ BeanWithMixedCompositeGsi afterPut = putAndGetSingleRecord(record);
+
+ // Use the full loaded item for update so CREATE strategy fields are present in the write payload.
+ mappedTable.updateItem(afterPut);
+ BeanWithMixedCompositeGsi afterUpdate = getByPrimaryKey(afterPut.getId(), afterPut.getSort());
+
+ assertCreateStrategyFieldsPreserved(afterPut, afterUpdate);
+ assertAlwaysStrategyFieldsRegenerated(afterPut, afterUpdate);
+ }
+
+ @Test
+ public void batchwrite_compositeGsi_whenKeysNotPopulated_generatesExpectedUuidsForAllItems() {
+ BeanWithMixedCompositeGsi firstRecord = new BeanWithMixedCompositeGsi();
+ BeanWithMixedCompositeGsi secondRecord = new BeanWithMixedCompositeGsi();
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(
+ WriteBatch.builder(BeanWithMixedCompositeGsi.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(firstRecord)
+ .addPutItem(secondRecord)
+ .build()));
+
+ List results = scanItems();
+ assertThat(results.size()).isEqualTo(2);
+ assertAllKeysAreValidUuids(results.get(0));
+ assertAllKeysAreValidUuids(results.get(1));
+ }
+
+ @Test
+ public void batchwrite_compositeGsi_whenKeysAlreadyPopulatedAndStrategyCreate_preservesCreateValues() {
+ BeanWithMixedCompositeGsi firstRecord = buildBeanWithCompositeGsiAndKeysPopulated(1);
+ BeanWithMixedCompositeGsi secondRecord = buildBeanWithCompositeGsiAndKeysPopulated(2);
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(WriteBatch.builder(BeanWithMixedCompositeGsi.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(firstRecord)
+ .addPutItem(secondRecord)
+ .build()));
+
+ BeanWithMixedCompositeGsi firstSavedRecord = getByPrimaryKey("existing-id_1", "existing-sort_1");
+ assertCompositeKeyValuesArePreserved(firstSavedRecord, 1);
+
+ BeanWithMixedCompositeGsi secondSavedRecord = getByPrimaryKey("existing-id_2", "existing-sort_2");
+ assertCompositeKeyValuesArePreserved(secondSavedRecord, 2);
+ }
+
+ @Test
+ public void transactwrite_compositeGsi_whenKeysNotPopulated_generatesExpectedUuids() {
+ BeanWithMixedCompositeGsi record = new BeanWithMixedCompositeGsi();
+ enhancedClient.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+ BeanWithMixedCompositeGsi result = scanItems().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertAllKeysAreValidUuids(result);
+ }
+
+ @Test
+ public void transactwrite_compositeGsi_whenKeysAlreadyPopulatedAndStrategyCreate_preservesCreateValues() {
+ BeanWithMixedCompositeGsi record = buildBeanWithCompositeGsiAndKeysPopulated();
+ enhancedClient.transactWriteItems(TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+ BeanWithMixedCompositeGsi result = scanItems().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertCompositeKeyValuesArePreserved(result);
+ }
+
+ @Test
+ public void putItem_whenLsiSortKeyAbsent_generatesUuid() {
+ BeanWithLsiCreate record = new BeanWithLsiCreate();
+ record.setId("pk-1");
+ record.setSort("sk-1");
+ lsiTable.putItem(record);
+
+ BeanWithLsiCreate stored = lsiTable.getItem(r -> r.key(k -> k.partitionValue("pk-1").sortValue("sk-1")));
+ assertThat(isValidUuid(stored.getLsiSort())).isTrue();
+ }
+
+ @Test
+ public void updateItem_whenLsiSortKeyPresent_preservesUuid() {
+ BeanWithLsiCreate record = new BeanWithLsiCreate();
+ record.setId("pk-2");
+ record.setSort("sk-2");
+ lsiTable.putItem(record);
+
+ BeanWithLsiCreate loaded = lsiTable.getItem(r -> r.key(k -> k.partitionValue("pk-2").sortValue("sk-2")));
+ String generated = loaded.getLsiSort();
+ BeanWithLsiCreate updated = lsiTable.updateItem(loaded);
+
+ assertThat(updated.getLsiSort()).isEqualTo(generated);
+ }
+
+ private static void assertAllKeysAreValidUuids(BeanWithMixedCompositeGsi record) {
+ assertThat(isValidUuid(record.getId())).isTrue();
+ assertThat(isValidUuid(record.getSort())).isTrue();
+ assertThat(isValidUuid(record.getRootPartitionKey1())).isTrue();
+ assertThat(isValidUuid(record.getRootPartitionKey2())).isTrue();
+ assertThat(isValidUuid(record.getRootSortKey1())).isTrue();
+ assertThat(isValidUuid(record.getRootSortKey2())).isTrue();
+ assertThat(isValidUuid(record.getFlattenedKeys().flattenedPartitionKey1)).isTrue();
+ assertThat(isValidUuid(record.getFlattenedKeys().flattenedPartitionKey2)).isTrue();
+ assertThat(isValidUuid(record.getFlattenedKeys().flattenedSortKey1)).isTrue();
+ assertThat(isValidUuid(record.getFlattenedKeys().flattenedSortKey2)).isTrue();
+ }
+
+ private static void assertCompositeKeyValuesArePreserved(BeanWithMixedCompositeGsi actual) {
+ assertCompositeKeyValuesArePreserved(actual, null);
+ }
+
+ private static void assertCompositeKeyValuesArePreserved(BeanWithMixedCompositeGsi actual, Integer recordIndex) {
+ String suffix = recordIndex == null ? "" : "_" + recordIndex;
+
+ assertThat(actual.getId()).isEqualTo("existing-id" + suffix);
+ assertThat(actual.getSort()).isEqualTo("existing-sort" + suffix);
+
+ // ALWAYS fields are expected to regenerate each write; assert create-strategy fields only.
+ assertThat(actual.getRootPartitionKey2()).isEqualTo("existing-rootPk2" + suffix);
+ assertThat(actual.getRootSortKey2()).isEqualTo("existing-rootSk2" + suffix);
+ assertThat(actual.getFlattenedKeys().flattenedPartitionKey2).isEqualTo("existing-flattenedPk2" + suffix);
+ assertThat(actual.getFlattenedKeys().flattenedSortKey2).isEqualTo("existing-flattenedSk2" + suffix);
+ }
+
+ private static void assertCreateStrategyFieldsPreserved(BeanWithMixedCompositeGsi before,
+ BeanWithMixedCompositeGsi after) {
+ assertThat(after.getId()).isEqualTo(before.getId());
+ assertThat(after.getSort()).isEqualTo(before.getSort());
+ assertThat(after.getRootPartitionKey2()).isEqualTo(before.getRootPartitionKey2());
+ assertThat(after.getRootSortKey2()).isEqualTo(before.getRootSortKey2());
+ assertThat(after.getFlattenedKeys().flattenedPartitionKey2).isEqualTo(before.getFlattenedKeys().flattenedPartitionKey2);
+ assertThat(after.getFlattenedKeys().flattenedSortKey2).isEqualTo(before.getFlattenedKeys().flattenedSortKey2);
+ }
+
+ private static void assertAlwaysStrategyFieldsRegenerated(BeanWithMixedCompositeGsi before,
+ BeanWithMixedCompositeGsi after) {
+ assertThat(after.getRootPartitionKey1()).isNotEqualTo(before.getRootPartitionKey1());
+ assertThat(after.getRootSortKey1()).isNotEqualTo(before.getRootSortKey1());
+ assertThat(after.getFlattenedKeys().flattenedPartitionKey1).isNotEqualTo(before.getFlattenedKeys().flattenedPartitionKey1);
+ assertThat(after.getFlattenedKeys().flattenedSortKey1).isNotEqualTo(before.getFlattenedKeys().flattenedSortKey1);
+ }
+
+ private BeanWithMixedCompositeGsi putAndGetSingleRecord(BeanWithMixedCompositeGsi record) {
+ mappedTable.putItem(record);
+ return scanItems().stream().findFirst().orElseThrow(() -> new AssertionError("No record found"));
+ }
+
+ private BeanWithMixedCompositeGsi getByPrimaryKey(String partitionKey, String sortKey) {
+ return mappedTable.getItem(r -> r.key(k -> k.partitionValue(partitionKey).sortValue(sortKey)));
+ }
+
+ private List scanItems() {
+ return mappedTable.scan().items().stream().collect(Collectors.toList());
+ }
+
+ private static BeanWithMixedCompositeGsi buildBeanWithCompositeGsiAndKeysPopulated() {
+ return buildBeanWithCompositeGsiAndKeysPopulated(null);
+ }
+
+ private static BeanWithMixedCompositeGsi buildBeanWithCompositeGsiAndKeysPopulated(Integer index) {
+ String suffix = index == null ? "" : "_" + index;
+
+ BeanWithMixedCompositeGsi record = new BeanWithMixedCompositeGsi();
+ record.setId("existing-id" + suffix);
+ record.setSort("existing-sort" + suffix);
+ record.setRootPartitionKey1("existing-rootPk1" + suffix);
+ record.setRootPartitionKey2("existing-rootPk2" + suffix);
+ record.setRootSortKey1("existing-rootSk1" + suffix);
+ record.setRootSortKey2("existing-rootSk2" + suffix);
+
+ BeanWithMixedCompositeGsi.FlattenedKeys flattenedKeys = new BeanWithMixedCompositeGsi.FlattenedKeys();
+ flattenedKeys.setFlattenedPartitionKey1("existing-flattenedPk1" + suffix);
+ flattenedKeys.setFlattenedPartitionKey2("existing-flattenedPk2" + suffix);
+ flattenedKeys.setFlattenedSortKey1("existing-flattenedSk1" + suffix);
+ flattenedKeys.setFlattenedSortKey2("existing-flattenedSk2" + suffix);
+ record.setFlattenedKeys(flattenedKeys);
+
+ return record;
+ }
+
+ @DynamoDbBean
+ public static class BeanWithMixedCompositeGsi {
+ private String id;
+ private String sort;
+ private String rootPartitionKey1;
+ private String rootPartitionKey2;
+ private String rootSortKey1;
+ private String rootSortKey2;
+ private FlattenedKeys flattenedKeys;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ public String getSort() {
+ return sort;
+ }
+
+ public void setSort(String sort) {
+ this.sort = sort;
+ }
+
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.ALWAYS)
+ @DynamoDbSecondaryPartitionKey(indexNames = {"mixed_partition_gsi", "full_mixed_gsi", "mixed_sort_gsi"}, order = FIRST)
+ public String getRootPartitionKey1() {
+ return rootPartitionKey1;
+ }
+
+ public void setRootPartitionKey1(String rootPartitionKey1) {
+ this.rootPartitionKey1 = rootPartitionKey1;
+ }
+
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ @DynamoDbSecondaryPartitionKey(indexNames = {"mixed_partition_gsi", "full_mixed_gsi", "mixed_sort_gsi"}, order = SECOND)
+ public String getRootPartitionKey2() {
+ return rootPartitionKey2;
+ }
+
+ public void setRootPartitionKey2(String rootPartitionKey2) {
+ this.rootPartitionKey2 = rootPartitionKey2;
+ }
+
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.ALWAYS)
+ @DynamoDbSecondarySortKey(indexNames = {"mixed_sort_gsi", "full_mixed_gsi"}, order = FIRST)
+ public String getRootSortKey1() {
+ return rootSortKey1;
+ }
+
+ public void setRootSortKey1(String rootSortKey1) {
+ this.rootSortKey1 = rootSortKey1;
+ }
+
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ @DynamoDbSecondarySortKey(indexNames = {"mixed_sort_gsi", "full_mixed_gsi"}, order = SECOND)
+ public String getRootSortKey2() {
+ return rootSortKey2;
+ }
+
+ public void setRootSortKey2(String rootSortKey2) {
+ this.rootSortKey2 = rootSortKey2;
+ }
+
+ @DynamoDbFlatten
+ public FlattenedKeys getFlattenedKeys() {
+ return flattenedKeys;
+ }
+
+ public void setFlattenedKeys(FlattenedKeys flattenedKeys) {
+ this.flattenedKeys = flattenedKeys;
+ }
+
+ @DynamoDbBean
+ public static class FlattenedKeys {
+ private String flattenedPartitionKey1;
+ private String flattenedPartitionKey2;
+ private String flattenedSortKey1;
+ private String flattenedSortKey2;
+
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.ALWAYS)
+ @DynamoDbSecondaryPartitionKey(indexNames = {"mixed_partition_gsi", "full_mixed_gsi"}, order = Order.THIRD)
+ public String getFlattenedPartitionKey1() {
+ return flattenedPartitionKey1;
+ }
+
+ public void setFlattenedPartitionKey1(String flattenedPartitionKey1) {
+ this.flattenedPartitionKey1 = flattenedPartitionKey1;
+ }
+
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ @DynamoDbSecondaryPartitionKey(indexNames = {"mixed_partition_gsi", "full_mixed_gsi"}, order = Order.FOURTH)
+ public String getFlattenedPartitionKey2() {
+ return flattenedPartitionKey2;
+ }
+
+ public void setFlattenedPartitionKey2(String flattenedPartitionKey2) {
+ this.flattenedPartitionKey2 = flattenedPartitionKey2;
+ }
+
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.ALWAYS)
+ @DynamoDbSecondarySortKey(indexNames = {"mixed_sort_gsi", "full_mixed_gsi"}, order = Order.THIRD)
+ public String getFlattenedSortKey1() {
+ return flattenedSortKey1;
+ }
+
+ public void setFlattenedSortKey1(String flattenedSortKey1) {
+ this.flattenedSortKey1 = flattenedSortKey1;
+ }
+
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ @DynamoDbSecondarySortKey(indexNames = {"mixed_sort_gsi", "full_mixed_gsi"}, order = Order.FOURTH)
+ public String getFlattenedSortKey2() {
+ return flattenedSortKey2;
+ }
+
+ public void setFlattenedSortKey2(String flattenedSortKey2) {
+ this.flattenedSortKey2 = flattenedSortKey2;
+ }
+ }
+ }
+
+ @DynamoDbBean
+ public static class BeanWithLsiCreate {
+ private String id;
+ private String sort;
+ private String lsiSort;
+
+ @DynamoDbPartitionKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ public String getSort() {
+ return sort;
+ }
+
+ public void setSort(String sort) {
+ this.sort = sort;
+ }
+
+ @DynamoDbSecondarySortKey(indexNames = "lsi1")
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ public String getLsiSort() {
+ return lsiSort;
+ }
+
+ public void setLsiSort(String lsiSort) {
+ this.lsiSort = lsiSort;
+ }
+ }
+}
+
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidStrategyRecordTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidStrategyRecordTest.java
new file mode 100644
index 000000000000..71177c9c1052
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedUuidStrategyRecordTest.java
@@ -0,0 +1,842 @@
+/*
+ * 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.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
+import static software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute;
+import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import org.junit.After;
+import org.junit.Test;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
+import software.amazon.awssdk.enhanced.dynamodb.Document;
+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.Key;
+import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
+import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGenerateStrategy;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute;
+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.DynamoDbConvertedBy;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional;
+import software.amazon.awssdk.enhanced.dynamodb.model.ReadBatch;
+import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
+import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
+import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
+
+public class AutoGeneratedUuidStrategyRecordTest extends LocalDynamoDbSyncTestBase {
+ private final List createdTables = new ArrayList<>();
+
+ private DynamoDbEnhancedClient enhancedClient() {
+ return DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(AutoGeneratedUuidExtension.create(),
+ VersionedRecordExtension.builder().build())
+ .build();
+ }
+
+ @After
+ public void deleteTables() {
+ createdTables.forEach(tableName -> {
+ try {
+ getDynamoDbClient().deleteTable(DeleteTableRequest.builder().tableName(tableName).build());
+ } catch (ResourceNotFoundException ignored) {
+ }
+ });
+ createdTables.clear();
+ }
+
+ @Test
+ public void create_bean_whenUuidMissingAndStrategyCreate_generatesUuid() {
+ DynamoDbTable table =
+ createTableResource(newTableName("bean-create"), TableSchema.fromBean(CreateBeanRecord.class));
+
+ CreateBeanRecord inserted = table.updateItem(new CreateBeanRecord().setPayload("v1"));
+
+ assertUuidGenerated(inserted.getId());
+ assertThat(inserted.getPayload()).isEqualTo("v1");
+ }
+
+ @Test
+ public void update_bean_whenPkUsesStrategyCreate_updatesExistingItem() {
+ DynamoDbTable table =
+ createTableResource(newTableName("bean-update-create"), TableSchema.fromBean(CreateBeanRecord.class));
+
+ CreateBeanRecord inserted = table.updateItem(new CreateBeanRecord().setPayload("v1"));
+ CreateBeanRecord updated = table.updateItem(new CreateBeanRecord().setId(inserted.getId()).setPayload("v2"));
+
+ assertUuidPreserved(inserted.getId(), updated.getId());
+ assertThat(updated.getPayload()).isEqualTo("v2");
+ }
+
+ @Test
+ public void update_bean_whenPkUsesStrategyAlways_createsDifferentItem() {
+ DynamoDbTable table =
+ createTableResource(newTableName("bean-update-always"), TableSchema.fromBean(AlwaysBeanRecord.class));
+
+ AlwaysBeanRecord inserted = table.updateItem(new AlwaysBeanRecord().setPayload("v1"));
+ AlwaysBeanRecord updated = table.updateItem(new AlwaysBeanRecord().setId(inserted.getId()).setPayload("v2"));
+
+ assertUuidRegenerated(inserted.getId(), updated.getId());
+ assertThat(table.getItem(r -> r.key(k -> k.partitionValue(inserted.getId()))).getPayload()).isEqualTo("v1");
+ assertThat(table.getItem(r -> r.key(k -> k.partitionValue(updated.getId()))).getPayload()).isEqualTo("v2");
+ }
+
+ @Test
+ public void update_bean_whenVersionedPkUsesStrategyCreate_incrementsVersionSuccessfully() {
+ DynamoDbTable table =
+ createTableResource(newTableName("bean-version-create"), TableSchema.fromBean(VersionedCreateBeanRecord.class));
+
+ VersionedCreateBeanRecord inserted = table.updateItem(new VersionedCreateBeanRecord().setPayload("v1"));
+ VersionedCreateBeanRecord loaded = table.getItem(r -> r.key(k -> k.partitionValue(inserted.getId())));
+ loaded.setPayload("v2");
+
+ VersionedCreateBeanRecord updated = table.updateItem(loaded);
+
+ assertUuidPreserved(inserted.getId(), updated.getId());
+ assertVersionIncremented(loaded.getVersion(), updated.getVersion());
+ }
+
+ @Test
+ public void update_bean_whenVersionedPkUsesStrategyAlways_throwsConditionalCheckFailedException() {
+ DynamoDbTable table =
+ createTableResource(newTableName("bean-version-always"), TableSchema.fromBean(VersionedAlwaysBeanRecord.class));
+
+ VersionedAlwaysBeanRecord inserted = table.updateItem(new VersionedAlwaysBeanRecord().setPayload("v1"));
+ VersionedAlwaysBeanRecord loaded = table.getItem(r -> r.key(k -> k.partitionValue(inserted.getId())));
+ loaded.setPayload("v2");
+
+ assertThatExceptionOfType(ConditionalCheckFailedException.class)
+ .isThrownBy(() -> table.updateItem(loaded));
+ }
+
+ @Test
+ public void update_static_whenPkUsesStrategyCreate_updatesExistingItem() {
+ DynamoDbTable table = createTableResource(newTableName("static-create"), STATIC_CREATE_SCHEMA);
+
+ StaticRecord inserted = table.updateItem(new StaticRecord().setPayload("v1"));
+ StaticRecord updated = table.updateItem(new StaticRecord().setId(inserted.getId()).setPayload("v2"));
+
+ assertUuidPreserved(inserted.getId(), updated.getId());
+ assertThat(updated.getPayload()).isEqualTo("v2");
+ }
+
+ @Test
+ public void update_immutable_whenPkUsesStrategyCreate_updatesExistingItem() {
+ DynamoDbTable table =
+ createTableResource(newTableName("immutable-create"), TableSchema.fromImmutableClass(ImmutableRecord.class));
+
+ ImmutableRecord inserted = table.updateItem(ImmutableRecord.builder().payload("v1").build());
+ ImmutableRecord updated = table.updateItem(ImmutableRecord.builder().id(inserted.id()).payload("v2").build());
+
+ assertUuidPreserved(inserted.id(), updated.id());
+ assertThat(updated.payload()).isEqualTo("v2");
+ }
+
+ @Test
+ public void update_staticimmutable_whenPkUsesStrategyCreate_updatesExistingItem() {
+ DynamoDbTable table =
+ createTableResource(newTableName("static-immutable-create"), STATIC_IMMUTABLE_CREATE_SCHEMA);
+
+ StaticImmutableRecord inserted = table.updateItem(StaticImmutableRecord.builder().payload("v1").build());
+ StaticImmutableRecord updated = table.updateItem(StaticImmutableRecord.builder()
+ .id(inserted.id())
+ .payload("v2")
+ .build());
+
+ assertUuidPreserved(inserted.id(), updated.id());
+ assertThat(updated.payload()).isEqualTo("v2");
+ }
+
+ @Test
+ public void update_convertedby_whenPkUsesStrategyCreate_updatesExistingItem() {
+ DynamoDbTable table =
+ createTableResource(newTableName("convertedby-create"), TableSchema.fromBean(ConvertedBeanRecord.class));
+
+ ConvertedBeanRecord inserted =
+ table.updateItem(new ConvertedBeanRecord().setPayload(new ConvertedPayload("v1")));
+ ConvertedBeanRecord updated =
+ table.updateItem(new ConvertedBeanRecord().setId(inserted.getId()).setPayload(new ConvertedPayload("v2")));
+
+ assertUuidPreserved(inserted.getId(), updated.getId());
+ assertThat(updated.getPayload().value()).isEqualTo("v2");
+ }
+
+ @Test
+ public void subsequentoperations_bean_whenPkUsesStrategyCreate_batchWriteAndTransactWriteBehaveAsExpected() {
+ DynamoDbEnhancedClient enhancedClient = enhancedClient();
+ DynamoDbTable table =
+ createTableResource(newTableName("subsequent-ops-create"), TableSchema.fromBean(CreateBeanRecord.class));
+
+ CreateBeanRecord first = table.updateItem(new CreateBeanRecord().setPayload("first"));
+ CreateBeanRecord second = new CreateBeanRecord().setPayload("second");
+ enhancedClient.batchWriteItem(r -> r.addWriteBatch(WriteBatch.builder(CreateBeanRecord.class)
+ .mappedTableResource(table)
+ .addPutItem(second)
+ .build()));
+
+ CreateBeanRecord batchInserted = table.scan().items().stream()
+ .filter(item -> "second".equals(item.getPayload()))
+ .findFirst()
+ .orElseThrow(IllegalStateException::new);
+ assertUuidGenerated(batchInserted.getId());
+
+ CreateBeanRecord transactUpdated =
+ new CreateBeanRecord().setId(first.getId()).setPayload("first-updated");
+ enhancedClient.transactWriteItems(r -> r.addUpdateItem(table, transactUpdated));
+
+ assertThat(table.getItem(r -> r.key(k -> k.partitionValue(first.getId()))).getPayload()).isEqualTo("first-updated");
+ }
+
+ @Test
+ public void putItem_whenPkCreateAndIdNull_generatesUuid() {
+ DynamoDbTable table =
+ createTableResource(newTableName("put-pk-create"), TableSchema.fromBean(CreateBeanRecord.class));
+
+ table.putItem(new CreateBeanRecord().setPayload("v1"));
+ CreateBeanRecord inserted = table.scan().items().stream()
+ .findFirst()
+ .orElseThrow(IllegalStateException::new);
+
+ assertUuidGenerated(inserted.getId());
+ assertThat(inserted.getPayload()).isEqualTo("v1");
+ }
+
+ @Test
+ public void query_whenPkCreate_returnsItemByGeneratedKey() {
+ DynamoDbTable table =
+ createTableResource(newTableName("query-pk-create"), TableSchema.fromBean(CreateBeanRecord.class));
+
+ CreateBeanRecord inserted = table.updateItem(new CreateBeanRecord().setPayload("v1"));
+ List results =
+ table.query(r -> r.queryConditional(QueryConditional.keyEqualTo(k -> k.partitionValue(inserted.getId()))))
+ .items()
+ .stream()
+ .collect(Collectors.toList());
+
+ assertThat(results).hasSize(1);
+ assertUuidPreserved(inserted.getId(), results.get(0).getId());
+ assertThat(results.get(0).getPayload()).isEqualTo("v1");
+ }
+
+ @Test
+ public void batchGetItem_whenTwoCreateItems_returnsGeneratedKeys() {
+ DynamoDbEnhancedClient enhancedClient = enhancedClient();
+ DynamoDbTable table =
+ enhancedClient.table(newTableName("batch-get-create"), TableSchema.fromBean(CreateBeanRecord.class));
+ createTable(table);
+
+ CreateBeanRecord first = table.updateItem(new CreateBeanRecord().setPayload("a"));
+ CreateBeanRecord second = table.updateItem(new CreateBeanRecord().setPayload("b"));
+
+ List results =
+ enhancedClient.batchGetItem(r -> r.readBatches(
+ ReadBatch.builder(CreateBeanRecord.class)
+ .mappedTableResource(table)
+ .addGetItem(i -> i.key(k -> k.partitionValue(first.getId())))
+ .addGetItem(i -> i.key(k -> k.partitionValue(second.getId())))
+ .build()))
+ .resultsForTable(table)
+ .stream()
+ .collect(Collectors.toList());
+
+ assertThat(results).hasSize(2);
+ assertThat(results.stream().map(CreateBeanRecord::getId)).containsExactlyInAnyOrder(first.getId(), second.getId());
+ }
+
+ @Test
+ public void transactGetItems_whenCreateItem_returnsGeneratedKey() {
+ DynamoDbEnhancedClient enhancedClient = enhancedClient();
+ DynamoDbTable table =
+ enhancedClient.table(newTableName("transact-get-create"), TableSchema.fromBean(CreateBeanRecord.class));
+ createTable(table);
+
+ CreateBeanRecord inserted = table.updateItem(new CreateBeanRecord().setPayload("v1"));
+ List results = enhancedClient.transactGetItems(
+ r -> r.addGetItem(table, Key.builder().partitionValue(inserted.getId()).build()));
+
+ assertThat(results).hasSize(1);
+ assertThat(results.get(0).getItem(table).getId()).isEqualTo(inserted.getId());
+ assertThat(results.get(0).getItem(table).getPayload()).isEqualTo("v1");
+ }
+
+ @Test
+ public void update_document_whenNoAutoGeneratedMetadata_present_doesNotGenerateUuid() {
+ DynamoDbTable table =
+ createTableResource(newTableName("document-no-metadata"),
+ TableSchema.documentSchemaBuilder()
+ .addIndexPartitionKey(TableMetadata.primaryIndexName(),
+ "id",
+ AttributeValueType.S)
+ .build());
+
+ table.updateItem(EnhancedDocument.builder().putString("id", "doc-id").putString("payload", "v1").build());
+ EnhancedDocument updated = table.updateItem(EnhancedDocument.builder()
+ .putString("id", "doc-id")
+ .putString("payload", "v2")
+ .build());
+ assertThat(updated.getString("id")).isEqualTo("doc-id");
+ assertThat(updated.getString("payload")).isEqualTo("v2");
+ }
+
+ @Test
+ public void annotationAndStaticBuilder_onSharedClient_bothGenerateUuid() {
+ DynamoDbEnhancedClient enhancedClient = enhancedClient();
+ DynamoDbTable beanTable =
+ enhancedClient.table(newTableName("annotation-create"), TableSchema.fromBean(CreateBeanRecord.class));
+ DynamoDbTable staticTable =
+ enhancedClient.table(newTableName("builder-create"), STATIC_CREATE_SCHEMA);
+ createTable(beanTable);
+ createTable(staticTable);
+
+ CreateBeanRecord beanInserted = beanTable.updateItem(new CreateBeanRecord().setPayload("bean"));
+ StaticRecord staticInserted = staticTable.updateItem(new StaticRecord().setPayload("static"));
+
+ assertUuidGenerated(beanInserted.getId());
+ assertUuidGenerated(staticInserted.getId());
+ }
+
+ @Test
+ public void sharedClient_afterDocumentWrite_beanCreateStillGeneratesUuid() {
+ DynamoDbEnhancedClient enhancedClient = enhancedClient();
+ DynamoDbTable documentTable =
+ enhancedClient.table(newTableName("document-then-bean"), documentSchema());
+ DynamoDbTable beanTable =
+ enhancedClient.table(newTableName("bean-after-document"), TableSchema.fromBean(CreateBeanRecord.class));
+ createTable(documentTable);
+ createTable(beanTable);
+
+ documentTable.putItem(EnhancedDocument.builder().putString("id", "doc-id").putString("payload", "v1").build());
+ CreateBeanRecord inserted = beanTable.updateItem(new CreateBeanRecord().setPayload("v1"));
+
+ assertThat(documentTable.getItem(r -> r.key(k -> k.partitionValue("doc-id"))).getString("payload")).isEqualTo("v1");
+ assertUuidGenerated(inserted.getId());
+ }
+
+ @Test
+ public void batchGet_mixedBeanStaticAndDocument_returnsItems() {
+ DynamoDbEnhancedClient enhancedClient = enhancedClient();
+ DynamoDbTable beanTable =
+ enhancedClient.table(newTableName("mixed-get-bean"), TableSchema.fromBean(CreateBeanRecord.class));
+ DynamoDbTable staticTable =
+ enhancedClient.table(newTableName("mixed-get-static"), STATIC_CREATE_SCHEMA);
+ DynamoDbTable immutableTable =
+ enhancedClient.table(newTableName("mixed-get-immutable"), TableSchema.fromImmutableClass(ImmutableRecord.class));
+ DynamoDbTable documentTable =
+ enhancedClient.table(newTableName("mixed-get-document"), documentSchema());
+ createTable(beanTable);
+ createTable(staticTable);
+ createTable(immutableTable);
+ createTable(documentTable);
+
+ CreateBeanRecord bean = beanTable.updateItem(new CreateBeanRecord().setPayload("bean"));
+ StaticRecord staticRecord = staticTable.updateItem(new StaticRecord().setPayload("static"));
+ ImmutableRecord immutable = immutableTable.updateItem(ImmutableRecord.builder().payload("immutable").build());
+ documentTable.putItem(EnhancedDocument.builder().putString("id", "doc-id").putString("payload", "doc").build());
+
+ software.amazon.awssdk.enhanced.dynamodb.model.BatchGetResultPageIterable pages =
+ enhancedClient.batchGetItem(r -> r.readBatches(
+ ReadBatch.builder(CreateBeanRecord.class)
+ .mappedTableResource(beanTable)
+ .addGetItem(i -> i.key(k -> k.partitionValue(bean.getId())))
+ .build(),
+ ReadBatch.builder(StaticRecord.class)
+ .mappedTableResource(staticTable)
+ .addGetItem(i -> i.key(k -> k.partitionValue(staticRecord.getId())))
+ .build(),
+ ReadBatch.builder(ImmutableRecord.class)
+ .mappedTableResource(immutableTable)
+ .addGetItem(i -> i.key(k -> k.partitionValue(immutable.id())))
+ .build(),
+ ReadBatch.builder(EnhancedDocument.class)
+ .mappedTableResource(documentTable)
+ .addGetItem(i -> i.key(k -> k.partitionValue("doc-id")))
+ .build()));
+
+ List beans =
+ pages.resultsForTable(beanTable).stream().collect(Collectors.toList());
+ List staticItems =
+ pages.resultsForTable(staticTable).stream().collect(Collectors.toList());
+ List immutableItems =
+ pages.resultsForTable(immutableTable).stream().collect(Collectors.toList());
+ List documents =
+ pages.resultsForTable(documentTable).stream().collect(Collectors.toList());
+
+ assertThat(beans).hasSize(1);
+ assertThat(staticItems).hasSize(1);
+ assertThat(immutableItems).hasSize(1);
+ assertThat(documents).hasSize(1);
+ assertThat(beans.get(0).getId()).isEqualTo(bean.getId());
+ assertThat(documents.get(0).getString("payload")).isEqualTo("doc");
+ }
+
+ @Test
+ public void batchWrite_beanCreateAndDocument_generatesUuidAndPreservesDocument() {
+ DynamoDbEnhancedClient enhancedClient = enhancedClient();
+ DynamoDbTable beanTable =
+ enhancedClient.table(newTableName("mixed-write-bean"), TableSchema.fromBean(CreateBeanRecord.class));
+ DynamoDbTable documentTable =
+ enhancedClient.table(newTableName("mixed-write-document"), documentSchema());
+ createTable(beanTable);
+ createTable(documentTable);
+
+ enhancedClient.batchWriteItem(r -> r.addWriteBatch(
+ WriteBatch.builder(CreateBeanRecord.class)
+ .mappedTableResource(beanTable)
+ .addPutItem(new CreateBeanRecord().setPayload("bean"))
+ .build())
+ .addWriteBatch(
+ WriteBatch.builder(EnhancedDocument.class)
+ .mappedTableResource(documentTable)
+ .addPutItem(EnhancedDocument.builder()
+ .putString("id", "doc-id")
+ .putString("payload", "doc")
+ .build())
+ .build()));
+
+ CreateBeanRecord bean = beanTable.scan().items().stream().findFirst().orElseThrow(IllegalStateException::new);
+ assertUuidGenerated(bean.getId());
+ assertThat(documentTable.getItem(r -> r.key(k -> k.partitionValue("doc-id"))).getString("payload")).isEqualTo("doc");
+ }
+
+ @Test
+ public void transactGet_mixedBeanAndDocument_returnsItems() {
+ DynamoDbEnhancedClient enhancedClient = enhancedClient();
+ DynamoDbTable beanTable =
+ enhancedClient.table(newTableName("mixed-tget-bean"), TableSchema.fromBean(CreateBeanRecord.class));
+ DynamoDbTable documentTable =
+ enhancedClient.table(newTableName("mixed-tget-document"), documentSchema());
+ createTable(beanTable);
+ createTable(documentTable);
+
+ CreateBeanRecord bean = beanTable.updateItem(new CreateBeanRecord().setPayload("bean"));
+ documentTable.putItem(EnhancedDocument.builder().putString("id", "doc-id").putString("payload", "doc").build());
+
+ List results = enhancedClient.transactGetItems(
+ r -> r.addGetItem(beanTable, Key.builder().partitionValue(bean.getId()).build())
+ .addGetItem(documentTable, Key.builder().partitionValue("doc-id").build()));
+
+ assertThat(results).hasSize(2);
+ assertThat(results.get(0).getItem(beanTable).getId()).isEqualTo(bean.getId());
+ assertThat(results.get(1).getItem(documentTable).getString("payload")).isEqualTo("doc");
+ }
+
+ @Test
+ public void transactWrite_mixedPutAndUpdate_createPreservesOnUpdate() {
+ DynamoDbEnhancedClient enhancedClient = enhancedClient();
+ DynamoDbTable table =
+ enhancedClient.table(newTableName("mixed-twrite-create"), TableSchema.fromBean(CreateBeanRecord.class));
+ createTable(table);
+
+ CreateBeanRecord first = table.updateItem(new CreateBeanRecord().setPayload("first"));
+ enhancedClient.transactWriteItems(
+ r -> r.addPutItem(table, new CreateBeanRecord().setPayload("second"))
+ .addUpdateItem(table, new CreateBeanRecord().setId(first.getId()).setPayload("first-updated")));
+
+ CreateBeanRecord updated = table.getItem(r -> r.key(k -> k.partitionValue(first.getId())));
+ CreateBeanRecord inserted = table.scan().items().stream()
+ .filter(item -> "second".equals(item.getPayload()))
+ .findFirst()
+ .orElseThrow(IllegalStateException::new);
+
+ assertUuidPreserved(first.getId(), updated.getId());
+ assertThat(updated.getPayload()).isEqualTo("first-updated");
+ assertUuidGenerated(inserted.getId());
+ }
+
+ private static TableSchema documentSchema() {
+ return TableSchema.documentSchemaBuilder()
+ .addIndexPartitionKey(TableMetadata.primaryIndexName(), "id", AttributeValueType.S)
+ .build();
+ }
+
+ private void createTable(DynamoDbTable> table) {
+ table.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+ createdTables.add(table.tableName());
+ }
+
+ private DynamoDbTable createTableResource(String tableName, TableSchema tableSchema) {
+ DynamoDbTable table = enhancedClient().table(tableName, tableSchema);
+ createTable(table);
+ return table;
+ }
+
+ private void assertUuidGenerated(String value) {
+ assertThat(isValidUuid(value)).isTrue();
+ }
+
+ private void assertUuidPreserved(String before, String after) {
+ assertThat(after).isEqualTo(before);
+ }
+
+ private void assertUuidRegenerated(String before, String after) {
+ assertThat(after).isNotEqualTo(before);
+ assertUuidGenerated(after);
+ }
+
+ private void assertVersionIncremented(Integer previous, Integer current) {
+ assertThat(current).isEqualTo(previous + 1);
+ }
+
+ private String newTableName(String prefix) {
+ return getConcreteTableName(prefix + "-" + UUID.randomUUID());
+ }
+
+ private static final TableSchema STATIC_CREATE_SCHEMA =
+ StaticTableSchema.builder(StaticRecord.class)
+ .newItemSupplier(StaticRecord::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(StaticRecord::getId)
+ .setter(StaticRecord::setId)
+ .tags(primaryPartitionKey(),
+ autoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy.CREATE)))
+ .addAttribute(String.class, a -> a.name("payload")
+ .getter(StaticRecord::getPayload)
+ .setter(StaticRecord::setPayload))
+ .build();
+
+ private static final TableSchema STATIC_IMMUTABLE_CREATE_SCHEMA =
+ StaticImmutableTableSchema.builder(StaticImmutableRecord.class, StaticImmutableRecord.Builder.class)
+ .newItemBuilder(StaticImmutableRecord::builder, StaticImmutableRecord.Builder::build)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(StaticImmutableRecord::id)
+ .setter(StaticImmutableRecord.Builder::id)
+ .tags(primaryPartitionKey(),
+ autoGeneratedUuidAttribute(
+ DynamoDbAutoGenerateStrategy.CREATE)))
+ .addAttribute(String.class, a -> a.name("payload")
+ .getter(StaticImmutableRecord::payload)
+ .setter(StaticImmutableRecord.Builder::payload))
+ .build();
+
+ @DynamoDbBean
+ public static class CreateBeanRecord {
+ private String id;
+ private String payload;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ public String getId() {
+ return id;
+ }
+
+ public CreateBeanRecord setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getPayload() {
+ return payload;
+ }
+
+ public CreateBeanRecord setPayload(String payload) {
+ this.payload = payload;
+ return this;
+ }
+ }
+
+ @DynamoDbBean
+ public static class AlwaysBeanRecord {
+ private String id;
+ private String payload;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid
+ public String getId() {
+ return id;
+ }
+
+ public AlwaysBeanRecord setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getPayload() {
+ return payload;
+ }
+
+ public AlwaysBeanRecord setPayload(String payload) {
+ this.payload = payload;
+ return this;
+ }
+ }
+
+ @DynamoDbBean
+ public static class VersionedCreateBeanRecord {
+ private String id;
+ private String payload;
+ private Integer version;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ public String getId() {
+ return id;
+ }
+
+ public VersionedCreateBeanRecord setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getPayload() {
+ return payload;
+ }
+
+ public VersionedCreateBeanRecord setPayload(String payload) {
+ this.payload = payload;
+ return this;
+ }
+
+ @DynamoDbVersionAttribute
+ public Integer getVersion() {
+ return version;
+ }
+
+ public VersionedCreateBeanRecord setVersion(Integer version) {
+ this.version = version;
+ return this;
+ }
+ }
+
+ @DynamoDbBean
+ public static class VersionedAlwaysBeanRecord {
+ private String id;
+ private String payload;
+ private Integer version;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid
+ public String getId() {
+ return id;
+ }
+
+ public VersionedAlwaysBeanRecord setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getPayload() {
+ return payload;
+ }
+
+ public VersionedAlwaysBeanRecord setPayload(String payload) {
+ this.payload = payload;
+ return this;
+ }
+
+ @DynamoDbVersionAttribute
+ public Integer getVersion() {
+ return version;
+ }
+
+ public VersionedAlwaysBeanRecord setVersion(Integer version) {
+ this.version = version;
+ return this;
+ }
+ }
+
+ @DynamoDbBean
+ public static class StaticRecord {
+ private String id;
+ private String payload;
+
+ public String getId() {
+ return id;
+ }
+
+ public StaticRecord setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getPayload() {
+ return payload;
+ }
+
+ public StaticRecord setPayload(String payload) {
+ this.payload = payload;
+ return this;
+ }
+ }
+
+ @DynamoDbImmutable(builder = ImmutableRecord.Builder.class)
+ public static class ImmutableRecord {
+ private final String id;
+ private final String payload;
+
+ private ImmutableRecord(Builder builder) {
+ this.id = builder.id;
+ this.payload = builder.payload;
+ }
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ public String id() {
+ return id;
+ }
+
+ public String payload() {
+ return payload;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String id;
+ private String payload;
+
+ public Builder id(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder payload(String payload) {
+ this.payload = payload;
+ return this;
+ }
+
+ public ImmutableRecord build() {
+ return new ImmutableRecord(this);
+ }
+ }
+ }
+
+ public static class StaticImmutableRecord {
+ private final String id;
+ private final String payload;
+
+ private StaticImmutableRecord(Builder builder) {
+ this.id = builder.id;
+ this.payload = builder.payload;
+ }
+
+ public String id() {
+ return id;
+ }
+
+ public String payload() {
+ return payload;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String id;
+ private String payload;
+
+ public Builder id(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder payload(String payload) {
+ this.payload = payload;
+ return this;
+ }
+
+ public StaticImmutableRecord build() {
+ return new StaticImmutableRecord(this);
+ }
+ }
+ }
+
+ @DynamoDbBean
+ public static class ConvertedBeanRecord {
+ private String id;
+ private ConvertedPayload payload;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)
+ public String getId() {
+ return id;
+ }
+
+ public ConvertedBeanRecord setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ @DynamoDbConvertedBy(ConvertedPayloadConverter.class)
+ public ConvertedPayload getPayload() {
+ return payload;
+ }
+
+ public ConvertedBeanRecord setPayload(ConvertedPayload payload) {
+ this.payload = payload;
+ return this;
+ }
+ }
+
+ public static class ConvertedPayload {
+ private final String value;
+
+ public ConvertedPayload(String value) {
+ this.value = value;
+ }
+
+ public String value() {
+ return value;
+ }
+ }
+
+ public static class ConvertedPayloadConverter implements AttributeConverter {
+ @Override
+ public AttributeValue transformFrom(ConvertedPayload input) {
+ return input == null ? AttributeValue.builder().nul(true).build() : AttributeValue.fromS(input.value());
+ }
+
+ @Override
+ public ConvertedPayload transformTo(AttributeValue input) {
+ if (input == null || Boolean.TRUE.equals(input.nul())) {
+ return null;
+ }
+ return new ConvertedPayload(input.s());
+ }
+
+ @Override
+ public EnhancedType type() {
+ return EnhancedType.of(ConvertedPayload.class);
+ }
+
+ @Override
+ public AttributeValueType attributeValueType() {
+ return AttributeValueType.S;
+ }
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
new file mode 100644
index 000000000000..458e31368d30
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.extensions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static software.amazon.awssdk.enhanced.dynamodb.UuidTestUtils.isValidUuid;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+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.AutoGeneratedUuidExtension;
+import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
+import software.amazon.awssdk.enhanced.dynamodb.functionaltests.LocalDynamoDbSyncTestBase;
+import software.amazon.awssdk.enhanced.dynamodb.internal.client.ExtensionResolver;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
+import software.amazon.awssdk.enhanced.dynamodb.model.TransactWriteItemsEnhancedRequest;
+import software.amazon.awssdk.enhanced.dynamodb.model.WriteBatch;
+
+public class AutoGeneratedUuidExtensionTest extends LocalDynamoDbSyncTestBase {
+
+ private static final TableSchema TABLE_SCHEMA =
+ TableSchema.fromClass(RecordWithAutogeneratedUuid.class);
+
+ private final DynamoDbEnhancedClient enhancedClient =
+ DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(getDynamoDbClient())
+ .extensions(Stream.concat(
+ ExtensionResolver.defaultExtensions().stream(),
+ Stream.of(AutoGeneratedUuidExtension.create()))
+ .collect(Collectors.toList()))
+ .build();
+
+ private final DynamoDbTable mappedTable =
+ enhancedClient.table(getConcreteTableName("autogenerated-uuid-table"), TABLE_SCHEMA);
+
+ @Before
+ public void createTable() {
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+ }
+
+ @After
+ public void deleteTable() {
+ getDynamoDbClient().deleteTable(r -> r.tableName(getConcreteTableName("autogenerated-uuid-table")));
+ }
+
+ @Test
+ public void putItem_whenKeysNotAlreadyPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
+ record.setId("existing-id");
+
+ mappedTable.putItem(record);
+ RecordWithAutogeneratedUuid result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertThat(isValidUuid(result.getId())).isTrue();
+ assertThat(isValidUuid(result.getSortKey())).isTrue();
+ }
+
+ @Test
+ public void putItem_whenKeysAlreadyPopulated_replacesExistingUuids() {
+ RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+
+ mappedTable.putItem(record);
+ RecordWithAutogeneratedUuid result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertThat(isValidUuid(result.getId())).isTrue();
+ assertThat(isValidUuid(result.getSortKey())).isTrue();
+ assertThat(result.getId()).isNotEqualTo("existing-id");
+ assertThat(result.getSortKey()).isNotEqualTo("existing-sk");
+ }
+
+ @Test
+ public void batchWrite_whenKeysNotAlreadyPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record1 = new RecordWithAutogeneratedUuid();
+ RecordWithAutogeneratedUuid record2 = new RecordWithAutogeneratedUuid();
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(
+ WriteBatch.builder(RecordWithAutogeneratedUuid.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record1)
+ .addPutItem(record2)
+ .build()));
+ List results = mappedTable.scan().items().stream().collect(Collectors.toList());
+
+ assertThat(results.size()).isEqualTo(2);
+ assertThat(isValidUuid(results.get(0).getId())).isTrue();
+ assertThat(isValidUuid(results.get(1).getId())).isTrue();
+ assertThat(isValidUuid(results.get(0).getSortKey())).isTrue();
+ assertThat(isValidUuid(results.get(1).getSortKey())).isTrue();
+ }
+
+ @Test
+ public void batchWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record1 = new RecordWithAutogeneratedUuid();
+ record1.setId("existing-id-1");
+ record1.setSortKey("existing-sk-1");
+
+ RecordWithAutogeneratedUuid record2 = new RecordWithAutogeneratedUuid();
+ record2.setId("existing-id-2");
+ record2.setSortKey("existing-sk-2");
+
+ enhancedClient.batchWriteItem(req -> req.addWriteBatch(
+ WriteBatch.builder(RecordWithAutogeneratedUuid.class)
+ .mappedTableResource(mappedTable)
+ .addPutItem(record1)
+ .addPutItem(record2)
+ .build()));
+
+ List results = mappedTable.scan().items().stream().collect(Collectors.toList());
+
+ assertThat(results.size()).isEqualTo(2);
+ assertThat(results.size()).isEqualTo(2);
+ assertThat(isValidUuid(results.get(0).getId())).isTrue();
+ assertThat(isValidUuid(results.get(1).getId())).isTrue();
+ assertThat(isValidUuid(results.get(0).getSortKey())).isTrue();
+ assertThat(isValidUuid(results.get(1).getSortKey())).isTrue();
+
+ assertThat(results.get(0).getId()).isNotEqualTo("existing-id-1");
+ assertThat(results.get(1).getId()).isNotEqualTo("existing-id-2");
+ assertThat(results.get(0).getSortKey()).isNotEqualTo("existing-sk-1");
+ assertThat(results.get(1).getSortKey()).isNotEqualTo("existing-sk-2");
+ }
+
+ @Test
+ public void transactWrite_whenKeysNotAlreadyPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
+
+ enhancedClient.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+ RecordWithAutogeneratedUuid result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertThat(isValidUuid(result.getId())).isTrue();
+ assertThat(isValidUuid(result.getSortKey())).isTrue();
+ }
+
+ @Test
+ public void transactWrite_whenKeysAlreadyPopulated_generatesNewUuids() {
+ RecordWithAutogeneratedUuid record = new RecordWithAutogeneratedUuid();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+
+ enhancedClient.transactWriteItems(
+ TransactWriteItemsEnhancedRequest.builder()
+ .addPutItem(mappedTable, record)
+ .build());
+ RecordWithAutogeneratedUuid result = mappedTable.scan().items().stream().findFirst()
+ .orElseThrow(() -> new AssertionError("No record found"));
+
+ assertThat(isValidUuid(result.getId())).isTrue();
+ assertThat(isValidUuid(result.getSortKey())).isTrue();
+ assertThat(result.getId()).isNotEqualTo("existing-id");
+ assertThat(result.getSortKey()).isNotEqualTo("existing-sk");
+ }
+
+ @Test
+ public void putItem_whenAutogeneratedUuidAnnotationIsNotPresent_doesNotRegenerateUuids() {
+ String tableName = "no-autogenerated-uuid-table";
+ DynamoDbTable mappedTable =
+ enhancedClient.table(tableName, TableSchema.fromClass(RecordWithoutAutogeneratedUuid.class));
+
+ try {
+ mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+
+ RecordWithoutAutogeneratedUuid record = new RecordWithoutAutogeneratedUuid();
+ record.setId("existing-id");
+ record.setSortKey("existing-sk");
+ record.setGsiPk("existing-gsiPk");
+ record.setGsiSk("existing-gsiSk");
+ record.setData("data");
+
+ mappedTable.putItem(record);
+ RecordWithoutAutogeneratedUuid retrieved = mappedTable.getItem(
+ r -> r.key(k -> k.partitionValue("existing-id").sortValue("existing-sk")));
+
+ assertThat(retrieved.getId()).isEqualTo("existing-id");
+ assertThat(retrieved.getSortKey()).isEqualTo("existing-sk");
+ assertThat(retrieved.getGsiPk()).isEqualTo("existing-gsiPk");
+ assertThat(retrieved.getGsiSk()).isEqualTo("existing-gsiSk");
+ assertThat(retrieved.getData()).isEqualTo("data");
+ } finally {
+ try {
+ mappedTable.deleteTable();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ @Test
+ public void createBean_whenAutogeneratedUuidAnnotationIsAppliedOnNonStringAttribute_throwsException() {
+ assertThatThrownBy(() -> TableSchema.fromBean(AutogeneratedUuidInvalidTypeRecord.class))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage(
+ "Attribute 'id' of Class type class java.lang.Integer is not a suitable Java Class type to be used "
+ + "as a Auto Generated Uuid attribute. Only String Class type is supported.");
+ }
+
+ @DynamoDbBean
+ public static class RecordWithAutogeneratedUuid {
+ private String id;
+ private String sortKey;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ @DynamoDbAutoGeneratedUuid
+ public String getSortKey() {
+ return sortKey;
+ }
+
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
+ }
+
+ @DynamoDbBean
+ public static class RecordWithoutAutogeneratedUuid {
+ private String id;
+ private String sortKey;
+ private String gsiPk;
+ private String gsiSk;
+ private String data;
+
+ @DynamoDbPartitionKey
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @DynamoDbSortKey
+ public String getSortKey() {
+ return sortKey;
+ }
+
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
+
+ @DynamoDbSecondaryPartitionKey(indexNames = "gsi1")
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
+ public String getGsiPk() {
+ return gsiPk;
+ }
+
+ public void setGsiPk(String gsiPk) {
+ this.gsiPk = gsiPk;
+ }
+
+ @DynamoDbSecondarySortKey(indexNames = "gsi1")
+ @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
+ public String getGsiSk() {
+ return gsiSk;
+ }
+
+ public void setGsiSk(String gsiSk) {
+ this.gsiSk = gsiSk;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+ }
+
+ @DynamoDbBean
+ public static class AutogeneratedUuidInvalidTypeRecord {
+ private Integer id;
+
+ @DynamoDbPartitionKey
+ @DynamoDbAutoGeneratedUuid
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+ }
+}
\ No newline at end of file