Skip to content

Commit 018ed4d

Browse files
authored
Merge branch 'master' into fix/dynamodb-enhanced-null-checks-6639-5890/2026-02-20
2 parents 18e29d1 + 0fac95c commit 018ed4d

File tree

3 files changed

+120
-31
lines changed

3 files changed

+120
-31
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "Amazon DynamoDB Enhanced Client",
4+
"contributor": "",
5+
"description": "Improved performance of UpdateExpression conversion by replacing Stream.concat chains and String.format with direct iteration and StringJoiner."
6+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Build SDK
2+
on:
3+
pull_request:
4+
types: [opened, synchronize, ready_for_review]
5+
6+
concurrency:
7+
group: start-pull-request-build-${{ github.ref }}
8+
cancel-in-progress: true
9+
10+
env:
11+
DOWNLOAD_FOLDER: '.build-scripts/'
12+
SCRIPT_LOCATION: 'workflows/start-pull-request-build/pull-request-build-v1.sh'
13+
14+
jobs:
15+
aws-sdk-pr-build:
16+
if: github.event.pull_request.draft == false
17+
runs-on: ubuntu-latest
18+
permissions:
19+
id-token: write
20+
issues: write
21+
pull-requests: write
22+
contents: read
23+
steps:
24+
- name: Configure AWS Credentials
25+
uses: aws-actions/configure-aws-credentials@v4
26+
with:
27+
role-to-assume: ${{secrets.PR_WORKFLOW_IAM_ROLE_ARN}}
28+
role-session-name: PullRequestBuildGitHubAction
29+
aws-region: us-west-2
30+
role-duration-seconds: 7200 # 2 hours
31+
- name: Download Build Script
32+
run: |
33+
aws s3 cp s3://aws-sdk-builds-github-assets-prod-us-west-2/$SCRIPT_LOCATION ./$DOWNLOAD_FOLDER/$SCRIPT_LOCATION --no-progress
34+
chmod +x ./$DOWNLOAD_FOLDER/$SCRIPT_LOCATION
35+
- name: Build
36+
env:
37+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
HEAD_REF: ${{ github.event.pull_request.head.ref }}
39+
run: |
40+
./$DOWNLOAD_FOLDER/$SCRIPT_LOCATION \
41+
--repo "${{ github.repository }}" \
42+
--branch "$HEAD_REF" \
43+
--pr-number "${{ github.event.pull_request.number }}" \
44+
--run-id "${{ github.run_id }}"
45+
timeout-minutes: 120

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/update/UpdateExpressionConverter.java

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.Collections;
20+
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
23+
import java.util.StringJoiner;
2224
import java.util.stream.Collectors;
2325
import java.util.stream.Stream;
2426
import software.amazon.awssdk.annotations.SdkInternalApi;
@@ -113,53 +115,89 @@ public static List<String> findAttributeNames(UpdateExpression updateExpression)
113115
private static List<String> groupExpressions(UpdateExpression expression) {
114116
List<String> groupExpressions = new ArrayList<>();
115117
if (!expression.setActions().isEmpty()) {
116-
groupExpressions.add(SET + expression.setActions().stream()
117-
.map(a -> String.format("%s = %s", a.path(), a.value()))
118-
.collect(Collectors.joining(ACTION_SEPARATOR)));
118+
StringJoiner joiner = new StringJoiner(ACTION_SEPARATOR, SET, "");
119+
expression.setActions().forEach(a -> joiner.add(a.path() + " = " + a.value()));
120+
groupExpressions.add(joiner.toString());
119121
}
120122
if (!expression.removeActions().isEmpty()) {
121-
groupExpressions.add(REMOVE + expression.removeActions().stream()
122-
.map(RemoveAction::path)
123-
.collect(Collectors.joining(ACTION_SEPARATOR)));
123+
StringJoiner joiner = new StringJoiner(ACTION_SEPARATOR, REMOVE, "");
124+
expression.removeActions().forEach(a -> joiner.add(a.path()));
125+
groupExpressions.add(joiner.toString());
124126
}
125127
if (!expression.deleteActions().isEmpty()) {
126-
groupExpressions.add(DELETE + expression.deleteActions().stream()
127-
.map(a -> String.format("%s %s", a.path(), a.value()))
128-
.collect(Collectors.joining(ACTION_SEPARATOR)));
128+
StringJoiner joiner = new StringJoiner(ACTION_SEPARATOR, DELETE, "");
129+
expression.deleteActions().forEach(a -> joiner.add(a.path() + " " + a.value()));
130+
groupExpressions.add(joiner.toString());
129131
}
130132
if (!expression.addActions().isEmpty()) {
131-
groupExpressions.add(ADD + expression.addActions().stream()
132-
.map(a -> String.format("%s %s", a.path(), a.value()))
133-
.collect(Collectors.joining(ACTION_SEPARATOR)));
133+
StringJoiner joiner = new StringJoiner(ACTION_SEPARATOR, ADD, "");
134+
expression.addActions().forEach(a -> joiner.add(a.path() + " " + a.value()));
135+
groupExpressions.add(joiner.toString());
134136
}
135137
return groupExpressions;
136138
}
137139

