Skip to content

Commit 8b85f34

Browse files
musketyrbmaizels
authored andcommitted
DDB Enhanced: Added support for projection expression
added new property attributesToProject to QueryEnhancedRequest and ScanEnhancedRequest which enables to fetch only partial results to save the bandwidth of the queries.
1 parent 5b867a0 commit 8b85f34

File tree

11 files changed

+425
-7
lines changed

11 files changed

+425
-7
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# __2.11.10__
2+
3+
## __Amazon DynamoDB Enhanced Client [Preview]__
4+
- ### Features
5+
- The enhanced DDB query and scan request now supports projections.
6+
17
# __2.11.9__ __2020-04-03__
28
## __AWS RoboMaker__
39
- ### Features

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

+23-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@
1515

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

18+
import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.cleanAttributeName;
19+
20+
import java.util.ArrayList;
21+
import java.util.HashMap;
22+
import java.util.List;
1823
import java.util.Map;
1924
import java.util.function.Function;
25+
import java.util.function.UnaryOperator;
2026
import software.amazon.awssdk.annotations.SdkInternalApi;
2127
import software.amazon.awssdk.core.async.SdkPublisher;
2228
import software.amazon.awssdk.core.pagination.sync.SdkIterable;
@@ -37,6 +43,8 @@
3743
public class QueryOperation<T> implements PaginatedTableOperation<T, QueryRequest, QueryResponse>,
3844
PaginatedIndexOperation<T, QueryRequest, QueryResponse> {
3945

46+
private static final UnaryOperator<String> PROJECTION_EXPRESSION_KEY_MAPPER = k -> "#AMZN_MAPPED_" + cleanAttributeName(k);
47+
4048
private final QueryEnhancedRequest request;
4149

4250
private QueryOperation(QueryEnhancedRequest request) {
@@ -60,6 +68,19 @@ public QueryRequest generateRequest(TableSchema<T> tableSchema,
6068
expressionNames = Expression.joinNames(expressionNames, this.request.filterExpression().expressionNames());
6169
}
6270

71+
String projectionExpression = null;
72+
if (this.request.attributesToProject() != null) {
73+
List<String> placeholders = new ArrayList<>();
74+
Map<String, String> projectionPlaceholders = new HashMap<>();
75+
this.request.attributesToProject().forEach(attr -> {
76+
String placeholder = PROJECTION_EXPRESSION_KEY_MAPPER.apply(attr);
77+
placeholders.add(placeholder);
78+
projectionPlaceholders.put(placeholder, attr);
79+
});
80+
projectionExpression = String.join(",", placeholders);
81+
expressionNames = Expression.joinNames(expressionNames, projectionPlaceholders);
82+
}
83+
6384
QueryRequest.Builder queryRequest = QueryRequest.builder()
6485
.tableName(operationContext.tableName())
6586
.keyConditionExpression(queryExpression.expression())
@@ -68,7 +89,8 @@ public QueryRequest generateRequest(TableSchema<T> tableSchema,
6889
.scanIndexForward(this.request.scanIndexForward())
6990
.limit(this.request.limit())
7091
.exclusiveStartKey(this.request.exclusiveStartKey())
71-
.consistentRead(this.request.consistentRead());
92+
.consistentRead(this.request.consistentRead())
93+
.projectionExpression(projectionExpression);
7294

7395
if (!TableMetadata.primaryIndexName().equals(operationContext.indexName())) {
7496
queryRequest = queryRequest.indexName(operationContext.indexName());

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

+37-4
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,36 @@
1515

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

18+
import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.cleanAttributeName;
19+
20+
import java.util.ArrayList;
21+
import java.util.HashMap;
22+
import java.util.List;
23+
import java.util.Map;
1824
import java.util.function.Function;
25+
import java.util.function.UnaryOperator;
1926
import software.amazon.awssdk.annotations.SdkInternalApi;
2027
import software.amazon.awssdk.core.async.SdkPublisher;
2128
import software.amazon.awssdk.core.pagination.sync.SdkIterable;
2229
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
30+
import software.amazon.awssdk.enhanced.dynamodb.Expression;
2331
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
2432
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
2533
import software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils;
2634
import software.amazon.awssdk.enhanced.dynamodb.model.Page;
2735
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest;
2836
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
2937
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
38+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
3039
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
3140
import software.amazon.awssdk.services.dynamodb.model.ScanResponse;
3241

3342
@SdkInternalApi
3443
public class ScanOperation<T> implements PaginatedTableOperation<T, ScanRequest, ScanResponse>,
3544
PaginatedIndexOperation<T, ScanRequest, ScanResponse> {
3645

46+
private static final UnaryOperator<String> PROJECTION_EXPRESSION_KEY_MAPPER = k -> "#AMZN_MAPPED_" + cleanAttributeName(k);
47+
3748
private final ScanEnhancedRequest request;
3849

3950
private ScanOperation(ScanEnhancedRequest request) {
@@ -48,20 +59,42 @@ public static <T> ScanOperation<T> create(ScanEnhancedRequest request) {
4859
public ScanRequest generateRequest(TableSchema<T> tableSchema,
4960
OperationContext operationContext,
5061
DynamoDbEnhancedClientExtension extension) {
62+
Map<String, AttributeValue> expressionValues = null;
63+
Map<String, String> expressionNames = null;
64+
65+
if (this.request.filterExpression() != null) {
66+
expressionValues = this.request.filterExpression().expressionValues();
67+
expressionNames = this.request.filterExpression().expressionNames();
68+
}
69+
70+
String projectionExpression = null;
71+
if (this.request.attributesToProject() != null) {
72+
List<String> placeholders = new ArrayList<>();
73+
Map<String, String> projectionPlaceholders = new HashMap<>();
74+
this.request.attributesToProject().forEach(attr -> {
75+
String placeholder = PROJECTION_EXPRESSION_KEY_MAPPER.apply(attr);
76+
placeholders.add(placeholder);
77+
projectionPlaceholders.put(placeholder, attr);
78+
});
79+
projectionExpression = String.join(",", placeholders);
80+
expressionNames = Expression.joinNames(expressionNames, projectionPlaceholders);
81+
}
82+
5183
ScanRequest.Builder scanRequest = ScanRequest.builder()
5284
.tableName(operationContext.tableName())
5385
.limit(this.request.limit())
5486
.exclusiveStartKey(this.request.exclusiveStartKey())
55-
.consistentRead(this.request.consistentRead());
87+
.consistentRead(this.request.consistentRead())
88+
.expressionAttributeValues(expressionValues)
89+
.expressionAttributeNames(expressionNames)
90+
.projectionExpression(projectionExpression);
5691

5792
if (!TableMetadata.primaryIndexName().equals(operationContext.indexName())) {
5893
scanRequest = scanRequest.indexName(operationContext.indexName());
5994
}
6095

6196
if (this.request.filterExpression() != null) {
62-
scanRequest = scanRequest.filterExpression(this.request.filterExpression().expression())
63-
.expressionAttributeValues(this.request.filterExpression().expressionValues())
64-
.expressionAttributeNames(this.request.filterExpression().expressionNames());
97+
scanRequest = scanRequest.filterExpression(this.request.filterExpression().expression());
6598
}
6699

67100
return scanRequest.build();

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

+93-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515

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

18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.Collection;
21+
import java.util.Collections;
1822
import java.util.HashMap;
23+
import java.util.List;
1924
import java.util.Map;
2025
import software.amazon.awssdk.annotations.SdkPublicApi;
2126
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbAsyncIndex;
@@ -41,6 +46,7 @@ public final class QueryEnhancedRequest {
4146
private final Integer limit;
4247
private final Boolean consistentRead;
4348
private final Expression filterExpression;
49+
private final List<String> attributesToProject;
4450

4551
private QueryEnhancedRequest(Builder builder) {
4652
this.queryConditional = builder.queryConditional;
@@ -49,6 +55,9 @@ private QueryEnhancedRequest(Builder builder) {
4955
this.limit = builder.limit;
5056
this.consistentRead = builder.consistentRead;
5157
this.filterExpression = builder.filterExpression;
58+
this.attributesToProject = builder.attributesToProject != null
59+
? Collections.unmodifiableList(builder.attributesToProject)
60+
: null;
5261
}
5362

5463
/**
@@ -67,7 +76,8 @@ public Builder toBuilder() {
6776
.scanIndexForward(scanIndexForward)
6877
.limit(limit)
6978
.consistentRead(consistentRead)
70-
.filterExpression(filterExpression);
79+
.filterExpression(filterExpression)
80+
.attributesToProject(attributesToProject);
7181
}
7282

7383
/**
@@ -113,6 +123,13 @@ public Expression filterExpression() {
113123
return filterExpression;
114124
}
115125

126+
/**
127+
* Returns the list of projected attributes on this request object, or an null if no projection is specified.
128+
*/
129+
public List<String> attributesToProject() {
130+
return attributesToProject;
131+
}
132+
116133
@Override
117134
public boolean equals(Object o) {
118135
if (this == o) {
@@ -142,6 +159,12 @@ public boolean equals(Object o) {
142159
if (consistentRead != null ? ! consistentRead.equals(query.consistentRead) : query.consistentRead != null) {
143160
return false;
144161
}
162+
if (attributesToProject != null
163+
? ! attributesToProject.equals(query.attributesToProject)
164+
: query.attributesToProject != null
165+
) {
166+
return false;
167+
}
145168
return filterExpression != null ? filterExpression.equals(query.filterExpression) : query.filterExpression == null;
146169
}
147170

@@ -153,6 +176,7 @@ public int hashCode() {
153176
result = 31 * result + (limit != null ? limit.hashCode() : 0);
154177
result = 31 * result + (consistentRead != null ? consistentRead.hashCode() : 0);
155178
result = 31 * result + (filterExpression != null ? filterExpression.hashCode() : 0);
179+
result = 31 * result + (attributesToProject != null ? attributesToProject.hashCode() : 0);
156180
return result;
157181
}
158182

@@ -168,6 +192,7 @@ public static final class Builder {
168192
private Integer limit;
169193
private Boolean consistentRead;
170194
private Expression filterExpression;
195+
private List<String> attributesToProject;
171196

172197
private Builder() {
173198
}
@@ -255,6 +280,73 @@ public Builder filterExpression(Expression filterExpression) {
255280
return this;
256281
}
257282

283+
/**
284+
* <p>
285+
* Sets a collection of the attribute names to be retrieved from the database. These attributes can include
286+
* scalars, sets, or elements of a JSON document.
287+
* </p>
288+
* <p>
289+
* If no attribute names are specified, then all attributes will be returned. If any of the requested attributes
290+
* are not found, they will not appear in the result.
291+
* </p>
292+
* <p>
293+
* For more information, see <a href=
294+
* "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.AccessingItemAttributes.html"
295+
* >Accessing Item Attributes</a> in the <i>Amazon DynamoDB Developer Guide</i>.
296+
* </p>
297+
* @param attributesToProject
298+
* A collection of the attributes names to be retrieved from the database.
299+
* @return Returns a reference to this object so that method calls can be chained together.
300+
*/
301+
public Builder attributesToProject(Collection<String> attributesToProject) {
302+
this.attributesToProject = attributesToProject != null ? new ArrayList<>(attributesToProject) : null;
303+
return this;
304+
}
305+
306+
/**
307+
* <p>
308+
* Sets one or more attribute names to be retrieved from the database. These attributes can include
309+
* scalars, sets, or elements of a JSON document.
310+
* </p>
311+
* <p>
312+
* If no attribute names are specified, then all attributes will be returned. If any of the requested attributes
313+
* are not found, they will not appear in the result.
314+
* </p>
315+
* <p>
316+
* For more information, see <a href=
317+
* "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.AccessingItemAttributes.html"
318+
* >Accessing Item Attributes</a> in the <i>Amazon DynamoDB Developer Guide</i>.
319+
* </p>
320+
* @param attributesToProject
321+
* One or more attributes names to be retrieved from the database.
322+
* @return Returns a reference to this object so that method calls can be chained together.
323+
*/
324+
public Builder attributesToProject(String... attributesToProject) {
325+
return attributesToProject(Arrays.asList(attributesToProject));
326+
}
327+
328+
/**
329+
* <p>
330+
* Adds a single attribute name to be retrieved from the database. This attribute can include
331+
* scalars, sets, or elements of a JSON document.
332+
* </p>
333+
* <p>
334+
* For more information, see <a href=
335+
* "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.AccessingItemAttributes.html"
336+
* >Accessing Item Attributes</a> in the <i>Amazon DynamoDB Developer Guide</i>.
337+
* </p>
338+
* @param attributeToProject
339+
* An additional single attribute name to be retrieved from the database.
340+
* @return Returns a reference to this object so that method calls can be chained together.
341+
*/
342+
public Builder addAttributeToProject(String attributeToProject) {
343+
if (attributesToProject == null) {
344+
attributesToProject = new ArrayList<>();
345+
}
346+
attributesToProject.add(attributeToProject);
347+
return this;
348+
}
349+
258350
public QueryEnhancedRequest build() {
259351
return new QueryEnhancedRequest(this);
260352
}

0 commit comments

Comments
 (0)