diff --git a/.changes/next-release/feature-DynamoDBEnhancedClient-88d6815.json b/.changes/next-release/feature-DynamoDBEnhancedClient-88d6815.json new file mode 100644 index 000000000000..f08615f6cdba --- /dev/null +++ b/.changes/next-release/feature-DynamoDBEnhancedClient-88d6815.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "DynamoDB Enhanced Client", + "contributor": "", + "description": "Adds count, scanned count and returned consumed capacity to Page, used by Scan and Query operations" +} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncCrudWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncCrudWithResponseIntegrationTest.java new file mode 100644 index 000000000000..8dec8620aebb --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncCrudWithResponseIntegrationTest.java @@ -0,0 +1,204 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey; +import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; + +import org.assertj.core.data.Offset; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedResponse; +import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedResponse; +import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedResponse; +import software.amazon.awssdk.enhanced.dynamodb.model.Record; +import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedResponse; +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; +import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity; +import software.amazon.awssdk.services.dynamodb.model.Projection; +import software.amazon.awssdk.services.dynamodb.model.ProjectionType; +import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; +import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics; + +public class AsyncCrudWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { + + + private static final String TABLE_NAME = createTestTableName(); + + + private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = EnhancedLocalSecondaryIndex.builder() + .indexName("index1") + .projection(Projection.builder() + .projectionType(ProjectionType.ALL) + .build()) + .build(); + + private static DynamoDbAsyncClient dynamoDbClient; + private static DynamoDbEnhancedAsyncClient enhancedClient; + private static DynamoDbAsyncTable mappedTable; + + @BeforeClass + public static void setup() { + dynamoDbClient = createAsyncDynamoDbClient(); + enhancedClient = DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(dynamoDbClient).build(); + mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); + mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)).join(); + dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)).join(); + } + + @AfterClass + public static void teardown() { + try { + dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)).join(); + } finally { + dynamoDbClient.close(); + } + } + + + @Test + public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNull() { + Record record = new Record().setId("1").setSort(10); + PutItemEnhancedRequest request = PutItemEnhancedRequest.builder(Record.class) + .item(record) + .build(); + + PutItemEnhancedResponse response = mappedTable.putItemWithResponse(request).join(); + + assertThat(response.itemCollectionMetrics()).isNull(); + } + + @Test + public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() { + Record record = new Record().setId("1").setSort(10); + PutItemEnhancedRequest request = PutItemEnhancedRequest.builder(Record.class) + .item(record) + .returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE) + .build(); + + PutItemEnhancedResponse response = mappedTable.putItemWithResponse(request).join(); + + assertThat(response.itemCollectionMetrics()).isNotNull(); + } + + @Test + public void updateItem_returnItemCollectionMetrics_set_itemCollectionMetricsNull() { + Record record = new Record().setId("1").setSort(10); + UpdateItemEnhancedRequest request = UpdateItemEnhancedRequest.builder(Record.class) + .item(record) + .build(); + + UpdateItemEnhancedResponse response = mappedTable.updateItemWithResponse(request).join(); + + assertThat(response.itemCollectionMetrics()).isNull(); + } + + @Test + public void updateItem_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() { + Record record = new Record().setId("1").setSort(10); + UpdateItemEnhancedRequest request = UpdateItemEnhancedRequest.builder(Record.class) + .item(record) + .returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE) + .build(); + + UpdateItemEnhancedResponse response = mappedTable.updateItemWithResponse(request).join(); + + assertThat(response.itemCollectionMetrics()).isNotNull(); + } + + @Test + public void deleteItem_returnConsumedCapacity_unset_consumedCapacityNull() { + Key key = Key.builder().partitionValue("1").sortValue(10).build(); + + DeleteItemEnhancedResponse response = mappedTable.deleteItemWithResponse(r -> r.key(key)).join(); + + assertThat(response.consumedCapacity()).isNull(); + } + + @Test + public void deleteItem_returnConsumedCapacity_set_consumedCapacityNotNull() { + Key key = Key.builder().partitionValue("1").sortValue(10).build(); + + DeleteItemEnhancedResponse response = + mappedTable.deleteItemWithResponse(r -> r.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)).join(); + + assertThat(response.consumedCapacity()).isNotNull(); + } + + @Test + public void delete_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() { + Key key = Key.builder().partitionValue("1").sortValue(10).build(); + + DeleteItemEnhancedResponse response = + mappedTable.deleteItemWithResponse(r -> r.key(key).returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE)) + .join(); + + assertThat(response.itemCollectionMetrics()).isNotNull(); + } + + @Test + public void getItem_withoutReturnConsumedCapacity() { + Record record = new Record().setId("101").setSort(102).setStringAttribute(getStringAttrValue(80_000)); + Key key = Key.builder() + .partitionValue(record.getId()) + .sortValue(record.getSort()) + .build(); + + GetItemEnhancedResponse response = mappedTable.getItemWithResponse(req -> req.key(key)).join(); + assertThat(response.consumedCapacity()).isNull(); + } + + @Test + public void getItem_withReturnConsumedCapacity_eventualConsistent() { + Record record = new Record().setId("101").setSort(102).setStringAttribute(getStringAttrValue(80 * 1024)); + Key key = Key.builder() + .partitionValue(record.getId()) + .sortValue(record.getSort()) + .build(); + mappedTable.putItem(record).join(); + + GetItemEnhancedResponse response = mappedTable.getItemWithResponse( + req -> req.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) + ).join(); + ConsumedCapacity consumedCapacity = response.consumedCapacity(); + assertThat(consumedCapacity).isNotNull(); + // An eventually consistent read request of an item up to 4 KB requires one-half read request unit. + assertThat(consumedCapacity.capacityUnits()).isCloseTo(10.0, Offset.offset(1.0)); + } + + @Test + public void getItem_withReturnConsumedCapacity_stronglyConsistent() { + Record record = new Record().setId("201").setSort(202).setStringAttribute(getStringAttrValue(80 * 1024)); + Key key = Key.builder() + .partitionValue(record.getId()) + .sortValue(record.getSort()) + .build(); + mappedTable.putItem(record).join(); + + GetItemEnhancedResponse response = mappedTable.getItemWithResponse( + req -> req.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL).consistentRead(true) + ).join(); + ConsumedCapacity consumedCapacity = response.consumedCapacity(); + assertThat(consumedCapacity).isNotNull(); + // A strongly consistent read request of an item up to 4 KB requires one read request unit. + assertThat(consumedCapacity.capacityUnits()).isCloseTo(20.0, Offset.offset(1.0)); + } +} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncDeleteItemWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncDeleteItemWithResponseIntegrationTest.java deleted file mode 100644 index 88687e6ac725..000000000000 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncDeleteItemWithResponseIntegrationTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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 static org.assertj.core.api.Assertions.assertThat; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; - -import java.util.Objects; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; -import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedResponse; -import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex; -import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; -import software.amazon.awssdk.services.dynamodb.model.Projection; -import software.amazon.awssdk.services.dynamodb.model.ProjectionType; -import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; -import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics; - -public class AsyncDeleteItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { - private static class Record { - private Integer id; - private Integer id2; - private String stringAttr1; - - private Integer getId() { - return id; - } - - private Record setId(Integer id) { - this.id = id; - return this; - } - - private Integer getId2() { - return id2; - } - - private Record setId2(Integer id2) { - this.id2 = id2; - return this; - } - - private String getStringAttr1() { - return stringAttr1; - } - - private Record setStringAttr1(String stringAttr1) { - this.stringAttr1 = stringAttr1; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return Objects.equals(id, record.id) - && Objects.equals(id2, record.id2) - && Objects.equals(stringAttr1, record.stringAttr1); - } - - @Override - public int hashCode() { - return Objects.hash(id, id2, stringAttr1); - } - } - - private static final String TABLE_NAME = createTestTableName(); - - private static final TableSchema TABLE_SCHEMA = - StaticTableSchema.builder(Record.class) - .newItemSupplier(Record::new) - .addAttribute(Integer.class, a -> a.name("id_1") - .getter(Record::getId) - .setter(Record::setId) - .tags(primaryPartitionKey(), secondaryPartitionKey("index1"))) - .addAttribute(Integer.class, a -> a.name("id_2") - .getter(Record::getId2) - .setter(Record::setId2) - .tags(primarySortKey(), secondarySortKey("index1"))) - .addAttribute(String.class, a -> a.name("stringAttr1") - .getter(Record::getStringAttr1) - .setter(Record::setStringAttr1)) - .build(); - - private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = EnhancedLocalSecondaryIndex.builder() - .indexName("index1") - .projection(Projection.builder() - .projectionType(ProjectionType.ALL) - .build()) - .build(); - - private static DynamoDbAsyncClient dynamoDbClient; - private static DynamoDbEnhancedAsyncClient enhancedClient; - private static DynamoDbAsyncTable mappedTable; - - @BeforeClass - public static void setup() { - dynamoDbClient = createAsyncDynamoDbClient(); - enhancedClient = DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(dynamoDbClient).build(); - mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); - mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)).join(); - dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)).join(); - } - - @AfterClass - public static void teardown() { - try { - dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); - } finally { - dynamoDbClient.close(); - } - } - - @Test - public void deleteItem_returnConsumedCapacity_unset_consumedCapacityNull() { - Key key = Key.builder().partitionValue(1).sortValue(10).build(); - - DeleteItemEnhancedResponse response = mappedTable.deleteItemWithResponse(r -> r.key(key)).join(); - - assertThat(response.consumedCapacity()).isNull(); - } - - @Test - public void deleteItem_returnConsumedCapacity_set_consumedCapacityNotNull() { - Key key = Key.builder().partitionValue(1).sortValue(10).build(); - - DeleteItemEnhancedResponse response = - mappedTable.deleteItemWithResponse(r -> r.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)).join(); - - assertThat(response.consumedCapacity()).isNotNull(); - } - - @Test - public void delete_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() { - Key key = Key.builder().partitionValue(1).sortValue(10).build(); - - DeleteItemEnhancedResponse response = - mappedTable.deleteItemWithResponse(r -> r.key(key).returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE)) - .join(); - - assertThat(response.itemCollectionMetrics()).isNotNull(); - } -} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncGetItemWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncGetItemWithResponseIntegrationTest.java deleted file mode 100644 index 257e1c604c30..000000000000 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncGetItemWithResponseIntegrationTest.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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 static org.assertj.core.api.Assertions.assertThat; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey; - -import java.util.Arrays; -import java.util.Objects; -import org.assertj.core.data.Offset; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; -import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedResponse; -import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; -import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity; -import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; - -public class AsyncGetItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { - - private static final String TABLE_NAME = createTestTableName(); - private static final TableSchema TABLE_SCHEMA = - StaticTableSchema.builder(Record.class) - .newItemSupplier(Record::new) - .addAttribute(Integer.class, a -> a.name("id_1") - .getter(Record::getId) - .setter(Record::setId) - .tags(primaryPartitionKey())) - .addAttribute(Integer.class, a -> a.name("id_2") - .getter(Record::getId2) - .setter(Record::setId2) - .tags(primarySortKey())) - .addAttribute(String.class, a -> a.name("stringAttr1") - .getter(Record::getStringAttr1) - .setter(Record::setStringAttr1)) - .build(); - - private static DynamoDbAsyncClient asyncDynamoDbClient; - private static DynamoDbEnhancedAsyncClient enhancedClient; - private static DynamoDbAsyncTable mappedTable; - - @BeforeClass - public static void setup() { - asyncDynamoDbClient = createAsyncDynamoDbClient(); - enhancedClient = DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(asyncDynamoDbClient).build(); - mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); - mappedTable.createTable().join(); - asyncDynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)).join(); - } - - @AfterClass - public static void teardown() { - try { - asyncDynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); - } finally { - asyncDynamoDbClient.close(); - } - } - - @Test - public void getItem_withoutReturnConsumedCapacity() { - Record record = new Record().setId(101).setId2(102).setStringAttr1(getStringAttrValue(80_000)); - Key key = Key.builder() - .partitionValue(record.getId()) - .sortValue(record.getId2()) - .build(); - - GetItemEnhancedResponse response = mappedTable.getItemWithResponse(req -> req.key(key)).join(); - assertThat(response.consumedCapacity()).isNull(); - } - - @Test - public void getItem_withReturnConsumedCapacity_eventualConsistent() { - Record record = new Record().setId(101).setId2(102).setStringAttr1(getStringAttrValue(80 * 1024)); - Key key = Key.builder() - .partitionValue(record.getId()) - .sortValue(record.getId2()) - .build(); - mappedTable.putItem(record).join(); - - GetItemEnhancedResponse response = mappedTable.getItemWithResponse( - req -> req.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) - ).join(); - ConsumedCapacity consumedCapacity = response.consumedCapacity(); - assertThat(consumedCapacity).isNotNull(); - // An eventually consistent read request of an item up to 4 KB requires one-half read request unit. - assertThat(consumedCapacity.capacityUnits()).isCloseTo(10.0, Offset.offset(1.0)); - } - - @Test - public void getItem_withReturnConsumedCapacity_stronglyConsistent() { - Record record = new Record().setId(201).setId2(202).setStringAttr1(getStringAttrValue(80 * 1024)); - Key key = Key.builder() - .partitionValue(record.getId()) - .sortValue(record.getId2()) - .build(); - mappedTable.putItem(record).join(); - - GetItemEnhancedResponse response = mappedTable.getItemWithResponse( - req -> req.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL).consistentRead(true) - ).join(); - ConsumedCapacity consumedCapacity = response.consumedCapacity(); - assertThat(consumedCapacity).isNotNull(); - // A strongly consistent read request of an item up to 4 KB requires one read request unit. - assertThat(consumedCapacity.capacityUnits()).isCloseTo(20.0, Offset.offset(1.0)); - } - - private static String getStringAttrValue(int numChars) { - char[] chars = new char[numChars]; - Arrays.fill(chars, 'a'); - return new String(chars); - } - - private static final class Record { - private Integer id; - private Integer id2; - private String stringAttr1; - - private Integer getId() { - return id; - } - - private Record setId(Integer id) { - this.id = id; - return this; - } - - private Integer getId2() { - return id2; - } - - private Record setId2(Integer id2) { - this.id2 = id2; - return this; - } - - private String getStringAttr1() { - return stringAttr1; - } - - private Record setStringAttr1(String stringAttr1) { - this.stringAttr1 = stringAttr1; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return Objects.equals(id, record.id) - && Objects.equals(id2, record.id2) - && Objects.equals(stringAttr1, record.stringAttr1); - } - - @Override - public int hashCode() { - return Objects.hash(id, id2, stringAttr1); - } - } -} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncPutItemWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncPutItemWithResponseIntegrationTest.java deleted file mode 100644 index f423309b3e72..000000000000 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncPutItemWithResponseIntegrationTest.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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 static org.assertj.core.api.Assertions.assertThat; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; - -import java.util.Objects; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; -import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex; -import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest; -import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedResponse; -import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.Projection; -import software.amazon.awssdk.services.dynamodb.model.ProjectionType; -import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics; - -public class AsyncPutItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { - private static class Record { - private Integer id; - private Integer id2; - private String stringAttr1; - - private Integer getId() { - return id; - } - - private Record setId(Integer id) { - this.id = id; - return this; - } - - private Integer getId2() { - return id2; - } - - private Record setId2(Integer id2) { - this.id2 = id2; - return this; - } - - private String getStringAttr1() { - return stringAttr1; - } - - private Record setStringAttr1(String stringAttr1) { - this.stringAttr1 = stringAttr1; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return Objects.equals(id, record.id) - && Objects.equals(id2, record.id2) - && Objects.equals(stringAttr1, record.stringAttr1); - } - - @Override - public int hashCode() { - return Objects.hash(id, id2, stringAttr1); - } - } - - private static final String TABLE_NAME = createTestTableName(); - - private static final TableSchema TABLE_SCHEMA = - StaticTableSchema.builder(Record.class) - .newItemSupplier(Record::new) - .addAttribute(Integer.class, a -> a.name("id_1") - .getter(Record::getId) - .setter(Record::setId) - .tags(primaryPartitionKey(), secondaryPartitionKey("index1"))) - .addAttribute(Integer.class, a -> a.name("id_2") - .getter(Record::getId2) - .setter(Record::setId2) - .tags(primarySortKey(), secondarySortKey("index1"))) - .addAttribute(String.class, a -> a.name("stringAttr1") - .getter(Record::getStringAttr1) - .setter(Record::setStringAttr1)) - .build(); - - private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = EnhancedLocalSecondaryIndex.builder() - .indexName("index1") - .projection(Projection.builder() - .projectionType(ProjectionType.ALL) - .build()) - .build(); - - private static DynamoDbClient dynamoDbClient; - private static DynamoDbAsyncClient asyncDynamoDbClient; - private static DynamoDbEnhancedAsyncClient enhancedClient; - - private static DynamoDbAsyncTable mappedTable; - - @BeforeClass - public static void setup() { - dynamoDbClient = createDynamoDbClient(); - asyncDynamoDbClient = createAsyncDynamoDbClient(); - enhancedClient = DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(asyncDynamoDbClient).build(); - - mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); - mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)).join(); - - dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)); - } - - @AfterClass - public static void teardown() { - try { - dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); - } finally { - dynamoDbClient.close(); - } - asyncDynamoDbClient.close(); - } - - @Test - public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNull() { - Record record = new Record().setId(1).setId2(10); - PutItemEnhancedRequest request = PutItemEnhancedRequest.builder(Record.class) - .item(record) - .build(); - - PutItemEnhancedResponse response = mappedTable.putItemWithResponse(request).join(); - - assertThat(response.itemCollectionMetrics()).isNull(); - } - - @Test - public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() { - Record record = new Record().setId(1).setId2(10); - PutItemEnhancedRequest request = PutItemEnhancedRequest.builder(Record.class) - .item(record) - .returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE) - .build(); - - PutItemEnhancedResponse response = mappedTable.putItemWithResponse(request).join(); - - assertThat(response.itemCollectionMetrics()).isNotNull(); - } -} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncUpdateItemWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncUpdateItemWithResponseIntegrationTest.java deleted file mode 100644 index 28183923f546..000000000000 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/AsyncUpdateItemWithResponseIntegrationTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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 static org.assertj.core.api.Assertions.assertThat; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; - -import java.util.Objects; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; -import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex; -import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest; -import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedResponse; -import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; -import software.amazon.awssdk.services.dynamodb.model.Projection; -import software.amazon.awssdk.services.dynamodb.model.ProjectionType; -import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics; - -public class AsyncUpdateItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { - private static class Record { - private Integer id; - private Integer id2; - private String stringAttr1; - - private Integer getId() { - return id; - } - - private Record setId(Integer id) { - this.id = id; - return this; - } - - private Integer getId2() { - return id2; - } - - private Record setId2(Integer id2) { - this.id2 = id2; - return this; - } - - private String getStringAttr1() { - return stringAttr1; - } - - private Record setStringAttr1(String stringAttr1) { - this.stringAttr1 = stringAttr1; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return Objects.equals(id, record.id) - && Objects.equals(id2, record.id2) - && Objects.equals(stringAttr1, record.stringAttr1); - } - - @Override - public int hashCode() { - return Objects.hash(id, id2, stringAttr1); - } - } - - private static final String TABLE_NAME = createTestTableName(); - - private static final TableSchema TABLE_SCHEMA = - StaticTableSchema.builder(Record.class) - .newItemSupplier(Record::new) - .addAttribute(Integer.class, a -> a.name("id_1") - .getter(Record::getId) - .setter(Record::setId) - .tags(primaryPartitionKey(), secondaryPartitionKey("index1"))) - .addAttribute(Integer.class, a -> a.name("id_2") - .getter(Record::getId2) - .setter(Record::setId2) - .tags(primarySortKey(), secondarySortKey("index1"))) - .addAttribute(String.class, a -> a.name("stringAttr1") - .getter(Record::getStringAttr1) - .setter(Record::setStringAttr1)) - .build(); - - private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = EnhancedLocalSecondaryIndex.builder() - .indexName("index1") - .projection(Projection.builder() - .projectionType(ProjectionType.ALL) - .build()) - .build(); - - private static DynamoDbAsyncClient dynamoDbClient; - private static DynamoDbEnhancedAsyncClient enhancedClient; - private static DynamoDbAsyncTable mappedTable; - - @BeforeClass - public static void setup() { - dynamoDbClient = createAsyncDynamoDbClient(); - enhancedClient = DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(dynamoDbClient).build(); - mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); - mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)).join(); - dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)).join(); - } - - @AfterClass - public static void teardown() { - try { - dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)).join(); - } finally { - dynamoDbClient.close(); - } - } - - @Test - public void updateItem_returnItemCollectionMetrics_set_itemCollectionMetricsNull() { - Record record = new Record().setId(1).setId2(10); - UpdateItemEnhancedRequest request = UpdateItemEnhancedRequest.builder(Record.class) - .item(record) - .build(); - - UpdateItemEnhancedResponse response = mappedTable.updateItemWithResponse(request).join(); - - assertThat(response.itemCollectionMetrics()).isNull(); - } - - @Test - public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() { - Record record = new Record().setId(1).setId2(10); - UpdateItemEnhancedRequest request = UpdateItemEnhancedRequest.builder(Record.class) - .item(record) - .returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE) - .build(); - - UpdateItemEnhancedResponse response = mappedTable.updateItemWithResponse(request).join(); - - assertThat(response.itemCollectionMetrics()).isNotNull(); - } -} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/CrudWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/CrudWithResponseIntegrationTest.java new file mode 100644 index 000000000000..d6fc0a36d639 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/CrudWithResponseIntegrationTest.java @@ -0,0 +1,193 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import org.assertj.core.data.Offset; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedResponse; +import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedResponse; +import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedResponse; +import software.amazon.awssdk.enhanced.dynamodb.model.Record; +import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedResponse; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity; +import software.amazon.awssdk.services.dynamodb.model.Projection; +import software.amazon.awssdk.services.dynamodb.model.ProjectionType; +import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; +import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics; + +public class CrudWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { + + private static final String TABLE_NAME = createTestTableName(); + + private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = + EnhancedLocalSecondaryIndex.builder() + .indexName("index1") + .projection(Projection.builder() + .projectionType(ProjectionType.ALL) + .build()) + .build(); + + private static DynamoDbClient dynamoDbClient; + private static DynamoDbEnhancedClient enhancedClient; + private static DynamoDbTable mappedTable; + + @BeforeClass + public static void setup() { + dynamoDbClient = createDynamoDbClient(); + enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build(); + mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); + mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)); + dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)); + } + + @AfterClass + public static void teardown() { + try { + dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); + } finally { + dynamoDbClient.close(); + } + } + + @Test + public void putItem_set_requestedMetadataNull() { + Record record = new Record().setId("1").setSort(10); + PutItemEnhancedResponse response = mappedTable.putItemWithResponse(r -> r.item(record)); + + assertThat(response.itemCollectionMetrics()).isNull(); + assertThat(response.consumedCapacity()).isNull(); + } + + @Test + public void putItem_set_requestedMetadataNotNull() { + Record record = new Record().setId("1").setSort(10); + PutItemEnhancedRequest request = PutItemEnhancedRequest.builder(Record.class) + .item(record) + .returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE) + .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) + .build(); + + PutItemEnhancedResponse response = mappedTable.putItemWithResponse(request); + + assertThat(response.itemCollectionMetrics()).isNotNull(); + assertThat(response.consumedCapacity()).isNotNull(); + assertThat(response.consumedCapacity().capacityUnits()).isNotNull(); + } + + @Test + public void updateItem_set_requestedMetadataNull() { + Record record = new Record().setId("1").setSort(10); + UpdateItemEnhancedResponse response = mappedTable.updateItemWithResponse(r -> r.item(record)); + + assertThat(response.itemCollectionMetrics()).isNull(); + assertThat(response.consumedCapacity()).isNull(); + } + + @Test + public void updateItem_set_itemCollectionMetricsNotNull() { + Record record = new Record().setId("1").setSort(10); + UpdateItemEnhancedRequest request = UpdateItemEnhancedRequest.builder(Record.class) + .item(record) + .returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE) + .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) + .build(); + + UpdateItemEnhancedResponse response = mappedTable.updateItemWithResponse(request); + + assertThat(response.itemCollectionMetrics()).isNotNull(); + assertThat(response.consumedCapacity()).isNotNull(); + assertThat(response.consumedCapacity().capacityUnits()).isNotNull(); + } + + @Test + public void deleteItem__unset_consumedCapacityNull() { + Key key = Key.builder().partitionValue("1").sortValue(10).build(); + + DeleteItemEnhancedResponse response = mappedTable.deleteItemWithResponse(r -> r.key(key)); + + assertThat(response.consumedCapacity()).isNull(); + } + + @Test + public void deleteItem__set_consumedCapacityNotNull() { + Key key = Key.builder().partitionValue("1").sortValue(10).build(); + + DeleteItemEnhancedResponse response = + mappedTable.deleteItemWithResponse(r -> r.key(key) + .returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE) + .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)); + + assertThat(response.consumedCapacity()).isNotNull(); + } + + @Test + public void getItem_set_requestedMetadataNull() { + Record record = new Record().setId("101").setSort(102).setStringAttribute(getStringAttrValue(80_000)); + Key key = Key.builder() + .partitionValue(record.getId()) + .sortValue(record.getSort()) + .build(); + + mappedTable.putItem(record); + GetItemEnhancedResponse response = mappedTable.getItemWithResponse(req -> req.key(key)); + + assertThat(response.consumedCapacity()).isNull(); + } + + @Test + public void getItem_set_eventualConsistent() { + Record record = new Record().setId("101").setSort(102).setStringAttribute(getStringAttrValue(80 * 1024)); + Key key = Key.builder() + .partitionValue(record.getId()) + .sortValue(record.getSort()) + .build(); + mappedTable.putItem(record); + + GetItemEnhancedResponse response = mappedTable.getItemWithResponse( + req -> req.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) + ); + ConsumedCapacity consumedCapacity = response.consumedCapacity(); + assertThat(consumedCapacity).isNotNull(); + // An eventually consistent read request of an item up to 4 KB requires one-half read request unit. + assertThat(consumedCapacity.capacityUnits()).isCloseTo(10.0, Offset.offset(1.0)); + } + + @Test + public void getItem_set_stronglyConsistent() { + Record record = new Record().setId("101").setSort(102).setStringAttribute(getStringAttrValue(80 * 1024)); + Key key = Key.builder() + .partitionValue(record.getId()) + .sortValue(record.getSort()) + .build(); + mappedTable.putItem(record); + + GetItemEnhancedResponse response = mappedTable.getItemWithResponse( + req -> req.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL).consistentRead(true) + ); + ConsumedCapacity consumedCapacity = response.consumedCapacity(); + assertThat(consumedCapacity).isNotNull(); + // A strongly consistent read request of an item up to 4 KB requires one read request unit. + assertThat(consumedCapacity.capacityUnits()).isCloseTo(20.0, Offset.offset(1.0)); + } +} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DeleteItemWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DeleteItemWithResponseIntegrationTest.java deleted file mode 100644 index 7c8c2612e477..000000000000 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DeleteItemWithResponseIntegrationTest.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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 static org.assertj.core.api.Assertions.assertThat; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; - -import java.util.Objects; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; -import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedResponse; -import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.Projection; -import software.amazon.awssdk.services.dynamodb.model.ProjectionType; -import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; -import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics; - -public class DeleteItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { - private static class Record { - private Integer id; - private Integer id2; - private String stringAttr1; - - private Integer getId() { - return id; - } - - private Record setId(Integer id) { - this.id = id; - return this; - } - - private Integer getId2() { - return id2; - } - - private Record setId2(Integer id2) { - this.id2 = id2; - return this; - } - - private String getStringAttr1() { - return stringAttr1; - } - - private Record setStringAttr1(String stringAttr1) { - this.stringAttr1 = stringAttr1; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return Objects.equals(id, record.id) - && Objects.equals(id2, record.id2) - && Objects.equals(stringAttr1, record.stringAttr1); - } - - @Override - public int hashCode() { - return Objects.hash(id, id2, stringAttr1); - } - } - - private static final String TABLE_NAME = createTestTableName(); - - private static final TableSchema TABLE_SCHEMA = - StaticTableSchema.builder(Record.class) - .newItemSupplier(Record::new) - .addAttribute(Integer.class, a -> a.name("id_1") - .getter(Record::getId) - .setter(Record::setId) - .tags(primaryPartitionKey(), secondaryPartitionKey("index1"))) - .addAttribute(Integer.class, a -> a.name("id_2") - .getter(Record::getId2) - .setter(Record::setId2) - .tags(primarySortKey(), secondarySortKey("index1"))) - .addAttribute(String.class, a -> a.name("stringAttr1") - .getter(Record::getStringAttr1) - .setter(Record::setStringAttr1)) - .build(); - - private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = EnhancedLocalSecondaryIndex.builder() - .indexName("index1") - .projection(Projection.builder() - .projectionType(ProjectionType.ALL) - .build()) - .build(); - - private static DynamoDbClient dynamoDbClient; - private static DynamoDbEnhancedClient enhancedClient; - private static DynamoDbTable mappedTable; - - @BeforeClass - public static void setup() { - dynamoDbClient = createDynamoDbClient(); - enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build(); - mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); - mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)); - dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)); - } - - @AfterClass - public static void teardown() { - try { - dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); - } finally { - dynamoDbClient.close(); - } - } - - @Test - public void deleteItem_returnConsumedCapacity_unset_consumedCapacityNull() { - Key key = Key.builder().partitionValue(1).sortValue(10).build(); - - DeleteItemEnhancedResponse response = mappedTable.deleteItemWithResponse(r -> r.key(key)); - - assertThat(response.consumedCapacity()).isNull(); - } - - @Test - public void deleteItem_returnConsumedCapacity_set_consumedCapacityNotNull() { - Key key = Key.builder().partitionValue(1).sortValue(10).build(); - - DeleteItemEnhancedResponse response = - mappedTable.deleteItemWithResponse(r -> r.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL)); - - assertThat(response.consumedCapacity()).isNotNull(); - } - - @Test - public void delete_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() { - Key key = Key.builder().partitionValue(1).sortValue(10).build(); - - DeleteItemEnhancedResponse response = - mappedTable.deleteItemWithResponse(r -> r.key(key).returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE)); - - assertThat(response.itemCollectionMetrics()).isNotNull(); - } -} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedIntegrationTestBase.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedIntegrationTestBase.java index a2b56ddad5cc..8a8e35470c20 100644 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedIntegrationTestBase.java +++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedIntegrationTestBase.java @@ -15,7 +15,18 @@ package software.amazon.awssdk.enhanced.dynamodb; +import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; +import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey; +import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey; +import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; + +import java.util.Arrays; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.Record; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.testutils.service.AwsIntegrationTestBase; @@ -36,4 +47,59 @@ protected static DynamoDbAsyncClient createAsyncDynamoDbClient() { .credentialsProvider(getCredentialsProvider()) .build(); } + + protected static final TableSchema TABLE_SCHEMA = + StaticTableSchema.builder(Record.class) + .newItemSupplier(Record::new) + .addAttribute(String.class, a -> a.name("id") + .getter(Record::getId) + .setter(Record::setId) + .tags(primaryPartitionKey(), secondaryPartitionKey("index1"))) + .addAttribute(Integer.class, a -> a.name("sort") + .getter(Record::getSort) + .setter(Record::setSort) + .tags(primarySortKey(), secondarySortKey("index1"))) + .addAttribute(Integer.class, a -> a.name("value") + .getter(Record::getValue) + .setter(Record::setValue)) + .addAttribute(String.class, a -> a.name("gsi_id") + .getter(Record::getGsiId) + .setter(Record::setGsiId) + .tags(secondaryPartitionKey("gsi_keys_only"))) + .addAttribute(Integer.class, a -> a.name("gsi_sort") + .getter(Record::getGsiSort) + .setter(Record::setGsiSort) + .tags(secondarySortKey("gsi_keys_only"))) + .addAttribute(String.class, a -> a.name("stringAttribute") + .getter(Record::getStringAttribute) + .setter(Record::setStringAttribute)) + .build(); + + + protected static final List RECORDS = + IntStream.range(0, 9) + .mapToObj(i -> new Record() + .setId("id-value") + .setSort(i) + .setValue(i) + .setStringAttribute(getStringAttrValue(10 * 1024)) + .setGsiId("gsi-id-value") + .setGsiSort(i)) + .collect(Collectors.toList()); + + protected static final List KEYS_ONLY_RECORDS = + RECORDS.stream() + .map(record -> new Record() + .setId(record.getId()) + .setSort(record.getSort()) + .setGsiId(record.getGsiId()) + .setGsiSort(record.getGsiSort())) + .collect(Collectors.toList()); + + protected static String getStringAttrValue(int numChars) { + char[] chars = new char[numChars]; + Arrays.fill(chars, 'a'); + return new String(chars); + } + } diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/GetItemWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/GetItemWithResponseIntegrationTest.java deleted file mode 100644 index 50bd079dc545..000000000000 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/GetItemWithResponseIntegrationTest.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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 static org.assertj.core.api.Assertions.assertThat; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey; - -import java.util.Arrays; -import java.util.Objects; -import org.assertj.core.data.Offset; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; -import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedResponse; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity; -import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; - -public class GetItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { - - private static final String TABLE_NAME = createTestTableName(); - private static final TableSchema TABLE_SCHEMA = - StaticTableSchema.builder(Record.class) - .newItemSupplier(Record::new) - .addAttribute(Integer.class, a -> a.name("id_1") - .getter(Record::getId) - .setter(Record::setId) - .tags(primaryPartitionKey())) - .addAttribute(Integer.class, a -> a.name("id_2") - .getter(Record::getId2) - .setter(Record::setId2) - .tags(primarySortKey())) - .addAttribute(String.class, a -> a.name("stringAttr1") - .getter(Record::getStringAttr1) - .setter(Record::setStringAttr1)) - .build(); - - private static DynamoDbClient dynamoDbClient; - private static DynamoDbEnhancedClient enhancedClient; - private static DynamoDbTable mappedTable; - - @BeforeClass - public static void setup() { - dynamoDbClient = createDynamoDbClient(); - enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build(); - mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); - mappedTable.createTable(); - dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)); - } - - @AfterClass - public static void teardown() { - try { - dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); - } finally { - dynamoDbClient.close(); - } - } - - @Test - public void getItem_withoutReturnConsumedCapacity() { - Record record = new Record().setId(101).setId2(102).setStringAttr1(getStringAttrValue(80_000)); - Key key = Key.builder() - .partitionValue(record.getId()) - .sortValue(record.getId2()) - .build(); - - GetItemEnhancedResponse response = mappedTable.getItemWithResponse(req -> req.key(key)); - assertThat(response.consumedCapacity()).isNull(); - } - - @Test - public void getItem_withReturnConsumedCapacity_eventualConsistent() { - Record record = new Record().setId(101).setId2(102).setStringAttr1(getStringAttrValue(80 * 1024)); - Key key = Key.builder() - .partitionValue(record.getId()) - .sortValue(record.getId2()) - .build(); - mappedTable.putItem(record); - - GetItemEnhancedResponse response = mappedTable.getItemWithResponse( - req -> req.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) - ); - ConsumedCapacity consumedCapacity = response.consumedCapacity(); - assertThat(consumedCapacity).isNotNull(); - // An eventually consistent read request of an item up to 4 KB requires one-half read request unit. - assertThat(consumedCapacity.capacityUnits()).isCloseTo(10.0, Offset.offset(1.0)); - } - - @Test - public void getItem_withReturnConsumedCapacity_stronglyConsistent() { - Record record = new Record().setId(101).setId2(102).setStringAttr1(getStringAttrValue(80 * 1024)); - Key key = Key.builder() - .partitionValue(record.getId()) - .sortValue(record.getId2()) - .build(); - mappedTable.putItem(record); - - GetItemEnhancedResponse response = mappedTable.getItemWithResponse( - req -> req.key(key).returnConsumedCapacity(ReturnConsumedCapacity.TOTAL).consistentRead(true) - ); - ConsumedCapacity consumedCapacity = response.consumedCapacity(); - assertThat(consumedCapacity).isNotNull(); - // A strongly consistent read request of an item up to 4 KB requires one read request unit. - assertThat(consumedCapacity.capacityUnits()).isCloseTo(20.0, Offset.offset(1.0)); - } - - private static String getStringAttrValue(int numChars) { - char[] chars = new char[numChars]; - Arrays.fill(chars, 'a'); - return new String(chars); - } - - private static final class Record { - private Integer id; - private Integer id2; - private String stringAttr1; - - private Integer getId() { - return id; - } - - private Record setId(Integer id) { - this.id = id; - return this; - } - - private Integer getId2() { - return id2; - } - - private Record setId2(Integer id2) { - this.id2 = id2; - return this; - } - - private String getStringAttr1() { - return stringAttr1; - } - - private Record setStringAttr1(String stringAttr1) { - this.stringAttr1 = stringAttr1; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return Objects.equals(id, record.id) - && Objects.equals(id2, record.id2) - && Objects.equals(stringAttr1, record.stringAttr1); - } - - @Override - public int hashCode() { - return Objects.hash(id, id2, stringAttr1); - } - } -} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/PutItemWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/PutItemWithResponseIntegrationTest.java deleted file mode 100644 index 24f67a92d78e..000000000000 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/PutItemWithResponseIntegrationTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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 static org.assertj.core.api.Assertions.assertThat; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; - -import java.util.Objects; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; -import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex; -import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest; -import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedResponse; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.Projection; -import software.amazon.awssdk.services.dynamodb.model.ProjectionType; -import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics; - -public class PutItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { - private static class Record { - private Integer id; - private Integer id2; - private String stringAttr1; - - private Integer getId() { - return id; - } - - private Record setId(Integer id) { - this.id = id; - return this; - } - - private Integer getId2() { - return id2; - } - - private Record setId2(Integer id2) { - this.id2 = id2; - return this; - } - - private String getStringAttr1() { - return stringAttr1; - } - - private Record setStringAttr1(String stringAttr1) { - this.stringAttr1 = stringAttr1; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return Objects.equals(id, record.id) - && Objects.equals(id2, record.id2) - && Objects.equals(stringAttr1, record.stringAttr1); - } - - @Override - public int hashCode() { - return Objects.hash(id, id2, stringAttr1); - } - } - - private static final String TABLE_NAME = createTestTableName(); - - private static final TableSchema TABLE_SCHEMA = - StaticTableSchema.builder(Record.class) - .newItemSupplier(Record::new) - .addAttribute(Integer.class, a -> a.name("id_1") - .getter(Record::getId) - .setter(Record::setId) - .tags(primaryPartitionKey(), secondaryPartitionKey("index1"))) - .addAttribute(Integer.class, a -> a.name("id_2") - .getter(Record::getId2) - .setter(Record::setId2) - .tags(primarySortKey(), secondarySortKey("index1"))) - .addAttribute(String.class, a -> a.name("stringAttr1") - .getter(Record::getStringAttr1) - .setter(Record::setStringAttr1)) - .build(); - - private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = EnhancedLocalSecondaryIndex.builder() - .indexName("index1") - .projection(Projection.builder() - .projectionType(ProjectionType.ALL) - .build()) - .build(); - - private static DynamoDbClient dynamoDbClient; - private static DynamoDbEnhancedClient enhancedClient; - private static DynamoDbTable mappedTable; - - @BeforeClass - public static void setup() { - dynamoDbClient = createDynamoDbClient(); - enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build(); - mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); - mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)); - dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)); - } - - @AfterClass - public static void teardown() { - try { - dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); - } finally { - dynamoDbClient.close(); - } - } - - @Test - public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNull() { - Record record = new Record().setId(1).setId2(10); - PutItemEnhancedRequest request = PutItemEnhancedRequest.builder(Record.class) - .item(record) - .build(); - - PutItemEnhancedResponse response = mappedTable.putItemWithResponse(request); - - assertThat(response.itemCollectionMetrics()).isNull(); - } - - @Test - public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() { - Record record = new Record().setId(1).setId2(10); - PutItemEnhancedRequest request = PutItemEnhancedRequest.builder(Record.class) - .item(record) - .returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE) - .build(); - - PutItemEnhancedResponse response = mappedTable.putItemWithResponse(request); - - assertThat(response.itemCollectionMetrics()).isNotNull(); - } -} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/ScanQueryIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/ScanQueryIntegrationTest.java new file mode 100644 index 000000000000..5ca76dec9011 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/ScanQueryIntegrationTest.java @@ -0,0 +1,191 @@ +/* + * 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 static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +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.sortBetween; +import static software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional.sortGreaterThan; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import software.amazon.awssdk.enhanced.dynamodb.model.Page; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.Record; +import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity; +import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; + +public class ScanQueryIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { + + private static final String TABLE_NAME = createTestTableName(); + + private static DynamoDbClient dynamoDbClient; + private static DynamoDbEnhancedClient enhancedClient; + private static DynamoDbTable mappedTable; + + @BeforeClass + public static void setup() { + dynamoDbClient = createDynamoDbClient(); + enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build(); + mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); + mappedTable.createTable(); + dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)); + } + + @AfterClass + public static void teardown() { + try { + dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); + } finally { + dynamoDbClient.close(); + } + } + + private void insertRecords() { + RECORDS.forEach(record -> mappedTable.putItem(r -> r.item(record))); + } + + @Test + public void scan_withoutReturnConsumedCapacity_checksPageCount() { + insertRecords(); + + Iterator> results = mappedTable.scan(ScanEnhancedRequest.builder().limit(5).build()) + .iterator(); + Page page1 = results.next(); + assertThat(results.hasNext(), is(true)); + Page page2 = results.next(); + assertThat(results.hasNext(), is(false)); + + assertThat(page1.items(), is(RECORDS.subList(0, 5))); + assertThat(page1.consumedCapacity(), is(nullValue())); + assertThat(page1.lastEvaluatedKey(), is(getKeyMap(4))); + assertThat(page1.count(), equalTo(5)); + assertThat(page1.scannedCount(), equalTo(5)); + + assertThat(page2.items(), is(RECORDS.subList(5, 9))); + assertThat(page2.lastEvaluatedKey(), is(nullValue())); + assertThat(page2.count(), equalTo(4)); + assertThat(page2.scannedCount(), equalTo(4)); + } + + @Test + public void scan_withReturnConsumedCapacityAndDifferentReadConsistency_checksConsumedCapacity() { + insertRecords(); + + Iterator> eventualConsistencyResult = + mappedTable.scan(ScanEnhancedRequest.builder().returnConsumedCapacity(ReturnConsumedCapacity.TOTAL).build()) + .iterator(); + + Page page = eventualConsistencyResult.next(); + assertThat(eventualConsistencyResult.hasNext(), is(false)); + ConsumedCapacity eventualConsumedCapacity = page.consumedCapacity(); + assertThat(eventualConsumedCapacity, is(notNullValue())); + + Iterator> strongConsistencyResult = + mappedTable.scan(ScanEnhancedRequest.builder().returnConsumedCapacity(ReturnConsumedCapacity.TOTAL).build()) + .iterator(); + + page = strongConsistencyResult.next(); + assertThat(strongConsistencyResult.hasNext(), is(false)); + ConsumedCapacity strongConsumedCapacity = page.consumedCapacity(); + assertThat(strongConsumedCapacity, is(notNullValue())); + + assertThat(strongConsumedCapacity.capacityUnits(), is(greaterThanOrEqualTo(eventualConsumedCapacity.capacityUnits()))); + } + + @Test + public void query_withoutReturnConsumedCapacity_checksPageCount() { + insertRecords(); + + Iterator> results = + mappedTable.query(QueryEnhancedRequest.builder() + .queryConditional(sortBetween(k-> k.partitionValue("id-value").sortValue(2), + k-> k.partitionValue("id-value").sortValue(6))) + .limit(3) + .build()) + .iterator(); + + Page page1 = results.next(); + assertThat(results.hasNext(), is(true)); + Page page2 = results.next(); + assertThat(results.hasNext(), is(false)); + + assertThat(page1.items(), is(RECORDS.subList(2, 5))); + assertThat(page1.consumedCapacity(), is(nullValue())); + assertThat(page1.lastEvaluatedKey(), is(getKeyMap(4))); + assertThat(page1.count(), equalTo(3)); + assertThat(page1.scannedCount(), equalTo(3)); + + assertThat(page2.items(), is(RECORDS.subList(5, 7))); + assertThat(page2.lastEvaluatedKey(), is(nullValue())); + assertThat(page2.count(), equalTo(2)); + assertThat(page2.scannedCount(), equalTo(2)); + } + + @Test + public void query_withReturnConsumedCapacityAndDifferentReadConsistency_checksConsumedCapacity() { + insertRecords(); + + Iterator> eventualConsistencyResult = + mappedTable.query(QueryEnhancedRequest.builder() + .queryConditional(sortGreaterThan(k -> k.partitionValue("id-value").sortValue(3))) + .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) + .build()) + .iterator(); + + Page page = eventualConsistencyResult.next(); + assertThat(eventualConsistencyResult.hasNext(), is(false)); + ConsumedCapacity eventualConsumedCapacity = page.consumedCapacity(); + assertThat(eventualConsumedCapacity, is(notNullValue())); + + Iterator> strongConsistencyResult = + mappedTable.query(QueryEnhancedRequest.builder() + .queryConditional(sortGreaterThan(k -> k.partitionValue("id-value").sortValue(3))) + .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) + .build()) + .iterator(); + + page = strongConsistencyResult.next(); + assertThat(strongConsistencyResult.hasNext(), is(false)); + ConsumedCapacity strongConsumedCapacity = page.consumedCapacity(); + assertThat(strongConsumedCapacity, is(notNullValue())); + + assertThat(strongConsumedCapacity.capacityUnits(), is(greaterThanOrEqualTo(eventualConsumedCapacity.capacityUnits()))); + } + + private Map getKeyMap(int sort) { + Map result = new HashMap<>(); + result.put("id", stringValue(RECORDS.get(sort).getId())); + result.put("sort", numberValue(RECORDS.get(sort).getSort())); + return Collections.unmodifiableMap(result); + } +} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/UpdateItemWithResponseIntegrationTest.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/UpdateItemWithResponseIntegrationTest.java deleted file mode 100644 index c8767edaf216..000000000000 --- a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/UpdateItemWithResponseIntegrationTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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 static org.assertj.core.api.Assertions.assertThat; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.primarySortKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey; -import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; - -import java.util.Objects; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema; -import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex; -import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedRequest; -import software.amazon.awssdk.enhanced.dynamodb.model.UpdateItemEnhancedResponse; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.Projection; -import software.amazon.awssdk.services.dynamodb.model.ProjectionType; -import software.amazon.awssdk.services.dynamodb.model.ReturnItemCollectionMetrics; - -public class UpdateItemWithResponseIntegrationTest extends DynamoDbEnhancedIntegrationTestBase { - private static class Record { - private Integer id; - private Integer id2; - private String stringAttr1; - - private Integer getId() { - return id; - } - - private Record setId(Integer id) { - this.id = id; - return this; - } - - private Integer getId2() { - return id2; - } - - private Record setId2(Integer id2) { - this.id2 = id2; - return this; - } - - private String getStringAttr1() { - return stringAttr1; - } - - private Record setStringAttr1(String stringAttr1) { - this.stringAttr1 = stringAttr1; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Record record = (Record) o; - return Objects.equals(id, record.id) - && Objects.equals(id2, record.id2) - && Objects.equals(stringAttr1, record.stringAttr1); - } - - @Override - public int hashCode() { - return Objects.hash(id, id2, stringAttr1); - } - } - - private static final String TABLE_NAME = createTestTableName(); - - private static final TableSchema TABLE_SCHEMA = - StaticTableSchema.builder(Record.class) - .newItemSupplier(Record::new) - .addAttribute(Integer.class, a -> a.name("id_1") - .getter(Record::getId) - .setter(Record::setId) - .tags(primaryPartitionKey(), secondaryPartitionKey("index1"))) - .addAttribute(Integer.class, a -> a.name("id_2") - .getter(Record::getId2) - .setter(Record::setId2) - .tags(primarySortKey(), secondarySortKey("index1"))) - .addAttribute(String.class, a -> a.name("stringAttr1") - .getter(Record::getStringAttr1) - .setter(Record::setStringAttr1)) - .build(); - - private static final EnhancedLocalSecondaryIndex LOCAL_SECONDARY_INDEX = EnhancedLocalSecondaryIndex.builder() - .indexName("index1") - .projection(Projection.builder() - .projectionType(ProjectionType.ALL) - .build()) - .build(); - - private static DynamoDbClient dynamoDbClient; - private static DynamoDbEnhancedClient enhancedClient; - private static DynamoDbTable mappedTable; - - @BeforeClass - public static void setup() { - dynamoDbClient = createDynamoDbClient(); - enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build(); - mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); - mappedTable.createTable(r -> r.localSecondaryIndices(LOCAL_SECONDARY_INDEX)); - dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)); - } - - @AfterClass - public static void teardown() { - try { - dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); - } finally { - dynamoDbClient.close(); - } - } - - @Test - public void updateItem_returnItemCollectionMetrics_set_itemCollectionMetricsNull() { - Record record = new Record().setId(1).setId2(10); - UpdateItemEnhancedRequest request = UpdateItemEnhancedRequest.builder(Record.class) - .item(record) - .build(); - - UpdateItemEnhancedResponse response = mappedTable.updateItemWithResponse(request); - - assertThat(response.itemCollectionMetrics()).isNull(); - } - - @Test - public void putItem_returnItemCollectionMetrics_set_itemCollectionMetricsNotNull() { - Record record = new Record().setId(1).setId2(10); - UpdateItemEnhancedRequest request = UpdateItemEnhancedRequest.builder(Record.class) - .item(record) - .returnItemCollectionMetrics(ReturnItemCollectionMetrics.SIZE) - .build(); - - UpdateItemEnhancedResponse response = mappedTable.updateItemWithResponse(request); - - assertThat(response.itemCollectionMetrics()).isNotNull(); - } -} diff --git a/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/Record.java b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/Record.java new file mode 100644 index 000000000000..962b5d8a10f0 --- /dev/null +++ b/services-custom/dynamodb-enhanced/src/it/java/software/amazon/awssdk/enhanced/dynamodb/model/Record.java @@ -0,0 +1,101 @@ +/* + * 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.model; + +import java.util.Objects; + +public class Record { + + private String id; + private Integer sort; + private Integer value; + private String gsiId; + private Integer gsiSort; + + private String stringAttribute; + + public String getId() { + return id; + } + + public Record setId(String id) { + this.id = id; + return this; + } + + public Integer getSort() { + return sort; + } + + public Record setSort(Integer sort) { + this.sort = sort; + return this; + } + + public Integer getValue() { + return value; + } + + public Record setValue(Integer value) { + this.value = value; + return this; + } + + public String getGsiId() { + return gsiId; + } + + public Record setGsiId(String gsiId) { + this.gsiId = gsiId; + return this; + } + + public Integer getGsiSort() { + return gsiSort; + } + + public Record setGsiSort(Integer gsiSort) { + this.gsiSort = gsiSort; + return this; + } + + public String getStringAttribute() { + return stringAttribute; + } + + public Record setStringAttribute(String stringAttribute) { + this.stringAttribute = stringAttribute; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Record record = (Record) o; + return Objects.equals(id, record.id) && + Objects.equals(sort, record.sort) && + Objects.equals(value, record.value) && + Objects.equals(gsiId, record.gsiId) && + Objects.equals(stringAttribute, record.stringAttribute) && + Objects.equals(gsiSort, record.gsiSort); + } + + @Override + public int hashCode() { + return Objects.hash(id, sort, value, gsiId, gsiSort, stringAttribute); + } +} diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtils.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtils.java index 1036de6eb642..afd719d5a82a 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtils.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtils.java @@ -33,6 +33,7 @@ import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext; import software.amazon.awssdk.enhanced.dynamodb.model.Page; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity; @SdkInternalApi public final class EnhancedClientUtils { @@ -110,28 +111,29 @@ public static Page readAndTransformPaginatedItems( OperationContext operationContext, DynamoDbEnhancedClientExtension dynamoDbEnhancedClientExtension, Function>> getItems, - Function> getLastEvaluatedKey) { - - if (getLastEvaluatedKey.apply(response) == null || getLastEvaluatedKey.apply(response).isEmpty()) { - // Last page - return Page.create(getItems.apply(response) - .stream() - .map(itemMap -> readAndTransformSingleItem(itemMap, - tableSchema, - operationContext, - dynamoDbEnhancedClientExtension)) - .collect(Collectors.toList())); - } else { - // More pages to come; add the lastEvaluatedKey - return Page.create(getItems.apply(response) - .stream() - .map(itemMap -> readAndTransformSingleItem(itemMap, - tableSchema, - operationContext, - dynamoDbEnhancedClientExtension)) - .collect(Collectors.toList()), - getLastEvaluatedKey.apply(response)); + Function> getLastEvaluatedKey, + Function count, + Function scannedCount, + Function consumedCapacity) { + + List collect = getItems.apply(response) + .stream() + .map(itemMap -> readAndTransformSingleItem(itemMap, + tableSchema, + operationContext, + dynamoDbEnhancedClientExtension)) + .collect(Collectors.toList()); + + Page.Builder pageBuilder = Page.builder(tableSchema.itemType().rawClass()) + .items(collect) + .count(count.apply(response)) + .scannedCount(scannedCount.apply(response)) + .consumedCapacity(consumedCapacity.apply(response)); + + if (getLastEvaluatedKey.apply(response) != null && !getLastEvaluatedKey.apply(response).isEmpty()) { + pageBuilder.lastEvaluatedKey(getLastEvaluatedKey.apply(response)); } + return pageBuilder.build(); } public static Key createKeyFromItem(T item, TableSchema tableSchema, String indexName) { diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/QueryOperation.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/QueryOperation.java index 8788cc4f56f5..826feabe4022 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/QueryOperation.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/QueryOperation.java @@ -83,6 +83,7 @@ public QueryRequest generateRequest(TableSchema tableSchema, .limit(this.request.limit()) .exclusiveStartKey(this.request.exclusiveStartKey()) .consistentRead(this.request.consistentRead()) + .returnConsumedCapacity(this.request.returnConsumedCapacity()) .projectionExpression(projectionExpressionAsString); if (!TableMetadata.primaryIndexName().equals(operationContext.indexName())) { @@ -117,7 +118,10 @@ public Page transformResponse(QueryResponse response, context, dynamoDbEnhancedClientExtension, QueryResponse::items, - QueryResponse::lastEvaluatedKey); + QueryResponse::lastEvaluatedKey, + QueryResponse::count, + QueryResponse::scannedCount, + QueryResponse::consumedCapacity); } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/ScanOperation.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/ScanOperation.java index da58e38caf40..e8714a5ac54b 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/ScanOperation.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/operations/ScanOperation.java @@ -81,6 +81,7 @@ public ScanRequest generateRequest(TableSchema tableSchema, .totalSegments(this.request.totalSegments()) .exclusiveStartKey(this.request.exclusiveStartKey()) .consistentRead(this.request.consistentRead()) + .returnConsumedCapacity(this.request.returnConsumedCapacity()) .expressionAttributeValues(expressionValues) .expressionAttributeNames(expressionNames) .projectionExpression(projectionExpressionAsString); @@ -107,7 +108,10 @@ public Page transformResponse(ScanResponse response, context, dynamoDbEnhancedClientExtension, ScanResponse::items, - ScanResponse::lastEvaluatedKey); + ScanResponse::lastEvaluatedKey, + ScanResponse::count, + ScanResponse::scannedCount, + ScanResponse::consumedCapacity); } @Override diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/Page.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/Page.java index a10808dffa5c..f9c4ba7dcc0e 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/Page.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/Page.java @@ -15,11 +15,14 @@ package software.amazon.awssdk.enhanced.dynamodb.model; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity; import software.amazon.awssdk.utils.ToString; /** @@ -33,30 +36,47 @@ public final class Page { private final List items; private final Map lastEvaluatedKey; + private final Integer count; + private final Integer scannedCount; + private final ConsumedCapacity consumedCapacity; private Page(List items, Map lastEvaluatedKey) { this.items = items; this.lastEvaluatedKey = lastEvaluatedKey; + this.count = null; + this.scannedCount = null; + this.consumedCapacity = null; + } + + private Page(Builder builder) { + this.items = builder.items; + this.lastEvaluatedKey = builder.lastEvaluatedKey; + this.count = builder.count; + this.scannedCount = builder.scannedCount; + this.consumedCapacity = builder.consumedCapacity; } /** - * Static constructor for this object. + * Static constructor for this object. Deprecated in favor of using the builder() pattern to construct this object. + * * @param items A list of items to store for the page. * @param lastEvaluatedKey A 'lastEvaluatedKey' to store for the page. * @param The modelled type of the object that has been read. * @return A newly constructed {@link Page} object. */ + @Deprecated public static Page create(List items, Map lastEvaluatedKey) { return new Page<>(items, lastEvaluatedKey); } /** * Static constructor for this object that sets a null 'lastEvaluatedKey' which indicates this is the final page - * of results. + * of results. Deprecated in favor of using the builder() pattern to construct this object. * @param items A list of items to store for the page. * @param The modelled type of the object that has been read. * @return A newly constructed {@link Page} object. */ + @Deprecated public static Page create(List items) { return new Page<>(items, null); } @@ -78,6 +98,31 @@ public Map lastEvaluatedKey() { return lastEvaluatedKey; } + /** + * The count of the returned items from the last page query or scan, after any filters were applied. + */ + public Integer count() { + return count; + } + + /** + * The scanned count of the returned items from the last page query or scan, before any filters were applied. + * This number will be equal or greater than the count. + */ + public Integer scannedCount() { + return scannedCount; + } + + /** + * Returns the capacity units consumed by the last page query or scan. Will only be returned if it has been + * explicitly requested by the user when calling the operation. + * + * @return The 'consumedCapacity' from the last query or scan operation or null if it was not requested. + */ + public ConsumedCapacity consumedCapacity() { + return consumedCapacity; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -92,13 +137,25 @@ public boolean equals(Object o) { if (items != null ? ! items.equals(page.items) : page.items != null) { return false; } - return lastEvaluatedKey != null ? lastEvaluatedKey.equals(page.lastEvaluatedKey) : page.lastEvaluatedKey == null; + if (lastEvaluatedKey != null ? ! lastEvaluatedKey.equals(page.lastEvaluatedKey) : page.lastEvaluatedKey != null) { + return false; + } + if (consumedCapacity != null ? ! consumedCapacity.equals(page.consumedCapacity) : page.consumedCapacity != null) { + return false; + } + if (count != null ? ! count.equals(page.count) : page.count != null) { + return false; + } + return scannedCount != null ? scannedCount.equals(page.scannedCount) : page.scannedCount == null; } @Override public int hashCode() { int result = items != null ? items.hashCode() : 0; result = 31 * result + (lastEvaluatedKey != null ? lastEvaluatedKey.hashCode() : 0); + result = 31 * result + (consumedCapacity != null ? consumedCapacity.hashCode() : 0); + result = 31 * result + (count != null ? count.hashCode() : 0); + result = 31 * result + (scannedCount != null ? scannedCount.hashCode() : 0); return result; } @@ -109,4 +166,45 @@ public String toString() { .add("items", items) .build(); } + + public static Builder builder(Class itemClass) { + return new Builder<>(); + } + + public static final class Builder { + private List items; + private Map lastEvaluatedKey; + private Integer count; + private Integer scannedCount; + private ConsumedCapacity consumedCapacity; + + public Builder items(List items) { + this.items = new ArrayList<>(items); + return this; + } + + public Builder lastEvaluatedKey(Map lastEvaluatedKey) { + this.lastEvaluatedKey = new HashMap<>(lastEvaluatedKey); + return this; + } + + public Builder count(Integer count) { + this.count = count; + return this; + } + + public Builder scannedCount(Integer scannedCount) { + this.scannedCount = scannedCount; + return this; + } + + public Builder consumedCapacity(ConsumedCapacity consumedCapacity) { + this.consumedCapacity = consumedCapacity; + return this; + } + + public Page build() { + return new Page(this); + } + } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequest.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequest.java index fbc4736cae3c..21b81868d44f 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequest.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/QueryEnhancedRequest.java @@ -31,6 +31,9 @@ import software.amazon.awssdk.enhanced.dynamodb.Expression; import software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; +import software.amazon.awssdk.services.dynamodb.model.ScanRequest; import software.amazon.awssdk.utils.Validate; /** @@ -53,6 +56,7 @@ public final class QueryEnhancedRequest { private final Boolean consistentRead; private final Expression filterExpression; private final List attributesToProject; + private final String returnConsumedCapacity; private QueryEnhancedRequest(Builder builder) { this.queryConditional = builder.queryConditional; @@ -61,6 +65,7 @@ private QueryEnhancedRequest(Builder builder) { this.limit = builder.limit; this.consistentRead = builder.consistentRead; this.filterExpression = builder.filterExpression; + this.returnConsumedCapacity = builder.returnConsumedCapacity; this.attributesToProject = builder.attributesToProject != null ? Collections.unmodifiableList(builder.attributesToProject) : null; @@ -78,12 +83,13 @@ public static Builder builder() { */ public Builder toBuilder() { return builder().queryConditional(queryConditional) - .exclusiveStartKey(exclusiveStartKey) - .scanIndexForward(scanIndexForward) - .limit(limit) - .consistentRead(consistentRead) - .filterExpression(filterExpression) - .addNestedAttributesToProject(attributesToProject); + .exclusiveStartKey(exclusiveStartKey) + .scanIndexForward(scanIndexForward) + .limit(limit) + .consistentRead(consistentRead) + .filterExpression(filterExpression) + .returnConsumedCapacity(returnConsumedCapacity) + .addNestedAttributesToProject(attributesToProject); } /** @@ -149,6 +155,26 @@ public List nestedAttributesToProject() { return attributesToProject; } + + /** + * Whether to return the capacity consumed by this operation. + * + * @see ScanRequest#returnConsumedCapacity() + */ + public ReturnConsumedCapacity returnConsumedCapacity() { + return ReturnConsumedCapacity.fromValue(returnConsumedCapacity); + } + + /** + * Whether to return the capacity consumed by this operation. + *

