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

com.arcadedb.query.sql.parser.FunctionCall Maven / Gradle / Ivy

There is a newer version: 24.11.1
Show newest version
/*
 * 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.
 *
 * SPDX-FileCopyrightText: 2021-present Arcade Data Ltd ([email protected])
 * SPDX-License-Identifier: Apache-2.0
 */
/* Generated By:JJTree: Do not edit this line. OFunctionCall.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_USERTYPE_VISIBILITY_PUBLIC=true */
package com.arcadedb.query.sql.parser;

import com.arcadedb.database.Identifiable;
import com.arcadedb.database.Record;
import com.arcadedb.exception.CommandExecutionException;
import com.arcadedb.query.sql.SQLQueryEngine;
import com.arcadedb.query.sql.executor.AggregationContext;
import com.arcadedb.query.sql.executor.CommandContext;
import com.arcadedb.query.sql.executor.FunctionAggregationContext;
import com.arcadedb.query.sql.executor.IndexableSQLFunction;
import com.arcadedb.query.sql.executor.Result;
import com.arcadedb.query.sql.executor.SQLFunction;
import com.arcadedb.query.sql.function.graph.SQLFunctionMove;

import java.util.*;
import java.util.stream.*;

public class FunctionCall extends SimpleNode {
  protected Identifier       name;
  protected List params = new ArrayList<>();
  private   SQLFunction      cachedFunction;

  public FunctionCall(final int id) {
    super(id);
  }

  public boolean isStar() {
    if (this.params.size() != 1)
      return false;

    final Expression param = params.get(0);
    if (param.mathExpression == null || !(param.mathExpression instanceof BaseExpression))
      return false;

    final BaseExpression base = (BaseExpression) param.mathExpression;
    if (base.identifier == null || base.identifier.suffix == null)
      return false;

    return base.identifier.suffix.star;
  }

  public List getParams() {
    return params;
  }

  public void setParams(final List params) {
    this.params = params;
  }

  public void toString(final Map params, final StringBuilder builder) {
    name.toString(params, builder);
    builder.append("(");
    boolean first = true;
    for (final Expression expr : this.params) {
      if (!first) {
        builder.append(", ");
      }
      expr.toString(params, builder);
      first = false;
    }
    builder.append(")");
  }

  public Object execute(final Object targetObjects, final CommandContext context) {
    return execute(targetObjects, context, name.getStringValue());
  }

  private Object execute(final Object targetObjects, final CommandContext context, final String name) {
    final List paramValues = new ArrayList<>();

    Object record;
    if (targetObjects instanceof Identifiable) {
      record = targetObjects;
    } else if (targetObjects instanceof Result) {
      if (((Result) targetObjects).isElement())
        record = ((Result) targetObjects).toElement();
      else
        record = targetObjects;
    } else {
      record = targetObjects;
    }

    if (record == null) {
      final Object current = context == null ? null : context.getVariable("current");
      if (current != null) {
        if (current instanceof Identifiable) {
          record = current;
        } else if (current instanceof Result) {
          record = ((Result) current).toElement();
        } else {
          record = current;
        }
      }
    }
    for (final Expression expr : this.params) {
      if (record instanceof Identifiable) {
        paramValues.add(expr.execute((Identifiable) record, context));
      } else if (record instanceof Result) {
        paramValues.add(expr.execute((Result) record, context));
      } else if (record == null) {
        paramValues.add(expr.execute((Result) record, context));
      } else {
        throw new CommandExecutionException("Invalid value for $current: " + record);
      }
    }

    final SQLFunction function = ((SQLQueryEngine) context.getDatabase().getQueryEngine("sql")).getFunction(name);
    if (function != null) {
      if (record instanceof Identifiable) {
        return function.execute(targetObjects, (Identifiable) record, null, paramValues.toArray(), context);
      } else if (record instanceof Result) {
        return function.execute(targetObjects, ((Result) record).getElement().orElse(null), null, paramValues.toArray(), context);
      } else if (record == null) {
        return function.execute(targetObjects, null, null, paramValues.toArray(), context);
      } else {
        throw new CommandExecutionException("Invalid value for $current: " + record);
      }
    } else {
      throw new CommandExecutionException("Function not found: " + name);
    }
  }

  public boolean isIndexedFunctionCall(final CommandContext context) {
    final SQLFunction function = getCachedFunction(context);
    return (function instanceof IndexableSQLFunction);
  }

  /**
   * see OIndexableSQLFunction.searchFromTarget()
   *
   * @param target
   * @param context
   * @param operator
   * @param rightValue
   *
   * @return
   */
  public Iterable executeIndexedFunction(final FromClause target, final CommandContext context, final BinaryCompareOperator operator,
      final Object rightValue) {
    final SQLFunction function = getFunction(context);
    if (function instanceof IndexableSQLFunction)
      return ((IndexableSQLFunction) function).searchFromTarget(target, operator, rightValue, context, this.getParams().toArray(new Expression[] {}));

    return null;
  }

  /**
   * @param target     query target
   * @param context    execution context
   * @param operator   operator at the right of the function
   * @param rightValue value to compare to function result
   *
   * @return the approximate number of items returned by the condition execution, -1 if the estimation cannot be executed
   */
  public long estimateIndexedFunction(final FromClause target, final CommandContext context, final BinaryCompareOperator operator, final Object rightValue) {
    final SQLFunction function = getFunction(context);
    if (function instanceof IndexableSQLFunction)
      return ((IndexableSQLFunction) function).estimate(target, operator, rightValue, context, this.getParams().toArray(new Expression[] {}));

    return -1;
  }

  /**
   * tests if current function is an indexed function AND that function can also be executed without using the index
   *
   * @param target   the query target
   * @param context  the execution context
   * @param operator
   * @param right
   *
   * @return true if current function is an indexed function AND that function can also be executed without using the index, false
   * otherwise
   */
  public boolean canExecuteIndexedFunctionWithoutIndex(final FromClause target, final CommandContext context, final BinaryCompareOperator operator,
      final Object right) {
    final SQLFunction function = getCachedFunction(context);
    if (function instanceof IndexableSQLFunction)
      return ((IndexableSQLFunction) function).canExecuteInline(target, operator, right, context, this.getParams().toArray(new Expression[] {}));

    return false;
  }

  /**
   * tests if current function is an indexed function AND that function can be used on this target
   *
   * @param target   the query target
   * @param context  the execution context
   * @param operator
   * @param right
   *
   * @return true if current function is an indexed function AND that function can be used on this target, false otherwise
   */
  public boolean allowsIndexedFunctionExecutionOnTarget(final FromClause target, final CommandContext context, final BinaryCompareOperator operator,
      final Object right) {
    final SQLFunction function = getCachedFunction(context);
    if (function instanceof IndexableSQLFunction)
      return ((IndexableSQLFunction) function).allowsIndexedExecution(target, operator, right, context, this.getParams().toArray(new Expression[] {}));

    return false;
  }

  /**
   * tests if current expression is an indexed function AND the function has also to be executed after the index search. In some
   * cases, the index search is accurate, so this condition can be excluded from further evaluation. In other cases the result from
   * the index is a superset of the expected result, so the function has to be executed anyway for further filtering
   *
   * @param target  the query target
   * @param context the execution context
   *
   * @return true if current expression is an indexed function AND the function has also to be executed after the index search.
   */
  public boolean executeIndexedFunctionAfterIndexSearch(final FromClause target, final CommandContext context, final BinaryCompareOperator operator,
      final Object right) {
    final SQLFunction function = getFunction(context);
    if (function instanceof IndexableSQLFunction)
      return ((IndexableSQLFunction) function).shouldExecuteAfterSearch(target, operator, right, context, this.getParams().toArray(new Expression[] {}));

    return false;
  }

  public boolean isExpand() {
    return name.getStringValue().equals("expand");
  }

  public boolean isAggregate(final CommandContext context) {
    if (isAggregateFunction(context)) {
      return true;
    }

    for (final Expression exp : params) {
      if (exp.isAggregate(context)) {
        return true;
      }
    }

    return false;
  }

  public SimpleNode splitForAggregation(final AggregateProjectionSplit aggregateProj, final CommandContext context) {
    if (isAggregate(context)) {
      final FunctionCall newFunct = new FunctionCall(-1);
      newFunct.name = this.name;
      final Identifier functionResultAlias = aggregateProj.getNextAlias();

      if (isAggregateFunction(context)) {
        if (isStar()) {
          for (final Expression param : params) {
            newFunct.getParams().add(param);
          }
        } else {
          for (final Expression param : params) {
            if (param.isAggregate(context)) {
              throw new CommandExecutionException("Cannot calculate an aggregate function of another aggregate function " + this);
            }
            final Identifier nextAlias = aggregateProj.getNextAlias();
            final ProjectionItem paramItem = new ProjectionItem(-1);
            paramItem.alias = nextAlias;
            paramItem.expression = param;
            aggregateProj.getPreAggregate().add(paramItem);

            newFunct.params.add(new Expression(nextAlias));
          }
        }
        aggregateProj.getAggregate().add(createProjection(newFunct, functionResultAlias));
        return new Expression(functionResultAlias);
      } else {
        if (isStar()) {
          for (final Expression param : params) {
            newFunct.getParams().add(param);
          }
        } else {
          for (final Expression param : params) {
            newFunct.getParams().add(param.splitForAggregation(aggregateProj, context));
          }
        }
      }
      return newFunct;
    }
    return this;
  }

  private boolean isAggregateFunction(final CommandContext context) {
    return getCachedFunction(context).aggregateResults();
  }

  private ProjectionItem createProjection(final FunctionCall newFunct, final Identifier alias) {
    final LevelZeroIdentifier l0 = new LevelZeroIdentifier(-1);
    l0.functionCall = newFunct;
    final BaseIdentifier l1 = new BaseIdentifier(-1);
    l1.levelZero = l0;
    final BaseExpression l2 = new BaseExpression(-1);
    l2.identifier = l1;
    final Expression l3 = new Expression(-1);
    l3.mathExpression = l2;
    final ProjectionItem item = new ProjectionItem(-1);
    item.alias = alias;
    item.expression = l3;
    return item;
  }

  public boolean isEarlyCalculated(final CommandContext context) {
    if (isTraverseFunction(context))
      return false;

    for (final Expression param : params) {
      if (!param.isEarlyCalculated(context)) {
        return false;
      }
    }
    return true;
  }

  private boolean isTraverseFunction(final CommandContext context) {
    if (name == null)
      return false;

    final SQLFunction function = getFunction(context);
    return function instanceof SQLFunctionMove;
  }

  public AggregationContext getAggregationContext(final CommandContext context) {
    return new FunctionAggregationContext(getFunction(context), this.params);
  }

  public FunctionCall copy() {
    final FunctionCall result = new FunctionCall(-1);
    result.name = name;
    result.params = params.stream().map(x -> x.copy()).collect(Collectors.toList());
    return result;
  }

  @Override
  protected Object[] getIdentityElements() {
    return new Object[] { name, params };
  }

  @Override
  public boolean refersToParent() {
    if (params != null) {
      for (final Expression param : params) {
        if (param != null && param.refersToParent()) {
          return true;
        }
      }
    }
    return false;
  }

  public Identifier getName() {
    return name;
  }

  public MethodCall toMethod() {
    final MethodCall result = new MethodCall(-1);
    result.methodName = name.copy();
    result.params = params.stream().map(x -> x.copy()).collect(Collectors.toList());
    return result;
  }

  public void extractSubQueries(final Identifier letAlias, final SubQueryCollector collector) {
    for (final Expression param : this.params) {
      param.extractSubQueries(letAlias, collector);
    }
  }

  public void extractSubQueries(final SubQueryCollector collector) {
    for (final Expression param : this.params)
      param.extractSubQueries(collector);
  }

  public boolean isCacheable() {
    return isGraphFunction();
  }

  private boolean isGraphFunction() {
    final String string = name.getStringValue();
    if (string.equalsIgnoreCase("out"))
      return true;
    else if (string.equalsIgnoreCase("outE"))
      return true;
    else if (string.equalsIgnoreCase("outV"))
      return true;
    else if (string.equalsIgnoreCase("in"))
      return true;
    else if (string.equalsIgnoreCase("inE"))
      return true;
    else if (string.equalsIgnoreCase("inV"))
      return true;
    else if (string.equalsIgnoreCase("both"))
      return true;
    else if (string.equalsIgnoreCase("bothE"))
      return true;
    else
      return string.equalsIgnoreCase("bothV");
  }

  private SQLFunction getFunction(final CommandContext context) {
    return ((SQLQueryEngine) context.getDatabase().getQueryEngine("sql")).getFunction(name.getStringValue()).config(params.toArray());
  }

  private SQLFunction getCachedFunction(CommandContext context) {
    if (cachedFunction == null)
      cachedFunction = getFunction(context);

    return cachedFunction;
  }
}
/* JavaCC - OriginalChecksum=290d4e1a3f663299452e05f8db718419 (do not edit this line) */