in services/dynamodb/src/it/java/software/amazon/awssdk/services/dynamodb/SecondaryIndexesIntegrationTest.java [143:339]
public void testQueryWithGlobalSecondaryIndex() throws InterruptedException {
// GSI attributes don't have to be unique
// so items with the same GSI keys but different primary keys
// could co-exist in the table.
int totalDuplicateGSIKeys = 10;
Random random = new Random();
String duplicateGSIHashValue = UUID.randomUUID().toString();
int duplicateGSIRangeValue = random.nextInt();
for (int i = 0; i < totalDuplicateGSIKeys; i++) {
Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
item.put(HASH_KEY_NAME, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
item.put(RANGE_KEY_NAME, AttributeValue.builder().n(Integer.toString(i)).build());
item.put(GSI_HASH_KEY_NAME, AttributeValue.builder().s(duplicateGSIHashValue).build());
item.put(GSI_RANGE_KEY_NAME, AttributeValue.builder().n(Integer.toString(duplicateGSIRangeValue)).build());
dynamo.putItem(PutItemRequest.builder().tableName(tableName).item(item).build());
}
// Query the duplicate GSI key values should return all the items
Map<String, Condition> keyConditions = new HashMap<String, Condition>();
keyConditions.put(
GSI_HASH_KEY_NAME,
Condition.builder().attributeValueList(
AttributeValue.builder().s((duplicateGSIHashValue)).build())
.comparisonOperator(ComparisonOperator.EQ).build());
keyConditions.put(
GSI_RANGE_KEY_NAME,
Condition.builder().attributeValueList(
AttributeValue.builder().n(Integer
.toString(duplicateGSIRangeValue)).build())
.comparisonOperator(ComparisonOperator.EQ).build());
// All the items with the GSI keys should be returned
assertQueryResponseCount(totalDuplicateGSIKeys, QueryRequest.builder()
.tableName(tableName)
.indexName(GSI_NAME)
.keyConditions(keyConditions).build());
// Other than this, the behavior of GSI query should be the similar
// as LSI query. So following code is similar to that used for
// LSI query test.
String randomPrimaryHashKeyValue = UUID.randomUUID().toString();
String randomGSIHashKeyValue = UUID.randomUUID().toString();
int totalItemsPerHash = 10;
int totalIndexedItemsPerHash = 5;
Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
item.put(HASH_KEY_NAME, AttributeValue.builder().s(randomPrimaryHashKeyValue).build());
item.put(GSI_HASH_KEY_NAME, AttributeValue.builder().s(randomGSIHashKeyValue).build());
// Items with GSI keys
for (int i = 0; i < totalIndexedItemsPerHash; i++) {
item.put(RANGE_KEY_NAME, AttributeValue.builder().n(Integer.toString(i)).build());
item.put(GSI_RANGE_KEY_NAME, AttributeValue.builder().n(Integer.toString(i)).build());
item.put("attribute_" + i, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
dynamo.putItem(PutItemRequest.builder().tableName(tableName).item(item).build());
item.remove("attribute_" + i);
}
item.remove(GSI_RANGE_KEY_NAME);
// Items with incomplete GSI keys (no GSI range key)
for (int i = totalIndexedItemsPerHash; i < totalItemsPerHash; i++) {
item.put(RANGE_KEY_NAME, AttributeValue.builder().n(Integer.toString(i)).build());
item.put("attribute_" + i, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
dynamo.putItem(PutItemRequest.builder().tableName(tableName).item(item).build());
item.remove("attribute_" + i);
}
/**
* 1) Query-with-GSI (only by GSI hash key)
*/
QueryResponse result = dynamo.query(QueryRequest.builder()
.tableName(tableName)
.indexName(GSI_NAME)
.keyConditions(
Collections.singletonMap(
GSI_HASH_KEY_NAME,
Condition.builder()
.attributeValueList(AttributeValue.builder()
.s(randomGSIHashKeyValue).build())
.comparisonOperator(
ComparisonOperator.EQ).build())).build());
// Only the indexed items should be returned
assertEquals((Object) totalIndexedItemsPerHash, (Object) result.count());
// By default, the result includes all the key attributes (2 primary + 2 GSI).
assertEquals(4, result.items().get(0).size());
/**
* 2) Query-with-GSI (by GSI hash + range)
*/
int rangeKeyConditionRange = 2;
keyConditions = new HashMap<String, Condition>();
keyConditions.put(
GSI_HASH_KEY_NAME,
Condition.builder()
.attributeValueList(AttributeValue.builder()
.s(randomGSIHashKeyValue)
.build())
.comparisonOperator(ComparisonOperator.EQ)
.build());
keyConditions.put(
GSI_RANGE_KEY_NAME,
Condition.builder()
.attributeValueList(AttributeValue.builder()
.n(Integer.toString(rangeKeyConditionRange))
.build())
.comparisonOperator(ComparisonOperator.LT)
.build());
result = dynamo.query(QueryRequest.builder()
.tableName(tableName)
.indexName(GSI_NAME)
.keyConditions(keyConditions)
.build());
assertEquals((Object) rangeKeyConditionRange, (Object) result.count());
/**
* 3) Query-with-GSI does not support Select.ALL_ATTRIBUTES if the index
* was not created with this projection type.
*/
try {
result = dynamo.query(QueryRequest.builder()
.tableName(tableName)
.indexName(GSI_NAME)
.keyConditions(
Collections.singletonMap(
GSI_HASH_KEY_NAME,
Condition.builder()
.attributeValueList(AttributeValue.builder()
.s(randomGSIHashKeyValue)
.build())
.comparisonOperator(ComparisonOperator.EQ)
.build()))
.select(Select.ALL_ATTRIBUTES).build());
fail("SdkServiceException is expected");
} catch (SdkServiceException exception) {
assertTrue(exception.getMessage().contains("Select type ALL_ATTRIBUTES is not supported for global secondary"));
}
/**
* 4) Query-with-GSI on selected attributes (by AttributesToGet)
*/
result = dynamo.query(QueryRequest.builder()
.tableName(tableName)
.indexName(GSI_NAME)
.keyConditions(
Collections.singletonMap(
GSI_HASH_KEY_NAME,
Condition.builder()
.attributeValueList(AttributeValue.builder()
.s(randomGSIHashKeyValue).build())
.comparisonOperator(ComparisonOperator.EQ).build()))
.attributesToGet(HASH_KEY_NAME, RANGE_KEY_NAME).build());
// Only the indexed items should be returned
assertEquals((Object) totalIndexedItemsPerHash, (Object) result.count());
// Two attributes as specified in AttributesToGet
assertEquals(2, result.items().get(0).size());
/**
* 5) Exception when using both Selection and AttributeToGet
*/
try {
result = dynamo.query(QueryRequest.builder()
.tableName(tableName)
.indexName(GSI_NAME)
.keyConditions(
Collections.singletonMap(
GSI_HASH_KEY_NAME,
Condition.builder().attributeValueList(
AttributeValue.builder()
.s(randomGSIHashKeyValue).build())
.comparisonOperator(ComparisonOperator.EQ).build()))
.attributesToGet(HASH_KEY_NAME, RANGE_KEY_NAME, LSI_RANGE_KEY_NAME)
.select(Select.ALL_PROJECTED_ATTRIBUTES).build());
fail("Should trigger exception when using both Select and AttributeToGet.");
} catch (SdkServiceException exception) {
// Ignored or expected.
}
/**
* 6) Query-with-GSI on selected attributes (by Select.SPECIFIC_ATTRIBUTES)
*/
result = dynamo.query(QueryRequest.builder()
.tableName(tableName)
.indexName(GSI_NAME)
.keyConditions(
Collections.singletonMap(
GSI_HASH_KEY_NAME,
Condition.builder().attributeValueList(
AttributeValue.builder()
.s(randomGSIHashKeyValue).build())
.comparisonOperator(
ComparisonOperator.EQ).build()))
.attributesToGet(HASH_KEY_NAME)
.select(Select.SPECIFIC_ATTRIBUTES).build());
// Only the indexed items should be returned
assertEquals((Object) totalIndexedItemsPerHash, (Object) result.count());
// Only one attribute as specified in AttributesToGet
assertEquals(1, result.items().get(0).size());
}