Package com.orientechnologies.orient.core.sql

Source Code of com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect$OSearchInIndexTriple

/*
* Copyright 1999-2010 Luca Garulli (l.garulli--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.
*/
package com.orientechnologies.orient.core.sql;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.orientechnologies.common.parser.OStringParser;
import com.orientechnologies.common.profiler.OProfiler;
import com.orientechnologies.common.util.OPair;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordLazySet;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.exception.OQueryParsingException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.index.OIndexFullText;
import com.orientechnologies.orient.core.index.OIndexNotUnique;
import com.orientechnologies.orient.core.index.OIndexUnique;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.metadata.security.ODatabaseSecurityResources;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.record.ORecordSchemaAware;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper;
import com.orientechnologies.orient.core.sort.ODocumentSorter;
import com.orientechnologies.orient.core.sql.filter.OSQLFilter;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItem;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime;
import com.orientechnologies.orient.core.sql.operator.OIndexReuseType;
import com.orientechnologies.orient.core.sql.operator.OQueryOperator;
import com.orientechnologies.orient.core.sql.operator.OQueryOperatorBetween;
import com.orientechnologies.orient.core.sql.operator.OQueryOperatorContainsText;
import com.orientechnologies.orient.core.sql.operator.OQueryOperatorEquals;
import com.orientechnologies.orient.core.sql.operator.OQueryOperatorMajor;
import com.orientechnologies.orient.core.sql.operator.OQueryOperatorMajorEquals;
import com.orientechnologies.orient.core.sql.operator.OQueryOperatorMinor;
import com.orientechnologies.orient.core.sql.operator.OQueryOperatorMinorEquals;
import com.orientechnologies.orient.core.sql.operator.OQueryOperatorNotEquals;
import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery;
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;
import com.orientechnologies.orient.core.storage.ORecordBrowsingListener;
import com.orientechnologies.orient.core.storage.OStorageEmbedded;

/**
* Executes the SQL SELECT statement. the parse() method compiles the query and builds the meta information needed by the execute().
* If the query contains the ORDER BY clause, the results are temporary collected internally, then ordered and finally returned all
* together to the listener.
*
* @author Luca Garulli
*/
public class OCommandExecutorSQLSelect extends OCommandExecutorSQLAbstract implements ORecordBrowsingListener {
  private static final String                      KEYWORD_AS            = " AS ";
  public static final String                      KEYWORD_SELECT        = "SELECT";
  public static final String                      KEYWORD_ASC            = "ASC";
  public static final String                      KEYWORD_DESC          = "DESC";
  public static final String                      KEYWORD_ORDER          = "ORDER";
  public static final String                      KEYWORD_BY            = "BY";
  public static final String                      KEYWORD_ORDER_BY      = "ORDER BY";
  public static final String                      KEYWORD_LIMIT          = "LIMIT";
  public static final String                      KEYWORD_RANGE          = "RANGE";
  public static final String                      KEYWORD_RANGE_FIRST    = "FIRST";
  public static final String                      KEYWORD_RANGE_LAST    = "LAST";
  private static final String                      KEYWORD_FROM_2FIND    = " " + KEYWORD_FROM + " ";

  private static ORecordId                        FIRST                  = new ORecordId();
  private static ORecordId                        LAST                  = new ORecordId();

  private OSQLAsynchQuery<ORecordSchemaAware<?>>  request;
  private OSQLFilter                              compiledFilter;
  private Map<String, Object>                      projections            = null;
  private List<OPair<String, String>>              orderedFields;
  private List<OIdentifiable>                      tempResult;
  private int                                      resultCount;
  private ORecordId                                rangeFrom              = FIRST;
  private ORecordId                                rangeTo                = LAST;
  private Object                                  flattenTarget;
  private boolean                                  anyFunctionAggregates  = false;

  private static final class OSearchInIndexTriple {
    private OQueryOperator  indexOperator;
    private Object          key;
    private OIndex          index;

    private OSearchInIndexTriple(final OQueryOperator indexOperator, final Object key, final OIndex index) {
      this.indexOperator = indexOperator;
      this.key = key;
      this.index = index;
    }
  }

