use of com.google.storage.onestore.v3.OnestoreEntity.Index.Property in project appengine-java-standard by GoogleCloudPlatform.
the class CompositeIndexManager method compositeIndexForQuery.
/**
* Given a {@link IndexComponentsOnlyQuery}, return the {@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.
* @return The index that must be present in order to fulfill the query, or {@code null} if no
* index is needed.
*/
@Nullable
protected Index compositeIndexForQuery(final IndexComponentsOnlyQuery indexOnlyQuery) {
DatastoreV3Pb.Query query = indexOnlyQuery.getQuery();
boolean hasKind = query.hasKind();
boolean isAncestor = query.hasAncestor();
List<Filter> filters = query.filters();
List<Order> orders = query.orders();
if (filters.isEmpty() && orders.isEmpty()) {
// built-in primary key or kind index can handle this.
return null;
}
// Group the filters by operator.
List<String> eqProps = indexOnlyQuery.getPrefix();
List<Property> indexProperties = indexOnlyQuery.isGeo() ? getNeededSearchProps(eqProps, indexOnlyQuery.getGeoProperties()) : getRecommendedIndexProps(indexOnlyQuery);
if (hasKind && !eqProps.isEmpty() && eqProps.size() == filters.size() && !indexOnlyQuery.hasKeyProperty() && orders.isEmpty()) {
// specified, and those queries can _not_ be satisfied by merge-join.
return null;
}
if (hasKind && !isAncestor && indexProperties.size() <= 1 && !indexOnlyQuery.isGeo() && (!indexOnlyQuery.hasKeyProperty() || indexProperties.get(0).getDirectionEnum() == Property.Direction.ASCENDING)) {
// (a.k.a. Search) indexes, we might.)
return null;
}
Index index = new Index();
index.setEntityType(query.getKind());
index.setAncestor(isAncestor);
index.mutablePropertys().addAll(indexProperties);
return index;
}
use of com.google.storage.onestore.v3.OnestoreEntity.Index.Property 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.Index.Property in project appengine-java-standard by GoogleCloudPlatform.
the class CompositeIndexUtils method generateXmlForIndex.
/**
* Generate an xml representation of the provided {@link Index}.
*
* <pre>{@code
* <datastore-indexes autoGenerate="true">
* <datastore-index kind="a" ancestor="false">
* <property name="yam" direction="asc"/>
* <property name="not yam" direction="desc"/>
* </datastore-index>
* </datastore-indexes>
* }</pre>
*
* @param index The index for which we want an xml representation.
* @param source The source of the provided index.
* @return The xml representation of the provided index.
*/
public static String generateXmlForIndex(Index index, IndexSource source) {
// Careful with pb method names - index.hasAncestor just returns whether or not
// the ancestor was set, not the value of the field itself. We always provide
// a value for this field, so we just want the value.
boolean isAncestor = index.isAncestor();
if (index.propertySize() == 0) {
return String.format(DATASTORE_INDEX_NO_PROPERTIES_XML_FORMAT, index.getEntityType(), isAncestor, source);
}
boolean isSearchIndex = false;
StringBuilder sb = new StringBuilder();
for (Property prop : index.propertys()) {
String extraAttribute;
if (prop.getDirectionEnum() == Direction.ASCENDING) {
extraAttribute = ASC_ATTRIBUTE;
} else if (prop.getDirectionEnum() == Direction.DESCENDING) {
extraAttribute = DESC_ATTRIBUTE;
} else if (prop.getModeEnum() == Mode.GEOSPATIAL) {
isSearchIndex = true;
extraAttribute = GEOSPATIAL_ATTRIBUTE;
} else {
extraAttribute = "";
}
sb.append(String.format(PROPERTY_XML_FORMAT, prop.getName(), extraAttribute));
}
String ancestorAttribute = isSearchIndex ? "" : String.format(ANCESTOR_ATTRIBUTE_FORMAT, isAncestor);
return String.format(DATASTORE_INDEX_WITH_PROPERTIES_XML_FORMAT, index.getEntityType(), ancestorAttribute, source, sb.toString());
}
use of com.google.storage.onestore.v3.OnestoreEntity.Index.Property in project appengine-java-standard by GoogleCloudPlatform.
the class DataTypeTranslator method buildImplicitKeyProperty.
private static Property buildImplicitKeyProperty(EntityProto proto) {
Property keyProp = new Property();
keyProp.setName(Entity.KEY_RESERVED_PROPERTY);
PropertyValue propVal = new PropertyValue();
propVal.setReferenceValue(KeyType.toReferenceValue(proto.getKey()));
keyProp.setValue(propVal);
return keyProp;
}
use of com.google.storage.onestore.v3.OnestoreEntity.Index.Property in project appengine-java-standard by GoogleCloudPlatform.
the class DataTypeTranslator method addListPropertyToPb.
private static void addListPropertyToPb(EntityProto proto, String name, boolean indexed, Collection<?> values, boolean forceIndexedEmbeddedEntity) {
if (values.isEmpty()) {
Property property = new Property();
property.setName(name);
property.setMultiple(false);
if (DatastoreServiceConfig.getEmptyListSupport()) {
// DS now supports empty lists, so we write a real empty list
property.setMeaning(Meaning.EMPTY_LIST);
} else {
// Backward compatible behavior: Write an empty collection as null.
// If the value is indexed it appears in queries, but distinction between
// null and empty list is lost.
}
// Indicate to the proto that we have set this field
property.getMutableValue();
if (indexed) {
proto.addProperty(property);
} else {
proto.addRawProperty(property);
}
} else {
// Write every element to the PB
for (Object listValue : values) {
addPropertyToPb(name, listValue, indexed, forceIndexedEmbeddedEntity, true, /* multiple */
proto);
}
}
}
Aggregations