com.orientechnologies.orient.core.sql.OChainedIndexProxy Maven / Gradle / Ivy
/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.orientechnologies.orient.core.sql;
import com.orientechnologies.common.listener.OProgressListener;
import com.orientechnologies.common.profiler.OProfiler;
import com.orientechnologies.common.profiler.OProfilerMBean;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.index.OIndexAbstractCursor;
import com.orientechnologies.orient.core.index.OIndexCursor;
import com.orientechnologies.orient.core.index.OIndexDefinition;
import com.orientechnologies.orient.core.index.OIndexInternal;
import com.orientechnologies.orient.core.index.OIndexKeyCursor;
import com.orientechnologies.orient.core.iterator.OEmptyIterator;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField;
import java.util.*;
/**
*
* There are some cases when we need to create index for some class by traversed property. Unfortunately, such functionality is not
* supported yet. But we can do that by creating index for each element of {@link OSQLFilterItemField.FieldChain} (which define
* "way" to our property), and then process operations consequently using previously created indexes.
*
*
* This class provides possibility to find optimal chain of indexes and then use it just like it was index for traversed property.
*
*
* IMPORTANT: this class is only for internal usage!
*
*
* @author Artem Orobets
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class OChainedIndexProxy implements OIndex {
private final OIndex firstIndex;
private final List> indexChain;
private final OIndex lastIndex;
private OChainedIndexProxy(List> indexChain) {
this.firstIndex = (OIndex) indexChain.get(0);
this.indexChain = Collections.unmodifiableList(indexChain);
lastIndex = indexChain.get(indexChain.size() - 1);
}
/**
* Create proxies that support maximum number of different operations. In case when several different indexes which support
* different operations (e.g. indexes of {@code UNIQUE} and {@code FULLTEXT} types) are possible, the creates the only one index
* of each type.
*
* @param longChain
* - property chain from the query, which should be evaluated
* @return proxies needed to process query.
*/
public static Collection> createProxies(OClass iSchemaClass, OSQLFilterItemField.FieldChain longChain) {
List> proxies = new ArrayList>();
for (List> indexChain : getIndexesForChain(iSchemaClass, longChain)) {
proxies.add(new OChainedIndexProxy(indexChain));
}
return proxies;
}
private static boolean isComposite(OIndex currentIndex) {
return currentIndex.getDefinition().getParamCount() > 1;
}
private static Iterable>> getIndexesForChain(OClass iSchemaClass, OSQLFilterItemField.FieldChain fieldChain) {
List> baseIndexes = prepareBaseIndexes(iSchemaClass, fieldChain);
if (baseIndexes == null)
return Collections.emptyList();
Collection> lastIndexes = prepareLastIndexVariants(iSchemaClass, fieldChain);
Collection>> result = new ArrayList>>();
for (OIndex lastIndex : lastIndexes) {
final List> indexes = new ArrayList>(fieldChain.getItemCount());
indexes.addAll(baseIndexes);
indexes.add(lastIndex);
result.add(indexes);
}
return result;
}
private static Collection> prepareLastIndexVariants(OClass iSchemaClass, OSQLFilterItemField.FieldChain fieldChain) {
OClass oClass = iSchemaClass;
final Collection> result = new ArrayList>();
for (int i = 0; i < fieldChain.getItemCount() - 1; i++) {
oClass = oClass.getProperty(fieldChain.getItemName(i)).getLinkedClass();
if (oClass == null) {
return result;
}
}
final Set> involvedIndexes = new TreeSet>(new Comparator>() {
public int compare(OIndex o1, OIndex o2) {
return o1.getDefinition().getParamCount() - o2.getDefinition().getParamCount();
}
});
involvedIndexes.addAll(oClass.getInvolvedIndexes(fieldChain.getItemName(fieldChain.getItemCount() - 1)));
final Collection> indexTypes = new HashSet>(3);
for (OIndex involvedIndex : involvedIndexes) {
if (!indexTypes.contains(involvedIndex.getInternal().getClass())) {
result.add(involvedIndex);
indexTypes.add(involvedIndex.getInternal().getClass());
}
}
return result;
}
private static List> prepareBaseIndexes(OClass iSchemaClass, OSQLFilterItemField.FieldChain fieldChain) {
List> result = new ArrayList>(fieldChain.getItemCount() - 1);
OClass oClass = iSchemaClass;
for (int i = 0; i < fieldChain.getItemCount() - 1; i++) {
final Set> involvedIndexes = oClass.getInvolvedIndexes(fieldChain.getItemName(i));
final OIndex bestIndex = findBestIndex(involvedIndexes);
if (bestIndex == null)
return null;
result.add(bestIndex);
oClass = oClass.getProperty(fieldChain.getItemName(i)).getLinkedClass();
}
return result;
}
/**
* Finds the index that fits better as a base index in chain.
*
* Requirements to the base index:
*
* - Should be unique or not unique. Other types can not be used to get all documents with required links.
* - Should not be composite hash index. As soon as hash index does not support partial match search.
* - Composite index that ignores null values should not be used.
* - Hash index is better than tree based indexes.
* - Non composite indexes is better that composite.
*
*
* @param indexes
* where search
* @return the index that fits better as a base index in chain
*/
protected static OIndex findBestIndex(Iterable> indexes) {
OIndex bestIndex = null;
for (OIndex index : indexes) {
if (priorityOfUsage(index) > priorityOfUsage(bestIndex))
bestIndex = index;
}
return bestIndex;
}
private static int priorityOfUsage(OIndex index) {
if (index == null)
return -1;
final OClass.INDEX_TYPE indexType = OClass.INDEX_TYPE.valueOf(index.getType());
final boolean isComposite = isComposite(index);
final boolean supportNullValues = supportNullValues(index);
int priority = 1;
if (isComposite) {
if (!supportNullValues)
return -1;
} else {
priority += 10;
}
switch (indexType) {
case UNIQUE_HASH_INDEX:
case NOTUNIQUE_HASH_INDEX:
if (isComposite)
return -1;
else
priority += 10;
break;
case UNIQUE:
case NOTUNIQUE:
priority += 5;
break;
case PROXY:
case FULLTEXT:
case DICTIONARY:
case FULLTEXT_HASH_INDEX:
case DICTIONARY_HASH_INDEX:
case SPATIAL:
return -1;
}
return priority;
}
/**
* Check if index can be used as base index.
*
* Requirements to the base index:
*
* - Should be unique or not unique. Other types can not be used to get all documents with required links.
* - Should not be composite hash index. As soon as hash index does not support partial match search.
* - Composite index that ignores null values should not be used.
*
*
* @param index
* to check
* @return true if index usage is allowed as base index.
*/
public static boolean isAppropriateAsBase(OIndex index) {
return priorityOfUsage(index) > 0;
}
private static boolean supportNullValues(OIndex index) {
final ODocument metadata = index.getMetadata();
if (metadata == null)
return false;
final Boolean ignoreNullValues = metadata.field("ignoreNullValues");
return Boolean.FALSE.equals(ignoreNullValues);
}
public String getDatabaseName() {
return firstIndex.getDatabaseName();
}
public List getIndexNames() {
final ArrayList names = new ArrayList(indexChain.size());
for (OIndex oIndex : indexChain) {
names.add(oIndex.getName());
}
return names;
}
@Override
public String getName() {
final StringBuilder res = new StringBuilder("IndexChain{");
final List indexNames = getIndexNames();
for (int i = 0; i < indexNames.size(); i++) {
String indexName = indexNames.get(i);
if (i > 0)
res.append(", ");
res.append(indexName);
}
res.append("}");
return res.toString();
}
/**
* {@inheritDoc}
*/
@Override
public T get(Object iKey) {
final Object lastIndexResult = lastIndex.get(iKey);
final Set result = new HashSet();
if (lastIndexResult != null)
result.addAll(applyTailIndexes(lastIndexResult));
return (T) result;
}
/**
* Returns internal index of last chain index, because proxy applicable to all operations that last index applicable.
*/
public OIndexInternal getInternal() {
return (OIndexInternal) lastIndex.getInternal();
}
/**
* {@inheritDoc}
*/
public OIndexDefinition getDefinition() {
return lastIndex.getDefinition();
}
private List applyTailIndexes(final Object lastIndexResult) {
final OIndex beforeTheLastIndex = indexChain.get(indexChain.size() - 2);
Set currentKeys = prepareKeys(beforeTheLastIndex, lastIndexResult);
for (int j = indexChain.size() - 2; j > 0; j--) {
final OIndex currentIndex = indexChain.get(j);
final OIndex nextIndex = indexChain.get(j - 1);
final Set newKeys;
if (isComposite(currentIndex)) {
newKeys = new TreeSet();
for (Comparable currentKey : currentKeys) {
final List currentResult = getFromCompositeIndex(currentKey, currentIndex);
newKeys.addAll(prepareKeys(nextIndex, currentResult));
}
} else {
final OIndexCursor cursor = currentIndex.iterateEntries(currentKeys, true);
final List keys = cursorToList(cursor);
newKeys = prepareKeys(nextIndex, keys);
}
updateStatistic(currentIndex);
currentKeys = newKeys;
}
return applyFirstIndex(currentKeys);
}
private List applyFirstIndex(Collection currentKeys) {
final List result;
if (isComposite(firstIndex)) {
result = new ArrayList();
for (Comparable key : currentKeys) {
result.addAll(getFromCompositeIndex(key, firstIndex));
}
} else {
final OIndexCursor cursor = firstIndex.iterateEntries(currentKeys, true);
result = cursorToList(cursor);
}
updateStatistic(firstIndex);
return result;
}
private List getFromCompositeIndex(Comparable currentKey, OIndex currentIndex) {
final OIndexCursor cursor = currentIndex.iterateEntriesBetween(currentKey, true, currentKey, true, true);
return cursorToList(cursor);
}
private List cursorToList(OIndexCursor cursor) {
final List currentResult = new ArrayList();
Map.Entry