  /**
   * Compile the filter conditions only the first time.
   */
  @SuppressWarnings("unchecked")
  public OCommandExecutorSQLSelect parse(final OCommandRequestText iRequest) {
    iRequest.getDatabase().checkSecurity(ODatabaseSecurityResources.COMMAND, ORole.PERMISSION_READ);

    init(iRequest.getDatabase(), iRequest.getText());

    if (iRequest instanceof OSQLSynchQuery) {
      request = (OSQLSynchQuery<ORecordSchemaAware<?>>) iRequest;
      if (request.getBeginRange().isValid())
        rangeFrom = request.getBeginRange();
      if (request.getEndRange().isValid())
        rangeTo = request.getEndRange();
    } else if (iRequest instanceof OSQLAsynchQuery)
      request = (OSQLAsynchQuery<ORecordSchemaAware<?>>) iRequest;
    else {
      // BUILD A QUERY OBJECT FROM THE COMMAND REQUEST
      request = new OSQLSynchQuery<ORecordSchemaAware<?>>(iRequest.getText());
      request.setDatabase(iRequest.getDatabase());
      if (iRequest.getResultListener() != null)
        request.setResultListener(iRequest.getResultListener());
    }

    final int pos = parseProjections();
    if (pos == -1)
      return this;

    int endPosition = textUpperCase.indexOf(" " + OCommandExecutorSQLSelect.KEYWORD_ORDER_BY, currentPos);
    if (endPosition == -1) {
      endPosition = textUpperCase.indexOf(" " + OCommandExecutorSQLSelect.KEYWORD_RANGE, currentPos);
      if (endPosition == -1) {
        endPosition = textUpperCase.indexOf(" " + OCommandExecutorSQLSelect.KEYWORD_LIMIT, currentPos);
        if (endPosition == -1) {
          // NO OTHER STUFF: GET UNTIL THE END AND ASSURE TO RETURN FALSE IN ORDER TO AVOID PARSING OF CONDITIONS
          endPosition = text.length();
        }
      }
    }

    compiledFilter = OSQLEngine.getInstance().parseFromWhereCondition(iRequest.getDatabase(), text.substring(pos, endPosition));

    optimize();

    currentPos = compiledFilter.currentPos < 0 ? endPosition : compiledFilter.currentPos + pos;

    if (currentPos > -1 && currentPos < text.length()) {
      currentPos = OStringParser.jump(text, currentPos, " \r\n");

      final StringBuilder word = new StringBuilder();
      String w;

      while (currentPos > -1) {
        currentPos = OSQLHelper.nextWord(text, textUpperCase, currentPos, word, true);

        if (currentPos > -1) {
          w = word.toString();
          if (w.equals(KEYWORD_ORDER))
            parseOrderBy(word);
          else if (w.equals(KEYWORD_RANGE))
            parseRange(word);
          else if (w.equals(KEYWORD_LIMIT))
            parseLimit(word);
        }
      }
    }
    if (limit == 0 || limit < -1) {
      throw new IllegalArgumentException("Limit must be > 0 or = -1 (no limit)");
    }
    return this;
  }

  public Object execute(final Map<Object, Object> iArgs) {
    // TODO: SUPPORT MULTIPLE CLASSES LIKE A SQL JOIN
    compiledFilter.bindParameters(iArgs);

    if (compiledFilter.getTargetClasses() != null)
      searchInClasses();
    else if (compiledFilter.getTargetClusters() != null)
      searchInClusters();
    else if (compiledFilter.getTargetIndex() != null)
      searchInIndex();
    else if (compiledFilter.getTargetRecords() != null)
      searchInRecords();
    else
      throw new OQueryParsingException("No source found in query: specify class, clusters or single records");

    applyOrderBy();
    applyFlatten();
    return processResult();
  }

  public boolean foreach(final ORecordInternal<?> iRecord) {
    if (filter(iRecord))
      return addResult(iRecord);

    return true;
  }

  protected boolean addResult(final OIdentifiable iRecord) {
    resultCount++;

    final OIdentifiable recordCopy = iRecord instanceof ORecord<?> ? ((ORecord<?>) iRecord).copy() : iRecord.getIdentity().copy();

    if (orderedFields != null || flattenTarget != null) {
      // ORDER BY CLAUSE: COLLECT ALL THE RECORDS AND ORDER THEM AT THE END
      if (tempResult == null)
        tempResult = new ArrayList<OIdentifiable>();

      tempResult.add(recordCopy);
    } else
      // CALL THE LISTENER NOW
      processRecordAsResult(recordCopy);

    if (orderedFields == null && limit > -1 && resultCount >= limit || request.getLimit() > -1 && resultCount >= request.getLimit())
      // BREAK THE EXECUTION
      return false;

    return true;
  }

  public Map<String, Object> getProjections() {
    return projections;
  }

  public List<OPair<String, String>> getOrderedFields() {
    return orderedFields;
  }