138-
private static Stream<Map<String, String>> streamOfExpressionNames(UpdateExpression expression) {
139-
return Stream.concat(expression.setActions().stream().map(SetAction::expressionNames),
140-
Stream.concat(expression.removeActions().stream().map(RemoveAction::expressionNames),
141-
Stream.concat(expression.deleteActions().stream()
142-
.map(DeleteAction::expressionNames),
143-
expression.addActions().stream()
144-
.map(AddAction::expressionNames))));
140+
private static Map<String, AttributeValue> mergeExpressionValues(UpdateExpression expression) {
141+
Map<String, AttributeValue> merged = new HashMap<>();
142+
143+
for (SetAction action : expression.setActions()) {
144+
mergeValuesInto(merged, action.expressionValues());
145+
}
146+
for (DeleteAction action : expression.deleteActions()) {
147+
mergeValuesInto(merged, action.expressionValues());
148+
}
149+
for (AddAction action : expression.addActions()) {
150+
mergeValuesInto(merged, action.expressionValues());
151+
}
152+
153+
return merged.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(merged);
145154
}
146155

147-
private static Map<String, AttributeValue> mergeExpressionValues(UpdateExpression expression) {
148-
return streamOfExpressionValues(expression)
149-
.reduce(Expression::joinValues)
150-
.orElseGet(Collections::emptyMap);
156+
private static Map<String, String> mergeExpressionNames(UpdateExpression expression) {
157+
Map<String, String> merged = new HashMap<>();
158+
159+
for (SetAction action : expression.setActions()) {
160+
mergeNamesInto(merged, action.expressionNames());
161+
}
162+
for (RemoveAction action : expression.removeActions()) {
163+
mergeNamesInto(merged, action.expressionNames());
164+
}
165+
for (DeleteAction action : expression.deleteActions()) {
166+
mergeNamesInto(merged, action.expressionNames());
167+
}
168+
for (AddAction action : expression.addActions()) {
169+
mergeNamesInto(merged, action.expressionNames());
170+
}
171+
172+
return merged.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(merged);
151173
}
152174

153-
private static Stream<Map<String, AttributeValue>> streamOfExpressionValues(UpdateExpression expression) {
154-
return Stream.concat(expression.setActions().stream().map(SetAction::expressionValues),
155-
Stream.concat(expression.deleteActions().stream().map(DeleteAction::expressionValues),
156-
expression.addActions().stream().map(AddAction::expressionValues)));
175+
private static void mergeNamesInto(Map<String, String> target, Map<String, String> source) {
176+
if (source == null || source.isEmpty()) {
177+
return;
178+
}
179+
source.forEach((key, value) -> {
180+
String oldValue = target.put(key, value);
181+
if (oldValue != null && !oldValue.equals(value)) {
182+
throw new IllegalArgumentException(
183+
String.format("Attempt to coalesce two expressions with conflicting expression names. "
184+
+ "Expression name key = '%s'", key));
185+
}
186+
});
157187
}
158188

159-
private static Map<String, String> mergeExpressionNames(UpdateExpression expression) {
160-
return streamOfExpressionNames(expression)
161-
.reduce(Expression::joinNames)
162-
.orElseGet(Collections::emptyMap);
189+
private static void mergeValuesInto(Map<String, AttributeValue> target, Map<String, AttributeValue> source) {
190+
if (source == null || source.isEmpty()) {
191+
return;
192+
}
193+
source.forEach((key, value) -> {
194+
AttributeValue oldValue = target.put(key, value);
195+
if (oldValue != null && !oldValue.equals(value)) {
196+
throw new IllegalArgumentException(
197+
String.format("Attempt to coalesce two expressions with conflicting expression values. "
198+
+ "Expression value key = '%s'", key));
199+
}
200+
});
163201
}
164202

165203
private static List<String> listPathsWithoutTokens(UpdateExpression expression) {

0 commit comments

Comments
 (0)