use of com.google.storage.onestore.v3.OnestoreEntity.IndexPostfix in project appengine-java-standard by GoogleCloudPlatform.
the class CompositeIndexManager method minimumCompositeIndexForQuery.
/**
* Given a {@link IndexComponentsOnlyQuery} and a collection of existing {@link Index}s, return
* the minimum {@link Index} needed to fulfill the query, or {@code null} if no index is needed.
*
* <p>This code needs to remain in sync with its counterparts in other languages. If you modify
* this code please make sure you make the same update in the local datastore for other languages.
*
* @param indexOnlyQuery The query.
* @param indexes The existing indexes.
* @return The minimum index that must be present in order to fulfill the query, or {@code null}
* if no index is needed.
*/
@Nullable
protected Index minimumCompositeIndexForQuery(IndexComponentsOnlyQuery indexOnlyQuery, Collection<Index> indexes) {
Index suggestedIndex = compositeIndexForQuery(indexOnlyQuery);
if (suggestedIndex == null) {
return null;
}
if (indexOnlyQuery.isGeo()) {
// None of the shortcuts/optimizations below are applicable for Search indexes.
return suggestedIndex;
}
class EqPropsAndAncestorConstraint {
final Set<String> equalityProperties;
final boolean ancestorConstraint;
EqPropsAndAncestorConstraint(Set<String> equalityProperties, boolean ancestorConstraint) {
this.equalityProperties = equalityProperties;
this.ancestorConstraint = ancestorConstraint;
}
}
// Map from postfix to the remaining equality properties and ancestor constraints.
Map<List<Property>, EqPropsAndAncestorConstraint> remainingMap = new HashMap<List<Property>, EqPropsAndAncestorConstraint>();
index_for: for (Index index : indexes) {
if (// Kind must match.
!indexOnlyQuery.getQuery().getKind().equals(index.getEntityType()) || // Ancestor indexes can only be used on ancestor queries.
(!indexOnlyQuery.getQuery().hasAncestor() && index.isAncestor())) {
continue;
}
// Matching the postfix.
int postfixSplit = index.propertySize();
for (IndexComponent component : Lists.reverse(indexOnlyQuery.getPostfix())) {
if (!component.matches(index.propertys().subList(Math.max(postfixSplit - component.size(), 0), postfixSplit))) {
continue index_for;
}
postfixSplit -= component.size();
}
// Postfix matches! Now checking the prefix.
Set<String> indexEqProps = Sets.newHashSetWithExpectedSize(postfixSplit);
for (Property prop : index.propertys().subList(0, postfixSplit)) {
// Index must not contain extra properties in the prefix.
if (!indexOnlyQuery.getPrefix().contains(prop.getName())) {
continue index_for;
}
indexEqProps.add(prop.getName());
}
// Index matches!
// Find the matching remaining requirements.
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;
}
}
// Remove any remaining requirements handled by this index.
boolean modified = remainingEqProps.removeAll(indexEqProps);
if (remainingAncestor && index.isAncestor()) {
modified = true;
remainingAncestor = false;
}
if (remainingEqProps.isEmpty() && !remainingAncestor) {
// No new index needed!
return null;
}
if (!modified) {
// Index made no contribution, don't update the map.
continue;
}
// Save indexes contribution
remainingMap.put(indexPostfix, new EqPropsAndAncestorConstraint(remainingEqProps, remainingAncestor));
}
if (remainingMap.isEmpty()) {
// suggested index is the minimum index
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) {
// Arbitrary value picked because ancestor are multi-valued.
cost += 2;
}
if (cost < minimumCost) {
minimumCost = cost;
minimumPostfix = entry.getKey();
minimumRemaining = entry.getValue();
}
}
// map not empty so we should have found cost < MAX_VALUE.
requireNonNull(minimumRemaining);
requireNonNull(minimumPostfix);
// Populating suggesting the minimal index instead.
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;
}
use of com.google.storage.onestore.v3.OnestoreEntity.IndexPostfix in project appengine-java-standard by GoogleCloudPlatform.
the class CursorTest method testReverseCursorPostfix.
@SuppressWarnings("deprecation")
@Test
public void testReverseCursorPostfix() {
IndexPostfix postfixPosition = new IndexPostfix().setKey(new Reference()).setBefore(true);
Cursor pfCursor = toCursor(new CompiledCursor().setPostfixPosition(postfixPosition));
// reverse() is a no-op.
Cursor pfReverse = pfCursor.reverse();
assertThat(pfReverse).isEqualTo(pfCursor);
assertThat(pfCursor).isEqualTo(pfReverse.reverse());
}
use of com.google.storage.onestore.v3.OnestoreEntity.IndexPostfix in project appengine-java-standard by GoogleCloudPlatform.
the class CursorModernizer method modernizeCursor.
public static void modernizeCursor(CompiledCursor cursor, @Nullable DatastoreV3Pb.Query.Order.Direction firstSortDirection) throws InvalidConversionException {
// First, convert any contents of the position field.
if (cursor.hasPosition()) {
InvalidConversionException.checkConversion(!cursor.hasPostfixPosition(), "A cursor cannot specify both position and postfix position.");
InvalidConversionException.checkConversion(!cursor.hasAbsolutePosition(), "A cursor cannot specify both position and absolute position.");
Position pos = cursor.getPosition();
if (pos.hasStartKey()) {
IndexPosition indexPos = cursor.getMutableAbsolutePosition();
indexPos.setKeyAsBytes(pos.getStartKeyAsBytes());
if (pos.hasStartInclusive()) {
indexPos.setBefore(pos.isStartInclusive());
}
if (pos.hasBeforeAscending()) {
indexPos.setBeforeAscending(pos.isBeforeAscending());
}
} else if (pos.hasKey() || pos.indexValueSize() > 0) {
IndexPostfix postfixPos = cursor.getMutablePostfixPosition();
for (PositionIndexValue value : pos.indexValues()) {
IndexPostfix_IndexValue indexValue = postfixPos.addIndexValue().setPropertyName(value.getProperty());
indexValue.getMutableValue().mergeFrom(value.getValue());
}
if (pos.hasKey()) {
postfixPos.getMutableKey().mergeFrom(pos.getKey());
}
if (pos.hasStartInclusive()) {
postfixPos.setBefore(pos.isStartInclusive());
}
if (pos.hasBeforeAscending()) {
postfixPos.setBeforeAscending(pos.isBeforeAscending());
}
}
cursor.clearPosition();
}
// Next, populate before_ascending or before.
if (isEmpty(cursor)) {
return;
} else if (cursor.hasAbsolutePosition()) {
IndexPosition indexPosition = cursor.getAbsolutePosition();
if (indexPosition.hasBeforeAscending()) {
setBefore(indexPosition, firstSortDirection);
} else {
setBeforeAscending(indexPosition, firstSortDirection);
}
} else if (cursor.hasPostfixPosition()) {
IndexPostfix indexPostfix = cursor.getPostfixPosition();
if (indexPostfix.hasBeforeAscending()) {
setBefore(indexPostfix, firstSortDirection);
} else {
setBeforeAscending(indexPostfix, firstSortDirection);
}
}
}
Aggregations