  protected void parseOrderBy(final StringBuilder word) {
    int newPos = OSQLHelper.nextWord(text, textUpperCase, currentPos, word, true);

    if (!KEYWORD_BY.equals(word.toString()))
      throw new OQueryParsingException("Expected keyword " + KEYWORD_BY);

    currentPos = newPos;

    String fieldName;
    String fieldOrdering;

    orderedFields = new ArrayList<OPair<String, String>>();
    while (currentPos != -1 && (orderedFields.size() == 0 || word.toString().equals(","))) {
      currentPos = OSQLHelper.nextWord(text, textUpperCase, currentPos, word, false);
      if (currentPos == -1)
        throw new OCommandSQLParsingException("Field name expected", text, currentPos);

      fieldName = word.toString();

      currentPos = OSQLHelper.nextWord(text, textUpperCase, currentPos, word, true);
      if (currentPos == -1 || word.toString().equals(KEYWORD_LIMIT))
        // END/NEXT CLAUSE: SET AS ASC BY DEFAULT
        fieldOrdering = KEYWORD_ASC;
      else {

        if (word.toString().endsWith(",")) {
          currentPos--;
          word.deleteCharAt(word.length() - 1);
        }

        if (word.toString().equals(KEYWORD_ASC))
          fieldOrdering = KEYWORD_ASC;
        else if (word.toString().equals(KEYWORD_DESC))
          fieldOrdering = KEYWORD_DESC;
        else
          throw new OCommandSQLParsingException("Ordering mode '" + word
              + "' not supported. Valid is 'ASC', 'DESC' or nothing ('ASC' by default)", text, currentPos);

        currentPos = OSQLHelper.nextWord(text, textUpperCase, currentPos, word, true);
      }

      orderedFields.add(new OPair<String, String>(fieldName, fieldOrdering));

      if (currentPos == -1)
        break;
    }

    if (orderedFields.size() == 0)
      throw new OCommandSQLParsingException("Order by field set was missed. Example: ORDER BY name ASC, salary DESC", text,
          currentPos);

    if (word.toString().equals(KEYWORD_LIMIT))
      // GO BACK
      currentPos -= KEYWORD_LIMIT.length();

    if (word.toString().equals(KEYWORD_RANGE))
      // GO BACK
      currentPos -= KEYWORD_RANGE.length();
  }

  protected void parseRange(final StringBuilder word) {
    int newPos = OSQLHelper.nextWord(text, textUpperCase, currentPos, word, true);

    rangeFrom = extractRangeBound(word.toString());

    if (newPos == -1)
      return;

    currentPos = newPos;
    newPos = OSQLHelper.nextWord(text, textUpperCase, currentPos, word, true);

    if (newPos == -1)
      return;

    if (!word.toString().equalsIgnoreCase("LIMIT")) {
      rangeTo = extractRangeBound(word.toString());
      currentPos = newPos;
    }
  }

  /**
   * Extract a range bound. Allowed values are: first, last and a valid record id
   *
   * @param iRangeBound
   *          String to parse
   * @return {@link ORecordId} instance
   * @throws OCommandSQLParsingException
   *           if no valid range has been found
   */
  protected ORecordId extractRangeBound(final String iRangeBound) throws OCommandSQLParsingException {
    if (iRangeBound.equalsIgnoreCase(KEYWORD_RANGE_FIRST))
      return FIRST;
    else if (iRangeBound.equalsIgnoreCase(KEYWORD_RANGE_LAST))
      return LAST;
    else if (!iRangeBound.contains(":"))
      throw new OCommandSQLParsingException(
          "Range must contains the keyword 'first', 'last' or a valid record id in the form of <cluster-id>:<cluster-pos>. Example: RANGE 10:50, last",
          text, currentPos);

    try {
      return new ORecordId(iRangeBound);
    } catch (Exception e) {
      throw new OCommandSQLParsingException("Invalid record id setted as RANGE to. Value setted is '" + iRangeBound
          + "' but it should be a valid record id in the form of <cluster-id>:<cluster-pos>. Example: 10:50", text, currentPos);
    }
  }

  /**
   * Parses the limit keyword if found.
   *
   * @param word
   *          StringBuilder to parse
   * @return
   * @return the limit found as integer, or -1 if no limit is found. -1 means no limits.
   * @throws OCommandSQLParsingException
   *           if no valid limit has been found
   */
  protected int parseLimit(final StringBuilder word) throws OCommandSQLParsingException {
    if (!word.toString().equals(KEYWORD_LIMIT))
      return -1;

    currentPos = OSQLHelper.nextWord(text, textUpperCase, currentPos, word, true);
    try {
      limit = Integer.parseInt(word.toString());
    } catch (Exception e) {
      throw new OCommandSQLParsingException("Invalid LIMIT value setted to '" + word
          + "' but it should be a valid integer. Example: LIMIT 10", text, currentPos);
    }
    return limit;
  }

