diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/NestedAttributeName.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/NestedAttributeName.java
new file mode 100644
index 000000000000..21404c4b47c8
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/NestedAttributeName.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import software.amazon.awssdk.annotations.SdkPublicApi;
+import software.amazon.awssdk.utils.Validate;
+
+/**
+ * High-level representation of a DynamoDB 'NestedAttributeName' that can be used in various situations where the API requires
+ * or accepts an Nested Attribute Name.
+ * Simple Attribute Name can be represented by passing just the name of the attribute.
+ * Nested Attributes are represented by List of String where each index of list corresponds to Nesting level Names.
+ *
While using attributeToProject in {@link software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest}
+ * and {@link software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest} we need way to represent Nested Attributes.
+ * The normal DOT(.) separator is not recognized as a Nesting level separator by DynamoDB request,
+ * thus we need to use NestedAttributeName
+ * which can be used to represent Nested attributes.
+ *
Example : NestedAttributeName.create("foo") corresponds to a NestedAttributeName with elements list
+ * with single element foo which represents Simple attribute name "foo" without nesting.
+ *
NestedAttributeName.create("foo", "bar") corresponds to a NestedAttributeName with elements list "foo", "bar"
+ * respresenting nested attribute name "foo.bar".
+ */
+@SdkPublicApi
+public final class NestedAttributeName {
+
+ private final List elements;
+
+ private NestedAttributeName(List nestedAttributeNames) {
+ Validate.validState(nestedAttributeNames != null, "nestedAttributeNames must not be null.");
+ Validate.notEmpty(nestedAttributeNames, "nestedAttributeNames must not be empty");
+ Validate.noNullElements(nestedAttributeNames, "nestedAttributeNames must not contain null values");
+ this.elements = Collections.unmodifiableList(nestedAttributeNames);
+ }
+
+ /**
+ * Creates a NestedAttributeName with a single element, which is effectively just a simple attribute name without nesting.
+ *
+ * Example:create("foo") will create NestedAttributeName corresponding to Attribute foo.
+ *
+ * @param element Attribute Name. Single String represents just a simple attribute name without nesting.
+ * @return NestedAttributeName with attribute name as specified element.
+ */
+ public static NestedAttributeName create(String element) {
+ return new Builder().addElement(element).build();
+ }
+
+ /**
+ * Creates a NestedAttributeName from a list of elements that compose the full path of the nested attribute.
+ *
+ * Example:create("foo", "bar") will create NestedAttributeName which represents foo.bar nested attribute.
+ *
+ * @param elements Nested Attribute Names. Each of strings in varargs represent the nested attribute name
+ * at subsequent levels.
+ * @return NestedAttributeName with Nested attribute name set as specified in elements var args.
+ */
+ public static NestedAttributeName create(String... elements) {
+ return new Builder().elements(elements).build();
+ }
+
+ /**
+ * Creates a NestedAttributeName from a list of elements that compose the full path of the nested attribute.
+ *
+ * Example:create(Arrays.asList("foo", "bar")) will create NestedAttributeName
+ * which represents foo.bar nested attribute.
+ *
+ * @param elements List of Nested Attribute Names. Each of strings in List represent the nested attribute name
+ * at subsequent levels.
+ * @return NestedAttributeName with Nested attribute name set as specified in elements Collections.
+ */
+ public static NestedAttributeName create(List elements) {
+ return new Builder().elements(elements).build();
+ }
+
+ /**
+ * Create a builder that can be used to create a {@link NestedAttributeName}.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Gets elements of NestedAttributeName in the form of List. Each element in the list corresponds
+ * to the subsequent Nested Attribute name.
+ *
+ * @return List of nested attributes, each entry in the list represent one level of nesting.
+ * Example, A Two level Attribute name foo.bar will be represented as ["foo", "bar"]
+ */
+ public List elements() {
+ return elements;
+ }
+
+ /**
+ * Returns a builder initialized with all existing values on the request object.
+ */
+ public Builder toBuilder() {
+ return builder().elements(elements);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ NestedAttributeName that = (NestedAttributeName) o;
+
+ return elements != null
+ ? elements.equals(that.elements) : that.elements == null;
+ }
+
+ @Override
+ public int hashCode() {
+ return elements != null ? elements.hashCode() : 0;
+ }
+
+ /**
+ * A builder for {@link NestedAttributeName}.
+ */
+ public static class Builder {
+ private List elements = null;
+
+ private Builder() {
+
+ }
+
+ /**
+ * Adds a single element of NestedAttributeName.
+ * Subsequent calls to this method can add attribute Names at subsequent nesting levels.
+ *
+ * Example:builder().addElement("foo").addElement("bar") will add elements in NestedAttributeName
+ * which represent a Nested Attribute Name foo.bar
+ *
+ * @param element Attribute Name.
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ public Builder addElement(String element) {
+ if (elements == null) {
+ elements = new ArrayList<>();
+ }
+ elements.add(element);
+ return this;
+ }
+
+ /**
+ * Adds a single element of NestedAttributeName.
+ * Subsequent calls to this method will append the new elements to the end of the existing chain of elements
+ * creating new levels of nesting.
+ *
+ * Example:builder().addElements("foo","bar") will add elements in NestedAttributeName
+ * which represent a Nested Attribute Name foo.bar
+ *
+ * @param elements Nested Attribute Names. Each of strings in varargs represent the nested attribute name
+ * at subsequent levels.
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ public Builder addElements(String... elements) {
+ if (this.elements == null) {
+ this.elements = new ArrayList<>();
+ }
+ this.elements.addAll(Arrays.asList(elements));
+ return this;
+ }
+
+ /**
+ * Adds a List of elements to NestedAttributeName.
+ * Subsequent calls to this method will append the new elements to the end of the existing chain of elements
+ * creating new levels of nesting.
+ *
+ * Example:builder().addElements(Arrays.asList("foo","bar")) will add elements in NestedAttributeName
+ * to represent a Nested Attribute Name foo.bar
+ *
+ * @param elements List of Strings where each string corresponds to subsequent nesting attribute name.
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ public Builder addElements(List elements) {
+ if (this.elements == null) {
+ this.elements = new ArrayList<>();
+ }
+ this.elements.addAll(elements);
+ return this;
+ }
+
+ /**
+ * Set elements of NestedAttributeName with list of Strings. Will overwrite any existing elements stored by this builder.
+ *
+ * Example:builder().elements("foo","bar") will set the elements in NestedAttributeName
+ * to represent a nested attribute name of 'foo.bar'
+ *
+ * @param elements a list of strings that correspond to the elements in a nested attribute name.
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ public Builder elements(String... elements) {
+ this.elements = new ArrayList<>(Arrays.asList(elements));
+ return this;
+ }
+
+ /**
+ * Sets the elements that compose a nested attribute name. Will overwrite any existing elements stored by this builder.
+ *
+ * Example:builder().elements(Arrays.asList("foo","bar")) will add elements in NestedAttributeName
+ * which represent a Nested Attribute Name foo.bar
+ *
+ * @param elements a list of strings that correspond to the elements in a nested attribute name.
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ public Builder elements(List elements) {
+ this.elements = new ArrayList<>(elements);
+ return this;
+ }
+
+
+ public NestedAttributeName build() {
+ return new NestedAttributeName(elements);
+ }
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/ProjectionExpressionConvertor.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/ProjectionExpressionConvertor.java
new file mode 100644
index 000000000000..f5d03ac9fdd7
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/ProjectionExpressionConvertor.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.enhanced.dynamodb.internal;
+
+import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.cleanAttributeName;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName;
+
+/**
+ * Wrapper method to get Projection Expression Name map and Projection Expressions from NestedAttributeNames.
+ */
+@SdkInternalApi
+public class ProjectionExpressionConvertor {
+
+ private static final String AMZN_MAPPED = "#AMZN_MAPPED_";
+ private static final UnaryOperator PROJECTION_EXPRESSION_KEY_MAPPER = k -> AMZN_MAPPED + cleanAttributeName(k);
+ private final List nestedAttributeNames;
+
+ private ProjectionExpressionConvertor(List nestedAttributeNames) {
+ this.nestedAttributeNames = nestedAttributeNames;
+ }
+
+ public static ProjectionExpressionConvertor create(List nestedAttributeNames) {
+ return new ProjectionExpressionConvertor(nestedAttributeNames);
+ }
+
+ private static Optional
- * @param attributesToProject
- * One or more attributes names to be retrieved from the database.
+ *
+ * @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) {
@@ -272,15 +297,86 @@ public Builder attributesToProject(String... attributesToProject) {
* "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.AccessingItemAttributes.html"
* >Accessing Item Attributes in the Amazon DynamoDB Developer Guide.
*
- * @param attributeToProject
- * An additional single attribute name to be retrieved from the database.
+ *
+ * @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<>();
+ if (attributeToProject != null) {
+ addNestedAttributesToProject(NestedAttributeName.create(attributeToProject));
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Adds a collection of the NestedAttributeNames to be retrieved from the database. These attributes can include
+ * scalars, sets, or elements of a JSON document.
+ * This method takes arguments in form of NestedAttributeName which supports representing nested attributes.
+ * The NestedAttributeNames is specially created for projecting Nested Attribute names.
+ * The DOT characters are not recognized as nesting separator by DDB thus for Enhanced request NestedAttributeNames
+ * should be created to project Nested Attribute name at various levels.
+ * This method will add new attributes to project to the existing list of attributes to project stored by this builder.
+ *
+ * @param nestedAttributeNames A collection of the attributes names to be retrieved from the database.
+ * Nested levels of Attributes can be added using NestedAttributeName class.
+ * Refer {@link NestedAttributeName}.
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ public Builder addNestedAttributesToProject(Collection nestedAttributeNames) {
+ if (nestedAttributeNames != null) {
+ Validate.noNullElements(nestedAttributeNames,
+ "nestedAttributeNames list must not contain null elements");
+ if (attributesToProject == null) {
+ this.attributesToProject = new ArrayList<>(nestedAttributeNames);
+ } else {
+ this.attributesToProject.addAll(nestedAttributeNames);
+ }
+ }
+ return this;
+ }
+
+ /**
+ *
+ * Add one or more attribute names to be retrieved from the database. These attributes can include
+ * scalars, sets, or elements of a JSON document.
+ * This method takes arguments in form of NestedAttributeName which supports representing nested attributes.
+ * This method takes arguments in form of NestedAttributeName which supports representing nested attributes.
+ * The NestedAttributeNames is specially created for projecting Nested Attribute names.
+ * The DOT characters are not recognized as nesting separator by DDB thus for Enhanced request NestedAttributeNames
+ * should be created to project Nested Attribute name at various levels.
+ * This method will add new attributes to project to the existing list of attributes to project stored by this builder.
+ *
+ * @param nestedAttributeNames One or more attributesNames to be retrieved from the database.
+ * Nested levels of Attributes can be added using NestedAttributeName class.
+ * Refer {@link NestedAttributeName}.
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ public Builder addNestedAttributesToProject(NestedAttributeName... nestedAttributeNames) {
+ addNestedAttributesToProject(Arrays.asList(nestedAttributeNames));
+ return this;
+ }
+
+ /**
+ *
+ * Adds a single NestedAttributeName to be retrieved from the database. This attribute can include
+ * scalars, sets, or elements of a JSON document.
+ * This method takes arguments in form of NestedAttributeName which supports representing nested attributes.
+ *
+ *
+ * For more information, see Accessing Item Attributes in the Amazon DynamoDB Developer Guide.
+ *
+ *
+ * @param nestedAttributeName An additional single attribute name to be retrieved from the database.
+ * Refer {@link NestedAttributeName}.
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ public Builder addNestedAttributeToProject(NestedAttributeName nestedAttributeName) {
+ if (nestedAttributeName != null) {
+ addNestedAttributesToProject(Arrays.asList(nestedAttributeName));
}
- attributesToProject.add(attributeToProject);
return this;
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/ProjectionExpressionConvertorTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/ProjectionExpressionConvertorTest.java
new file mode 100644
index 000000000000..4a967ae8c784
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/ProjectionExpressionConvertorTest.java
@@ -0,0 +1,65 @@
+package software.amazon.awssdk.enhanced.dynamodb;
+
+import org.junit.Test;
+import software.amazon.awssdk.enhanced.dynamodb.internal.ProjectionExpressionConvertor;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static software.amazon.awssdk.enhanced.dynamodb.converters.attribute.ConverterTestUtils.assertFails;
+
+public class ProjectionExpressionConvertorTest {
+
+ public static final String MAPPED_INDICATOR = "#AMZN_MAPPED_";
+ public static final String NESTING_SEPARATOR = ".";
+
+ @Test
+ public void testAttributeNameWithNoNestedAttributes() {
+ final String keyName = "fieldKey";
+ NestedAttributeName attributeName = NestedAttributeName.builder().elements(keyName).build();
+ ProjectionExpressionConvertor expressionConvertor = ProjectionExpressionConvertor.create(Arrays.asList(attributeName));
+ final Map stringStringMap = expressionConvertor.convertToExpressionMap();
+ final Optional toNameExpression = expressionConvertor.convertToProjectionExpression();
+ Map expectedmap = new HashMap<>();
+ expectedmap.put(MAPPED_INDICATOR + keyName, keyName);
+ assertThat(stringStringMap).isEqualTo(expectedmap);
+ assertThat(toNameExpression.get()).contains((MAPPED_INDICATOR + keyName));
+ }
+
+ @Test
+ public void testAttributeNameWithNestedNestedAttributes() {
+ final String keyName = "fieldKey";
+ final String nestedAttribute = "levelOne";
+ NestedAttributeName attributeName = NestedAttributeName.builder().addElements(keyName, nestedAttribute).build();
+ ProjectionExpressionConvertor expressionConvertor = ProjectionExpressionConvertor.create(Arrays.asList(attributeName));
+ final Map stringStringMap = expressionConvertor.convertToExpressionMap();
+ final Optional toNameExpression = expressionConvertor.convertToProjectionExpression();
+ Map expectedmap = new HashMap<>();
+ expectedmap.put(MAPPED_INDICATOR + keyName, keyName);
+ expectedmap.put(MAPPED_INDICATOR + nestedAttribute, nestedAttribute);
+ assertThat(stringStringMap).isEqualTo(expectedmap);
+ assertThat(toNameExpression.get()).contains(MAPPED_INDICATOR + keyName + NESTING_SEPARATOR + MAPPED_INDICATOR + nestedAttribute);
+ }
+
+ @Test
+ public void testAttributeNameWithNullAttributeName() {
+ assertFails(() -> NestedAttributeName.builder().addElement(null).build());
+
+ }
+
+ @Test
+ public void testAttributeNameWithNullElementsForNestingElement() {
+ assertFails(() -> NestedAttributeName.builder()
+ .elements("foo").addElement(null).build());
+ }
+
+ @Test
+ public void toBuilder() {
+ NestedAttributeName builtObject = NestedAttributeName.builder().addElement("foo").build();
+ NestedAttributeName copiedObject = builtObject.toBuilder().build();
+ assertThat(copiedObject).isEqualTo(builtObject);
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/BasicQueryTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/BasicQueryTest.java
index b60efcdc870f..b62bad8d9975 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/BasicQueryTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/BasicQueryTest.java
@@ -15,6 +15,7 @@
package software.amazon.awssdk.enhanced.dynamodb.functionaltests;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
@@ -27,23 +28,17 @@
import static software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional.keyEqualTo;
import static software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional.sortBetween;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import software.amazon.awssdk.core.pagination.sync.SdkIterable;
-import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
-import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
-import software.amazon.awssdk.enhanced.dynamodb.Expression;
-import software.amazon.awssdk.enhanced.dynamodb.Key;
-import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.*;
+import software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName;
+import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.InnerAttributeRecord;
+import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.NestedTestRecord;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable;
import software.amazon.awssdk.enhanced.dynamodb.model.Page;
@@ -52,6 +47,7 @@
import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
public class BasicQueryTest extends LocalDynamoDbSyncTestBase {
+
private static class Record {
private String id;
private Integer sort;
@@ -121,19 +117,44 @@ public int hashCode() {
.mapToObj(i -> new Record().setId("id-value").setSort(i).setValue(i))
.collect(Collectors.toList());
+ private static final List NESTED_TEST_RECORDS =
+ IntStream.range(0, 10)
+ .mapToObj(i -> {
+ final NestedTestRecord nestedTestRecord = new NestedTestRecord();
+ nestedTestRecord.setOuterAttribOne("id-value-" + i);
+ nestedTestRecord.setSort(i);
+ final InnerAttributeRecord innerAttributeRecord = new InnerAttributeRecord();
+ innerAttributeRecord.setAttribOne("attribOne-"+i);
+ innerAttributeRecord.setAttribTwo(i);
+ nestedTestRecord.setInnerAttributeRecord(innerAttributeRecord);
+ nestedTestRecord.setDotVariable("v"+i);
+ return nestedTestRecord;
+ })
+ .collect(Collectors.toList());
+
private DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
.build();
private DynamoDbTable mappedTable = enhancedClient.table(getConcreteTableName("table-name"), TABLE_SCHEMA);
+ private DynamoDbTable mappedNestedTable = enhancedClient.table(getConcreteTableName("nested-table-name"),
+ TableSchema.fromClass(NestedTestRecord.class));
+
private void insertRecords() {
RECORDS.forEach(record -> mappedTable.putItem(r -> r.item(record)));
+ NESTED_TEST_RECORDS.forEach(nestedTestRecord -> mappedNestedTable.putItem(r -> r.item(nestedTestRecord)));
+ }
+
+ private void insertNestedRecords() {
+ NESTED_TEST_RECORDS.forEach(nestedTestRecord -> mappedNestedTable.putItem(r -> r.item(nestedTestRecord)));
}
@Before
public void createTable() {
mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+ mappedNestedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+
}
@After
@@ -141,6 +162,9 @@ public void deleteTable() {
getDynamoDbClient().deleteTable(DeleteTableRequest.builder()
.tableName(getConcreteTableName("table-name"))
.build());
+ getDynamoDbClient().deleteTable(DeleteTableRequest.builder()
+ .tableName(getConcreteTableName("nested-table-name"))
+ .build());
}
@Test
@@ -352,4 +376,192 @@ public void queryExclusiveStartKey_viaItems() {
assertThat(results.stream().collect(Collectors.toList()), is(RECORDS.subList(8, 10)));
}
+
+ @Test
+ public void queryNestedRecord_SingleAttributeName() {
+ insertNestedRecords();
+ Iterator> results =
+ mappedNestedTable.query(b -> b
+ .queryConditional(keyEqualTo(k -> k.partitionValue("id-value-1")))
+ .addNestedAttributeToProject(NestedAttributeName.builder().addElement("innerAttributeRecord")
+ .addElement("attribOne").build())).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(1));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is(nullValue()));
+ assertThat(firstRecord.getSort(), is(nullValue()));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribOne(), is("attribOne-1"));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(nullValue()));
+ results =
+ mappedNestedTable.query(b -> b
+ .queryConditional(keyEqualTo(k -> k.partitionValue("id-value-1")))
+ .addNestedAttributeToProject(NestedAttributeName.create("sort"))
+ .addAttributeToProject("sort")).iterator();
+ assertThat(results.hasNext(), is(true));
+ page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(1));
+ firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is(nullValue()));
+ assertThat(firstRecord.getSort(), is(1));
+ assertThat(firstRecord.getInnerAttributeRecord(), is(nullValue()));
+ }
+
+
+ @Test
+ public void queryNestedRecord_withAttributeNameList() {
+ insertNestedRecords();
+ Iterator> results =
+ mappedNestedTable.query(b -> b
+ .queryConditional(keyEqualTo(k -> k.partitionValue("id-value-1")))
+ .addNestedAttributesToProject(Arrays.asList(
+ NestedAttributeName.builder().elements("innerAttributeRecord", "attribOne").build(),
+ NestedAttributeName.builder().addElement("outerAttribOne").build()))
+ .addNestedAttributesToProject(NestedAttributeName.builder()
+ .addElements(Arrays.asList("innerAttributeRecord","attribTwo")).build())).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(1));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is("id-value-1"));
+ assertThat(firstRecord.getSort(), is(nullValue()));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribOne(), is("attribOne-1"));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(1));
+ }
+
+
+
+
+ @Test
+ public void queryNestedRecord_withAttributeNameListAndStringAttributeToProjectAppended() {
+ insertNestedRecords();
+ Iterator> results =
+ mappedNestedTable.query(b -> b
+ .queryConditional(keyEqualTo(k -> k.partitionValue("id-value-1")))
+ .addNestedAttributesToProject(Arrays.asList(
+ NestedAttributeName.builder().elements("innerAttributeRecord","attribOne").build()))
+ .addNestedAttributesToProject(NestedAttributeName.create("innerAttributeRecord","attribTwo"))
+ .addAttributeToProject("sort")).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(1));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is(is(nullValue())));
+ assertThat(firstRecord.getSort(), is(1));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribOne(), is("attribOne-1"));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(1));
+ }
+
+ @Test
+ public void queryAllRecordsDefaultSettings_withNestedProjectionNamesNotInNameMap() {
+ insertNestedRecords();
+
+ Iterator> results =
+ mappedNestedTable.query(b -> b
+ .queryConditional(keyEqualTo(k -> k.partitionValue("id-value-1")))
+ .addNestedAttributeToProject( NestedAttributeName.builder().addElement("nonExistentSlot").build())).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(1));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord, is(nullValue()));
+ }
+ @Test
+ public void queryRecordDefaultSettings_withDotInTheName() {
+ insertNestedRecords();
+ Iterator> results =
+ mappedNestedTable.query(b -> b
+ .queryConditional(keyEqualTo(k -> k.partitionValue("id-value-7")))
+ .addNestedAttributeToProject( NestedAttributeName.create("test.com"))).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(1));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is(is(nullValue())));
+ assertThat(firstRecord.getSort(), is(is(nullValue())));
+ assertThat(firstRecord.getInnerAttributeRecord() , is(nullValue()));
+ assertThat(firstRecord.getDotVariable(), is("v7"));
+ Iterator> resultWithAttributeToProject =
+ mappedNestedTable.query(b -> b
+ .queryConditional(keyEqualTo(k -> k.partitionValue("id-value-7")))
+ .attributesToProject( "test.com").build()).iterator();
+ assertThat(resultWithAttributeToProject.hasNext(), is(true));
+ Page pageResult = resultWithAttributeToProject.next();
+ assertThat(resultWithAttributeToProject.hasNext(), is(false));
+ assertThat(pageResult.items().size(), is(1));
+ NestedTestRecord record = pageResult.items().get(0);
+ assertThat(record.getOuterAttribOne(), is(is(nullValue())));
+ assertThat(record.getSort(), is(is(nullValue())));
+ assertThat(firstRecord.getInnerAttributeRecord() , is(nullValue()));
+ assertThat(record.getDotVariable(), is("v7"));
+ }
+
+ @Test
+ public void queryRecordDefaultSettings_withEmptyAttributeList() {
+ insertNestedRecords();
+ Iterator> results =
+ mappedNestedTable.query(b -> b
+ .queryConditional(keyEqualTo(k -> k.partitionValue("id-value-7")))
+ .attributesToProject(new ArrayList<>()).build()).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(1));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is("id-value-7"));
+ assertThat(firstRecord.getSort(), is(7));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(7));
+ assertThat(firstRecord.getDotVariable(), is("v7"));
+ }
+
+ @Test
+ public void queryRecordDefaultSettings_withNullAttributeList() {
+ insertNestedRecords();
+
+ List backwardCompatibilty = null;
+
+ Iterator> results =
+ mappedNestedTable.query(b -> b
+ .queryConditional(keyEqualTo(k -> k.partitionValue("id-value-7")))
+ .attributesToProject(backwardCompatibilty).build()).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(1));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is("id-value-7"));
+ assertThat(firstRecord.getSort(), is(7));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(7));
+ assertThat(firstRecord.getDotVariable(), is("v7"));
+ }
+
+ @Test
+ public void queryAllRecordsDefaultSettings_withNestedProjectionNameEmptyNameMap() {
+ insertNestedRecords();
+
+ assertThatExceptionOfType(Exception.class).isThrownBy(
+ () -> {
+ Iterator> results = mappedNestedTable.query(b -> b.queryConditional(
+ keyEqualTo(k -> k.partitionValue("id-value-3")))
+ .attributesToProject("").build()).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ });
+
+ assertThatExceptionOfType(Exception.class).isThrownBy(
+ () -> {
+ Iterator> results = mappedNestedTable.query(b -> b.queryConditional(
+ keyEqualTo(k -> k.partitionValue("id-value-3")))
+ .addNestedAttributeToProject(NestedAttributeName.create("")).build()).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+
+ });
+ }
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/BasicScanTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/BasicScanTest.java
index caa6e855ed18..b11666510d3c 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/BasicScanTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/BasicScanTest.java
@@ -15,32 +15,24 @@
package software.amazon.awssdk.enhanced.dynamodb.functionaltests;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.*;
import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.numberValue;
import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue;
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import software.amazon.awssdk.core.pagination.sync.SdkIterable;
-import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
-import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
-import software.amazon.awssdk.enhanced.dynamodb.Expression;
-import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.*;
+import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.InnerAttributeRecord;
+import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.NestedTestRecord;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
import software.amazon.awssdk.enhanced.dynamodb.model.Page;
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest;
@@ -103,19 +95,45 @@ public int hashCode() {
.mapToObj(i -> new Record().setId("id-value").setSort(i))
.collect(Collectors.toList());
+ private static final List NESTED_TEST_RECORDS =
+ IntStream.range(0, 10)
+ .mapToObj(i -> {
+ final NestedTestRecord nestedTestRecord = new NestedTestRecord();
+ nestedTestRecord.setOuterAttribOne("id-value-" + i);
+ nestedTestRecord.setSort(i);
+ final InnerAttributeRecord innerAttributeRecord = new InnerAttributeRecord();
+ innerAttributeRecord.setAttribOne("attribOne-"+i);
+ innerAttributeRecord.setAttribTwo(i);
+ nestedTestRecord.setInnerAttributeRecord(innerAttributeRecord);
+ nestedTestRecord.setDotVariable("v"+i);
+ return nestedTestRecord;
+ })
+ .collect(Collectors.toList());
+
private DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(getDynamoDbClient())
.build();
private DynamoDbTable mappedTable = enhancedClient.table(getConcreteTableName("table-name"), TABLE_SCHEMA);
+ private DynamoDbTable mappedNestedTable = enhancedClient.table(getConcreteTableName("nested-table-name"),
+ TableSchema.fromClass(NestedTestRecord.class));
+
+
private void insertRecords() {
RECORDS.forEach(record -> mappedTable.putItem(r -> r.item(record)));
}
+ private void insertNestedRecords() {
+ NESTED_TEST_RECORDS.forEach(nestedTestRecord -> mappedNestedTable.putItem(r -> r.item(nestedTestRecord)));
+ }
+
+
@Before
public void createTable() {
mappedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+ mappedNestedTable.createTable(r -> r.provisionedThroughput(getDefaultProvisionedThroughput()));
+
}
@After
@@ -123,6 +141,9 @@ public void deleteTable() {
getDynamoDbClient().deleteTable(DeleteTableRequest.builder()
.tableName(getConcreteTableName("table-name"))
.build());
+ getDynamoDbClient().deleteTable(DeleteTableRequest.builder()
+ .tableName(getConcreteTableName("nested-table-name"))
+ .build());
}
@Test
@@ -292,4 +313,322 @@ private Map getKeyMap(int sort) {
result.put("sort", numberValue(sort));
return Collections.unmodifiableMap(result);
}
+ @Test
+ public void scanAllRecordsWithFilterAndNestedProjectionSingleAttribute() {
+ insertNestedRecords();
+ Map expressionValues = new HashMap<>();
+ expressionValues.put(":min_value", numberValue(3));
+ expressionValues.put(":max_value", numberValue(5));
+ Expression expression = Expression.builder()
+ .expression("#sort >= :min_value AND #sort <= :max_value")
+ .expressionValues(expressionValues)
+ .putExpressionName("#sort", "sort")
+ .build();
+
+ Iterator> results =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addNestedAttributesToProject(
+ NestedAttributeName.create(Arrays.asList("innerAttributeRecord","attribOne")))
+ .build()
+ ).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(3));
+ Collections.sort(page.items(), (item1, item2) ->
+ item1.getInnerAttributeRecord().getAttribOne()
+ .compareTo(item2.getInnerAttributeRecord().getAttribOne()));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is(nullValue()));
+ assertThat(firstRecord.getSort(), is(nullValue()));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribOne(), is("attribOne-3"));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(nullValue()));
+
+ //Attribute repeated with new and old attributeToProject
+ results =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addNestedAttributesToProject(NestedAttributeName.create("sort"))
+ .addAttributeToProject("sort")
+ .build()
+ ).iterator();
+ assertThat(results.hasNext(), is(true));
+ page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(3));
+ Collections.sort(page.items(), (item1, item2) ->
+ item1.getSort()
+ .compareTo(item2.getSort()));
+ firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is(nullValue()));
+ assertThat(firstRecord.getSort(), is(3));
+ assertThat(firstRecord.getInnerAttributeRecord(), is(nullValue()));
+ assertThat(firstRecord.getInnerAttributeRecord(), is(nullValue()));
+
+ results =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addNestedAttributeToProject(
+ NestedAttributeName.create(Arrays.asList("innerAttributeRecord","attribOne")))
+ .build()
+ ).iterator();
+ assertThat(results.hasNext(), is(true));
+ page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(3));
+ Collections.sort(page.items(), (item1, item2) ->
+ item1.getInnerAttributeRecord().getAttribOne()
+ .compareTo(item2.getInnerAttributeRecord().getAttribOne()));
+ firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is(nullValue()));
+ assertThat(firstRecord.getSort(), is(nullValue()));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribOne(), is("attribOne-3"));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(nullValue()));
+ }
+
+ @Test
+ public void scanAllRecordsWithFilterAndNestedProjectionMultipleAttribute() {
+ insertNestedRecords();
+ Map expressionValues = new HashMap<>();
+ expressionValues.put(":min_value", numberValue(3));
+ expressionValues.put(":max_value", numberValue(5));
+ Expression expression = Expression.builder()
+ .expression("#sort >= :min_value AND #sort <= :max_value")
+ .expressionValues(expressionValues)
+ .putExpressionName("#sort", "sort")
+ .build();
+
+ final ScanEnhancedRequest build = ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addAttributeToProject("outerAttribOne")
+ .addNestedAttributesToProject(Arrays.asList(NestedAttributeName.builder().elements("innerAttributeRecord")
+ .addElement("attribOne").build()))
+ .addNestedAttributeToProject(NestedAttributeName.builder()
+ .elements(Arrays.asList("innerAttributeRecord", "attribTwo")).build())
+ .build();
+ Iterator> results =
+ mappedNestedTable.scan(
+ build
+ ).iterator();
+
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(3));
+ Collections.sort(page.items(), (item1, item2) ->
+ item1.getInnerAttributeRecord().getAttribOne()
+ .compareTo(item2.getInnerAttributeRecord().getAttribOne()));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is("id-value-3"));
+ assertThat(firstRecord.getSort(), is(nullValue()));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribOne(), is("attribOne-3"));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(3));
+
+ }
+
+ @Test
+ public void scanAllRecordsWithNonExistigKeyName() {
+ insertNestedRecords();
+ Map expressionValues = new HashMap<>();
+ expressionValues.put(":min_value", numberValue(3));
+ expressionValues.put(":max_value", numberValue(5));
+ Expression expression = Expression.builder()
+ .expression("#sort >= :min_value AND #sort <= :max_value")
+ .expressionValues(expressionValues)
+ .putExpressionName("#sort", "sort")
+ .build();
+
+
+ Iterator> results =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addNestedAttributesToProject(NestedAttributeName.builder().addElement("nonExistent").build())
+ .build()
+ ).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(3));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord, is(nullValue()));
+ }
+
+ @Test
+ public void scanAllRecordsWithDotInAttributeKeyName() {
+ insertNestedRecords();
+ Map expressionValues = new HashMap<>();
+ expressionValues.put(":min_value", numberValue(3));
+ expressionValues.put(":max_value", numberValue(5));
+ Expression expression = Expression.builder()
+ .expression("#sort >= :min_value AND #sort <= :max_value")
+ .expressionValues(expressionValues)
+ .putExpressionName("#sort", "sort")
+ .build();
+
+ Iterator> results =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addNestedAttributesToProject(NestedAttributeName
+ .create("test.com")).build()
+ ).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(3));
+ Collections.sort(page.items(), (item1, item2) ->
+ item1.getDotVariable()
+ .compareTo(item2.getDotVariable()));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is(nullValue()));
+ assertThat(firstRecord.getSort(), is(nullValue()));
+ assertThat(firstRecord.getDotVariable(), is("v3"));
+ assertThat(firstRecord.getInnerAttributeRecord(), is(nullValue()));
+ assertThat(firstRecord.getInnerAttributeRecord(), is(nullValue()));
+ }
+
+ @Test
+ public void scanAllRecordsWithSameNamesRepeated() {
+ //Attribute repeated with new and old attributeToProject
+ insertNestedRecords();
+ Map expressionValues = new HashMap<>();
+ expressionValues.put(":min_value", numberValue(3));
+ expressionValues.put(":max_value", numberValue(5));
+ Expression expression = Expression.builder()
+ .expression("#sort >= :min_value AND #sort <= :max_value")
+ .expressionValues(expressionValues)
+ .putExpressionName("#sort", "sort")
+ .build();
+
+ Iterator >results =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addNestedAttributesToProject(NestedAttributeName.builder().elements("sort").build())
+ .addAttributeToProject("sort")
+ .build()
+ ).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(3));
+ Collections.sort(page.items(), (item1, item2) ->
+ item1.getSort()
+ .compareTo(item2.getSort()));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is(nullValue()));
+ assertThat(firstRecord.getSort(), is(3));
+ assertThat(firstRecord.getInnerAttributeRecord(), is(nullValue()));
+ assertThat(firstRecord.getInnerAttributeRecord(), is(nullValue()));
+ }
+
+ @Test
+ public void scanAllRecordsWithEmptyList() {
+ //Attribute repeated with new and old attributeToProject
+ insertNestedRecords();
+ Map expressionValues = new HashMap<>();
+ expressionValues.put(":min_value", numberValue(3));
+ expressionValues.put(":max_value", numberValue(5));
+ Expression expression = Expression.builder()
+ .expression("#sort >= :min_value AND #sort <= :max_value")
+ .expressionValues(expressionValues)
+ .putExpressionName("#sort", "sort")
+ .build();
+
+ Iterator >results =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addNestedAttributesToProject(new ArrayList<>())
+ .build()
+ ).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(3));
+ Collections.sort(page.items(), (item1, item2) ->
+ item1.getSort()
+ .compareTo(item2.getSort()));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is("id-value-3"));
+ assertThat(firstRecord.getSort(), is(3));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(3));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribOne(), is("attribOne-3"));
+ }
+
+ @Test
+ public void scanAllRecordsWithNullAttributesToProject() {
+ //Attribute repeated with new and old attributeToProject
+ insertNestedRecords();
+ List backwardCompatibilityNull = null;
+ Map expressionValues = new HashMap<>();
+ expressionValues.put(":min_value", numberValue(3));
+ expressionValues.put(":max_value", numberValue(5));
+ Expression expression = Expression.builder()
+ .expression("#sort >= :min_value AND #sort <= :max_value")
+ .expressionValues(expressionValues)
+ .putExpressionName("#sort", "sort")
+ .build();
+
+ Iterator >results =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .attributesToProject("test.com")
+ .attributesToProject(backwardCompatibilityNull)
+ .build()
+ ).iterator();
+ assertThat(results.hasNext(), is(true));
+ Page page = results.next();
+ assertThat(results.hasNext(), is(false));
+ assertThat(page.items().size(), is(3));
+ Collections.sort(page.items(), (item1, item2) ->
+ item1.getSort()
+ .compareTo(item2.getSort()));
+ NestedTestRecord firstRecord = page.items().get(0);
+ assertThat(firstRecord.getOuterAttribOne(), is("id-value-3"));
+ assertThat(firstRecord.getSort(), is(3));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribTwo(), is(3));
+ assertThat(firstRecord.getInnerAttributeRecord().getAttribOne(), is("attribOne-3"));
+ }
+
+ @Test
+ public void scanAllRecordsWithNestedProjectionNameEmptyNameMap() {
+ insertNestedRecords();
+ Map expressionValues = new HashMap<>();
+ expressionValues.put(":min_value", numberValue(3));
+ expressionValues.put(":max_value", numberValue(5));
+ Expression expression = Expression.builder()
+ .expression("#sort >= :min_value AND #sort <= :max_value")
+ .expressionValues(expressionValues)
+ .putExpressionName("#sort", "sort")
+ .build();
+
+ final Iterator> results =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addNestedAttributesToProject(NestedAttributeName.builder().elements("").build()).build()
+ ).iterator();
+
+ assertThatExceptionOfType(Exception.class).isThrownBy(() -> { final boolean b = results.hasNext();
+ Page next = results.next(); }).withMessageContaining("ExpressionAttributeNames contains invalid value");
+
+ final Iterator> resultsAttributeToProject =
+ mappedNestedTable.scan(
+ ScanEnhancedRequest.builder()
+ .filterExpression(expression)
+ .addAttributeToProject("").build()
+ ).iterator();
+
+ assertThatExceptionOfType(Exception.class).isThrownBy(() -> {
+ final boolean b = resultsAttributeToProject.hasNext();
+ Page next = resultsAttributeToProject.next();
+ });
+ }
}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/InnerAttribConverter.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/InnerAttribConverter.java
new file mode 100755
index 000000000000..a006e0f4bf0f
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/InnerAttribConverter.java
@@ -0,0 +1,81 @@
+package software.amazon.awssdk.enhanced.dynamodb.functionaltests.models;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType;
+import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue;
+
+/**
+ * Event Payload Converter to save the record on the class
+ */
+public class InnerAttribConverter implements AttributeConverter {
+
+ private final ObjectMapper objectMapper;
+
+ /**
+ * This No Args constuctor is needed by the DynamoDbConvertedBy annotation
+ */
+ public InnerAttribConverter() {
+ this.objectMapper = new ObjectMapper();
+ this.objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+ final AttributeValue dd = stringValue("dd");
+ AttributeConverter attributeConverter = null;
+ AttributeValueType attributeValueType = null;
+ EnhancedType enhancedType = null;
+ // add this to preserve the same offset (don't convert to UTC)
+ this.objectMapper.configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false);
+ }
+
+ @Override
+ public AttributeValue transformFrom(final T input) {
+
+ Map map = null;
+ if (input != null) {
+ map = new HashMap<>();
+ InnerAttributeRecord innerAttributeRecord = (InnerAttributeRecord) input;
+ if (innerAttributeRecord.getAttribOne() != null) {
+
+ final AttributeValue attributeValue = stringValue(innerAttributeRecord.getAttribOne());
+ map.put("attribOne", stringValue(innerAttributeRecord.getAttribOne()));
+ }
+ if (innerAttributeRecord.getAttribTwo() != null) {
+ map.put("attribTwo", stringValue(String.valueOf(innerAttributeRecord.getAttribTwo())));
+ }
+ }
+ return AttributeValue.builder().m(map).build();
+
+ }
+
+ @Override
+ public T transformTo(final AttributeValue attributeValue) {
+ InnerAttributeRecord innerMetadata = new InnerAttributeRecord();
+ if (attributeValue.m().get("attribOne") != null) {
+ innerMetadata.setAttribOne(attributeValue.m().get("attribOne").s());
+ }
+ if (attributeValue.m().get("attribTwo") != null) {
+ innerMetadata.setAttribTwo(Integer.valueOf(attributeValue.m().get("attribTwo").s()));
+ }
+ return (T) innerMetadata;
+ }
+
+ @Override
+ public EnhancedType type() {
+ return (EnhancedType) EnhancedType.of(InnerAttributeRecord.class);
+ }
+
+ @Override
+ public AttributeValueType attributeValueType() {
+ return AttributeValueType.S;
+ }
+
+
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/InnerAttribConverterProvider.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/InnerAttribConverterProvider.java
new file mode 100755
index 000000000000..b38cc7554a37
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/InnerAttribConverterProvider.java
@@ -0,0 +1,18 @@
+package software.amazon.awssdk.enhanced.dynamodb.functionaltests.models;
+
+
+import software.amazon.awssdk.enhanced.dynamodb.AttributeConverter;
+import software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider;
+import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
+
+/**
+ * InnerAttribConverterProvider to save the InnerAttribConverter on the class.
+ */
+public class InnerAttribConverterProvider implements AttributeConverterProvider {
+
+
+ @Override
+ public AttributeConverter converterFor(EnhancedType enhancedType) {
+ return new InnerAttribConverter();
+ }
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/InnerAttributeRecord.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/InnerAttributeRecord.java
new file mode 100755
index 000000000000..6edd5112ff94
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/InnerAttributeRecord.java
@@ -0,0 +1,34 @@
+package software.amazon.awssdk.enhanced.dynamodb.functionaltests.models;
+
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
+
+
+public class InnerAttributeRecord {
+ private String attribOne;
+ private Integer attribTwo;
+
+ @DynamoDbPartitionKey
+ public String getAttribOne() {
+ return attribOne;
+ }
+
+ public void setAttribOne(String attribOne) {
+ this.attribOne = attribOne;
+ }
+
+ public Integer getAttribTwo() {
+ return attribTwo;
+ }
+
+ public void setAttribTwo(Integer attribTwo) {
+ this.attribTwo = attribTwo;
+ }
+
+ @Override
+ public String toString() {
+ return "InnerAttributeRecord{" +
+ "attribOne='" + attribOne + '\'' +
+ ", attribTwo=" + attribTwo +
+ '}';
+ }
+}
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedTestRecord.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedTestRecord.java
new file mode 100755
index 000000000000..b1122efbc0f0
--- /dev/null
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/models/NestedTestRecord.java
@@ -0,0 +1,62 @@
+package software.amazon.awssdk.enhanced.dynamodb.functionaltests.models;
+
+import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*;
+
+
+@DynamoDbBean
+public class NestedTestRecord {
+ private String outerAttribOne;
+ private Integer sort;
+ private InnerAttributeRecord innerAttributeRecord;
+
+ private String dotVariable;
+
+
+ @DynamoDbPartitionKey
+ public String getOuterAttribOne() {
+ return outerAttribOne;
+ }
+
+ public void setOuterAttribOne(String outerAttribOne) {
+ this.outerAttribOne = outerAttribOne;
+ }
+
+ @DynamoDbSortKey
+ public Integer getSort() {
+ return sort;
+ }
+
+ public void setSort(Integer sort) {
+ this.sort = sort;
+ }
+
+
+
+ @DynamoDbConvertedBy(InnerAttribConverter.class)
+ public InnerAttributeRecord getInnerAttributeRecord() {
+ return innerAttributeRecord;
+ }
+
+ public void setInnerAttributeRecord(InnerAttributeRecord innerAttributeRecord) {
+ this.innerAttributeRecord = innerAttributeRecord;
+ }
+
+ @DynamoDbAttribute("test.com")
+ public String getDotVariable() {
+ return dotVariable;
+ }
+
+ public void setDotVariable(String dotVariable) {
+ this.dotVariable = dotVariable;
+ }
+
+ @Override
+ public String toString() {
+ return "NestedTestRecord{" +
+ "outerAttribOne='" + outerAttribOne + '\'' +
+ ", sort=" + sort +
+ ", innerAttributeRecord=" + innerAttributeRecord +
+ ", dotVariable='" + dotVariable + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequestTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequestTest.java
index 2b146cf3f194..827113e1f72b 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequestTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequestTest.java
@@ -15,25 +15,24 @@
package software.amazon.awssdk.enhanced.dynamodb.model;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+import software.amazon.awssdk.enhanced.dynamodb.Expression;
+import software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+
+import java.util.*;
+
import static java.util.Collections.singletonMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
+import static software.amazon.awssdk.enhanced.dynamodb.converters.attribute.ConverterTestUtils.assertFails;
import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.numberValue;
import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue;
import static software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional.keyEqualTo;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
-import software.amazon.awssdk.enhanced.dynamodb.Expression;
-import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
-
@RunWith(MockitoJUnitRunner.class)
public class QueryEnhancedRequestTest {
@@ -58,9 +57,9 @@ public void builder_maximal() {
Map expressionValues = singletonMap(":test-key", stringValue("test-value"));
Expression filterExpression = Expression.builder()
- .expression("test-expression")
- .expressionValues(expressionValues)
- .build();
+ .expression("test-expression")
+ .expressionValues(expressionValues)
+ .build();
QueryConditional queryConditional = keyEqualTo(k -> k.partitionValue("id-value"));
@@ -70,15 +69,15 @@ public void builder_maximal() {
attributesToProject.add(additionalElement);
QueryEnhancedRequest builtObject = QueryEnhancedRequest.builder()
- .exclusiveStartKey(exclusiveStartKey)
- .consistentRead(false)
- .filterExpression(filterExpression)
- .limit(3)
- .queryConditional(queryConditional)
- .scanIndexForward(true)
- .attributesToProject(attributesToProjectArray)
- .addAttributeToProject(additionalElement)
- .build();
+ .exclusiveStartKey(exclusiveStartKey)
+ .consistentRead(false)
+ .filterExpression(filterExpression)
+ .limit(3)
+ .queryConditional(queryConditional)
+ .scanIndexForward(true)
+ .attributesToProject(attributesToProjectArray)
+ .addAttributeToProject(additionalElement)
+ .build();
assertThat(builtObject.exclusiveStartKey(), is(exclusiveStartKey));
assertThat(builtObject.consistentRead(), is(false));
@@ -89,6 +88,95 @@ public void builder_maximal() {
assertThat(builtObject.attributesToProject(), is(attributesToProject));
}
+
+ @Test
+ public void test_withNestedAttributeAddedFirstAndThenAttributesToProject() {
+
+ String[] attributesToProjectArray = {"one", "two"};
+ String additionalElement = "three";
+ QueryEnhancedRequest builtObject = QueryEnhancedRequest.builder()
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"))
+ .attributesToProject(attributesToProjectArray)
+ .addAttributeToProject(additionalElement)
+ .build();
+ List attributesToProject = Arrays.asList("one", "two", "three");
+ assertThat(builtObject.attributesToProject(), is(attributesToProject));
+ }
+
+
+ @Test
+ public void test_nestedAttributesToProjectWithNestedAttributeAddedLast() {
+
+ String[] attributesToProjectArray = {"one", "two"};
+ String additionalElement = "three";
+
+ QueryEnhancedRequest builtObjectOne = QueryEnhancedRequest.builder()
+ .attributesToProject(attributesToProjectArray)
+ .addAttributeToProject(additionalElement)
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"))
+ .build();
+ List attributesToProjectNestedLast = Arrays.asList("one", "two", "three", "foo.bar");
+ assertThat(builtObjectOne.attributesToProject(), is(attributesToProjectNestedLast));
+
+ }
+
+ @Test
+ public void test_nestedAttributesToProjectWithNestedAttributeAddedInBetween() {
+
+ String[] attributesToProjectArray = {"one", "two"};
+ String additionalElement = "three";
+
+ QueryEnhancedRequest builtObjectOne = QueryEnhancedRequest.builder()
+ .attributesToProject(attributesToProjectArray)
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"))
+ .addAttributeToProject(additionalElement)
+ .build();
+ List attributesToProjectNestedLast = Arrays.asList("one", "two", "foo.bar", "three");
+ assertThat(builtObjectOne.attributesToProject(), is(attributesToProjectNestedLast));
+
+ }
+
+ @Test
+ public void test_nestedAttributesToProjectOverwrite() {
+
+ String[] attributesToProjectArray = {"one", "two"};
+ String additionalElement = "three";
+ String[] overwrite = { "overwrite"};
+
+ QueryEnhancedRequest builtObjectTwo = QueryEnhancedRequest.builder()
+ .attributesToProject(attributesToProjectArray)
+ .addAttributeToProject(additionalElement)
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"))
+ .attributesToProject(overwrite)
+ .build();
+ assertThat(builtObjectTwo.attributesToProject(), is(Arrays.asList(overwrite)));
+ }
+
+ @Test
+ public void test_nestedAttributesNullNestedAttributeElement() {
+ List attributeNames = new ArrayList<>();
+ attributeNames.add(NestedAttributeName.create("foo"));
+ attributeNames.add(null);
+ assertFails(() -> QueryEnhancedRequest.builder()
+ .addNestedAttributesToProject(attributeNames)
+ .build());
+
+ assertFails(() -> QueryEnhancedRequest.builder()
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"), null)
+ .build());
+
+ NestedAttributeName nestedAttributeName = null;
+ QueryEnhancedRequest.builder()
+ .addNestedAttributeToProject(nestedAttributeName)
+ .build();
+ assertFails(() -> QueryEnhancedRequest.builder()
+ .addNestedAttributesToProject(nestedAttributeName)
+ .build());
+ }
+
+
+
+
@Test
public void toBuilder() {
QueryEnhancedRequest builtObject = QueryEnhancedRequest.builder().build();
diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequestTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequestTest.java
index ce2a86f443c6..04d77c7d6090 100644
--- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequestTest.java
+++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequestTest.java
@@ -19,6 +19,8 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
+import static software.amazon.awssdk.enhanced.dynamodb.converters.attribute.ConverterTestUtils.assertFails;
+import static software.amazon.awssdk.enhanced.dynamodb.converters.attribute.ConverterTestUtils.transformTo;
import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.numberValue;
import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue;
@@ -31,6 +33,8 @@
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import software.amazon.awssdk.enhanced.dynamodb.Expression;
+import software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName;
+import software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnhancedAttributeValue;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
@RunWith(MockitoJUnitRunner.class)
@@ -80,6 +84,108 @@ public void builder_maximal() {
assertThat(builtObject.limit(), is(3));
}
+ @Test
+ public void test_withNestedAttributeAddedFirst() {
+
+ String[] attributesToProjectArray = {"one", "two"};
+ String additionalElement = "three";
+ ScanEnhancedRequest builtObject = ScanEnhancedRequest.builder()
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"))
+ .attributesToProject(attributesToProjectArray)
+ .addAttributeToProject(additionalElement)
+ .build();
+ List attributesToProject = Arrays.asList("one", "two", "three");
+ assertThat(builtObject.attributesToProject(), is(attributesToProject));
+ }
+
+
+ @Test
+ public void test_nestedAttributesToProjectWithNestedAttributeAddedLast() {
+
+ String[] attributesToProjectArray = {"one", "two"};
+ String additionalElement = "three";
+
+ ScanEnhancedRequest builtObjectOne = ScanEnhancedRequest.builder()
+ .attributesToProject(attributesToProjectArray)
+ .addAttributeToProject(additionalElement)
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"))
+ .build();
+ List attributesToProjectNestedLast = Arrays.asList("one", "two", "three", "foo.bar");
+ assertThat(builtObjectOne.attributesToProject(), is(attributesToProjectNestedLast));
+
+ }
+
+ @Test
+ public void test_nestedAttributesToProjectWithNestedAttributeAddedInBetween() {
+
+ String[] attributesToProjectArray = {"one", "two"};
+ String additionalElement = "three";
+
+ ScanEnhancedRequest builtObjectOne = ScanEnhancedRequest.builder()
+ .attributesToProject(attributesToProjectArray)
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"))
+ .addAttributeToProject(additionalElement)
+ .build();
+ List attributesToProjectNestedLast = Arrays.asList("one", "two", "foo.bar", "three");
+ assertThat(builtObjectOne.attributesToProject(), is(attributesToProjectNestedLast));
+
+ }
+ @Test
+ public void test_nestedAttributesToProjectOverwrite() {
+
+ String[] attributesToProjectArray = {"one", "two"};
+ String additionalElement = "three";
+ String[] overwrite = { "overwrite"};
+
+ ScanEnhancedRequest builtObjectTwo = ScanEnhancedRequest.builder()
+ .attributesToProject(attributesToProjectArray)
+ .addAttributeToProject(additionalElement)
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"))
+ .attributesToProject(overwrite)
+ .build();
+ assertThat(builtObjectTwo.attributesToProject(), is(Arrays.asList(overwrite)));
+ }
+
+ @Test
+ public void test_nestedAttributesNullStringElement() {
+
+ String[] attributesToProjectArray = {"one", "two", null};
+ String additionalElement = "three";
+ assertFails(() -> ScanEnhancedRequest.builder()
+ .attributesToProject(attributesToProjectArray)
+ .addAttributeToProject(additionalElement)
+ .addAttributeToProject(null)
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"))
+ .build());
+
+ assertFails(() -> ScanEnhancedRequest.builder()
+ .attributesToProject("foo", "bar", null)
+ .build());
+
+ }
+
+ @Test
+ public void test_nestedAttributesNullNestedAttributeElement() {
+ List attributeNames = new ArrayList<>();
+ attributeNames.add(NestedAttributeName.create("foo"));
+ attributeNames.add(null);
+ assertFails(() -> ScanEnhancedRequest.builder()
+ .addNestedAttributesToProject(attributeNames)
+ .build());
+ assertFails(() -> ScanEnhancedRequest.builder()
+ .addNestedAttributesToProject(NestedAttributeName.create("foo", "bar"), null)
+ .build());
+ NestedAttributeName nestedAttributeName = null;
+ ScanEnhancedRequest.builder()
+ .addNestedAttributeToProject(nestedAttributeName)
+ .build();
+ assertFails(() -> ScanEnhancedRequest.builder()
+ .addNestedAttributesToProject(nestedAttributeName)
+ .build());
+ }
+
+
+
@Test
public void toBuilder() {
ScanEnhancedRequest builtObject = ScanEnhancedRequest.builder().exclusiveStartKey(null).build();