in aws-android-sdk-ddb-mapper/src/main/java/com/amazonaws/mobileconnectors/dynamodbv2/dynamodbmapper/DynamoDBMapper.java [2705:2985]
private void processKeyConditions(Class<?> clazz,
QueryRequest queryRequest,
Map<String, Condition> hashKeyConditions,
Map<String, Condition> rangeKeyConditions) {
// There should be least one hash key condition.
if (hashKeyConditions == null || hashKeyConditions.isEmpty()) {
throw new IllegalArgumentException(
"Illegal query expression: No hash key condition is found in the query");
}
// We don't allow multiple range key conditions.
if (rangeKeyConditions != null && rangeKeyConditions.size() > 1) {
throw new IllegalArgumentException(
"Illegal query expression: Conditions on multiple range keys ("
+ rangeKeyConditions.keySet().toString()
+ ") are found in the query. DynamoDB service only accepts up to ONE range key condition.");
}
final boolean hasRangeKeyCondition = (rangeKeyConditions != null)
&& (!rangeKeyConditions.isEmpty());
final String userProvidedIndexName = queryRequest.getIndexName();
final String primaryHashKeyName = reflector.getPrimaryHashKeyName(clazz);
final TableIndexesInfo parsedIndexesInfo = schemaParser.parseTableIndexes(clazz, reflector);
// First collect the names of all the global/local secondary indexes
// that could be applied to this query.
// If the user explicitly specified an index name, we also need to
// 1) check the index is applicable for both hash and range key
// conditions
// 2) choose one hash key condition if there are more than one of them
boolean hasPrimaryHashKeyCondition = false;
final Map<String, Set<String>> annotatedGSIsOnHashKeys = new HashMap<String, Set<String>>();
String hashKeyNameForThisQuery = null;
boolean hasPrimaryRangeKeyCondition = false;
final Set<String> annotatedLSIsOnRangeKey = new HashSet<String>();
final Set<String> annotatedGSIsOnRangeKey = new HashSet<String>();
// Range key condition
String rangeKeyNameForThisQuery = null;
if (hasRangeKeyCondition) {
for (final String rangeKeyName : rangeKeyConditions.keySet()) {
rangeKeyNameForThisQuery = rangeKeyName;
if (reflector.hasPrimaryRangeKey(clazz)
&& rangeKeyName.equals(reflector.getPrimaryRangeKeyName(clazz))) {
hasPrimaryRangeKeyCondition = true;
}
final Collection<String> annotatedLSI = parsedIndexesInfo
.getLsiNamesByIndexRangeKey(rangeKeyName);
if (annotatedLSI != null) {
annotatedLSIsOnRangeKey.addAll(annotatedLSI);
}
final Collection<String> annotatedGSI = parsedIndexesInfo
.getGsiNamesByIndexRangeKey(rangeKeyName);
if (annotatedGSI != null) {
annotatedGSIsOnRangeKey.addAll(annotatedGSI);
}
}
if (!hasPrimaryRangeKeyCondition
&& annotatedLSIsOnRangeKey.isEmpty()
&& annotatedGSIsOnRangeKey.isEmpty()) {
throw new DynamoDBMappingException(
"The query contains a condition on a range key ("
+
rangeKeyNameForThisQuery
+ ") "
+
"that is not annotated with either @DynamoDBRangeKey or @DynamoDBIndexRangeKey.");
}
}
final boolean userProvidedLSIWithRangeKeyCondition = (userProvidedIndexName != null)
&& (annotatedLSIsOnRangeKey.contains(userProvidedIndexName));
final boolean hashOnlyLSIQuery = (userProvidedIndexName != null)
&& (!hasRangeKeyCondition)
&& parsedIndexesInfo.getAllLsiNames().contains(userProvidedIndexName);
final boolean userProvidedLSI = userProvidedLSIWithRangeKeyCondition || hashOnlyLSIQuery;
final boolean userProvidedGSIWithRangeKeyCondition = (userProvidedIndexName != null)
&& (annotatedGSIsOnRangeKey.contains(userProvidedIndexName));
final boolean hashOnlyGSIQuery = (userProvidedIndexName != null)
&& (!hasRangeKeyCondition)
&& parsedIndexesInfo.getAllGsiNames().contains(userProvidedIndexName);
final boolean userProvidedGSI = userProvidedGSIWithRangeKeyCondition || hashOnlyGSIQuery;
if (userProvidedLSI && userProvidedGSI) {
throw new DynamoDBMappingException(
"Invalid query: " +
"Index \"" + userProvidedIndexName + "\" " +
"is annotateded as both a LSI and a GSI for attribute.");
}
// Hash key conditions
for (final String hashKeyName : hashKeyConditions.keySet()) {
if (hashKeyName.equals(primaryHashKeyName)) {
hasPrimaryHashKeyCondition = true;
}
final Collection<String> annotatedGSINames = parsedIndexesInfo
.getGsiNamesByIndexHashKey(hashKeyName);
annotatedGSIsOnHashKeys.put(hashKeyName,
annotatedGSINames == null ? new HashSet<String>() : new HashSet<String>(
annotatedGSINames));
// Additional validation if the user provided an index name.
if (userProvidedIndexName != null) {
boolean foundHashKeyConditionValidWithUserProvidedIndex = false;
if (userProvidedLSI && hashKeyName.equals(primaryHashKeyName)) {
// found an applicable hash key condition (primary hash +
// LSI range)
foundHashKeyConditionValidWithUserProvidedIndex = true;
} else if (userProvidedGSI &&
annotatedGSINames != null
&& annotatedGSINames.contains(userProvidedIndexName)) {
// found an applicable hash key condition (GSI hash + range)
foundHashKeyConditionValidWithUserProvidedIndex = true;
}
if (foundHashKeyConditionValidWithUserProvidedIndex) {
if (hashKeyNameForThisQuery != null) {
throw new IllegalArgumentException(
"Ambiguous query expression: More than one hash key EQ conditions ("
+
hashKeyNameForThisQuery + ", " + hashKeyName +
") are applicable to the specified index ("
+ userProvidedIndexName + "). " +
"Please provide only one of them in the query expression.");
} else {
// found an applicable hash key condition
hashKeyNameForThisQuery = hashKeyName;
}
}
}
}
// Collate all the key conditions
final Map<String, Condition> keyConditions = new HashMap<String, Condition>();
// With user-provided index name
if (userProvidedIndexName != null) {
if (hasRangeKeyCondition
&& (!userProvidedLSI)
&& (!userProvidedGSI)) {
throw new IllegalArgumentException(
"Illegal query expression: No range key condition is applicable to the specified index ("
+ userProvidedIndexName + "). ");
}
if (hashKeyNameForThisQuery == null) {
throw new IllegalArgumentException(
"Illegal query expression: No hash key condition is applicable to the specified index ("
+ userProvidedIndexName + "). ");
}
keyConditions.put(hashKeyNameForThisQuery,
hashKeyConditions.get(hashKeyNameForThisQuery));
if (hasRangeKeyCondition) {
keyConditions.putAll(rangeKeyConditions);
}
}
// Infer the index name by finding the index shared by both hash and
// range key annotations.
else {
if (hasRangeKeyCondition) {
String inferredIndexName = null;
hashKeyNameForThisQuery = null;
if (hasPrimaryHashKeyCondition && hasPrimaryRangeKeyCondition) {
// Found valid query: primary hash + range key conditions
hashKeyNameForThisQuery = primaryHashKeyName;
} else {
// Intersect the set of all the indexes applicable to the
// range key
// with the set of indexes applicable to each hash key
// condition.
for (final String hashKeyName : annotatedGSIsOnHashKeys.keySet()) {
boolean foundValidQueryExpressionWithInferredIndex = false;
String indexNameInferredByThisHashKey = null;
if (hashKeyName.equals(primaryHashKeyName)) {
if (annotatedLSIsOnRangeKey.size() == 1) {
// Found valid query (Primary hash + LSI range
// conditions)
foundValidQueryExpressionWithInferredIndex = true;
indexNameInferredByThisHashKey = annotatedLSIsOnRangeKey.iterator()
.next();
}
}
final Set<String> annotatedGSIsOnHashKey = annotatedGSIsOnHashKeys
.get(hashKeyName);
// We don't need the data in annotatedGSIsOnHashKeys
// afterwards,
// so it's safe to do the intersection in-place.
annotatedGSIsOnHashKey.retainAll(annotatedGSIsOnRangeKey);
if (annotatedGSIsOnHashKey.size() == 1) {
// Found valid query (Hash + range conditions on a
// GSI)
if (foundValidQueryExpressionWithInferredIndex) {
hashKeyNameForThisQuery = hashKeyName;
inferredIndexName = indexNameInferredByThisHashKey;
}
foundValidQueryExpressionWithInferredIndex = true;
indexNameInferredByThisHashKey = annotatedGSIsOnHashKey.iterator()
.next();
}
if (foundValidQueryExpressionWithInferredIndex) {
if (hashKeyNameForThisQuery != null) {
throw new IllegalArgumentException(
"Ambiguous query expression: Found multiple valid queries: "
+
"(Hash: \"" + hashKeyNameForThisQuery
+ "\", Range: \"" + rangeKeyNameForThisQuery
+ "\", Index: \"" + inferredIndexName + "\") and " +
"(Hash: \"" + hashKeyName + "\", Range: \""
+ rangeKeyNameForThisQuery + "\", Index: \""
+ indexNameInferredByThisHashKey + "\").");
} else {
hashKeyNameForThisQuery = hashKeyName;
inferredIndexName = indexNameInferredByThisHashKey;
}
}
}
}
if (hashKeyNameForThisQuery != null) {
keyConditions.put(hashKeyNameForThisQuery,
hashKeyConditions.get(hashKeyNameForThisQuery));
keyConditions.putAll(rangeKeyConditions);
queryRequest.setIndexName(inferredIndexName);
} else {
throw new IllegalArgumentException(
"Illegal query expression: Cannot infer the index name from the query expression.");
}
} else {
// No range key condition is specified.
if (hashKeyConditions.size() > 1) {
if (hasPrimaryHashKeyCondition) {
keyConditions.put(primaryHashKeyName,
hashKeyConditions.get(primaryHashKeyName));
} else {
throw new IllegalArgumentException(
"Ambiguous query expression: More than one index hash key EQ conditions ("
+
hashKeyConditions.keySet()
+
") are applicable to the query. "
+
"Please provide only one of them in the query expression, or specify the appropriate index name.");
}
} else {
// Only one hash key condition
final String hashKeyName = annotatedGSIsOnHashKeys.keySet().iterator().next();
if (!hasPrimaryHashKeyCondition) {
if (annotatedGSIsOnHashKeys.get(hashKeyName).size() == 1) {
// Set the index if the index hash key is only
// annotated with one GSI.
queryRequest.setIndexName(annotatedGSIsOnHashKeys.get(hashKeyName)
.iterator().next());
} else if (annotatedGSIsOnHashKeys.get(hashKeyName).size() > 1) {
throw new IllegalArgumentException(
"Ambiguous query expression: More than one GSIs (" +
annotatedGSIsOnHashKeys.get(hashKeyName) +
") are applicable to the query. " +
"Please specify one of them in your query expression.");
} else {
throw new IllegalArgumentException(
"Illegal query expression: No GSI is found in the @DynamoDBIndexHashKey annotation for attribute "
+
"\"" + hashKeyName + "\".");
}
}
keyConditions.putAll(hashKeyConditions);
}
}
}
queryRequest.setKeyConditions(keyConditions);
}