  private boolean searchForIndexes(final List<ORecord<?>> iResultSet, final OClass iSchemaClass) {
    final List<OSearchInIndexTriple> searchInIndexTriples = new LinkedList<OSearchInIndexTriple>();
    analyzeQueryBranch(iSchemaClass, compiledFilter.getRootCondition(), searchInIndexTriples);

    if (searchInIndexTriples.isEmpty())
      return false;

    for (OSearchInIndexTriple indexTriple : searchInIndexTriples) {
      final OIndex idx = indexTriple.index.getInternal();
      final OQueryOperator operator = indexTriple.indexOperator;
      final Object key = indexTriple.key;

      final boolean indexCanBeUsedInEqualityOperators = (idx instanceof OIndexUnique || idx instanceof OIndexNotUnique);

      if (indexCanBeUsedInEqualityOperators && operator instanceof OQueryOperatorBetween) {
        final Object[] betweenKeys = (Object[]) key;
        fillSearchIndexResultSet(iResultSet,
            indexTriple.index.getValuesBetween(OSQLHelper.getValue(betweenKeys[0]), OSQLHelper.getValue(betweenKeys[2])));
        return true;
      }

      if ((indexCanBeUsedInEqualityOperators && operator instanceof OQueryOperatorEquals) || idx instanceof OIndexFullText
          && operator instanceof OQueryOperatorContainsText) {
        fillSearchIndexResultSet(iResultSet, indexTriple.index.get(key));
        return true;
      }

      if (indexCanBeUsedInEqualityOperators && operator instanceof OQueryOperatorMajor) {
        fillSearchIndexResultSet(iResultSet, idx.getValuesMajor(key, false));
        return true;
      }

      if (indexCanBeUsedInEqualityOperators && operator instanceof OQueryOperatorMajorEquals) {
        fillSearchIndexResultSet(iResultSet, idx.getValuesMajor(key, true));
        return true;
      }

      if (indexCanBeUsedInEqualityOperators && operator instanceof OQueryOperatorMinor) {
        fillSearchIndexResultSet(iResultSet, idx.getValuesMinor(key, false));
        return true;
      }

      if (indexCanBeUsedInEqualityOperators && operator instanceof OQueryOperatorMinorEquals) {
        fillSearchIndexResultSet(iResultSet, idx.getValuesMinor(key, true));
        return true;
      }
    }
    return false;
  }

  private void analyzeQueryBranch(final OClass iSchemaClass, final OSQLFilterCondition iCondition,
      final List<OSearchInIndexTriple> iSearchInIndexTriples) {
    if (iCondition == null)
      return;

    final OQueryOperator operator = iCondition.getOperator();
    if (operator == null)
      if (iCondition.getLeft() != null && iCondition.getRight() == null) {
        analyzeQueryBranch(iSchemaClass, (OSQLFilterCondition) iCondition.getLeft(), iSearchInIndexTriples);
        return;
      } else {
        return;
      }

    final OIndexReuseType indexReuseType = operator.getIndexReuseType(iCondition.getLeft(), iCondition.getRight());
    if (indexReuseType.equals(OIndexReuseType.ANY_INDEX)) {
      analyzeQueryBranch(iSchemaClass, (OSQLFilterCondition) iCondition.getLeft(), iSearchInIndexTriples);
      analyzeQueryBranch(iSchemaClass, (OSQLFilterCondition) iCondition.getRight(), iSearchInIndexTriples);
    } else if (indexReuseType.equals(OIndexReuseType.INDEX_METHOD)) {
      if (!searchIndexedProperty(iSchemaClass, iCondition, iCondition.getLeft(), iSearchInIndexTriples))
        searchIndexedProperty(iSchemaClass, iCondition, iCondition.getRight(), iSearchInIndexTriples);
    }
  }

  /**
   * Searches a value in index if the property defines it.
   *
   * @param iSchemaClass
   *          Schema class
   * @param iCondition
   *          Condition item
   * @param iItem
   *          Value to search
   * @return true if the property was indexed and found, otherwise false
   */
  private boolean searchIndexedProperty(OClass iSchemaClass, final OSQLFilterCondition iCondition, final Object iItem,
      final List<OSearchInIndexTriple> iSearchInIndexTriples) {
    if (iItem == null || !(iItem instanceof OSQLFilterItemField))
      return false;

    if (iCondition.getLeft() instanceof OSQLFilterItemField && iCondition.getRight() instanceof OSQLFilterItemField)
      return false;

    final OSQLFilterItemField item = (OSQLFilterItemField) iItem;

    OProperty prop = iSchemaClass.getProperty(item.getRoot());

    while ((prop == null || !prop.isIndexed()) && iSchemaClass.getSuperClass() != null) {
      iSchemaClass = iSchemaClass.getSuperClass();
      prop = iSchemaClass.getProperty(item.getRoot());
    }

    if (prop != null && prop.isIndexed()) {
      final Object origValue = iCondition.getLeft() == iItem ? iCondition.getRight() : iCondition.getLeft();
      final OIndex underlyingIndex = prop.getIndex().getUnderlying();

      if (iCondition.getOperator() instanceof OQueryOperatorBetween) {
        iSearchInIndexTriples.add(new OSearchInIndexTriple(iCondition.getOperator(), origValue, underlyingIndex));
        return true;
      }

      Object value = OSQLHelper.getValue(origValue);

      if (value == null)
        return false;

      value = OType.convert(value, underlyingIndex.getKeyType().getDefaultJavaType());

      if (value == null)
        return false;

      iSearchInIndexTriples.add(new OSearchInIndexTriple(iCondition.getOperator(), value, underlyingIndex));
      return true;
    }

    return false;
  }

