All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.arcadedb.query.select.SelectExecutor Maven / Gradle / Ivy

There is a newer version: 24.11.1
Show newest version
package com.arcadedb.query.select;/*
 * Copyright © 2021-present Arcade Data Ltd ([email protected])
 *
 * 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.
 */

import com.arcadedb.database.Document;
import com.arcadedb.database.Identifiable;
import com.arcadedb.engine.Bucket;
import com.arcadedb.index.Index;
import com.arcadedb.index.IndexCursor;
import com.arcadedb.index.MultiIndexCursor;
import com.arcadedb.index.TypeIndex;
import com.arcadedb.utility.MultiIterator;
import com.arcadedb.utility.Pair;

import java.util.*;

/**
 * Native Query engine is a simple query engine that covers most of the classic use cases, such as the retrieval of records
 * with a where condition. It could be much faster than the same SQL query because it does not use any parser and it is very
 * JIT friendly. Future versions could translate the query into bytecode to have an even faster execution.
 *
 * @author Luca Garulli ([email protected])
 */
public class SelectExecutor {
  final Select select;
  long            evaluatedRecords = 0;
  List usedIndexes      = null;

  static class IndexInfo {
    public final Index   index;
    public final String  property;
    public final boolean order;

    IndexInfo(final Index index, final String property, final boolean order) {
      this.index = index;
      this.property = property;
      this.order = order;
    }
  }

  public SelectExecutor(final Select select) {
    this.select = select;
  }

   SelectIterator execute() {
    final MultiIndexCursor iteratorFromIndexes = lookForIndexes();

    final Iterator iterator;

    if (iteratorFromIndexes != null)
      iterator = iteratorFromIndexes;
    else if (select.fromType != null)
      iterator = select.database.iterateType(select.fromType.getName(), select.polymorphic);
    else if (select.fromBuckets.size() == 1)
      iterator = select.database.iterateBucket(select.fromBuckets.get(0).getName());
    else {
      final MultiIterator multiIterator = new MultiIterator<>();
      for (Bucket b : select.fromBuckets)
        multiIterator.addIterator(b.iterator());
      iterator = multiIterator;
    }

    if (select.timeoutInMs > 0 && iterator instanceof MultiIterator)
      ((MultiIterator) iterator).setTimeout(select.timeoutInMs, select.exceptionOnTimeout);

    if (select.parallel)
      return new SelectParallelIterator<>(this, iterator, iteratorFromIndexes != null && iteratorFromIndexes.getCursors() > 1);

    return new SelectIterator<>(this, iterator, iteratorFromIndexes != null && iteratorFromIndexes.getCursors() > 1);
  }

  private MultiIndexCursor lookForIndexes() {
    if (select.fromType != null && select.rootTreeElement != null) {
      final List cursors = new ArrayList<>();

      // FIND AVAILABLE INDEXES
      boolean canUseIndexes = isTheNodeFullyIndexed(select.rootTreeElement);

      filterWithIndexes(select.rootTreeElement, cursors);
      if (!cursors.isEmpty())
        return new MultiIndexCursor(cursors, select.limit, true);
    }
    return null;
  }

  private void filterWithIndexes(final SelectTreeNode node, final List cursors) {
    if (!(node.left instanceof SelectTreeNode))
      filterWithIndexesFinalNode(node, cursors);
    else {
      filterWithIndexes((SelectTreeNode) node.left, cursors);
      if (node.right != null)
        filterWithIndexes((SelectTreeNode) node.right, cursors);
    }
  }

