Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DDB Enhanced: Added support for projection expression #1756

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# __2.11.10__

## __Amazon DynamoDB Enhanced Client [Preview]__
- ### Features
- The enhanced DDB query and scan request now supports projections.

# __2.11.9__ __2020-04-03__
## __AWS RoboMaker__
- ### Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@

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

import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.cleanAttributeName;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.core.pagination.sync.SdkIterable;
Expand All @@ -37,6 +43,8 @@
public class QueryOperation<T> implements PaginatedTableOperation<T, QueryRequest, QueryResponse>,
PaginatedIndexOperation<T, QueryRequest, QueryResponse> {

private static final UnaryOperator<String> PROJECTION_EXPRESSION_KEY_MAPPER = k -> "#AMZN_MAPPED_" + cleanAttributeName(k);

private final QueryEnhancedRequest request;

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

String projectionExpression = null;
if (this.request.attributesToProject() != null) {
List<String> placeholders = new ArrayList<>();
Map<String, String> projectionPlaceholders = new HashMap<>();
this.request.attributesToProject().forEach(attr -> {
String placeholder = PROJECTION_EXPRESSION_KEY_MAPPER.apply(attr);
placeholders.add(placeholder);
projectionPlaceholders.put(placeholder, attr);
});
projectionExpression = String.join(",", placeholders);
expressionNames = Expression.joinNames(expressionNames, projectionPlaceholders);
}

QueryRequest.Builder queryRequest = QueryRequest.builder()
.tableName(operationContext.tableName())
.keyConditionExpression(queryExpression.expression())
Expand All @@ -68,7 +89,8 @@ public QueryRequest generateRequest(TableSchema<T> tableSchema,
.scanIndexForward(this.request.scanIndexForward())
.limit(this.request.limit())
.exclusiveStartKey(this.request.exclusiveStartKey())
.consistentRead(this.request.consistentRead());
.consistentRead(this.request.consistentRead())
.projectionExpression(projectionExpression);

if (!TableMetadata.primaryIndexName().equals(operationContext.indexName())) {
queryRequest = queryRequest.indexName(operationContext.indexName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,36 @@

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

import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.cleanAttributeName;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.core.pagination.sync.SdkIterable;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
import software.amazon.awssdk.enhanced.dynamodb.Expression;
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils;
import software.amazon.awssdk.enhanced.dynamodb.model.Page;
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanResponse;

@SdkInternalApi
public class ScanOperation<T> implements PaginatedTableOperation<T, ScanRequest, ScanResponse>,
PaginatedIndexOperation<T, ScanRequest, ScanResponse> {

private static final UnaryOperator<String> PROJECTION_EXPRESSION_KEY_MAPPER = k -> "#AMZN_MAPPED_" + cleanAttributeName(k);

private final ScanEnhancedRequest request;

private ScanOperation(ScanEnhancedRequest request) {
Expand All @@ -48,20 +59,42 @@ public static <T> ScanOperation<T> create(ScanEnhancedRequest request) {
public ScanRequest generateRequest(TableSchema<T> tableSchema,
OperationContext operationContext,
DynamoDbEnhancedClientExtension extension) {
Map<String, AttributeValue> expressionValues = null;
Map<String, String> expressionNames = null;

if (this.request.filterExpression() != null) {
expressionValues = this.request.filterExpression().expressionValues();
expressionNames = this.request.filterExpression().expressionNames();
}

String projectionExpression = null;
if (this.request.attributesToProject() != null) {
List<String> placeholders = new ArrayList<>();
Map<String, String> projectionPlaceholders = new HashMap<>();
this.request.attributesToProject().forEach(attr -> {
String placeholder = PROJECTION_EXPRESSION_KEY_MAPPER.apply(attr);
placeholders.add(placeholder);
projectionPlaceholders.put(placeholder, attr);
});
projectionExpression = String.join(",", placeholders);
expressionNames = Expression.joinNames(expressionNames, projectionPlaceholders);
}

ScanRequest.Builder scanRequest = ScanRequest.builder()
.tableName(operationContext.tableName())
.limit(this.request.limit())
.exclusiveStartKey(this.request.exclusiveStartKey())
.consistentRead(this.request.consistentRead());
.consistentRead(this.request.consistentRead())
.expressionAttributeValues(expressionValues)
.expressionAttributeNames(expressionNames)
.projectionExpression(projectionExpression);

if (!TableMetadata.primaryIndexName().equals(operationContext.indexName())) {
scanRequest = scanRequest.indexName(operationContext.indexName());
}

if (this.request.filterExpression() != null) {
scanRequest = scanRequest.filterExpression(this.request.filterExpression().expression())
.expressionAttributeValues(this.request.filterExpression().expressionValues())
.expressionAttributeNames(this.request.filterExpression().expressionNames());
scanRequest = scanRequest.filterExpression(this.request.filterExpression().expression());
}

return scanRequest.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbAsyncIndex;
Expand All @@ -41,6 +46,7 @@ public final class QueryEnhancedRequest {
private final Integer limit;
private final Boolean consistentRead;
private final Expression filterExpression;
private final List<String> attributesToProject;

private QueryEnhancedRequest(Builder builder) {
this.queryConditional = builder.queryConditional;
Expand All @@ -49,6 +55,9 @@ private QueryEnhancedRequest(Builder builder) {
this.limit = builder.limit;
this.consistentRead = builder.consistentRead;
this.filterExpression = builder.filterExpression;
this.attributesToProject = builder.attributesToProject != null
? Collections.unmodifiableList(builder.attributesToProject)
: null;
}

/**
Expand All @@ -67,7 +76,8 @@ public Builder toBuilder() {
.scanIndexForward(scanIndexForward)
.limit(limit)
.consistentRead(consistentRead)
.filterExpression(filterExpression);
.filterExpression(filterExpression)
.attributesToProject(attributesToProject);
}

/**
Expand Down Expand Up @@ -113,6 +123,13 @@ public Expression filterExpression() {
return filterExpression;
}

/**
* Returns the list of projected attributes on this request object, or an null if no projection is specified.
*/
public List<String> attributesToProject() {
return attributesToProject;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down Expand Up @@ -142,6 +159,12 @@ public boolean equals(Object o) {
if (consistentRead != null ? ! consistentRead.equals(query.consistentRead) : query.consistentRead != null) {
return false;
}
if (attributesToProject != null
? ! attributesToProject.equals(query.attributesToProject)
: query.attributesToProject != null
) {
return false;
}
return filterExpression != null ? filterExpression.equals(query.filterExpression) : query.filterExpression == null;
}

Expand All @@ -153,6 +176,7 @@ public int hashCode() {
result = 31 * result + (limit != null ? limit.hashCode() : 0);
result = 31 * result + (consistentRead != null ? consistentRead.hashCode() : 0);
result = 31 * result + (filterExpression != null ? filterExpression.hashCode() : 0);
result = 31 * result + (attributesToProject != null ? attributesToProject.hashCode() : 0);
return result;
}

Expand All @@ -168,6 +192,7 @@ public static final class Builder {
private Integer limit;
private Boolean consistentRead;
private Expression filterExpression;
private List<String> attributesToProject;

private Builder() {
}
Expand Down Expand Up @@ -255,6 +280,73 @@ public Builder filterExpression(Expression filterExpression) {
return this;
}

/**
* <p>
* Sets a collection of the attribute names to be retrieved from the database. These attributes can include
* scalars, sets, or elements of a JSON document.
* </p>
* <p>
* If no attribute names are specified, then all attributes will be returned. If any of the requested attributes
* are not found, they will not appear in the result.
* </p>
* <p>
* For more information, see <a href=
* "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.AccessingItemAttributes.html"
* >Accessing Item Attributes</a> in the <i>Amazon DynamoDB Developer Guide</i>.
* </p>
* @param attributesToProject
* A collection of the attributes names to be retrieved from the database.
* @return Returns a reference to this object so that method calls can be chained together.
*/
public Builder attributesToProject(Collection<String> attributesToProject) {
this.attributesToProject = attributesToProject != null ? new ArrayList<>(attributesToProject) : null;
return this;
}

/**
* <p>
* Sets one or more attribute names to be retrieved from the database. These attributes can include
* scalars, sets, or elements of a JSON document.
* </p>
* <p>
* If no attribute names are specified, then all attributes will be returned. If any of the requested attributes
* are not found, they will not appear in the result.
* </p>
* <p>
* For more information, see <a href=
* "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.AccessingItemAttributes.html"
* >Accessing Item Attributes</a> in the <i>Amazon DynamoDB Developer Guide</i>.
* </p>
* @param attributesToProject
* One or more attributes names to be retrieved from the database.
* @return Returns a reference to this object so that method calls can be chained together.
*/
public Builder attributesToProject(String... attributesToProject) {
return attributesToProject(Arrays.asList(attributesToProject));
}

/**
* <p>
* Adds a single attribute name to be retrieved from the database. This attribute can include
* scalars, sets, or elements of a JSON document.
* </p>
* <p>
* For more information, see <a href=
* "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.AccessingItemAttributes.html"
* >Accessing Item Attributes</a> in the <i>Amazon DynamoDB Developer Guide</i>.
* </p>
* @param attributeToProject
* An additional single attribute name to be retrieved from the database.
* @return Returns a reference to this object so that method calls can be chained together.
*/
public Builder addAttributeToProject(String attributeToProject) {
if (attributesToProject == null) {
attributesToProject = new ArrayList<>();
}
attributesToProject.add(attributeToProject);
return this;
}

public QueryEnhancedRequest build() {
return new QueryEnhancedRequest(this);
}
Expand Down
Loading