  /**
   * Copies or loads by their {@link ORID}s records that are returned from property index in {@link #searchIndexedProperty} method
   * to the search result.
   *
   * @param resultSet
   *          Search result.
   * @param indexResultSet
   *          Result of index search.
   */
  private void fillSearchIndexResultSet(final List<ORecord<?>> resultSet, final Collection<OIdentifiable> indexResultSet) {
    if (indexResultSet != null && indexResultSet.size() > 0)
      for (OIdentifiable o : indexResultSet) {
        if (rangeFrom != FIRST && o.getIdentity().compareTo(rangeFrom) <= 0)
          continue;

        if (rangeTo != LAST && o.getIdentity().compareTo(rangeTo) > 0)
          continue;

        if (o instanceof ORID)
          resultSet.add(database.load((ORID) o));
        else
          resultSet.add((ORecord<?>) o);
      }
  }

  protected boolean filter(final ORecordInternal<?> iRecord) {
    return compiledFilter.evaluate(database, (ORecordSchemaAware<?>) iRecord);
  }

  protected int parseProjections() {
    int currentPos = 0;
    final StringBuilder word = new StringBuilder();

    currentPos = OSQLHelper.nextWord(text, textUpperCase, currentPos, word, true);
    if (!word.toString().equals(KEYWORD_SELECT))
      return -1;

    int fromPosition = textUpperCase.indexOf(KEYWORD_FROM_2FIND, currentPos);
    if (fromPosition == -1)
      throw new OQueryParsingException("Missed " + KEYWORD_FROM, text, currentPos);

    Object projectionValue;
    final String projectionString = text.substring(currentPos, fromPosition).trim();
    if (projectionString.length() > 0 && !projectionString.equals("*")) {
      // EXTRACT PROJECTIONS
      projections = new LinkedHashMap<String, Object>();
      final List<String> items = OStringSerializerHelper.smartSplit(projectionString, ',');

      String fieldName;
      int pos;
      for (String projection : items) {
        projection = projection.trim();

        if (projections == null)
          throw new OCommandSQLParsingException("Projection not allowed with FLATTEN() operator");

        fieldName = null;
        pos = projection.toUpperCase().indexOf(KEYWORD_AS);
        if (pos > -1) {
          // EXTRACT ALIAS
          fieldName = projection.substring(pos + KEYWORD_AS.length()).trim();
          projection = projection.substring(0, pos).trim();

          if (projections.containsKey(fieldName))
            throw new OCommandSQLParsingException("Field '" + fieldName
                + "' is duplicated in current SELECT, choose a different name");
        } else {
          // EXTRACT THE FIELD NAME WITHOUT FUNCTIONS AND/OR LINKS
          final int pos1 = projection.indexOf('.');
          final int pos2 = projection.indexOf('(');

          pos = -1;

          if (pos1 > -1 && pos2 == -1)
            pos = pos1;
          else if (pos2 > -1 && pos1 == -1)
            pos = pos2;
          else if (pos1 > -1 && pos2 > -1)
            pos = Math.min(pos1, pos2);

          fieldName = pos > -1 ? projection.substring(0, pos) : projection;

          fieldName = OSQLHelper.stringContent(fieldName);

          // FIND A UNIQUE NAME BY ADDING A COUNTER
          for (int fieldIndex = 2; projections.containsKey(fieldName); ++fieldIndex) {
            fieldName += fieldIndex;
          }
        }

        if (projection.toUpperCase().startsWith("FLATTEN(")) {
          List<String> pars = OStringSerializerHelper.getParameters(projection);
          if (pars.size() != 1)
            throw new OCommandSQLParsingException("FLATTEN operator expects the field name as parameter. Example FLATTEN( out )");
          flattenTarget = OSQLHelper.parseValue(database, this, pars.get(0).trim());

          // BY PASS THIS AS PROJECTION BUT TREAT IT AS SPECIAL
          projections = null;

          if (!anyFunctionAggregates && flattenTarget instanceof OSQLFunctionRuntime
              && ((OSQLFunctionRuntime) flattenTarget).aggregateResults())
            anyFunctionAggregates = true;

          continue;
        }

        projectionValue = OSQLHelper.parseValue(database, this, projection);
        projections.put(fieldName, projectionValue);

        if (!anyFunctionAggregates && projectionValue instanceof OSQLFunctionRuntime
            && ((OSQLFunctionRuntime) projectionValue).aggregateResults())
          anyFunctionAggregates = true;
      }
    }

    currentPos = fromPosition + KEYWORD_FROM.length() + 1;

    return currentPos;
  }

