* query, or {@code null} if no index is needed.
*/
protected Index minimumCompositeIndexForQuery(IndexComponentsOnlyQuery indexOnlyQuery,
Collection<Index> indexes) {
Index suggestedIndex = compositeIndexForQuery(indexOnlyQuery);
if (suggestedIndex == null) {
return null;
}
class EqPropsAndAncestorConstraint {
final Set<String> equalityProperties;
final boolean ancestorConstraint;
EqPropsAndAncestorConstraint(Set<String> equalityProperties, boolean ancestorConstraint) {
this.equalityProperties = equalityProperties;
this.ancestorConstraint = ancestorConstraint;
}
}
Map<List<Property>, EqPropsAndAncestorConstraint> remainingMap =
new HashMap<List<Property>, EqPropsAndAncestorConstraint>();
index_for:
for (Index index : indexes) {
if (
!indexOnlyQuery.getQuery().getKind().equals(index.getEntityType()) ||
(!indexOnlyQuery.getQuery().hasAncestor() && index.isAncestor())) {
continue;
}
int postfixSplit = index.propertySize();
for (IndexComponent component : Lists.reverse(indexOnlyQuery.getPostfix())) {
if (!component.matches(index.propertys().subList(postfixSplit - component.size(),
postfixSplit))) {
continue index_for;
}
postfixSplit -= component.size();
}
Set<String> indexEqProps = Sets.newHashSetWithExpectedSize(postfixSplit);
for (Property prop : index.propertys().subList(0, postfixSplit)) {
if (!indexOnlyQuery.getPrefix().contains(prop.getName())) {
continue index_for;
}
indexEqProps.add(prop.getName());
}
List<Property> indexPostfix = index.propertys().subList(postfixSplit, index.propertySize());
Set<String> remainingEqProps;
boolean remainingAncestor;
{
EqPropsAndAncestorConstraint remaining = remainingMap.get(indexPostfix);
if (remaining == null) {
remainingEqProps = Sets.newHashSet(indexOnlyQuery.getPrefix());
remainingAncestor = indexOnlyQuery.getQuery().hasAncestor();
} else {
remainingEqProps = remaining.equalityProperties;
remainingAncestor = remaining.ancestorConstraint;
}
}
boolean modified = remainingEqProps.removeAll(indexEqProps);
if (remainingAncestor && index.isAncestor()) {
modified = true;
remainingAncestor = false;
}
if (remainingEqProps.isEmpty() && !remainingAncestor) {
return null;
}
if (!modified) {
continue;
}
remainingMap.put(indexPostfix,
new EqPropsAndAncestorConstraint(remainingEqProps, remainingAncestor));
}
if (remainingMap.isEmpty()) {
return suggestedIndex;
}
int minimumCost = Integer.MAX_VALUE;
List<Property> minimumPostfix = null;
EqPropsAndAncestorConstraint minimumRemaining = null;
for (Map.Entry<List<Property>, EqPropsAndAncestorConstraint> entry : remainingMap.entrySet()) {
int cost = entry.getValue().equalityProperties.size();
if (entry.getValue().ancestorConstraint) {
cost += 2;
}
if (cost < minimumCost) {
minimumCost = cost;
minimumPostfix = entry.getKey();
minimumRemaining = entry.getValue();
}
}
suggestedIndex.clearProperty();
suggestedIndex.setAncestor(minimumRemaining.ancestorConstraint);
for (String name : minimumRemaining.equalityProperties) {
suggestedIndex.addProperty().setName(name).setDirection(Direction.ASCENDING);
}
Collections.sort(suggestedIndex.mutablePropertys(), PROPERTY_NAME_COMPARATOR);
suggestedIndex.mutablePropertys().addAll(minimumPostfix);
return suggestedIndex;
}