Skip to content

Commit ecc9149

Browse files
authored
Implement return values for enhanced UpdateItem (#2748)
This commit introduces DynamoDbTable#updateItemWithResponse() that allows customers to specify additional parameters on the request such as ReturnConsumedCapacity to get additional information the service response.
1 parent 1f2cd2d commit ecc9149

File tree

15 files changed

+1351
-15
lines changed

15 files changed

+1351
-15
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "DynamoDB Enhanced Client",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "This commit introduces DynamoDbTable#updateItemWithResponse() that allows customers to specify additional parameters on the request such as ReturnConsumedCapacity to get additional information the service response."
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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 static org.assertj.core.api.Assertions.assertThat;
19+
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
20+
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey;
21+
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey;
22+
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey;
23+
24+
import java.util.Objects;
25+
import org.junit.AfterClass;
26+
import org.junit.BeforeClass;
27+
import org.junit.Test;
28+
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
29+
import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex;
30+
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest;
31+
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedResponse;
32+
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
33+
import software.amazon.awssdk.services.dynamodb.model.Projection;
34+
import software.amazon.awssdk.services.dynamodb.model.ProjectionType;
35+
import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics;
36+
37+
public class AsyncUpdateItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase {
38+
private static class Record {
39+
private Integer id;
40+
private Integer id2;
41+
private String stringAttr1;
42+
43+
private Integer getId() {
44+
return id;
45+
}
46+
47+
private Record setId(Integer id) {
48+
this.id = id;
49+
return this;
50+
}
51+
52+
private Integer getId2() {
53+
return id2;
54+
}
55+
56+
private Record setId2(Integer id2) {
57+
this.id2 = id2;
58+
return this;
59+
}
60+
61+
private String getStringAttr1() {
62+
return stringAttr1;
63+
}
64+
65+
private Record setStringAttr1(String stringAttr1) {
66+
this.stringAttr1 = stringAttr1;
67+
return this;
68+
}
69+
70+
@Override
71+
public boolean equals(Object o) {
72+
if (this == o) {
73+
return true;
74+
}
75+
if (o == null || getClass() != o.getClass()) {
76+
return false;
77+
}
78+
Record record = (Record) o;
79+
return Objects.equals(id, record.id)
80+
&& Objects.equals(id2, record.id2)
81+
&& Objects.equals(stringAttr1, record.stringAttr1);
82+
}
83+
84+
@Override
85+
public int hashCode() {
86+
return Objects.hash(id, id2, stringAttr1);
87+
}
88+
}
89+
90+
private static final String TABLE_NAME = createTestTableName();
91+
92+
private static final TableSchema<Record> TABLE_SCHEMA =
93+
StaticTableSchema.builder(Record.class)
94+
.newItemSupplier(Record::new)
95+
.addAttribute(Integer.class, a -> a.name("id_1")
96+
.getter(Record::getId)
97+
.setter(Record::setId)
98+
.tags(primaryPartitionKey(), secondaryPartitionKey("index1")))
99+
.addAttribute(Integer.class, a -> a.name("id_2")
100+
.getter(Record::getId2)
101+
.setter(Record::setId2)
102+
.tags(primarySortKey(), secondarySortKey("index1")))
103+
.addAttribute(String.class, a -> a.name("stringAttr1")
104+
.getter(Record::getStringAttr1)
105+
.setter(Record::setStringAttr1))
106+
.build();
107+
108+
private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = EnhancedLocalSecondaryIndex.builder()
109+
.indexName("index1")
110+
.projection(Projection.builder()
111+
.projectionType(ProjectionType.ALL)
112+
.build())
113+
.build();
114+
115+
private static DynamoDbAsyncClient dynamoDbClient;
116+
private static DynamoDbEnhancedAsyncClient enhancedClient;
117+
private static DynamoDbAsyncTable<Record> mappedTable;
118+
119+
@BeforeClass
120+
public static void setup() {
121+
dynamoDbClient = createAsyncDynamoDbClient();
122+
enhancedClient = DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(dynamoDbClient).build();
123+
mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA);
124+
mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)).join();
125+
dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)).join();
126+
}
127+
128+
@AfterClass
129+
public static void teardown() {
130+
try {
131+
dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)).join();
132+
} finally {
133+
dynamoDbClient.close();
134+
}
135+
}
136+
137+
@Test
138+
public void updateItem_returnItemCollectionMetrics_set_itemCollectionMetricsNull() {
139+
Record record = new Record().setId(1).setId2(10);
140+
UpdateItemEnhancedRequest<Record> request = UpdateItemEnhancedRequest.builder(Record.class)
141+
.item(record)
142+
.build();
143+
144+
UpdateItemEnhancedResponse<Record> response = mappedTable.updateItemWithResponse(request).join();
145+
146+
assertThat(response.itemCollectionMetrics()).isNull();
147+
}
148+
149+
@Test
150+
public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() {
151+
Record record = new Record().setId(1).setId2(10);
152+
UpdateItemEnhancedRequest<Record> request = UpdateItemEnhancedRequest.builder(Record.class)
153+
.item(record)
154+
.returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE)
155+
.build();
156+
157+
UpdateItemEnhancedResponse<Record> response = mappedTable.updateItemWithResponse(request).join();
158+
159+
assertThat(response.itemCollectionMetrics()).isNotNull();
160+
}
161+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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 static org.assertj.core.api.Assertions.assertThat;
19+
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey;
20+
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey;
21+
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey;
22+
import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey;
23+
24+
import java.util.Objects;
25+
import org.junit.AfterClass;
26+
import org.junit.BeforeClass;
27+
import org.junit.Test;
28+
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
29+
import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex;
30+
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest;
31+
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedResponse;
32+
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
33+
import software.amazon.awssdk.services.dynamodb.model.Projection;
34+
import software.amazon.awssdk.services.dynamodb.model.ProjectionType;
35+
import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics;
36+
37+
public class UpdateItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase {
38+
private static class Record {
39+
private Integer id;
40+
private Integer id2;
41+
private String stringAttr1;
42+
43+
private Integer getId() {
44+
return id;
45+
}
46+
47+
private Record setId(Integer id) {
48+
this.id = id;
49+
return this;
50+
}
51+
52+
private Integer getId2() {
53+
return id2;
54+
}
55+
56+
private Record setId2(Integer id2) {
57+
this.id2 = id2;
58+
return this;
59+
}
60+
61+
private String getStringAttr1() {
62+
return stringAttr1;
63+
}
64+
65+
private Record setStringAttr1(String stringAttr1) {
66+
this.stringAttr1 = stringAttr1;
67+
return this;
68+
}
69+
70+
@Override
71+
public boolean equals(Object o) {
72+
if (this == o) {
73+
return true;
74+
}
75+
if (o == null || getClass() != o.getClass()) {
76+
return false;
77+
}
78+
Record record = (Record) o;
79+
return Objects.equals(id, record.id)
80+
&& Objects.equals(id2, record.id2)
81+
&& Objects.equals(stringAttr1, record.stringAttr1);
82+
}
83+
84+
@Override
85+
public int hashCode() {
86+
return Objects.hash(id, id2, stringAttr1);
87+
}
88+
}
89+
90+
private static final String TABLE_NAME = createTestTableName();
91+
92+
private static final TableSchema<Record> TABLE_SCHEMA =
93+
StaticTableSchema.builder(Record.class)
94+
.newItemSupplier(Record::new)
95+
.addAttribute(Integer.class, a -> a.name("id_1")
96+
.getter(Record::getId)
97+
.setter(Record::setId)
98+
.tags(primaryPartitionKey(), secondaryPartitionKey("index1")))
99+
.addAttribute(Integer.class, a -> a.name("id_2")
100+
.getter(Record::getId2)
101+
.setter(Record::setId2)
102+
.tags(primarySortKey(), secondarySortKey("index1")))
103+
.addAttribute(String.class, a -> a.name("stringAttr1")
104+
.getter(Record::getStringAttr1)
105+
.setter(Record::setStringAttr1))
106+
.build();
107+
108+
private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = EnhancedLocalSecondaryIndex.builder()
109+
.indexName("index1")
110+
.projection(Projection.builder()
111+
.projectionType(ProjectionType.ALL)
112+
.build())
113+
.build();
114+
115+
private static DynamoDbClient dynamoDbClient;
116+
private static DynamoDbEnhancedClient enhancedClient;
117+
private static DynamoDbTable<Record> mappedTable;
118+
119+
@BeforeClass
120+
public static void setup() {
121+
dynamoDbClient = createDynamoDbClient();
122+
enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build();
123+
mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA);
124+
mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX));
125+
dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME));
126+
}
127+
128+
@AfterClass
129+
public static void teardown() {
130+
try {
131+
dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME));
132+
} finally {
133+
dynamoDbClient.close();
134+
}
135+
}
136+
137+
@Test
138+
public void updateItem_returnItemCollectionMetrics_set_itemCollectionMetricsNull() {
139+
Record record = new Record().setId(1).setId2(10);
140+
UpdateItemEnhancedRequest<Record> request = UpdateItemEnhancedRequest.builder(Record.class)
141+
.item(record)
142+
.build();
143+
144+
UpdateItemEnhancedResponse<Record> response = mappedTable.updateItemWithResponse(request);
145+
146+
assertThat(response.itemCollectionMetrics()).isNull();
147+
}
148+
149+
@Test
150+
public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() {
151+
Record record = new Record().setId(1).setId2(10);
152+
UpdateItemEnhancedRequest<Record> request = UpdateItemEnhancedRequest.builder(Record.class)
153+
.item(record)
154+
.returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE)
155+
.build();
156+
157+
UpdateItemEnhancedResponse<Record> response = mappedTable.updateItemWithResponse(request);
158+
159+
assertThat(response.itemCollectionMetrics()).isNotNull();
160+
}
161+
}

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest;
3131
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest;
3232
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest;
33+
import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedResponse;
3334
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
3435
import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity;
3536