  private void scanEntireClusters(final int[] clusterIds) {
    final ORecordId realRangeFrom = getRealRange(clusterIds, rangeFrom);
    final ORecordId realRangeTo = getRealRange(clusterIds, rangeTo);

    ((OStorageEmbedded) database.getStorage()).browse(clusterIds, realRangeFrom, realRangeTo, this,
        (ORecordInternal<?>) database.newInstance(), false);
  }

  private ORecordId getRealRange(final int[] clusterIds, final ORecordId iRange) {
    if (iRange == FIRST)
      // COMPUTE THE REAL RANGE BASED ON CLUSTERS: GET THE FIRST POSITION
      return new ORecordId(clusterIds[0], 0);
    else if (iRange == LAST)
      // COMPUTE THE REAL RANGE BASED ON CLUSTERS: GET LATEST POSITION
      return new ORecordId(clusterIds[clusterIds.length - 1], -1);
    return iRange;
  }

  private void applyOrderBy() {
    if (orderedFields == null || tempResult == null)
      return;

    ODocumentSorter.sort(tempResult, orderedFields);
    orderedFields.clear();
  }

  /**
   * Extract the content of collections and/or links and put it as result
   */
  private void applyFlatten() {
    if (flattenTarget == null)
      return;

    final List<OIdentifiable> finalResult = new ArrayList<OIdentifiable>();
    Object fieldValue;
    if (tempResult != null)
      for (OIdentifiable id : tempResult) {
        if (flattenTarget instanceof OSQLFilterItem)
          fieldValue = ((OSQLFilterItem) flattenTarget).getValue((ORecordInternal<?>) id.getRecord());
        else if (flattenTarget instanceof OSQLFunctionRuntime)
          fieldValue = ((OSQLFunctionRuntime) flattenTarget).getResult();
        else
          fieldValue = flattenTarget.toString();

        if (fieldValue != null)
          if (fieldValue instanceof Collection<?>) {
            for (Object o : ((Collection<?>) fieldValue)) {
              if (o instanceof ODocument)
                finalResult.add((ODocument) o);
            }
          } else
            finalResult.add((ODocument) fieldValue);
      }

    tempResult = finalResult;
  }

  private void processRecordAsResult(final OIdentifiable iRecord) {
    if (projections != null) {
      if (projections.size() == 1 && projections.keySet().iterator().next().equalsIgnoreCase("@rid")) {
        // SPECIAL CASE
        request.getResultListener().result(iRecord.getIdentity());
        return;
      }

      // APPLY PROJECTIONS
      final ODocument doc = (ODocument) iRecord.getRecord();
      final ODocument result = new ODocument(database).setOrdered(true);

      boolean canExcludeResult = false;

      Object value;
      for (Entry<String, Object> projection : projections.entrySet()) {
        if (projection.getValue() instanceof OSQLFilterItemField)
          value = ((OSQLFilterItemField) projection.getValue()).getValue(doc);
        else if (projection.getValue() instanceof OSQLFunctionRuntime) {
          final OSQLFunctionRuntime f = (OSQLFunctionRuntime) projection.getValue();
          canExcludeResult = f.filterResult();
          value = f.execute(doc);
        } else
          value = projection.getValue();

        if (value != null)
          result.field(projection.getKey(), value);
      }

      if (canExcludeResult && result.isEmpty())
        // RESULT EXCLUDED FOR EMPTY RECORD
        return;

      if (!anyFunctionAggregates)
        // INVOKE THE LISTENER
        request.getResultListener().result(result);
    } else
      // INVOKE THE LISTENER
      request.getResultListener().result(iRecord);
  }

  private void searchInClasses() {
    final int[] clusterIds;
    final OClass cls = compiledFilter.getTargetClasses().keySet().iterator().next();

    database.checkSecurity(ODatabaseSecurityResources.CLASS, ORole.PERMISSION_READ, cls.getName());

    clusterIds = cls.getPolymorphicClusterIds();

    // CHECK PERMISSION TO ACCESS TO ALL THE CONFIGURED CLUSTERS
    for (int clusterId : clusterIds)
      database.checkSecurity(ODatabaseSecurityResources.CLUSTER, ORole.PERMISSION_READ, database.getClusterNameById(clusterId));

    final List<ORecord<?>> resultSet = new ArrayList<ORecord<?>>();
    if (searchForIndexes(resultSet, cls)) {
      OProfiler.getInstance().updateCounter("Query.indexUsage", 1);

      // FOUND USING INDEXES
      for (ORecord<?> record : resultSet) {
        if (filter((ORecordInternal<?>) record))
          addResult(record);
      }
    } else
      // NO INDEXES: SCAN THE ENTIRE CLUSTER
      scanEntireClusters(clusterIds);
  }

