content: /*
   * 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.Ord;
  import org.apache.calcite.rel.type.RelDataType;
  import org.apache.calcite.rex.RexWindowBound;
  import org.apache.calcite.rex.RexWindowBounds;
  import org.apache.calcite.sql.parser.SqlParserPos;
  import org.apache.calcite.sql.type.ReturnTypes;
  import org.apache.calcite.sql.type.SqlTypeFamily;
  import org.apache.calcite.sql.util.SqlBasicVisitor;
  import org.apache.calcite.sql.util.SqlVisitor;
  import org.apache.calcite.sql.validate.SqlValidator;
  import org.apache.calcite.sql.validate.SqlValidatorScope;
  import org.apache.calcite.sql.validate.SqlValidatorUtil;
  import org.apache.calcite.util.ControlFlowException;
  import org.apache.calcite.util.ImmutableNullableList;
  import org.apache.calcite.util.Litmus;
  import org.apache.calcite.util.Util;
  
  import com.google.common.collect.ImmutableList;
  
  import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
  import org.checkerframework.checker.nullness.qual.Nullable;
  import org.checkerframework.dataflow.qual.Pure;
  
  import java.math.BigDecimal;
  import java.util.List;
  
  import static org.apache.calcite.linq4j.Nullness.castNonNull;
  import static org.apache.calcite.util.Static.RESOURCE;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * SQL window specification.
   *
   * <p>For example, the query
   *
   * <blockquote>
   * <pre>SELECT sum(a) OVER (w ROWS 3 PRECEDING)
   * FROM t
   * WINDOW w AS (PARTITION BY x, y ORDER BY z),
   *     w1 AS (w ROWS 5 PRECEDING UNBOUNDED FOLLOWING)</pre>
   * </blockquote>
   *
   * <p>declares windows w and w1, and uses a window in an OVER clause. It thus
   * contains 3 {@link SqlWindow} objects.
   */
  public class SqlWindow extends SqlCall {
    /**
     * The FOLLOWING operator used exclusively in a window specification.
     */
    public static final SqlPostfixOperator FOLLOWING_OPERATOR =
        new SqlPostfixOperator("FOLLOWING", SqlKind.FOLLOWING, 20,
            ReturnTypes.ARG0, null,
            null);
    /**
     * The PRECEDING operator used exclusively in a window specification.
     */
    public static final SqlPostfixOperator PRECEDING_OPERATOR =
        new SqlPostfixOperator("PRECEDING", SqlKind.PRECEDING, 20,
            ReturnTypes.ARG0, null,
            null);
  
    //~ Instance fields --------------------------------------------------------
  
    /** The name of the window being declared. */
    @Nullable SqlIdentifier declName;
  
    /** The name of the window being referenced, or null. */
    @Nullable SqlIdentifier refName;
  
    /** The list of partitioning columns. */
    SqlNodeList partitionList;
  
    /** The list of ordering columns. */
    SqlNodeList orderList;
  
    /** Whether it is a physical (rows) or logical (values) range. */
    SqlLiteral isRows;
  
    /** The lower bound of the window. */
    @Nullable SqlNode lowerBound;
  
    /** The upper bound of the window. */
    @Nullable SqlNode upperBound;
  
    /** Exclude rows from the frame.   */
    SqlLiteral exclude;
  
    /** Whether to allow partial results. It may be null.   */
    @Nullable SqlLiteral allowPartial;
  
    private @Nullable SqlCall windowCall = null;
  
    //~ Constructors -----------------------------------------------------------
  
    /**
     * Creates a window.
     */
    public SqlWindow(SqlParserPos pos, @Nullable SqlIdentifier declName,
        @Nullable SqlIdentifier refName, SqlNodeList partitionList, SqlNodeList orderList,
        SqlLiteral isRows, @Nullable SqlNode lowerBound, @Nullable SqlNode upperBound,
        @Nullable SqlLiteral allowPartial) {
      this(pos, declName, refName, partitionList, orderList,
          isRows, lowerBound, upperBound, allowPartial, createExcludeNoOthers(SqlParserPos.ZERO));
    }
  
    /**
     * Creates a window.
     */
    public SqlWindow(SqlParserPos pos, @Nullable SqlIdentifier declName,
        @Nullable SqlIdentifier refName, SqlNodeList partitionList, SqlNodeList orderList,
        SqlLiteral isRows, @Nullable SqlNode lowerBound, @Nullable SqlNode upperBound,
        @Nullable SqlLiteral allowPartial, SqlLiteral exclude) {
      super(pos);
      this.declName = declName;
      this.refName = refName;
      this.partitionList = requireNonNull(partitionList, "partitionList");
      this.orderList = requireNonNull(orderList, "orderList");
      this.isRows = isRows;
      this.lowerBound = lowerBound;
      this.upperBound = upperBound;
      this.allowPartial = allowPartial;
      this.exclude = exclude;
  
      assert exclude.symbolValue(Exclusion.class) == Exclusion.EXCLUDE_NO_OTHER
          || (lowerBound != null || upperBound != null);
      assert declName == null || declName.isSimple();
    }
  
    public static SqlWindow create(@Nullable SqlIdentifier declName, @Nullable SqlIdentifier refName,
        SqlNodeList partitionList, SqlNodeList orderList, SqlLiteral isRows,
        @Nullable SqlNode lowerBound, @Nullable SqlNode upperBound, @Nullable SqlLiteral allowPartial,
        SqlParserPos pos) {
      return create(declName, refName, partitionList, orderList, isRows, lowerBound, upperBound,
          allowPartial, createExcludeNoOthers(SqlParserPos.ZERO), pos);
    }
  
    public static SqlWindow create(@Nullable SqlIdentifier declName, @Nullable SqlIdentifier refName,
        SqlNodeList partitionList, SqlNodeList orderList, SqlLiteral isRows,
        @Nullable SqlNode lowerBound, @Nullable SqlNode upperBound, @Nullable SqlLiteral allowPartial,
        SqlLiteral exclude, SqlParserPos pos) {
      // If there's only one bound and it's 'FOLLOWING', make it the upper
      // bound.
      if (upperBound == null
          && lowerBound != null
          && lowerBound.getKind() == SqlKind.FOLLOWING) {
        upperBound = lowerBound;
        lowerBound = null;
      }
      return new SqlWindow(pos, declName, refName, partitionList, orderList,
          isRows, lowerBound, upperBound, allowPartial, exclude);
    }
  
    //~ Methods ----------------------------------------------------------------
  
    @Override public SqlOperator getOperator() {
      return SqlWindowOperator.INSTANCE;
    }
  
    @Override public SqlKind getKind() {
      return SqlKind.WINDOW;
    }
  
    @SuppressWarnings("nullness")
    @Override public List<SqlNode> getOperandList() {
      return ImmutableNullableList.of(declName, refName, partitionList, orderList,
          isRows, lowerBound, upperBound, allowPartial, exclude);
    }
  
    @SuppressWarnings("assignment.type.incompatible")
    @Override public void setOperand(int i, @Nullable SqlNode operand) {
      switch (i) {
      case 0:
        this.declName = (SqlIdentifier) operand;
        break;
      case 1:
        this.refName = (SqlIdentifier) operand;
        break;
      case 2:
        this.partitionList = (SqlNodeList) operand;
        break;
      case 3:
        this.orderList = (SqlNodeList) operand;
        break;
      case 4:
        this.isRows = (SqlLiteral) operand;
        break;
      case 5:
        this.lowerBound = operand;
        break;
      case 6:
        this.upperBound = operand;
        break;
      case 7:
        this.allowPartial = (SqlLiteral) operand;
        break;
      case 8:
        this.exclude = (SqlLiteral) operand;
        break;
      default:
        throw new AssertionError(i);
      }
    }
  
    @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
      if (null != declName) {
        declName.unparse(writer, 0, 0);
        writer.keyword("AS");
      }
  
      // Override, so we don't print extra parentheses.
      getOperator().unparse(writer, this, 0, 0);
    }
  
    public @Nullable SqlIdentifier getDeclName() {
      return declName;
    }
  
    public void setDeclName(SqlIdentifier declName) {
      assert declName.isSimple();
      this.declName = declName;
    }
  
    public @Nullable SqlNode getLowerBound() {
      return lowerBound;
    }
  
    public void setLowerBound(@Nullable SqlNode lowerBound) {
      this.lowerBound = lowerBound;
    }
  
    public @Nullable SqlNode getUpperBound() {
      return upperBound;
    }
  
    public void setUpperBound(@Nullable SqlNode upperBound) {
      this.upperBound = upperBound;
    }
  
    public SqlLiteral getExclude() {
      return exclude;
    }
  
    /**
     * Returns if the window is guaranteed to have rows.
     * This is useful to refine data type of window aggregates.
     * For instance sum(non-nullable) over (empty window) is NULL.
     *
     * @return true when the window is non-empty
     *
     * @see org.apache.calcite.rel.core.Window.Group#isAlwaysNonEmpty()
     * @see SqlOperatorBinding#hasEmptyGroup()
     * @see org.apache.calcite.sql.validate.SqlValidatorImpl#resolveWindow(SqlNode, SqlValidatorScope)
     */
    public boolean isAlwaysNonEmpty() {
      final RexWindowBound lower;
      final RexWindowBound upper;
      if (lowerBound == null) {
        if (upperBound == null) {
          lower = RexWindowBounds.UNBOUNDED_PRECEDING;
        } else {
          lower = RexWindowBounds.CURRENT_ROW;
        }
      } else if (lowerBound instanceof SqlLiteral) {
        lower = RexWindowBounds.create(lowerBound, null);
      } else {
        return false;
      }
      if (upperBound == null) {
        upper = RexWindowBounds.CURRENT_ROW;
      } else if (upperBound instanceof SqlLiteral) {
        upper = RexWindowBounds.create(upperBound, null);
      } else {
        return false;
      }
      return isAlwaysNonEmpty(lower, upper);
    }
  
    public static boolean isAlwaysNonEmpty(RexWindowBound lower,
        RexWindowBound upper) {
      final int lowerKey = lower.getOrderKey();
      final int upperKey = upper.getOrderKey();
      return lowerKey > -1 && lowerKey <= upperKey;
    }
  
    public void setRows(SqlLiteral isRows) {
      this.isRows = isRows;
    }
  
    @Pure
    public boolean isRows() {
      return isRows.booleanValue();
    }
  
    public SqlNodeList getOrderList() {
      return orderList;
    }
  
    public void setOrderList(SqlNodeList orderList) {
      this.orderList = orderList;
    }
  
    public SqlNodeList getPartitionList() {
      return partitionList;
    }
  
    public void setPartitionList(SqlNodeList partitionList) {
      this.partitionList = partitionList;
    }
  
    public @Nullable SqlIdentifier getRefName() {
      return refName;
    }
  
    public void setWindowCall(@Nullable SqlCall windowCall) {
      this.windowCall = windowCall;
      assert windowCall == null
          || windowCall.getOperator() instanceof SqlAggFunction;
    }
  
    public @Nullable SqlCall getWindowCall() {
      return windowCall;
    }
  
    // CHECKSTYLE: IGNORE 1
    /** @see Util#deprecated(Object, boolean) */
    static void checkSpecialLiterals(SqlWindow window, SqlValidator validator) {
      final SqlNode lowerBound = window.getLowerBound();
      final SqlNode upperBound = window.getUpperBound();
      Object lowerLitType = null;
      Object upperLitType = null;
      SqlOperator lowerOp = null;
      SqlOperator upperOp = null;
      if (null != lowerBound) {
        if (lowerBound.getKind() == SqlKind.LITERAL) {
          lowerLitType = ((SqlLiteral) lowerBound).getValue();
          if (Bound.UNBOUNDED_FOLLOWING == lowerLitType) {
            throw validator.newValidationError(lowerBound,
                RESOURCE.badLowerBoundary());
          }
        } else if (lowerBound instanceof SqlCall) {
          lowerOp = ((SqlCall) lowerBound).getOperator();
        }
      }
      if (null != upperBound) {
        if (upperBound.getKind() == SqlKind.LITERAL) {
          upperLitType = ((SqlLiteral) upperBound).getValue();
          if (Bound.UNBOUNDED_PRECEDING == upperLitType) {
            throw validator.newValidationError(upperBound,
                RESOURCE.badUpperBoundary());
          }
        } else if (upperBound instanceof SqlCall) {
          upperOp = ((SqlCall) upperBound).getOperator();
        }
      }
  
      if (Bound.CURRENT_ROW == lowerLitType) {
        if (null != upperOp) {
          if (upperOp == PRECEDING_OPERATOR) {
            throw validator.newValidationError(castNonNull(upperBound),
                RESOURCE.currentRowPrecedingError());
          }
        }
      } else if (null != lowerOp) {
        if (lowerOp == FOLLOWING_OPERATOR) {
          if (null != upperOp) {
            if (upperOp == PRECEDING_OPERATOR) {
              throw validator.newValidationError(castNonNull(upperBound),
                  RESOURCE.followingBeforePrecedingError());
            }
          } else if (null != upperLitType) {
            if (Bound.CURRENT_ROW == upperLitType) {
              throw validator.newValidationError(castNonNull(upperBound),
                  RESOURCE.currentRowFollowingError());
            }
          }
        }
      }
    }
  
    public static SqlLiteral createExcludeNoOthers(SqlParserPos pos) {
      return Exclusion.EXCLUDE_NO_OTHER.symbol(pos);
    }
  
    public static SqlLiteral createExcludeCurrentRow(SqlParserPos pos) {
      return Exclusion.EXCLUDE_CURRENT_ROW.symbol(pos);
    }
  
    public static SqlLiteral createExcludeTies(SqlParserPos pos) {
      return Exclusion.EXCLUDE_TIES.symbol(pos);
    }
  
    public static SqlLiteral createExcludeGroup(SqlParserPos pos) {
      return Exclusion.EXCLUDE_GROUP.symbol(pos);
    }
  
    public static boolean isExcludeNoOthers(SqlLiteral node) {
      return node.symbolValue(Exclusion.class) == Exclusion.EXCLUDE_NO_OTHER;
    }
  
    public static boolean isExcludeCurrentRow(SqlLiteral node) {
      return node.symbolValue(Exclusion.class) == Exclusion.EXCLUDE_CURRENT_ROW;
    }
  
    public static boolean isExcludeGroup(SqlLiteral node) {
      return node.symbolValue(Exclusion.class) == Exclusion.EXCLUDE_GROUP;
    }
  
    public static boolean isExcludeTies(SqlLiteral node) {
      return node.symbolValue(Exclusion.class) == Exclusion.EXCLUDE_TIES;
    }
  
    public static SqlNode createCurrentRow(SqlParserPos pos) {
      return Bound.CURRENT_ROW.symbol(pos);
    }
  
    public static SqlNode createUnboundedFollowing(SqlParserPos pos) {
      return Bound.UNBOUNDED_FOLLOWING.symbol(pos);
    }
  
    public static SqlNode createUnboundedPreceding(SqlParserPos pos) {
      return Bound.UNBOUNDED_PRECEDING.symbol(pos);
    }
  
    public static SqlNode createFollowing(SqlNode e, SqlParserPos pos) {
      return FOLLOWING_OPERATOR.createCall(pos, e);
    }
  
    public static SqlNode createPreceding(SqlNode e, SqlParserPos pos) {
      return PRECEDING_OPERATOR.createCall(pos, e);
    }
  
    public static SqlNode createBound(SqlLiteral range) {
      return range;
    }
  
    /**
     * Returns whether an expression represents the "CURRENT ROW" bound.
     */
    public static boolean isCurrentRow(SqlNode node) {
      return (node instanceof SqlLiteral)
          && ((SqlLiteral) node).symbolValue(Bound.class) == Bound.CURRENT_ROW;
    }
  
    /**
     * Returns whether an expression represents the "UNBOUNDED PRECEDING" bound.
     */
    public static boolean isUnboundedPreceding(SqlNode node) {
      return (node instanceof SqlLiteral)
          && ((SqlLiteral) node).symbolValue(Bound.class) == Bound.UNBOUNDED_PRECEDING;
    }
  
    /**
     * Returns whether an expression represents the "UNBOUNDED FOLLOWING" bound.
     */
    public static boolean isUnboundedFollowing(SqlNode node) {
      return (node instanceof SqlLiteral)
          && ((SqlLiteral) node).symbolValue(Bound.class) == Bound.UNBOUNDED_FOLLOWING;
    }
  
    /**
     * Creates a new window by combining this one with another.
     *
     * <p>For example,
     *
     * <blockquote><pre>WINDOW (w PARTITION BY x ORDER BY y)
     *   overlay
     *   WINDOW w AS (PARTITION BY z)</pre></blockquote>
     *
     * <p>yields
     *
     * <blockquote><pre>WINDOW (PARTITION BY z ORDER BY y)</pre></blockquote>
     *
     * <p>Does not alter this or the other window.
     *
     * @return A new window
     */
    public SqlWindow overlay(SqlWindow that, SqlValidator validator) {
      // check 7.11 rule 10c
      final SqlNodeList partitions = getPartitionList();
      if (!partitions.isEmpty()) {
        throw validator.newValidationError(partitions.get(0),
            RESOURCE.partitionNotAllowed());
      }
  
      // 7.11 rule 10d
      final SqlNodeList baseOrder = getOrderList();
      final SqlNodeList refOrder = that.getOrderList();
      if (!baseOrder.isEmpty() && !refOrder.isEmpty()) {
        throw validator.newValidationError(baseOrder.get(0),
            RESOURCE.orderByOverlap());
      }
  
      // 711 rule 10e
      final SqlNode lowerBound = that.getLowerBound();
      final SqlNode upperBound = that.getUpperBound();
      final SqlLiteral exclude = that.getExclude();
      if ((null != lowerBound) || (null != upperBound)
          || exclude.symbolValue(Exclusion.class) != Exclusion.EXCLUDE_NO_OTHER) {
        throw validator.newValidationError(that.isRows,
            RESOURCE.refWindowWithFrame());
      }
  
      SqlIdentifier declNameNew = declName;
      SqlIdentifier refNameNew;
      SqlNodeList partitionListNew = partitionList;
      SqlNodeList orderListNew = orderList;
      SqlLiteral isRowsNew = isRows;
      SqlNode lowerBoundNew = lowerBound;
      SqlNode upperBoundNew = upperBound;
      SqlLiteral allowPartialNew = allowPartial;
  
      // Clear the reference window, because the reference is now resolved.
      // The overlaying window may have its own reference, of course.
      refNameNew = null;
  
      // Overlay other parameters.
      if (setOperand(partitionListNew, that.partitionList, validator)) {
        partitionListNew = that.partitionList;
      }
      if (setOperand(orderListNew, that.orderList, validator)) {
        orderListNew = that.orderList;
      }
      if (setOperand(lowerBoundNew, that.lowerBound, validator)) {
        lowerBoundNew = that.lowerBound;
      }
      if (setOperand(upperBoundNew, that.upperBound, validator)) {
        upperBoundNew = that.upperBound;
      }
      return new SqlWindow(
          SqlParserPos.ZERO,
          declNameNew,
          refNameNew,
          partitionListNew,
          orderListNew,
          isRowsNew,
          lowerBoundNew,
          upperBoundNew,
          allowPartialNew,
          exclude);
    }
  
    private static boolean setOperand(@Nullable SqlNode clonedOperand, @Nullable SqlNode thatOperand,
        SqlValidator validator) {
      if ((thatOperand != null) && !SqlNodeList.isEmptyList(thatOperand)) {
        if ((clonedOperand == null)
            || SqlNodeList.isEmptyList(clonedOperand)) {
          return true;
        } else {
          throw validator.newValidationError(clonedOperand,
              RESOURCE.cannotOverrideWindowAttribute());
        }
      }
      return false;
    }
  
    /**
     * Overridden method to specifically check only the right subtree of a window
     * definition.
     *
     * @param node The SqlWindow to compare to "this" window
     * @param litmus What to do if an error is detected (nodes are not equal)
     *
     * @return boolean true if all nodes in the subtree are equal
     */
    @Override public boolean equalsDeep(@Nullable SqlNode node, Litmus litmus) {
      // This is the difference over super.equalsDeep.  It skips
      // operands[0] the declared name fo this window.  We only want
      // to check the window components.
      return node == this
          || node instanceof SqlWindow
          && SqlNode.equalDeep(
              Util.skip(getOperandList()),
              Util.skip(((SqlWindow) node).getOperandList()), litmus);
    }
  
    /**
     * Returns whether partial windows are allowed. If false, a partial window
     * (for example, a window of size 1 hour which has only 45 minutes of data
     * in it) will appear to windowed aggregate functions to be empty.
     */
    @EnsuresNonNullIf(expression = "allowPartial", result = false)
    public boolean isAllowPartial() {
      // Default (and standard behavior) is to allow partial windows.
      return allowPartial == null
          || allowPartial.booleanValue();
    }
  
    @Override public void validate(SqlValidator validator,
        SqlValidatorScope scope) {
      SqlValidatorScope operandScope = scope; // REVIEW
  
      @SuppressWarnings("unused")
      SqlIdentifier declName = this.declName;
      SqlIdentifier refName = this.refName;
      SqlNodeList partitionList = this.partitionList;
      SqlNodeList orderList = this.orderList;
      SqlLiteral isRows = this.isRows;
      SqlNode lowerBound = this.lowerBound;
      SqlNode upperBound = this.upperBound;
      SqlLiteral allowPartial = this.allowPartial;
  
      if (refName != null) {
        SqlWindow win = validator.resolveWindow(this, operandScope);
        partitionList = win.partitionList;
        orderList = win.orderList;
        isRows = win.isRows;
        lowerBound = win.lowerBound;
        upperBound = win.upperBound;
        allowPartial = win.allowPartial;
      }
  
      for (SqlNode partitionItem : partitionList) {
        try {
          partitionItem.accept(Util.OverFinder.INSTANCE);
        } catch (ControlFlowException e) {
          throw validator.newValidationError(this,
              RESOURCE.partitionbyShouldNotContainOver());
        }
  
        partitionItem.validateExpr(validator, operandScope);
      }
  
      for (SqlNode orderItem : orderList) {
        boolean savedColumnReferenceExpansion =
            validator.config().columnReferenceExpansion();
        validator.transform(config -> config.withColumnReferenceExpansion(false));
        try {
          orderItem.accept(Util.OverFinder.INSTANCE);
        } catch (ControlFlowException e) {
          throw validator.newValidationError(this,
              RESOURCE.orderbyShouldNotContainOver());
        }
  
        try {
          orderItem.validateExpr(validator, scope);
        } finally {
          validator.transform(config ->
              config.withColumnReferenceExpansion(savedColumnReferenceExpansion));
        }
      }
  
      // 6.10 rule 6a Function RANK & DENSE_RANK require ORDER BY clause
      if (orderList.isEmpty()
          && !SqlValidatorUtil.containsMonotonic(scope)
          && windowCall != null
          && windowCall.getOperator().requiresOrder()) {
        throw validator.newValidationError(this, RESOURCE.funcNeedsOrderBy());
      }
  
      // Run framing checks if there are any
      if (upperBound != null || lowerBound != null) {
        // 6.10 Rule 6a RANK & DENSE_RANK do not allow ROWS or RANGE
        if (windowCall != null && !windowCall.getOperator().allowsFraming()) {
          throw validator.newValidationError(isRows, RESOURCE.rankWithFrame());
        }
        SqlTypeFamily orderTypeFam = null;
  
        // SQL03 7.10 Rule 11a
        if (!orderList.isEmpty()) {
          // if order by is a compound list then range not allowed
          if (orderList.size() > 1
              && !isRows()
              && !onlySymbolBounds(lowerBound, upperBound)) {
            throw validator.newValidationError(isRows,
                RESOURCE.compoundOrderByProhibitsRange());
          }
  
          // get the type family for the sort key for Frame Boundary Val.
          RelDataType orderType =
              validator.deriveType(
                  operandScope,
                  orderList.get(0));
          orderTypeFam = orderType.getSqlTypeName().getFamily();
        } else {
          // requires an ORDER BY clause if frame is logical(RANGE)
          // We relax this requirement if the table appears to be
          // sorted already
          if (!onlySymbolBounds(lowerBound, upperBound)
              && !isRows()
              && !SqlValidatorUtil.containsMonotonic(scope)) {
            throw validator.newValidationError(this,
                RESOURCE.overMissingOrderBy());
          }
        }
  
        // Let the bounds validate themselves
        validateFrameBoundary(
            lowerBound,
            isRows(),
            orderTypeFam,
            validator,
            operandScope);
        validateFrameBoundary(
            upperBound,
            isRows(),
            orderTypeFam,
            validator,
            operandScope);
  
        // Validate across boundaries. 7.10 Rule 8 a-d
        checkSpecialLiterals(this, validator);
      } else if (orderList.isEmpty()
          && !SqlValidatorUtil.containsMonotonic(scope)
          && windowCall != null
          && windowCall.getOperator().requiresOrder()) {
        throw validator.newValidationError(this, RESOURCE.overMissingOrderBy());
      }
  
      if (!isRows() && !isAllowPartial()) {
        throw validator.newValidationError(castNonNull(allowPartial),
            RESOURCE.cannotUseDisallowPartialWithRange());
      }
    }
  
    private static boolean onlySymbolBounds(@Nullable SqlNode lowerBound,
        @Nullable SqlNode upperBound) {
      return lowerBound != null && upperBound != null
          && (isCurrentRow(lowerBound) || isUnboundedPreceding(lowerBound))
          && (isCurrentRow(upperBound) || isUnboundedFollowing(upperBound));
    }
  
    private static void validateFrameBoundary(
        @Nullable SqlNode bound,
        boolean isRows,
        @Nullable SqlTypeFamily orderTypeFam,
        SqlValidator validator,
        SqlValidatorScope scope) {
      if (null == bound) {
        return;
      }
      bound.validate(validator, scope);
      switch (bound.getKind()) {
      case LITERAL:
        // is there really anything to validate here? this covers
        // "CURRENT_ROW","unbounded preceding" & "unbounded following"
        break;
  
      case OTHER:
      case FOLLOWING:
      case PRECEDING:
        assert bound instanceof SqlCall;
        final SqlNode boundVal = ((SqlCall) bound).operand(0);
  
        // SQL03 7.10 rule 11b Physical ROWS must be a numeric constant. JR:
        // actually it's SQL03 7.11 rule 11b "exact numeric with scale 0"
        // means not only numeric constant but exact numeric integral
        // constant. We also interpret the spec. to not allow negative
        // values, but allow zero.
        if (isRows) {
          if (boundVal instanceof SqlNumericLiteral) {
            final SqlNumericLiteral boundLiteral =
                (SqlNumericLiteral) boundVal;
            if (!boundLiteral.isExact()
                || (boundLiteral.getScale() != null
                  && boundLiteral.getValueAs(BigDecimal.class).stripTrailingZeros().scale() > 0)
                || (0 > boundLiteral.longValue(true))) {
              // true == throw if not exact (we just tested that - right?)
              throw validator.newValidationError(boundVal,
                  RESOURCE.rowMustBeNonNegativeIntegral());
            }
          } else {
            // Allow expressions in ROWS clause
          }
        }
  
        // if this is a range spec check and make sure the boundary type
        // and order by type are compatible
        if (orderTypeFam != null && !isRows) {
          final RelDataType boundType = validator.deriveType(scope, boundVal);
          final SqlTypeFamily boundTypeFamily =
              boundType.getSqlTypeName().getFamily();
          final List<SqlTypeFamily> allowableBoundTypeFamilies =
              orderTypeFam.allowableDifferenceTypes();
          if (allowableBoundTypeFamilies.isEmpty()) {
            throw validator.newValidationError(boundVal,
                RESOURCE.orderByDataTypeProhibitsRange());
          }
          if (!allowableBoundTypeFamilies.contains(boundTypeFamily)) {
            throw validator.newValidationError(boundVal,
                RESOURCE.orderByRangeMismatch());
          }
        }
        break;
      default:
        throw new AssertionError("Unexpected node type");
      }
    }
  
    /**
     * Creates a window <code>(RANGE <i>columnName</i> CURRENT ROW)</code>.
     *
     * @param columnName Order column
     */
    public SqlWindow createCurrentRowWindow(final String columnName) {
      return SqlWindow.create(
          null,
          null,
          new SqlNodeList(SqlParserPos.ZERO),
          new SqlNodeList(
              ImmutableList.of(
                  new SqlIdentifier(columnName, SqlParserPos.ZERO)),
              SqlParserPos.ZERO),
          SqlLiteral.createBoolean(true, SqlParserPos.ZERO),
          SqlWindow.createCurrentRow(SqlParserPos.ZERO),
          SqlWindow.createCurrentRow(SqlParserPos.ZERO),
          SqlLiteral.createBoolean(true, SqlParserPos.ZERO),
          SqlParserPos.ZERO);
    }
  
    /**
     * Creates a window <code>(RANGE <i>columnName</i> UNBOUNDED
     * PRECEDING)</code>.
     *
     * @param columnName Order column
     */
    public SqlWindow createUnboundedPrecedingWindow(final String columnName) {
      return SqlWindow.create(
          null,
          null,
          new SqlNodeList(SqlParserPos.ZERO),
          new SqlNodeList(
              ImmutableList.of(
                  new SqlIdentifier(columnName, SqlParserPos.ZERO)),
              SqlParserPos.ZERO),
          SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
          SqlWindow.createUnboundedPreceding(SqlParserPos.ZERO),
          SqlWindow.createCurrentRow(SqlParserPos.ZERO),
          SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
          SqlParserPos.ZERO);
    }
  
    @Deprecated // to be removed before 2.0
    public void populateBounds() {
      if (lowerBound == null && upperBound == null) {
        setLowerBound(SqlWindow.createUnboundedPreceding(pos));
      }
      if (lowerBound == null) {
        setLowerBound(SqlWindow.createCurrentRow(pos));
      }
      if (upperBound == null) {
        setUpperBound(SqlWindow.createCurrentRow(pos));
      }
    }
  
    /**
     * An enumeration of types of exclusion rows in a window: <code>EXCLUDE NO OTHERS</code>,
     * <code>EXCLUDE CURRENT ROW</code>, <code>EXCLUDE TIES</code> and <code>EXCLUDE GROUP</code>.
     */
    enum Exclusion implements Symbolizable {
      EXCLUDE_NO_OTHER("EXCLUDE NO OTHER"),
      EXCLUDE_CURRENT_ROW("EXCLUDE CURRENT ROW"),
      EXCLUDE_TIES("EXCLUDE TIES"),
      EXCLUDE_GROUP("EXCLUDE GROUP");
  
      private final String sql;
  
      Exclusion(String sql) {
        this.sql = sql;
      }
  
      @Override public String toString() {
        return sql;
      }
  
    }
  
    /**
     * An enumeration of types of bounds in a window: <code>CURRENT ROW</code>,
     * <code>UNBOUNDED PRECEDING</code>, and <code>UNBOUNDED FOLLOWING</code>.
     */
    enum Bound implements Symbolizable {
      CURRENT_ROW("CURRENT ROW"),
      UNBOUNDED_PRECEDING("UNBOUNDED PRECEDING"),
      UNBOUNDED_FOLLOWING("UNBOUNDED FOLLOWING");
  
      private final String sql;
  
      Bound(String sql) {
        this.sql = sql;
      }
  
      @Override public String toString() {
        return sql;
      }
    }
  
    /** An operator describing a window specification. */
    private static class SqlWindowOperator extends SqlOperator {
      private static final SqlWindowOperator INSTANCE = new SqlWindowOperator();
  
      private SqlWindowOperator() {
        super("WINDOW", SqlKind.WINDOW, 2, true, null, null, null);
      }
  
      @Override public SqlSyntax getSyntax() {
        return SqlSyntax.SPECIAL;
      }
  
      @SuppressWarnings("argument.type.incompatible")
      @Override public SqlCall createCall(
          @Nullable SqlLiteral functionQualifier,
          SqlParserPos pos,
          @Nullable SqlNode... operands) {
        assert functionQualifier == null;
        assert operands.length == 9;
        return create(
            (SqlIdentifier) operands[0],
            (SqlIdentifier) operands[1],
            (SqlNodeList) requireNonNull(operands[2]),
            (SqlNodeList) requireNonNull(operands[3]),
            (SqlLiteral) requireNonNull(operands[4]),
            operands[5],
            operands[6],
            (SqlLiteral) operands[7],
            (SqlLiteral) requireNonNull(operands[8]),
            pos);
      }
  
      @Override public <R> void acceptCall(
          SqlVisitor<R> visitor,
          SqlCall call,
          boolean onlyExpressions,
          SqlBasicVisitor.ArgHandler<R> argHandler) {
        if (onlyExpressions) {
          for (Ord<SqlNode> operand : Ord.zip(call.getOperandList())) {
            // if the second param is an Identifier then it's supposed to
            // be a name from a window clause and isn't part of the
            // group by check
            if (operand.e == null) {
              continue;
            }
            if (operand.i == 1 && operand.e instanceof SqlIdentifier) {
              // skip refName
              continue;
            }
            argHandler.visitChild(visitor, call, operand.i, operand.e);
          }
        } else {
          super.acceptCall(visitor, call, onlyExpressions, argHandler);
        }
      }
  
      @Override public void unparse(
          SqlWriter writer,
          SqlCall call,
          int leftPrec,
          int rightPrec) {
        final SqlWindow window = (SqlWindow) call;
        final SqlWriter.Frame frame =
            writer.startList(SqlWriter.FrameTypeEnum.WINDOW, "(", ")");
        if (window.refName != null) {
          window.refName.unparse(writer, 0, 0);
        }
        if (!window.partitionList.isEmpty()) {
          writer.sep("PARTITION BY");
          final SqlWriter.Frame partitionFrame = writer.startList("", "");
          window.partitionList.unparse(writer, 0, 0);
          writer.endList(partitionFrame);
        }
        if (!window.orderList.isEmpty()) {
          writer.sep("ORDER BY");
          final SqlWriter.Frame orderFrame = writer.startList("", "");
          window.orderList.unparse(writer, 0, 0);
          writer.endList(orderFrame);
        }
        SqlNode lowerBound = window.lowerBound;
        SqlNode upperBound = window.upperBound;
        SqlLiteral exclude = window.exclude;
        if (lowerBound == null) {
          // No ROWS or RANGE clause
        } else if (upperBound == null) {
          if (window.isRows()) {
            writer.sep("ROWS");
          } else {
            writer.sep("RANGE");
          }
          lowerBound.unparse(writer, 0, 0);
          if (!isExcludeNoOthers(exclude)) {
            exclude.unparse(writer, 0, 0);
          }
        } else {
          if (window.isRows()) {
            writer.sep("ROWS BETWEEN");
          } else {
            writer.sep("RANGE BETWEEN");
          }
          lowerBound.unparse(writer, 0, 0);
          writer.keyword("AND");
          upperBound.unparse(writer, 0, 0);
          if (!isExcludeNoOthers(exclude)) {
            exclude.unparse(writer, 0, 0);
          }
        }
  
        // ALLOW PARTIAL/DISALLOW PARTIAL
        if (window.allowPartial == null) {
          // do nothing
        } else if (window.isAllowPartial()) {
          // We could output "ALLOW PARTIAL", but this syntax is
          // non-standard. Omitting the clause has the same effect.
        } else {
          writer.keyword("DISALLOW PARTIAL");
        }
  
        writer.endList(frame);
      }
    }
  }
  