From 88d1198ca9535a95563e27ea32c2f213c463f3fe Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Tue, 8 Apr 2025 10:28:41 -0700 Subject: [PATCH 1/8] Adding annotation support --- .../awssdk/enhanced/dynamodb/Expression.java | 11 +- .../extensions/VersionedRecordExtension.java | 201 +++++++++++++++--- .../annotations/DynamoDbVersionAttribute.java | 16 ++ .../VersionRecordAttributeTags.java | 2 +- .../VersionedRecordExtensionTest.java | 122 ++++++++++- 5 files changed, 311 insertions(+), 41 deletions(-) diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java index fa0f69ad9ed3..e14cc33401fe 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java @@ -311,8 +311,17 @@ public int hashCode() { return result; } + @Override + public String toString() { + return "Expression{" + + "expression='" + expression + '\'' + + ", expressionValues=" + expressionValues + + ", expressionNames=" + expressionNames + + '}'; + } + /** - * A builder for {@link Expression} + * A builder for {@link Expression}v */ @NotThreadSafe public static final class Builder { diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java index 34a6396c5109..a13d683c7562 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java @@ -34,6 +34,7 @@ import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag; import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.utils.Pair; /** * This extension implements optimistic locking on record writes by means of a 'record version number' that is used @@ -60,8 +61,20 @@ public final class VersionedRecordExtension implements DynamoDbEnhancedClientExt private static final Function VERSIONED_RECORD_EXPRESSION_VALUE_KEY_MAPPER = key -> ":old_" + key + "_value"; private static final String CUSTOM_METADATA_KEY = "VersionedRecordExtension:VersionAttribute"; private static final VersionAttribute VERSION_ATTRIBUTE = new VersionAttribute(); + private static final AttributeValue DEFAULT_VALUE = AttributeValue.fromNul(Boolean.TRUE); - private VersionedRecordExtension() { + private final int startAt; + private final int incrementBy; + + /** + * Creates a new {@link VersionedRecordExtension} using the supplied starting and incrementing value. + * + * @param startAt the value used to compare if a record is the initial version of a record. + * @param incrementBy the amount to increment the version by with each subsequent update. + */ + private VersionedRecordExtension(int startAt, int incrementBy) { + this.startAt = startAt; + this.incrementBy = incrementBy; } public static Builder builder() { @@ -75,19 +88,42 @@ private AttributeTags() { public static StaticAttributeTag versionAttribute() { return VERSION_ATTRIBUTE; } + + public static StaticAttributeTag versionAttribute(int startAt, int incrementBy) { + return new VersionAttribute(startAt, incrementBy); + } } - private static class VersionAttribute implements StaticAttributeTag { + private static final class VersionAttribute implements StaticAttributeTag { + private static final String START_AT_METADATA_KEY = "VersionedRecordExtension:StartAt"; + private static final String INCREMENT_BY_METADATA_KEY = "VersionedRecordExtension:IncrementBy"; + + private final int startAt; + private final int incrementBy; + + private VersionAttribute() { + this.startAt = 0; + this.incrementBy = 1; + } + + private VersionAttribute(int startAt, int incrementBy) { + this.startAt = startAt; + this.incrementBy = incrementBy; + } + @Override public Consumer modifyMetadata(String attributeName, AttributeValueType attributeValueType) { if (attributeValueType != AttributeValueType.N) { throw new IllegalArgumentException(String.format( "Attribute '%s' of type %s is not a suitable type to be used as a version attribute. Only type 'N' " + - "is supported.", attributeName, attributeValueType.name())); + "is supported.", attributeName, attributeValueType.name())); } - return metadata -> metadata.addCustomMetadataObject(CUSTOM_METADATA_KEY, attributeName) + return metadata -> metadata + .addCustomMetadataObject(CUSTOM_METADATA_KEY, attributeName) + .addCustomMetadataObject(START_AT_METADATA_KEY, startAt) + .addCustomMetadataObject(INCREMENT_BY_METADATA_KEY, incrementBy) .markAttributeAsKey(attributeName, attributeValueType); } } @@ -101,39 +137,14 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex return WriteModification.builder().build(); } - Map itemToTransform = new HashMap<>(context.items()); - String attributeKeyRef = keyRef(versionAttributeKey.get()); - AttributeValue newVersionValue; - Expression condition; - Optional existingVersionValue = - Optional.ofNullable(itemToTransform.get(versionAttributeKey.get())); - - if (!existingVersionValue.isPresent() || isNullAttributeValue(existingVersionValue.get())) { - // First version of the record - newVersionValue = AttributeValue.builder().n("1").build(); - condition = Expression.builder() - .expression(String.format("attribute_not_exists(%s)", attributeKeyRef)) - .expressionNames(Collections.singletonMap(attributeKeyRef, versionAttributeKey.get())) - .build(); - } else { - // Existing record, increment version - if (existingVersionValue.get().n() == null) { - // In this case a non-null version attribute is present, but it's not an N - throw new IllegalArgumentException("Version attribute appears to be the wrong type. N is required."); - } + Pair updates = getRecordUpdates(versionAttributeKey.get(), context); - int existingVersion = Integer.parseInt(existingVersionValue.get().n()); - String existingVersionValueKey = VERSIONED_RECORD_EXPRESSION_VALUE_KEY_MAPPER.apply(versionAttributeKey.get()); - newVersionValue = AttributeValue.builder().n(Integer.toString(existingVersion + 1)).build(); - condition = Expression.builder() - .expression(String.format("%s = %s", attributeKeyRef, existingVersionValueKey)) - .expressionNames(Collections.singletonMap(attributeKeyRef, versionAttributeKey.get())) - .expressionValues(Collections.singletonMap(existingVersionValueKey, - existingVersionValue.get())) - .build(); - } + // Unpack values from Pair + AttributeValue newVersionValue = updates.left(); + Expression condition = updates.right(); + Map itemToTransform = new HashMap<>(context.items()); itemToTransform.put(versionAttributeKey.get(), newVersionValue); return WriteModification.builder() @@ -142,13 +153,133 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex .build(); } + private Pair getRecordUpdates(String versionAttributeKey, + DynamoDbExtensionContext.BeforeWrite context) { + Map itemToTransform = context.items(); + + // Default to NUL if not present to reduce additional checks further along + AttributeValue existingVersionValue = itemToTransform.getOrDefault(versionAttributeKey, DEFAULT_VALUE); + + if (isInitialVersion(existingVersionValue, context)) { + // First version of the record ensure it does not exist + return createInitialRecord(versionAttributeKey, context); + } + // Existing record, increment version + return updateExistingRecord(versionAttributeKey, existingVersionValue, context); + } + + private boolean isInitialVersion(AttributeValue existingVersionValue, DynamoDbExtensionContext.BeforeWrite context) { + if (isNullAttributeValue(existingVersionValue)) { + return true; + } + + + Optional versionStartAtFromAnnotation = context.tableMetadata() + .customMetadataObject(VersionAttribute.START_AT_METADATA_KEY, + Integer.class); + + return isNullAttributeValue(existingVersionValue) + || (versionStartAtFromAnnotation.isPresent() && + getExistingVersion(existingVersionValue) == versionStartAtFromAnnotation.get()) + || (!versionStartAtFromAnnotation.isPresent() && + getExistingVersion(existingVersionValue) == this.startAt); + } + + private Pair createInitialRecord(String versionAttributeKey, + DynamoDbExtensionContext.BeforeWrite context) { + Optional versionStartAtFromAnnotation = context.tableMetadata() + .customMetadataObject(VersionAttribute.START_AT_METADATA_KEY, + Integer.class); + + AttributeValue newVersionValue = versionStartAtFromAnnotation.isPresent() ? + incrementVersion(versionStartAtFromAnnotation.get(), context) : + incrementVersion(this.startAt, context); + + + String attributeKeyRef = keyRef(versionAttributeKey); + + Expression condition = Expression.builder() + // Check that the version does not exist before setting the initial value. + .expression(String.format("attribute_not_exists(%s)", attributeKeyRef)) + .expressionNames(Collections.singletonMap(attributeKeyRef, versionAttributeKey)) + .build(); + + return Pair.of(newVersionValue, condition); + } + + private Pair updateExistingRecord(String versionAttributeKey, + AttributeValue existingVersionValue, + DynamoDbExtensionContext.BeforeWrite context) { + int existingVersion = getExistingVersion(existingVersionValue); + AttributeValue newVersionValue = incrementVersion(existingVersion, context); + + String attributeKeyRef = keyRef(versionAttributeKey); + String existingVersionValueKey = VERSIONED_RECORD_EXPRESSION_VALUE_KEY_MAPPER.apply(versionAttributeKey); + + Expression condition = Expression.builder() + // Check that the version matches the existing value before setting the updated value. + .expression(String.format("%s = %s", attributeKeyRef, existingVersionValueKey)) + .expressionNames(Collections.singletonMap(attributeKeyRef, versionAttributeKey)) + .expressionValues(Collections.singletonMap(existingVersionValueKey, + existingVersionValue)) + .build(); + + return Pair.of(newVersionValue, condition); + } + + private int getExistingVersion(AttributeValue existingVersionValue) { + if (existingVersionValue.n() == null) { + // In this case a non-null version attribute is present, but it's not an N + throw new IllegalArgumentException("Version attribute appears to be the wrong type. N is required."); + } + + return Integer.parseInt(existingVersionValue.n()); + } + + private AttributeValue incrementVersion(int version, DynamoDbExtensionContext.BeforeWrite context) { + Optional versionIncrementByFromAnnotation = context.tableMetadata() + .customMetadataObject(VersionAttribute.INCREMENT_BY_METADATA_KEY, + Integer.class); + if (versionIncrementByFromAnnotation.isPresent()) { + return AttributeValue.fromN(Integer.toString(version + versionIncrementByFromAnnotation.get())); + } + return AttributeValue.fromN(Integer.toString(version + this.incrementBy)); + } + @NotThreadSafe public static final class Builder { + private int startAt = 0; + private int incrementBy = 1; + private Builder() { } + /** + * Sets the startAt used to compare if a record is the initial version of a record. + * Default value - {@code 0}. + * + * @param startAt + * @return the builder instance + */ + public Builder startAt(int startAt) { + this.startAt = startAt; + return this; + } + + /** + * Sets the amount to increment the version by with each subsequent update. + * Default value - {@code 1}. + * + * @param incrementBy + * @return the builder instance + */ + public Builder incrementBy(int incrementBy) { + this.incrementBy = incrementBy; + return this; + } + public VersionedRecordExtension build() { - return new VersionedRecordExtension(); + return new VersionedRecordExtension(this.startAt, this.incrementBy); } } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbVersionAttribute.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbVersionAttribute.java index 21f3beeeb446..c664340db03e 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbVersionAttribute.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbVersionAttribute.java @@ -33,4 +33,20 @@ @Retention(RetentionPolicy.RUNTIME) @BeanTableSchemaAttributeTag(VersionRecordAttributeTags.class) public @interface DynamoDbVersionAttribute { + /** + * The starting value for the version attribute. + * Default value - {@code 0}. + * + * @return the starting value + */ + int startAt() default 0; + + /** + * The amount to increment the version by with each update. + * Default value - {@code 1}. + * + * @return the increment value + */ + int incrementBy() default 1; + } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/VersionRecordAttributeTags.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/VersionRecordAttributeTags.java index e1c2d527866b..d81cf268afff 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/VersionRecordAttributeTags.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/extensions/VersionRecordAttributeTags.java @@ -26,6 +26,6 @@ private VersionRecordAttributeTags() { } public static StaticAttributeTag attributeTagFor(DynamoDbVersionAttribute annotation) { - return VersionedRecordExtension.AttributeTags.versionAttribute(); + return VersionedRecordExtension.AttributeTags.versionAttribute(annotation.startAt(), annotation.incrementBy()); } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java index 4f61db7487e9..072407349832 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java @@ -23,14 +23,22 @@ import java.util.HashMap; import java.util.Map; +import java.util.UUID; import org.junit.Test; import software.amazon.awssdk.enhanced.dynamodb.Expression; import software.amazon.awssdk.enhanced.dynamodb.OperationContext; import software.amazon.awssdk.enhanced.dynamodb.TableMetadata; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute; import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItem; import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithSort; import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext; import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext; +import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +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.DynamoDbSortKey; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; public class VersionedRecordExtensionTest { @@ -47,10 +55,10 @@ public void beforeRead_doesNotTransformObject() { ReadModification result = versionedRecordExtension.afterRead(DefaultDynamoDbExtensionContext - .builder() - .items(fakeItemMap) - .tableMetadata(FakeItem.getTableMetadata()) - .operationContext(PRIMARY_CONTEXT).build()); + .builder() + .items(fakeItemMap) + .tableMetadata(FakeItem.getTableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); assertThat(result, is(ReadModification.builder().build())); } @@ -112,6 +120,65 @@ public void beforeWrite_initialVersionDueToExplicitNull_transformedItemIsCorrect assertThat(result.transformedItem(), is(fakeItemWithInitialVersion)); } + @Test + public void customStartingValueAndIncrement_worksAsExpected() { + VersionedRecordExtension recordExtension = VersionedRecordExtension.builder() + .startAt(5) + .incrementBy(2) + .build(); + + FakeItem fakeItem = createUniqueFakeItem(); + + Map inputMap = + new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); + + Map expectedInitialVersion = + new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); + + expectedInitialVersion.put("version", AttributeValue.builder().n("7").build()); + + WriteModification result = + recordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(FakeItem.getTableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + assertThat(result.transformedItem(), is(expectedInitialVersion)); + assertThat(result.additionalConditionalExpression(), + is(Expression.builder() + .expression("attribute_not_exists(#AMZN_MAPPED_version)") + .expressionNames(singletonMap("#AMZN_MAPPED_version", "version")) + .build())); + } + + @Test + public void beforeWrite_initialVersionDueToExplicitZero_expressionAndTransformedItemIsCorrect() { + FakeItem fakeItem = createUniqueFakeItem(); + + Map inputMap = + new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); + inputMap.put("version", AttributeValue.builder().n("0").build()); + + Map fakeItemWithInitialVersion = + new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); + fakeItemWithInitialVersion.put("version", AttributeValue.builder().n("1").build()); + + WriteModification result = + versionedRecordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(FakeItem.getTableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + assertThat(result.transformedItem(), is(fakeItemWithInitialVersion)); + assertThat(result.additionalConditionalExpression(), + is(Expression.builder() + .expression("attribute_not_exists(#AMZN_MAPPED_version)") + .expressionNames(singletonMap("#AMZN_MAPPED_version", "version")) + .build())); + } + @Test public void beforeWrite_existingVersion_expressionIsCorrect() { FakeItem fakeItem = createUniqueFakeItem(); @@ -179,4 +246,51 @@ public void beforeWrite_throwsIllegalArgumentException_ifVersionAttributeIsWrong .tableMetadata(FakeItem.getTableMetadata()) .build()); } + + @DynamoDbBean + public static class CustomVersionedItem { + private String id; + private Integer version; + + public CustomVersionedItem() { + } + + @DynamoDbPartitionKey + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + @DynamoDbVersionAttribute(startAt = 3, incrementBy = 2) + public Integer getVersion() { return version; } + public void setVersion(Integer version) { this.version = version; } + } + + + @Test + public void customStartingValueAndIncrementWithAnnotation_worksAsExpected() { + VersionedRecordExtension recordExtension = VersionedRecordExtension.builder().build(); + + CustomVersionedItem item = new CustomVersionedItem(); + item.setId(UUID.randomUUID().toString()); + + TableSchema schema = TableSchema.fromBean(CustomVersionedItem.class); + + Map inputMap = new HashMap<>(schema.itemToMap(item, true)); + + Map expectedInitialVersion = new HashMap<>(schema.itemToMap(item, true)); + expectedInitialVersion.put("version", AttributeValue.builder().n("5").build()); + + WriteModification result = + recordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(schema.tableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + assertThat(result.transformedItem(), is(expectedInitialVersion)); + assertThat(result.additionalConditionalExpression(), + is(Expression.builder() + .expression("attribute_not_exists(#AMZN_MAPPED_version)") + .expressionNames(singletonMap("#AMZN_MAPPED_version", "version")) + .build())); + } } From 6d443641962d52381d6cd2c109a3c833c815b6fa Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Wed, 9 Apr 2025 15:03:48 -0700 Subject: [PATCH 2/8] Making annotation values nullable to know if initial value is explicitly set --- .../extensions/VersionedRecordExtension.java | 38 +- .../VersionedRecordExtensionTest.java | 340 ++++++++++++++---- 2 files changed, 293 insertions(+), 85 deletions(-) diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java index a13d683c7562..08cef7a07a38 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java @@ -89,7 +89,7 @@ public static StaticAttributeTag versionAttribute() { return VERSION_ATTRIBUTE; } - public static StaticAttributeTag versionAttribute(int startAt, int incrementBy) { + public static StaticAttributeTag versionAttribute(Integer startAt, Integer incrementBy) { return new VersionAttribute(startAt, incrementBy); } } @@ -98,15 +98,15 @@ private static final class VersionAttribute implements StaticAttributeTag { private static final String START_AT_METADATA_KEY = "VersionedRecordExtension:StartAt"; private static final String INCREMENT_BY_METADATA_KEY = "VersionedRecordExtension:IncrementBy"; - private final int startAt; - private final int incrementBy; + private final Integer startAt; + private final Integer incrementBy; private VersionAttribute() { - this.startAt = 0; - this.incrementBy = 1; + this.startAt = null; + this.incrementBy = null; } - private VersionAttribute(int startAt, int incrementBy) { + private VersionAttribute(Integer startAt, Integer incrementBy) { this.startAt = startAt; this.incrementBy = incrementBy; } @@ -120,6 +120,14 @@ public Consumer modifyMetadata(String attributeName "is supported.", attributeName, attributeValueType.name())); } + if (startAt != null && startAt < 0) { + throw new IllegalArgumentException("StartAt cannot be negative."); + } + + if (incrementBy != null && incrementBy < 1) { + throw new IllegalArgumentException("IncrementBy must be greater than 0."); + } + return metadata -> metadata .addCustomMetadataObject(CUSTOM_METADATA_KEY, attributeName) .addCustomMetadataObject(START_AT_METADATA_KEY, startAt) @@ -137,7 +145,6 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex return WriteModification.builder().build(); } - Pair updates = getRecordUpdates(versionAttributeKey.get(), context); // Unpack values from Pair @@ -169,20 +176,13 @@ private Pair getRecordUpdates(String versionAttribut } private boolean isInitialVersion(AttributeValue existingVersionValue, DynamoDbExtensionContext.BeforeWrite context) { - if (isNullAttributeValue(existingVersionValue)) { - return true; - } - - Optional versionStartAtFromAnnotation = context.tableMetadata() .customMetadataObject(VersionAttribute.START_AT_METADATA_KEY, Integer.class); return isNullAttributeValue(existingVersionValue) - || (versionStartAtFromAnnotation.isPresent() && - getExistingVersion(existingVersionValue) == versionStartAtFromAnnotation.get()) - || (!versionStartAtFromAnnotation.isPresent() && - getExistingVersion(existingVersionValue) == this.startAt); + || (versionStartAtFromAnnotation.isPresent() && getExistingVersion(existingVersionValue) == versionStartAtFromAnnotation.get()) + || getExistingVersion(existingVersionValue) == this.startAt; } private Pair createInitialRecord(String versionAttributeKey, @@ -262,6 +262,9 @@ private Builder() { * @return the builder instance */ public Builder startAt(int startAt) { + if (startAt < 0) { + throw new IllegalArgumentException("StartAt cannot be negative."); + } this.startAt = startAt; return this; } @@ -274,6 +277,9 @@ public Builder startAt(int startAt) { * @return the builder instance */ public Builder incrementBy(int incrementBy) { + if (incrementBy < 1) { + throw new IllegalArgumentException("IncrementBy must be greater than 0."); + } this.incrementBy = incrementBy; return this; } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java index 072407349832..ed4ecafc2544 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java @@ -18,13 +18,18 @@ import static java.util.Collections.singletonMap; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItem.createUniqueFakeItem; import static software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithSort.createUniqueFakeItemWithSort; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.stream.Stream; import org.junit.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import software.amazon.awssdk.enhanced.dynamodb.Expression; import software.amazon.awssdk.enhanced.dynamodb.OperationContext; import software.amazon.awssdk.enhanced.dynamodb.TableMetadata; @@ -34,11 +39,8 @@ import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithSort; import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext; import software.amazon.awssdk.enhanced.dynamodb.internal.operations.DefaultOperationContext; -import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata; -import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; 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.DynamoDbSortKey; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; public class VersionedRecordExtensionTest { @@ -120,65 +122,6 @@ public void beforeWrite_initialVersionDueToExplicitNull_transformedItemIsCorrect assertThat(result.transformedItem(), is(fakeItemWithInitialVersion)); } - @Test - public void customStartingValueAndIncrement_worksAsExpected() { - VersionedRecordExtension recordExtension = VersionedRecordExtension.builder() - .startAt(5) - .incrementBy(2) - .build(); - - FakeItem fakeItem = createUniqueFakeItem(); - - Map inputMap = - new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); - - Map expectedInitialVersion = - new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); - - expectedInitialVersion.put("version", AttributeValue.builder().n("7").build()); - - WriteModification result = - recordExtension.beforeWrite(DefaultDynamoDbExtensionContext - .builder() - .items(inputMap) - .tableMetadata(FakeItem.getTableMetadata()) - .operationContext(PRIMARY_CONTEXT).build()); - - assertThat(result.transformedItem(), is(expectedInitialVersion)); - assertThat(result.additionalConditionalExpression(), - is(Expression.builder() - .expression("attribute_not_exists(#AMZN_MAPPED_version)") - .expressionNames(singletonMap("#AMZN_MAPPED_version", "version")) - .build())); - } - - @Test - public void beforeWrite_initialVersionDueToExplicitZero_expressionAndTransformedItemIsCorrect() { - FakeItem fakeItem = createUniqueFakeItem(); - - Map inputMap = - new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); - inputMap.put("version", AttributeValue.builder().n("0").build()); - - Map fakeItemWithInitialVersion = - new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); - fakeItemWithInitialVersion.put("version", AttributeValue.builder().n("1").build()); - - WriteModification result = - versionedRecordExtension.beforeWrite(DefaultDynamoDbExtensionContext - .builder() - .items(inputMap) - .tableMetadata(FakeItem.getTableMetadata()) - .operationContext(PRIMARY_CONTEXT).build()); - - assertThat(result.transformedItem(), is(fakeItemWithInitialVersion)); - assertThat(result.additionalConditionalExpression(), - is(Expression.builder() - .expression("attribute_not_exists(#AMZN_MAPPED_version)") - .expressionNames(singletonMap("#AMZN_MAPPED_version", "version")) - .build())); - } - @Test public void beforeWrite_existingVersion_expressionIsCorrect() { FakeItem fakeItem = createUniqueFakeItem(); @@ -235,24 +178,134 @@ public void beforeWrite_returnsNoOpModification_ifVersionAttributeNotDefined() { @Test(expected = IllegalArgumentException.class) public void beforeWrite_throwsIllegalArgumentException_ifVersionAttributeIsWrongType() { FakeItem fakeItem = createUniqueFakeItem(); - Map fakeItemWIthBadVersion = + Map fakeItemWithBadVersion = new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); - fakeItemWIthBadVersion.put("version", AttributeValue.builder().s("14").build()); + fakeItemWithBadVersion.put("version", AttributeValue.builder().s("14").build()); versionedRecordExtension.beforeWrite( DefaultDynamoDbExtensionContext.builder() - .items(fakeItemWIthBadVersion) + .items(fakeItemWithBadVersion) .operationContext(PRIMARY_CONTEXT) .tableMetadata(FakeItem.getTableMetadata()) .build()); } + @Test + public void beforeWrite_versionEqualsStartAt_treatedAsInitialVersion() { + VersionedRecordExtension recordExtension = VersionedRecordExtension.builder() + .startAt(5) + .build(); + + FakeItem fakeItem = createUniqueFakeItem(); + fakeItem.setVersion(5); + + Map inputMap = + new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); + + WriteModification result = + recordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(FakeItem.getTableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + assertThat(result.additionalConditionalExpression().expression(), + is("attribute_not_exists(#AMZN_MAPPED_version)")); + } + + @ParameterizedTest + @MethodSource("customStartAtAndIncrementValues") + public void customStartingValueAndIncrement_worksAsExpected(Integer startAt, Integer incrementBy, String expectedVersion) { + VersionedRecordExtension.Builder recordExtensionBuilder = VersionedRecordExtension.builder(); + if (startAt != null) { + recordExtensionBuilder.startAt(startAt); + } + if (incrementBy != null) { + recordExtensionBuilder.incrementBy(incrementBy); + } + + VersionedRecordExtension recordExtension = recordExtensionBuilder.build(); + + FakeItem fakeItem = createUniqueFakeItem(); + + Map inputMap = + new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); + + Map expectedInitialVersion = + new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); + + expectedInitialVersion.put("version", AttributeValue.builder().n(expectedVersion).build()); + + WriteModification result = + recordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(FakeItem.getTableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + assertThat(result.transformedItem(), is(expectedInitialVersion)); + assertThat(result.additionalConditionalExpression(), + is(Expression.builder() + .expression("attribute_not_exists(#AMZN_MAPPED_version)") + .expressionNames(singletonMap("#AMZN_MAPPED_version", "version")) + .build())); + } + + public static Stream customStartAtAndIncrementValues() { + return Stream.of( + Arguments.of(0,1,"1"), + Arguments.of(3,2,"5"), + Arguments.of(3,null,"4"), + Arguments.of(null,3,"3")); + } + + @ParameterizedTest + @MethodSource("customFailingStartAtAndIncrementValues") + public void customStartingValueAndIncrement_shouldThrow(Integer startAt, Integer incrementBy) { + assertThrows(IllegalArgumentException.class, () -> VersionedRecordExtension.builder() + .startAt(startAt) + .incrementBy(incrementBy) + .build()); + } + + public static Stream customFailingStartAtAndIncrementValues() { + return Stream.of( + Arguments.of(-2, 1), + Arguments.of(3, 0)); + } + + @Test + public void beforeWrite_versionNotEqualsAnnotationStartAt_notTreatedAsInitialVersion() { + FakeVersionedThroughAnnotationItem item = new FakeVersionedThroughAnnotationItem(); + item.setId(UUID.randomUUID().toString()); + item.setVersion(10); + + TableSchema schema = + TableSchema.fromBean(FakeVersionedThroughAnnotationItem.class); + + Map inputMap = new HashMap<>(schema.itemToMap(item, true)); + + VersionedRecordExtension recordExtension = VersionedRecordExtension.builder().build(); + + WriteModification result = + recordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(schema.tableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + // Should be treated as existing version + assertThat(result.additionalConditionalExpression().expression(), + is("#AMZN_MAPPED_version = :old_version_value")); + } + + @DynamoDbBean - public static class CustomVersionedItem { + public static class FakeVersionedThroughAnnotationItem { private String id; private Integer version; - public CustomVersionedItem() { + public FakeVersionedThroughAnnotationItem() { } @DynamoDbPartitionKey @@ -269,10 +322,42 @@ public CustomVersionedItem() { public void customStartingValueAndIncrementWithAnnotation_worksAsExpected() { VersionedRecordExtension recordExtension = VersionedRecordExtension.builder().build(); - CustomVersionedItem item = new CustomVersionedItem(); + FakeVersionedThroughAnnotationItem item = new FakeVersionedThroughAnnotationItem(); + item.setId(UUID.randomUUID().toString()); + + TableSchema schema = TableSchema.fromBean(FakeVersionedThroughAnnotationItem.class); + + Map inputMap = new HashMap<>(schema.itemToMap(item, true)); + + Map expectedInitialVersion = new HashMap<>(schema.itemToMap(item, true)); + expectedInitialVersion.put("version", AttributeValue.builder().n("5").build()); + + WriteModification result = + recordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(schema.tableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + assertThat(result.transformedItem(), is(expectedInitialVersion)); + assertThat(result.additionalConditionalExpression(), + is(Expression.builder() + .expression("attribute_not_exists(#AMZN_MAPPED_version)") + .expressionNames(singletonMap("#AMZN_MAPPED_version", "version")) + .build())); + } + + @Test + public void customAnnotationValuesAndBuilderValues_annotationShouldTakePrecedence() { + VersionedRecordExtension recordExtension = VersionedRecordExtension.builder() + .startAt(5) + .incrementBy(2) + .build(); + + FakeVersionedThroughAnnotationItem item = new FakeVersionedThroughAnnotationItem(); item.setId(UUID.randomUUID().toString()); - TableSchema schema = TableSchema.fromBean(CustomVersionedItem.class); + TableSchema schema = TableSchema.fromBean(FakeVersionedThroughAnnotationItem.class); Map inputMap = new HashMap<>(schema.itemToMap(item, true)); @@ -293,4 +378,121 @@ public void customStartingValueAndIncrementWithAnnotation_worksAsExpected() { .expressionNames(singletonMap("#AMZN_MAPPED_version", "version")) .build())); } + + @DynamoDbBean + public static class FakeVersionedThroughAnnotationItemWithExplicitDefaultValues { + private String id; + private Integer version; + + public FakeVersionedThroughAnnotationItemWithExplicitDefaultValues() { + } + + @DynamoDbPartitionKey + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + @DynamoDbVersionAttribute(startAt = 0, incrementBy = 1) + public Integer getVersion() { return version; } + public void setVersion(Integer version) { this.version = version; } + } + + @Test + public void customAnnotationDefaultValuesAndBuilderValues_annotationShouldTakePrecedence() { + VersionedRecordExtension recordExtension = VersionedRecordExtension.builder() + .startAt(5) + .incrementBy(2) + .build(); + + FakeVersionedThroughAnnotationItemWithExplicitDefaultValues item = new FakeVersionedThroughAnnotationItemWithExplicitDefaultValues(); + item.setId(UUID.randomUUID().toString()); + + TableSchema schema = TableSchema.fromBean(FakeVersionedThroughAnnotationItemWithExplicitDefaultValues.class); + + Map inputMap = new HashMap<>(schema.itemToMap(item, true)); + + Map expectedInitialVersion = new HashMap<>(schema.itemToMap(item, true)); + expectedInitialVersion.put("version", AttributeValue.builder().n("1").build()); + + WriteModification result = + recordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(schema.tableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + assertThat(result.transformedItem(), is(expectedInitialVersion)); + assertThat(result.additionalConditionalExpression(), + is(Expression.builder() + .expression("attribute_not_exists(#AMZN_MAPPED_version)") + .expressionNames(singletonMap("#AMZN_MAPPED_version", "version")) + .build())); + } + + @DynamoDbBean + public static class FakeVersionedThroughAnnotationItemWithInvalidValues { + private String id; + private Integer version; + + public FakeVersionedThroughAnnotationItemWithInvalidValues() { + } + + @DynamoDbPartitionKey + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + @DynamoDbVersionAttribute(startAt = -1, incrementBy = -1) + public Integer getVersion() { return version; } + public void setVersion(Integer version) { this.version = version; } + } + + @Test + public void invalidAnnotationValues_shouldThrowException() { + FakeVersionedThroughAnnotationItemWithInvalidValues item = new FakeVersionedThroughAnnotationItemWithInvalidValues(); + item.setId(UUID.randomUUID().toString()); + + assertThrows(IllegalArgumentException.class, () -> TableSchema.fromBean(FakeVersionedThroughAnnotationItemWithInvalidValues.class)); + } + + @ParameterizedTest + @MethodSource("customIncrementForExistingVersionValues") + public void customIncrementForExistingVersion_worksAsExpected(Integer startAt, Integer incrementBy, + Integer existingVersion, String expectedNextVersion) { + VersionedRecordExtension.Builder recordExtensionBuilder = VersionedRecordExtension.builder(); + if (startAt != null) { + recordExtensionBuilder.startAt(startAt); + } + if (incrementBy != null) { + recordExtensionBuilder.incrementBy(incrementBy); + } + VersionedRecordExtension recordExtension = recordExtensionBuilder.build(); + + FakeItem fakeItem = createUniqueFakeItem(); + fakeItem.setVersion(existingVersion); + + Map inputMap = + new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); + + Map expectedVersionedItem = + new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); + expectedVersionedItem.put("version", AttributeValue.builder().n(expectedNextVersion).build()); + + WriteModification result = + recordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(FakeItem.getTableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + assertThat(result.transformedItem(), is(expectedVersionedItem)); + assertThat(result.additionalConditionalExpression().expression(), + is("#AMZN_MAPPED_version = :old_version_value")); + } + + public static Stream customIncrementForExistingVersionValues() { + return Stream.of( + Arguments.of(0, 1, 5, "6"), + Arguments.of(3, 2, 7, "9"), + Arguments.of(3, null, 10, "11"), + Arguments.of(null, 3, 4, "7")); + } } From f0255ff82d17cc139e4148dc32920944868323d0 Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Wed, 9 Apr 2025 15:23:14 -0700 Subject: [PATCH 3/8] Added changelog with attribution --- .../feature-DynamoDBEnhancedClient-2a501d8.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/next-release/feature-DynamoDBEnhancedClient-2a501d8.json diff --git a/.changes/next-release/feature-DynamoDBEnhancedClient-2a501d8.json b/.changes/next-release/feature-DynamoDBEnhancedClient-2a501d8.json new file mode 100644 index 000000000000..cfb75bbcf67e --- /dev/null +++ b/.changes/next-release/feature-DynamoDBEnhancedClient-2a501d8.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "DynamoDB Enhanced Client", + "contributor": "akiesler", + "description": "DynamoDB Enhanced Client: Support for Version Starting at 0 with Configurable Increment" +} From db487aaa04fd0f44c41b2a78965f824fc1bdd547 Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Thu, 10 Apr 2025 10:06:45 -0700 Subject: [PATCH 4/8] Fix checkstyle --- .../dynamodb/extensions/VersionedRecordExtension.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java index 08cef7a07a38..abb79ad19b44 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java @@ -181,7 +181,8 @@ private boolean isInitialVersion(AttributeValue existingVersionValue, DynamoDbEx Integer.class); return isNullAttributeValue(existingVersionValue) - || (versionStartAtFromAnnotation.isPresent() && getExistingVersion(existingVersionValue) == versionStartAtFromAnnotation.get()) + || (versionStartAtFromAnnotation.isPresent() + && getExistingVersion(existingVersionValue) == versionStartAtFromAnnotation.get()) || getExistingVersion(existingVersionValue) == this.startAt; } @@ -238,8 +239,8 @@ private int getExistingVersion(AttributeValue existingVersionValue) { private AttributeValue incrementVersion(int version, DynamoDbExtensionContext.BeforeWrite context) { Optional versionIncrementByFromAnnotation = context.tableMetadata() - .customMetadataObject(VersionAttribute.INCREMENT_BY_METADATA_KEY, - Integer.class); + .customMetadataObject(VersionAttribute.INCREMENT_BY_METADATA_KEY, + Integer.class); if (versionIncrementByFromAnnotation.isPresent()) { return AttributeValue.fromN(Integer.toString(version + versionIncrementByFromAnnotation.get())); } From d87025f67977e1b9e8a22f7f976d33d92c84a866 Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Thu, 10 Apr 2025 15:54:23 -0700 Subject: [PATCH 5/8] Adding japicmp excludes block --- services-custom/dynamodb-enhanced/pom.xml | 14 ++++++++++++++ .../src/main/resources/log4j2.xml | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 test/http-client-tests/src/main/resources/log4j2.xml diff --git a/services-custom/dynamodb-enhanced/pom.xml b/services-custom/dynamodb-enhanced/pom.xml index b3a125725df1..17f838d32603 100644 --- a/services-custom/dynamodb-enhanced/pom.xml +++ b/services-custom/dynamodb-enhanced/pom.xml @@ -76,6 +76,20 @@ + + com.github.siom79.japicmp + japicmp-maven-plugin + ${japicmp-maven-plugin.version} + + + + + software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute#incrementBy() + software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute#startAt() + + + + diff --git a/test/http-client-tests/src/main/resources/log4j2.xml b/test/http-client-tests/src/main/resources/log4j2.xml new file mode 100644 index 000000000000..d14a91c10db4 --- /dev/null +++ b/test/http-client-tests/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file From 30361435fca763300adef5c7b316c3db5c35b85a Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Fri, 11 Apr 2025 11:39:14 -0700 Subject: [PATCH 6/8] Adding additional test coverage to account for all isInitialVersion codepaths --- .../extensions/VersionedRecordExtension.java | 3 --- .../VersionedRecordExtensionTest.java | 25 ++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java index abb79ad19b44..60e9aac2d0e3 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java @@ -147,7 +147,6 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex Pair updates = getRecordUpdates(versionAttributeKey.get(), context); - // Unpack values from Pair AttributeValue newVersionValue = updates.left(); Expression condition = updates.right(); @@ -168,7 +167,6 @@ private Pair getRecordUpdates(String versionAttribut AttributeValue existingVersionValue = itemToTransform.getOrDefault(versionAttributeKey, DEFAULT_VALUE); if (isInitialVersion(existingVersionValue, context)) { - // First version of the record ensure it does not exist return createInitialRecord(versionAttributeKey, context); } // Existing record, increment version @@ -230,7 +228,6 @@ private Pair updateExistingRecord(String versionAttr private int getExistingVersion(AttributeValue existingVersionValue) { if (existingVersionValue.n() == null) { - // In this case a non-null version attribute is present, but it's not an N throw new IllegalArgumentException("Version attribute appears to be the wrong type. N is required."); } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java index ed4ecafc2544..20e9671b86fe 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java @@ -294,11 +294,34 @@ public void beforeWrite_versionNotEqualsAnnotationStartAt_notTreatedAsInitialVer .tableMetadata(schema.tableMetadata()) .operationContext(PRIMARY_CONTEXT).build()); - // Should be treated as existing version assertThat(result.additionalConditionalExpression().expression(), is("#AMZN_MAPPED_version = :old_version_value")); } + @Test + public void beforeWrite_versionEqualsAnnotationStartAt_isTreatedAsInitialVersion() { + FakeVersionedThroughAnnotationItem item = new FakeVersionedThroughAnnotationItem(); + item.setId(UUID.randomUUID().toString()); + item.setVersion(3); + + TableSchema schema = + TableSchema.fromBean(FakeVersionedThroughAnnotationItem.class); + + Map inputMap = new HashMap<>(schema.itemToMap(item, true)); + + VersionedRecordExtension recordExtension = VersionedRecordExtension.builder().build(); + + WriteModification result = + recordExtension.beforeWrite(DefaultDynamoDbExtensionContext + .builder() + .items(inputMap) + .tableMetadata(schema.tableMetadata()) + .operationContext(PRIMARY_CONTEXT).build()); + + assertThat(result.additionalConditionalExpression().expression(), + is("attribute_not_exists(#AMZN_MAPPED_version)")); + } + @DynamoDbBean public static class FakeVersionedThroughAnnotationItem { From e8a60751683eb4c6fc798da71683db5a7f55604b Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Fri, 11 Apr 2025 11:43:58 -0700 Subject: [PATCH 7/8] Removing logger config file --- .../src/main/resources/log4j2.xml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 test/http-client-tests/src/main/resources/log4j2.xml diff --git a/test/http-client-tests/src/main/resources/log4j2.xml b/test/http-client-tests/src/main/resources/log4j2.xml deleted file mode 100644 index d14a91c10db4..000000000000 --- a/test/http-client-tests/src/main/resources/log4j2.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file From 3d505a6f14aac912bf11b24ce8f06cff4ff38680 Mon Sep 17 00:00:00 2001 From: Ran Vaknin Date: Mon, 21 Apr 2025 16:41:56 -0700 Subject: [PATCH 8/8] porting version from Integer to Long, removing refactoring --- pom.xml | 2 + services-custom/dynamodb-enhanced/pom.xml | 14 -- .../awssdk/enhanced/dynamodb/Expression.java | 2 +- .../extensions/VersionedRecordExtension.java | 196 +++++++----------- .../annotations/DynamoDbVersionAttribute.java | 4 +- .../VersionedRecordExtensionTest.java | 62 +++--- 6 files changed, 109 insertions(+), 171 deletions(-) diff --git a/pom.xml b/pom.xml index 92cc17a8ab3f..70cba84d3421 100644 --- a/pom.xml +++ b/pom.xml @@ -679,6 +679,8 @@ polly + software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute#incrementBy() + software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute#startAt() *.internal.* software.amazon.awssdk.thirdparty.* software.amazon.awssdk.regions.* diff --git a/services-custom/dynamodb-enhanced/pom.xml b/services-custom/dynamodb-enhanced/pom.xml index 17f838d32603..b3a125725df1 100644 --- a/services-custom/dynamodb-enhanced/pom.xml +++ b/services-custom/dynamodb-enhanced/pom.xml @@ -76,20 +76,6 @@ - - com.github.siom79.japicmp - japicmp-maven-plugin - ${japicmp-maven-plugin.version} - - - - - software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute#incrementBy() - software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute#startAt() - - - - diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java index e14cc33401fe..5a4d9e454e47 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java @@ -321,7 +321,7 @@ public String toString() { } /** - * A builder for {@link Expression}v + * A builder for {@link Expression} */ @NotThreadSafe public static final class Builder { diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java index 60e9aac2d0e3..89bb11aa31df 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java @@ -34,7 +34,7 @@ import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag; import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.utils.Pair; +import software.amazon.awssdk.utils.Validate; /** * This extension implements optimistic locking on record writes by means of a 'record version number' that is used @@ -61,20 +61,19 @@ public final class VersionedRecordExtension implements DynamoDbEnhancedClientExt private static final Function VERSIONED_RECORD_EXPRESSION_VALUE_KEY_MAPPER = key -> ":old_" + key + "_value"; private static final String CUSTOM_METADATA_KEY = "VersionedRecordExtension:VersionAttribute"; private static final VersionAttribute VERSION_ATTRIBUTE = new VersionAttribute(); - private static final AttributeValue DEFAULT_VALUE = AttributeValue.fromNul(Boolean.TRUE); - - private final int startAt; - private final int incrementBy; - - /** - * Creates a new {@link VersionedRecordExtension} using the supplied starting and incrementing value. - * - * @param startAt the value used to compare if a record is the initial version of a record. - * @param incrementBy the amount to increment the version by with each subsequent update. - */ - private VersionedRecordExtension(int startAt, int incrementBy) { - this.startAt = startAt; - this.incrementBy = incrementBy; + + private final long startAt; + private final long incrementBy; + + private VersionedRecordExtension(Long startAt, Long incrementBy) { + Validate.isNotNegativeOrNull(startAt, "startAt"); + + if (incrementBy != null && incrementBy < 1) { + throw new IllegalArgumentException("IncrementBy must be greater than 0."); + } + + this.startAt = startAt != null ? startAt : 0L; + this.incrementBy = incrementBy != null ? incrementBy : 1L; } public static Builder builder() { @@ -89,7 +88,7 @@ public static StaticAttributeTag versionAttribute() { return VERSION_ATTRIBUTE; } - public static StaticAttributeTag versionAttribute(Integer startAt, Integer incrementBy) { + public static StaticAttributeTag versionAttribute(Long startAt, Long incrementBy) { return new VersionAttribute(startAt, incrementBy); } } @@ -98,15 +97,15 @@ private static final class VersionAttribute implements StaticAttributeTag { private static final String START_AT_METADATA_KEY = "VersionedRecordExtension:StartAt"; private static final String INCREMENT_BY_METADATA_KEY = "VersionedRecordExtension:IncrementBy"; - private final Integer startAt; - private final Integer incrementBy; + private final Long startAt; + private final Long incrementBy; private VersionAttribute() { this.startAt = null; this.incrementBy = null; } - private VersionAttribute(Integer startAt, Integer incrementBy) { + private VersionAttribute(Long startAt, Long incrementBy) { this.startAt = startAt; this.incrementBy = incrementBy; } @@ -120,16 +119,13 @@ public Consumer modifyMetadata(String attributeName "is supported.", attributeName, attributeValueType.name())); } - if (startAt != null && startAt < 0) { - throw new IllegalArgumentException("StartAt cannot be negative."); - } + Validate.isNotNegativeOrNull(startAt, "startAt"); if (incrementBy != null && incrementBy < 1) { throw new IllegalArgumentException("IncrementBy must be greater than 0."); } - return metadata -> metadata - .addCustomMetadataObject(CUSTOM_METADATA_KEY, attributeName) + return metadata -> metadata.addCustomMetadataObject(CUSTOM_METADATA_KEY, attributeName) .addCustomMetadataObject(START_AT_METADATA_KEY, startAt) .addCustomMetadataObject(INCREMENT_BY_METADATA_KEY, incrementBy) .markAttributeAsKey(attributeName, attributeValueType); @@ -145,109 +141,69 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex return WriteModification.builder().build(); } - Pair updates = getRecordUpdates(versionAttributeKey.get(), context); - - AttributeValue newVersionValue = updates.left(); - Expression condition = updates.right(); - Map itemToTransform = new HashMap<>(context.items()); - itemToTransform.put(versionAttributeKey.get(), newVersionValue); - - return WriteModification.builder() - .transformedItem(Collections.unmodifiableMap(itemToTransform)) - .additionalConditionalExpression(condition) - .build(); - } - - private Pair getRecordUpdates(String versionAttributeKey, - DynamoDbExtensionContext.BeforeWrite context) { - Map itemToTransform = context.items(); - - // Default to NUL if not present to reduce additional checks further along - AttributeValue existingVersionValue = itemToTransform.getOrDefault(versionAttributeKey, DEFAULT_VALUE); - if (isInitialVersion(existingVersionValue, context)) { - return createInitialRecord(versionAttributeKey, context); - } - // Existing record, increment version - return updateExistingRecord(versionAttributeKey, existingVersionValue, context); - } - - private boolean isInitialVersion(AttributeValue existingVersionValue, DynamoDbExtensionContext.BeforeWrite context) { - Optional versionStartAtFromAnnotation = context.tableMetadata() - .customMetadataObject(VersionAttribute.START_AT_METADATA_KEY, - Integer.class); - - return isNullAttributeValue(existingVersionValue) - || (versionStartAtFromAnnotation.isPresent() - && getExistingVersion(existingVersionValue) == versionStartAtFromAnnotation.get()) - || getExistingVersion(existingVersionValue) == this.startAt; - } - - private Pair createInitialRecord(String versionAttributeKey, - DynamoDbExtensionContext.BeforeWrite context) { - Optional versionStartAtFromAnnotation = context.tableMetadata() - .customMetadataObject(VersionAttribute.START_AT_METADATA_KEY, - Integer.class); - - AttributeValue newVersionValue = versionStartAtFromAnnotation.isPresent() ? - incrementVersion(versionStartAtFromAnnotation.get(), context) : - incrementVersion(this.startAt, context); - - - String attributeKeyRef = keyRef(versionAttributeKey); - - Expression condition = Expression.builder() - // Check that the version does not exist before setting the initial value. - .expression(String.format("attribute_not_exists(%s)", attributeKeyRef)) - .expressionNames(Collections.singletonMap(attributeKeyRef, versionAttributeKey)) - .build(); - - return Pair.of(newVersionValue, condition); - } - - private Pair updateExistingRecord(String versionAttributeKey, - AttributeValue existingVersionValue, - DynamoDbExtensionContext.BeforeWrite context) { - int existingVersion = getExistingVersion(existingVersionValue); - AttributeValue newVersionValue = incrementVersion(existingVersion, context); - - String attributeKeyRef = keyRef(versionAttributeKey); - String existingVersionValueKey = VERSIONED_RECORD_EXPRESSION_VALUE_KEY_MAPPER.apply(versionAttributeKey); + String attributeKeyRef = keyRef(versionAttributeKey.get()); + AttributeValue newVersionValue; + Expression condition; + Optional existingVersionValue = + Optional.ofNullable(itemToTransform.get(versionAttributeKey.get())); + + Optional versionStartAtFromAnnotation = context.tableMetadata() + .customMetadataObject(VersionAttribute.START_AT_METADATA_KEY, + Long.class); + + Optional versionIncrementByFromAnnotation = context.tableMetadata() + .customMetadataObject(VersionAttribute.INCREMENT_BY_METADATA_KEY, + Long.class); + + if (!existingVersionValue.isPresent() || isNullAttributeValue(existingVersionValue.get()) || + (existingVersionValue.get().n() != null && + ((versionStartAtFromAnnotation.isPresent() && + Long.parseLong(existingVersionValue.get().n()) == versionStartAtFromAnnotation.get()) || + Long.parseLong(existingVersionValue.get().n()) == this.startAt))) { + + long startValue = versionStartAtFromAnnotation.orElse(this.startAt); + long increment = versionIncrementByFromAnnotation.orElse(this.incrementBy); + + newVersionValue = AttributeValue.builder().n(Long.toString(startValue + increment)).build(); + condition = Expression.builder() + .expression(String.format("attribute_not_exists(%s)", attributeKeyRef)) + .expressionNames(Collections.singletonMap(attributeKeyRef, versionAttributeKey.get())) + .build(); + } else { + // Existing record, increment version + if (existingVersionValue.get().n() == null) { + // In this case a non-null version attribute is present, but it's not an N + throw new IllegalArgumentException("Version attribute appears to be the wrong type. N is required."); + } - Expression condition = Expression.builder() - // Check that the version matches the existing value before setting the updated value. - .expression(String.format("%s = %s", attributeKeyRef, existingVersionValueKey)) - .expressionNames(Collections.singletonMap(attributeKeyRef, versionAttributeKey)) - .expressionValues(Collections.singletonMap(existingVersionValueKey, - existingVersionValue)) - .build(); + long existingVersion = Long.parseLong(existingVersionValue.get().n()); + String existingVersionValueKey = VERSIONED_RECORD_EXPRESSION_VALUE_KEY_MAPPER.apply(versionAttributeKey.get()); - return Pair.of(newVersionValue, condition); - } + long increment = versionIncrementByFromAnnotation.orElse(this.incrementBy); + newVersionValue = AttributeValue.builder().n(Long.toString(existingVersion + increment)).build(); - private int getExistingVersion(AttributeValue existingVersionValue) { - if (existingVersionValue.n() == null) { - throw new IllegalArgumentException("Version attribute appears to be the wrong type. N is required."); + condition = Expression.builder() + .expression(String.format("%s = %s", attributeKeyRef, existingVersionValueKey)) + .expressionNames(Collections.singletonMap(attributeKeyRef, versionAttributeKey.get())) + .expressionValues(Collections.singletonMap(existingVersionValueKey, + existingVersionValue.get())) + .build(); } - return Integer.parseInt(existingVersionValue.n()); - } + itemToTransform.put(versionAttributeKey.get(), newVersionValue); - private AttributeValue incrementVersion(int version, DynamoDbExtensionContext.BeforeWrite context) { - Optional versionIncrementByFromAnnotation = context.tableMetadata() - .customMetadataObject(VersionAttribute.INCREMENT_BY_METADATA_KEY, - Integer.class); - if (versionIncrementByFromAnnotation.isPresent()) { - return AttributeValue.fromN(Integer.toString(version + versionIncrementByFromAnnotation.get())); - } - return AttributeValue.fromN(Integer.toString(version + this.incrementBy)); + return WriteModification.builder() + .transformedItem(Collections.unmodifiableMap(itemToTransform)) + .additionalConditionalExpression(condition) + .build(); } @NotThreadSafe public static final class Builder { - private int startAt = 0; - private int incrementBy = 1; + private Long startAt = 0L; + private Long incrementBy = 1L; private Builder() { } @@ -259,10 +215,7 @@ private Builder() { * @param startAt * @return the builder instance */ - public Builder startAt(int startAt) { - if (startAt < 0) { - throw new IllegalArgumentException("StartAt cannot be negative."); - } + public Builder startAt(Long startAt) { this.startAt = startAt; return this; } @@ -274,10 +227,7 @@ public Builder startAt(int startAt) { * @param incrementBy * @return the builder instance */ - public Builder incrementBy(int incrementBy) { - if (incrementBy < 1) { - throw new IllegalArgumentException("IncrementBy must be greater than 0."); - } + public Builder incrementBy(Long incrementBy) { this.incrementBy = incrementBy; return this; } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbVersionAttribute.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbVersionAttribute.java index c664340db03e..09ab6eb00159 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbVersionAttribute.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/annotations/DynamoDbVersionAttribute.java @@ -39,7 +39,7 @@ * * @return the starting value */ - int startAt() default 0; + long startAt() default 0; /** * The amount to increment the version by with each update. @@ -47,6 +47,6 @@ * * @return the increment value */ - int incrementBy() default 1; + long incrementBy() default 1; } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java index 20e9671b86fe..2dac494b943c 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtensionTest.java @@ -193,7 +193,7 @@ public void beforeWrite_throwsIllegalArgumentException_ifVersionAttributeIsWrong @Test public void beforeWrite_versionEqualsStartAt_treatedAsInitialVersion() { VersionedRecordExtension recordExtension = VersionedRecordExtension.builder() - .startAt(5) + .startAt(5L) .build(); FakeItem fakeItem = createUniqueFakeItem(); @@ -215,7 +215,7 @@ public void beforeWrite_versionEqualsStartAt_treatedAsInitialVersion() { @ParameterizedTest @MethodSource("customStartAtAndIncrementValues") - public void customStartingValueAndIncrement_worksAsExpected(Integer startAt, Integer incrementBy, String expectedVersion) { + public void customStartingValueAndIncrement_worksAsExpected(Long startAt, Long incrementBy, String expectedVersion) { VersionedRecordExtension.Builder recordExtensionBuilder = VersionedRecordExtension.builder(); if (startAt != null) { recordExtensionBuilder.startAt(startAt); @@ -253,15 +253,15 @@ public void customStartingValueAndIncrement_worksAsExpected(Integer startAt, Int public static Stream customStartAtAndIncrementValues() { return Stream.of( - Arguments.of(0,1,"1"), - Arguments.of(3,2,"5"), - Arguments.of(3,null,"4"), - Arguments.of(null,3,"3")); + Arguments.of(0L,1L,"1"), + Arguments.of(3L,2L,"5"), + Arguments.of(3L,null,"4"), + Arguments.of(null,3L,"3")); } @ParameterizedTest @MethodSource("customFailingStartAtAndIncrementValues") - public void customStartingValueAndIncrement_shouldThrow(Integer startAt, Integer incrementBy) { + public void customStartingValueAndIncrement_shouldThrow(Long startAt, Long incrementBy) { assertThrows(IllegalArgumentException.class, () -> VersionedRecordExtension.builder() .startAt(startAt) .incrementBy(incrementBy) @@ -270,15 +270,15 @@ public void customStartingValueAndIncrement_shouldThrow(Integer startAt, Integer public static Stream customFailingStartAtAndIncrementValues() { return Stream.of( - Arguments.of(-2, 1), - Arguments.of(3, 0)); + Arguments.of(-2L, 1L), + Arguments.of(3L, 0L)); } @Test public void beforeWrite_versionNotEqualsAnnotationStartAt_notTreatedAsInitialVersion() { FakeVersionedThroughAnnotationItem item = new FakeVersionedThroughAnnotationItem(); item.setId(UUID.randomUUID().toString()); - item.setVersion(10); + item.setVersion(10L); TableSchema schema = TableSchema.fromBean(FakeVersionedThroughAnnotationItem.class); @@ -302,7 +302,7 @@ public void beforeWrite_versionNotEqualsAnnotationStartAt_notTreatedAsInitialVer public void beforeWrite_versionEqualsAnnotationStartAt_isTreatedAsInitialVersion() { FakeVersionedThroughAnnotationItem item = new FakeVersionedThroughAnnotationItem(); item.setId(UUID.randomUUID().toString()); - item.setVersion(3); + item.setVersion(3L); TableSchema schema = TableSchema.fromBean(FakeVersionedThroughAnnotationItem.class); @@ -326,7 +326,7 @@ public void beforeWrite_versionEqualsAnnotationStartAt_isTreatedAsInitialVersion @DynamoDbBean public static class FakeVersionedThroughAnnotationItem { private String id; - private Integer version; + private Long version; public FakeVersionedThroughAnnotationItem() { } @@ -336,8 +336,8 @@ public FakeVersionedThroughAnnotationItem() { public void setId(String id) { this.id = id; } @DynamoDbVersionAttribute(startAt = 3, incrementBy = 2) - public Integer getVersion() { return version; } - public void setVersion(Integer version) { this.version = version; } + public Long getVersion() { return version; } + public void setVersion(Long version) { this.version = version; } } @@ -373,8 +373,8 @@ public void customStartingValueAndIncrementWithAnnotation_worksAsExpected() { @Test public void customAnnotationValuesAndBuilderValues_annotationShouldTakePrecedence() { VersionedRecordExtension recordExtension = VersionedRecordExtension.builder() - .startAt(5) - .incrementBy(2) + .startAt(5L) + .incrementBy(2L) .build(); FakeVersionedThroughAnnotationItem item = new FakeVersionedThroughAnnotationItem(); @@ -405,7 +405,7 @@ public void customAnnotationValuesAndBuilderValues_annotationShouldTakePrecedenc @DynamoDbBean public static class FakeVersionedThroughAnnotationItemWithExplicitDefaultValues { private String id; - private Integer version; + private Long version; public FakeVersionedThroughAnnotationItemWithExplicitDefaultValues() { } @@ -415,15 +415,15 @@ public FakeVersionedThroughAnnotationItemWithExplicitDefaultValues() { public void setId(String id) { this.id = id; } @DynamoDbVersionAttribute(startAt = 0, incrementBy = 1) - public Integer getVersion() { return version; } - public void setVersion(Integer version) { this.version = version; } + public Long getVersion() { return version; } + public void setVersion(Long version) { this.version = version; } } @Test public void customAnnotationDefaultValuesAndBuilderValues_annotationShouldTakePrecedence() { VersionedRecordExtension recordExtension = VersionedRecordExtension.builder() - .startAt(5) - .incrementBy(2) + .startAt(5L) + .incrementBy(2L) .build(); FakeVersionedThroughAnnotationItemWithExplicitDefaultValues item = new FakeVersionedThroughAnnotationItemWithExplicitDefaultValues(); @@ -454,7 +454,7 @@ public void customAnnotationDefaultValuesAndBuilderValues_annotationShouldTakePr @DynamoDbBean public static class FakeVersionedThroughAnnotationItemWithInvalidValues { private String id; - private Integer version; + private Long version; public FakeVersionedThroughAnnotationItemWithInvalidValues() { } @@ -464,8 +464,8 @@ public FakeVersionedThroughAnnotationItemWithInvalidValues() { public void setId(String id) { this.id = id; } @DynamoDbVersionAttribute(startAt = -1, incrementBy = -1) - public Integer getVersion() { return version; } - public void setVersion(Integer version) { this.version = version; } + public Long getVersion() { return version; } + public void setVersion(Long version) { this.version = version; } } @Test @@ -478,8 +478,8 @@ public void invalidAnnotationValues_shouldThrowException() { @ParameterizedTest @MethodSource("customIncrementForExistingVersionValues") - public void customIncrementForExistingVersion_worksAsExpected(Integer startAt, Integer incrementBy, - Integer existingVersion, String expectedNextVersion) { + public void customIncrementForExistingVersion_worksAsExpected(Long startAt, Long incrementBy, + Long existingVersion, String expectedNextVersion) { VersionedRecordExtension.Builder recordExtensionBuilder = VersionedRecordExtension.builder(); if (startAt != null) { recordExtensionBuilder.startAt(startAt); @@ -490,7 +490,7 @@ public void customIncrementForExistingVersion_worksAsExpected(Integer startAt, I VersionedRecordExtension recordExtension = recordExtensionBuilder.build(); FakeItem fakeItem = createUniqueFakeItem(); - fakeItem.setVersion(existingVersion); + fakeItem.setVersion(existingVersion.intValue()); Map inputMap = new HashMap<>(FakeItem.getTableSchema().itemToMap(fakeItem, true)); @@ -513,9 +513,9 @@ public void customIncrementForExistingVersion_worksAsExpected(Integer startAt, I public static Stream customIncrementForExistingVersionValues() { return Stream.of( - Arguments.of(0, 1, 5, "6"), - Arguments.of(3, 2, 7, "9"), - Arguments.of(3, null, 10, "11"), - Arguments.of(null, 3, 4, "7")); + Arguments.of(0L, 1L, 5L, "6"), + Arguments.of(3L, 2L, 7L, "9"), + Arguments.of(3L, null, 10L, "11"), + Arguments.of(null, 3L, 4L, "7")); } }