@@ -787,6 +788,56 @@ default CompletableFuture<T> updateItem(T item) {
787788
throw new UnsupportedOperationException();
788789
}
789790

791+
/**
792+
* Updates an item in the mapped table, or adds it if it doesn't exist. This is similar to
793+
* {@link #updateItem(UpdateItemEnhancedRequest)}} but returns {@link UpdateItemEnhancedResponse} for additional information.
794+
* <p>
795+
* This operation calls the low-level DynamoDB API UpdateItem operation. Consult the UpdateItem documentation for
796+
* further details and constraints.
797+
* <p>
798+
* Example:
799+
* <pre>
800+
* {@code
801+
* UpdateItemEnhancedRequest<MyItem> request = UpdateItemEnhancedRequest.builder(MyItem.class).item(myItem).build();
802+
* UpdateItemEnhancedResponse<MyItem> response = mappedTable.updateItemWithResponse(request).join();
803+
* }
804+
* </pre>
805+
* <p>
806+
*
807+
*
808+
* @param request the modelled item to be inserted into or updated in the database table.
809+
* @return A {@link CompletableFuture} containing the response from DynamoDB.
810+
*/
811+
default CompletableFuture<UpdateItemEnhancedResponse<T>> updateItemWithResponse(UpdateItemEnhancedRequest<T> request) {
812+
throw new UnsupportedOperationException();
813+
}
814+
815+
/**
816+
* Updates an item in the mapped table, or adds it if it doesn't exist. This is similar to
817+
* {@link #updateItem(Consumer)} but returns {@link UpdateItemEnhancedResponse} for additional information.
818+
* <p>
819+
* This operation calls the low-level DynamoDB API UpdateItem operation. Consult the UpdateItem documentation for
820+
* further details and constraints.
821+
* <p>
822+
* Example:
823+
* <pre>
824+
* {@code
825+
*
826+
* UpdateItemEnhancedResponse<MyItem> response = mappedTable.updateItemWithResponse(r ->r.item(myItem)).join();
827+
* }
828+
* </pre>
829+
* <p>
830+
*
831+
*
832+
* @param requestConsumer A {@link Consumer} of {@link UpdateItemEnhancedRequest.Builder} that includes the item
833+
* * to be updated, its class and optional directives.
834+
* @return A {@link CompletableFuture} containing the response from DynamoDB.
835+
*/
836+
default CompletableFuture<UpdateItemEnhancedResponse<T>> updateItemWithResponse(
837+
Consumer<UpdateItemEnhancedRequest.Builder<T>> requestConsumer) {
838+
throw new UnsupportedOperationException();
839+
}
840+
790841
/**
791842
* Deletes a table in DynamoDb with the name and schema already defined for this DynamoDbTable.
792843
* <p>

0 commit comments

Comments
 (0)