Skip to content

Commit bc13f17

Browse files
committed
DDB Enhanced:Added support to read Nested objects in attributesToProject Enhanced Scan and Enhanced query requests
1 parent c532841 commit bc13f17

15 files changed

+1640
-132
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb;
17+
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.List;
22+
import software.amazon.awssdk.annotations.SdkPublicApi;
23+
import software.amazon.awssdk.utils.Validate;
24+
25+
/**
26+
* High-level representation of a DynamoDB 'NestedAttributeName' that can be used in various situations where the API requires
27+
* or accepts an Nested Attribute Name.
28+
* Simple Attribute Name can be represented by passing just the name of the attribute.
29+
* Nested Attributes are represented by List of String where each index of list corresponds to Nesting level Names.
30+
* <p> While using attributeToProject in {@link software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest}
31+
* and {@link software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest} we need way to represent Nested Attributes.
32+
* The normal DOT(.) separator is not recognized as a Nesting level separator by DynamoDB request,
33+
* thus we need to use NestedAttributeName
34+
* which can be used to represent Nested attributes.
35+
* <p> Example : NestedAttributeName.create("foo") corresponds to a NestedAttributeName with elements list
36+
* with single element foo which represents Simple attribute name "foo" without nesting.
37+
* <p>NestedAttributeName.create("foo", "bar") corresponds to a NestedAttributeName with elements list "foo", "bar"
38+
* respresenting nested attribute name "foo.bar".
39+
*/
40+
@SdkPublicApi
41+
public final class NestedAttributeName {
42+
43+
private final List<String> elements;
44+
45+
private NestedAttributeName(List<String> nestedAttributeNames) {
46+
Validate.validState(nestedAttributeNames != null, "nestedAttributeNames must not be null.");
47+
Validate.notEmpty(nestedAttributeNames, "nestedAttributeNames must not be empty");
48+
Validate.noNullElements(nestedAttributeNames, "nestedAttributeNames must not contain null values");
49+
this.elements = Collections.unmodifiableList(nestedAttributeNames);
50+
}
51+
52+
/**
53+
* Creates a NestedAttributeName with a single element, which is effectively just a simple attribute name without nesting.
54+
* <p>
55+
* <b>Example:</b>create("foo") will create NestedAttributeName corresponding to Attribute foo.
56+
*
57+
* @param element Attribute Name. Single String represents just a simple attribute name without nesting.
58+
* @return NestedAttributeName with attribute name as specified element.
59+
*/
60+
public static NestedAttributeName create(String element) {
61+
return new Builder().addElement(element).build();
62+
}
63+
64+
/**
65+
* Creates a NestedAttributeName from a list of elements that compose the full path of the nested attribute.
66+
* <p>
67+
* <b>Example:</b>create("foo", "bar") will create NestedAttributeName which represents foo.bar nested attribute.
68+
*
69+
* @param elements Nested Attribute Names. Each of strings in varargs represent the nested attribute name
70+
* at subsequent levels.
71+
* @return NestedAttributeName with Nested attribute name set as specified in elements var args.
72+
*/
73+
public static NestedAttributeName create(String... elements) {
74+
return new Builder().elements(elements).build();
75+
}
76+
77+
/**
78+
* Creates a NestedAttributeName from a list of elements that compose the full path of the nested attribute.
79+
* <p>
80+
* <b>Example:</b>create(Arrays.asList("foo", "bar")) will create NestedAttributeName
81+
* which represents foo.bar nested attribute.
82+
*
83+
* @param elements List of Nested Attribute Names. Each of strings in List represent the nested attribute name
84+
* at subsequent levels.
85+
* @return NestedAttributeName with Nested attribute name set as specified in elements Collections.
86+
*/
87+
public static NestedAttributeName create(List<String> elements) {
88+
return new Builder().elements(elements).build();
89+
}
90+
91+
/**
92+
* Create a builder that can be used to create a {@link NestedAttributeName}.
93+
*/
94+
public static Builder builder() {
95+
return new Builder();
96+
}
97+
98+
/**
99+
* Gets elements of NestedAttributeName in the form of List. Each element in the list corresponds
100+
* to the subsequent Nested Attribute name.
101+
*
102+
* @return List of nested attributes, each entry in the list represent one level of nesting.
103+
* Example, A Two level Attribute name foo.bar will be represented as ["foo", "bar"]
104+
*/
105+
public List<String> elements() {
106+
return elements;
107+
}
108+
109+
/**
110+
* Returns a builder initialized with all existing values on the request object.
111+
*/
112+
public Builder toBuilder() {
113+
return builder().elements(elements);
114+
}
115+
116+
@Override
117+
public boolean equals(Object o) {
118+
if (this == o) {
119+
return true;
120+
}
121+
if (o == null || getClass() != o.getClass()) {
122+
return false;
123+
}
124+
NestedAttributeName that = (NestedAttributeName) o;
125+
126+
return elements != null
127+
? elements.equals(that.elements) : that.elements == null;
128+
}
129+
130+
@Override
131+
public int hashCode() {
132+
return elements != null ? elements.hashCode() : 0;
133+
}
134+
135+
/**
136+
* A builder for {@link NestedAttributeName}.
137+
*/
138+
public static class Builder {
139+
private List<String> elements = null;
140+
141+
private Builder() {
142+
143+
}
144+
145+
/**
146+
* Adds a single element of NestedAttributeName.
147+
* Subsequent calls to this method can add attribute Names at subsequent nesting levels.
148+
* <p>
149+
* <b>Example:</b>builder().addElement("foo").addElement("bar") will add elements in NestedAttributeName
150+
* which represent a Nested Attribute Name foo.bar
151+
*
152+
* @param element Attribute Name.
153+
* @return Returns a reference to this object so that method calls can be chained together.
154+
*/
155+
public Builder addElement(String element) {
156+
if (elements == null) {
157+
elements = new ArrayList<>();
158+
}
159+
elements.add(element);
160+
return this;
161+
}
162+
163+
/**
164+
* Adds a single element of NestedAttributeName.
165+
* Subsequent calls to this method will append the new elements to the end of the existing chain of elements
166+
* creating new levels of nesting.
167+
* <p>
168+
* <b>Example:</b>builder().addElements("foo","bar") will add elements in NestedAttributeName
169+
* which represent a Nested Attribute Name foo.bar
170+
*
171+
* @param elements Nested Attribute Names. Each of strings in varargs represent the nested attribute name
172+
* at subsequent levels.
173+
* @return Returns a reference to this object so that method calls can be chained together.
174+
*/
175+
public Builder addElements(String... elements) {
176+
if (this.elements == null) {
177+
this.elements = new ArrayList<>();
178+
}
179+
this.elements.addAll(Arrays.asList(elements));
180+
return this;
181+
}
182+
183+
/**
184+
* Adds a List of elements to NestedAttributeName.
185+
* Subsequent calls to this method will append the new elements to the end of the existing chain of elements
186+
* creating new levels of nesting.
187+
* <p>
188+
* <b>Example:</b>builder().addElements(Arrays.asList("foo","bar")) will add elements in NestedAttributeName
189+
* to represent a Nested Attribute Name foo.bar
190+
*
191+
* @param elements List of Strings where each string corresponds to subsequent nesting attribute name.
192+
* @return Returns a reference to this object so that method calls can be chained together.
193+
*/
194+
public Builder addElements(List<String> elements) {
195+
if (this.elements == null) {
196+
this.elements = new ArrayList<>();
197+
}
198+
this.elements.addAll(elements);
199+
return this;
200+
}
201+
202+
/**
203+
* Set elements of NestedAttributeName with list of Strings. Will overwrite any existing elements stored by this builder.
204+
* <p>
205+
* <b>Example:</b>builder().elements("foo","bar") will set the elements in NestedAttributeName
206+
* to represent a nested attribute name of 'foo.bar'
207+
*
208+
* @param elements a list of strings that correspond to the elements in a nested attribute name.
209+
* @return Returns a reference to this object so that method calls can be chained together.
210+
*/
211+
public Builder elements(String... elements) {
212+
this.elements = new ArrayList<>(Arrays.asList(elements));
213+
return this;
214+
}
215+
216+
/**
217+
* Sets the elements that compose a nested attribute name. Will overwrite any existing elements stored by this builder.
218+
* <p>
219+
* <b>Example:</b>builder().elements(Arrays.asList("foo","bar")) will add elements in NestedAttributeName
220+
* which represent a Nested Attribute Name foo.bar
221+
*
222+
* @param elements a list of strings that correspond to the elements in a nested attribute name.
223+
* @return Returns a reference to this object so that method calls can be chained together.
224+
*/
225+
public Builder elements(List<String> elements) {
226+
this.elements = new ArrayList<>(elements);
227+
return this;
228+
}
229+
230+
231+
public NestedAttributeName build() {
232+
return new NestedAttributeName(elements);
233+
}
234+
}
235+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb.internal;
17+
18+
import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.cleanAttributeName;
19+
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.LinkedHashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Objects;
26+
import java.util.Optional;
27+
import java.util.function.UnaryOperator;
28+
import java.util.stream.Collectors;
29+
import software.amazon.awssdk.annotations.SdkInternalApi;
30+
import software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName;
31+
32+
/**
33+
* Wrapper method to get Projection Expression Name map and Projection Expressions from NestedAttributeNames.
34+
*/
35+
@SdkInternalApi
36+
public class ProjectionExpressionConvertor {
37+
38+
private static final String AMZN_MAPPED = "#AMZN_MAPPED_";
39+
private static final UnaryOperator<String> PROJECTION_EXPRESSION_KEY_MAPPER = k -> AMZN_MAPPED + cleanAttributeName(k);
40+
private final List<NestedAttributeName> nestedAttributeNames;
41+
42+
private ProjectionExpressionConvertor(List<NestedAttributeName> nestedAttributeNames) {
43+
this.nestedAttributeNames = nestedAttributeNames;
44+
}
45+
46+
public static ProjectionExpressionConvertor create(List<NestedAttributeName> nestedAttributeNames) {
47+
return new ProjectionExpressionConvertor(nestedAttributeNames);
48+
}
49+
50+
private static Optional<Map<String, String>> convertToExpressionNameMap(NestedAttributeName attributeName) {
51+
List<String> nestedAttributeNames = attributeName.elements();
52+
if (nestedAttributeNames != null) {
53+
Map<String, String> resultNameMap = new LinkedHashMap<>();
54+
nestedAttributeNames.stream().forEach(nestedAttribute ->
55+
resultNameMap.put(PROJECTION_EXPRESSION_KEY_MAPPER.apply(nestedAttribute), nestedAttribute));
56+
return Optional.of(resultNameMap);
57+
}
58+
return Optional.empty();
59+
}
60+
61+
private static Optional<String> convertToNameExpression(NestedAttributeName nestedAttributeName) {
62+
63+
String name = nestedAttributeName.elements().stream().findFirst().orElse(null);
64+
65+
List<String> nestedAttributes = null;
66+
if (nestedAttributeName.elements().size() > 1) {
67+
nestedAttributes = nestedAttributeName.elements().subList(1, nestedAttributeName.elements().size());
68+
}
69+
if (name != null) {
70+
List<String> hashSeparatedNestedStringList =
71+
new ArrayList<>(Arrays.asList(PROJECTION_EXPRESSION_KEY_MAPPER.apply(name)));
72+
if (nestedAttributes != null) {
73+
nestedAttributes.stream().forEach(hashSeparatedNestedStringList::add);
74+
}
75+
return Optional.of(String.join(".".concat(AMZN_MAPPED), hashSeparatedNestedStringList));
76+
}
77+
return Optional.empty();
78+
}
79+
80+
public List<NestedAttributeName> nestedAttributeNames() {
81+
return nestedAttributeNames;
82+
}
83+
84+
public Map<String, String> convertToExpressionMap() {
85+
Map<String, String> attributeNameMap = new LinkedHashMap<>();
86+
if (this.nestedAttributeNames() != null) {
87+
this.nestedAttributeNames().stream().forEach(attribs -> convertToExpressionNameMap(attribs)
88+
.ifPresent(attributeNameMap::putAll));
89+
}
90+
return attributeNameMap;
91+
}
92+
93+
public Optional<String> convertToProjectionExpression() {
94+
if (nestedAttributeNames != null) {
95+
List<String> expressionList = new ArrayList<>();
96+
this.nestedAttributeNames().stream().filter(Objects::nonNull)
97+
.filter(item -> item.elements() != null && !item.elements().isEmpty())
98+
.forEach(attributeName -> convertToNameExpression(attributeName)
99+
.ifPresent(expressionList::add));
100+
String joinedExpression = String.join(",", expressionList.stream()
101+
.distinct().collect(Collectors.toList()));
102+
return Optional.ofNullable(joinedExpression.isEmpty() ? null : joinedExpression);
103+
}
104+
return Optional.empty();
105+
}
106+
107+
}

0 commit comments

Comments
 (0)