-
Notifications
You must be signed in to change notification settings - Fork 898
DDB Enhanced: Allow custom versioning #6019
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
DDB Enhanced: Allow custom versioning #6019
Conversation
abcd87a
to
4e82d7e
Compare
...tom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java
Outdated
Show resolved
Hide resolved
.../main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java
Outdated
Show resolved
Hide resolved
.../main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java
Outdated
Show resolved
Hide resolved
<configuration> | ||
<parameter> | ||
<excludes> | ||
<!-- japicmp flagging these incorrectly as a breaking change --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this is odd, can you paste the error logs? If we have to add exclusion, we should add it to the top level pom https://github.com/aws/aws-sdk-java-v2/blob/master/pom.xml#L681
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the error only was shown on CI, but I was able to raise with running mvn japicmp:cmp -pl :dynamodb-enhanced
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.557 s
[INFO] Finished at: 2025-04-21T11:29:14-07:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal com.github.siom79.japicmp:japicmp-maven-plugin:0.15.6:cmp (default-cli) on project dynamodb-enhanced: There is at least one incompatibility: software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute.incrementBy():METHOD_ABSTRACT_ADDED_TO_CLASS,software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute.startAt():METHOD_ABSTRACT_ADDED_TO_CLASS -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
.../main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java
Outdated
Show resolved
Hide resolved
.../main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java
Outdated
Show resolved
Hide resolved
.../main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java
Outdated
Show resolved
Hide resolved
.../main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.java
Outdated
Show resolved
Hide resolved
Thank you for picking up this change! It will greatly simplify our version handling. Please let me know if I can do anything to help. |
9e39a83
to
3d505a6
Compare
|
"type": "feature", | ||
"category": "DynamoDB Enhanced Client", | ||
"contributor": "akiesler", | ||
"description": "DynamoDB Enhanced Client: Support for Version Starting at 0 with Configurable Increment" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Don't need the DynamoDB Enhanced Client:
in the description since it's already a category
@@ -679,6 +679,8 @@ | |||
<includeModule>polly</includeModule> | |||
</includeModules> | |||
<excludes> | |||
<exclude>software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbVersionAttribute#incrementBy()</exclude> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a TODO
and create a tracking task to remove this after release
return "Expression{" + | ||
"expression='" + expression + '\'' + | ||
", expressionValues=" + expressionValues + | ||
", expressionNames=" + expressionNames + | ||
'}'; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use ToString.builder()
so it's consistent with the other toString methods in DDB enhanced:
Lines 736 to 740 in f6adeaa
return ToString.builder("EnhancedAttributeValue") | |
.add("type", type) | |
.add("value", value) | |
.build(); | |
} |
Validate.isNotNegativeOrNull(startAt, "startAt"); | ||
|
||
if (incrementBy != null && incrementBy < 1) { | ||
throw new IllegalArgumentException("IncrementBy must be greater than 0."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: technically should match the param name, so should be lowercase "i"
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))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- This is a very complex expression. Let's move this to a separate function so it's easier to reason about and we can document it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. Why do we need to check existingVersionValue.get().n() != null && ((versionStartAtFromAnnotation.isPresent() && Long.parseLong(existingVersionValue.get().n()) == versionStartAtFromAnnotation.get()) || Long.parseLong(existingVersionValue.get().n()) == this.startAt)))
?
Suggesting the following
if (existingValueNotAvailable(existingAttributeValue)) {
long startVersion = versionStartAtFromAnnotation.orElse(this.startAt);
long incrementVersion = versionIncrementByFromAnnotation.orElse(this.incrementBy);
...
} else {
....
}
newVersionValue = AttributeValue.builder().n(Integer.toString(existingVersion + 1)).build(); | ||
|
||
long increment = versionIncrementByFromAnnotation.orElse(this.incrementBy); | ||
newVersionValue = AttributeValue.builder().n(Long.toString(existingVersion + increment)).build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like the original version handled this, but how do we deal with integer overflow? Originally incrementBy
was always 1
so it's unlikely to happen, but with incrementBy
being configurable now, it's technically more likely now if the user sets a relatively large value.
Can we check how v1's mapper handles this (if at all)?
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))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. Why do we need to check existingVersionValue.get().n() != null && ((versionStartAtFromAnnotation.isPresent() && Long.parseLong(existingVersionValue.get().n()) == versionStartAtFromAnnotation.get()) || Long.parseLong(existingVersionValue.get().n()) == this.startAt)))
?
Suggesting the following
if (existingValueNotAvailable(existingAttributeValue)) {
long startVersion = versionStartAtFromAnnotation.orElse(this.startAt);
long incrementVersion = versionIncrementByFromAnnotation.orElse(this.incrementBy);
...
} else {
....
}
@@ -144,11 +202,38 @@ public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite contex | |||
|
|||
@NotThreadSafe | |||
public static final class Builder { | |||
private Long startAt = 0L; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason we are setting the default values in Builder? Builder class should not have any logic ideally and it also seems we are already setting the default values in the top level classes
.operationContext(PRIMARY_CONTEXT) | ||
.tableMetadata(FakeItem.getTableMetadata()) | ||
.build()); | ||
} | ||
|
||
@Test | ||
public void beforeWrite_versionEqualsStartAt_treatedAsInitialVersion() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add tests for different table schemas such as static table schema, immutable table scheme and bean table schema? https://github.com/aws/aws-sdk-java-v2/pull/6067/files#diff-9826429ca566bd1325f9ffa5d4fe1141cd26df01ec67262bab6f4acb12c1aa28
Motivation and Context
This PR builds on the work started in issue #3894 where the
VersionedRecordExtension
didn't support starting version numbers at 0, requiring clients to use Integer objects (with null initial values) rather than long primitives. As mentioned by @akiesler in the original issue, developers might expect versions to start at 0 and increment from there, rather than having a special case where the value must be initialized to null.This implementation allows the extension to be more flexible by allowing:
Modifications
VersionedRecordExtension
to support explicitstartAt
andincrementBy
values via builder methods (making it opt-in).DynamoDbVersionAttribute
annotation to supportstartAt
andincrementBy
parametersTesting
startAt
andincrementBy
values through both builder and annotationsstartAt
, zero/negativeincrementBy
)