1+ /*
2+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License").
5+ * You may not use this file except in compliance with the License.
6+ * A copy of the License is located at
7+ *
8+ * http://aws.amazon.com/apache2.0
9+ *
10+ * or in the "license" file accompanying this file. This file is distributed
11+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+ * express or implied. See the License for the specific language governing
13+ * permissions and limitations under the License.
14+ */
15+
16+ package software .amazon .awssdk .enhanced .dynamodb .internal .update ;
17+
18+ import static software .amazon .awssdk .enhanced .dynamodb .internal .EnhancedClientUtils .isNullAttributeValue ;
19+ import static software .amazon .awssdk .enhanced .dynamodb .internal .update .UpdateExpressionUtils .removeActionsFor ;
20+ import static software .amazon .awssdk .enhanced .dynamodb .internal .update .UpdateExpressionUtils .setActionsFor ;
21+ import static software .amazon .awssdk .utils .CollectionUtils .filterMap ;
22+
23+ import java .util .Arrays ;
24+ import java .util .List ;
25+ import java .util .Map ;
26+ import java .util .stream .Collectors ;
27+ import software .amazon .awssdk .annotations .SdkInternalApi ;
28+ import software .amazon .awssdk .enhanced .dynamodb .TableMetadata ;
29+ import software .amazon .awssdk .enhanced .dynamodb .update .UpdateExpression ;
30+ import software .amazon .awssdk .services .dynamodb .model .AttributeValue ;
31+
32+ /**
33+ *
34+ */
35+ @ SdkInternalApi
36+ public final class UpdateExpressionResolver {
37+
38+ private final UpdateExpression extensionExpression ;
39+ private final UpdateExpression requestExpression ;
40+ private final Map <String , AttributeValue > itemNonKeyAttributes ;
41+ private final TableMetadata tableMetadata ;
42+
43+ private UpdateExpressionResolver (Builder builder ) {
44+ this .extensionExpression = builder .transformationExpression ;
45+ this .requestExpression = builder .requestExpression ;
46+ this .itemNonKeyAttributes = builder .nonKeyAttributes ;
47+ this .tableMetadata = builder .tableMetadata ;
48+ }
49+
50+ public static Builder builder () {
51+ return new Builder ();
52+ }
53+
54+ /**
55+ * Resolves all available and potential update expressions by priority and returns a merged update expression. It may return
56+ * null, if the item attribute map is empty / does not contain non-null attributes and no other update expressions are
57+ * present.
58+ * <p>
59+ * Conditions that will result in error:
60+ * <ul>
61+ * <li>Two expressions contain actions referencing the same attribute</li>
62+ * </ul>
63+ * <p>
64+ * <b>Note: </b> The presence of attributes in update expressions submitted through the request or generated from extensions
65+ * take precedence over removing attributes based on item configuration.
66+ * For example, when IGNORE_NULLS is set to true (default), the client generates REMOVE actions for all
67+ * attributes in the schema that are not explicitly set in the request item submitted to the operation. If such
68+ * attributes are referenced in update expressions on the request or from extensions, the remove actions are filtered
69+ * out.
70+ */
71+ public UpdateExpression resolve () {
72+ UpdateExpression itemSetExpression = generateItemSetExpression (itemNonKeyAttributes , tableMetadata );
73+
74+ List <String > nonRemoveAttributes = attributesPresentInExpressions (Arrays .asList (extensionExpression , requestExpression ));
75+ UpdateExpression itemRemoveExpression = generateItemRemoveExpression (itemNonKeyAttributes , nonRemoveAttributes );
76+
77+ UpdateExpression itemExpression = UpdateExpression .mergeExpressions (itemSetExpression , itemRemoveExpression );
78+ UpdateExpression extensionItemExpression = UpdateExpression .mergeExpressions (extensionExpression , itemExpression );
79+ return UpdateExpression .mergeExpressions (requestExpression , extensionItemExpression );
80+ }
81+
82+ private static List <String > attributesPresentInExpressions (List <UpdateExpression > updateExpressions ) {
83+ return updateExpressions .stream ()
84+ .map (UpdateExpressionConverter ::findAttributeNames )
85+ .flatMap (List ::stream )
86+ .collect (Collectors .toList ());
87+ }
88+
89+ public static UpdateExpression generateItemSetExpression (Map <String , AttributeValue > itemMap ,
90+ TableMetadata tableMetadata ) {
91+
92+ Map <String , AttributeValue > setAttributes = filterMap (itemMap , e -> !isNullAttributeValue (e .getValue ()));
93+ return UpdateExpression .builder ()
94+ .actions (setActionsFor (setAttributes , tableMetadata ))
95+ .build ();
96+ }
97+
98+ public static UpdateExpression generateItemRemoveExpression (Map <String , AttributeValue > itemMap ,
99+ List <String > nonRemoveAttributes ) {
100+ Map <String , AttributeValue > removeAttributes =
101+ filterMap (itemMap , e -> isNullAttributeValue (e .getValue ()) && !nonRemoveAttributes .contains (e .getKey ()));
102+
103+ return UpdateExpression .builder ()
104+ .actions (removeActionsFor (removeAttributes ))
105+ .build ();
106+ }
107+
108+ public static final class Builder {
109+
110+ private TableMetadata tableMetadata ;
111+ private UpdateExpression transformationExpression ;
112+ private UpdateExpression requestExpression ;
113+ private Map <String , AttributeValue > nonKeyAttributes ;
114+
115+ public Builder tableMetadata (TableMetadata tableMetadata ) {
116+ this .tableMetadata = tableMetadata ;
117+ return this ;
118+ }
119+
120+ public Builder transformationExpression (UpdateExpression transformationExpression ) {
121+ this .transformationExpression = transformationExpression ;
122+ return this ;
123+ }
124+
125+ public Builder itemNonKeyAttributes (Map <String , AttributeValue > nonKeyAttributes ) {
126+ this .nonKeyAttributes = nonKeyAttributes ;
127+ return this ;
128+ }
129+
130+ public Builder requestExpression (UpdateExpression requestExpression ) {
131+ this .requestExpression = requestExpression ;
132+ return this ;
133+ }
134+
135+ public UpdateExpressionResolver build () {
136+ return new UpdateExpressionResolver (this );
137+ }
138+
139+ }
140+ }
0 commit comments