Support update expressions in single request update
Motivation and Context
#5554
This enhancement addresses the need for more flexible and powerful update operations in the DynamoDB Enhanced Client. Previously, users could only update items using POJO attributes or extension-generated expressions, but had no way to provide explicit UpdateExpressions directly in their requests. This limitation prevented users from:
- Performing complex atomic operations (e.g., incrementing counters, appending to lists)
- Using advanced DynamoDB update features like conditional updates with custom expressions
- Combining POJO-based updates with custom UpdateExpressions in a single operation
This feature implements support for explicit UpdateExpressions in UpdateItemEnhancedRequest and TransactUpdateItemEnhancedRequest, giving users access to all DynamoDB update features without breaking existing code.
Modifications
Core Changes
-
Enhanced Request Models
- Added
updateExpression()method toUpdateItemEnhancedRequestandTransactUpdateItemEnhancedRequest - Maintained backward compatibility - existing code continues to work unchanged
- Added
-
Expression Resolution System
- Created
UpdateExpressionResolverclass to merge expressions from three sources with priority-based conflict resolution:- POJO attributes (lowest priority) - generates SET/REMOVE actions
- Extension expressions (medium priority) - from client extensions
- Request expressions (highest priority) - explicit user-provided expressions
- Implemented smart filtering to prevent DynamoDB conflicts by excluding attributes referenced in extension/request expressions from automatic REMOVE actions
- Created
-
Conflict Prevention
- Attributes referenced in extension or request expressions are automatically excluded from REMOVE actions generated by null POJO attributes
- This prevents "Two document paths overlap" errors from DynamoDB when the same attribute is updated by multiple sources
-
Integration Points
- Updated
UpdateItemOperation.generateUpdateExpressionIfExist()to use the new resolver - Enhanced
UpdateExpressionConverterwith attribute name extraction capabilities - Added comprehensive test coverage in
UpdateExpressionTest
- Updated
Testing
Test Coverage
-
Basic Functionality Tests
- UpdateExpression provided in request works correctly
- Backward compatibility with POJO-only updates
- Extension-only updates continue to work
-
Conflict Resolution Tests
- POJO vs Request expression conflicts (server-side detection)
- Extension vs Request expression conflicts (server-side detection)
- Attribute filtering prevents REMOVE action conflicts
-
Integration Tests
- Scan operations after UpdateExpression usage
- Delete operations after UpdateExpression usage
- Batch operations (batchGetItem, batchWriteItem)
- Transactional operations (transactGetItems, transactWriteItems)
- Static table schema compatibility
-
Edge Cases
- Empty expressions
- Null handling with ignoreNulls flag
- Multiple chained extensions
Test Results
- All existing tests pass (backward compatibility verified)
- New functionality tests pass across all scenarios
- Performance impact minimal due to efficient expression merging
Test Coverage Checklist
| Scenario | Done | Comments if Not Done |
|---|---|---|
| 1. Different TableSchema Creation Methods | ||
| a. TableSchema.fromBean(Customer.class) | [x] | |
| b. TableSchema.fromImmutableClass(Customer.class) for immutable classes | [x] | |
| c. TableSchema.documentSchemaBuilder().build() | [ ] | Not applicable for UpdateExpression feature |
| d. StaticTableSchema.builder(Customer.class) | [x] | |
| 2. Nesting of Different TableSchema Types | ||
| a. @DynamoDbBean with annotated auto-generated key | [x] | |
| b. @DynamoDbImmutable with annotated auto-generated key | [x] | |
| c. Auto-generated key combined with partition/sort key | [x] | |
| 3. CRUD Operations | ||
| a. scan() | [x] | |
| b. query() | [ ] | Not directly tested with UpdateExpression |
| c. updateItem() preserves existing value or generates when absent | [x] | |
| d. putItem() with no key set (auto-generation occurs) | [x] | |
| e. putItem() with key set manually | [x] | |
| f. getItem() retrieves auto-generated key | [x] | |
| g. deleteItem() | [x] | |
| h. batchGetItem() | [x] | |
| i. batchWriteItem() | [x] | |
| j. transactGetItems() | [x] | |
| k. transactWriteItems() | [x] | |
| 4. Data Types and Null Handling | ||
| a. Annotated attribute is null → key auto-generated | [x] | |
| b. Annotated attribute non-null → value preserved | [x] | |
| c. Validation rejects non-String annotated attribute | [x] | |
| 5. AsyncTable and SyncTable | ||
| a. DynamoDbAsyncTable Testing | [ ] | Focus was on sync operations |
| b. DynamoDbTable Testing | [x] | |
| 6. New/Modification in Extensions | ||
| a. Works with other extensions like VersionedRecordExtension | [x] | |
| b. Test with Default Values in Annotations | [ ] | Not applicable for UpdateExpression |
| c. Combination of Annotation and Builder passes extension | [ ] | Not applicable for UpdateExpression |
| 7. New/Modification in Converters | ||
| a. Tables with Scenario in Scenario No.1 (All table schemas are Must) | [ ] | Not applicable for UpdateExpression |
| b. Test with Default Values in Annotations | [ ] | Not applicable for UpdateExpression |
| c. Test All Scenarios from 1 to 5 | [ ] | Not applicable for UpdateExpression |
Types of changes
- [x] New feature (non-breaking change which adds functionality)
Checklist
- [x] I have read the CONTRIBUTING document
- [x] Local run of
mvn installsucceeds - [x] My code follows the code style of this project
- [x] My change requires a change to the Javadoc documentation
- [x] I have updated the Javadoc documentation accordingly
- [x] I have added tests to cover my changes
- [x] All new and existing tests passed
- [ ] I have added a changelog entry. Adding a new entry must be accomplished by running the
scripts/new-changescript and following the instructions. Commit the new file created by the script in.changes/next-releasewith your changes. - [ ] My change is to implement 1.11 parity feature and I have updated LaunchChangelog
License
- [x] I confirm that this pull request can be released under the Apache 2 license