diff --git a/.changes/next-release/feature-DynamoDBEnhancedClient-078c185.json b/.changes/next-release/feature-DynamoDBEnhancedClient-078c185.json new file mode 100644 index 000000000000..70e7369aa998 --- /dev/null +++ b/.changes/next-release/feature-DynamoDBEnhancedClient-078c185.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "DynamoDB Enhanced Client", + "contributor": "", + "description": "Adds consistent read client configuration to DDB Enhanced Client" +} diff --git a/services-custom/dynamodb-enhanced/pom.xml b/services-custom/dynamodb-enhanced/pom.xml index 133dead6983a..dab9f325b879 100644 --- a/services-custom/dynamodb-enhanced/pom.xml +++ b/services-custom/dynamodb-enhanced/pom.xml @@ -233,5 +233,10 @@ so test + + org.mockito + mockito-junit-jupiter + test + 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 index f6c4d3fd40bf..cb9020639d4e 100644 --- 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 @@ -15,15 +15,14 @@ package software.amazon.awssdk.enhanced.dynamodb; -import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.concurrent.CompletionException; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedResponse; import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex; @@ -56,17 +55,24 @@ public class AsyncCrudWithResponseIntegrationTest extends DynamoDbEnhancedIntegr private static DynamoDbAsyncClient dynamoDbClient; private static DynamoDbEnhancedAsyncClient enhancedClient; private static DynamoDbAsyncTable mappedTable; + private static CapturingInterceptor capturingInterceptor; - @BeforeClass + @BeforeAll public static void beforeClass() { - dynamoDbClient = createAsyncDynamoDbClient(); - enhancedClient = DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(dynamoDbClient).build(); + capturingInterceptor = new CapturingInterceptor(); + dynamoDbClient = dynamoDbAsyncClientBuilder() + .overrideConfiguration(o -> o.addExecutionInterceptor(capturingInterceptor)) + .build(); + enhancedClient = DynamoDbEnhancedAsyncClient.builder() + .consistentRead(true) + .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(); } - @After + @AfterEach public void tearDown() { mappedTable.scan() .items() @@ -74,7 +80,12 @@ public void tearDown() { .join(); } - @AfterClass + @AfterEach + public void reset() { + capturingInterceptor.reset(); + } + + @AfterAll public static void afterClass() { try { dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)).join(); @@ -340,5 +351,22 @@ public void getItem_withoutReturnConsumedCapacity() { GetItemEnhancedResponse response = mappedTable.getItemWithResponse(req -> req.key(key)).join(); assertThat(response.consumedCapacity()).isNull(); + + assertThat(capturingInterceptor.getItemRequests.size()).isEqualTo(1); + assertThat(capturingInterceptor.getItemRequests.get(0).consistentRead()).isTrue(); + } + + @Test + public void getItem_consistentReadSetOnRequest_overridesClientValue() { + Record record = new Record().setId("101").setSort(102).setStringAttribute(getStringAttrValue(80_000)); + Key key = Key.builder() + .partitionValue(record.getId()) + .sortValue(record.getSort()) + .build(); + + mappedTable.getItemWithResponse(req -> req.consistentRead(false).key(key)).join(); + + assertThat(capturingInterceptor.getItemRequests.size()).isEqualTo(1); + assertThat(capturingInterceptor.getItemRequests.get(0).consistentRead()).isFalse(); } } 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 8a8e35470c20..aa37108a83a3 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 @@ -20,15 +20,25 @@ import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondaryPartitionKey; import static software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags.secondarySortKey; +import java.util.ArrayList; 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.core.SdkRequest; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; 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.DynamoDbAsyncClientBuilder; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.ScanRequest; import software.amazon.awssdk.testutils.service.AwsIntegrationTestBase; public abstract class DynamoDbEnhancedIntegrationTestBase extends AwsIntegrationTestBase { @@ -37,15 +47,21 @@ protected static String createTestTableName() { } protected static DynamoDbClient createDynamoDbClient() { - return DynamoDbClient.builder() - .credentialsProvider(getCredentialsProvider()) - .build(); + return dynamoDbClientBuilder().build(); } protected static DynamoDbAsyncClient createAsyncDynamoDbClient() { + return dynamoDbAsyncClientBuilder().build(); + } + + protected static DynamoDbClientBuilder dynamoDbClientBuilder() { + return DynamoDbClient.builder() + .credentialsProvider(getCredentialsProvider()); + } + + protected static DynamoDbAsyncClientBuilder dynamoDbAsyncClientBuilder() { return DynamoDbAsyncClient.builder() - .credentialsProvider(getCredentialsProvider()) - .build(); + .credentialsProvider(getCredentialsProvider()); } protected static final TableSchema TABLE_SCHEMA = @@ -102,4 +118,29 @@ protected static String getStringAttrValue(int numChars) { return new String(chars); } + protected static class CapturingInterceptor implements ExecutionInterceptor { + public final List getItemRequests = new ArrayList<>(); + public final List scanRequests = new ArrayList<>(); + public final List queryRequests = new ArrayList<>(); + + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + SdkRequest sdkRequest = context.request(); + if (sdkRequest instanceof GetItemRequest) { + getItemRequests.add((GetItemRequest) sdkRequest); + } + if (sdkRequest instanceof ScanRequest) { + scanRequests.add((ScanRequest) sdkRequest); + } + if (sdkRequest instanceof QueryRequest) { + queryRequests.add((QueryRequest) sdkRequest); + } + } + + public void reset() { + getItemRequests.clear(); + scanRequests.clear(); + queryRequests.clear(); + } + } } 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 index adfb9eddd2ad..4e860c76b437 100644 --- 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 @@ -30,9 +30,10 @@ 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 org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import software.amazon.awssdk.enhanced.dynamodb.model.Page; import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.Record; @@ -49,17 +50,39 @@ public class ScanQueryIntegrationTest extends DynamoDbEnhancedIntegrationTestBas private static DynamoDbClient dynamoDbClient; private static DynamoDbEnhancedClient enhancedClient; private static DynamoDbTable mappedTable; + private static CapturingInterceptor capturingInterceptor; - @BeforeClass + public static void main(String[] args) { + DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() + .consistentRead(true) + .dynamoDbClient(dynamoDbClient) + .build(); + + mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); + + } + + @BeforeAll public static void setup() { - dynamoDbClient = createDynamoDbClient(); - enhancedClient = DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDbClient).build(); + capturingInterceptor = new CapturingInterceptor(); + dynamoDbClient = dynamoDbClientBuilder() + .overrideConfiguration(o -> o.addExecutionInterceptor(capturingInterceptor)) + .build(); + enhancedClient = DynamoDbEnhancedClient.builder() + .consistentRead(true) + .dynamoDbClient(dynamoDbClient) + .build(); mappedTable = enhancedClient.table(TABLE_NAME, TABLE_SCHEMA); mappedTable.createTable(); dynamoDbClient.waiter().waitUntilTableExists(r -> r.tableName(TABLE_NAME)); } - @AfterClass + @AfterEach + public void reset() { + capturingInterceptor.reset(); + } + + @AfterAll public static void teardown() { try { dynamoDbClient.deleteTable(r -> r.tableName(TABLE_NAME)); @@ -100,7 +123,10 @@ public void scan_withReturnConsumedCapacityAndDifferentReadConsistency_checksCon insertRecords(); Iterator> eventualConsistencyResult = - mappedTable.scan(ScanEnhancedRequest.builder().returnConsumedCapacity(ReturnConsumedCapacity.TOTAL).build()) + mappedTable.scan(ScanEnhancedRequest.builder() + .consistentRead(false) + .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) + .build()) .iterator(); Page page = eventualConsistencyResult.next(); @@ -109,7 +135,10 @@ public void scan_withReturnConsumedCapacityAndDifferentReadConsistency_checksCon assertThat(eventualConsumedCapacity, is(notNullValue())); Iterator> strongConsistencyResult = - mappedTable.scan(ScanEnhancedRequest.builder().returnConsumedCapacity(ReturnConsumedCapacity.TOTAL).build()) + mappedTable.scan(ScanEnhancedRequest.builder() + .consistentRead(true) + .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) + .build()) .iterator(); page = strongConsistencyResult.next(); @@ -155,6 +184,7 @@ public void query_withReturnConsumedCapacityAndDifferentReadConsistency_checksCo Iterator> eventualConsistencyResult = mappedTable.query(QueryEnhancedRequest.builder() + .consistentRead(false) .queryConditional(sortGreaterThan(k -> k.partitionValue("id-value").sortValue(3))) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()) @@ -167,6 +197,7 @@ public void query_withReturnConsumedCapacityAndDifferentReadConsistency_checksCo Iterator> strongConsistencyResult = mappedTable.query(QueryEnhancedRequest.builder() + .consistentRead(true) .queryConditional(sortGreaterThan(k -> k.partitionValue("id-value").sortValue(3))) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()) @@ -180,6 +211,29 @@ public void query_withReturnConsumedCapacityAndDifferentReadConsistency_checksCo assertThat(strongConsumedCapacity.capacityUnits(), is(greaterThanOrEqualTo(eventualConsumedCapacity.capacityUnits()))); } + @Test + public void scan_consistentReadNotSetOnRequest_usesClientValue() { + mappedTable.scan(ScanEnhancedRequest.builder().build()) + .items().stream().count(); + + assertThat(capturingInterceptor.scanRequests.size(), is(1)); + Boolean consistentRead = capturingInterceptor.scanRequests.get(0).consistentRead(); + assertThat(consistentRead, is(true)); + } + + @Test + public void query_consistentReadSetOnRequest_overridesClientValue() { + mappedTable.query(QueryEnhancedRequest.builder() + .consistentRead(false) + .queryConditional(sortGreaterThan(k -> k.partitionValue("id-value").sortValue(3))) + .build()) + .items().stream().count(); + + assertThat(capturingInterceptor.queryRequests.size(), is(1)); + Boolean consistentRead = capturingInterceptor.queryRequests.get(0).consistentRead(); + assertThat(consistentRead, is(false)); + } + private Map getKeyMap(int sort) { Map result = new HashMap<>(); result.put("id", stringValue(RECORDS.get(sort).getId())); diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.java index 962e456e9642..e60f859c5cb9 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.java @@ -560,6 +560,9 @@ interface Builder extends DynamoDbEnhancedResource.Builder { @Override Builder extensions(List dynamoDbEnhancedClientExtensions); + @Override + Builder consistentRead(Boolean consistentRead); + /** * Builds an enhanced client based on the settings supplied to this builder * diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.java index 8cf50cda7b68..a005be4db549 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.java @@ -569,6 +569,9 @@ interface Builder extends DynamoDbEnhancedResource.Builder { @Override Builder extensions(List dynamoDbEnhancedClientExtensions); + @Override + Builder consistentRead(Boolean consistentRead); + /** * Builds an enhanced client based on the settings supplied to this builder * diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedResource.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedResource.java index af0d917f83e3..bc83afb57d82 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedResource.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedResource.java @@ -54,5 +54,28 @@ interface Builder { * @param dynamoDbEnhancedClientExtensions a list of extensions to load with the enhanced client */ Builder extensions(List dynamoDbEnhancedClientExtensions); + + /** + * Sets the default read consistency model for single read operations (GetItem, Query, Scan). When set to true, these + * operations will use strongly consistent reads. By default, this is set to null/false, i.e., eventually consistent + * reads. + *

+ * If set at the request level, e.g., {@code QueryEnhancedRequest}, the request level value will take precedence. + *

+ * Note: This setting applies to single read operations only: + *

    + *
  • BatchGetItem: Eventually consistent by default, consistent read setting must be configured at the individual + * request level, i.e. {@code GetItemEnhancedRequest}s passed to {@code ReachBatch} on the {@code + * BatchGetItemEnhancedRequest}, when performing batch GET. + *
  • + *
  • TransactGetItems: Always uses strongly consistent reads by design, so this setting is not applicable.
  • + *
+ * + * @param consistentRead true for strongly consistent reads, null/false (default value) for eventually consistent reads + * @return this builder for method chaining + */ + default Builder consistentRead(Boolean consistentRead) { + throw new UnsupportedOperationException(); + } } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.java index 6687e6b54308..c7b22dc0f3c4 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.java @@ -54,4 +54,25 @@ public interface MappedTableResource { * @return A key that has been initialized with the index values extracted from the modelled object. */ Key keyFrom(T item); + + /** + * The default read consistency model for single read operations (GetItem, Query, Scan). When set to true, these + * operations will use strongly consistent reads. By default, this is set to null/false, i.e., eventually consistent reads. + *

+ * If set at the request level, e.g., {@code QueryEnhancedRequest}, the request level value will take precedence. + *

+ * Note: This setting applies to single read operations only: + *

    + *
  • BatchGetItem: Eventually consistent by default, consistent read setting must be configured at the individual + * request level, i.e. {@code GetItemEnhancedRequest}s passed to {@code ReachBatch} on the {@code + * BatchGetItemEnhancedRequest}, when performing batch GET. + *
  • + *
  • TransactGetItems: Always uses strongly consistent reads by design, so this setting is not applicable.
  • + *
+ * + * @return The default consistent read setting on the table. + */ + default Boolean consistentRead() { + throw new UnsupportedOperationException(); + } } 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 61d750e98a7e..baeb2c1101dc 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 @@ -30,11 +30,15 @@ import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension; import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.MappedTableResource; import software.amazon.awssdk.enhanced.dynamodb.OperationContext; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.enhanced.dynamodb.extensions.ReadModification; import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.DefaultDynamoDbExtensionContext; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; import software.amazon.awssdk.enhanced.dynamodb.model.Page; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.ConsumedCapacity; @@ -204,4 +208,31 @@ public static List getItemsFromSupplier(List> itemSupplierLis public static boolean isNullAttributeValue(AttributeValue attributeValue) { return attributeValue.nul() != null && attributeValue.nul(); } + + public static GetItemEnhancedRequest applyClientDefaultsIfAbsentOnRequest(GetItemEnhancedRequest request, + MappedTableResource table) { + if (request.consistentRead() == null) { + request = request.toBuilder().consistentRead(table.consistentRead()).build(); + } + + return request; + } + + public static QueryEnhancedRequest applyClientDefaultsIfAbsentOnRequest(QueryEnhancedRequest request, + MappedTableResource table) { + if (request.consistentRead() == null) { + request = request.toBuilder().consistentRead(table.consistentRead()).build(); + } + + return request; + } + + public static ScanEnhancedRequest applyClientDefaultsIfAbsentOnRequest(ScanEnhancedRequest request, + MappedTableResource table) { + if (request.consistentRead() == null) { + request = request.toBuilder().consistentRead(table.consistentRead()).build(); + } + + return request; + } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncIndex.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncIndex.java index 0b7391a5e7f0..c2f238fc681e 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncIndex.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncIndex.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.enhanced.dynamodb.internal.client; +import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.applyClientDefaultsIfAbsentOnRequest; import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.createKeyFromItem; import java.util.function.Consumer; @@ -40,22 +41,21 @@ public final class DefaultDynamoDbAsyncIndex implements DynamoDbAsyncIndex private final TableSchema tableSchema; private final String tableName; private final String indexName; + private final DefaultDynamoDbAsyncTable table; - DefaultDynamoDbAsyncIndex(DynamoDbAsyncClient dynamoDbClient, - DynamoDbEnhancedClientExtension extension, - TableSchema tableSchema, - String tableName, - String indexName) { - this.dynamoDbClient = dynamoDbClient; - this.extension = extension; - this.tableSchema = tableSchema; - this.tableName = tableName; + DefaultDynamoDbAsyncIndex(DefaultDynamoDbAsyncTable table, String indexName) { + this.dynamoDbClient = table.dynamoDbClient(); + this.extension = table.mapperExtension(); + this.tableSchema = table.tableSchema(); + this.tableName = table.tableName(); this.indexName = indexName; + this.table = table; } @Override public SdkPublisher> query(QueryEnhancedRequest request) { - PaginatedIndexOperation operation = QueryOperation.create(request); + QueryEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this.table); + PaginatedIndexOperation operation = QueryOperation.create(requestWithDefaults); return operation.executeOnSecondaryIndexAsync(tableSchema, tableName, indexName, extension, dynamoDbClient); } @@ -73,7 +73,8 @@ public SdkPublisher> query(QueryConditional queryConditional) { @Override public SdkPublisher> scan(ScanEnhancedRequest request) { - PaginatedIndexOperation operation = ScanOperation.create(request); + ScanEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this.table); + PaginatedIndexOperation operation = ScanOperation.create(requestWithDefaults); return operation.executeOnSecondaryIndexAsync(tableSchema, tableName, indexName, extension, dynamoDbClient); } @@ -143,6 +144,9 @@ public boolean equals(Object o) { if (tableName != null ? ! tableName.equals(that.tableName) : that.tableName != null) { return false; } + if (table != null ? ! table.equals(that.table) : that.table != null) { + return false; + } return indexName != null ? indexName.equals(that.indexName) : that.indexName == null; } @@ -153,6 +157,7 @@ public int hashCode() { result = 31 * result + (tableSchema != null ? tableSchema.hashCode() : 0); result = 31 * result + (tableName != null ? tableName.hashCode() : 0); result = 31 * result + (indexName != null ? indexName.hashCode() : 0); + result = 31 * result + (table != null ? table.hashCode() : 0); return result; } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncTable.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncTable.java index cd281dec3d24..b515dc262dec 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncTable.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncTable.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.enhanced.dynamodb.internal.client; +import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.applyClientDefaultsIfAbsentOnRequest; import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.createKeyFromItem; import java.util.ArrayList; @@ -62,15 +63,25 @@ public final class DefaultDynamoDbAsyncTable implements DynamoDbAsyncTable private final DynamoDbEnhancedClientExtension extension; private final TableSchema tableSchema; private final String tableName; + private final Boolean consistentRead; DefaultDynamoDbAsyncTable(DynamoDbAsyncClient dynamoDbClient, DynamoDbEnhancedClientExtension extension, TableSchema tableSchema, String tableName) { + this(dynamoDbClient, extension, tableSchema, tableName, null); + } + + DefaultDynamoDbAsyncTable(DynamoDbAsyncClient dynamoDbClient, + DynamoDbEnhancedClientExtension extension, + TableSchema tableSchema, + String tableName, + Boolean consistentRead) { this.dynamoDbClient = dynamoDbClient; this.extension = extension; this.tableSchema = tableSchema; this.tableName = tableName; + this.consistentRead = consistentRead; } @Override @@ -92,12 +103,17 @@ public String tableName() { return tableName; } + @Override + public Boolean consistentRead() { + return this.consistentRead; + } + @Override public DefaultDynamoDbAsyncIndex index(String indexName) { // Force a check for the existence of the index tableSchema.tableMetadata().indexPartitionKey(indexName); - return new DefaultDynamoDbAsyncIndex<>(dynamoDbClient, extension, tableSchema, tableName, indexName); + return new DefaultDynamoDbAsyncIndex<>(this, indexName); } @Override @@ -164,7 +180,8 @@ public CompletableFuture> deleteItemWithResponse( @Override public CompletableFuture getItem(GetItemEnhancedRequest request) { - TableOperation> operation = GetItemOperation.create(request); + GetItemEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this); + TableOperation> operation = GetItemOperation.create(requestWithDefaults); CompletableFuture> future = operation.executeOnPrimaryIndexAsync( tableSchema, tableName, extension, dynamoDbClient ); @@ -190,7 +207,8 @@ public CompletableFuture getItem(T keyItem) { @Override public CompletableFuture> getItemWithResponse(GetItemEnhancedRequest request) { - TableOperation> operation = GetItemOperation.create(request); + GetItemEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this); + TableOperation> operation = GetItemOperation.create(requestWithDefaults); return operation.executeOnPrimaryIndexAsync(tableSchema, tableName, extension, dynamoDbClient); } @@ -204,7 +222,8 @@ public CompletableFuture> getItemWithResponse( @Override public PagePublisher query(QueryEnhancedRequest request) { - PaginatedTableOperation operation = QueryOperation.create(request); + QueryEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this); + PaginatedTableOperation operation = QueryOperation.create(requestWithDefaults); return operation.executeOnPrimaryIndexAsync(tableSchema, tableName, extension, dynamoDbClient); } @@ -256,7 +275,8 @@ public CompletableFuture> putItemWithResponse( @Override public PagePublisher scan(ScanEnhancedRequest request) { - PaginatedTableOperation operation = ScanOperation.create(request); + ScanEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this); + PaginatedTableOperation operation = ScanOperation.create(requestWithDefaults); return operation.executeOnPrimaryIndexAsync(tableSchema, tableName, extension, dynamoDbClient); } @@ -348,6 +368,9 @@ public boolean equals(Object o) { if (tableSchema != null ? ! tableSchema.equals(that.tableSchema) : that.tableSchema != null) { return false; } + if (consistentRead != null ? !consistentRead.equals(that.consistentRead) : that.consistentRead != null) { + return false; + } return tableName != null ? tableName.equals(that.tableName) : that.tableName == null; } @@ -357,6 +380,7 @@ public int hashCode() { result = 31 * result + (extension != null ? extension.hashCode() : 0); result = 31 * result + (tableSchema != null ? tableSchema.hashCode() : 0); result = 31 * result + (tableName != null ? tableName.hashCode() : 0); + result = 31 * result + (consistentRead != null ? consistentRead.hashCode() : 0); return result; } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedAsyncClient.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedAsyncClient.java index 2bff93dbf004..ae5d53f02017 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedAsyncClient.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedAsyncClient.java @@ -43,10 +43,12 @@ public final class DefaultDynamoDbEnhancedAsyncClient implements DynamoDbEnhancedAsyncClient { private final DynamoDbAsyncClient dynamoDbClient; private final DynamoDbEnhancedClientExtension extension; + private final Boolean consistentRead; private DefaultDynamoDbEnhancedAsyncClient(Builder builder) { this.dynamoDbClient = builder.dynamoDbClient == null ? DynamoDbAsyncClient.create() : builder.dynamoDbClient; this.extension = ExtensionResolver.resolveExtensions(builder.dynamoDbEnhancedClientExtensions); + this.consistentRead = builder.consistentRead; } public static Builder builder() { @@ -55,7 +57,7 @@ public static Builder builder() { @Override public DefaultDynamoDbAsyncTable table(String tableName, TableSchema tableSchema) { - return new DefaultDynamoDbAsyncTable<>(dynamoDbClient, extension, tableSchema, tableName); + return new DefaultDynamoDbAsyncTable<>(dynamoDbClient, extension, tableSchema, tableName, consistentRead); } @Override @@ -140,8 +142,14 @@ public DynamoDbEnhancedClientExtension mapperExtension() { return extension; } + public Boolean consistentRead() { + return consistentRead; + } + public Builder toBuilder() { - return builder().dynamoDbClient(this.dynamoDbClient).extensions(this.extension); + return builder().dynamoDbClient(this.dynamoDbClient) + .extensions(this.extension) + .consistentRead(this.consistentRead); } @Override @@ -160,6 +168,9 @@ public boolean equals(Object o) { return false; } + if (consistentRead != null ? !consistentRead.equals(that.consistentRead) : that.consistentRead != null) { + return false; + } return extension != null ? extension.equals(that.extension) : that.extension == null; } @@ -167,6 +178,7 @@ public boolean equals(Object o) { public int hashCode() { int result = dynamoDbClient != null ? dynamoDbClient.hashCode() : 0; result = 31 * result + (extension != null ? extension.hashCode() : 0); + result = 31 * result + (consistentRead != null ? consistentRead.hashCode() : 0); return result; } @@ -175,6 +187,7 @@ public static final class Builder implements DynamoDbEnhancedAsyncClient.Builder private DynamoDbAsyncClient dynamoDbClient; private List dynamoDbEnhancedClientExtensions = new ArrayList<>(ExtensionResolver.defaultExtensions()); + private Boolean consistentRead; @Override public DefaultDynamoDbEnhancedAsyncClient build() { @@ -198,5 +211,11 @@ public Builder extensions(List dynamoDbEnhanced this.dynamoDbEnhancedClientExtensions = new ArrayList<>(dynamoDbEnhancedClientExtensions); return this; } + + @Override + public Builder consistentRead(Boolean consistentRead) { + this.consistentRead = consistentRead; + return this; + } } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedClient.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedClient.java index 7450a0e2f8e4..74dda3f358c0 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedClient.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedClient.java @@ -42,10 +42,12 @@ public final class DefaultDynamoDbEnhancedClient implements DynamoDbEnhancedClient { private final DynamoDbClient dynamoDbClient; private final DynamoDbEnhancedClientExtension extension; + private final Boolean consistentRead; private DefaultDynamoDbEnhancedClient(Builder builder) { this.dynamoDbClient = builder.dynamoDbClient == null ? DynamoDbClient.create() : builder.dynamoDbClient; this.extension = ExtensionResolver.resolveExtensions(builder.dynamoDbEnhancedClientExtensions); + this.consistentRead = builder.consistentRead; } public static Builder builder() { @@ -54,7 +56,7 @@ public static Builder builder() { @Override public DefaultDynamoDbTable table(String tableName, TableSchema tableSchema) { - return new DefaultDynamoDbTable<>(dynamoDbClient, extension, tableSchema, tableName); + return new DefaultDynamoDbTable<>(dynamoDbClient, extension, tableSchema, tableName, consistentRead); } @Override @@ -134,8 +136,14 @@ public DynamoDbEnhancedClientExtension mapperExtension() { return extension; } + public Boolean consistentRead() { + return consistentRead; + } + public Builder toBuilder() { - return builder().dynamoDbClient(this.dynamoDbClient).extensions(this.extension); + return builder().dynamoDbClient(this.dynamoDbClient) + .extensions(this.extension) + .consistentRead(this.consistentRead); } @Override @@ -152,6 +160,9 @@ public boolean equals(Object o) { if (dynamoDbClient != null ? !dynamoDbClient.equals(that.dynamoDbClient) : that.dynamoDbClient != null) { return false; } + if (consistentRead != null ? !consistentRead.equals(that.consistentRead) : that.consistentRead != null) { + return false; + } return extension != null ? extension.equals(that.extension) : that.extension == null; @@ -160,8 +171,8 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = dynamoDbClient != null ? dynamoDbClient.hashCode() : 0; - result = 31 * result + (extension != null ? - extension.hashCode() : 0); + result = 31 * result + (extension != null ? extension.hashCode() : 0); + result = 31 * result + (consistentRead != null ? consistentRead.hashCode() : 0); return result; } @@ -170,6 +181,7 @@ public static final class Builder implements DynamoDbEnhancedClient.Builder { private DynamoDbClient dynamoDbClient; private List dynamoDbEnhancedClientExtensions = new ArrayList<>(ExtensionResolver.defaultExtensions()); + private Boolean consistentRead; @Override public DefaultDynamoDbEnhancedClient build() { @@ -193,5 +205,11 @@ public Builder extensions(List dynamoDbEnhanced this.dynamoDbEnhancedClientExtensions = new ArrayList<>(dynamoDbEnhancedClientExtensions); return this; } + + @Override + public Builder consistentRead(Boolean consistentRead) { + this.consistentRead = consistentRead; + return this; + } } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbIndex.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbIndex.java index 323ff790592b..66fd8bac1e14 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbIndex.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbIndex.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.enhanced.dynamodb.internal.client; +import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.applyClientDefaultsIfAbsentOnRequest; import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.createKeyFromItem; import java.util.function.Consumer; @@ -40,22 +41,21 @@ public class DefaultDynamoDbIndex implements DynamoDbIndex { private final TableSchema tableSchema; private final String tableName; private final String indexName; + private final DefaultDynamoDbTable table; - DefaultDynamoDbIndex(DynamoDbClient dynamoDbClient, - DynamoDbEnhancedClientExtension extension, - TableSchema tableSchema, - String tableName, - String indexName) { - this.dynamoDbClient = dynamoDbClient; - this.extension = extension; - this.tableSchema = tableSchema; - this.tableName = tableName; + DefaultDynamoDbIndex(DefaultDynamoDbTable table, String indexName) { + this.dynamoDbClient = table.dynamoDbClient(); + this.extension = table.mapperExtension(); + this.tableSchema = table.tableSchema(); + this.tableName = table.tableName(); this.indexName = indexName; + this.table = table; } @Override public SdkIterable> query(QueryEnhancedRequest request) { - PaginatedIndexOperation operation = QueryOperation.create(request); + QueryEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this.table); + PaginatedIndexOperation operation = QueryOperation.create(requestWithDefaults); return operation.executeOnSecondaryIndex(tableSchema, tableName, indexName, extension, dynamoDbClient); } @@ -73,7 +73,8 @@ public SdkIterable> query(QueryConditional queryConditional) { @Override public SdkIterable> scan(ScanEnhancedRequest request) { - PaginatedIndexOperation operation = ScanOperation.create(request); + ScanEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this.table); + PaginatedIndexOperation operation = ScanOperation.create(requestWithDefaults); return operation.executeOnSecondaryIndex(tableSchema, tableName, indexName, extension, dynamoDbClient); } @@ -141,6 +142,9 @@ public boolean equals(Object o) { if (tableName != null ? ! tableName.equals(that.tableName) : that.tableName != null) { return false; } + if (table != null ? ! table.equals(that.table) : that.table != null) { + return false; + } return indexName != null ? indexName.equals(that.indexName) : that.indexName == null; } @@ -151,6 +155,7 @@ public int hashCode() { result = 31 * result + (tableSchema != null ? tableSchema.hashCode() : 0); result = 31 * result + (tableName != null ? tableName.hashCode() : 0); result = 31 * result + (indexName != null ? indexName.hashCode() : 0); + result = 31 * result + (table != null ? table.hashCode() : 0); return result; } } diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbTable.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbTable.java index 31ce811b3483..803a02fe9254 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbTable.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbTable.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.enhanced.dynamodb.internal.client; +import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.applyClientDefaultsIfAbsentOnRequest; import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.createKeyFromItem; import java.util.ArrayList; @@ -61,15 +62,25 @@ public class DefaultDynamoDbTable implements DynamoDbTable { private final DynamoDbEnhancedClientExtension extension; private final TableSchema tableSchema; private final String tableName; + private final Boolean consistentRead; DefaultDynamoDbTable(DynamoDbClient dynamoDbClient, DynamoDbEnhancedClientExtension extension, TableSchema tableSchema, String tableName) { + this(dynamoDbClient, extension, tableSchema, tableName, null); + } + + DefaultDynamoDbTable(DynamoDbClient dynamoDbClient, + DynamoDbEnhancedClientExtension extension, + TableSchema tableSchema, + String tableName, + Boolean consistentRead) { this.dynamoDbClient = dynamoDbClient; this.extension = extension; this.tableSchema = tableSchema; this.tableName = tableName; + this.consistentRead = consistentRead; } @Override @@ -91,16 +102,17 @@ public String tableName() { return tableName; } + @Override + public Boolean consistentRead() { + return this.consistentRead; + } + @Override public DefaultDynamoDbIndex index(String indexName) { // Force a check for the existence of the index tableSchema.tableMetadata().indexPartitionKey(indexName); - return new DefaultDynamoDbIndex<>(dynamoDbClient, - extension, - tableSchema, - tableName, - indexName); + return new DefaultDynamoDbIndex<>(this, indexName); } @Override @@ -164,7 +176,8 @@ public DeleteItemEnhancedResponse deleteItemWithResponse(Consumer> operation = GetItemOperation.create(request); + GetItemEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this); + TableOperation> operation = GetItemOperation.create(requestWithDefaults); return operation.executeOnPrimaryIndex(tableSchema, tableName, extension, dynamoDbClient).attributes(); } @@ -194,13 +207,15 @@ public GetItemEnhancedResponse getItemWithResponse(Consumer getItemWithResponse(GetItemEnhancedRequest request) { - TableOperation> operation = GetItemOperation.create(request); + GetItemEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this); + TableOperation> operation = GetItemOperation.create(requestWithDefaults); return operation.executeOnPrimaryIndex(tableSchema, tableName, extension, dynamoDbClient); } @Override public PageIterable query(QueryEnhancedRequest request) { - PaginatedTableOperation operation = QueryOperation.create(request); + QueryEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this); + PaginatedTableOperation operation = QueryOperation.create(requestWithDefaults); return operation.executeOnPrimaryIndex(tableSchema, tableName, extension, dynamoDbClient); } @@ -251,7 +266,8 @@ public PutItemEnhancedResponse putItemWithResponse(Consumer scan(ScanEnhancedRequest request) { - PaginatedTableOperation operation = ScanOperation.create(request); + ScanEnhancedRequest requestWithDefaults = applyClientDefaultsIfAbsentOnRequest(request, this); + PaginatedTableOperation operation = ScanOperation.create(requestWithDefaults); return operation.executeOnPrimaryIndex(tableSchema, tableName, extension, dynamoDbClient); } @@ -341,6 +357,9 @@ public boolean equals(Object o) { if (tableSchema != null ? ! tableSchema.equals(that.tableSchema) : that.tableSchema != null) { return false; } + if (consistentRead != null ? !consistentRead.equals(that.consistentRead) : that.consistentRead != null) { + return false; + } return tableName != null ? tableName.equals(that.tableName) : that.tableName == null; } @@ -351,6 +370,7 @@ public int hashCode() { extension.hashCode() : 0); result = 31 * result + (tableSchema != null ? tableSchema.hashCode() : 0); result = 31 * result + (tableName != null ? tableName.hashCode() : 0); + result = 31 * result + (consistentRead != null ? consistentRead.hashCode() : 0); return result; } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtilsTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtilsTest.java index 6e3bbdbdc9ad..72058f303e3f 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtilsTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/EnhancedClientUtilsTest.java @@ -16,21 +16,34 @@ package software.amazon.awssdk.enhanced.dynamodb.internal; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; import java.util.HashMap; import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.enhanced.dynamodb.Key; import software.amazon.awssdk.enhanced.dynamodb.TableMetadata; import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItem; import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithSort; +import software.amazon.awssdk.enhanced.dynamodb.internal.client.DefaultDynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +@ExtendWith(MockitoExtension.class) public class EnhancedClientUtilsTest { private static final AttributeValue PARTITION_VALUE = AttributeValue.builder().s("id123").build(); private static final AttributeValue SORT_VALUE = AttributeValue.builder().s("sort123").build(); + @Mock + private DefaultDynamoDbTable mockTable; + @Test public void createKeyFromMap_partitionOnly() { Map itemMap = new HashMap<>(); @@ -64,4 +77,40 @@ public void cleanAttributeName_cleansSpecialCharacters() { assertThat(result).isEqualTo("a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u"); } + + @Test + public void applyClientDefaultsGetItem_consistentReadAbsentOnRequestAndClient_shouldDefaultToNull() { + GetItemEnhancedRequest request = GetItemEnhancedRequest.builder().build(); + when(mockTable.consistentRead()).thenReturn(null); + GetItemEnhancedRequest processedRequest = EnhancedClientUtils.applyClientDefaultsIfAbsentOnRequest(request, mockTable); + + assertThat(processedRequest.consistentRead()).isNull(); + } + + @Test + public void applyClientDefaultsGetItem_consistentReadAbsentOnRequest_shouldAddClientValue() { + GetItemEnhancedRequest request = GetItemEnhancedRequest.builder().build(); + when(mockTable.consistentRead()).thenReturn(true); + GetItemEnhancedRequest processedRequest = EnhancedClientUtils.applyClientDefaultsIfAbsentOnRequest(request, mockTable); + + assertThat(processedRequest.consistentRead()).isTrue(); + } + + @Test + public void applyClientDefaultsQuery_consistentReadFalseOnRequest_shouldNotAddClientValue() { + QueryEnhancedRequest request = QueryEnhancedRequest.builder().consistentRead(false).build(); + lenient().when(mockTable.consistentRead()).thenReturn(true); + QueryEnhancedRequest processedRequest = EnhancedClientUtils.applyClientDefaultsIfAbsentOnRequest(request, mockTable); + + assertThat(processedRequest.consistentRead()).isFalse(); + } + + @Test + public void applyClientDefaultsScan_consistentReadTrueOnRequest_shouldNotAddClientValue() { + ScanEnhancedRequest request = ScanEnhancedRequest.builder().consistentRead(true).build(); + lenient().when(mockTable.consistentRead()).thenReturn(false); + ScanEnhancedRequest processedRequest = EnhancedClientUtils.applyClientDefaultsIfAbsentOnRequest(request, mockTable); + + assertThat(processedRequest.consistentRead()).isTrue(); + } } \ No newline at end of file diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncIndexTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncIndexTest.java index 127f6f4a774d..e863d2c32b9e 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncIndexTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbAsyncIndexTest.java @@ -39,13 +39,11 @@ public class DefaultDynamoDbAsyncIndexTest { @Test public void keyFrom_secondaryIndex_partitionAndSort() { + DefaultDynamoDbAsyncTable table = createTable(); + FakeItemWithIndices item = FakeItemWithIndices.createUniqueFakeItemWithIndices(); DefaultDynamoDbAsyncIndex dynamoDbMappedIndex = - new DefaultDynamoDbAsyncIndex<>(mockDynamoDbAsyncClient, - mockDynamoDbEnhancedClientExtension, - FakeItemWithIndices.getTableSchema(), - "test_table", - "gsi_1"); + new DefaultDynamoDbAsyncIndex<>(table, "gsi_1"); Key key = dynamoDbMappedIndex.keyFrom(item); @@ -55,17 +53,21 @@ public void keyFrom_secondaryIndex_partitionAndSort() { @Test public void keyFrom_secondaryIndex_partitionOnly() { + DefaultDynamoDbAsyncTable table = createTable(); + FakeItemWithIndices item = FakeItemWithIndices.createUniqueFakeItemWithIndices(); DefaultDynamoDbAsyncIndex dynamoDbMappedIndex = - new DefaultDynamoDbAsyncIndex<>(mockDynamoDbAsyncClient, - mockDynamoDbEnhancedClientExtension, - FakeItemWithIndices.getTableSchema(), - "test_table", - "gsi_2"); + new DefaultDynamoDbAsyncIndex<>(table,"gsi_2"); Key key = dynamoDbMappedIndex.keyFrom(item); assertThat(key.partitionKeyValue(), is(stringValue(item.getGsiId()))); assertThat(key.sortKeyValue(), is(Optional.empty())); } + + private DefaultDynamoDbAsyncTable createTable() { + return new DefaultDynamoDbAsyncTable<>(mockDynamoDbAsyncClient, + mockDynamoDbEnhancedClientExtension, + FakeItemWithIndices.getTableSchema(), "test_table"); + } } diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedAsyncClientTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedAsyncClientTest.java index bd801f51dce7..23dcb4e45c6f 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedAsyncClientTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedAsyncClientTest.java @@ -18,7 +18,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertNotNull; +import static org.hamcrest.Matchers.nullValue; import java.util.Arrays; import org.junit.Before; @@ -28,7 +28,6 @@ import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; -import software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension; import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.ChainExtension; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; @@ -51,6 +50,7 @@ public void initializeClient() { DefaultDynamoDbEnhancedAsyncClient.builder() .dynamoDbClient(mockDynamoDbAsyncClient) .extensions(mockDynamoDbEnhancedClientExtension) + .consistentRead(true) .build(); } @@ -62,6 +62,7 @@ public void table() { assertThat(mappedTable.mapperExtension(), is(mockDynamoDbEnhancedClientExtension)); assertThat(mappedTable.tableSchema(), is(mockTableSchema)); assertThat(mappedTable.tableName(), is("table-name")); + assertThat(mappedTable.consistentRead(), is(true)); } @Test @@ -73,6 +74,7 @@ public void builder_minimal() { assertThat(builtObject.dynamoDbAsyncClient(), is(mockDynamoDbAsyncClient)); assertThat(builtObject.mapperExtension(), instanceOf(ChainExtension.class)); + assertThat(builtObject.consistentRead(), is(nullValue())); } @Test @@ -81,10 +83,12 @@ public void builder_maximal() { DefaultDynamoDbEnhancedAsyncClient.builder() .dynamoDbClient(mockDynamoDbAsyncClient) .extensions(mockDynamoDbEnhancedClientExtension) + .consistentRead(true) .build(); assertThat(builtObject.dynamoDbAsyncClient(), is(mockDynamoDbAsyncClient)); assertThat(builtObject.mapperExtension(), is(mockDynamoDbEnhancedClientExtension)); + assertThat(builtObject.consistentRead(), is(true)); } @Test diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedClientTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedClientTest.java index 3ac025e559d1..7d7ed8fa1833 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedClientTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbEnhancedClientTest.java @@ -18,6 +18,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; import java.util.Arrays; import org.junit.Before; @@ -27,7 +28,6 @@ import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; -import software.amazon.awssdk.enhanced.dynamodb.extensions.VersionedRecordExtension; import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.ChainExtension; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; @@ -52,6 +52,7 @@ public void initializeClient() { this.dynamoDbEnhancedClient = DefaultDynamoDbEnhancedClient.builder() .dynamoDbClient(mockDynamoDbClient) .extensions(mockDynamoDbEnhancedClientExtension) + .consistentRead(true) .build(); } @@ -63,6 +64,7 @@ public void table() { assertThat(mappedTable.mapperExtension(), is(mockDynamoDbEnhancedClientExtension)); assertThat(mappedTable.tableSchema(), is(mockTableSchema)); assertThat(mappedTable.tableName(), is("table-name")); + assertThat(mappedTable.consistentRead(), is(true)); } @Test @@ -73,6 +75,7 @@ public void builder_minimal() { assertThat(builtObject.dynamoDbClient(), is(mockDynamoDbClient)); assertThat(builtObject.mapperExtension(), instanceOf(ChainExtension.class)); + assertThat(builtObject.consistentRead(), is(nullValue())); } @Test @@ -80,10 +83,12 @@ public void builder_maximal() { DefaultDynamoDbEnhancedClient builtObject = DefaultDynamoDbEnhancedClient.builder() .dynamoDbClient(mockDynamoDbClient) .extensions(mockDynamoDbEnhancedClientExtension) + .consistentRead(true) .build(); assertThat(builtObject.dynamoDbClient(), is(mockDynamoDbClient)); assertThat(builtObject.mapperExtension(), is(mockDynamoDbEnhancedClientExtension)); + assertThat(builtObject.consistentRead(), is(true)); } @Test diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbIndexTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbIndexTest.java index 30ee03983858..804d957eb0a4 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbIndexTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbIndexTest.java @@ -17,17 +17,23 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.verify; import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension; import software.amazon.awssdk.enhanced.dynamodb.Key; import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithIndices; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.ScanRequest; @RunWith(MockitoJUnitRunner.class) public class DefaultDynamoDbIndexTest { @@ -39,13 +45,11 @@ public class DefaultDynamoDbIndexTest { @Test public void keyFrom_secondaryIndex_partitionAndSort() { + DefaultDynamoDbTable table = createTable(); + FakeItemWithIndices item = FakeItemWithIndices.createUniqueFakeItemWithIndices(); DefaultDynamoDbIndex dynamoDbMappedIndex = - new DefaultDynamoDbIndex<>(mockDynamoDbClient, - mockDynamoDbEnhancedClientExtension, - FakeItemWithIndices.getTableSchema(), - "test_table", - "gsi_1"); + new DefaultDynamoDbIndex<>(table, "gsi_1"); Key key = dynamoDbMappedIndex.keyFrom(item); @@ -55,17 +59,68 @@ public void keyFrom_secondaryIndex_partitionAndSort() { @Test public void keyFrom_secondaryIndex_partitionOnly() { + DefaultDynamoDbTable table = createTable(); + FakeItemWithIndices item = FakeItemWithIndices.createUniqueFakeItemWithIndices(); DefaultDynamoDbIndex dynamoDbMappedIndex = - new DefaultDynamoDbIndex<>(mockDynamoDbClient, - mockDynamoDbEnhancedClientExtension, - FakeItemWithIndices.getTableSchema(), - "test_table", - "gsi_2"); + new DefaultDynamoDbIndex<>(table, "gsi_2"); Key key = dynamoDbMappedIndex.keyFrom(item); assertThat(key.partitionKeyValue(), is(stringValue(item.getGsiId()))); assertThat(key.sortKeyValue(), is(Optional.empty())); } + + @Test + public void query_consistentReadNotSetOnTableOrRequest_shouldDefaultToNull() { + DefaultDynamoDbTable table = createTable(); + DefaultDynamoDbIndex dynamoDbMappedIndex = new DefaultDynamoDbIndex<>(table, "gsi_2"); + dynamoDbMappedIndex.query(q -> q.queryConditional(QueryConditional.keyEqualTo(Key.builder().partitionValue("val").build()))); + + ArgumentCaptor captor = ArgumentCaptor.forClass(QueryRequest.class); + verify(mockDynamoDbClient).queryPaginator(captor.capture()); + + QueryRequest actualRequest = captor.getValue(); + assertThat(actualRequest.consistentRead(), is(nullValue())); + } + + @Test + public void scan_consistentReadSetOnTableNotSetOnRequest_shouldUseTableValue() { + DefaultDynamoDbTable table = new DefaultDynamoDbTable<>(mockDynamoDbClient, + mockDynamoDbEnhancedClientExtension, + FakeItemWithIndices.getTableSchema(), + "test-table", + true); + DefaultDynamoDbIndex dynamoDbMappedIndex = new DefaultDynamoDbIndex<>(table, "gsi_2"); + dynamoDbMappedIndex.scan(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ScanRequest.class); + verify(mockDynamoDbClient).scanPaginator(captor.capture()); + + ScanRequest actualRequest = captor.getValue(); + assertThat(actualRequest.consistentRead(), is(true)); + } + + @Test + public void scan_consistentReadSetOnTableAndRequest_shouldUseRequestValue() { + DefaultDynamoDbTable table = new DefaultDynamoDbTable<>(mockDynamoDbClient, + mockDynamoDbEnhancedClientExtension, + FakeItemWithIndices.getTableSchema(), + "test-table", + true); + DefaultDynamoDbIndex dynamoDbMappedIndex = new DefaultDynamoDbIndex<>(table, "gsi_2"); + dynamoDbMappedIndex.scan(s -> s.consistentRead(false)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ScanRequest.class); + verify(mockDynamoDbClient).scanPaginator(captor.capture()); + + ScanRequest actualRequest = captor.getValue(); + assertThat(actualRequest.consistentRead(), is(false)); + } + + private DefaultDynamoDbTable createTable() { + return new DefaultDynamoDbTable<>(mockDynamoDbClient, + mockDynamoDbEnhancedClientExtension, + FakeItemWithIndices.getTableSchema(), "test-table"); + } }