Skip to content

Commit a3fe036

Browse files
akieslerAndy Kieslerdagnir
authored
Add additional helper methods to DynamoDB Expression (#4765)
These helpers make it easier to write complex queries without having to use the existing overly verbose static methods. This lets users write more clear and concise code that makes is obvious how the expressions are logically combined together. #4764 Co-authored-by: Andy Kiesler <[email protected]> Co-authored-by: Dongie Agnir <[email protected]>
1 parent b51a51c commit a3fe036

File tree

3 files changed

+383
-1
lines changed

3 files changed

+383
-1
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",
4+
"contributor": "akiesler",
5+
"description": "Add additional logical operator ('and' and 'or') methods to DynamoDB Expression"
6+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/Expression.java

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515

1616
package software.amazon.awssdk.enhanced.dynamodb;
1717

18+
import java.util.Arrays;
19+
import java.util.Collection;
1820
import java.util.Collections;
1921
import java.util.HashMap;
22+
import java.util.LinkedList;
2023
import java.util.Map;
24+
import java.util.stream.Collectors;
2125
import software.amazon.awssdk.annotations.NotThreadSafe;
2226
import software.amazon.awssdk.annotations.SdkPublicApi;
2327
import software.amazon.awssdk.annotations.ThreadSafe;
@@ -46,6 +50,9 @@
4650
@SdkPublicApi
4751
@ThreadSafe
4852
public final class Expression {
53+
public static final String AND = "AND";
54+
public static final String OR = "OR";
55+
4956
private final String expression;
5057
private final Map<String, AttributeValue> expressionValues;
5158
private final Map<String, String> expressionNames;
@@ -67,7 +74,7 @@ public static Builder builder() {
6774
}
6875

6976
/**
70-
* Coalesces two complete expressions into a single expression. The expression string will be joined using the
77+
* Coalesces two complete expressions into a single expression. The expression string will be joined using the
7178
* supplied join token, and the ExpressionNames and ExpressionValues maps will be merged.
7279
* @param expression1 The first expression to coalesce
7380
* @param expression2 The second expression to coalesce
@@ -93,6 +100,61 @@ public static Expression join(Expression expression1, Expression expression2, St
93100
.build();
94101
}
95102

103+
/**
104+
* @see #join(String, Collection)
105+
*/
106+
public static Expression and(Collection<Expression> expressions) {
107+
return join(AND, expressions);
108+
}
109+
110+
/**
111+
* @see #join(String, Collection)
112+
*/
113+
public static Expression or(Collection<Expression> expressions) {
114+
return join(OR, expressions);
115+
}
116+
117+
/**
118+
* @see #join(String, Collection)
119+
*/
120+
public static Expression join(String joinToken, Expression... expressions) {
121+
return join(joinToken, Arrays.asList(expressions));
122+
}
123+
124+
/**
125+
* Coalesces multiple complete expressions into a single expression. The expression string will be joined using the
126+
* supplied join token, and the ExpressionNames and ExpressionValues maps will be merged.
127+
* @param joinToken The join token to be used to join the expression strings (e.g.: 'AND', 'OR')
128+
* @param expressions The expressions to coalesce
129+
* @return The coalesced expression
130+
* @throws IllegalArgumentException if a conflict occurs when merging ExpressionNames or ExpressionValues
131+
*/
132+
public static Expression join(String joinToken, Collection<Expression> expressions) {
133+
joinToken = joinToken.trim();
134+
if (expressions.isEmpty()) {
135+
return null;
136+
}
137+
138+
if (expressions.size() == 1) {
139+
return expressions.toArray(new Expression[] {})[0];
140+
}
141+
142+
joinToken = ") " + joinToken + " (";
143+
String expression = expressions.stream()
144+
.map(Expression::expression)
145+
.collect(Collectors.joining(joinToken, "(", ")"));
146+
147+
Builder builder = Expression.builder()
148+
.expression(expression);
149+
150+
expressions.forEach(expr -> {
151+
builder.mergeExpressionValues(expr.expressionValues())
152+
.mergeExpressionNames(expr.expressionNames());
153+
});
154+
155+
return builder.build();
156+
}
157+
96158
/**
97159
* Coalesces two expression strings into a single expression string. The expression string will be joined using the
98160
* supplied join token.
@@ -198,6 +260,28 @@ public Expression and(Expression expression) {
198260
return join(this, expression, " AND ");
199261
}
200262

263+
/**
264+
* Coalesces multiple complete expressions into a single expression joined by 'AND'.
265+
*
266+
* @see #join(String, Collection)
267+
*/
268+
public Expression and(Expression... expressions) {
269+
LinkedList<Expression> expressionList = new LinkedList<>(Arrays.asList(expressions));
270+
expressionList.addFirst(this);
271+
return join(AND, expressionList);
272+
}
273+
274+
/**
275+
* Coalesces multiple complete expressions into a single expression joined by 'OR'.
276+
*
277+
* @see #join(String, Collection)
278+
*/
279+
public Expression or(Expression... expressions) {
280+
LinkedList<Expression> expressionList = new LinkedList<>(Arrays.asList(expressions));
281+
expressionList.addFirst(this);
282+
return join(OR, expressionList);
283+
}
284+
201285
@Override
202286
public boolean equals(Object o) {
203287
if (this == o) {
@@ -255,6 +339,33 @@ public Builder expressionValues(Map<String, AttributeValue> expressionValues) {
255339
return this;
256340
}
257341

342+
/**
343+
* Merge the given ExpressionValues into the builders existing ExpressionValues
344+
* @param expressionValues The values to merge into the ExpressionValues map
345+
* @throws IllegalArgumentException if a conflict occurs when merging ExpressionValues
346+
*/
347+
public Builder mergeExpressionValues(Map<String, AttributeValue> expressionValues) {
348+
if (this.expressionValues == null) {
349+
return expressionValues(expressionValues);
350+
}
351+
352+
if (expressionValues == null) {
353+
return this;
354+
}
355+
356+
expressionValues.forEach((key, value) -> {
357+
AttributeValue oldValue = this.expressionValues.put(key, value);
358+
359+
if (oldValue != null && !oldValue.equals(value)) {
360+
throw new IllegalArgumentException(
361+
String.format("Attempt to coalesce expressions with conflicting expression values. "
362+
+ "Expression value key = '%s'", key));
363+
}
364+
});
365+
366+
return this;
367+
}
368+
258369
/**
259370
* Adds a single element to the optional 'expression values' token map
260371
*/
@@ -275,6 +386,33 @@ public Builder expressionNames(Map<String, String> expressionNames) {
275386
return this;
276387
}
277388

389+
/**
390+
* Merge the given ExpressionNames into the builders existing ExpressionNames
391+
* @param expressionNames The values to merge into the ExpressionNames map
392+
* @throws IllegalArgumentException if a conflict occurs when merging ExpressionNames
393+
*/
394+
public Builder mergeExpressionNames(Map<String, String> expressionNames) {
395+
if (this.expressionNames == null) {
396+
return expressionNames(expressionNames);
397+
}
398+
399+
if (expressionNames == null) {
400+
return this;
401+
}
402+
403+
expressionNames.forEach((key, value) -> {
404+
String oldValue = this.expressionNames.put(key, value);
405+
406+
if (oldValue != null && !oldValue.equals(value)) {
407+
throw new IllegalArgumentException(
408+
String.format("Attempt to coalesce expressions with conflicting expression names. "
409+
+ "Expression name key = '%s'", key));
410+
}
411+
});
412+
413+
return this;
414+
}
415+
278416
/**
279417
* Adds a single element to the optional 'expression names' token map
280418
*/

0 commit comments

Comments
 (0)