1**Design:** New Feature, **Status:** [Released](../../../../../services-custom/dynamodb-enhanced/README.md)
2
3## Problem
4The DynamoDB Enhanced `updateItem()` table operation supports creating or updating an existing item by overwriting some or all attributes when supplying a POJO type instance with key attributes. In contrast, the low level DynamoDB [updateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) operation that is underlying the DynamoDB Enhanced op supports a wider range of functionality through its [UpdateExpression syntax](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html).
5
6This document proposes a mechanism for users to provide update expressions that will allow them to take advantage of the features of the low level expressions and implement functionality such as atomic counters.
7
8### Requested features
9Customer-requested features related to DynamoDB Enhanced UpdateItem:
10
111. Increment/decrement numerical attribute values in order to support atomic counters and similar use cases
122. Adding items to lists
133. Unsetting / nullifying specific attributes while not modifying the whole item.
144. Modifying return value behavior.
15
16Example Github issue: https://github.com/aws/aws-sdk-java-v2/issues/2292
17
18## Current functionality
19When calling updateItem, the enhanced client converts the supplied POJO into the low-level UpdateExpression syntax. It supports only a few specific actions:
20- REMOVE - to delete attributes
21- SET - setting whole attributes
22
23## Proposed Solution
24The enhanced client lets users provide custom update expressions in addition to the normal POJO records provided
25to the updateItem operation.
26### Enhanced Client UpdateExpression API
27The UpdateExpressions you can write in the enhanced client models the DynamoDB syntax at a higher abstraction level in order to support merging and analyzing expressions.  To create an UpdateExpression, create one or more UpdateAction (AddAction, SetAction, RemoveAction and DeleteAction) and add to the UpdateExpression builder.
28
29~~~
30SetAction setAction = SetAction.builder()
31                               .path("#attr1_ref")
32                               .value(":new_value")
33                               .putExpressionName("#attr1_ref", "attr1")
34                               .putExpressionValue(":new_value", newValue)
35                               .build();
36
37UpdateExpression updateExpression = UpdateExpression.builder()
38                                                    .addAction(setAction)
39                                                    .build();
40~~~
41*path = either the attribute name or another expression supported by the low level API.*<br>
42*value = the value of the path. Can also contain low level expressions.*<br>
43*expressionNames = (optional) maps name tokens to attribute names.*<br>
44*expressionValues = maps value tokens to attribute values.*<br>
45
46### Applying an UpdateExpression
47#### Schema level
48Schema level UpdateExpression enable use cases where the same action should be applied every time the database is called, such as atomic counters.
49The extension framework supports UpdateExpression as an output in a WriteModification.
50
51In an extension class implementing [DynamoDbEnhancedClientExtension](https://github.com/aws/aws-sdk-java-v2/blob/feature/master/DynamoDBenhanced-updateexpression/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClientExtension.java):
52~~~
53WriteModification.builder()
54                 .updateExpression(updateExpression)
55                 .build();
56~~~
57Adding the extension to the extension chain:
58~~~
59DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
60                                                              .dynamoDbClient(getDynamoDbClient())
61                                                              .extensions(MyExtension.create())
62                                                              .build();
63
64DynamoDbTable<Record> table = enhancedClient.table("some-table-name"), TableSchema.fromClass(SomeRecord.class));
65~~~
66
67
68#### Request level
69Request level UpdateExpression would allow you to modify the record at a single instant in time, such as adding an item to a list or deleting an attribute, by adding an update expression to the request itself.
70
71**NOTE:** This feature is not supported in the initial release.
72
73### Transforming enhanced UpdateExpression to low-level
74Performed by the enhanced client in the UpdateItemOperation when creating the low level request. It uses the `UpdateExpressionConverter` to transform the UpdateExpression into an Expression and then the string format DynamoDB expects.
75
76### Precedence and merging rules
77Several extensions can return an UpdateExpression, and they need to be merged in the extension chain so that a final extension UpdateExpression reaches the UpdateItemOperation. Within the operation, the extension expression must be merged with the one generated for the request POJO.
78
79[PR #2926](https://github.com/aws/aws-sdk-java-v2/pull/2926) outlines the challenges when resolving the final UpdateExpression in further detail. One of the biggest issues is reconciling the user experience in both using extensions and request and getting a result that makes sense with default configuration.
80
81#### Merging UpdateExpression in the extension chain
82
83Extension merge adds later expressions in the chain to any existing UpdateExpression (remember, the UpdateExpression is just a set of collections before parsing).
84
85Q: What happens if one extension later in the chain modifies the same attribute?<br>
86A: Their update actions will both be in the UpdateExpression, and it will fail at parse time.
87Should we have more support here?
88
89Q: Can an extension see previous UpdateExpression in the extension chain?<br>
90A: The extension context, input to the extension, does not currently contain UpdateExpression, which means the extension cannot view previous expressions (The extension CAN view any updates to the transact item however).
91
92#### Merging extension and request POJO UpdateExpressions
93
94Q: What takes precedence, extension expressions or reqest-level POJO extensions?<br>
95A: We currently just add them together, with one exception: A filter is in place that removes automatically generated delete of attributes for the POJO expression, that are explicitly modified by the extension UpdateExpression. This is because `ignoreNulls` is false by default and remove statements would be created to collide with extension attributes.
96
97Q: Should we consider doing something similar for POJO SET operations? <br>
98A: It depends on the view one takes of request level POJO updates compared to the extension ones. On one hand, request level often takes precedence. On the other, it’s important to protect the extension from being overwritten. This is not implemented.
99
100Q: What happens if the POJO sets an attribute that is also modified by an extension?
101A: An exception is thrown at parse time, preventing users from overwriting an extension attribute.
102
103## Appendix B: Alternative solutions
104
105### Design alternative: More fluent UpdateExpression API
106In this design alternative, the UpdateExpression API and update actions are more fluent, allowing users to write less code to achieve their goals:
107~~~
108UpdateExpression updateExpression1 = UpdateExpression.builder()
109                                                     .remove("attr1")
110                                                     .set("attr1", value1, updateBehavior)
111                                                     .build();
112~~~
113If directly adding actions, these had methods exposed
114~~~
115UpdateExpression updateExpression2 = UpdateExpression.builder()
116                                                     .removeAction(UpdateAction.remove("attr1"))
117                                                     .setAction(SetUpdateAction.addWithStartValue(attr2, delta, start))
118                                                     .addAction(UpdateAction.appendToList("attr1", 3, myListAttributeValue))
119                                                     .addAction(UpdateAction.appendToList("attr1", 3, myListAttributeValue))
120                                                     .build();
121~~~
122
123**Decision**
124
125This alternative was discarded due to the lack of flexibility in the highly modeled API risking difficulties in keeping up with changes to the low level syntax by DynamoDB.
126
127### Design alternative: Single UpdateAction class
128Using a type field to differentiate between different actions:
129~~~
130UpdateAction updateAction =
131          UpdateAction.builder()
132                      .type(UpdateActionType.REMOVE)
133                      .attributeName(attributeName)
134                      .expression(keyRef(attributeName))
135                      .build();
136~~~
137
138**Decision**
139
140Discarded in favor of the current design.