  private void searchInClusters() {
    final int[] clusterIds;
    String firstCluster = compiledFilter.getTargetClusters().keySet().iterator().next();

    if (firstCluster == null || firstCluster.length() == 0)
      throw new OCommandExecutionException("No cluster or schema class selected in query");

    if (Character.isDigit(firstCluster.charAt(0)))
      // GET THE CLUSTER NUMBER
      clusterIds = OStringSerializerHelper.splitIntArray(firstCluster);
    else
      // GET THE CLUSTER NUMBER BY THE CLASS NAME
      clusterIds = new int[] { database.getClusterIdByName(firstCluster.toLowerCase()) };

    database.checkSecurity(ODatabaseSecurityResources.CLUSTER, ORole.PERMISSION_READ, firstCluster.toLowerCase());

    scanEntireClusters(clusterIds);
  }

  private void searchInRecords() {
    ORecordId rid = new ORecordId();
    ORecordInternal<?> record;
    for (String rec : compiledFilter.getTargetRecords()) {
      rid.fromString(rec);
      record = database.load(rid);
      foreach(record);
    }
  }

  private void searchInIndex() {
    final OIndex index = database.getMetadata().getIndexManager().getIndex(compiledFilter.getTargetIndex());
    if (index == null)
      throw new OCommandExecutionException("Target index '" + compiledFilter.getTargetIndex() + "' not found");

    if (compiledFilter.getRootCondition() != null) {
      if (!"KEY".equalsIgnoreCase(compiledFilter.getRootCondition().getLeft().toString()))
        throw new OCommandExecutionException("'Key' field is required for queries against indexes");

      final Object right = compiledFilter.getRootCondition().getRight();
      final Object keyValue = OSQLHelper.getValue(right);

      Collection<OIdentifiable> result = null;
      final OQueryOperator indexOperator = compiledFilter.getRootCondition().getOperator();
      if (indexOperator instanceof OQueryOperatorBetween) {
        final Object[] values = (Object[]) compiledFilter.getRootCondition().getRight();

        if (projections != null && projections.size() == 1 && projections.keySet().iterator().next().equalsIgnoreCase("@rid")) {
          // SPECIAL CASE
          result = index.getValuesBetween(OSQLHelper.getValue(values[0]), OSQLHelper.getValue(values[2]));

          for (OIdentifiable e : result)
            addResult(e.getIdentity());

        } else {
          final Collection<ODocument> entries = index.getEntriesBetween(OSQLHelper.getValue(values[0]),
              OSQLHelper.getValue(values[2]));

          for (OIdentifiable r : entries)
            addResult(r);
        }

      } else if (indexOperator instanceof OQueryOperatorMajor) {
        final Object value = compiledFilter.getRootCondition().getRight();
        if (projections != null && projections.size() == 1 && projections.keySet().iterator().next().equalsIgnoreCase("@rid")) {
          // SPECIAL CASE
          result = index.getValuesMajor(OSQLHelper.getValue(value), false);

          for (OIdentifiable e : result)
            addResult(e.getIdentity());

        } else {
          final Collection<ODocument> entries = index.getEntriesMajor(OSQLHelper.getValue(value), false);

          for (ODocument document : entries)
            addResult(document);
        }
      } else if (indexOperator instanceof OQueryOperatorMajorEquals) {
        final Object value = compiledFilter.getRootCondition().getRight();
        if (projections != null && projections.size() == 1 && projections.keySet().iterator().next().equalsIgnoreCase("@rid")) {
          // SPECIAL CASE
          result = index.getValuesMajor(OSQLHelper.getValue(value), true);

          for (OIdentifiable e : result)
            addResult(e.getIdentity());

        } else {
          final Collection<ODocument> entries = index.getEntriesMajor(OSQLHelper.getValue(value), true);

          for (ODocument document : entries)
            addResult(document);
        }
      } else if (indexOperator instanceof OQueryOperatorMinor) {
        final Object value = compiledFilter.getRootCondition().getRight();
        if (projections != null && projections.size() == 1 && projections.keySet().iterator().next().equalsIgnoreCase("@rid")) {
          // SPECIAL CASE
          result = index.getValuesMinor(OSQLHelper.getValue(value), false);

          for (OIdentifiable e : result)
            addResult(e.getIdentity());

        } else {
          final Collection<ODocument> entries = index.getEntriesMinor(OSQLHelper.getValue(value), false);

          for (ODocument document : entries)
            addResult(document);
        }
      } else if (indexOperator instanceof OQueryOperatorMinorEquals) {
        final Object value = compiledFilter.getRootCondition().getRight();
        if (projections != null && projections.size() == 1 && projections.keySet().iterator().next().equalsIgnoreCase("@rid")) {
          // SPECIAL CASE
          result = index.getValuesMinor(OSQLHelper.getValue(value), true);

          for (OIdentifiable e : result)
            addResult(e.getIdentity());

        } else {
          final Collection<ODocument> entries = index.getEntriesMinor(OSQLHelper.getValue(value), true);

          for (ODocument document : entries)
            addResult(document);
        }
      } else {
        result = index.get(keyValue);

        for (OIdentifiable r : result)
          addResult(createIndexEntryAsDocument(keyValue, r.getIdentity()));
      }

    } else {

      // ADD ALL THE ITEMS AS RESULT
      for (Iterator<Entry<Object, Set<OIdentifiable>>> it = index.iterator(); it.hasNext();) {
        final Entry<Object, Set<OIdentifiable>> current = it.next();

        for (Iterator<OIdentifiable> collIt = ((ORecordLazySet) current.getValue()).rawIterator(); collIt.hasNext();)
          addResult(createIndexEntryAsDocument(current.getKey(), collIt.next().getIdentity()));
      }

    }

    if (anyFunctionAggregates) {
      for (Entry<String, Object> projection : projections.entrySet()) {
        if (projection.getValue() instanceof OSQLFunctionRuntime) {
          final OSQLFunctionRuntime f = (OSQLFunctionRuntime) projection.getValue();
          f.setResult(index.getSize());
        }
      }
    }
  }

