Skip to content

Commit 9e2ee8b

Browse files
Adrian ChleboszAdrian Chlebosz
Adrian Chlebosz
authored and
Adrian Chlebosz
committed
Create secondary indices based on table bean annotations (aws#3923)
* detect and group indices present in table schema into LSIs and GSIs * pass request with indices information appended further
1 parent f12ccc7 commit 9e2ee8b

File tree

4 files changed

+145
-1
lines changed

4 files changed

+145
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon DynamoDB Enhanced",
3+
"contributor": "breader124",
4+
"type": "bugfix",
5+
"description": "Thanks to this bugfix it'll be possible to create DynamoDB table containing\nsecondary indices when using no arugments `createTable` method from `DefaultDynamoDbTable`\nclass. Information about their presence might be expressed using annotations, but it was ignored\nand created tables didn't contain specified indices. Plase note that it is still not possible\nto specify projections for indices using annotations. By default, all fields will be projected."
6+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbTable.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,20 @@
1515

1616
package software.amazon.awssdk.enhanced.dynamodb.internal.client;
1717

18+
import static java.util.Collections.emptyList;
1819
import static software.amazon.awssdk.enhanced.dynamodb.internal.EnhancedClientUtils.createKeyFromItem;
1920

21+
import java.util.Collection;
22+
import java.util.List;
23+
import java.util.Map;
2024
import java.util.function.Consumer;
25+
import java.util.stream.Collectors;
2126
import software.amazon.awssdk.annotations.SdkInternalApi;
2227
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
2328
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
29+
import software.amazon.awssdk.enhanced.dynamodb.IndexMetadata;
2430
import software.amazon.awssdk.enhanced.dynamodb.Key;
31+
import software.amazon.awssdk.enhanced.dynamodb.KeyAttributeMetadata;
2532
import software.amazon.awssdk.enhanced.dynamodb.TableMetadata;
2633
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
2734
import software.amazon.awssdk.enhanced.dynamodb.internal.operations.CreateTableOperation;
@@ -39,6 +46,8 @@
3946
import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedRequest;
4047
import software.amazon.awssdk.enhanced.dynamodb.model.DeleteItemEnhancedResponse;
4148
import software.amazon.awssdk.enhanced.dynamodb.model.DescribeTableEnhancedResponse;
49+
import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedGlobalSecondaryIndex;
50+
import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex;
4251
import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest;
4352
import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable;
4453
import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest;
@@ -51,6 +60,7 @@
5160
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
5261
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
5362
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
63+
import software.amazon.awssdk.services.dynamodb.model.ProjectionType;
5464

5565
@SdkInternalApi
5666
public class DefaultDynamoDbTable<T> implements DynamoDbTable<T> {
@@ -115,7 +125,52 @@ public void createTable(Consumer<CreateTableEnhancedRequest.Builder> requestCons
115125

116126
@Override
117127
public void createTable() {
118-
createTable(CreateTableEnhancedRequest.builder().build());
128+
Map<IndexType, List<IndexMetadata>> indexGroups = splitSecondaryIndicesToLocalAndGlobalOnes();
129+
createTable(CreateTableEnhancedRequest.builder()
130+
.localSecondaryIndices(extractLocalSecondaryIndices(indexGroups))
131+
.globalSecondaryIndices(extractGlobalSecondaryIndices(indexGroups))
132+
.build());
133+
}
134+
135+
private Map<IndexType, List<IndexMetadata>> splitSecondaryIndicesToLocalAndGlobalOnes() {
136+
String primaryPartitionKeyName = tableSchema.tableMetadata().primaryPartitionKey();
137+
Collection<IndexMetadata> indices = tableSchema.tableMetadata().indices();
138+
return indices.stream()
139+
.filter(index -> !TableMetadata.primaryIndexName().equals(index.name()))
140+
.collect(Collectors.groupingBy(metadata -> {
141+
String partitionKeyName = metadata.partitionKey().map(KeyAttributeMetadata::name).orElse(null);
142+
if (partitionKeyName == null || primaryPartitionKeyName.equals(partitionKeyName)) {
143+
return IndexType.LSI;
144+
}
145+
return IndexType.GSI;
146+
}));
147+
}
148+
149+
private List<EnhancedLocalSecondaryIndex> extractLocalSecondaryIndices(Map<IndexType, List<IndexMetadata>> indicesGroups) {
150+
return indicesGroups.getOrDefault(IndexType.LSI, emptyList()).stream()
151+
.map(this::mapIndexMetadataToEnhancedLocalSecondaryIndex)
152+
.collect(Collectors.toList());
153+
}
154+
155+
private EnhancedLocalSecondaryIndex mapIndexMetadataToEnhancedLocalSecondaryIndex(IndexMetadata indexMetadata) {
156+
return EnhancedLocalSecondaryIndex.builder()
157+
.indexName(indexMetadata.name())
158+
.projection(pb -> pb.projectionType(ProjectionType.ALL))
159+
.build();
160+
}
161+
162+
private List<EnhancedGlobalSecondaryIndex> extractGlobalSecondaryIndices(Map<IndexType, List<IndexMetadata>> indicesGroups) {
163+
return indicesGroups.getOrDefault(IndexType.GSI, emptyList()).stream()
164+
.map(this::mapIndexMetadataToEnhancedGlobalSecondaryIndex)
165+
.collect(Collectors.toList());
166+
}
167+
168+
private EnhancedGlobalSecondaryIndex mapIndexMetadataToEnhancedGlobalSecondaryIndex(IndexMetadata indexMetadata) {
169+
return EnhancedGlobalSecondaryIndex.builder()
170+
.indexName(indexMetadata.name())
171+
.projection(pb -> pb.projectionType(ProjectionType.ALL))
172+
.provisionedThroughput(ptb -> ptb.readCapacityUnits(20L).writeCapacityUnits(20L))
173+
.build();
119174
}
120175

121176
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.enhanced.dynamodb.internal.client;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
20+
/**
21+
* Enum collecting types of secondary indexes
22+
*/
23+
@SdkInternalApi
24+
public enum IndexType {
25+
LSI,
26+
GSI
27+
}

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/internal/client/DefaultDynamoDbTableTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,30 @@
1616
package software.amazon.awssdk.enhanced.dynamodb.internal.client;
1717

1818
import static org.hamcrest.MatcherAssert.assertThat;
19+
import static org.hamcrest.Matchers.containsInAnyOrder;
1920
import static org.hamcrest.Matchers.is;
2021
import static org.hamcrest.Matchers.sameInstance;
22+
import static org.mockito.Mockito.verify;
2123
import static software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues.stringValue;
2224

25+
import java.util.Iterator;
26+
import java.util.List;
2327
import java.util.Optional;
28+
import java.util.stream.Collectors;
2429
import org.junit.Test;
2530
import org.junit.runner.RunWith;
31+
import org.mockito.ArgumentCaptor;
2632
import org.mockito.Mock;
33+
import org.mockito.Mockito;
2734
import org.mockito.junit.MockitoJUnitRunner;
2835
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
2936
import software.amazon.awssdk.enhanced.dynamodb.Key;
3037
import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItem;
3138
import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithIndices;
3239
import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithSort;
40+
import software.amazon.awssdk.enhanced.dynamodb.model.CreateTableEnhancedRequest;
41+
import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedGlobalSecondaryIndex;
42+
import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedLocalSecondaryIndex;
3343
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
3444

3545
@RunWith(MockitoJUnitRunner.class)
@@ -113,4 +123,50 @@ public void keyFrom_primaryIndex_partitionAndNullSort() {
113123
assertThat(key.partitionKeyValue(), is(stringValue(item.getId())));
114124
assertThat(key.sortKeyValue(), is(Optional.empty()));
115125
}
126+
127+
@Test
128+
public void createTable_doesNotTreatPrimaryIndexAsAnyOfSecondaryIndexes() {
129+
DefaultDynamoDbTable<FakeItem> dynamoDbMappedIndex =
130+
Mockito.spy(new DefaultDynamoDbTable<>(mockDynamoDbClient,
131+
mockDynamoDbEnhancedClientExtension,
132+
FakeItem.getTableSchema(),
133+
"test_table"));
134+
135+
dynamoDbMappedIndex.createTable();
136+
137+
CreateTableEnhancedRequest request = captureCreateTableRequest(dynamoDbMappedIndex);
138+
139+
assertThat(request.localSecondaryIndices().size(), is(0));
140+
assertThat(request.globalSecondaryIndices().size(), is(0));
141+
}
142+
143+
@Test
144+
public void createTable_groupsSecondaryIndexesExistingInTableSchema() {
145+
DefaultDynamoDbTable<FakeItemWithIndices> dynamoDbMappedIndex =
146+
Mockito.spy(new DefaultDynamoDbTable<>(mockDynamoDbClient,
147+
mockDynamoDbEnhancedClientExtension,
148+
FakeItemWithIndices.getTableSchema(),
149+
"test_table"));
150+
151+
dynamoDbMappedIndex.createTable();
152+
153+
CreateTableEnhancedRequest request = captureCreateTableRequest(dynamoDbMappedIndex);
154+
155+
assertThat(request.localSecondaryIndices().size(), is(1));
156+
Iterator<EnhancedLocalSecondaryIndex> lsiIterator = request.localSecondaryIndices().iterator();
157+
assertThat(lsiIterator.next().indexName(), is("lsi_1"));
158+
159+
assertThat(request.globalSecondaryIndices().size(), is(2));
160+
List<String> globalIndicesNames = request.globalSecondaryIndices().stream()
161+
.map(EnhancedGlobalSecondaryIndex::indexName)
162+
.collect(Collectors.toList());
163+
assertThat(globalIndicesNames, containsInAnyOrder("gsi_1", "gsi_2"));
164+
}
165+
166+
private static <T> CreateTableEnhancedRequest captureCreateTableRequest(DefaultDynamoDbTable<T> index) {
167+
ArgumentCaptor<CreateTableEnhancedRequest> createTableOperationCaptor =
168+
ArgumentCaptor.forClass(CreateTableEnhancedRequest.class);
169+
verify(index).createTable(createTableOperationCaptor.capture());
170+
return createTableOperationCaptor.getValue();
171+
}
116172
}

0 commit comments

Comments
 (0)