private List<OIdentifiable> applyTailIndexes(final Object lastIndexResult) {
    final OIndex<?> beforeTheLastIndex = indexChain.get(indexChain.size() - 2);
    Set<Comparable> 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<Comparable> newKeys;
      if (isComposite(currentIndex)) {
        newKeys = new TreeSet<Comparable>();
        for (Comparable currentKey : currentKeys) {
          final List<OIdentifiable> currentResult = getFromCompositeIndex(currentKey, currentIndex);
          newKeys.addAll(prepareKeys(nextIndex, currentResult));
        }
      } else {
        final OIndexCursor cursor = currentIndex.iterateEntries(currentKeys, true);
        final List<OIdentifiable> keys = cursorToList(cursor);
        newKeys = prepareKeys(nextIndex, keys);
      }

      updateStatistic(currentIndex);

      currentKeys = newKeys;
    }

    return applyFirstIndex(currentKeys);
  }
  private static Collection<OIndex<?>> prepareLastIndexVariants(
      OClass iSchemaClass, OSQLFilterItemField.FieldChain fieldChain) {
    OClass oClass = iSchemaClass;
    final Collection<OIndex<?>> result = new ArrayList<OIndex<?>>();

    for (int i = 0; i < fieldChain.getItemCount() - 1; i++) {
      oClass = oClass.getProperty(fieldChain.getItemName(i)).getLinkedClass();
      if (oClass == null) {
        return result;
      }
    }

    final Set<OIndex<?>> involvedIndexes =
        new TreeSet<OIndex<?>>(
            new Comparator<OIndex<?>>() {
              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<Class<? extends OIndex>> indexTypes = new HashSet<Class<? extends OIndex>>(3);

    for (OIndex<?> involvedIndex : involvedIndexes) {
      if (!indexTypes.contains(involvedIndex.getInternal().getClass())) {
        result.add(involvedIndex);
        indexTypes.add(involvedIndex.getInternal().getClass());
      }
    }

    return result;
  }
  public List<String> getIndexNames() {
    final ArrayList<String> names = new ArrayList<String>(indexChain.size());
    for (OIndex<?> oIndex : indexChain) {
      names.add(oIndex.getName());
    }

    return names;
  }
  /** {@inheritDoc} */
  @Override
  public long getRebuildVersion() {
    long rebuildVersion = 0;

    for (OIndex<?> index : indexChain) {
      rebuildVersion += index.getRebuildVersion();
    }

    return rebuildVersion;
  }
 @Override
 public OIndexCursor iterateEntriesBetween(
     Object fromKey, boolean fromInclusive, Object toKey, boolean toInclusive, boolean ascOrder) {
   final OIndexCursor internalCursor =
       lastIndex.iterateEntriesBetween(fromKey, fromInclusive, toKey, toInclusive, ascOrder);
   return new ExternalIndexCursor(internalCursor);
 }
  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);
  }
  /** {@inheritDoc} */
  @Override
  public T get(Object iKey) {
    final Object lastIndexResult = lastIndex.get(iKey);

    final Set<OIdentifiable> result = new HashSet<OIdentifiable>();

    if (lastIndexResult != null) result.addAll(applyTailIndexes(lastIndexResult));

    return (T) result;
  }
 /**
  * Make type conversion of keys for specific index.
  *
  * @param index - index for which keys prepared for.
  * @param keys - which should be prepared.
  * @return keys converted to necessary type.
  */
 private Set<Comparable> prepareKeys(OIndex<?> index, Object keys) {
   final OIndexDefinition indexDefinition = index.getDefinition();
   if (keys instanceof Collection) {
     final Set<Comparable> newKeys = new TreeSet<Comparable>();
     for (Object o : ((Collection) keys)) {
       newKeys.add((Comparable) indexDefinition.createValue(o));
     }
     return newKeys;
   } else {
     return Collections.singleton((Comparable) indexDefinition.createValue(keys));
   }
 }
  /**
   * Register statistic information about usage of index in {@link OProfilerStub}.
   *
   * @param index which usage is registering.
   */
  private void updateStatistic(OIndex<?> index) {

    final OProfiler profiler = Orient.instance().getProfiler();
    if (profiler.isRecording()) {
      Orient.instance()
          .getProfiler()
          .updateCounter(
              profiler.getDatabaseMetric(index.getDatabaseName(), "query.indexUsed"),
              "Used index in query",
              +1);

      final int paramCount = index.getDefinition().getParamCount();
      if (paramCount > 1) {
        final String profiler_prefix =
            profiler.getDatabaseMetric(index.getDatabaseName(), "query.compositeIndexUsed");
        profiler.updateCounter(profiler_prefix, "Used composite index in query", +1);
        profiler.updateCounter(
            profiler_prefix + "." + paramCount,
            "Used composite index in query with " + paramCount + " params",
            +1);
      }
    }
  }
  private List<OIdentifiable> applyFirstIndex(Collection<Comparable> currentKeys) {
    final List<OIdentifiable> result;
    if (isComposite(firstIndex)) {
      result = new ArrayList<OIdentifiable>();
      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 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;
  }
 private static boolean isComposite(OIndex<?> currentIndex) {
   return currentIndex.getDefinition().getParamCount() > 1;
 }
 @Override
 public OIndexCursor iterateEntriesMinor(Object toKey, boolean toInclusive, boolean ascOrder) {
   final OIndexCursor internalCursor = lastIndex.iterateEntriesMinor(toKey, toInclusive, ascOrder);
   return new ExternalIndexCursor(internalCursor);
 }
 public String getDatabaseName() {
   return firstIndex.getDatabaseName();
 }
 @Override
 public OIndexCursor iterateEntries(Collection<?> keys, boolean ascSortOrder) {
   final OIndexCursor internalCursor = lastIndex.iterateEntries(keys, ascSortOrder);
   return new ExternalIndexCursor(internalCursor);
 }
 @Override
 public boolean isUnique() {
   return firstIndex.isUnique();
 }
 public ODocument checkEntry(final OIdentifiable iRecord, final Object iKey) {
   return firstIndex.checkEntry(iRecord, iKey);
 }
 /**
  * Returns internal index of last chain index, because proxy applicable to all operations that
  * last index applicable.
  */
 public OIndexInternal<T> getInternal() {
   return (OIndexInternal<T>) lastIndex.getInternal();
 }
 /** {@inheritDoc} */
 public OIndexDefinition getDefinition() {
   return lastIndex.getDefinition();
 }
  private List<OIdentifiable> getFromCompositeIndex(Comparable currentKey, OIndex<?> currentIndex) {
    final OIndexCursor cursor =
        currentIndex.iterateEntriesBetween(currentKey, true, currentKey, true, true);

    return cursorToList(cursor);
  }