Skip to content

Commit 79cfa56

Browse files
committed
DynamoDb enhanced client: support UpdateExpressions in single-request update
1 parent f222216 commit 79cfa56

File tree

2 files changed

+239
-1
lines changed

2 files changed

+239
-1
lines changed

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/UpdateItemOperation.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import java.util.Collection;
2323
import java.util.HashMap;
24-
import java.util.List;
2524
import java.util.Map;
2625
import java.util.Optional;
2726
import java.util.concurrent.CompletableFuture;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
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 org.assertj.core.api.Assertions.assertThat;
19+
import static org.mockito.Mockito.mock;
20+
import static org.mockito.Mockito.when;
21+
22+
import java.util.Collections;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
import org.junit.Test;
27+
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
28+
import software.amazon.awssdk.enhanced.dynamodb.update.RemoveAction;
29+
import software.amazon.awssdk.enhanced.dynamodb.update.SetAction;
30+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
31+
32+
public class UpdateExpressionUtilsTest {
33+
34+
private final TableMetadata mockTableMetadata = mock(TableMetadata.class);
35+
36+
@Test
37+
public void ifNotExists_createsCorrectExpression() {
38+
String result = UpdateExpressionUtils.ifNotExists("key", "value");
39+
40+
assertThat(result).isEqualTo("if_not_exists(#AMZN_MAPPED_key, :AMZN_MAPPED_value)");
41+
}
42+
43+
@Test
44+
public void setActionsFor_emptyMap_returnsEmptyList() {
45+
List<SetAction> result = UpdateExpressionUtils.setActionsFor(Collections.emptyMap(), mockTableMetadata);
46+
47+
assertThat(result).isEmpty();
48+
}
49+
50+
@Test
51+
public void setActionsFor_singleAttribute_createsSetAction() {
52+
Map<String, AttributeValue> attributes = new HashMap<>();
53+
attributes.put("attrName", AttributeValue.builder().s("attrValue").build());
54+
when(mockTableMetadata.primaryKeys()).thenReturn(Collections.emptyList());
55+
56+
List<SetAction> result = UpdateExpressionUtils.setActionsFor(attributes, mockTableMetadata);
57+
58+
assertThat(result).isEqualTo(Collections.singletonList(
59+
SetAction.builder()
60+
.path("#AMZN_MAPPED_attrName")
61+
.value(":AMZN_MAPPED_attrName")
62+
.putExpressionName("#AMZN_MAPPED_attrName", "attrName")
63+
.putExpressionValue(":AMZN_MAPPED_attrName", AttributeValue.builder().s("attrValue").build())
64+
.build()));
65+
}
66+
67+
@Test
68+
public void setActionsFor_multipleAttributes_createsMultipleSetActions() {
69+
Map<String, AttributeValue> attributes = new HashMap<>();
70+
attributes.put("attrName1", AttributeValue.builder().s("attrValue1").build());
71+
attributes.put("attrName2", AttributeValue.builder().s("attrValue2").build());
72+
when(mockTableMetadata.primaryKeys()).thenReturn(Collections.emptyList());
73+
74+
List<SetAction> result = UpdateExpressionUtils.setActionsFor(attributes, mockTableMetadata);
75+
76+
assertThat(result).hasSize(2).containsExactlyInAnyOrder(
77+
SetAction.builder()
78+
.path("#AMZN_MAPPED_attrName1")
79+
.value(":AMZN_MAPPED_attrName1")
80+
.putExpressionName("#AMZN_MAPPED_attrName1", "attrName1")
81+
.putExpressionValue(":AMZN_MAPPED_attrName1", AttributeValue.builder().s("attrValue1").build())
82+
.build(),
83+
84+
SetAction.builder()
85+
.path("#AMZN_MAPPED_attrName2")
86+
.value(":AMZN_MAPPED_attrName2")
87+
.putExpressionName("#AMZN_MAPPED_attrName2", "attrName2")
88+
.putExpressionValue(":AMZN_MAPPED_attrName2", AttributeValue.builder().s("attrValue2").build())
89+
.build());
90+
}
91+
92+
@Test
93+
public void setActionsFor_nestedAttribute_handlesCorrectly() {
94+
Map<String, AttributeValue> attributes = new HashMap<>();
95+
attributes.put("level1.level2", AttributeValue.builder().s("attrValue").build());
96+
when(mockTableMetadata.primaryKeys()).thenReturn(Collections.emptyList());
97+
98+
List<SetAction> result = UpdateExpressionUtils.setActionsFor(attributes, mockTableMetadata);
99+
100+
assertThat(result).isEqualTo(Collections.singletonList(
101+
SetAction.builder()
102+
.path("#AMZN_MAPPED_level1_level2")
103+
.value(":AMZN_MAPPED_level1_level2")
104+
.putExpressionName("#AMZN_MAPPED_level1_level2", "level1.level2")
105+
.putExpressionValue(":AMZN_MAPPED_level1_level2", AttributeValue.builder().s("attrValue").build())
106+
.build()));
107+
}
108+
109+
@Test
110+
public void setActionsFor_deeplyNestedAttribute_handlesCorrectly() {
111+
Map<String, AttributeValue> attributes = new HashMap<>();
112+
attributes.put("level1.level2.level3", AttributeValue.builder().s("attrValue").build());
113+
when(mockTableMetadata.primaryKeys()).thenReturn(Collections.emptyList());
114+
115+
List<SetAction> result = UpdateExpressionUtils.setActionsFor(attributes, mockTableMetadata);
116+
117+
assertThat(result).isEqualTo(Collections.singletonList(
118+
SetAction.builder()
119+
.path("#AMZN_MAPPED_level1_level2_level3")
120+
.value(":AMZN_MAPPED_level1_level2_level3")
121+
.putExpressionName("#AMZN_MAPPED_level1_level2_level3", "level1.level2.level3")
122+
.putExpressionValue(":AMZN_MAPPED_level1_level2_level3", AttributeValue.builder().s("attrValue").build())
123+
.build()));
124+
}
125+
126+
@Test
127+
public void setActionsFor_attributeWithSpecialCharacters_handlesCorrectly() {
128+
Map<String, AttributeValue> attributes = new HashMap<>();
129+
attributes.put("attrWithDash", AttributeValue.builder().s("#value").build());
130+
attributes.put("attrWithUnderscore", AttributeValue.builder().s("_value").build());
131+
when(mockTableMetadata.primaryKeys()).thenReturn(Collections.emptyList());
132+
133+
List<SetAction> result = UpdateExpressionUtils.setActionsFor(attributes, mockTableMetadata);
134+
135+
assertThat(result).hasSize(2).containsExactlyInAnyOrder(
136+
SetAction.builder()
137+
.path("#AMZN_MAPPED_attrWithDash")
138+
.value(":AMZN_MAPPED_attrWithDash")
139+
.putExpressionName("#AMZN_MAPPED_attrWithDash", "attrWithDash")
140+
.putExpressionValue(":AMZN_MAPPED_attrWithDash", AttributeValue.builder().s("#value").build())
141+
.build(),
142+
143+
SetAction.builder()
144+
.path("#AMZN_MAPPED_attrWithUnderscore")
145+
.value(":AMZN_MAPPED_attrWithUnderscore")
146+
.putExpressionName("#AMZN_MAPPED_attrWithUnderscore", "attrWithUnderscore")
147+
.putExpressionValue(":AMZN_MAPPED_attrWithUnderscore", AttributeValue.builder().s("_value").build())
148+
.build());
149+
}
150+
151+
@Test
152+
public void removeActionsFor_emptyMap_returnsEmptyList() {
153+
List<RemoveAction> result = UpdateExpressionUtils.removeActionsFor(Collections.emptyMap());
154+
155+
assertThat(result).isEmpty();
156+
}
157+
158+
@Test
159+
public void removeActionsFor_singleAttribute_createsRemoveAction() {
160+
Map<String, AttributeValue> attributes = new HashMap<>();
161+
attributes.put("attrName", AttributeValue.builder().s("attrValue").build());
162+
163+
List<RemoveAction> result = UpdateExpressionUtils.removeActionsFor(attributes);
164+
165+
assertThat(result).isEqualTo(Collections.singletonList(
166+
RemoveAction.builder()
167+
.path("#AMZN_MAPPED_attrName")
168+
.putExpressionName("#AMZN_MAPPED_attrName", "attrName")
169+
.build()));
170+
}
171+
172+
@Test
173+
public void removeActionsFor_multipleAttributes_createsMultipleRemoveActions() {
174+
Map<String, AttributeValue> attributes = new HashMap<>();
175+
attributes.put("attrName1", AttributeValue.builder().nul(true).build());
176+
attributes.put("attrName2", AttributeValue.builder().nul(true).build());
177+
178+
List<RemoveAction> result = UpdateExpressionUtils.removeActionsFor(attributes);
179+
180+
assertThat(result).hasSize(2).containsExactlyInAnyOrder(
181+
RemoveAction.builder()
182+
.path("#AMZN_MAPPED_attrName1")
183+
.putExpressionName("#AMZN_MAPPED_attrName1", "attrName1")
184+
.build(),
185+
RemoveAction.builder()
186+
.path("#AMZN_MAPPED_attrName2")
187+
.putExpressionName("#AMZN_MAPPED_attrName2", "attrName2")
188+
.build());
189+
}
190+
191+
@Test
192+
public void removeActionsFor_nestedAttribute_handlesCorrectly() {
193+
Map<String, AttributeValue> attributes = new HashMap<>();
194+
attributes.put("level1.level2", AttributeValue.builder().s("attrValue").build());
195+
196+
List<RemoveAction> result = UpdateExpressionUtils.removeActionsFor(attributes);
197+
198+
assertThat(result).isEqualTo(Collections.singletonList(
199+
RemoveAction.builder()
200+
.path("#AMZN_MAPPED_level1_level2")
201+
.putExpressionName("#AMZN_MAPPED_level1_level2", "level1.level2")
202+
.build()));
203+
}
204+
205+
@Test
206+
public void removeActionsFor_deeplyNestedAttribute_handlesCorrectly() {
207+
Map<String, AttributeValue> attributes = new HashMap<>();
208+
attributes.put("level1.level2.level3", AttributeValue.builder().s("attrValue").build());
209+
210+
List<RemoveAction> result = UpdateExpressionUtils.removeActionsFor(attributes);
211+
212+
assertThat(result).isEqualTo(Collections.singletonList(
213+
RemoveAction.builder()
214+
.path("#AMZN_MAPPED_level1_level2_level3")
215+
.putExpressionName("#AMZN_MAPPED_level1_level2_level3", "level1.level2.level3")
216+
.build()));
217+
}
218+
219+
@Test
220+
public void removeActionsFor_attributeWithSpecialCharacters_handlesCorrectly() {
221+
Map<String, AttributeValue> attributes = new HashMap<>();
222+
attributes.put("attrWithDash", AttributeValue.builder().s("#value").build());
223+
attributes.put("attrWithUnderscore", AttributeValue.builder().s("_value").build());
224+
225+
List<RemoveAction> result = UpdateExpressionUtils.removeActionsFor(attributes);
226+
227+
assertThat(result).hasSize(2).containsExactlyInAnyOrder(
228+
RemoveAction.builder()
229+
.path("#AMZN_MAPPED_attrWithDash")
230+
.putExpressionName("#AMZN_MAPPED_attrWithDash", "attrWithDash")
231+
.build(),
232+
233+
RemoveAction.builder()
234+
.path("#AMZN_MAPPED_attrWithUnderscore")
235+
.putExpressionName("#AMZN_MAPPED_attrWithUnderscore", "attrWithUnderscore")
236+
.build());
237+
}
238+
}
239+

0 commit comments

Comments
 (0)