+ * Similar to {@link #returnConsumedCapacity()} but return the value as a string. This is useful in situations where the + * value is not defined in {@link ReturnConsumedCapacity}. + */ + public String returnConsumedCapacityAsString() { + return returnConsumedCapacity; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -182,6 +208,10 @@ public boolean equals(Object o) { ? !attributesToProject.equals(query.attributesToProject) : query.attributesToProject != null) { return false; } + if (returnConsumedCapacity != null + ? !returnConsumedCapacity.equals(query.returnConsumedCapacity) : query.returnConsumedCapacity != null) { + return false; + } return filterExpression != null ? filterExpression.equals(query.filterExpression) : query.filterExpression == null; } @@ -194,6 +224,7 @@ public int hashCode() { result = 31 * result + (consistentRead != null ? consistentRead.hashCode() : 0); result = 31 * result + (filterExpression != null ? filterExpression.hashCode() : 0); result = 31 * result + (attributesToProject != null ? attributesToProject.hashCode() : 0); + result = 31 * result + (returnConsumedCapacity != null ? returnConsumedCapacity.hashCode() : 0); return result; } @@ -211,6 +242,7 @@ public static final class Builder { private Boolean consistentRead; private Expression filterExpression; private List attributesToProject; + private String returnConsumedCapacity; private Builder() { } @@ -419,6 +451,27 @@ public Builder addNestedAttributeToProject(NestedAttributeName nestedAttributeNa return this; } + + /** + * Whether to return the capacity consumed by this operation. + * + * @see QueryRequest.Builder#returnConsumedCapacity(ReturnConsumedCapacity) + */ + public Builder returnConsumedCapacity(ReturnConsumedCapacity returnConsumedCapacity) { + this.returnConsumedCapacity = returnConsumedCapacity == null ? null : returnConsumedCapacity.toString(); + return this; + } + + /** + * Whether to return the capacity consumed by this operation. + * + * @see QueryRequest.Builder#returnConsumedCapacity(String) + */ + public Builder returnConsumedCapacity(String returnConsumedCapacity) { + this.returnConsumedCapacity = returnConsumedCapacity; + return this; + } + public QueryEnhancedRequest build() { return new QueryEnhancedRequest(this); } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.java index 09b8a55f1f19..624e9beaa9ba 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.java @@ -30,6 +30,8 @@ import software.amazon.awssdk.enhanced.dynamodb.Expression; import software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; +import software.amazon.awssdk.services.dynamodb.model.ScanRequest; import software.amazon.awssdk.utils.Validate; /** @@ -49,6 +51,7 @@ public final class ScanEnhancedRequest { private final List attributesToProject; private final Integer segment; private final Integer totalSegments; + private final String returnConsumedCapacity; private ScanEnhancedRequest(Builder builder) { this.exclusiveStartKey = builder.exclusiveStartKey; @@ -57,6 +60,7 @@ private ScanEnhancedRequest(Builder builder) { this.totalSegments = builder.totalSegments; this.consistentRead = builder.consistentRead; this.filterExpression = builder.filterExpression; + this.returnConsumedCapacity = builder.returnConsumedCapacity; this.attributesToProject = builder.attributesToProject != null ? Collections.unmodifiableList(builder.attributesToProject) : null; @@ -74,10 +78,13 @@ public static Builder builder() { */ public Builder toBuilder() { return builder().exclusiveStartKey(exclusiveStartKey) - .limit(limit) - .consistentRead(consistentRead) - .filterExpression(filterExpression) - .addNestedAttributesToProject(attributesToProject); + .limit(limit) + .segment(segment) + .totalSegments(totalSegments) + .consistentRead(consistentRead) + .filterExpression(filterExpression) + .returnConsumedCapacity(returnConsumedCapacity) + .addNestedAttributesToProject(attributesToProject); } /** @@ -143,6 +150,24 @@ public List nestedAttributesToProject() { return attributesToProject; } + /** + * Whether to return the capacity consumed by this operation. + * + * @see ScanRequest#returnConsumedCapacity() + */ + public ReturnConsumedCapacity returnConsumedCapacity() { + return ReturnConsumedCapacity.fromValue(returnConsumedCapacity); + } + + /** + * Whether to return the capacity consumed by this operation. + *

+ * Similar to {@link #returnConsumedCapacity()} but return the value as a string. This is useful in situations where the + * value is not defined in {@link ReturnConsumedCapacity}. + */ + public String returnConsumedCapacityAsString() { + return returnConsumedCapacity; + } @Override public boolean equals(Object o) { @@ -175,6 +200,10 @@ public boolean equals(Object o) { ? !attributesToProject.equals(scan.attributesToProject) : scan.attributesToProject != null) { return false; } + if (returnConsumedCapacity != null + ? !returnConsumedCapacity.equals(scan.returnConsumedCapacity) : scan.returnConsumedCapacity != null) { + return false; + } return filterExpression != null ? filterExpression.equals(scan.filterExpression) : scan.filterExpression == null; } @@ -187,6 +216,7 @@ public int hashCode() { result = 31 * result + (consistentRead != null ? consistentRead.hashCode() : 0); result = 31 * result + (filterExpression != null ? filterExpression.hashCode() : 0); result = 31 * result + (attributesToProject != null ? attributesToProject.hashCode() : 0); + result = 31 * result + (returnConsumedCapacity != null ? returnConsumedCapacity.hashCode() : 0); return result; } @@ -202,6 +232,7 @@ public static final class Builder { private List attributesToProject; private Integer segment; private Integer totalSegments; + private String returnConsumedCapacity; private Builder() { } @@ -428,6 +459,26 @@ public Builder addNestedAttributeToProject(NestedAttributeName nestedAttributeNa return this; } + /** + * Whether to return the capacity consumed by this operation. + * + * @see ScanRequest.Builder#returnConsumedCapacity(ReturnConsumedCapacity) + */ + public Builder returnConsumedCapacity(ReturnConsumedCapacity returnConsumedCapacity) { + this.returnConsumedCapacity = returnConsumedCapacity == null ? null : returnConsumedCapacity.toString(); + return this; + } + + /** + * Whether to return the capacity consumed by this operation. + * + * @see ScanRequest.Builder#returnConsumedCapacity(String) + */ + public Builder returnConsumedCapacity(String returnConsumedCapacity) { + this.returnConsumedCapacity = returnConsumedCapacity; + return this; + } + public ScanEnhancedRequest build() { return new ScanEnhancedRequest(this); } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/IndexQueryTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/IndexQueryTest.java index e82e340e90ac..82182ffad4e4 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/IndexQueryTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/IndexQueryTest.java @@ -17,7 +17,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.numberValue; import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue; @@ -51,6 +53,7 @@ import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest; import software.amazon.awssdk.services.dynamodb.model.ProjectionType; +import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class IndexQueryTest extends LocalDynamoDbSyncTestBase { private static class Record { @@ -211,6 +214,7 @@ public void queryAllRecordsDefaultSettings_usingShortcutForm() { assertThat(page.items(), is(KEYS_ONLY_RECORDS)); assertThat(page.lastEvaluatedKey(), is(nullValue())); + assertThat(page.consumedCapacity(), is(nullValue())); } @Test @@ -219,7 +223,9 @@ public void queryBetween() { Key fromKey = Key.builder().partitionValue("gsi-id-value").sortValue(3).build(); Key toKey = Key.builder().partitionValue("gsi-id-value").sortValue(5).build(); Iterator> results = - keysOnlyMappedIndex.query(r -> r.queryConditional(QueryConditional.sortBetween(fromKey, toKey))).iterator(); + keysOnlyMappedIndex.query(r -> r.queryConditional(QueryConditional.sortBetween(fromKey, toKey)) + .returnConsumedCapacity(ReturnConsumedCapacity.INDEXES)) + .iterator(); assertThat(results.hasNext(), is(true)); Page page = results.next(); @@ -228,6 +234,13 @@ public void queryBetween() { assertThat(page.items(), is(KEYS_ONLY_RECORDS.stream().filter(r -> r.sort >= 3 && r.sort <= 5).collect(Collectors.toList()))); assertThat(page.lastEvaluatedKey(), is(nullValue())); + + assertThat(page.consumedCapacity(), is(notNullValue())); + assertThat(page.consumedCapacity().capacityUnits(), is(notNullValue())); + assertThat(page.consumedCapacity().globalSecondaryIndexes(), is(notNullValue())); + + assertThat(page.count(), equalTo(3)); + assertThat(page.scannedCount(), equalTo(3)); } @Test @@ -261,10 +274,18 @@ public void queryLimit() { assertThat(page1.items(), is(KEYS_ONLY_RECORDS.subList(0, 5))); assertThat(page1.lastEvaluatedKey(), is(expectedLastEvaluatedKey1)); + assertThat(page1.count(), equalTo(5)); + assertThat(page1.scannedCount(), equalTo(5)); + assertThat(page2.items(), is(KEYS_ONLY_RECORDS.subList(5, 10))); assertThat(page2.lastEvaluatedKey(), is(expectedLastEvaluatedKey2)); + assertThat(page2.count(), equalTo(5)); + assertThat(page2.scannedCount(), equalTo(5)); + assertThat(page3.items(), is(empty())); assertThat(page3.lastEvaluatedKey(), is(nullValue())); + assertThat(page3.count(), equalTo(0)); + assertThat(page3.scannedCount(), equalTo(0)); } @Test @@ -276,6 +297,8 @@ public void queryEmpty() { assertThat(results.hasNext(), is(false)); assertThat(page.items(), is(empty())); assertThat(page.lastEvaluatedKey(), is(nullValue())); + assertThat(page.count(), equalTo(0)); + assertThat(page.scannedCount(), equalTo(0)); } @Test @@ -297,5 +320,7 @@ public void queryExclusiveStartKey() { assertThat(results.hasNext(), is(false)); assertThat(page.items(), is(KEYS_ONLY_RECORDS.subList(8, 10))); assertThat(page.lastEvaluatedKey(), is(nullValue())); + assertThat(page.count(), equalTo(2)); + assertThat(page.scannedCount(), equalTo(2)); } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/IndexScanTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/IndexScanTest.java index 9cc674ed489a..90205bfc6f75 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/IndexScanTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/IndexScanTest.java @@ -17,7 +17,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.numberValue; import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue; @@ -49,6 +51,7 @@ import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest; import software.amazon.awssdk.services.dynamodb.model.ProjectionType; +import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class IndexScanTest extends LocalDynamoDbSyncTestBase { private static class Record { @@ -198,7 +201,8 @@ public void deleteTable() { public void scanAllRecordsDefaultSettings() { insertRecords(); - Iterator> results = keysOnlyMappedIndex.scan(ScanEnhancedRequest.builder().build()).iterator(); + Iterator> results = keysOnlyMappedIndex.scan(r -> r.returnConsumedCapacity(ReturnConsumedCapacity.INDEXES)) + .iterator(); assertThat(results.hasNext(), is(true)); Page page = results.next(); @@ -206,6 +210,13 @@ public void scanAllRecordsDefaultSettings() { assertThat(page.items(), is(KEYS_ONLY_RECORDS)); assertThat(page.lastEvaluatedKey(), is(nullValue())); + + assertThat(page.consumedCapacity(), is(notNullValue())); + assertThat(page.consumedCapacity().capacityUnits(), is(notNullValue())); + assertThat(page.consumedCapacity().globalSecondaryIndexes(), is(notNullValue())); + + assertThat(page.count(), equalTo(10)); + assertThat(page.scannedCount(), equalTo(10)); } @Test @@ -220,7 +231,9 @@ public void scanAllRecordsWithFilter() { .build(); Iterator> results = - keysOnlyMappedIndex.scan(ScanEnhancedRequest.builder().filterExpression(expression).build()).iterator(); + keysOnlyMappedIndex.scan(ScanEnhancedRequest.builder().filterExpression(expression) + .returnConsumedCapacity(ReturnConsumedCapacity.INDEXES).build()) + .iterator(); assertThat(results.hasNext(), is(true)); Page page = results.next(); @@ -229,6 +242,13 @@ public void scanAllRecordsWithFilter() { assertThat(page.items(), is(KEYS_ONLY_RECORDS.stream().filter(r -> r.sort >= 3 && r.sort <= 5).collect(Collectors.toList()))); assertThat(page.lastEvaluatedKey(), is(nullValue())); + + assertThat(page.consumedCapacity(), is(notNullValue())); + assertThat(page.consumedCapacity().capacityUnits(), is(notNullValue())); + assertThat(page.consumedCapacity().globalSecondaryIndexes(), is(notNullValue())); + + assertThat(page.count(), equalTo(3)); + assertThat(page.scannedCount(), equalTo(10)); } @Test @@ -244,11 +264,20 @@ public void scanLimit() { assertThat(results.hasNext(), is(false)); assertThat(page1.items(), is(KEYS_ONLY_RECORDS.subList(0, 5))); + assertThat(page1.consumedCapacity(), is(nullValue())); assertThat(page1.lastEvaluatedKey(), is(getKeyMap(4))); + assertThat(page1.count(), equalTo(5)); + assertThat(page1.scannedCount(), equalTo(5)); + assertThat(page2.items(), is(KEYS_ONLY_RECORDS.subList(5, 10))); assertThat(page2.lastEvaluatedKey(), is(getKeyMap(9))); + assertThat(page2.count(), equalTo(5)); + assertThat(page2.scannedCount(), equalTo(5)); + assertThat(page3.items(), is(empty())); assertThat(page3.lastEvaluatedKey(), is(nullValue())); + assertThat(page3.count(), equalTo(0)); + assertThat(page3.scannedCount(), equalTo(0)); } @Test @@ -259,6 +288,8 @@ public void scanEmpty() { assertThat(results.hasNext(), is(false)); assertThat(page.items(), is(empty())); assertThat(page.lastEvaluatedKey(), is(nullValue())); + assertThat(page.count(), equalTo(0)); + assertThat(page.scannedCount(), equalTo(0)); } @Test @@ -272,6 +303,8 @@ public void scanExclusiveStartKey() { assertThat(results.hasNext(), is(false)); assertThat(page.items(), is(KEYS_ONLY_RECORDS.subList(8, 10))); assertThat(page.lastEvaluatedKey(), is(nullValue())); + assertThat(page.count(), equalTo(2)); + assertThat(page.scannedCount(), equalTo(2)); } private Map getKeyMap(int sort) {