  private ODocument createIndexEntryAsDocument(final Object iKey, final OIdentifiable iValue) {
    final ODocument doc = new ODocument().setOrdered(true);
    doc.field("key", iKey);
    doc.field("rid", iValue);
    doc.unsetDirty();
    return doc;
  }

  private Object processResult() {
    if (anyFunctionAggregates) {
      // EXECUTE AGGREGATIONS
      Object value;
      final ODocument result = new ODocument(database).setOrdered(true);
      for (Entry<String, Object> projection : projections.entrySet()) {
        if (projection.getValue() instanceof OSQLFilterItemField)
          value = ((OSQLFilterItemField) projection.getValue()).getValue(result);
        else if (projection.getValue() instanceof OSQLFunctionRuntime) {
          final OSQLFunctionRuntime f = (OSQLFunctionRuntime) projection.getValue();
          value = f.getResult();
        } else
          value = projection.getValue();

        result.field(projection.getKey(), value);
      }

      request.getResultListener().result(result);

    } else if (tempResult != null) {
      int limitIndex = 0;
      // TEMP RESULT: RETURN ALL THE RECORDS AT THE END
      for (OIdentifiable doc : tempResult) {
        // CALL THE LISTENER
        if (orderedFields != null && limit > 0) {
          if (limitIndex < limit) {
            limitIndex++;
            processRecordAsResult(doc);
          } else
            // LIMIT REACHED
            break;

        } else {
          processRecordAsResult(doc);
        }
      }
      tempResult.clear();
      tempResult = null;

    }

    if (request instanceof OSQLSynchQuery)
      return ((OSQLSynchQuery<ORecordSchemaAware<?>>) request).getResult();

    return null;
  }

  /**
   * Optimizes the contidion tree.
   */
  private void optimize() {
    if (compiledFilter == null)
      return;

    optimizeBranch(null, compiledFilter.getRootCondition());
  }

  private void optimizeBranch(final OSQLFilterCondition iParentCondition, OSQLFilterCondition iCondition) {
    if (iCondition == null)
      return;

    final Object left = iCondition.getLeft();

    if (left instanceof OSQLFilterCondition)
      // ANALYSE LEFT RECURSIVELY
      optimizeBranch(iCondition, (OSQLFilterCondition) left);

    final Object right = iCondition.getRight();

    if (right instanceof OSQLFilterCondition)
      // ANALYSE RIGHT RECURSIVELY
      optimizeBranch(iCondition, (OSQLFilterCondition) right);

    final OQueryOperator oper = iCondition.getOperator();

    Object result = null;

    if (left instanceof OSQLFilterItemField & right instanceof OSQLFilterItemField) {
      if (((OSQLFilterItemField) left).getRoot().equals(((OSQLFilterItemField) right).getRoot())) {
        if (oper instanceof OQueryOperatorEquals)
          result = Boolean.TRUE;
        else if (oper instanceof OQueryOperatorNotEquals)
          result = Boolean.FALSE;
      }
    }

    if (result != null) {
      if (iParentCondition != null)
        if (iCondition == iParentCondition.getLeft())
          // REPLACE LEFT
          iCondition.setLeft(result);
        else
          // REPLACE RIGHT
          iCondition.setRight(result);
      else {
        // REPLACE ROOT CONDITION
        if (result instanceof Boolean && ((Boolean) result))
          compiledFilter.setRootCondition(null);
      }
    }
  }
}
TOP

Related Classes of com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect$OSearchInIndexTriple

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.