throw newValidationError()

in core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java [7325:7629]


              throw newValidationError(
                  literal, RESOURCE.orderByOrdinalOutOfRange());
            }

            // SQL ordinals are 1-based, but Sort's are 0-based
            int ordinal = intValue - 1;
            return nthSelectItem(ordinal, literal.getParserPosition());
          }
          break;
        default:
          break;
        }
      }

      return super.visit(literal);
    }

    /**
     * Returns the <code>ordinal</code>th item in the select list.
     */
    private SqlNode nthSelectItem(int ordinal, final SqlParserPos pos) {
      // TODO: Don't expand the list every time. Maybe keep an expanded
      // version of each expression -- select lists and identifiers -- in
      // the validator.

      SqlNodeList expandedSelectList =
          expandStar(
              SqlNonNullableAccessors.getSelectList(select),
              select,
              false);
      SqlNode expr = expandedSelectList.get(ordinal);
      expr = stripAs(expr);
      if (expr instanceof SqlIdentifier) {
        expr = getScope().fullyQualify((SqlIdentifier) expr).identifier;
      }

      // Create a copy of the expression with the position of the order
      // item.
      return expr.clone(pos);
    }

    @Override public SqlNode visit(SqlIdentifier id) {
      // Aliases, e.g. 'select a as x, b from t order by x'.
      if (id.isSimple()
          && config.conformance().isSortByAlias()) {
        String alias = id.getSimple();
        final SqlValidatorNamespace selectNs = getNamespaceOrThrow(select);
        final RelDataType rowType =
            selectNs.getRowTypeSansSystemColumns();
        final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
        RelDataTypeField field = nameMatcher.field(rowType, alias);
        if (field != null) {
          return nthSelectItem(
              field.getIndex(),
              id.getParserPosition());
        }
      }

      // No match. Return identifier unchanged.
      return getScope().fullyQualify(id).identifier;
    }

    @Override protected @Nullable SqlNode visitScoped(SqlCall call) {
      // Don't attempt to expand sub-queries. We haven't implemented
      // these yet.
      if (call instanceof SqlSelect) {
        return call;
      }
      return super.visitScoped(call);
    }
  }

  /**
   * Converts an expression into canonical form by fully-qualifying any
   * identifiers. For common columns in USING, it will be converted to
   * COALESCE(A.col, B.col) AS col.  When using a conformance that
   * allows isSelectAlias, it expands identifiers that refer to
   * local select expressions into the expressions themselves.
   */
  static class SelectExpander extends Expander {
    final SqlSelect select;
    // Maps simple identifiers to their expansions.
    final Map<String, SqlNode> expansions;
    // List of identifiers that are currently being looked-up.  Used to detect
    // circular dependencies when SqlConformance#isSelectAlias is ANY.
    final Set<String> lookingUp;
    @Nullable SqlNode root = null;

    private SelectExpander(SqlValidatorImpl validator, SelectScope scope,
        SqlSelect select, Map<String, SqlNode> expansions, Set<String> lookingUp) {
      super(validator, scope);
      this.select = select;
      this.expansions = expansions;
      this.lookingUp = lookingUp;
    }

    /**
     * Creates an expander for the items in a SELECT list.
     *
     * @param validator  Validator to use.
     * @param scope      Scope to lookup identifiers in.
     * @param select     Select that is being expanded.
     * @param expansions List of expansions computed for identifiers that are
     *                   defined in the current select list and also used in the select list.
     *                   Only used if {@link SqlConformance#isSelectAlias()} requires it.
     */
    private SelectExpander(SqlValidatorImpl validator, SelectScope scope,
        SqlSelect select, Map<String, SqlNode> expansions) {
      this(validator, scope, select, expansions, new HashSet<>());
    }

    private SelectExpander(SelectExpander expander) {
      this(expander.validator, (SelectScope) expander.getScope(),
          expander.select, expander.expansions, expander.lookingUp);
    }

    @Override public SqlNode go(SqlNode root) {
      this.root = root;
      return super.go(root);
    }

    @Override public @Nullable SqlNode visit(SqlIdentifier id) {
      final SqlNode node =
          expandCommonColumn(select, id, (SelectScope) getScope(), validator);
      if (node != id) {
        return node;
      } else {
        try {
          return super.visit(id);
        } catch (Exception ex) {
          if (!(ex instanceof CalciteContextException)) {
            throw ex;
          }
          String message = ex.getMessage();
          if (message != null && !message.contains("not found")) {
            throw ex;
          }
          // This point is reached only if the name lookup failed using standard rules
          SqlConformance.SelectAliasLookup selectAlias = validator.getConformance().isSelectAlias();
          if (selectAlias == SqlConformance.SelectAliasLookup.UNSUPPORTED || !id.isSimple()) {
            throw ex;
          }
          return expandAliases(id, (CalciteContextException) ex);
          // end of catch block
        }
      }
    }

    // Handle "SELECT expr as X, X+1 as Y"
    // where X is not defined previously.
    // Try to look up the item in the select list itself.
    private SqlNode expandAliases(SqlIdentifier id, CalciteContextException ex) {
      String name = id.getSimple();
      if (lookingUp.contains(name)) {
        final String dependentColumns = lookingUp.stream()
            .map(s -> "'" + s + "'")
            .collect(joining(", "));
        throw validator.newValidationError(id,
            RESOURCE.columnIsCyclic(name, dependentColumns));
      }
      SqlConformance.SelectAliasLookup selectAlias = validator.getConformance().isSelectAlias();
      // Check whether we already have an expansion for this identifier
      @Nullable SqlNode expr = null;
      if (expansions.containsKey(name)) {
        expr = expansions.get(name);
      } else {
        final SqlNameMatcher nameMatcher =
            validator.catalogReader.nameMatcher();
        int matches = 0;
        for (SqlNode s : select.getSelectList()) {
          if (s == this.root && selectAlias == SqlConformance.SelectAliasLookup.LEFT_TO_RIGHT) {
            // Stop lookup at the current item
            break;
          }
          final String alias = SqlValidatorUtil.alias(s);
          if (alias != null && nameMatcher.matches(alias, name)) {
            expr = s;
            matches++;
          }
        }
        if (matches == 0) {
          // Throw the original exception
          throw ex;
        } else if (matches > 1) {
          // More than one column has this alias.
          throw validator.newValidationError(id,
              RESOURCE.columnAmbiguous(name));
        }
        expr = stripAs(requireNonNull(expr, "expr"));
      }
      // Recursively expand the result
      lookingUp.add(name);
      SelectExpander expander = new SelectExpander(this);
      final SqlNode newExpr = expander.go(expr);
      lookingUp.remove(name);
      if (expr != newExpr) {
        validator.setOriginal(newExpr, expr);
      }
      expr = newExpr;
      if (expr instanceof SqlIdentifier) {
        expr = getScope().fullyQualify((SqlIdentifier) expr).identifier;
      }
      if (!expansions.containsKey(name)) {
        expansions.put(name, expr);
        validator.setOriginal(expr, id);
      }
      return expr;
    }
  }

  /**
   * Shuttle which walks over an expression in the GROUP BY/HAVING clause, replacing
   * usages of aliases or ordinals with the underlying expression.
   */
  static class ExtendedExpander extends Expander {
    final SqlSelect select;
    final SqlNode root;
    final Clause clause;
    // Retain only expandable aliases or ordinals to prevent their expansion in a SQL call expr.
    final Set<SqlNode> aliasOrdinalExpandSet = Sets.newIdentityHashSet();

    ExtendedExpander(SqlValidatorImpl validator, SqlValidatorScope scope,
        SqlSelect select, SqlNode root, Clause clause) {
      super(validator, scope);
      this.select = select;
      this.root = root;
      this.clause = clause;
      if (clause == Clause.GROUP_BY) {
        addExpandableExpressions();
      }
    }

    @Override public @Nullable SqlNode visit(SqlIdentifier id) {
      if (!id.isSimple()) {
        return super.visit(id);
      }

      final SelectScope selectScope = validator.getRawSelectScopeNonNull(select);
      final boolean replaceAliases = clause.shouldReplaceAliases(validator.config);
      if (!replaceAliases || (clause == Clause.GROUP_BY && !aliasOrdinalExpandSet.contains(id))) {
        SqlNode node = expandCommonColumn(select, id, selectScope, validator);
        if (node != id) {
          return node;
        }
        return super.visit(id);
      }

      String name = id.getSimple();
      SqlNode expr = null;
      final SqlNameMatcher nameMatcher =
          validator.catalogReader.nameMatcher();
      int n = 0;
      for (SqlNode s : SqlNonNullableAccessors.getSelectList(select)) {
        final @Nullable String alias = SqlValidatorUtil.alias(s);
        if (alias != null && nameMatcher.matches(alias, name)) {
          expr = s;
          n++;
        }
      }

      if (n == 0) {
        return super.visit(id);
      } else if (n > 1) {
        // More than one column has this alias.
        throw validator.newValidationError(id,
            RESOURCE.columnAmbiguous(name));
      }
      Iterable<SqlCall> allAggList = validator.aggFinder.findAll(ImmutableList.of(root));
      for (SqlCall agg : allAggList) {
        if (clause == Clause.HAVING && containsIdentifier(agg, id)) {
          return super.visit(id);
        }
      }

      // expr cannot be null; in that case n = 0 would have returned
      requireNonNull(expr, "expr");
      if (validator.getConformance().isSelectAlias()
              != SqlConformance.SelectAliasLookup.UNSUPPORTED) {
        Map<String, SqlNode> expansions = new HashMap<>();
        final Expander expander = new SelectExpander(validator, selectScope, select, expansions);
        expr = expander.go(expr);
      }
      expr = stripAs(expr);
      if (expr instanceof SqlIdentifier) {
        SqlIdentifier sid = (SqlIdentifier) expr;
        final SqlIdentifier fqId = getScope().fullyQualify(sid).identifier;
        expr = expandDynamicStar(sid, fqId);
      }

      return expr;
    }

    @Override public @Nullable SqlNode visit(SqlLiteral literal) {
      if (clause != Clause.GROUP_BY
          || !validator.config().conformance().isGroupByOrdinal()) {
        return super.visit(literal);
      }
      boolean isOrdinalLiteral = aliasOrdinalExpandSet.contains(literal);
      if (isOrdinalLiteral) {
        switch (literal.getTypeName()) {
        case DECIMAL:
        case DOUBLE:
          final int intValue = literal.intValue(false);
          if (intValue >= 0) {
            if (intValue < 1 || intValue > SqlNonNullableAccessors.getSelectList(select).size()) {