  private void filterWithIndexesFinalNode(final SelectTreeNode node, final List cursors) {
    if (node.index == null)
      return;

    if (node.getParent().operator == SelectOperator.or) {
      if (node != node.getParent().right &&//
          !isTheNodeFullyIndexed((SelectTreeNode) node.getParent().right))
        // UNDER AN 'OR' OPERATOR BUT THE OTHER RIGHT VALUE IS NOT INDEXED: CANNOT USE THE CURRENT INDEX
        return;
    }

    final Object rightValue;
    if (node.right instanceof SelectParameterValue)
      rightValue = ((SelectParameterValue) node.right).eval(null);
    else
      rightValue = node.right;

    final String propertyName = ((SelectPropertyValue) node.left).propertyName;

    boolean ascendingOrder = true; // DEFAULT

    final IndexCursor cursor;
    if (node.operator == SelectOperator.eq)
      cursor = node.index.get(new Object[] { rightValue });
    else {
      // RANGE
      if (select.orderBy != null)
        for (Pair entry : select.orderBy) {
          if (propertyName.equals(entry.getFirst())) {
            if (!entry.getSecond())
              ascendingOrder = false;
            break;
          }
        }

      if (node.operator == SelectOperator.gt)
        cursor = node.index.range(ascendingOrder, new Object[] { rightValue }, false, null, false);
      else if (node.operator == SelectOperator.ge)
        cursor = node.index.range(ascendingOrder, new Object[] { rightValue }, true, null, false);
      else if (node.operator == SelectOperator.lt)
        cursor = node.index.range(ascendingOrder, null, false, new Object[] { rightValue }, false);
      else if (node.operator == SelectOperator.le)
        cursor = node.index.range(ascendingOrder, null, false, new Object[] { rightValue }, true);
      else
        return;
    }

    final SelectTreeNode parentNode = node.getParent();
    if (parentNode.operator == SelectOperator.and && parentNode.left == node) {
      if (!node.index.isUnique()) {
        // CHECK IF THERE IS ANOTHER INDEXED NODE ON THE SIBLING THAT IS UNIQUE (TO PREFER TO THIS)
        final TypeIndex rightIndex = ((SelectTreeNode) parentNode.right).index;
        if (rightIndex != null && rightIndex.isUnique()) {
          // DO NOT USE THIS INDEX (NOT UNIQUE), NOT WORTH IT
          node.index = null;
          return;
        }
      } else {
        // REMOVE THE INDEX ON THE SIBLING NODE
        // TODO CALCULATE WHICH ONE IS FASTER AND REMOVE THE SLOWER ONE
        ((SelectTreeNode) parentNode.right).index = null;
      }
    }

    if (usedIndexes == null)
      usedIndexes = new ArrayList<>();

    usedIndexes.add(new IndexInfo(node.index, propertyName, ascendingOrder));
    cursors.add(cursor);
  }

  /**
   * Considers a fully indexed node when both properties are indexed or only one with an AND operator.
   */
  private boolean isTheNodeFullyIndexed(final SelectTreeNode node) {
    if (node == null)
      return true;

    if (!(node.left instanceof SelectTreeNode)) {
      if (!(node.right instanceof SelectPropertyValue)) {
        final TypeIndex propertyIndex = select.fromType.getPolymorphicIndexByProperties(
            ((SelectPropertyValue) node.left).propertyName);

        if (propertyIndex != null)
          node.index = propertyIndex;

        return propertyIndex != null;
      }
    } else {
      final boolean leftIsIndexed = isTheNodeFullyIndexed((SelectTreeNode) node.left);
      final boolean rightIsIndexed = isTheNodeFullyIndexed((SelectTreeNode) node.right);

      if (node.operator.equals(SelectOperator.and))
        // AND: ONE OR BOTH MEANS INDEXED
        return leftIsIndexed || rightIsIndexed;
      else if (node.operator.equals(SelectOperator.or))
        return leftIsIndexed || rightIsIndexed;
      else if (node.operator.equals(SelectOperator.not))
        return leftIsIndexed;
    }
    return false;
  }

  public static Object evaluateValue(final Document record, final Object value) {
    if (value == null)
      return null;
    else if (value instanceof SelectTreeNode)
      return ((SelectTreeNode) value).eval(record);
    else if (value instanceof SelectRuntimeValue)
      return ((SelectRuntimeValue) value).eval(record);
    return value;
  }

  public Map metrics() {
    return Map.of("evaluatedRecords", evaluatedRecords, "usedIndexes", usedIndexes != null ? usedIndexes.size() : 0);
  }

  boolean evaluateWhere(final Document record) {
    ++evaluatedRecords;
    final Object result = select.rootTreeElement.eval(record);
    if (result instanceof Boolean)
      return (Boolean) result;
    throw new IllegalArgumentException("A boolean result was expected but '" + result + "' was returned");
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy