SqlAggFunction.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you 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 org.apache.calcite.sql;

import org.apache.calcite.linq4j.function.Experimental;
import org.apache.calcite.plan.Context;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.fun.SqlBasicAggFunction;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlOperandTypeInference;
import org.apache.calcite.sql.type.SqlReturnTypeInference;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.util.Optionality;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.List;

import static java.util.Objects.requireNonNull;

/**
 * Abstract base class for the definition of an aggregate function: an operator
 * which aggregates sets of values into a result.
 *
 * @see SqlBasicAggFunction
 */
public abstract class SqlAggFunction extends SqlFunction implements Context {
  private final boolean requiresOrder;
  private final boolean requiresOver;
  private final Optionality requiresGroupOrder;

  //~ Constructors -----------------------------------------------------------

  /** Creates a built-in SqlAggFunction. */
  @Deprecated // to be removed before 2.0
  protected SqlAggFunction(
      String name,
      SqlKind kind,
      SqlReturnTypeInference returnTypeInference,
      @Nullable SqlOperandTypeInference operandTypeInference,
      @Nullable SqlOperandTypeChecker operandTypeChecker,
      SqlFunctionCategory funcType) {
    // We leave sqlIdentifier as null to indicate that this is a builtin.
    this(name, null, kind, returnTypeInference, operandTypeInference,
        operandTypeChecker, funcType, false, false,
        Optionality.FORBIDDEN);
  }

  /** Creates a user-defined SqlAggFunction. */
  @Deprecated // to be removed before 2.0
  protected SqlAggFunction(
      String name,
      @Nullable SqlIdentifier sqlIdentifier,
      SqlKind kind,
      SqlReturnTypeInference returnTypeInference,
      @Nullable SqlOperandTypeInference operandTypeInference,
      @Nullable SqlOperandTypeChecker operandTypeChecker,
      SqlFunctionCategory funcType) {
    this(name, sqlIdentifier, kind, returnTypeInference, operandTypeInference,
        operandTypeChecker, funcType, false, false,
        Optionality.FORBIDDEN);
  }

  @Deprecated // to be removed before 2.0
  protected SqlAggFunction(
      String name,
      @Nullable SqlIdentifier sqlIdentifier,
      SqlKind kind,
      SqlReturnTypeInference returnTypeInference,
      @Nullable SqlOperandTypeInference operandTypeInference,
      @Nullable SqlOperandTypeChecker operandTypeChecker,
      SqlFunctionCategory funcType,
      boolean requiresOrder,
      boolean requiresOver) {
    this(name, sqlIdentifier, kind, returnTypeInference, operandTypeInference,
        operandTypeChecker, funcType, requiresOrder, requiresOver,
        Optionality.FORBIDDEN);
  }

  /** Creates a built-in or user-defined SqlAggFunction or window function.
   *
   * <p>A user-defined function will have a value for {@code sqlIdentifier}; for
   * a built-in function it will be null. */
  protected SqlAggFunction(
      String name,
      @Nullable SqlIdentifier sqlIdentifier,
      SqlKind kind,
      SqlReturnTypeInference returnTypeInference,
      @Nullable SqlOperandTypeInference operandTypeInference,
      @Nullable SqlOperandTypeChecker operandTypeChecker,
      SqlFunctionCategory funcType,
      boolean requiresOrder,
      boolean requiresOver,
      Optionality requiresGroupOrder) {
    super(name, sqlIdentifier, kind, returnTypeInference, operandTypeInference,
        operandTypeChecker, funcType);
    this.requiresOrder = requiresOrder;
    this.requiresOver = requiresOver;
    this.requiresGroupOrder =
        requireNonNull(requiresGroupOrder, "requiresGroupOrder");
  }

  //~ Methods ----------------------------------------------------------------

  @Override public <T extends Object> @Nullable T unwrap(Class<T> clazz) {
    return clazz.isInstance(this) ? clazz.cast(this) : null;
  }

  @Override public boolean isAggregator() {
    return true;
  }

  @Override public boolean isQuantifierAllowed() {
    return true;
  }

  @Override public void validateCall(
      SqlCall call,
      SqlValidator validator,
      SqlValidatorScope scope,
      SqlValidatorScope operandScope) {
    super.validateCall(call, validator, scope, operandScope);
    validator.validateAggregateParams(call, null, null, null, scope);
  }

  @Override public final boolean requiresOrder() {
    return requiresOrder;
  }

  /** Returns whether this aggregate function must, may, or must not contain a
   * {@code WITHIN GROUP (ORDER ...)} clause.
   *
   * <p>Cases:<ul>
   *
   * <li>If {@link Optionality#MANDATORY},
   * then {@code AGG(x) WITHIN GROUP (ORDER BY 1)} is valid,
   * and {@code AGG(x)} is invalid.
   *
   * <li>If {@link Optionality#OPTIONAL},
   * then {@code AGG(x) WITHIN GROUP (ORDER BY 1)}
   * and {@code AGG(x)} are both valid.
   *
   * <li>If {@link Optionality#IGNORED},
   * then {@code AGG(x)} is valid,
   * and {@code AGG(x) WITHIN GROUP (ORDER BY 1)} is valid but is
   * treated the same as {@code AGG(x)}.
   *
   * <li>If {@link Optionality#FORBIDDEN},
   * then {@code AGG(x) WITHIN GROUP (ORDER BY 1)} is invalid,
   * and {@code AGG(x)} is valid.
   * </ul>
   */
  public Optionality requiresGroupOrder() {
    return requiresGroupOrder;
  }

  @Override public final boolean requiresOver() {
    return requiresOver;
  }

  /** Returns whether this aggregate function allows the {@code DISTINCT}
   * keyword.
   *
   * <p>The default implementation returns {@link Optionality#OPTIONAL},
   * which is appropriate for most aggregate functions, including {@code SUM}
   * and {@code COUNT}.
   *
   * <p>Some aggregate functions, for example {@code MIN}, produce the same
   * result with or without {@code DISTINCT}, and therefore return
   * {@link Optionality#IGNORED} to indicate this. For such functions,
   * Calcite will probably remove {@code DISTINCT} while optimizing the query.
   */
  public Optionality getDistinctOptionality() {
    return Optionality.OPTIONAL;
  }

  @Deprecated // to be removed before 2.0
  public List<RelDataType> getParameterTypes(RelDataTypeFactory typeFactory) {
    throw new UnsupportedOperationException("remove before calcite-2.0");
  }

  @Deprecated // to be removed before 2.0
  public RelDataType getReturnType(RelDataTypeFactory typeFactory) {
    throw new UnsupportedOperationException("remove before calcite-2.0");
  }

  /** Whether this aggregate function allows a {@code FILTER (WHERE ...)}
   * clause. */
  public boolean allowsFilter() {
    return true;
  }

  /** Returns whether this aggregate function allows specifying null treatment
   * ({@code RESPECT NULLS} or {@code IGNORE NULLS}). */
  public boolean allowsNullTreatment() {
    return false;
  }

  /**
   * Gets rollup aggregation function.
   */
  public @Nullable SqlAggFunction getRollup() {
    return null;
  }

  /** Returns whether this aggregate function is a PERCENTILE function.
   * Such functions require a {@code WITHIN GROUP} clause that has precisely
   * one sort key.
   *
   * <p>NOTE: This API is experimental and subject to change without notice. */
  @Experimental
  public boolean isPercentile() {
    return false;
  }
}