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

com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorImpl Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * 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 com.hazelcast.com.liance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.com.hazelcast.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 com.hazelcast.org.apache.calcite.sql.validate;

import com.hazelcast.org.apache.calcite.config.NullCollation;
import com.hazelcast.org.apache.calcite.linq4j.Ord;
import com.hazelcast.org.apache.calcite.linq4j.function.Function2;
import com.hazelcast.org.apache.calcite.linq4j.function.Functions;
import com.hazelcast.org.apache.calcite.plan.RelOptTable;
import com.hazelcast.org.apache.calcite.plan.RelOptUtil;
import com.hazelcast.org.apache.calcite.prepare.Prepare;
import com.hazelcast.org.apache.calcite.rel.type.DynamicRecordType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeFactory;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeField;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeSystem;
import com.hazelcast.org.apache.calcite.rel.type.RelRecordType;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.rex.RexPatternFieldRef;
import com.hazelcast.org.apache.calcite.rex.RexVisitor;
import com.hazelcast.org.apache.calcite.runtime.CalciteContextException;
import com.hazelcast.org.apache.calcite.runtime.CalciteException;
import com.hazelcast.org.apache.calcite.runtime.Feature;
import com.hazelcast.org.apache.calcite.runtime.Resources;
import com.hazelcast.org.apache.calcite.schema.ColumnStrategy;
import com.hazelcast.org.apache.calcite.schema.Table;
import com.hazelcast.org.apache.calcite.schema.impl.ModifiableViewTable;
import com.hazelcast.org.apache.calcite.sql.JoinConditionType;
import com.hazelcast.org.apache.calcite.sql.JoinType;
import com.hazelcast.org.apache.calcite.sql.SqlAccessEnum;
import com.hazelcast.org.apache.calcite.sql.SqlAccessType;
import com.hazelcast.org.apache.calcite.sql.SqlAggFunction;
import com.hazelcast.org.apache.calcite.sql.SqlBasicCall;
import com.hazelcast.org.apache.calcite.sql.SqlCall;
import com.hazelcast.org.apache.calcite.sql.SqlCallBinding;
import com.hazelcast.org.apache.calcite.sql.SqlDataTypeSpec;
import com.hazelcast.org.apache.calcite.sql.SqlDelete;
import com.hazelcast.org.apache.calcite.sql.SqlDynamicParam;
import com.hazelcast.org.apache.calcite.sql.SqlExplain;
import com.hazelcast.org.apache.calcite.sql.SqlFunction;
import com.hazelcast.org.apache.calcite.sql.SqlFunctionCategory;
import com.hazelcast.org.apache.calcite.sql.SqlIdentifier;
import com.hazelcast.org.apache.calcite.sql.SqlInsert;
import com.hazelcast.org.apache.calcite.sql.SqlIntervalLiteral;
import com.hazelcast.org.apache.calcite.sql.SqlIntervalQualifier;
import com.hazelcast.org.apache.calcite.sql.SqlJoin;
import com.hazelcast.org.apache.calcite.sql.SqlKind;
import com.hazelcast.org.apache.calcite.sql.SqlLiteral;
import com.hazelcast.org.apache.calcite.sql.SqlMatchRecognize;
import com.hazelcast.org.apache.calcite.sql.SqlMerge;
import com.hazelcast.org.apache.calcite.sql.SqlNode;
import com.hazelcast.org.apache.calcite.sql.SqlNodeList;
import com.hazelcast.org.apache.calcite.sql.SqlOperator;
import com.hazelcast.org.apache.calcite.sql.SqlOperatorTable;
import com.hazelcast.org.apache.calcite.sql.SqlOrderBy;
import com.hazelcast.org.apache.calcite.sql.SqlSampleSpec;
import com.hazelcast.org.apache.calcite.sql.SqlSelect;
import com.hazelcast.org.apache.calcite.sql.SqlSelectKeyword;
import com.hazelcast.org.apache.calcite.sql.SqlSnapshot;
import com.hazelcast.org.apache.calcite.sql.SqlSyntax;
import com.hazelcast.org.apache.calcite.sql.SqlUnresolvedFunction;
import com.hazelcast.org.apache.calcite.sql.SqlUpdate;
import com.hazelcast.org.apache.calcite.sql.SqlUtil;
import com.hazelcast.org.apache.calcite.sql.SqlWindow;
import com.hazelcast.org.apache.calcite.sql.SqlWith;
import com.hazelcast.org.apache.calcite.sql.SqlWithItem;
import com.hazelcast.org.apache.calcite.sql.fun.SqlCase;
import com.hazelcast.org.apache.calcite.sql.fun.SqlStdOperatorTable;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParserPos;
import com.hazelcast.org.apache.calcite.sql.type.AssignableOperandTypeChecker;
import com.hazelcast.org.apache.calcite.sql.type.ReturnTypes;
import com.hazelcast.org.apache.calcite.sql.type.SqlOperandTypeInference;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeCoercionRule;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeName;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeUtil;
import com.hazelcast.org.apache.calcite.sql.util.SqlBasicVisitor;
import com.hazelcast.org.apache.calcite.sql.util.SqlShuttle;
import com.hazelcast.org.apache.calcite.sql.util.SqlVisitor;
import com.hazelcast.org.apache.calcite.sql.validate.implicit.TypeCoercion;
import com.hazelcast.org.apache.calcite.util.BitString;
import com.hazelcast.org.apache.calcite.util.Bug;
import com.hazelcast.org.apache.calcite.util.ImmutableBitSet;
import com.hazelcast.org.apache.calcite.util.ImmutableIntList;
import com.hazelcast.org.apache.calcite.util.ImmutableNullableList;
import com.hazelcast.org.apache.calcite.util.Litmus;
import com.hazelcast.org.apache.calcite.util.Pair;
import com.hazelcast.org.apache.calcite.util.Static;
import com.hazelcast.org.apache.calcite.util.Util;
import com.hazelcast.org.apache.calcite.util.trace.CalciteTrace;

import com.hazelcast.com.google.com.hazelcast.com.on.annotations.VisibleForTesting;
import com.hazelcast.com.google.com.hazelcast.com.on.base.Preconditions;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.ImmutableList;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.ImmutableSet;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.Lists;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.Sets;

import com.hazelcast.org.slf4j.Logger;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.AbstractList;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.hazelcast.org.apache.calcite.sql.SqlUtil.stripAs;
import static com.hazelcast.org.apache.calcite.util.Static.RESOURCE;

/**
 * Default implementation of {@link SqlValidator}.
 */
public class SqlValidatorImpl implements SqlValidatorWithHints {
  //~ Static fields/initializers ---------------------------------------------

  public static final Logger TRACER = CalciteTrace.PARSER_LOGGER;

  /**
   * Alias generated for the source table when rewriting UPDATE to MERGE.
   */
  public static final String UPDATE_SRC_ALIAS = "SYS$SRC";

  /**
   * Alias generated for the target table when rewriting UPDATE to MERGE if no
   * alias was specified by the user.
   */
  public static final String UPDATE_TGT_ALIAS = "SYS$TGT";

  /**
   * Alias prefix generated for source columns when rewriting UPDATE to MERGE.
   */
  public static final String UPDATE_ANON_PREFIX = "SYS$ANON";

  //~ Instance fields --------------------------------------------------------

  private final SqlOperatorTable opTab;
  final SqlValidatorCatalogReader catalogReader;

  /**
   * Maps ParsePosition strings to the {@link SqlIdentifier} identifier
   * objects at these positions
   */
  protected final Map idPositions = new HashMap<>();

  /**
   * Maps {@link SqlNode query node} objects to the {@link SqlValidatorScope}
   * scope created from them.
   */
  protected final Map scopes =
      new IdentityHashMap<>();

  /**
   * Maps a {@link SqlSelect} node to the scope used by its WHERE and HAVING
   * clauses.
   */
  private final Map whereScopes =
      new IdentityHashMap<>();

  /**
   * Maps a {@link SqlSelect} node to the scope used by its GROUP BY clause.
   */
  private final Map groupByScopes =
      new IdentityHashMap<>();

  /**
   * Maps a {@link SqlSelect} node to the scope used by its SELECT and HAVING
   * clauses.
   */
  private final Map selectScopes =
      new IdentityHashMap<>();

  /**
   * Maps a {@link SqlSelect} node to the scope used by its ORDER BY clause.
   */
  private final Map orderScopes =
      new IdentityHashMap<>();

  /**
   * Maps a {@link SqlSelect} node that is the argument to a CURSOR
   * constructor to the scope of the result of that select node
   */
  private final Map cursorScopes =
      new IdentityHashMap<>();

  /**
   * The name-resolution scope of a LATERAL TABLE clause.
   */
  private TableScope tableScope = null;

  /**
   * Maps a {@link SqlNode node} to the
   * {@link SqlValidatorNamespace namespace} which describes what columns they
   * contain.
   */
  protected final Map namespaces =
      new IdentityHashMap<>();

  /**
   * Set of select expressions used as cursor definitions. In standard SQL,
   * only the top-level SELECT is a cursor; Calcite extends this with
   * cursors as inputs to table functions.
   */
  private final Set cursorSet = Sets.newIdentityHashSet();

  /**
   * Stack of objects that maintain information about function calls. A stack
   * is needed to handle nested function calls. The function call currently
   * being validated is at the top of the stack.
   */
  protected final Deque functionCallStack =
      new ArrayDeque<>();

  private int nextGeneratedId;
  protected final RelDataTypeFactory typeFactory;

  /** The type of dynamic parameters until a type is imposed on them. */
  protected final RelDataType unknownType;
  private final RelDataType booleanType;

  /**
   * Map of derived RelDataType for each node. This is an IdentityHashMap
   * since in some cases (such as null literals) we need to discriminate by
   * instance.
   */
  private final Map nodeToTypeMap =
      new IdentityHashMap<>();
  private final AggFinder aggFinder;
  private final AggFinder aggOrOverFinder;
  private final AggFinder aggOrOverOrGroupFinder;
  private final AggFinder groupFinder;
  private final AggFinder overFinder;

  private Config config;

  private final Map originalExprs = new HashMap<>();

  private SqlNode top;

  private NullCollation nullCollation = NullCollation.HIGH;

  // TODO jvs 11-Dec-2008:  make this local to performUnconditionalRewrites
  // if it's OK to expand the signature of that method.
  private boolean validatingSqlMerge;

  private boolean inWindow;                        // Allow nested aggregates

  private final SqlValidatorImpl.ValidationErrorFunction validationErrorFunction =
      new SqlValidatorImpl.ValidationErrorFunction();

  // TypeCoercion instance used for implicit type coercion.
  private TypeCoercion typeCoercion;

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

  /**
   * Creates a validator.
   *
   * @param opTab         Operator table
   * @param catalogReader Catalog reader
   * @param typeFactory   Type factory
   * @param config        Config
   */
  protected SqlValidatorImpl(
      SqlOperatorTable opTab,
      SqlValidatorCatalogReader catalogReader,
      RelDataTypeFactory typeFactory,
      Config config) {
    this.opTab = Objects.requireNonNull(opTab);
    this.catalogReader = Objects.requireNonNull(catalogReader);
    this.typeFactory = Objects.requireNonNull(typeFactory);
    this.config = Objects.requireNonNull(config);

    unknownType = typeFactory.createUnknownType();
    booleanType = typeFactory.createSqlType(SqlTypeName.BOOLEAN);

    final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
    aggFinder = new AggFinder(opTab, false, true, false, null, nameMatcher);
    aggOrOverFinder =
        new AggFinder(opTab, true, true, false, null, nameMatcher);
    overFinder = new AggFinder(opTab, true, false, false, aggOrOverFinder,
        nameMatcher);
    groupFinder = new AggFinder(opTab, false, false, true, null, nameMatcher);
    aggOrOverOrGroupFinder = new AggFinder(opTab, true, true, true, null,
        nameMatcher);
    this.typeCoercion = config.typeCoercionFactory().create(typeFactory, this);
    if (config.typeCoercionRules() != null) {
      SqlTypeCoercionRule.THREAD_PROVIDERS.set(config.typeCoercionRules());
    }
  }

  /**
   * Creates a validator.
   *
   * @param opTab         Operator table
   * @param catalogReader Catalog reader
   * @param typeFactory   Type factory
   * @param conformance   Compatibility mode
   */
  @Deprecated // to be removed before 1.24
  protected SqlValidatorImpl(
      SqlOperatorTable opTab,
      SqlValidatorCatalogReader catalogReader,
      RelDataTypeFactory typeFactory,
      SqlConformance conformance) {
    this(opTab, catalogReader, typeFactory, Config.DEFAULT.withSqlConformance(conformance));
  }

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

  public SqlConformance getConformance() {
    return config.sqlConformance();
  }

  public SqlValidatorCatalogReader getCatalogReader() {
    return catalogReader;
  }

  public SqlOperatorTable getOperatorTable() {
    return opTab;
  }

  public RelDataTypeFactory getTypeFactory() {
    return typeFactory;
  }

  public RelDataType getUnknownType() {
    return unknownType;
  }

  public SqlNodeList expandStar(
      SqlNodeList selectList,
      SqlSelect select,
      boolean includeSystemVars) {
    final List list = new ArrayList<>();
    final List> types = new ArrayList<>();
    for (int i = 0; i < selectList.size(); i++) {
      final SqlNode selectItem = selectList.get(i);
      final RelDataType originalType = getValidatedNodeTypeIfKnown(selectItem);
      expandSelectItem(
          selectItem,
          select,
          Util.first(originalType, unknownType),
          list,
          catalogReader.nameMatcher().createSet(),
          types,
          includeSystemVars);
    }
    getRawSelectScope(select).setExpandedSelectList(list);
    return new SqlNodeList(list, SqlParserPos.ZERO);
  }

  // implement SqlValidator
  public void declareCursor(SqlSelect select, SqlValidatorScope parentScope) {
    cursorSet.add(select);

    // add the cursor to a map that maps the cursor to its select based on
    // the position of the cursor relative to other cursors in that call
    FunctionParamInfo funcParamInfo = functionCallStack.peek();
    Map cursorMap = funcParamInfo.cursorPosToSelectMap;
    int numCursors = cursorMap.size();
    cursorMap.put(numCursors, select);

    // create a namespace associated with the result of the select
    // that is the argument to the cursor constructor; register it
    // with a scope corresponding to the cursor
    SelectScope cursorScope = new SelectScope(parentScope, null, select);
    cursorScopes.put(select, cursorScope);
    final SelectNamespace selectNs = createSelectNamespace(select, select);
    String alias = deriveAlias(select, nextGeneratedId++);
    registerNamespace(cursorScope, alias, selectNs, false);
  }

  // implement SqlValidator
  public void pushFunctionCall() {
    FunctionParamInfo funcInfo = new FunctionParamInfo();
    functionCallStack.push(funcInfo);
  }

  // implement SqlValidator
  public void popFunctionCall() {
    functionCallStack.pop();
  }

  // implement SqlValidator
  public String getParentCursor(String columnListParamName) {
    FunctionParamInfo funcParamInfo = functionCallStack.peek();
    Map parentCursorMap =
        funcParamInfo.columnListParamToParentCursorMap;
    return parentCursorMap.get(columnListParamName);
  }

  /**
   * If selectItem is "*" or "TABLE.*", expands it and returns
   * true; otherwise writes the unexpanded item.
   *
   * @param selectItem        Select-list item
   * @param select            Containing select clause
   * @param selectItems       List that expanded items are written to
   * @param aliases           Set of aliases
   * @param fields            List of field names and types, in alias order
   * @param includeSystemVars If true include system vars in lists
   * @return Whether the node was expanded
   */
  private boolean expandSelectItem(
      final SqlNode selectItem,
      SqlSelect select,
      RelDataType targetType,
      List selectItems,
      Set aliases,
      List> fields,
      final boolean includeSystemVars) {
    final SelectScope scope = (SelectScope) getWhereScope(select);
    if (expandStar(selectItems, aliases, fields, includeSystemVars, scope,
        selectItem)) {
      return true;
    }

    // Expand the select item: fully-qualify columns, and convert
    // parentheses-free functions such as LOCALTIME into explicit function
    // calls.
    SqlNode expanded = expandSelectExpr(selectItem, scope, select);
    final String alias =
        deriveAlias(
            selectItem,
            aliases.size());

    // If expansion has altered the natural alias, supply an explicit 'AS'.
    final SqlValidatorScope selectScope = getSelectScope(select);
    if (expanded != selectItem) {
      String newAlias =
          deriveAlias(
              expanded,
              aliases.size());
      if (!newAlias.equals(alias)) {
        expanded =
            SqlStdOperatorTable.AS.createCall(
                selectItem.getParserPosition(),
                expanded,
                new SqlIdentifier(alias, SqlParserPos.ZERO));
        deriveTypeImpl(selectScope, expanded);
      }
    }

    selectItems.add(expanded);
    aliases.add(alias);

    if (expanded != null) {
      inferUnknownTypes(targetType, scope, expanded);
    }
    final RelDataType type = deriveType(selectScope, expanded);
    setValidatedNodeType(expanded, type);
    fields.add(Pair.of(alias, type));
    return false;
  }

  private static SqlNode expandExprFromJoin(SqlJoin join, SqlIdentifier identifier,
      SelectScope scope) {
    if (join.getConditionType() != JoinConditionType.USING) {
      return identifier;
    }

    for (SqlNode node : (SqlNodeList) join.getCondition()) {
      final String name = ((SqlIdentifier) node).getSimple();
      if (identifier.getSimple().equals(name)) {
        final List qualifiedNode = new ArrayList<>();
        for (ScopeChild child : scope.children) {
          if (child.namespace.getRowType()
              .getFieldNames().indexOf(name) >= 0) {
            final SqlIdentifier exp =
                new SqlIdentifier(
                    ImmutableList.of(child.name, name),
                    identifier.getParserPosition());
            qualifiedNode.add(exp);
          }
        }

        assert qualifiedNode.size() == 2;
        final SqlNode finalNode =
            SqlStdOperatorTable.AS.createCall(SqlParserPos.ZERO,
                SqlStdOperatorTable.COALESCE.createCall(SqlParserPos.ZERO,
                    qualifiedNode.get(0),
                    qualifiedNode.get(1)),
                new SqlIdentifier(name, SqlParserPos.ZERO));
        return finalNode;
      }
    }

    // Only need to try to expand the expr from the left input of join
    // since it is always left-deep join.
    final SqlNode node = join.getLeft();
    if (node instanceof SqlJoin) {
      return expandExprFromJoin((SqlJoin) node, identifier, scope);
    } else {
      return identifier;
    }
  }

  /** Returns the set of field names in the join condition specified by USING
   * or implicitly by NATURAL, de-duplicated and in order. */
  public List usingNames(SqlJoin join) {
    switch (join.getConditionType()) {
    case USING:
      final ImmutableList.Builder list = ImmutableList.builder();
      final Set names = catalogReader.nameMatcher().createSet();
      for (SqlNode node : (SqlNodeList) join.getCondition()) {
        final String name = ((SqlIdentifier) node).getSimple();
        if (names.add(name)) {
          list.add(name);
        }
      }
      return list.build();
    case NONE:
      if (join.isNatural()) {
        final RelDataType t0 = getValidatedNodeType(join.getLeft());
        final RelDataType t1 = getValidatedNodeType(join.getRight());
        return SqlValidatorUtil.deriveNaturalJoinColumnList(
            catalogReader.nameMatcher(), t0, t1);
      }
    }
    return null;
  }

  private static SqlNode expandCommonColumn(SqlSelect sqlSelect,
      SqlNode selectItem, SelectScope scope, SqlValidatorImpl validator) {
    if (!(selectItem instanceof SqlIdentifier)) {
      return selectItem;
    }

    final SqlNode from = sqlSelect.getFrom();
    if (from == null || !(from instanceof SqlJoin)) {
      return selectItem;
    }

    final SqlIdentifier identifier = (SqlIdentifier) selectItem;
    if (!identifier.isSimple()) {
      if (!validator.config().sqlConformance().allowQualifyingCommonColumn()) {
        validateQualifiedCommonColumn((SqlJoin) from, identifier, scope, validator);
      }
      return selectItem;
    }

    return expandExprFromJoin((SqlJoin) from, identifier, scope);
  }

  private static void validateQualifiedCommonColumn(SqlJoin join,
      SqlIdentifier identifier, SelectScope scope, SqlValidatorImpl validator) {
    List names = validator.usingNames(join);
    if (names == null) {
      // Not USING or NATURAL.
      return;
    }

    // First we should make sure that the first com.hazelcast.com.onent is the table name.
    // Then check whether the qualified identifier contains com.hazelcast.com.on column.
    for (ScopeChild child : scope.children) {
      if (child.name.equals(identifier.getComponent(0).toString())) {
        if (names.indexOf(identifier.getComponent(1).toString()) >= 0) {
          throw validator.newValidationError(identifier,
              RESOURCE.disallowsQualifyingCommonColumn(identifier.toString()));
        }
      }
    }

    // Only need to try to validate the expr from the left input of join
    // since it is always left-deep join.
    final SqlNode node = join.getLeft();
    if (node instanceof SqlJoin) {
      validateQualifiedCommonColumn((SqlJoin) node, identifier, scope, validator);
    }
  }

  private boolean expandStar(List selectItems, Set aliases,
      List> fields, boolean includeSystemVars,
      SelectScope scope, SqlNode node) {
    if (!(node instanceof SqlIdentifier)) {
      return false;
    }
    final SqlIdentifier identifier = (SqlIdentifier) node;
    if (!identifier.isStar()) {
      return false;
    }
    final SqlParserPos startPosition = identifier.getParserPosition();
    switch (identifier.names.size()) {
    case 1:
      boolean hasDynamicStruct = false;
      for (ScopeChild child : scope.children) {
        final int before = fields.size();
        if (child.namespace.getRowType().isDynamicStruct()) {
          hasDynamicStruct = true;
          // don't expand star if the underneath table is dynamic.
          // Treat this star as a special field in validation/conversion and
          // wait until execution time to expand this star.
          final SqlNode exp =
              new SqlIdentifier(
                  ImmutableList.of(child.name,
                      DynamicRecordType.DYNAMIC_STAR_PREFIX),
                  startPosition);
          addToSelectList(
               selectItems,
               aliases,
               fields,
               exp,
               scope,
               includeSystemVars);
        } else {
          final SqlNode from = child.namespace.getNode();
          final SqlValidatorNamespace fromNs = getNamespace(from, scope);
          assert fromNs != null;
          final RelDataType rowType = fromNs.getRowType();
          for (RelDataTypeField field : rowType.getFieldList()) {
            String columnName = field.getName();

            // TODO: do real implicit collation here
            final SqlIdentifier exp =
                new SqlIdentifier(
                    ImmutableList.of(child.name, columnName),
                    startPosition);
            // Don't add expanded rolled up columns
            if (!isRolledUpColumn(exp, scope)) {
              addOrExpandField(
                      selectItems,
                      aliases,
                      fields,
                      includeSystemVars,
                      scope,
                      exp,
                      field);
            }
          }
        }
        if (child.nullable) {
          for (int i = before; i < fields.size(); i++) {
            final Map.Entry entry = fields.get(i);
            final RelDataType type = entry.getValue();
            if (!type.isNullable()) {
              fields.set(i,
                  Pair.of(entry.getKey(),
                      typeFactory.createTypeWithNullability(type, true)));
            }
          }
        }
      }
      // If NATURAL JOIN or USING is present, move key fields to the front of
      // the list, per standard SQL. Disabled if there are dynamic fields.
      if (!hasDynamicStruct || Bug.CALCITE_2400_FIXED) {
        new Permute(scope.getNode().getFrom(), 0).permute(selectItems, fields);
      }
      return true;

    default:
      final SqlIdentifier prefixId = identifier.skipLast(1);
      final SqlValidatorScope.ResolvedImpl resolved =
          new SqlValidatorScope.ResolvedImpl();
      final SqlNameMatcher nameMatcher =
          scope.validator.catalogReader.nameMatcher();
      scope.resolve(prefixId.names, nameMatcher, true, resolved);
      if (resolved.count() == 0) {
        // e.g. "select s.t.* from e"
        // or "select r.* from e"
        throw newValidationError(prefixId,
            RESOURCE.unknownIdentifier(prefixId.toString()));
      }
      final RelDataType rowType = resolved.only().rowType();
      if (rowType.isDynamicStruct()) {
        // don't expand star if the underneath table is dynamic.
        addToSelectList(
            selectItems,
            aliases,
            fields,
            prefixId.plus(DynamicRecordType.DYNAMIC_STAR_PREFIX, startPosition),
            scope,
            includeSystemVars);
      } else if (rowType.isStruct()) {
        for (RelDataTypeField field : rowType.getFieldList()) {
          String columnName = field.getName();

          // TODO: do real implicit collation here
          addOrExpandField(
              selectItems,
              aliases,
              fields,
              includeSystemVars,
              scope,
              prefixId.plus(columnName, startPosition),
              field);
        }
      } else {
        throw newValidationError(prefixId, RESOURCE.starRequiresRecordType());
      }
      return true;
    }
  }

  private SqlNode maybeCast(SqlNode node, RelDataType currentType,
      RelDataType desiredType) {
    return SqlTypeUtil.equalSansNullability(typeFactory, currentType, desiredType)
        ? node
        : SqlStdOperatorTable.CAST.createCall(SqlParserPos.ZERO,
            node, SqlTypeUtil.convertTypeToSpec(desiredType));
  }

  private boolean addOrExpandField(List selectItems, Set aliases,
      List> fields, boolean includeSystemVars,
      SelectScope scope, SqlIdentifier id, RelDataTypeField field) {
    switch (field.getType().getStructKind()) {
    case PEEK_FIELDS:
    case PEEK_FIELDS_DEFAULT:
      final SqlNode starExp = id.plusStar();
      expandStar(
          selectItems,
          aliases,
          fields,
          includeSystemVars,
          scope,
          starExp);
      return true;

    default:
      addToSelectList(
          selectItems,
          aliases,
          fields,
          id,
          scope,
          includeSystemVars);
    }

    return false;
  }

  public SqlNode validate(SqlNode topNode) {
    SqlValidatorScope scope = new EmptyScope(this);
    scope = new CatalogScope(scope, ImmutableList.of("CATALOG"));
    final SqlNode topNode2 = validateScopedExpression(topNode, scope);
    final RelDataType type = getValidatedNodeType(topNode2);
    Util.discard(type);
    return topNode2;
  }

  public List lookupHints(SqlNode topNode, SqlParserPos pos) {
    SqlValidatorScope scope = new EmptyScope(this);
    SqlNode outermostNode = performUnconditionalRewrites(topNode, false);
    cursorSet.add(outermostNode);
    if (outermostNode.isA(SqlKind.TOP_LEVEL)) {
      registerQuery(
          scope,
          null,
          outermostNode,
          outermostNode,
          null,
          false);
    }
    final SqlValidatorNamespace ns = getNamespace(outermostNode);
    if (ns == null) {
      throw new AssertionError("Not a query: " + outermostNode);
    }
    Collection hintList = Sets.newTreeSet(SqlMoniker.COMPARATOR);
    lookupSelectHints(ns, pos, hintList);
    return ImmutableList.copyOf(hintList);
  }

  public SqlMoniker lookupQualifiedName(SqlNode topNode, SqlParserPos pos) {
    final String posString = pos.toString();
    IdInfo info = idPositions.get(posString);
    if (info != null) {
      final SqlQualified qualified = info.scope.fullyQualify(info.id);
      return new SqlIdentifierMoniker(qualified.identifier);
    } else {
      return null;
    }
  }

  /**
   * Looks up com.hazelcast.com.letion hints for a syntactically correct select SQL that has
   * been parsed into an expression tree.
   *
   * @param select   the Select node of the parsed expression tree
   * @param pos      indicates the position in the sql statement we want to get
   *                 com.hazelcast.com.letion hints for
   * @param hintList list of {@link SqlMoniker} (sql identifiers) that can
   *                 fill in at the indicated position
   */
  void lookupSelectHints(
      SqlSelect select,
      SqlParserPos pos,
      Collection hintList) {
    IdInfo info = idPositions.get(pos.toString());
    if ((info == null) || (info.scope == null)) {
      SqlNode fromNode = select.getFrom();
      final SqlValidatorScope fromScope = getFromScope(select);
      lookupFromHints(fromNode, fromScope, pos, hintList);
    } else {
      lookupNameCompletionHints(info.scope, info.id.names,
          info.id.getParserPosition(), hintList);
    }
  }

  private void lookupSelectHints(
      SqlValidatorNamespace ns,
      SqlParserPos pos,
      Collection hintList) {
    final SqlNode node = ns.getNode();
    if (node instanceof SqlSelect) {
      lookupSelectHints((SqlSelect) node, pos, hintList);
    }
  }

  private void lookupFromHints(
      SqlNode node,
      SqlValidatorScope scope,
      SqlParserPos pos,
      Collection hintList) {
    if (node == null) {
      // This can happen in cases like "select * _suggest_", so from clause is absent
      return;
    }
    final SqlValidatorNamespace ns = getNamespace(node);
    if (ns.isWrapperFor(IdentifierNamespace.class)) {
      IdentifierNamespace idNs = ns.unwrap(IdentifierNamespace.class);
      final SqlIdentifier id = idNs.getId();
      for (int i = 0; i < id.names.size(); i++) {
        if (pos.toString().equals(
            id.getComponent(i).getParserPosition().toString())) {
          final List objNames = new ArrayList<>();
          SqlValidatorUtil.getSchemaObjectMonikers(
              getCatalogReader(),
              id.names.subList(0, i + 1),
              objNames);
          for (SqlMoniker objName : objNames) {
            if (objName.getType() != SqlMonikerType.FUNCTION) {
              hintList.add(objName);
            }
          }
          return;
        }
      }
    }
    switch (node.getKind()) {
    case JOIN:
      lookupJoinHints((SqlJoin) node, scope, pos, hintList);
      break;
    default:
      lookupSelectHints(ns, pos, hintList);
      break;
    }
  }

  private void lookupJoinHints(
      SqlJoin join,
      SqlValidatorScope scope,
      SqlParserPos pos,
      Collection hintList) {
    SqlNode left = join.getLeft();
    SqlNode right = join.getRight();
    SqlNode condition = join.getCondition();
    lookupFromHints(left, scope, pos, hintList);
    if (hintList.size() > 0) {
      return;
    }
    lookupFromHints(right, scope, pos, hintList);
    if (hintList.size() > 0) {
      return;
    }
    final JoinConditionType conditionType = join.getConditionType();
    final SqlValidatorScope joinScope = scopes.get(join);
    switch (conditionType) {
    case ON:
      condition.findValidOptions(this, joinScope, pos, hintList);
      return;
    default:

      // No suggestions.
      // Not supporting hints for other types such as 'Using' yet.
      return;
    }
  }

  /**
   * Populates a list of all the valid alternatives for an identifier.
   *
   * @param scope    Validation scope
   * @param names    Components of the identifier
   * @param pos      position
   * @param hintList a list of valid options
   */
  public final void lookupNameCompletionHints(
      SqlValidatorScope scope,
      List names,
      SqlParserPos pos,
      Collection hintList) {
    // Remove the last part of name - it is a dummy
    List subNames = Util.skipLast(names);

    if (subNames.size() > 0) {
      // If there's a prefix, resolve it to a namespace.
      SqlValidatorNamespace ns = null;
      for (String name : subNames) {
        if (ns == null) {
          final SqlValidatorScope.ResolvedImpl resolved =
              new SqlValidatorScope.ResolvedImpl();
          final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
          scope.resolve(ImmutableList.of(name), nameMatcher, false, resolved);
          if (resolved.count() == 1) {
            ns = resolved.only().namespace;
          }
        } else {
          ns = ns.lookupChild(name);
        }
        if (ns == null) {
          break;
        }
      }
      if (ns != null) {
        RelDataType rowType = ns.getRowType();
        if (rowType.isStruct()) {
          for (RelDataTypeField field : rowType.getFieldList()) {
            hintList.add(
                new SqlMonikerImpl(
                    field.getName(),
                    SqlMonikerType.COLUMN));
          }
        }
      }

      // builtin function names are valid com.hazelcast.com.letion hints when the
      // identifier has only 1 name part
      findAllValidFunctionNames(names, this, hintList, pos);
    } else {
      // No prefix; use the children of the current scope (that is,
      // the aliases in the FROM clause)
      scope.findAliases(hintList);

      // If there's only one alias, add all child columns
      SelectScope selectScope =
          SqlValidatorUtil.getEnclosingSelectScope(scope);
      if ((selectScope != null)
          && (selectScope.getChildren().size() == 1)) {
        RelDataType rowType =
            selectScope.getChildren().get(0).getRowType();
        for (RelDataTypeField field : rowType.getFieldList()) {
          hintList.add(
              new SqlMonikerImpl(
                  field.getName(),
                  SqlMonikerType.COLUMN));
        }
      }
    }

    findAllValidUdfNames(names, this, hintList);
  }

  private static void findAllValidUdfNames(
      List names,
      SqlValidator validator,
      Collection result) {
    final List objNames = new ArrayList<>();
    SqlValidatorUtil.getSchemaObjectMonikers(
        validator.getCatalogReader(),
        names,
        objNames);
    for (SqlMoniker objName : objNames) {
      if (objName.getType() == SqlMonikerType.FUNCTION) {
        result.add(objName);
      }
    }
  }

  private static void findAllValidFunctionNames(
      List names,
      SqlValidator validator,
      Collection result,
      SqlParserPos pos) {
    // a function name can only be 1 part
    if (names.size() > 1) {
      return;
    }
    for (SqlOperator op : validator.getOperatorTable().getOperatorList()) {
      SqlIdentifier curOpId =
          new SqlIdentifier(
              op.getName(),
              pos);

      final SqlCall call = validator.makeNullaryCall(curOpId);
      if (call != null) {
        result.add(
            new SqlMonikerImpl(
                op.getName(),
                SqlMonikerType.FUNCTION));
      } else {
        if ((op.getSyntax() == SqlSyntax.FUNCTION)
            || (op.getSyntax() == SqlSyntax.PREFIX)) {
          if (op.getOperandTypeChecker() != null) {
            String sig = op.getAllowedSignatures();
            sig = sig.replace("'", "");
            result.add(
                new SqlMonikerImpl(
                    sig,
                    SqlMonikerType.FUNCTION));
            continue;
          }
          result.add(
              new SqlMonikerImpl(
                  op.getName(),
                  SqlMonikerType.FUNCTION));
        }
      }
    }
  }

  public SqlNode validateParameterizedExpression(
      SqlNode topNode,
      final Map nameToTypeMap) {
    SqlValidatorScope scope = new ParameterScope(this, nameToTypeMap);
    return validateScopedExpression(topNode, scope);
  }

  private SqlNode validateScopedExpression(
      SqlNode topNode,
      SqlValidatorScope scope) {
    SqlNode outermostNode = performUnconditionalRewrites(topNode, false);
    cursorSet.add(outermostNode);
    top = outermostNode;
    TRACER.trace("After unconditional rewrite: {}", outermostNode);
    if (outermostNode.isA(SqlKind.TOP_LEVEL)) {
      registerQuery(scope, null, outermostNode, outermostNode, null, false);
    }
    outermostNode.validate(this, scope);
    if (!outermostNode.isA(SqlKind.TOP_LEVEL)) {
      // force type derivation so that we can provide it to the
      // caller later without needing the scope
      deriveType(scope, outermostNode);
    }
    TRACER.trace("After validation: {}", outermostNode);
    return outermostNode;
  }

  public void validateQuery(SqlNode node, SqlValidatorScope scope,
      RelDataType targetRowType) {
    final SqlValidatorNamespace ns = getNamespace(node, scope);
    if (node.getKind() == SqlKind.TABLESAMPLE) {
      List operands = ((SqlCall) node).getOperandList();
      SqlSampleSpec sampleSpec = SqlLiteral.sampleValue(operands.get(1));
      if (sampleSpec instanceof SqlSampleSpec.SqlTableSampleSpec) {
        validateFeature(RESOURCE.sQLFeature_T613(), node.getParserPosition());
      } else if (sampleSpec
          instanceof SqlSampleSpec.SqlSubstitutionSampleSpec) {
        validateFeature(RESOURCE.sQLFeatureExt_T613_Substitution(),
            node.getParserPosition());
      }
    }

    validateNamespace(ns, targetRowType);
    switch (node.getKind()) {
    case EXTEND:
      // Until we have a dedicated namespace for EXTEND
      deriveType(scope, node);
    }
    if (node == top) {
      validateModality(node);
    }
    validateAccess(
        node,
        ns.getTable(),
        SqlAccessEnum.SELECT);

    validateSnapshot(node, scope, ns);
  }

  /**
   * Validates a namespace.
   *
   * @param namespace Namespace
   * @param targetRowType Desired row type, must not be null, may be the data
   *                      type 'unknown'.
   */
  protected void validateNamespace(final SqlValidatorNamespace namespace,
      RelDataType targetRowType) {
    namespace.validate(targetRowType);
    if (namespace.getNode() != null) {
      setValidatedNodeType(namespace.getNode(), namespace.getType());
    }
  }

  @VisibleForTesting
  public SqlValidatorScope getEmptyScope() {
    return new EmptyScope(this);
  }

  public SqlValidatorScope getCursorScope(SqlSelect select) {
    return cursorScopes.get(select);
  }

  public SqlValidatorScope getWhereScope(SqlSelect select) {
    return whereScopes.get(select);
  }

  public SqlValidatorScope getSelectScope(SqlSelect select) {
    return selectScopes.get(select);
  }

  public SelectScope getRawSelectScope(SqlSelect select) {
    SqlValidatorScope scope = getSelectScope(select);
    if (scope instanceof AggregatingSelectScope) {
      scope = ((AggregatingSelectScope) scope).getParent();
    }
    return (SelectScope) scope;
  }

  public SqlValidatorScope getHavingScope(SqlSelect select) {
    // Yes, it's the same as getSelectScope
    return selectScopes.get(select);
  }

  public SqlValidatorScope getGroupScope(SqlSelect select) {
    // Yes, it's the same as getWhereScope
    return groupByScopes.get(select);
  }

  public SqlValidatorScope getFromScope(SqlSelect select) {
    return scopes.get(select);
  }

  public SqlValidatorScope getOrderScope(SqlSelect select) {
    return orderScopes.get(select);
  }

  public SqlValidatorScope getMatchRecognizeScope(SqlMatchRecognize node) {
    return scopes.get(node);
  }

  public SqlValidatorScope getJoinScope(SqlNode node) {
    return scopes.get(stripAs(node));
  }

  public SqlValidatorScope getOverScope(SqlNode node) {
    return scopes.get(node);
  }

  private SqlValidatorNamespace getNamespace(SqlNode node,
      SqlValidatorScope scope) {
    if (node instanceof SqlIdentifier && scope instanceof DelegatingScope) {
      final SqlIdentifier id = (SqlIdentifier) node;
      final DelegatingScope idScope = (DelegatingScope) ((DelegatingScope) scope).getParent();
      return getNamespace(id, idScope);
    } else if (node instanceof SqlCall) {
      // Handle extended identifiers.
      final SqlCall call = (SqlCall) node;
      switch (call.getOperator().getKind()) {
      case TABLE_REF:
        return getNamespace(call.operand(0), scope);
      case EXTEND:
        final SqlNode operand0 = call.getOperandList().get(0);
        final SqlIdentifier identifier = operand0.getKind() == SqlKind.TABLE_REF
            ? ((SqlCall) operand0).operand(0)
            : (SqlIdentifier) operand0;
        final DelegatingScope idScope = (DelegatingScope) scope;
        return getNamespace(identifier, idScope);
      case AS:
        final SqlNode nested = call.getOperandList().get(0);
        switch (nested.getKind()) {
        case TABLE_REF:
        case EXTEND:
          return getNamespace(nested, scope);
        }
        break;
      }
    }
    return getNamespace(node);
  }

  private SqlValidatorNamespace getNamespace(SqlIdentifier id, DelegatingScope scope) {
    if (id.isSimple()) {
      final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
      final SqlValidatorScope.ResolvedImpl resolved =
          new SqlValidatorScope.ResolvedImpl();
      scope.resolve(id.names, nameMatcher, false, resolved);
      if (resolved.count() == 1) {
        return resolved.only().namespace;
      }
    }
    return getNamespace(id);
  }

  public SqlValidatorNamespace getNamespace(SqlNode node) {
    switch (node.getKind()) {
    case AS:

      // AS has a namespace if it has a column list 'AS t (c1, c2, ...)'
      final SqlValidatorNamespace ns = namespaces.get(node);
      if (ns != null) {
        return ns;
      }
      // fall through
    case TABLE_REF:
    case SNAPSHOT:
    case OVER:
    case COLLECTION_TABLE:
    case ORDER_BY:
    case TABLESAMPLE:
      return getNamespace(((SqlCall) node).operand(0));
    default:
      return namespaces.get(node);
    }
  }

  private void handleOffsetFetch(SqlNode offset, SqlNode fetch) {
    if (offset instanceof SqlDynamicParam) {
      setValidatedNodeType(offset,
          typeFactory.createSqlType(SqlTypeName.INTEGER));
    }
    if (fetch instanceof SqlDynamicParam) {
      setValidatedNodeType(fetch,
          typeFactory.createSqlType(SqlTypeName.INTEGER));
    }
  }

  /**
   * Performs expression rewrites which are always used unconditionally. These
   * rewrites massage the expression tree into a standard form so that the
   * rest of the validation logic can be simpler.
   *
   * @param node      expression to be rewritten
   * @param underFrom whether node appears directly under a FROM clause
   * @return rewritten expression
   */
  protected SqlNode performUnconditionalRewrites(
      SqlNode node,
      boolean underFrom) {
    if (node == null) {
      return node;
    }

    SqlNode newOperand;

    // first transform operands and invoke generic call rewrite
    if (node instanceof SqlCall) {
      if (node instanceof SqlMerge) {
        validatingSqlMerge = true;
      }
      SqlCall call = (SqlCall) node;
      final SqlKind kind = call.getKind();
      final List operands = call.getOperandList();
      for (int i = 0; i < operands.size(); i++) {
        SqlNode operand = operands.get(i);
        boolean childUnderFrom;
        if (kind == SqlKind.SELECT) {
          childUnderFrom = i == SqlSelect.FROM_OPERAND;
        } else if (kind == SqlKind.AS && (i == 0)) {
          // for an aliased expression, it is under FROM if
          // the AS expression is under FROM
          childUnderFrom = underFrom;
        } else {
          childUnderFrom = false;
        }
        newOperand =
            performUnconditionalRewrites(operand, childUnderFrom);
        if (newOperand != null && newOperand != operand) {
          call.setOperand(i, newOperand);
        }
      }

      if (call.getOperator() instanceof SqlUnresolvedFunction) {
        assert call instanceof SqlBasicCall;
        final SqlUnresolvedFunction function =
            (SqlUnresolvedFunction) call.getOperator();
        // This function hasn't been resolved yet.  Perform
        // a half-hearted resolution now in case it's a
        // builtin function requiring special casing.  If it's
        // not, we'll handle it later during overload resolution.
        final List overloads = new ArrayList<>();
        opTab.lookupOperatorOverloads(function.getNameAsId(),
            function.getFunctionType(), SqlSyntax.FUNCTION, overloads,
            catalogReader.nameMatcher());
        if (overloads.size() == 1) {
          ((SqlBasicCall) call).setOperator(overloads.get(0));
        }
      }
      if (config.callRewrite()) {
        node = call.getOperator().rewriteCall(this, call);
      }
    } else if (node instanceof SqlNodeList) {
      SqlNodeList list = (SqlNodeList) node;
      for (int i = 0, count = list.size(); i < count; i++) {
        SqlNode operand = list.get(i);
        newOperand =
            performUnconditionalRewrites(
                operand,
                false);
        if (newOperand != null) {
          list.getList().set(i, newOperand);
        }
      }
    }

    // now transform node itself
    final SqlKind kind = node.getKind();
    switch (kind) {
    case VALUES:
      // CHECKSTYLE: IGNORE 1
      if (underFrom || true) {
        // leave FROM (VALUES(...)) [ AS alias ] clauses alone,
        // otherwise they grow cancerously if this rewrite is invoked
        // over and over
        return node;
      } else {
        final SqlNodeList selectList =
            new SqlNodeList(SqlParserPos.ZERO);
        selectList.add(SqlIdentifier.star(SqlParserPos.ZERO));
        return new SqlSelect(node.getParserPosition(), null, selectList, node,
            null, null, null, null, null, null, null, null);
      }

    case ORDER_BY: {
      SqlOrderBy orderBy = (SqlOrderBy) node;
      handleOffsetFetch(orderBy.offset, orderBy.fetch);
      if (orderBy.query instanceof SqlSelect) {
        SqlSelect select = (SqlSelect) orderBy.query;

        // Don't clobber existing ORDER BY.  It may be needed for
        // an order-sensitive function like RANK.
        if (select.getOrderList() == null) {
          // push ORDER BY into existing select
          select.setOrderBy(orderBy.orderList);
          select.setOffset(orderBy.offset);
          select.setFetch(orderBy.fetch);
          return select;
        }
      }
      if (orderBy.query instanceof SqlWith
          && ((SqlWith) orderBy.query).body instanceof SqlSelect) {
        SqlWith with = (SqlWith) orderBy.query;
        SqlSelect select = (SqlSelect) with.body;

        // Don't clobber existing ORDER BY.  It may be needed for
        // an order-sensitive function like RANK.
        if (select.getOrderList() == null) {
          // push ORDER BY into existing select
          select.setOrderBy(orderBy.orderList);
          select.setOffset(orderBy.offset);
          select.setFetch(orderBy.fetch);
          return with;
        }
      }
      final SqlNodeList selectList = new SqlNodeList(SqlParserPos.ZERO);
      selectList.add(SqlIdentifier.star(SqlParserPos.ZERO));
      final SqlNodeList orderList;
      if (getInnerSelect(node) != null && isAggregate(getInnerSelect(node))) {
        orderList = SqlNode.clone(orderBy.orderList);
        // We assume that ORDER BY item does not have ASC etc.
        // We assume that ORDER BY item is present in SELECT list.
        for (int i = 0; i < orderList.size(); i++) {
          SqlNode sqlNode = orderList.get(i);
          SqlNodeList selectList2 = getInnerSelect(node).getSelectList();
          for (Ord sel : Ord.zip(selectList2)) {
            if (stripAs(sel.e).equalsDeep(sqlNode, Litmus.IGNORE)) {
              orderList.set(i,
                  SqlLiteral.createExactNumeric(Integer.toString(sel.i + 1),
                      SqlParserPos.ZERO));
            }
          }
        }
      } else {
        orderList = orderBy.orderList;
      }
      return new SqlSelect(SqlParserPos.ZERO, null, selectList, orderBy.query,
          null, null, null, null, orderList, orderBy.offset,
          orderBy.fetch, null);
    }

    case EXPLICIT_TABLE: {
      // (TABLE t) is equivalent to (SELECT * FROM t)
      SqlCall call = (SqlCall) node;
      final SqlNodeList selectList = new SqlNodeList(SqlParserPos.ZERO);
      selectList.add(SqlIdentifier.star(SqlParserPos.ZERO));
      return new SqlSelect(SqlParserPos.ZERO, null, selectList, call.operand(0),
          null, null, null, null, null, null, null, null);
    }

    case DELETE: {
      SqlDelete call = (SqlDelete) node;
      SqlSelect select = createSourceSelectForDelete(call);
      call.setSourceSelect(select);
      break;
    }

    case UPDATE: {
      SqlUpdate call = (SqlUpdate) node;
      SqlSelect select = createSourceSelectForUpdate(call);
      call.setSourceSelect(select);

      // See if we're supposed to rewrite UPDATE to MERGE
      // (unless this is the UPDATE clause of a MERGE,
      // in which case leave it alone).
      if (!validatingSqlMerge) {
        SqlNode selfJoinSrcExpr =
            getSelfJoinExprForUpdate(
                call.getTargetTable(),
                UPDATE_SRC_ALIAS);
        if (selfJoinSrcExpr != null) {
          node = rewriteUpdateToMerge(call, selfJoinSrcExpr);
        }
      }
      break;
    }

    case MERGE: {
      SqlMerge call = (SqlMerge) node;
      rewriteMerge(call);
      break;
    }
    }
    return node;
  }

  private SqlSelect getInnerSelect(SqlNode node) {
    for (;;) {
      if (node instanceof SqlSelect) {
        return (SqlSelect) node;
      } else if (node instanceof SqlOrderBy) {
        node = ((SqlOrderBy) node).query;
      } else if (node instanceof SqlWith) {
        node = ((SqlWith) node).body;
      } else {
        return null;
      }
    }
  }

  private void rewriteMerge(SqlMerge call) {
    SqlNodeList selectList;
    SqlUpdate updateStmt = call.getUpdateCall();
    if (updateStmt != null) {
      // if we have an update statement, just clone the select list
      // from the update statement's source since it's the same as
      // what we want for the select list of the merge source -- '*'
      // followed by the update set expressions
      selectList = SqlNode.clone(updateStmt.getSourceSelect().getSelectList());
    } else {
      // otherwise, just use select *
      selectList = new SqlNodeList(SqlParserPos.ZERO);
      selectList.add(SqlIdentifier.star(SqlParserPos.ZERO));
    }
    SqlNode targetTable = call.getTargetTable();
    if (call.getAlias() != null) {
      targetTable =
          SqlValidatorUtil.addAlias(
              targetTable,
              call.getAlias().getSimple());
    }

    // Provided there is an insert substatement, the source select for
    // the merge is a left outer join between the source in the USING
    // clause and the target table; otherwise, the join is just an
    // inner join.  Need to clone the source table reference in order
    // for validation to work
    SqlNode sourceTableRef = call.getSourceTableRef();
    SqlInsert insertCall = call.getInsertCall();
    JoinType joinType = (insertCall == null) ? JoinType.INNER : JoinType.LEFT;
    final SqlNode leftJoinTerm = SqlNode.clone(sourceTableRef);
    SqlNode outerJoin =
        new SqlJoin(SqlParserPos.ZERO,
            leftJoinTerm,
            SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
            joinType.symbol(SqlParserPos.ZERO),
            targetTable,
            JoinConditionType.ON.symbol(SqlParserPos.ZERO),
            call.getCondition());
    SqlSelect select =
        new SqlSelect(SqlParserPos.ZERO, null, selectList, outerJoin, null,
            null, null, null, null, null, null, null);
    call.setSourceSelect(select);

    // Source for the insert call is a select of the source table
    // reference with the select list being the value expressions;
    // note that the values clause has already been converted to a
    // select on the values row constructor; so we need to extract
    // that via the from clause on the select
    if (insertCall != null) {
      SqlCall valuesCall = (SqlCall) insertCall.getSource();
      SqlCall rowCall = valuesCall.operand(0);
      selectList =
          new SqlNodeList(
              rowCall.getOperandList(),
              SqlParserPos.ZERO);
      final SqlNode insertSource = SqlNode.clone(sourceTableRef);
      select =
          new SqlSelect(SqlParserPos.ZERO, null, selectList, insertSource, null,
              null, null, null, null, null, null, null);
      insertCall.setSource(select);
    }
  }

  private SqlNode rewriteUpdateToMerge(
      SqlUpdate updateCall,
      SqlNode selfJoinSrcExpr) {
    // Make sure target has an alias.
    if (updateCall.getAlias() == null) {
      updateCall.setAlias(
          new SqlIdentifier(UPDATE_TGT_ALIAS, SqlParserPos.ZERO));
    }
    SqlNode selfJoinTgtExpr =
        getSelfJoinExprForUpdate(
            updateCall.getTargetTable(),
            updateCall.getAlias().getSimple());
    assert selfJoinTgtExpr != null;

    // Create join condition between source and target exprs,
    // creating a conjunction with the user-level WHERE
    // clause if one was supplied
    SqlNode condition = updateCall.getCondition();
    SqlNode selfJoinCond =
        SqlStdOperatorTable.EQUALS.createCall(
            SqlParserPos.ZERO,
            selfJoinSrcExpr,
            selfJoinTgtExpr);
    if (condition == null) {
      condition = selfJoinCond;
    } else {
      condition =
          SqlStdOperatorTable.AND.createCall(
              SqlParserPos.ZERO,
              selfJoinCond,
              condition);
    }
    SqlNode target =
        updateCall.getTargetTable().clone(SqlParserPos.ZERO);

    // For the source, we need to anonymize the fields, so
    // that for a statement like UPDATE T SET I = I + 1,
    // there's no ambiguity for the "I" in "I + 1";
    // this is OK because the source and target have
    // identical values due to the self-join.
    // Note that we anonymize the source rather than the
    // target because downstream, the optimizer rules
    // don't want to see any projection on top of the target.
    IdentifierNamespace ns =
        new IdentifierNamespace(this, target, null, null);
    RelDataType rowType = ns.getRowType();
    SqlNode source = updateCall.getTargetTable().clone(SqlParserPos.ZERO);
    final SqlNodeList selectList = new SqlNodeList(SqlParserPos.ZERO);
    int i = 1;
    for (RelDataTypeField field : rowType.getFieldList()) {
      SqlIdentifier col =
          new SqlIdentifier(
              field.getName(),
              SqlParserPos.ZERO);
      selectList.add(
          SqlValidatorUtil.addAlias(col, UPDATE_ANON_PREFIX + i));
      ++i;
    }
    source =
        new SqlSelect(SqlParserPos.ZERO, null, selectList, source, null, null,
            null, null, null, null, null, null);
    source = SqlValidatorUtil.addAlias(source, UPDATE_SRC_ALIAS);
    SqlMerge mergeCall =
        new SqlMerge(updateCall.getParserPosition(), target, condition, source,
            updateCall, null, null, updateCall.getAlias());
    rewriteMerge(mergeCall);
    return mergeCall;
  }

  /**
   * Allows a subclass to provide information about how to convert an UPDATE
   * into a MERGE via self-join. If this method returns null, then no such
   * conversion takes place. Otherwise, this method should return a suitable
   * unique identifier expression for the given table.
   *
   * @param table identifier for table being updated
   * @param alias alias to use for qualifying columns in expression, or null
   *              for unqualified references; if this is equal to
   *              {@value #UPDATE_SRC_ALIAS}, then column references have been
   *              anonymized to "SYS$ANONx", where x is the 1-based column
   *              number.
   * @return expression for unique identifier, or null to prevent conversion
   */
  protected SqlNode getSelfJoinExprForUpdate(
      SqlNode table,
      String alias) {
    return null;
  }

  /**
   * Creates the SELECT statement that putatively feeds rows into an UPDATE
   * statement to be updated.
   *
   * @param call Call to the UPDATE operator
   * @return select statement
   */
  protected SqlSelect createSourceSelectForUpdate(SqlUpdate call) {
    final SqlNodeList selectList = new SqlNodeList(SqlParserPos.ZERO);
    selectList.add(SqlIdentifier.star(SqlParserPos.ZERO));
    int ordinal = 0;
    for (SqlNode exp : call.getSourceExpressionList()) {
      // Force unique aliases to avoid a duplicate for Y with
      // SET X=Y
      String alias = SqlUtil.deriveAliasFromOrdinal(ordinal);
      selectList.add(SqlValidatorUtil.addAlias(exp, alias));
      ++ordinal;
    }
    SqlNode sourceTable = call.getTargetTable();
    if (call.getAlias() != null) {
      sourceTable =
          SqlValidatorUtil.addAlias(
              sourceTable,
              call.getAlias().getSimple());
    }
    return new SqlSelect(SqlParserPos.ZERO, null, selectList, sourceTable,
        call.getCondition(), null, null, null, null, null, null, null);
  }

  /**
   * Creates the SELECT statement that putatively feeds rows into a DELETE
   * statement to be deleted.
   *
   * @param call Call to the DELETE operator
   * @return select statement
   */
  protected SqlSelect createSourceSelectForDelete(SqlDelete call) {
    final SqlNodeList selectList = new SqlNodeList(SqlParserPos.ZERO);
    selectList.add(SqlIdentifier.star(SqlParserPos.ZERO));
    SqlNode sourceTable = call.getTargetTable();
    if (call.getAlias() != null) {
      sourceTable =
          SqlValidatorUtil.addAlias(
              sourceTable,
              call.getAlias().getSimple());
    }
    return new SqlSelect(SqlParserPos.ZERO, null, selectList, sourceTable,
        call.getCondition(), null, null, null, null, null, null, null);
  }

  /**
   * Returns null if there is no com.hazelcast.com.on type. E.g. if the rows have a
   * different number of columns.
   */
  RelDataType getTableConstructorRowType(
      SqlCall values,
      SqlValidatorScope scope) {
    final List rows = values.getOperandList();
    assert rows.size() >= 1;
    final List rowTypes = new ArrayList<>();
    for (final SqlNode row : rows) {
      assert row.getKind() == SqlKind.ROW;
      SqlCall rowConstructor = (SqlCall) row;

      // REVIEW jvs 10-Sept-2003: Once we support single-row queries as
      // rows, need to infer aliases from there.
      final List aliasList = new ArrayList<>();
      final List typeList = new ArrayList<>();
      for (Ord column : Ord.zip(rowConstructor.getOperandList())) {
        final String alias = deriveAlias(column.e, column.i);
        aliasList.add(alias);
        final RelDataType type = deriveType(scope, column.e);
        typeList.add(type);
      }
      rowTypes.add(typeFactory.createStructType(typeList, aliasList));
    }
    if (rows.size() == 1) {
      // TODO jvs 10-Oct-2005:  get rid of this workaround once
      // leastRestrictive can handle all cases
      return rowTypes.get(0);
    }
    return typeFactory.leastRestrictive(rowTypes);
  }

  public RelDataType getValidatedNodeType(SqlNode node) {
    RelDataType type = getValidatedNodeTypeIfKnown(node);
    if (type == null) {
      throw Util.needToImplement(node);
    } else {
      return type;
    }
  }

  public RelDataType getValidatedNodeTypeIfKnown(SqlNode node) {
    final RelDataType type = nodeToTypeMap.get(node);
    if (type != null) {
      return type;
    }
    final SqlValidatorNamespace ns = getNamespace(node);
    if (ns != null) {
      return ns.getType();
    }
    final SqlNode original = originalExprs.get(node);
    if (original != null && original != node) {
      return getValidatedNodeType(original);
    }
    if (node instanceof SqlIdentifier) {
      return getCatalogReader().getNamedType((SqlIdentifier) node);
    }
    return null;
  }

  /**
   * Saves the type of a {@link SqlNode}, now that it has been validated.
   *
   * 

Unlike the base class method, this method is not deprecated. * It is available from within Calcite, but is not part of the public API. * * @param node A SQL parse tree node, never null * @param type Its type; must not be null */ @SuppressWarnings("deprecation") public final void setValidatedNodeType(SqlNode node, RelDataType type) { Objects.requireNonNull(type); Objects.requireNonNull(node); if (type.equals(unknownType)) { // don't set anything until we know what it is, and don't overwrite // a known type with the unknown type return; } nodeToTypeMap.put(node, type); } public void removeValidatedNodeType(SqlNode node) { nodeToTypeMap.remove(node); } @Nullable public SqlCall makeNullaryCall(SqlIdentifier id) { if (id.names.size() == 1 && !id.isComponentQuoted(0)) { final List list = new ArrayList<>(); opTab.lookupOperatorOverloads(id, null, SqlSyntax.FUNCTION, list, catalogReader.nameMatcher()); for (SqlOperator operator : list) { if (operator.getSyntax() == SqlSyntax.FUNCTION_ID) { // Even though this looks like an identifier, it is a // actually a call to a function. Construct a fake // call to this function, so we can use the regular // operator validation. return new SqlBasicCall(operator, SqlNode.EMPTY_ARRAY, id.getParserPosition(), true, null); } } } return null; } public RelDataType deriveType( SqlValidatorScope scope, SqlNode expr) { Objects.requireNonNull(scope); Objects.requireNonNull(expr); // if we already know the type, no need to re-derive RelDataType type = nodeToTypeMap.get(expr); if (type != null) { return type; } final SqlValidatorNamespace ns = getNamespace(expr); if (ns != null) { return ns.getType(); } type = deriveTypeImpl(scope, expr); Preconditions.checkArgument( type != null, "SqlValidator.deriveTypeInternal returned null"); setValidatedNodeType(expr, type); return type; } /** * Derives the type of a node, never null. */ RelDataType deriveTypeImpl( SqlValidatorScope scope, SqlNode operand) { DeriveTypeVisitor v = new DeriveTypeVisitor(scope); final RelDataType type = operand.accept(v); return Objects.requireNonNull(scope.nullifyType(operand, type)); } public RelDataType deriveConstructorType( SqlValidatorScope scope, SqlCall call, SqlFunction unresolvedConstructor, SqlFunction resolvedConstructor, List argTypes) { SqlIdentifier sqlIdentifier = unresolvedConstructor.getSqlIdentifier(); assert sqlIdentifier != null; RelDataType type = catalogReader.getNamedType(sqlIdentifier); if (type == null) { // TODO jvs 12-Feb-2005: proper type name formatting throw newValidationError(sqlIdentifier, RESOURCE.unknownDatatypeName(sqlIdentifier.toString())); } if (resolvedConstructor == null) { if (call.operandCount() > 0) { // This is not a default constructor invocation, and // no user-defined constructor could be found throw handleUnresolvedFunction(call, unresolvedConstructor, argTypes, null); } } else { SqlCall testCall = resolvedConstructor.createCall( call.getParserPosition(), call.getOperandList()); RelDataType returnType = resolvedConstructor.validateOperands( this, scope, testCall); assert type == returnType; } if (config.identifierExpansion()) { if (resolvedConstructor != null) { ((SqlBasicCall) call).setOperator(resolvedConstructor); } else { // fake a fully-qualified call to the default constructor ((SqlBasicCall) call).setOperator( new SqlFunction( type.getSqlIdentifier(), ReturnTypes.explicit(type), null, null, null, SqlFunctionCategory.USER_DEFINED_CONSTRUCTOR)); } } return type; } public CalciteException handleUnresolvedFunction(SqlCall call, SqlFunction unresolvedFunction, List argTypes, List argNames) { // For builtins, we can give a better error message final List overloads = new ArrayList<>(); opTab.lookupOperatorOverloads(unresolvedFunction.getNameAsId(), null, SqlSyntax.FUNCTION, overloads, catalogReader.nameMatcher()); if (overloads.size() == 1) { SqlFunction fun = (SqlFunction) overloads.get(0); if ((fun.getSqlIdentifier() == null) && (fun.getSyntax() != SqlSyntax.FUNCTION_ID)) { final int expectedArgCount = fun.getOperandCountRange().getMin(); throw newValidationError(call, RESOURCE.invalidArgCount(call.getOperator().getName(), expectedArgCount)); } } AssignableOperandTypeChecker typeChecking = new AssignableOperandTypeChecker(argTypes, argNames); String signature = typeChecking.getAllowedSignatures( unresolvedFunction, unresolvedFunction.getName()); throw newValidationError(call, RESOURCE.validatorUnknownFunction(signature)); } protected void inferUnknownTypes( @Nonnull RelDataType inferredType, @Nonnull SqlValidatorScope scope, @Nonnull SqlNode node) { Objects.requireNonNull(inferredType); Objects.requireNonNull(scope); Objects.requireNonNull(node); final SqlValidatorScope newScope = scopes.get(node); if (newScope != null) { scope = newScope; } boolean isNullLiteral = SqlUtil.isNullLiteral(node, false); if ((node instanceof SqlDynamicParam) || isNullLiteral) { if (inferredType.equals(unknownType)) { if (isNullLiteral) { if (config.typeCoercionEnabled()) { // derive type of null literal deriveType(scope, node); return; } else { throw newValidationError(node, RESOURCE.nullIllegal()); } } else { throw newValidationError(node, RESOURCE.dynamicParamIllegal()); } } // REVIEW: should dynamic parameter types always be nullable? RelDataType newInferredType = typeFactory.createTypeWithNullability(inferredType, true); if (SqlTypeUtil.inCharFamily(inferredType)) { newInferredType = typeFactory.createTypeWithCharsetAndCollation( newInferredType, inferredType.getCharset(), inferredType.getCollation()); } setValidatedNodeType(node, newInferredType); } else if (node instanceof SqlNodeList) { SqlNodeList nodeList = (SqlNodeList) node; if (inferredType.isStruct()) { if (inferredType.getFieldCount() != nodeList.size()) { // this can happen when we're validating an INSERT // where the source and target degrees are different; // bust out, and the error will be detected higher up return; } } int i = 0; for (SqlNode child : nodeList) { RelDataType type; if (inferredType.isStruct()) { type = inferredType.getFieldList().get(i).getType(); ++i; } else { type = inferredType; } inferUnknownTypes(type, scope, child); } } else if (node instanceof SqlCase) { final SqlCase caseCall = (SqlCase) node; final RelDataType whenType = caseCall.getValueOperand() == null ? booleanType : unknownType; for (SqlNode sqlNode : caseCall.getWhenOperands().getList()) { inferUnknownTypes(whenType, scope, sqlNode); } RelDataType returnType = deriveType(scope, node); for (SqlNode sqlNode : caseCall.getThenOperands().getList()) { inferUnknownTypes(returnType, scope, sqlNode); } if (!SqlUtil.isNullLiteral(caseCall.getElseOperand(), false)) { inferUnknownTypes( returnType, scope, caseCall.getElseOperand()); } else { setValidatedNodeType(caseCall.getElseOperand(), returnType); } } else if (node.getKind() == SqlKind.AS) { // For AS operator, only infer the operand not the alias inferUnknownTypes(inferredType, scope, ((SqlCall) node).operand(0)); } else if (node instanceof SqlCall) { final SqlCall call = (SqlCall) node; final SqlOperandTypeInference operandTypeInference = call.getOperator().getOperandTypeInference(); final SqlCallBinding callBinding = new SqlCallBinding(this, scope, call); final List operands = callBinding.operands(); final RelDataType[] operandTypes = new RelDataType[operands.size()]; Arrays.fill(operandTypes, unknownType); // TODO: eventually should assert(operandTypeInference != null) // instead; for now just eat it if (operandTypeInference != null) { operandTypeInference.inferOperandTypes( callBinding, inferredType, operandTypes); } for (int i = 0; i < operands.size(); ++i) { final SqlNode operand = operands.get(i); if (operand != null) { inferUnknownTypes(operandTypes[i], scope, operand); } } } } /** * Adds an expression to a select list, ensuring that its alias does not * clash with any existing expressions on the list. */ protected void addToSelectList( List list, Set aliases, List> fieldList, SqlNode exp, SelectScope scope, final boolean includeSystemVars) { String alias = SqlValidatorUtil.getAlias(exp, -1); String uniqueAlias = SqlValidatorUtil.uniquify( alias, aliases, SqlValidatorUtil.EXPR_SUGGESTER); if (!alias.equals(uniqueAlias)) { exp = SqlValidatorUtil.addAlias(exp, uniqueAlias); } fieldList.add(Pair.of(uniqueAlias, deriveType(scope, exp))); list.add(exp); } public String deriveAlias( SqlNode node, int ordinal) { return SqlValidatorUtil.getAlias(node, ordinal); } // implement SqlValidator public void setIdentifierExpansion(boolean expandIdentifiers) { this.config = this.config.withIdentifierExpansion(expandIdentifiers); } // implement SqlValidator public void setColumnReferenceExpansion( boolean expandColumnReferences) { this.config = this.config.withColumnReferenceExpansion(expandColumnReferences); } // implement SqlValidator public boolean getColumnReferenceExpansion() { return this.config.columnReferenceExpansion(); } public void setDefaultNullCollation(NullCollation nullCollation) { this.config = this.config.withDefaultNullCollation(Objects.requireNonNull(nullCollation)); } public NullCollation getDefaultNullCollation() { return this.config.defaultNullCollation(); } // implement SqlValidator public void setCallRewrite(boolean rewriteCalls) { this.config = this.config.withCallRewrite(rewriteCalls); } public boolean shouldExpandIdentifiers() { return this.config.identifierExpansion(); } protected boolean shouldAllowIntermediateOrderBy() { return true; } private void registerMatchRecognize( SqlValidatorScope parentScope, SqlValidatorScope usingScope, SqlMatchRecognize call, SqlNode enclosingNode, String alias, boolean forceNullable) { final MatchRecognizeNamespace matchRecognizeNamespace = createMatchRecognizeNameSpace(call, enclosingNode); registerNamespace(usingScope, alias, matchRecognizeNamespace, forceNullable); final MatchRecognizeScope matchRecognizeScope = new MatchRecognizeScope(parentScope, call); scopes.put(call, matchRecognizeScope); // parse input query SqlNode expr = call.getTableRef(); SqlNode newExpr = registerFrom(usingScope, matchRecognizeScope, true, expr, expr, null, null, forceNullable, false); if (expr != newExpr) { call.setOperand(0, newExpr); } } protected MatchRecognizeNamespace createMatchRecognizeNameSpace( SqlMatchRecognize call, SqlNode enclosingNode) { return new MatchRecognizeNamespace(this, call, enclosingNode); } /** * Registers a new namespace, and adds it as a child of its parent scope. * Derived class can override this method to tinker with namespaces as they * are created. * * @param usingScope Parent scope (which will want to look for things in * this namespace) * @param alias Alias by which parent will refer to this namespace * @param ns Namespace * @param forceNullable Whether to force the type of namespace to be nullable */ protected void registerNamespace( SqlValidatorScope usingScope, String alias, SqlValidatorNamespace ns, boolean forceNullable) { namespaces.put(ns.getNode(), ns); if (usingScope != null) { usingScope.addChild(ns, alias, forceNullable); } } /** * Registers scopes and namespaces implied a relational expression in the * FROM clause. * *

{@code parentScope} and {@code usingScope} are often the same. They * differ when the namespace are not visible within the parent. (Example * needed.) * *

Likewise, {@code enclosingNode} and {@code node} are often the same. * {@code enclosingNode} is the topmost node within the FROM clause, from * which any decorations like an alias (AS alias) or a table * sample clause are stripped away to get {@code node}. Both are recorded in * the namespace. * * @param parentScope Parent scope which this scope turns to in order to * resolve objects * @param usingScope Scope whose child list this scope should add itself to * @param register Whether to register this scope as a child of * {@code usingScope} * @param node Node which namespace is based on * @param enclosingNode Outermost node for namespace, including decorations * such as alias and sample clause * @param alias Alias * @param extendList Definitions of extended columns * @param forceNullable Whether to force the type of namespace to be * nullable because it is in an outer join * @param lateral Whether LATERAL is specified, so that items to the * left of this in the JOIN tree are visible in the * scope * @return registered node, usually the same as {@code node} */ private SqlNode registerFrom( SqlValidatorScope parentScope, SqlValidatorScope usingScope, boolean register, final SqlNode node, SqlNode enclosingNode, String alias, SqlNodeList extendList, boolean forceNullable, final boolean lateral) { final SqlKind kind = node.getKind(); SqlNode expr; SqlNode newExpr; // Add an alias if necessary. SqlNode newNode = node; if (alias == null) { switch (kind) { case IDENTIFIER: case OVER: alias = deriveAlias(node, -1); if (alias == null) { alias = deriveAlias(node, nextGeneratedId++); } if (config.identifierExpansion()) { newNode = SqlValidatorUtil.addAlias(node, alias); } break; case SELECT: case UNION: case INTERSECT: case EXCEPT: case VALUES: case UNNEST: case OTHER_FUNCTION: case COLLECTION_TABLE: case MATCH_RECOGNIZE: // give this anonymous construct a name since later // query processing stages rely on it alias = deriveAlias(node, nextGeneratedId++); if (config.identifierExpansion()) { // Since we're expanding identifiers, we should make the // aliases explicit too, otherwise the expanded query // will not be consistent if we convert back to SQL, e.g. // "select EXPR$1.EXPR$2 from values (1)". newNode = SqlValidatorUtil.addAlias(node, alias); } break; } } if (lateral) { SqlValidatorScope s = usingScope; while (s instanceof JoinScope) { s = ((JoinScope) s).getUsingScope(); } final SqlNode node2 = s != null ? s.getNode() : node; final TableScope tableScope = new TableScope(parentScope, node2); if (usingScope instanceof ListScope) { for (ScopeChild child : ((ListScope) usingScope).children) { tableScope.addChild(child.namespace, child.name, child.nullable); } } parentScope = tableScope; } SqlCall call; SqlNode operand; SqlNode newOperand; switch (kind) { case AS: call = (SqlCall) node; if (alias == null) { alias = call.operand(1).toString(); } final boolean needAlias = call.operandCount() > 2; expr = call.operand(0); newExpr = registerFrom( parentScope, usingScope, !needAlias, expr, enclosingNode, alias, extendList, forceNullable, lateral); if (newExpr != expr) { call.setOperand(0, newExpr); } // If alias has a column list, introduce a namespace to translate // column names. We skipped registering it just now. if (needAlias) { registerNamespace( usingScope, alias, new AliasNamespace(this, call, enclosingNode), forceNullable); } return node; case MATCH_RECOGNIZE: registerMatchRecognize(parentScope, usingScope, (SqlMatchRecognize) node, enclosingNode, alias, forceNullable); return node; case TABLESAMPLE: call = (SqlCall) node; expr = call.operand(0); newExpr = registerFrom( parentScope, usingScope, true, expr, enclosingNode, alias, extendList, forceNullable, lateral); if (newExpr != expr) { call.setOperand(0, newExpr); } return node; case JOIN: final SqlJoin join = (SqlJoin) node; final JoinScope joinScope = new JoinScope(parentScope, usingScope, join); scopes.put(join, joinScope); final SqlNode left = join.getLeft(); final SqlNode right = join.getRight(); final boolean rightIsLateral = isLateral(right); boolean forceLeftNullable = forceNullable; boolean forceRightNullable = forceNullable; switch (join.getJoinType()) { case LEFT: forceRightNullable = true; break; case RIGHT: forceLeftNullable = true; break; case FULL: forceLeftNullable = true; forceRightNullable = true; break; } final SqlNode newLeft = registerFrom( parentScope, joinScope, true, left, left, null, null, forceLeftNullable, lateral); if (newLeft != left) { join.setLeft(newLeft); } final SqlNode newRight = registerFrom( parentScope, joinScope, true, right, right, null, null, forceRightNullable, lateral); if (newRight != right) { join.setRight(newRight); } registerSubQueries(joinScope, join.getCondition()); final JoinNamespace joinNamespace = new JoinNamespace(this, join); registerNamespace(null, null, joinNamespace, forceNullable); return join; case IDENTIFIER: final SqlIdentifier id = (SqlIdentifier) node; final IdentifierNamespace newNs = new IdentifierNamespace( this, id, extendList, enclosingNode, parentScope); registerNamespace(register ? usingScope : null, alias, newNs, forceNullable); if (tableScope == null) { tableScope = new TableScope(parentScope, node); } tableScope.addChild(newNs, alias, forceNullable); if (extendList != null && extendList.size() != 0) { return enclosingNode; } return newNode; case LATERAL: return registerFrom( parentScope, usingScope, register, ((SqlCall) node).operand(0), enclosingNode, alias, extendList, forceNullable, true); case COLLECTION_TABLE: call = (SqlCall) node; operand = call.operand(0); newOperand = registerFrom( parentScope, usingScope, register, operand, enclosingNode, alias, extendList, forceNullable, lateral); if (newOperand != operand) { call.setOperand(0, newOperand); } scopes.put(node, parentScope); return newNode; case UNNEST: if (!lateral) { return registerFrom(parentScope, usingScope, register, node, enclosingNode, alias, extendList, forceNullable, true); } // fall through case SELECT: case UNION: case INTERSECT: case EXCEPT: case VALUES: case WITH: case OTHER_FUNCTION: if (alias == null) { alias = deriveAlias(node, nextGeneratedId++); } registerQuery( parentScope, register ? usingScope : null, node, enclosingNode, alias, forceNullable); return newNode; case OVER: if (!shouldAllowOverRelation()) { throw Util.unexpected(kind); } call = (SqlCall) node; final OverScope overScope = new OverScope(usingScope, call); scopes.put(call, overScope); operand = call.operand(0); newOperand = registerFrom( parentScope, overScope, true, operand, enclosingNode, alias, extendList, forceNullable, lateral); if (newOperand != operand) { call.setOperand(0, newOperand); } for (ScopeChild child : overScope.children) { registerNamespace(register ? usingScope : null, child.name, child.namespace, forceNullable); } return newNode; case TABLE_REF: call = (SqlCall) node; registerFrom(parentScope, usingScope, register, call.operand(0), enclosingNode, alias, extendList, forceNullable, lateral); if (extendList != null && extendList.size() != 0) { return enclosingNode; } return newNode; case EXTEND: final SqlCall extend = (SqlCall) node; return registerFrom(parentScope, usingScope, true, extend.getOperandList().get(0), extend, alias, (SqlNodeList) extend.getOperandList().get(1), forceNullable, lateral); case SNAPSHOT: call = (SqlCall) node; operand = call.operand(0); newOperand = registerFrom( parentScope, usingScope, register, operand, enclosingNode, alias, extendList, forceNullable, lateral); if (newOperand != operand) { call.setOperand(0, newOperand); } // Put the usingScope which can be a JoinScope // or a SelectScope, in order to see the left items // of the JOIN tree. scopes.put(node, usingScope); return newNode; default: throw Util.unexpected(kind); } } private static boolean isLateral(SqlNode node) { switch (node.getKind()) { case LATERAL: case UNNEST: // Per SQL std, UNNEST is implicitly LATERAL. return true; case AS: return isLateral(((SqlCall) node).operand(0)); default: return false; } } protected boolean shouldAllowOverRelation() { return false; } /** * Creates a namespace for a SELECT node. Derived class may * override this factory method. * * @param select Select node * @param enclosingNode Enclosing node * @return Select namespace */ protected SelectNamespace createSelectNamespace( SqlSelect select, SqlNode enclosingNode) { return new SelectNamespace(this, select, enclosingNode); } /** * Creates a namespace for a set operation (UNION, * INTERSECT, or EXCEPT). Derived class may override * this factory method. * * @param call Call to set operation * @param enclosingNode Enclosing node * @return Set operation namespace */ protected SetopNamespace createSetopNamespace( SqlCall call, SqlNode enclosingNode) { return new SetopNamespace(this, call, enclosingNode); } /** * Registers a query in a parent scope. * * @param parentScope Parent scope which this scope turns to in order to * resolve objects * @param usingScope Scope whose child list this scope should add itself to * @param node Query node * @param alias Name of this query within its parent. Must be specified * if usingScope != null */ private void registerQuery( SqlValidatorScope parentScope, SqlValidatorScope usingScope, SqlNode node, SqlNode enclosingNode, String alias, boolean forceNullable) { Preconditions.checkArgument(usingScope == null || alias != null); registerQuery( parentScope, usingScope, node, enclosingNode, alias, forceNullable, true); } /** * Registers a query in a parent scope. * * @param parentScope Parent scope which this scope turns to in order to * resolve objects * @param usingScope Scope whose child list this scope should add itself to * @param node Query node * @param alias Name of this query within its parent. Must be specified * if usingScope != null * @param checkUpdate if true, validate that the update feature is supported * if validating the update statement */ private void registerQuery( SqlValidatorScope parentScope, SqlValidatorScope usingScope, SqlNode node, SqlNode enclosingNode, String alias, boolean forceNullable, boolean checkUpdate) { Objects.requireNonNull(node); Objects.requireNonNull(enclosingNode); Preconditions.checkArgument(usingScope == null || alias != null); SqlCall call; List operands; switch (node.getKind()) { case SELECT: final SqlSelect select = (SqlSelect) node; final SelectNamespace selectNs = createSelectNamespace(select, enclosingNode); registerNamespace(usingScope, alias, selectNs, forceNullable); final SqlValidatorScope windowParentScope = (usingScope != null) ? usingScope : parentScope; SelectScope selectScope = new SelectScope(parentScope, windowParentScope, select); scopes.put(select, selectScope); // Start by registering the WHERE clause whereScopes.put(select, selectScope); registerOperandSubQueries( selectScope, select, SqlSelect.WHERE_OPERAND); // Register FROM with the inherited scope 'parentScope', not // 'selectScope', otherwise tables in the FROM clause would be // able to see each other. final SqlNode from = select.getFrom(); if (from != null) { final SqlNode newFrom = registerFrom( parentScope, selectScope, true, from, from, null, null, false, false); if (newFrom != from) { select.setFrom(newFrom); } } // If this is an aggregating query, the SELECT list and HAVING // clause use a different scope, where you can only reference // columns which are in the GROUP BY clause. SqlValidatorScope aggScope = selectScope; if (isAggregate(select)) { aggScope = new AggregatingSelectScope(selectScope, select, false); selectScopes.put(select, aggScope); } else { selectScopes.put(select, selectScope); } if (select.getGroup() != null) { GroupByScope groupByScope = new GroupByScope(selectScope, select.getGroup(), select); groupByScopes.put(select, groupByScope); registerSubQueries(groupByScope, select.getGroup()); } registerOperandSubQueries( aggScope, select, SqlSelect.HAVING_OPERAND); registerSubQueries(aggScope, select.getSelectList()); final SqlNodeList orderList = select.getOrderList(); if (orderList != null) { // If the query is 'SELECT DISTINCT', restrict the columns // available to the ORDER BY clause. if (select.isDistinct()) { aggScope = new AggregatingSelectScope(selectScope, select, true); } OrderByScope orderScope = new OrderByScope(aggScope, orderList, select); orderScopes.put(select, orderScope); registerSubQueries(orderScope, orderList); if (!isAggregate(select)) { // Since this is not an aggregating query, // there cannot be any aggregates in the ORDER BY clause. SqlNode agg = aggFinder.findAgg(orderList); if (agg != null) { throw newValidationError(agg, RESOURCE.aggregateIllegalInOrderBy()); } } } break; case INTERSECT: validateFeature(RESOURCE.sQLFeature_F302(), node.getParserPosition()); registerSetop( parentScope, usingScope, node, node, alias, forceNullable); break; case EXCEPT: validateFeature(RESOURCE.sQLFeature_E071_03(), node.getParserPosition()); registerSetop( parentScope, usingScope, node, node, alias, forceNullable); break; case UNION: registerSetop( parentScope, usingScope, node, node, alias, forceNullable); break; case WITH: registerWith(parentScope, usingScope, (SqlWith) node, enclosingNode, alias, forceNullable, checkUpdate); break; case VALUES: call = (SqlCall) node; scopes.put(call, parentScope); final TableConstructorNamespace tableConstructorNamespace = new TableConstructorNamespace( this, call, parentScope, enclosingNode); registerNamespace( usingScope, alias, tableConstructorNamespace, forceNullable); operands = call.getOperandList(); for (int i = 0; i < operands.size(); ++i) { assert operands.get(i).getKind() == SqlKind.ROW; // FIXME jvs 9-Feb-2005: Correlation should // be illegal in these sub-queries. Same goes for // any non-lateral SELECT in the FROM list. registerOperandSubQueries(parentScope, call, i); } break; case INSERT: SqlInsert insertCall = (SqlInsert) node; InsertNamespace insertNs = new InsertNamespace( this, insertCall, enclosingNode, parentScope); registerNamespace(usingScope, null, insertNs, forceNullable); registerQuery( parentScope, usingScope, insertCall.getSource(), enclosingNode, null, false); break; case DELETE: SqlDelete deleteCall = (SqlDelete) node; DeleteNamespace deleteNs = new DeleteNamespace( this, deleteCall, enclosingNode, parentScope); registerNamespace(usingScope, null, deleteNs, forceNullable); registerQuery( parentScope, usingScope, deleteCall.getSourceSelect(), enclosingNode, null, false); break; case UPDATE: if (checkUpdate) { validateFeature(RESOURCE.sQLFeature_E101_03(), node.getParserPosition()); } SqlUpdate updateCall = (SqlUpdate) node; UpdateNamespace updateNs = new UpdateNamespace( this, updateCall, enclosingNode, parentScope); registerNamespace(usingScope, null, updateNs, forceNullable); registerQuery( parentScope, usingScope, updateCall.getSourceSelect(), enclosingNode, null, false); break; case MERGE: validateFeature(RESOURCE.sQLFeature_F312(), node.getParserPosition()); SqlMerge mergeCall = (SqlMerge) node; MergeNamespace mergeNs = new MergeNamespace( this, mergeCall, enclosingNode, parentScope); registerNamespace(usingScope, null, mergeNs, forceNullable); registerQuery( parentScope, usingScope, mergeCall.getSourceSelect(), enclosingNode, null, false); // update call can reference either the source table reference // or the target table, so set its parent scope to the merge's // source select; when validating the update, skip the feature // validation check if (mergeCall.getUpdateCall() != null) { registerQuery( whereScopes.get(mergeCall.getSourceSelect()), null, mergeCall.getUpdateCall(), enclosingNode, null, false, false); } if (mergeCall.getInsertCall() != null) { registerQuery( parentScope, null, mergeCall.getInsertCall(), enclosingNode, null, false); } break; case UNNEST: call = (SqlCall) node; final UnnestNamespace unnestNs = new UnnestNamespace(this, call, parentScope, enclosingNode); registerNamespace( usingScope, alias, unnestNs, forceNullable); registerOperandSubQueries(parentScope, call, 0); scopes.put(node, parentScope); break; case OTHER_FUNCTION: call = (SqlCall) node; ProcedureNamespace procNs = new ProcedureNamespace( this, parentScope, call, enclosingNode); registerNamespace( usingScope, alias, procNs, forceNullable); registerSubQueries(parentScope, call); break; case MULTISET_QUERY_CONSTRUCTOR: case MULTISET_VALUE_CONSTRUCTOR: validateFeature(RESOURCE.sQLFeature_S271(), node.getParserPosition()); call = (SqlCall) node; CollectScope cs = new CollectScope(parentScope, usingScope, call); final CollectNamespace tableConstructorNs = new CollectNamespace(call, cs, enclosingNode); final String alias2 = deriveAlias(node, nextGeneratedId++); registerNamespace( usingScope, alias2, tableConstructorNs, forceNullable); operands = call.getOperandList(); for (int i = 0; i < operands.size(); i++) { registerOperandSubQueries(parentScope, call, i); } break; default: throw Util.unexpected(node.getKind()); } } private void registerSetop( SqlValidatorScope parentScope, SqlValidatorScope usingScope, SqlNode node, SqlNode enclosingNode, String alias, boolean forceNullable) { SqlCall call = (SqlCall) node; final SetopNamespace setopNamespace = createSetopNamespace(call, enclosingNode); registerNamespace(usingScope, alias, setopNamespace, forceNullable); // A setop is in the same scope as its parent. scopes.put(call, parentScope); for (SqlNode operand : call.getOperandList()) { registerQuery( parentScope, null, operand, operand, null, false); } } private void registerWith( SqlValidatorScope parentScope, SqlValidatorScope usingScope, SqlWith with, SqlNode enclosingNode, String alias, boolean forceNullable, boolean checkUpdate) { final WithNamespace withNamespace = new WithNamespace(this, with, enclosingNode); registerNamespace(usingScope, alias, withNamespace, forceNullable); SqlValidatorScope scope = parentScope; for (SqlNode withItem_ : with.withList) { final SqlWithItem withItem = (SqlWithItem) withItem_; final WithScope withScope = new WithScope(scope, withItem); scopes.put(withItem, withScope); registerQuery(scope, null, withItem.query, with, withItem.name.getSimple(), false); registerNamespace(null, alias, new WithItemNamespace(this, withItem, enclosingNode), false); scope = withScope; } registerQuery(scope, null, with.body, enclosingNode, alias, forceNullable, checkUpdate); } public boolean isAggregate(SqlSelect select) { if (getAggregate(select) != null) { return true; } // Also when nested window aggregates are present for (SqlCall call : overFinder.findAll(select.getSelectList())) { assert call.getKind() == SqlKind.OVER; if (isNestedAggregateWindow(call.operand(0))) { return true; } if (isOverAggregateWindow(call.operand(1))) { return true; } } return false; } protected boolean isNestedAggregateWindow(SqlNode node) { AggFinder nestedAggFinder = new AggFinder(opTab, false, false, false, aggFinder, catalogReader.nameMatcher()); return nestedAggFinder.findAgg(node) != null; } protected boolean isOverAggregateWindow(SqlNode node) { return aggFinder.findAgg(node) != null; } /** Returns the parse tree node (GROUP BY, HAVING, or an aggregate function * call) that causes {@code select} to be an aggregate query, or null if it * is not an aggregate query. * *

The node is useful context for error messages, * but you cannot assume that the node is the only aggregate function. */ protected SqlNode getAggregate(SqlSelect select) { SqlNode node = select.getGroup(); if (node != null) { return node; } node = select.getHaving(); if (node != null) { return node; } return getAgg(select); } /** If there is at least one call to an aggregate function, returns the * first. */ private SqlNode getAgg(SqlSelect select) { final SelectScope selectScope = getRawSelectScope(select); if (selectScope != null) { final List selectList = selectScope.getExpandedSelectList(); if (selectList != null) { return aggFinder.findAgg(selectList); } } return aggFinder.findAgg(select.getSelectList()); } @SuppressWarnings("deprecation") public boolean isAggregate(SqlNode selectNode) { return aggFinder.findAgg(selectNode) != null; } private void validateNodeFeature(SqlNode node) { switch (node.getKind()) { case MULTISET_VALUE_CONSTRUCTOR: validateFeature(RESOURCE.sQLFeature_S271(), node.getParserPosition()); break; } } private void registerSubQueries( SqlValidatorScope parentScope, SqlNode node) { if (node == null) { return; } if (node.getKind().belongsTo(SqlKind.QUERY) || node.getKind() == SqlKind.MULTISET_QUERY_CONSTRUCTOR || node.getKind() == SqlKind.MULTISET_VALUE_CONSTRUCTOR) { registerQuery(parentScope, null, node, node, null, false); } else if (node instanceof SqlCall) { validateNodeFeature(node); SqlCall call = (SqlCall) node; for (int i = 0; i < call.operandCount(); i++) { registerOperandSubQueries(parentScope, call, i); } } else if (node instanceof SqlNodeList) { SqlNodeList list = (SqlNodeList) node; for (int i = 0, count = list.size(); i < count; i++) { SqlNode listNode = list.get(i); if (listNode.getKind().belongsTo(SqlKind.QUERY)) { listNode = SqlStdOperatorTable.SCALAR_QUERY.createCall( listNode.getParserPosition(), listNode); list.set(i, listNode); } registerSubQueries(parentScope, listNode); } } else { // atomic node -- can be ignored } } /** * Registers any sub-queries inside a given call operand, and converts the * operand to a scalar sub-query if the operator requires it. * * @param parentScope Parent scope * @param call Call * @param operandOrdinal Ordinal of operand within call * @see SqlOperator#argumentMustBeScalar(int) */ private void registerOperandSubQueries( SqlValidatorScope parentScope, SqlCall call, int operandOrdinal) { SqlNode operand = call.operand(operandOrdinal); if (operand == null) { return; } if (operand.getKind().belongsTo(SqlKind.QUERY) && call.getOperator().argumentMustBeScalar(operandOrdinal)) { operand = SqlStdOperatorTable.SCALAR_QUERY.createCall( operand.getParserPosition(), operand); call.setOperand(operandOrdinal, operand); } registerSubQueries(parentScope, operand); } public void validateIdentifier(SqlIdentifier id, SqlValidatorScope scope) { final SqlQualified fqId = scope.fullyQualify(id); if (this.config.columnReferenceExpansion()) { // NOTE jvs 9-Apr-2007: this doesn't cover ORDER BY, which has its // own ideas about qualification. id.assignNamesFrom(fqId.identifier); } else { Util.discard(fqId); } } public void validateLiteral(SqlLiteral literal) { switch (literal.getTypeName()) { case DECIMAL: // Decimal and long have the same precision (as 64-bit integers), so // the unscaled value of a decimal must fit into a long. // REVIEW jvs 4-Aug-2004: This should probably be calling over to // the available calculator implementations to see what they // support. For now use ESP instead. // // jhyde 2006/12/21: I think the limits should be baked into the // type system, not dependent on the calculator implementation. BigDecimal bd = (BigDecimal) literal.getValue(); BigInteger unscaled = bd.unscaledValue(); long longValue = unscaled.longValue(); if (!BigInteger.valueOf(longValue).equals(unscaled)) { // overflow throw newValidationError(literal, RESOURCE.numberLiteralOutOfRange(bd.toString())); } break; case DOUBLE: validateLiteralAsDouble(literal); break; case BINARY: final BitString bitString = (BitString) literal.getValue(); if ((bitString.getBitCount() % 8) != 0) { throw newValidationError(literal, RESOURCE.binaryLiteralOdd()); } break; case DATE: case TIME: case TIMESTAMP: Calendar calendar = literal.getValueAs(Calendar.class); final int year = calendar.get(Calendar.YEAR); final int era = calendar.get(Calendar.ERA); if (year < 1 || era == GregorianCalendar.BC || year > 9999) { throw newValidationError(literal, RESOURCE.dateLiteralOutOfRange(literal.toString())); } break; case INTERVAL_YEAR: case INTERVAL_YEAR_MONTH: case INTERVAL_MONTH: case INTERVAL_DAY: case INTERVAL_DAY_HOUR: case INTERVAL_DAY_MINUTE: case INTERVAL_DAY_SECOND: case INTERVAL_HOUR: case INTERVAL_HOUR_MINUTE: case INTERVAL_HOUR_SECOND: case INTERVAL_MINUTE: case INTERVAL_MINUTE_SECOND: case INTERVAL_SECOND: if (literal instanceof SqlIntervalLiteral) { SqlIntervalLiteral.IntervalValue interval = (SqlIntervalLiteral.IntervalValue) literal.getValue(); SqlIntervalQualifier intervalQualifier = interval.getIntervalQualifier(); // ensure qualifier is good before attempting to validate literal validateIntervalQualifier(intervalQualifier); String intervalStr = interval.getIntervalLiteral(); // throws CalciteContextException if string is invalid int[] values = intervalQualifier.evaluateIntervalLiteral(intervalStr, literal.getParserPosition(), typeFactory.getTypeSystem()); Util.discard(values); } break; default: // default is to do nothing } } private void validateLiteralAsDouble(SqlLiteral literal) { BigDecimal bd = (BigDecimal) literal.getValue(); double d = bd.doubleValue(); if (Double.isInfinite(d) || Double.isNaN(d)) { // overflow throw newValidationError(literal, RESOURCE.numberLiteralOutOfRange(Util.toScientificNotation(bd))); } // REVIEW jvs 4-Aug-2004: what about underflow? } public void validateIntervalQualifier(SqlIntervalQualifier qualifier) { assert qualifier != null; boolean startPrecisionOutOfRange = false; boolean fractionalSecondPrecisionOutOfRange = false; final RelDataTypeSystem typeSystem = typeFactory.getTypeSystem(); final int startPrecision = qualifier.getStartPrecision(typeSystem); final int fracPrecision = qualifier.getFractionalSecondPrecision(typeSystem); final int maxPrecision = typeSystem.getMaxPrecision(qualifier.typeName()); final int minPrecision = qualifier.typeName().getMinPrecision(); final int minScale = qualifier.typeName().getMinScale(); final int maxScale = typeSystem.getMaxScale(qualifier.typeName()); if (qualifier.isYearMonth()) { if (startPrecision < minPrecision || startPrecision > maxPrecision) { startPrecisionOutOfRange = true; } else { if (fracPrecision < minScale || fracPrecision > maxScale) { fractionalSecondPrecisionOutOfRange = true; } } } else { if (startPrecision < minPrecision || startPrecision > maxPrecision) { startPrecisionOutOfRange = true; } else { if (fracPrecision < minScale || fracPrecision > maxScale) { fractionalSecondPrecisionOutOfRange = true; } } } if (startPrecisionOutOfRange) { throw newValidationError(qualifier, RESOURCE.intervalStartPrecisionOutOfRange(startPrecision, "INTERVAL " + qualifier)); } else if (fractionalSecondPrecisionOutOfRange) { throw newValidationError(qualifier, RESOURCE.intervalFractionalSecondPrecisionOutOfRange( fracPrecision, "INTERVAL " + qualifier)); } } /** * Validates the FROM clause of a query, or (recursively) a child node of * the FROM clause: AS, OVER, JOIN, VALUES, or sub-query. * * @param node Node in FROM clause, typically a table or derived * table * @param targetRowType Desired row type of this expression, or * {@link #unknownType} if not fussy. Must not be null. * @param scope Scope */ protected void validateFrom( SqlNode node, RelDataType targetRowType, SqlValidatorScope scope) { Objects.requireNonNull(targetRowType); switch (node.getKind()) { case AS: case TABLE_REF: validateFrom( ((SqlCall) node).operand(0), targetRowType, scope); break; case VALUES: validateValues((SqlCall) node, targetRowType, scope); break; case JOIN: validateJoin((SqlJoin) node, scope); break; case OVER: validateOver((SqlCall) node, scope); break; case UNNEST: validateUnnest((SqlCall) node, scope, targetRowType); break; default: validateQuery(node, scope, targetRowType); break; } // Validate the namespace representation of the node, just in case the // validation did not occur implicitly. getNamespace(node, scope).validate(targetRowType); } protected void validateOver(SqlCall call, SqlValidatorScope scope) { throw new AssertionError("OVER unexpected in this context"); } protected void validateUnnest(SqlCall call, SqlValidatorScope scope, RelDataType targetRowType) { for (int i = 0; i < call.operandCount(); i++) { SqlNode expandedItem = expand(call.operand(i), scope); call.setOperand(i, expandedItem); } validateQuery(call, scope, targetRowType); } private void checkRollUpInUsing(SqlIdentifier identifier, SqlNode leftOrRight, SqlValidatorScope scope) { SqlValidatorNamespace namespace = getNamespace(leftOrRight, scope); if (namespace != null) { SqlValidatorTable sqlValidatorTable = namespace.getTable(); if (sqlValidatorTable != null) { Table table = sqlValidatorTable.unwrap(Table.class); String column = Util.last(identifier.names); if (table.isRolledUp(column)) { throw newValidationError(identifier, RESOURCE.rolledUpNotAllowed(column, "USING")); } } } } protected void validateJoin(SqlJoin join, SqlValidatorScope scope) { SqlNode left = join.getLeft(); SqlNode right = join.getRight(); SqlNode condition = join.getCondition(); boolean natural = join.isNatural(); final JoinType joinType = join.getJoinType(); final JoinConditionType conditionType = join.getConditionType(); final SqlValidatorScope joinScope = scopes.get(join); validateFrom(left, unknownType, joinScope); validateFrom(right, unknownType, joinScope); // Validate condition. switch (conditionType) { case NONE: Preconditions.checkArgument(condition == null); break; case ON: Preconditions.checkArgument(condition != null); SqlNode expandedCondition = expand(condition, joinScope); join.setOperand(5, expandedCondition); condition = join.getCondition(); validateWhereOrOn(joinScope, condition, "ON"); checkRollUp(null, join, condition, joinScope, "ON"); break; case USING: SqlNodeList list = (SqlNodeList) condition; // Parser ensures that using clause is not empty. Preconditions.checkArgument(list.size() > 0, "Empty USING clause"); for (SqlNode node : list) { SqlIdentifier id = (SqlIdentifier) node; final RelDataType leftColType = validateUsingCol(id, left); final RelDataType rightColType = validateUsingCol(id, right); if (!SqlTypeUtil.isComparable(leftColType, rightColType)) { throw newValidationError(id, RESOURCE.naturalOrUsingColumnNotCompatible(id.getSimple(), leftColType.toString(), rightColType.toString())); } checkRollUpInUsing(id, left, scope); checkRollUpInUsing(id, right, scope); } break; default: throw Util.unexpected(conditionType); } // Validate NATURAL. if (natural) { if (condition != null) { throw newValidationError(condition, RESOURCE.naturalDisallowsOnOrUsing()); } // Join on fields that occur exactly once on each side. Ignore // fields that occur more than once on either side. final RelDataType leftRowType = getNamespace(left).getRowType(); final RelDataType rightRowType = getNamespace(right).getRowType(); final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); List naturalColumnNames = SqlValidatorUtil.deriveNaturalJoinColumnList(nameMatcher, leftRowType, rightRowType); // Check com.hazelcast.com.atibility of the chosen columns. for (String name : naturalColumnNames) { final RelDataType leftColType = nameMatcher.field(leftRowType, name).getType(); final RelDataType rightColType = nameMatcher.field(rightRowType, name).getType(); if (!SqlTypeUtil.isComparable(leftColType, rightColType)) { throw newValidationError(join, RESOURCE.naturalOrUsingColumnNotCompatible(name, leftColType.toString(), rightColType.toString())); } } } // Which join types require/allow a ON/USING condition, or allow // a NATURAL keyword? switch (joinType) { case LEFT_SEMI_JOIN: if (!this.config.sqlConformance().isLiberal()) { throw newValidationError(join.getJoinTypeNode(), RESOURCE.dialectDoesNotSupportFeature("LEFT SEMI JOIN")); } // fall through case INNER: case LEFT: case RIGHT: case FULL: if ((condition == null) && !natural) { throw newValidationError(join, RESOURCE.joinRequiresCondition()); } break; case COMMA: case CROSS: if (condition != null) { throw newValidationError(join.getConditionTypeNode(), RESOURCE.crossJoinDisallowsCondition()); } if (natural) { throw newValidationError(join.getConditionTypeNode(), RESOURCE.crossJoinDisallowsCondition()); } break; default: throw Util.unexpected(joinType); } } /** * Throws an error if there is an aggregate or windowed aggregate in the * given clause. * * @param aggFinder Finder for the particular kind(s) of aggregate function * @param node Parse tree * @param clause Name of clause: "WHERE", "GROUP BY", "ON" */ private void validateNoAggs(AggFinder aggFinder, SqlNode node, String clause) { final SqlCall agg = aggFinder.findAgg(node); if (agg == null) { return; } final SqlOperator op = agg.getOperator(); if (op == SqlStdOperatorTable.OVER) { throw newValidationError(agg, RESOURCE.windowedAggregateIllegalInClause(clause)); } else if (op.isGroup() || op.isGroupAuxiliary()) { throw newValidationError(agg, RESOURCE.groupFunctionMustAppearInGroupByClause(op.getName())); } else { throw newValidationError(agg, RESOURCE.aggregateIllegalInClause(clause)); } } private RelDataType validateUsingCol(SqlIdentifier id, SqlNode leftOrRight) { if (id.names.size() == 1) { String name = id.names.get(0); final SqlValidatorNamespace namespace = getNamespace(leftOrRight); final RelDataType rowType = namespace.getRowType(); final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); final RelDataTypeField field = nameMatcher.field(rowType, name); if (field != null) { if (nameMatcher.frequency(rowType.getFieldNames(), name) > 1) { throw newValidationError(id, RESOURCE.columnInUsingNotUnique(id.toString())); } return field.getType(); } } throw newValidationError(id, RESOURCE.columnNotFound(id.toString())); } /** * Validates a SELECT statement. * * @param select Select statement * @param targetRowType Desired row type, must not be null, may be the data * type 'unknown'. */ protected void validateSelect( SqlSelect select, RelDataType targetRowType) { assert targetRowType != null; // Namespace is either a select namespace or a wrapper around one. final SelectNamespace ns = getNamespace(select).unwrap(SelectNamespace.class); // Its rowtype is null, meaning it hasn't been validated yet. // This is important, because we need to take the targetRowType into // account. assert ns.rowType == null; if (select.isDistinct()) { validateFeature(RESOURCE.sQLFeature_E051_01(), select.getModifierNode(SqlSelectKeyword.DISTINCT) .getParserPosition()); } final SqlNodeList selectItems = select.getSelectList(); RelDataType fromType = unknownType; if (selectItems.size() == 1) { final SqlNode selectItem = selectItems.get(0); if (selectItem instanceof SqlIdentifier) { SqlIdentifier id = (SqlIdentifier) selectItem; if (id.isStar() && (id.names.size() == 1)) { // Special case: for INSERT ... VALUES(?,?), the SQL // standard says we're supposed to propagate the target // types down. So iff the select list is an unqualified // star (as it will be after an INSERT ... VALUES has been // expanded), then propagate. fromType = targetRowType; } } } // Make sure that items in FROM clause have distinct aliases. final SelectScope fromScope = (SelectScope) getFromScope(select); List names = fromScope.getChildNames(); if (!catalogReader.nameMatcher().isCaseSensitive()) { names = Lists.transform(names, s -> s.toUpperCase(Locale.ROOT)); } final int duplicateAliasOrdinal = Util.firstDuplicate(names); if (duplicateAliasOrdinal >= 0) { final ScopeChild child = fromScope.children.get(duplicateAliasOrdinal); throw newValidationError(child.namespace.getEnclosingNode(), RESOURCE.fromAliasDuplicate(child.name)); } if (select.getFrom() == null) { if (this.config.sqlConformance().isFromRequired()) { throw newValidationError(select, RESOURCE.selectMissingFrom()); } } else { validateFrom(select.getFrom(), fromType, fromScope); } validateWhereClause(select); validateGroupClause(select); validateHavingClause(select); validateWindowClause(select); handleOffsetFetch(select.getOffset(), select.getFetch()); // Validate the SELECT clause late, because a select item might // depend on the GROUP BY list, or the window function might reference // window name in the WINDOW clause etc. final RelDataType rowType = validateSelectList(selectItems, select, targetRowType); ns.setType(rowType); // Validate ORDER BY after we have set ns.rowType because in some // dialects you can refer to columns of the select list, e.g. // "SELECT empno AS x FROM emp ORDER BY x" validateOrderList(select); if (shouldCheckForRollUp(select.getFrom())) { checkRollUpInSelectList(select); checkRollUp(null, select, select.getWhere(), getWhereScope(select)); checkRollUp(null, select, select.getHaving(), getHavingScope(select)); checkRollUpInWindowDecl(select); checkRollUpInGroupBy(select); checkRollUpInOrderBy(select); } } private void checkRollUpInSelectList(SqlSelect select) { SqlValidatorScope scope = getSelectScope(select); for (SqlNode item : select.getSelectList()) { checkRollUp(null, select, item, scope); } } private void checkRollUpInGroupBy(SqlSelect select) { SqlNodeList group = select.getGroup(); if (group != null) { for (SqlNode node : group) { checkRollUp(null, select, node, getGroupScope(select), "GROUP BY"); } } } private void checkRollUpInOrderBy(SqlSelect select) { SqlNodeList orderList = select.getOrderList(); if (orderList != null) { for (SqlNode node : orderList) { checkRollUp(null, select, node, getOrderScope(select), "ORDER BY"); } } } private void checkRollUpInWindow(SqlWindow window, SqlValidatorScope scope) { if (window != null) { for (SqlNode node : window.getPartitionList()) { checkRollUp(null, window, node, scope, "PARTITION BY"); } for (SqlNode node : window.getOrderList()) { checkRollUp(null, window, node, scope, "ORDER BY"); } } } private void checkRollUpInWindowDecl(SqlSelect select) { for (SqlNode decl : select.getWindowList()) { checkRollUpInWindow((SqlWindow) decl, getSelectScope(select)); } } private SqlNode stripDot(SqlNode node) { if (node != null && node.getKind() == SqlKind.DOT) { return stripDot(((SqlCall) node).operand(0)); } return node; } private void checkRollUp(SqlNode grandParent, SqlNode parent, SqlNode current, SqlValidatorScope scope, String optionalClause) { current = stripAs(current); if (current instanceof SqlCall && !(current instanceof SqlSelect)) { // Validate OVER separately checkRollUpInWindow(getWindowInOver(current), scope); current = stripOver(current); List children = ((SqlCall) stripAs(stripDot(current))).getOperandList(); for (SqlNode child : children) { checkRollUp(parent, current, child, scope, optionalClause); } } else if (current instanceof SqlIdentifier) { SqlIdentifier id = (SqlIdentifier) current; if (!id.isStar() && isRolledUpColumn(id, scope)) { if (!isAggregation(parent.getKind()) || !isRolledUpColumnAllowedInAgg(id, scope, (SqlCall) parent, grandParent)) { String context = optionalClause != null ? optionalClause : parent.getKind().toString(); throw newValidationError(id, RESOURCE.rolledUpNotAllowed(deriveAlias(id, 0), context)); } } } } private void checkRollUp(SqlNode grandParent, SqlNode parent, SqlNode current, SqlValidatorScope scope) { checkRollUp(grandParent, parent, current, scope, null); } private SqlWindow getWindowInOver(SqlNode over) { if (over.getKind() == SqlKind.OVER) { SqlNode window = ((SqlCall) over).getOperandList().get(1); if (window instanceof SqlWindow) { return (SqlWindow) window; } // SqlIdentifier, gets validated elsewhere return null; } return null; } private static SqlNode stripOver(SqlNode node) { switch (node.getKind()) { case OVER: return ((SqlCall) node).getOperandList().get(0); default: return node; } } private Pair findTableColumnPair(SqlIdentifier identifier, SqlValidatorScope scope) { final SqlCall call = makeNullaryCall(identifier); if (call != null) { return null; } SqlQualified qualified = scope.fullyQualify(identifier); List names = qualified.identifier.names; if (names.size() < 2) { return null; } return new Pair<>(names.get(names.size() - 2), Util.last(names)); } // Returns true iff the given column is valid inside the given aggCall. private boolean isRolledUpColumnAllowedInAgg(SqlIdentifier identifier, SqlValidatorScope scope, SqlCall aggCall, SqlNode parent) { Pair pair = findTableColumnPair(identifier, scope); if (pair == null) { return true; } String columnName = pair.right; SqlValidatorTable sqlValidatorTable = scope.fullyQualify(identifier).namespace.getTable(); if (sqlValidatorTable != null) { Table table = sqlValidatorTable.unwrap(Table.class); return table.rolledUpColumnValidInsideAgg(columnName, aggCall, parent, catalogReader.getConfig()); } return true; } // Returns true iff the given column is actually rolled up. private boolean isRolledUpColumn(SqlIdentifier identifier, SqlValidatorScope scope) { Pair pair = findTableColumnPair(identifier, scope); if (pair == null) { return false; } String columnName = pair.right; SqlValidatorTable sqlValidatorTable = scope.fullyQualify(identifier).namespace.getTable(); if (sqlValidatorTable != null) { Table table = sqlValidatorTable.unwrap(Table.class); return table.isRolledUp(columnName); } return false; } private boolean shouldCheckForRollUp(SqlNode from) { if (from != null) { SqlKind kind = stripAs(from).getKind(); return kind != SqlKind.VALUES && kind != SqlKind.SELECT; } return false; } /** Validates that a query can deliver the modality it promises. Only called * on the top-most SELECT or set operator in the tree. */ private void validateModality(SqlNode query) { final SqlModality modality = deduceModality(query); if (query instanceof SqlSelect) { final SqlSelect select = (SqlSelect) query; validateModality(select, modality, true); } else if (query.getKind() == SqlKind.VALUES) { switch (modality) { case STREAM: throw newValidationError(query, Static.RESOURCE.cannotStreamValues()); } } else { assert query.isA(SqlKind.SET_QUERY); final SqlCall call = (SqlCall) query; for (SqlNode operand : call.getOperandList()) { if (deduceModality(operand) != modality) { throw newValidationError(operand, Static.RESOURCE.streamSetOpInconsistentInputs()); } validateModality(operand); } } } /** Return the intended modality of a SELECT or set-op. */ private SqlModality deduceModality(SqlNode query) { if (query instanceof SqlSelect) { SqlSelect select = (SqlSelect) query; return select.getModifierNode(SqlSelectKeyword.STREAM) != null ? SqlModality.STREAM : SqlModality.RELATION; } else if (query.getKind() == SqlKind.VALUES) { return SqlModality.RELATION; } else { assert query.isA(SqlKind.SET_QUERY); final SqlCall call = (SqlCall) query; return deduceModality(call.getOperandList().get(0)); } } public boolean validateModality(SqlSelect select, SqlModality modality, boolean fail) { final SelectScope scope = getRawSelectScope(select); switch (modality) { case STREAM: if (scope.children.size() == 1) { for (ScopeChild child : scope.children) { if (!child.namespace.supportsModality(modality)) { if (fail) { throw newValidationError(child.namespace.getNode(), Static.RESOURCE.cannotConvertToStream(child.name)); } else { return false; } } } } else { int supportsModalityCount = 0; for (ScopeChild child : scope.children) { if (child.namespace.supportsModality(modality)) { ++supportsModalityCount; } } if (supportsModalityCount == 0) { if (fail) { String inputs = String.join(", ", scope.getChildNames()); throw newValidationError(select, Static.RESOURCE.cannotStreamResultsForNonStreamingInputs(inputs)); } else { return false; } } } break; default: for (ScopeChild child : scope.children) { if (!child.namespace.supportsModality(modality)) { if (fail) { throw newValidationError(child.namespace.getNode(), Static.RESOURCE.cannotConvertToRelation(child.name)); } else { return false; } } } } // Make sure that aggregation is possible. final SqlNode aggregateNode = getAggregate(select); if (aggregateNode != null) { switch (modality) { case STREAM: SqlNodeList groupList = select.getGroup(); if (groupList == null || !SqlValidatorUtil.containsMonotonic(scope, groupList)) { if (fail) { throw newValidationError(aggregateNode, Static.RESOURCE.streamMustGroupByMonotonic()); } else { return false; } } } } // Make sure that ORDER BY is possible. final SqlNodeList orderList = select.getOrderList(); if (orderList != null && orderList.size() > 0) { switch (modality) { case STREAM: if (!hasSortedPrefix(scope, orderList)) { if (fail) { throw newValidationError(orderList.get(0), Static.RESOURCE.streamMustOrderByMonotonic()); } else { return false; } } } } return true; } /** Returns whether the prefix is sorted. */ private boolean hasSortedPrefix(SelectScope scope, SqlNodeList orderList) { return isSortCompatible(scope, orderList.get(0), false); } private boolean isSortCompatible(SelectScope scope, SqlNode node, boolean descending) { switch (node.getKind()) { case DESCENDING: return isSortCompatible(scope, ((SqlCall) node).getOperandList().get(0), true); } final SqlMonotonicity monotonicity = scope.getMonotonicity(node); switch (monotonicity) { case INCREASING: case STRICTLY_INCREASING: return !descending; case DECREASING: case STRICTLY_DECREASING: return descending; default: return false; } } protected void validateWindowClause(SqlSelect select) { final SqlNodeList windowList = select.getWindowList(); @SuppressWarnings("unchecked") final List windows = (List) windowList.getList(); if (windows.isEmpty()) { return; } final SelectScope windowScope = (SelectScope) getFromScope(select); assert windowScope != null; // 1. ensure window names are simple // 2. ensure they are unique within this scope for (SqlWindow window : windows) { SqlIdentifier declName = window.getDeclName(); if (!declName.isSimple()) { throw newValidationError(declName, RESOURCE.windowNameMustBeSimple()); } if (windowScope.existingWindowName(declName.toString())) { throw newValidationError(declName, RESOURCE.duplicateWindowName()); } else { windowScope.addWindowName(declName.toString()); } } // 7.10 rule 2 // Check for pairs of windows which are equivalent. for (int i = 0; i < windows.size(); i++) { SqlNode window1 = windows.get(i); for (int j = i + 1; j < windows.size(); j++) { SqlNode window2 = windows.get(j); if (window1.equalsDeep(window2, Litmus.IGNORE)) { throw newValidationError(window2, RESOURCE.dupWindowSpec()); } } } for (SqlWindow window : windows) { final SqlNodeList expandedOrderList = (SqlNodeList) expand(window.getOrderList(), windowScope); window.setOrderList(expandedOrderList); expandedOrderList.validate(this, windowScope); final SqlNodeList expandedPartitionList = (SqlNodeList) expand(window.getPartitionList(), windowScope); window.setPartitionList(expandedPartitionList); expandedPartitionList.validate(this, windowScope); } // Hand off to validate window spec com.hazelcast.com.onents windowList.validate(this, windowScope); } public void validateWith(SqlWith with, SqlValidatorScope scope) { final SqlValidatorNamespace namespace = getNamespace(with); validateNamespace(namespace, unknownType); } public void validateWithItem(SqlWithItem withItem) { if (withItem.columnList != null) { final RelDataType rowType = getValidatedNodeType(withItem.query); final int fieldCount = rowType.getFieldCount(); if (withItem.columnList.size() != fieldCount) { throw newValidationError(withItem.columnList, RESOURCE.columnCountMismatch()); } SqlValidatorUtil.checkIdentifierListForDuplicates( withItem.columnList.getList(), validationErrorFunction); } else { // Luckily, field names have not been make unique yet. final List fieldNames = getValidatedNodeType(withItem.query).getFieldNames(); final int i = Util.firstDuplicate(fieldNames); if (i >= 0) { throw newValidationError(withItem.query, RESOURCE.duplicateColumnAndNoColumnList(fieldNames.get(i))); } } } public void validateSequenceValue(SqlValidatorScope scope, SqlIdentifier id) { // Resolve identifier as a table. final SqlValidatorScope.ResolvedImpl resolved = new SqlValidatorScope.ResolvedImpl(); scope.resolveTable(id.names, catalogReader.nameMatcher(), SqlValidatorScope.Path.EMPTY, resolved); if (resolved.count() != 1) { throw newValidationError(id, RESOURCE.tableNameNotFound(id.toString())); } // We've found a table. But is it a sequence? final SqlValidatorNamespace ns = resolved.only().namespace; if (ns instanceof TableNamespace) { final Table table = ns.getTable().unwrap(Table.class); switch (table.getJdbcTableType()) { case SEQUENCE: case TEMPORARY_SEQUENCE: return; } } throw newValidationError(id, RESOURCE.notASequence(id.toString())); } public SqlValidatorScope getWithScope(SqlNode withItem) { assert withItem.getKind() == SqlKind.WITH_ITEM; return scopes.get(withItem); } public SqlValidator setLenientOperatorLookup(boolean lenient) { this.config = this.config.withLenientOperatorLookup(lenient); return this; } public boolean isLenientOperatorLookup() { return this.config.lenientOperatorLookup(); } public SqlValidator setEnableTypeCoercion(boolean enabled) { this.config = this.config.withTypeCoercionEnabled(enabled); return this; } public boolean isTypeCoercionEnabled() { return this.config.typeCoercionEnabled(); } public void setTypeCoercion(TypeCoercion typeCoercion) { Objects.requireNonNull(typeCoercion); this.typeCoercion = typeCoercion; } public TypeCoercion getTypeCoercion() { assert config.typeCoercionEnabled(); return this.typeCoercion; } public void setSqlTypeCoercionRules(SqlTypeCoercionRule typeCoercionRules) { SqlTypeCoercionRule.THREAD_PROVIDERS.set(typeCoercionRules); } public Config config() { return this.config; } public SqlValidator transform(UnaryOperator transform) { this.config = transform.apply(this.config); return this; } /** * Validates the ORDER BY clause of a SELECT statement. * * @param select Select statement */ protected void validateOrderList(SqlSelect select) { // ORDER BY is validated in a scope where aliases in the SELECT clause // are visible. For example, "SELECT empno AS x FROM emp ORDER BY x" // is valid. SqlNodeList orderList = select.getOrderList(); if (orderList == null) { return; } if (!shouldAllowIntermediateOrderBy()) { if (!cursorSet.contains(select)) { throw newValidationError(select, RESOURCE.invalidOrderByPos()); } } final SqlValidatorScope orderScope = getOrderScope(select); Objects.requireNonNull(orderScope); List expandList = new ArrayList<>(); for (SqlNode orderItem : orderList) { SqlNode expandedOrderItem = expand(orderItem, orderScope); expandList.add(expandedOrderItem); } SqlNodeList expandedOrderList = new SqlNodeList( expandList, orderList.getParserPosition()); select.setOrderBy(expandedOrderList); for (SqlNode orderItem : expandedOrderList) { validateOrderItem(select, orderItem); } } /** * Validates an item in the GROUP BY clause of a SELECT statement. * * @param select Select statement * @param groupByItem GROUP BY clause item */ private void validateGroupByItem(SqlSelect select, SqlNode groupByItem) { final SqlValidatorScope groupByScope = getGroupScope(select); groupByScope.validateExpr(groupByItem); } /** * Validates an item in the ORDER BY clause of a SELECT statement. * * @param select Select statement * @param orderItem ORDER BY clause item */ private void validateOrderItem(SqlSelect select, SqlNode orderItem) { switch (orderItem.getKind()) { case DESCENDING: validateFeature(RESOURCE.sQLConformance_OrderByDesc(), orderItem.getParserPosition()); validateOrderItem(select, ((SqlCall) orderItem).operand(0)); return; } final SqlValidatorScope orderScope = getOrderScope(select); validateExpr(orderItem, orderScope); } public SqlNode expandOrderExpr(SqlSelect select, SqlNode orderExpr) { final SqlNode newSqlNode = new OrderExpressionExpander(select, orderExpr).go(); if (newSqlNode != orderExpr) { final SqlValidatorScope scope = getOrderScope(select); inferUnknownTypes(unknownType, scope, newSqlNode); final RelDataType type = deriveType(scope, newSqlNode); setValidatedNodeType(newSqlNode, type); } return newSqlNode; } /** * Validates the GROUP BY clause of a SELECT statement. This method is * called even if no GROUP BY clause is present. */ protected void validateGroupClause(SqlSelect select) { SqlNodeList groupList = select.getGroup(); if (groupList == null) { return; } final String clause = "GROUP BY"; validateNoAggs(aggOrOverFinder, groupList, clause); final SqlValidatorScope groupScope = getGroupScope(select); inferUnknownTypes(unknownType, groupScope, groupList); // expand the expression in group list. List expandedList = new ArrayList<>(); for (SqlNode groupItem : groupList) { SqlNode expandedItem = expandGroupByOrHavingExpr(groupItem, groupScope, select, false); expandedList.add(expandedItem); } groupList = new SqlNodeList(expandedList, groupList.getParserPosition()); select.setGroupBy(groupList); for (SqlNode groupItem : expandedList) { validateGroupByItem(select, groupItem); } // Nodes in the GROUP BY clause are expressions except if they are calls // to the GROUPING SETS, ROLLUP or CUBE operators; this operators are not // expressions, because they do not have a type. for (SqlNode node : groupList) { switch (node.getKind()) { case GROUPING_SETS: case ROLLUP: case CUBE: node.validate(this, groupScope); break; default: node.validateExpr(this, groupScope); } } // Derive the type of each GROUP BY item. We don't need the type, but // it resolves functions, and that is necessary for deducing // monotonicity. final SqlValidatorScope selectScope = getSelectScope(select); AggregatingSelectScope aggregatingScope = null; if (selectScope instanceof AggregatingSelectScope) { aggregatingScope = (AggregatingSelectScope) selectScope; } for (SqlNode groupItem : groupList) { if (groupItem instanceof SqlNodeList && ((SqlNodeList) groupItem).size() == 0) { continue; } validateGroupItem(groupScope, aggregatingScope, groupItem); } SqlNode agg = aggFinder.findAgg(groupList); if (agg != null) { throw newValidationError(agg, RESOURCE.aggregateIllegalInClause(clause)); } } private void validateGroupItem(SqlValidatorScope groupScope, AggregatingSelectScope aggregatingScope, SqlNode groupItem) { switch (groupItem.getKind()) { case GROUPING_SETS: case ROLLUP: case CUBE: validateGroupingSets(groupScope, aggregatingScope, (SqlCall) groupItem); break; default: if (groupItem instanceof SqlNodeList) { break; } final RelDataType type = deriveType(groupScope, groupItem); setValidatedNodeType(groupItem, type); } } private void validateGroupingSets(SqlValidatorScope groupScope, AggregatingSelectScope aggregatingScope, SqlCall groupItem) { for (SqlNode node : groupItem.getOperandList()) { validateGroupItem(groupScope, aggregatingScope, node); } } protected void validateWhereClause(SqlSelect select) { // validate WHERE clause final SqlNode where = select.getWhere(); if (where == null) { return; } final SqlValidatorScope whereScope = getWhereScope(select); final SqlNode expandedWhere = expand(where, whereScope); select.setWhere(expandedWhere); validateWhereOrOn(whereScope, expandedWhere, "WHERE"); } protected void validateWhereOrOn( SqlValidatorScope scope, SqlNode condition, String clause) { validateNoAggs(aggOrOverOrGroupFinder, condition, clause); inferUnknownTypes( booleanType, scope, condition); condition.validate(this, scope); final RelDataType type = deriveType(scope, condition); if (!SqlTypeUtil.inBooleanFamily(type)) { throw newValidationError(condition, RESOURCE.condMustBeBoolean(clause)); } } protected void validateHavingClause(SqlSelect select) { // HAVING is validated in the scope after groups have been created. // For example, in "SELECT empno FROM emp WHERE empno = 10 GROUP BY // deptno HAVING empno = 10", the reference to 'empno' in the HAVING // clause is illegal. SqlNode having = select.getHaving(); if (having == null) { return; } final AggregatingScope havingScope = (AggregatingScope) getSelectScope(select); if (config.sqlConformance().isHavingAlias()) { SqlNode newExpr = expandGroupByOrHavingExpr(having, havingScope, select, true); if (having != newExpr) { having = newExpr; select.setHaving(newExpr); } } havingScope.checkAggregateExpr(having, true); inferUnknownTypes( booleanType, havingScope, having); having.validate(this, havingScope); final RelDataType type = deriveType(havingScope, having); if (!SqlTypeUtil.inBooleanFamily(type)) { throw newValidationError(having, RESOURCE.havingMustBeBoolean()); } } protected RelDataType validateSelectList( final SqlNodeList selectItems, SqlSelect select, RelDataType targetRowType) { // First pass, ensure that aliases are unique. "*" and "TABLE.*" items // are ignored. // Validate SELECT list. Expand terms of the form "*" or "TABLE.*". final SqlValidatorScope selectScope = getSelectScope(select); final List expandedSelectItems = new ArrayList<>(); final Set aliases = new HashSet<>(); final List> fieldList = new ArrayList<>(); for (SqlNode selectItem : selectItems) { if (selectItem instanceof SqlSelect) { handleScalarSubQuery( select, (SqlSelect) selectItem, expandedSelectItems, aliases, fieldList); } else { // Use the field list size to record the field index // because the select item may be a STAR(*), which could have been expanded. final int fieldIdx = fieldList.size(); final RelDataType fieldType = targetRowType.isStruct() && targetRowType.getFieldCount() > fieldIdx ? targetRowType.getFieldList().get(fieldIdx).getType() : unknownType; expandSelectItem( selectItem, select, fieldType, expandedSelectItems, aliases, fieldList, false); } } // Create the new select list with expanded items. Pass through // the original parser position so that any overall failures can // still reference the original input text. SqlNodeList newSelectList = new SqlNodeList( expandedSelectItems, selectItems.getParserPosition()); if (config.identifierExpansion()) { select.setSelectList(newSelectList); } getRawSelectScope(select).setExpandedSelectList(expandedSelectItems); // TODO: when SELECT appears as a value sub-query, should be using // something other than unknownType for targetRowType inferUnknownTypes(targetRowType, selectScope, newSelectList); for (SqlNode selectItem : expandedSelectItems) { validateNoAggs(groupFinder, selectItem, "SELECT"); validateExpr(selectItem, selectScope); } return typeFactory.createStructType(fieldList); } /** * Validates an expression. * * @param expr Expression * @param scope Scope in which expression occurs */ private void validateExpr(SqlNode expr, SqlValidatorScope scope) { if (expr instanceof SqlCall) { final SqlOperator op = ((SqlCall) expr).getOperator(); if (op.isAggregator() && op.requiresOver()) { throw newValidationError(expr, RESOURCE.absentOverClause()); } } // Call on the expression to validate itself. expr.validateExpr(this, scope); // Perform any validation specific to the scope. For example, an // aggregating scope requires that expressions are valid aggregations. scope.validateExpr(expr); } /** * Processes SubQuery found in Select list. Checks that is actually Scalar * sub-query and makes proper entries in each of the 3 lists used to create * the final rowType entry. * * @param parentSelect base SqlSelect item * @param selectItem child SqlSelect from select list * @param expandedSelectItems Select items after processing * @param aliasList built from user or system values * @param fieldList Built up entries for each select list entry */ private void handleScalarSubQuery( SqlSelect parentSelect, SqlSelect selectItem, List expandedSelectItems, Set aliasList, List> fieldList) { // A scalar sub-query only has one output column. if (1 != selectItem.getSelectList().size()) { throw newValidationError(selectItem, RESOURCE.onlyScalarSubQueryAllowed()); } // No expansion in this routine just append to list. expandedSelectItems.add(selectItem); // Get or generate alias and add to list. final String alias = deriveAlias( selectItem, aliasList.size()); aliasList.add(alias); final SelectScope scope = (SelectScope) getWhereScope(parentSelect); final RelDataType type = deriveType(scope, selectItem); setValidatedNodeType(selectItem, type); // We do not want to pass on the RelRecordType returned // by the sub-query. Just the type of the single expression // in the sub-query select list. assert type instanceof RelRecordType; RelRecordType rec = (RelRecordType) type; RelDataType nodeType = rec.getFieldList().get(0).getType(); nodeType = typeFactory.createTypeWithNullability(nodeType, true); fieldList.add(Pair.of(alias, nodeType)); } /** * Derives a row-type for INSERT and UPDATE operations. * * @param table Target table for INSERT/UPDATE * @param targetColumnList List of target columns, or null if not specified * @param append Whether to append fields to those in * baseRowType * @return Rowtype */ protected RelDataType createTargetRowType( SqlValidatorTable table, SqlNodeList targetColumnList, boolean append) { RelDataType baseRowType = table.getRowType(); if (targetColumnList == null) { return baseRowType; } List targetFields = baseRowType.getFieldList(); final List> fields = new ArrayList<>(); if (append) { for (RelDataTypeField targetField : targetFields) { fields.add( Pair.of(SqlUtil.deriveAliasFromOrdinal(fields.size()), targetField.getType())); } } final Set assignedFields = new HashSet<>(); final RelOptTable relOptTable = table instanceof RelOptTable ? ((RelOptTable) table) : null; for (SqlNode node : targetColumnList) { SqlIdentifier id = (SqlIdentifier) node; RelDataTypeField targetField = SqlValidatorUtil.getTargetField( baseRowType, typeFactory, id, catalogReader, relOptTable); if (targetField == null) { throw newValidationError(id, RESOURCE.unknownTargetColumn(id.toString())); } if (!assignedFields.add(targetField.getIndex())) { throw newValidationError(id, RESOURCE.duplicateTargetColumn(targetField.getName())); } fields.add(targetField); } return typeFactory.createStructType(fields); } public void validateInsert(SqlInsert insert) { final SqlValidatorNamespace targetNamespace = getNamespace(insert); validateNamespace(targetNamespace, unknownType); final RelOptTable relOptTable = SqlValidatorUtil.getRelOptTable( targetNamespace, catalogReader.unwrap(Prepare.CatalogReader.class), null, null); final SqlValidatorTable table = relOptTable == null ? targetNamespace.getTable() : relOptTable.unwrap(SqlValidatorTable.class); // INSERT has an optional column name list. If present then // reduce the rowtype to the columns specified. If not present // then the entire target rowtype is used. final RelDataType targetRowType = createTargetRowType( table, insert.getTargetColumnList(), false); final SqlNode source = insert.getSource(); if (source instanceof SqlSelect) { final SqlSelect sqlSelect = (SqlSelect) source; validateSelect(sqlSelect, targetRowType); } else { final SqlValidatorScope scope = scopes.get(source); validateQuery(source, scope, targetRowType); } // REVIEW jvs 4-Dec-2008: In FRG-365, this namespace row type is // discarding the type inferred by inferUnknownTypes (which was invoked // from validateSelect above). It would be better if that information // were used here so that we never saw any untyped nulls during // checkTypeAssignment. final RelDataType sourceRowType = getNamespace(source).getRowType(); final RelDataType logicalTargetRowType = getLogicalTargetRowType(targetRowType, insert); setValidatedNodeType(insert, logicalTargetRowType); final RelDataType logicalSourceRowType = getLogicalSourceRowType(sourceRowType, insert); final List strategies = table.unwrap(RelOptTable.class).getColumnStrategies(); final RelDataType realTargetRowType = typeFactory.createStructType( logicalTargetRowType.getFieldList() .stream().filter(f -> strategies.get(f.getIndex()).canInsertInto()) .collect(Collectors.toList())); final RelDataType targetRowTypeToValidate = logicalSourceRowType.getFieldCount() == logicalTargetRowType.getFieldCount() ? logicalTargetRowType : realTargetRowType; checkFieldCount(insert.getTargetTable(), table, strategies, targetRowTypeToValidate, realTargetRowType, source, logicalSourceRowType, logicalTargetRowType); checkTypeAssignment(scopes.get(source), table, logicalSourceRowType, targetRowTypeToValidate, insert); checkConstraint(table, source, logicalTargetRowType); validateAccess(insert.getTargetTable(), table, SqlAccessEnum.INSERT); // Refresh the insert row type to keep sync with source. setValidatedNodeType(insert, targetRowTypeToValidate); } /** * Validates insert values against the constraint of a modifiable view. * * @param validatorTable Table that may wrap a ModifiableViewTable * @param source The values being inserted * @param targetRowType The target type for the view */ private void checkConstraint( SqlValidatorTable validatorTable, SqlNode source, RelDataType targetRowType) { final ModifiableViewTable modifiableViewTable = validatorTable.unwrap(ModifiableViewTable.class); if (modifiableViewTable != null && source instanceof SqlCall) { final Table table = modifiableViewTable.unwrap(Table.class); final RelDataType tableRowType = table.getRowType(typeFactory); final List tableFields = tableRowType.getFieldList(); // Get the mapping from column indexes of the underlying table // to the target columns and view constraints. final Map tableIndexToTargetField = SqlValidatorUtil.getIndexToFieldMap(tableFields, targetRowType); final Map projectMap = RelOptUtil.getColumnConstraints(modifiableViewTable, targetRowType, typeFactory); // Determine columns (indexed to the underlying table) that need // to be validated against the view constraint. final ImmutableBitSet targetColumns = ImmutableBitSet.of(tableIndexToTargetField.keySet()); final ImmutableBitSet constrainedColumns = ImmutableBitSet.of(projectMap.keySet()); final ImmutableBitSet constrainedTargetColumns = targetColumns.intersect(constrainedColumns); // Validate insert values against the view constraint. final List values = ((SqlCall) source).getOperandList(); for (final int colIndex : constrainedTargetColumns.asList()) { final String colName = tableFields.get(colIndex).getName(); final RelDataTypeField targetField = tableIndexToTargetField.get(colIndex); for (SqlNode row : values) { final SqlCall call = (SqlCall) row; final SqlNode sourceValue = call.operand(targetField.getIndex()); final ValidationError validationError = new ValidationError(sourceValue, RESOURCE.viewConstraintNotSatisfied(colName, Util.last(validatorTable.getQualifiedName()))); RelOptUtil.validateValueAgainstConstraint(sourceValue, projectMap.get(colIndex), validationError); } } } } /** * Validates updates against the constraint of a modifiable view. * * @param validatorTable A {@link SqlValidatorTable} that may wrap a * ModifiableViewTable * @param update The UPDATE parse tree node * @param targetRowType The target type */ private void checkConstraint( SqlValidatorTable validatorTable, SqlUpdate update, RelDataType targetRowType) { final ModifiableViewTable modifiableViewTable = validatorTable.unwrap(ModifiableViewTable.class); if (modifiableViewTable != null) { final Table table = modifiableViewTable.unwrap(Table.class); final RelDataType tableRowType = table.getRowType(typeFactory); final Map projectMap = RelOptUtil.getColumnConstraints(modifiableViewTable, targetRowType, typeFactory); final Map nameToIndex = SqlValidatorUtil.mapNameToIndex(tableRowType.getFieldList()); // Validate update values against the view constraint. final List targets = update.getTargetColumnList().getList(); final List sources = update.getSourceExpressionList().getList(); for (final Pair column : Pair.zip(targets, sources)) { final String columnName = ((SqlIdentifier) column.left).getSimple(); final Integer columnIndex = nameToIndex.get(columnName); if (projectMap.containsKey(columnIndex)) { final RexNode columnConstraint = projectMap.get(columnIndex); final ValidationError validationError = new ValidationError(column.right, RESOURCE.viewConstraintNotSatisfied(columnName, Util.last(validatorTable.getQualifiedName()))); RelOptUtil.validateValueAgainstConstraint(column.right, columnConstraint, validationError); } } } } /** * Check the field count of sql insert source and target node row type. * * @param node target table sql identifier * @param table target table * @param strategies column strategies of target table * @param targetRowTypeToValidate row type to validate mainly for column strategies * @param realTargetRowType target table row type exclusive virtual columns * @param source source node * @param logicalSourceRowType source node row type * @param logicalTargetRowType logical target row type, contains only target columns if * they are specified or if the sql dialect allows subset insert, * make a subset of fields(start from the left first field) whose * length is equals with the source row type fields number */ private void checkFieldCount(SqlNode node, SqlValidatorTable table, List strategies, RelDataType targetRowTypeToValidate, RelDataType realTargetRowType, SqlNode source, RelDataType logicalSourceRowType, RelDataType logicalTargetRowType) { final int sourceFieldCount = logicalSourceRowType.getFieldCount(); final int targetFieldCount = logicalTargetRowType.getFieldCount(); final int targetRealFieldCount = realTargetRowType.getFieldCount(); if (sourceFieldCount != targetFieldCount && sourceFieldCount != targetRealFieldCount) { // Allows the source row fields count to be equal with either // the logical or the real(excludes columns that can not insert into) // target row fields count. throw newValidationError(node, RESOURCE.unmatchInsertColumn(targetFieldCount, sourceFieldCount)); } // Ensure that non-nullable fields are targeted. for (final RelDataTypeField field : table.getRowType().getFieldList()) { final RelDataTypeField targetField = targetRowTypeToValidate.getField(field.getName(), true, false); switch (strategies.get(field.getIndex())) { case NOT_NULLABLE: assert !field.getType().isNullable(); if (targetField == null) { throw newValidationError(node, RESOURCE.columnNotNullable(field.getName())); } break; case NULLABLE: assert field.getType().isNullable(); break; case VIRTUAL: case STORED: if (targetField != null && !isValuesWithDefault(source, targetField.getIndex())) { throw newValidationError(node, RESOURCE.insertIntoAlwaysGenerated(field.getName())); } } } } /** Returns whether a query uses {@code DEFAULT} to populate a given * column. */ private boolean isValuesWithDefault(SqlNode source, int column) { switch (source.getKind()) { case VALUES: for (SqlNode operand : ((SqlCall) source).getOperandList()) { if (!isRowWithDefault(operand, column)) { return false; } } return true; } return false; } private boolean isRowWithDefault(SqlNode operand, int column) { switch (operand.getKind()) { case ROW: final SqlCall row = (SqlCall) operand; return row.getOperandList().size() >= column && row.getOperandList().get(column).getKind() == SqlKind.DEFAULT; } return false; } protected RelDataType getLogicalTargetRowType( RelDataType targetRowType, SqlInsert insert) { if (insert.getTargetColumnList() == null && this.config.sqlConformance().isInsertSubsetColumnsAllowed()) { // Target an implicit subset of columns. final SqlNode source = insert.getSource(); final RelDataType sourceRowType = getNamespace(source).getRowType(); final RelDataType logicalSourceRowType = getLogicalSourceRowType(sourceRowType, insert); final RelDataType implicitTargetRowType = typeFactory.createStructType( targetRowType.getFieldList() .subList(0, logicalSourceRowType.getFieldCount())); final SqlValidatorNamespace targetNamespace = getNamespace(insert); validateNamespace(targetNamespace, implicitTargetRowType); return implicitTargetRowType; } else { // Either the set of columns are explicitly targeted, or target the full // set of columns. return targetRowType; } } protected RelDataType getLogicalSourceRowType( RelDataType sourceRowType, SqlInsert insert) { return sourceRowType; } /** * Checks the type assignment of an INSERT or UPDATE query. * *

Skip the virtual columns(can not insert into) type assignment * check if the source fields count equals with * the real target table fields count, see how #checkFieldCount was used. * * @param sourceScope Scope of query source which is used to infer node type * @param table Target table * @param sourceRowType Source row type * @param targetRowType Target row type, it should either contain all the virtual columns * (can not insert into) or exclude all the virtual columns * @param query The query */ protected void checkTypeAssignment( SqlValidatorScope sourceScope, SqlValidatorTable table, RelDataType sourceRowType, RelDataType targetRowType, final SqlNode query) { // NOTE jvs 23-Feb-2006: subclasses may allow for extra targets // representing system-maintained columns, so stop after all sources // matched boolean isUpdateModifiableViewTable = false; if (query instanceof SqlUpdate) { final SqlNodeList targetColumnList = ((SqlUpdate) query).getTargetColumnList(); if (targetColumnList != null) { final int targetColumnCnt = targetColumnList.size(); targetRowType = SqlTypeUtil.extractLastNFields(typeFactory, targetRowType, targetColumnCnt); sourceRowType = SqlTypeUtil.extractLastNFields(typeFactory, sourceRowType, targetColumnCnt); } isUpdateModifiableViewTable = table.unwrap(ModifiableViewTable.class) != null; } if (SqlTypeUtil.equalAsStructSansNullability(typeFactory, sourceRowType, targetRowType, null)) { // Returns early if source and target row type equals sans nullability. return; } if (config.typeCoercionEnabled() && !isUpdateModifiableViewTable) { // Try type coercion first if implicit type coercion is allowed. boolean coerced = typeCoercion.querySourceCoercion(sourceScope, sourceRowType, targetRowType, query); if (coerced) { return; } } // Fall back to default behavior: com.hazelcast.com.are the type families. List sourceFields = sourceRowType.getFieldList(); List targetFields = targetRowType.getFieldList(); final int sourceCount = sourceFields.size(); for (int i = 0; i < sourceCount; ++i) { RelDataType sourceType = sourceFields.get(i).getType(); RelDataType targetType = targetFields.get(i).getType(); if (!SqlTypeUtil.canAssignFrom(targetType, sourceType)) { SqlNode node = getNthExpr(query, i, sourceCount); if (node instanceof SqlDynamicParam) { continue; } String targetTypeString; String sourceTypeString; if (SqlTypeUtil.areCharacterSetsMismatched( sourceType, targetType)) { sourceTypeString = sourceType.getFullTypeString(); targetTypeString = targetType.getFullTypeString(); } else { sourceTypeString = sourceType.toString(); targetTypeString = targetType.toString(); } throw newValidationError(node, RESOURCE.typeNotAssignable( targetFields.get(i).getName(), targetTypeString, sourceFields.get(i).getName(), sourceTypeString)); } } } /** * Locates the n'th expression in an INSERT or UPDATE query. * * @param query Query * @param ordinal Ordinal of expression * @param sourceCount Number of expressions * @return Ordinal'th expression, never null */ private SqlNode getNthExpr(SqlNode query, int ordinal, int sourceCount) { if (query instanceof SqlInsert) { SqlInsert insert = (SqlInsert) query; if (insert.getTargetColumnList() != null) { return insert.getTargetColumnList().get(ordinal); } else { return getNthExpr( insert.getSource(), ordinal, sourceCount); } } else if (query instanceof SqlUpdate) { SqlUpdate update = (SqlUpdate) query; if (update.getSourceExpressionList() != null) { return update.getSourceExpressionList().get(ordinal); } else { return getNthExpr( update.getSourceSelect(), ordinal, sourceCount); } } else if (query instanceof SqlSelect) { SqlSelect select = (SqlSelect) query; if (select.getSelectList().size() == sourceCount) { return select.getSelectList().get(ordinal); } else { return query; // give up } } else { return query; // give up } } public void validateDelete(SqlDelete call) { final SqlSelect sqlSelect = call.getSourceSelect(); validateSelect(sqlSelect, unknownType); final SqlValidatorNamespace targetNamespace = getNamespace(call); validateNamespace(targetNamespace, unknownType); final SqlValidatorTable table = targetNamespace.getTable(); validateAccess(call.getTargetTable(), table, SqlAccessEnum.DELETE); } public void validateUpdate(SqlUpdate call) { final SqlValidatorNamespace targetNamespace = getNamespace(call); validateNamespace(targetNamespace, unknownType); final RelOptTable relOptTable = SqlValidatorUtil.getRelOptTable( targetNamespace, catalogReader.unwrap(Prepare.CatalogReader.class), null, null); final SqlValidatorTable table = relOptTable == null ? targetNamespace.getTable() : relOptTable.unwrap(SqlValidatorTable.class); final RelDataType targetRowType = createTargetRowType( table, call.getTargetColumnList(), true); final SqlSelect select = call.getSourceSelect(); validateSelect(select, targetRowType); final RelDataType sourceRowType = getValidatedNodeType(select); checkTypeAssignment(scopes.get(select), table, sourceRowType, targetRowType, call); checkConstraint(table, call, targetRowType); validateAccess(call.getTargetTable(), table, SqlAccessEnum.UPDATE); } public void validateMerge(SqlMerge call) { SqlSelect sqlSelect = call.getSourceSelect(); // REVIEW zfong 5/25/06 - Does an actual type have to be passed into // validateSelect()? // REVIEW jvs 6-June-2006: In general, passing unknownType like // this means we won't be able to correctly infer the types // for dynamic parameter markers (SET x = ?). But // maybe validateUpdate and validateInsert below will do // the job? // REVIEW ksecretan 15-July-2011: They didn't get a chance to // since validateSelect() would bail. // Let's use the update/insert targetRowType when available. IdentifierNamespace targetNamespace = (IdentifierNamespace) getNamespace(call.getTargetTable()); validateNamespace(targetNamespace, unknownType); SqlValidatorTable table = targetNamespace.getTable(); validateAccess(call.getTargetTable(), table, SqlAccessEnum.UPDATE); RelDataType targetRowType = unknownType; if (call.getUpdateCall() != null) { targetRowType = createTargetRowType( table, call.getUpdateCall().getTargetColumnList(), true); } if (call.getInsertCall() != null) { targetRowType = createTargetRowType( table, call.getInsertCall().getTargetColumnList(), false); } validateSelect(sqlSelect, targetRowType); if (call.getUpdateCall() != null) { validateUpdate(call.getUpdateCall()); } if (call.getInsertCall() != null) { validateInsert(call.getInsertCall()); } } /** * Validates access to a table. * * @param table Table * @param requiredAccess Access requested on table */ private void validateAccess( SqlNode node, SqlValidatorTable table, SqlAccessEnum requiredAccess) { if (table != null) { SqlAccessType access = table.getAllowedAccess(); if (!access.allowsAccess(requiredAccess)) { throw newValidationError(node, RESOURCE.accessNotAllowed(requiredAccess.name(), table.getQualifiedName().toString())); } } } /** * Validates snapshot to a table. * * @param node The node to validate * @param scope Validator scope to derive type * @param ns The namespace to lookup table */ private void validateSnapshot( SqlNode node, SqlValidatorScope scope, SqlValidatorNamespace ns) { if (node.getKind() == SqlKind.SNAPSHOT) { SqlSnapshot snapshot = (SqlSnapshot) node; SqlNode period = snapshot.getPeriod(); RelDataType dataType = deriveType(scope, period); if (dataType.getSqlTypeName() != SqlTypeName.TIMESTAMP) { throw newValidationError(period, Static.RESOURCE.illegalExpressionForTemporal(dataType.getSqlTypeName().getName())); } if (!ns.getTable().isTemporal()) { List qualifiedName = ns.getTable().getQualifiedName(); String tableName = qualifiedName.get(qualifiedName.size() - 1); throw newValidationError(snapshot.getTableRef(), Static.RESOURCE.notTemporalTable(tableName)); } } } /** * Validates a VALUES clause. * * @param node Values clause * @param targetRowType Row type which expression must conform to * @param scope Scope within which clause occurs */ protected void validateValues( SqlCall node, RelDataType targetRowType, final SqlValidatorScope scope) { assert node.getKind() == SqlKind.VALUES; final List operands = node.getOperandList(); for (SqlNode operand : operands) { if (!(operand.getKind() == SqlKind.ROW)) { throw Util.needToImplement( "Values function where operands are scalars"); } SqlCall rowConstructor = (SqlCall) operand; if (this.config.sqlConformance().isInsertSubsetColumnsAllowed() && targetRowType.isStruct() && rowConstructor.operandCount() < targetRowType.getFieldCount()) { targetRowType = typeFactory.createStructType( targetRowType.getFieldList() .subList(0, rowConstructor.operandCount())); } else if (targetRowType.isStruct() && rowConstructor.operandCount() != targetRowType.getFieldCount()) { return; } inferUnknownTypes( targetRowType, scope, rowConstructor); if (targetRowType.isStruct()) { for (Pair pair : Pair.zip(rowConstructor.getOperandList(), targetRowType.getFieldList())) { if (!pair.right.getType().isNullable() && SqlUtil.isNullLiteral(pair.left, false)) { throw newValidationError(node, RESOURCE.columnNotNullable(pair.right.getName())); } } } } for (SqlNode operand : operands) { operand.validate(this, scope); } // validate that all row types have the same number of columns // and that expressions in each column are com.hazelcast.com.atible. // A values expression is turned into something that looks like // ROW(type00, type01,...), ROW(type11,...),... final int rowCount = operands.size(); if (rowCount >= 2) { SqlCall firstRow = (SqlCall) operands.get(0); final int columnCount = firstRow.operandCount(); // 1. check that all rows have the same cols length for (SqlNode operand : operands) { SqlCall thisRow = (SqlCall) operand; if (columnCount != thisRow.operandCount()) { throw newValidationError(node, RESOURCE.incompatibleValueType( SqlStdOperatorTable.VALUES.getName())); } } // 2. check if types at i:th position in each row are com.hazelcast.com.atible for (int col = 0; col < columnCount; col++) { final int c = col; final RelDataType type = typeFactory.leastRestrictive( new AbstractList() { public RelDataType get(int row) { SqlCall thisRow = (SqlCall) operands.get(row); return deriveType(scope, thisRow.operand(c)); } public int size() { return rowCount; } }); if (null == type) { throw newValidationError(node, RESOURCE.incompatibleValueType( SqlStdOperatorTable.VALUES.getName())); } } } } public void validateDataType(SqlDataTypeSpec dataType) { } public void validateDynamicParam(SqlDynamicParam dynamicParam) { } /** * Throws a validator exception with access to the validator context. * The exception is determined when an instance is created. */ private class ValidationError implements Supplier { private final SqlNode sqlNode; private final Resources.ExInst validatorException; ValidationError(SqlNode sqlNode, Resources.ExInst validatorException) { this.sqlNode = sqlNode; this.validatorException = validatorException; } public CalciteContextException get() { return newValidationError(sqlNode, validatorException); } } /** * Throws a validator exception with access to the validator context. * The exception is determined when the function is applied. */ class ValidationErrorFunction implements Function2, CalciteContextException> { @Override public CalciteContextException apply( SqlNode v0, Resources.ExInst v1) { return newValidationError(v0, v1); } } public ValidationErrorFunction getValidationErrorFunction() { return validationErrorFunction; } public CalciteContextException newValidationError(SqlNode node, Resources.ExInst e) { assert node != null; final SqlParserPos pos = node.getParserPosition(); return SqlUtil.newContextException(pos, e); } protected SqlWindow getWindowByName( SqlIdentifier id, SqlValidatorScope scope) { SqlWindow window = null; if (id.isSimple()) { final String name = id.getSimple(); window = scope.lookupWindow(name); } if (window == null) { throw newValidationError(id, RESOURCE.windowNotFound(id.toString())); } return window; } public SqlWindow resolveWindow( SqlNode windowOrRef, SqlValidatorScope scope) { SqlWindow window; if (windowOrRef instanceof SqlIdentifier) { window = getWindowByName((SqlIdentifier) windowOrRef, scope); } else { window = (SqlWindow) windowOrRef; } while (true) { final SqlIdentifier refId = window.getRefName(); if (refId == null) { break; } final String refName = refId.getSimple(); SqlWindow refWindow = scope.lookupWindow(refName); if (refWindow == null) { throw newValidationError(refId, RESOURCE.windowNotFound(refName)); } window = window.overlay(refWindow, this); } return window; } public SqlNode getOriginal(SqlNode expr) { SqlNode original = originalExprs.get(expr); if (original == null) { original = expr; } return original; } public void setOriginal(SqlNode expr, SqlNode original) { // Don't overwrite the original original. originalExprs.putIfAbsent(expr, original); } SqlValidatorNamespace lookupFieldNamespace(RelDataType rowType, String name) { final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); final RelDataTypeField field = nameMatcher.field(rowType, name); if (field == null) { return null; } return new FieldNamespace(this, field.getType()); } public void validateWindow( SqlNode windowOrId, SqlValidatorScope scope, SqlCall call) { // Enable nested aggregates with window aggregates (OVER operator) inWindow = true; final SqlWindow targetWindow; switch (windowOrId.getKind()) { case IDENTIFIER: // Just verify the window exists in this query. It will validate // when the definition is processed targetWindow = getWindowByName((SqlIdentifier) windowOrId, scope); break; case WINDOW: targetWindow = (SqlWindow) windowOrId; break; default: throw Util.unexpected(windowOrId.getKind()); } assert targetWindow.getWindowCall() == null; targetWindow.setWindowCall(call); targetWindow.validate(this, scope); targetWindow.setWindowCall(null); call.validate(this, scope); validateAggregateParams(call, null, null, scope); // Disable nested aggregates post validation inWindow = false; } @Override public void validateMatchRecognize(SqlCall call) { final SqlMatchRecognize matchRecognize = (SqlMatchRecognize) call; final MatchRecognizeScope scope = (MatchRecognizeScope) getMatchRecognizeScope(matchRecognize); final MatchRecognizeNamespace ns = getNamespace(call).unwrap(MatchRecognizeNamespace.class); assert ns.rowType == null; // rows per match final SqlLiteral rowsPerMatch = matchRecognize.getRowsPerMatch(); final boolean allRows = rowsPerMatch != null && rowsPerMatch.getValue() == SqlMatchRecognize.RowsPerMatchOption.ALL_ROWS; final RelDataTypeFactory.Builder typeBuilder = typeFactory.builder(); // parse PARTITION BY column SqlNodeList partitionBy = matchRecognize.getPartitionList(); if (partitionBy != null) { for (SqlNode node : partitionBy) { SqlIdentifier identifier = (SqlIdentifier) node; identifier.validate(this, scope); RelDataType type = deriveType(scope, identifier); String name = identifier.names.get(1); typeBuilder.add(name, type); } } // parse ORDER BY column SqlNodeList orderBy = matchRecognize.getOrderList(); if (orderBy != null) { for (SqlNode node : orderBy) { node.validate(this, scope); SqlIdentifier identifier; if (node instanceof SqlBasicCall) { identifier = (SqlIdentifier) ((SqlBasicCall) node).getOperands()[0]; } else { identifier = (SqlIdentifier) node; } if (allRows) { RelDataType type = deriveType(scope, identifier); String name = identifier.names.get(1); if (!typeBuilder.nameExists(name)) { typeBuilder.add(name, type); } } } } if (allRows) { final SqlValidatorNamespace sqlNs = getNamespace(matchRecognize.getTableRef()); final RelDataType inputDataType = sqlNs.getRowType(); for (RelDataTypeField fs : inputDataType.getFieldList()) { if (!typeBuilder.nameExists(fs.getName())) { typeBuilder.add(fs); } } } // retrieve pattern variables used in pattern and subset SqlNode pattern = matchRecognize.getPattern(); PatternVarVisitor visitor = new PatternVarVisitor(scope); pattern.accept(visitor); SqlLiteral interval = matchRecognize.getInterval(); if (interval != null) { interval.validate(this, scope); if (((SqlIntervalLiteral) interval).signum() < 0) { throw newValidationError(interval, RESOURCE.intervalMustBeNonNegative(interval.toValue())); } if (orderBy == null || orderBy.size() == 0) { throw newValidationError(interval, RESOURCE.cannotUseWithinWithoutOrderBy()); } SqlNode firstOrderByColumn = orderBy.getList().get(0); SqlIdentifier identifier; if (firstOrderByColumn instanceof SqlBasicCall) { identifier = (SqlIdentifier) ((SqlBasicCall) firstOrderByColumn).getOperands()[0]; } else { identifier = (SqlIdentifier) firstOrderByColumn; } RelDataType firstOrderByColumnType = deriveType(scope, identifier); if (firstOrderByColumnType.getSqlTypeName() != SqlTypeName.TIMESTAMP) { throw newValidationError(interval, RESOURCE.firstColumnOfOrderByMustBeTimestamp()); } SqlNode expand = expand(interval, scope); RelDataType type = deriveType(scope, expand); setValidatedNodeType(interval, type); } validateDefinitions(matchRecognize, scope); SqlNodeList subsets = matchRecognize.getSubsetList(); if (subsets != null && subsets.size() > 0) { for (SqlNode node : subsets) { List operands = ((SqlCall) node).getOperandList(); String leftString = ((SqlIdentifier) operands.get(0)).getSimple(); if (scope.getPatternVars().contains(leftString)) { throw newValidationError(operands.get(0), RESOURCE.patternVarAlreadyDefined(leftString)); } scope.addPatternVar(leftString); for (SqlNode right : (SqlNodeList) operands.get(1)) { SqlIdentifier id = (SqlIdentifier) right; if (!scope.getPatternVars().contains(id.getSimple())) { throw newValidationError(id, RESOURCE.unknownPattern(id.getSimple())); } scope.addPatternVar(id.getSimple()); } } } // validate AFTER ... SKIP TO final SqlNode skipTo = matchRecognize.getAfter(); if (skipTo instanceof SqlCall) { final SqlCall skipToCall = (SqlCall) skipTo; final SqlIdentifier id = skipToCall.operand(0); if (!scope.getPatternVars().contains(id.getSimple())) { throw newValidationError(id, RESOURCE.unknownPattern(id.getSimple())); } } List> measureColumns = validateMeasure(matchRecognize, scope, allRows); for (Map.Entry c : measureColumns) { if (!typeBuilder.nameExists(c.getKey())) { typeBuilder.add(c.getKey(), c.getValue()); } } final RelDataType rowType = typeBuilder.build(); if (matchRecognize.getMeasureList().size() == 0) { ns.setType(getNamespace(matchRecognize.getTableRef()).getRowType()); } else { ns.setType(rowType); } } private List> validateMeasure(SqlMatchRecognize mr, MatchRecognizeScope scope, boolean allRows) { final List aliases = new ArrayList<>(); final List sqlNodes = new ArrayList<>(); final SqlNodeList measures = mr.getMeasureList(); final List> fields = new ArrayList<>(); for (SqlNode measure : measures) { assert measure instanceof SqlCall; final String alias = deriveAlias(measure, aliases.size()); aliases.add(alias); SqlNode expand = expand(measure, scope); expand = navigationInMeasure(expand, allRows); setOriginal(expand, measure); inferUnknownTypes(unknownType, scope, expand); final RelDataType type = deriveType(scope, expand); setValidatedNodeType(measure, type); fields.add(Pair.of(alias, type)); sqlNodes.add( SqlStdOperatorTable.AS.createCall(SqlParserPos.ZERO, expand, new SqlIdentifier(alias, SqlParserPos.ZERO))); } SqlNodeList list = new SqlNodeList(sqlNodes, measures.getParserPosition()); inferUnknownTypes(unknownType, scope, list); for (SqlNode node : list) { validateExpr(node, scope); } mr.setOperand(SqlMatchRecognize.OPERAND_MEASURES, list); return fields; } private SqlNode navigationInMeasure(SqlNode node, boolean allRows) { final Set prefix = node.accept(new PatternValidator(true)); Util.discard(prefix); final List ops = ((SqlCall) node).getOperandList(); final SqlOperator defaultOp = allRows ? SqlStdOperatorTable.RUNNING : SqlStdOperatorTable.FINAL; final SqlNode op0 = ops.get(0); if (!isRunningOrFinal(op0.getKind()) || !allRows && op0.getKind() == SqlKind.RUNNING) { SqlNode newNode = defaultOp.createCall(SqlParserPos.ZERO, op0); node = SqlStdOperatorTable.AS.createCall(SqlParserPos.ZERO, newNode, ops.get(1)); } node = new NavigationExpander().go(node); return node; } private void validateDefinitions(SqlMatchRecognize mr, MatchRecognizeScope scope) { final Set aliases = catalogReader.nameMatcher().createSet(); for (SqlNode item : mr.getPatternDefList().getList()) { final String alias = alias(item); if (!aliases.add(alias)) { throw newValidationError(item, Static.RESOURCE.patternVarAlreadyDefined(alias)); } scope.addPatternVar(alias); } final List sqlNodes = new ArrayList<>(); for (SqlNode item : mr.getPatternDefList().getList()) { final String alias = alias(item); SqlNode expand = expand(item, scope); expand = navigationInDefine(expand, alias); setOriginal(expand, item); inferUnknownTypes(booleanType, scope, expand); expand.validate(this, scope); // Some extra work need required here. // In PREV, NEXT, FINAL and LAST, only one pattern variable is allowed. sqlNodes.add( SqlStdOperatorTable.AS.createCall(SqlParserPos.ZERO, expand, new SqlIdentifier(alias, SqlParserPos.ZERO))); final RelDataType type = deriveType(scope, expand); if (!SqlTypeUtil.inBooleanFamily(type)) { throw newValidationError(expand, RESOURCE.condMustBeBoolean("DEFINE")); } setValidatedNodeType(item, type); } SqlNodeList list = new SqlNodeList(sqlNodes, mr.getPatternDefList().getParserPosition()); inferUnknownTypes(unknownType, scope, list); for (SqlNode node : list) { validateExpr(node, scope); } mr.setOperand(SqlMatchRecognize.OPERAND_PATTERN_DEFINES, list); } /** Returns the alias of a "expr AS alias" expression. */ private static String alias(SqlNode item) { assert item instanceof SqlCall; assert item.getKind() == SqlKind.AS; final SqlIdentifier identifier = ((SqlCall) item).operand(1); return identifier.getSimple(); } /** Checks that all pattern variables within a function are the same, * and canonizes expressions such as {@code PREV(B.price)} to * {@code LAST(B.price, 0)}. */ private SqlNode navigationInDefine(SqlNode node, String alpha) { Set prefix = node.accept(new PatternValidator(false)); Util.discard(prefix); node = new NavigationExpander().go(node); node = new NavigationReplacer(alpha).go(node); return node; } public void validateAggregateParams(SqlCall aggCall, SqlNode filter, SqlNodeList orderList, SqlValidatorScope scope) { // For "agg(expr)", expr cannot itself contain aggregate function // invocations. For example, "SUM(2 * MAX(x))" is illegal; when // we see it, we'll report the error for the SUM (not the MAX). // For more than one level of nesting, the error which results // depends on the traversal order for validation. // // For a windowed aggregate "agg(expr)", expr can contain an aggregate // function. For example, // SELECT AVG(2 * MAX(x)) OVER (PARTITION BY y) // FROM t // GROUP BY y // is legal. Only one level of nesting is allowed since non-windowed // aggregates cannot nest aggregates. // Store nesting level of each aggregate. If an aggregate is found at an invalid // nesting level, throw an assert. final AggFinder a; if (inWindow) { a = overFinder; } else { a = aggOrOverFinder; } for (SqlNode param : aggCall.getOperandList()) { if (a.findAgg(param) != null) { throw newValidationError(aggCall, RESOURCE.nestedAggIllegal()); } } if (filter != null) { if (a.findAgg(filter) != null) { throw newValidationError(filter, RESOURCE.aggregateInFilterIllegal()); } } if (orderList != null) { for (SqlNode param : orderList) { if (a.findAgg(param) != null) { throw newValidationError(aggCall, RESOURCE.aggregateInWithinGroupIllegal()); } } } final SqlAggFunction op = (SqlAggFunction) aggCall.getOperator(); switch (op.requiresGroupOrder()) { case MANDATORY: if (orderList == null || orderList.size() == 0) { throw newValidationError(aggCall, RESOURCE.aggregateMissingWithinGroupClause(op.getName())); } break; case OPTIONAL: break; case IGNORED: // rewrite the order list to empty if (orderList != null) { orderList.getList().clear(); } break; case FORBIDDEN: if (orderList != null && orderList.size() != 0) { throw newValidationError(aggCall, RESOURCE.withinGroupClauseIllegalInAggregate(op.getName())); } break; default: throw new AssertionError(op); } } public void validateCall( SqlCall call, SqlValidatorScope scope) { final SqlOperator operator = call.getOperator(); if ((call.operandCount() == 0) && (operator.getSyntax() == SqlSyntax.FUNCTION_ID) && !call.isExpanded() && !this.config.sqlConformance().allowNiladicParentheses()) { // For example, "LOCALTIME()" is illegal. (It should be // "LOCALTIME", which would have been handled as a // SqlIdentifier.) throw handleUnresolvedFunction(call, (SqlFunction) operator, ImmutableList.of(), null); } SqlValidatorScope operandScope = scope.getOperandScope(call); if (operator instanceof SqlFunction && ((SqlFunction) operator).getFunctionType() == SqlFunctionCategory.MATCH_RECOGNIZE && !(operandScope instanceof MatchRecognizeScope)) { throw newValidationError(call, Static.RESOURCE.functionMatchRecognizeOnly(call.toString())); } // Delegate validation to the operator. operator.validateCall(call, this, scope, operandScope); } /** * Validates that a particular feature is enabled. By default, all features * are enabled; subclasses may override this method to be more * discriminating. * * @param feature feature being used, represented as a resource instance * @param context parser position context for error reporting, or null if */ protected void validateFeature( Feature feature, SqlParserPos context) { // By default, do nothing except to verify that the resource // represents a real feature definition. assert feature.getProperties().get("FeatureDefinition") != null; } public SqlNode expandSelectExpr(SqlNode expr, SelectScope scope, SqlSelect select) { final Expander expander = new SelectExpander(this, scope, select); final SqlNode newExpr = expr.accept(expander); if (expr != newExpr) { setOriginal(newExpr, expr); } return newExpr; } public SqlNode expand(SqlNode expr, SqlValidatorScope scope) { final Expander expander = new Expander(this, scope); SqlNode newExpr = expr.accept(expander); if (expr != newExpr) { setOriginal(newExpr, expr); } return newExpr; } public SqlNode expandGroupByOrHavingExpr(SqlNode expr, SqlValidatorScope scope, SqlSelect select, boolean havingExpression) { final Expander expander = new ExtendedExpander(this, scope, select, expr, havingExpression); SqlNode newExpr = expr.accept(expander); if (expr != newExpr) { setOriginal(newExpr, expr); } return newExpr; } public boolean isSystemField(RelDataTypeField field) { return false; } public List> getFieldOrigins(SqlNode sqlQuery) { if (sqlQuery instanceof SqlExplain) { return Collections.emptyList(); } final RelDataType rowType = getValidatedNodeType(sqlQuery); final int fieldCount = rowType.getFieldCount(); if (!sqlQuery.isA(SqlKind.QUERY)) { return Collections.nCopies(fieldCount, null); } final List> list = new ArrayList<>(); for (int i = 0; i < fieldCount; i++) { list.add(getFieldOrigin(sqlQuery, i)); } return ImmutableNullableList.copyOf(list); } private List getFieldOrigin(SqlNode sqlQuery, int i) { if (sqlQuery instanceof SqlSelect) { SqlSelect sqlSelect = (SqlSelect) sqlQuery; final SelectScope scope = getRawSelectScope(sqlSelect); final List selectList = scope.getExpandedSelectList(); final SqlNode selectItem = stripAs(selectList.get(i)); if (selectItem instanceof SqlIdentifier) { final SqlQualified qualified = scope.fullyQualify((SqlIdentifier) selectItem); SqlValidatorNamespace namespace = qualified.namespace; final SqlValidatorTable table = namespace.getTable(); if (table == null) { return null; } final List origin = new ArrayList<>(table.getQualifiedName()); for (String name : qualified.suffix()) { namespace = namespace.lookupChild(name); if (namespace == null) { return null; } origin.add(name); } return origin; } return null; } else if (sqlQuery instanceof SqlOrderBy) { return getFieldOrigin(((SqlOrderBy) sqlQuery).query, i); } else { return null; } } public RelDataType getParameterRowType(SqlNode sqlQuery) { // NOTE: We assume that bind variables occur in depth-first tree // traversal in the same order that they occurred in the SQL text. final List types = new ArrayList<>(); // NOTE: but parameters on fetch/offset would be counted twice // as they are counted in the SqlOrderBy call and the inner SqlSelect call final Set alreadyVisited = new HashSet<>(); sqlQuery.accept( new SqlShuttle() { @Override public SqlNode visit(SqlDynamicParam param) { if (alreadyVisited.add(param)) { RelDataType type = getValidatedNodeType(param); types.add(type); } return param; } }); return typeFactory.createStructType( types, new AbstractList() { @Override public String get(int index) { return "?" + index; } @Override public int size() { return types.size(); } }); } public void validateColumnListParams( SqlFunction function, List argTypes, List operands) { throw new UnsupportedOperationException(); } private static boolean isPhysicalNavigation(SqlKind kind) { return kind == SqlKind.PREV || kind == SqlKind.NEXT; } private static boolean isLogicalNavigation(SqlKind kind) { return kind == SqlKind.FIRST || kind == SqlKind.LAST; } private static boolean isAggregation(SqlKind kind) { return kind == SqlKind.SUM || kind == SqlKind.SUM0 || kind == SqlKind.AVG || kind == SqlKind.COUNT || kind == SqlKind.MAX || kind == SqlKind.MIN; } private static boolean isRunningOrFinal(SqlKind kind) { return kind == SqlKind.RUNNING || kind == SqlKind.FINAL; } private static boolean isSingleVarRequired(SqlKind kind) { return isPhysicalNavigation(kind) || isLogicalNavigation(kind) || isAggregation(kind); } //~ Inner Classes ---------------------------------------------------------- /** * Common base class for DML statement namespaces. */ public static class DmlNamespace extends IdentifierNamespace { protected DmlNamespace(SqlValidatorImpl validator, SqlNode id, SqlNode enclosingNode, SqlValidatorScope parentScope) { super(validator, id, enclosingNode, parentScope); } } /** * Namespace for an INSERT statement. */ private static class InsertNamespace extends DmlNamespace { private final SqlInsert node; InsertNamespace(SqlValidatorImpl validator, SqlInsert node, SqlNode enclosingNode, SqlValidatorScope parentScope) { super(validator, node.getTargetTable(), enclosingNode, parentScope); this.node = Objects.requireNonNull(node); } public SqlInsert getNode() { return node; } } /** * Namespace for an UPDATE statement. */ private static class UpdateNamespace extends DmlNamespace { private final SqlUpdate node; UpdateNamespace(SqlValidatorImpl validator, SqlUpdate node, SqlNode enclosingNode, SqlValidatorScope parentScope) { super(validator, node.getTargetTable(), enclosingNode, parentScope); this.node = Objects.requireNonNull(node); } public SqlUpdate getNode() { return node; } } /** * Namespace for a DELETE statement. */ private static class DeleteNamespace extends DmlNamespace { private final SqlDelete node; DeleteNamespace(SqlValidatorImpl validator, SqlDelete node, SqlNode enclosingNode, SqlValidatorScope parentScope) { super(validator, node.getTargetTable(), enclosingNode, parentScope); this.node = Objects.requireNonNull(node); } public SqlDelete getNode() { return node; } } /** * Namespace for a MERGE statement. */ private static class MergeNamespace extends DmlNamespace { private final SqlMerge node; MergeNamespace(SqlValidatorImpl validator, SqlMerge node, SqlNode enclosingNode, SqlValidatorScope parentScope) { super(validator, node.getTargetTable(), enclosingNode, parentScope); this.node = Objects.requireNonNull(node); } public SqlMerge getNode() { return node; } } /** * retrieve pattern variables defined */ private class PatternVarVisitor implements SqlVisitor { private MatchRecognizeScope scope; PatternVarVisitor(MatchRecognizeScope scope) { this.scope = scope; } @Override public Void visit(SqlLiteral literal) { return null; } @Override public Void visit(SqlCall call) { for (int i = 0; i < call.getOperandList().size(); i++) { call.getOperandList().get(i).accept(this); } return null; } @Override public Void visit(SqlNodeList nodeList) { throw Util.needToImplement(nodeList); } @Override public Void visit(SqlIdentifier id) { Preconditions.checkArgument(id.isSimple()); scope.addPatternVar(id.getSimple()); return null; } @Override public Void visit(SqlDataTypeSpec type) { throw Util.needToImplement(type); } @Override public Void visit(SqlDynamicParam param) { throw Util.needToImplement(param); } @Override public Void visit(SqlIntervalQualifier intervalQualifier) { throw Util.needToImplement(intervalQualifier); } } /** * Visitor which derives the type of a given {@link SqlNode}. * *

Each method must return the derived type. This visitor is basically a * single-use dispatcher; the visit is never recursive. */ private class DeriveTypeVisitor implements SqlVisitor { private final SqlValidatorScope scope; DeriveTypeVisitor(SqlValidatorScope scope) { this.scope = scope; } public RelDataType visit(SqlLiteral literal) { return literal.createSqlType(typeFactory); } public RelDataType visit(SqlCall call) { final SqlOperator operator = call.getOperator(); return operator.deriveType(SqlValidatorImpl.this, scope, call); } public RelDataType visit(SqlNodeList nodeList) { // Operand is of a type that we can't derive a type for. If the // operand is of a peculiar type, such as a SqlNodeList, then you // should override the operator's validateCall() method so that it // doesn't try to validate that operand as an expression. throw Util.needToImplement(nodeList); } public RelDataType visit(SqlIdentifier id) { // First check for builtin functions which don't have parentheses, // like "LOCALTIME". final SqlCall call = makeNullaryCall(id); if (call != null) { return call.getOperator().validateOperands( SqlValidatorImpl.this, scope, call); } RelDataType type = null; if (!(scope instanceof EmptyScope)) { id = scope.fullyQualify(id).identifier; } // Resolve the longest prefix of id that we can int i; for (i = id.names.size() - 1; i > 0; i--) { // REVIEW jvs 9-June-2005: The name resolution rules used // here are supposed to match SQL:2003 Part 2 Section 6.6 // (identifier chain), but we don't currently have enough // information to get everything right. In particular, // routine parameters are currently looked up via resolve; // we could do a better job if they were looked up via // resolveColumn. final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); final SqlValidatorScope.ResolvedImpl resolved = new SqlValidatorScope.ResolvedImpl(); scope.resolve(id.names.subList(0, i), nameMatcher, false, resolved); if (resolved.count() == 1) { // There's a namespace with the name we seek. final SqlValidatorScope.Resolve resolve = resolved.only(); type = resolve.rowType(); for (SqlValidatorScope.Step p : Util.skip(resolve.path.steps())) { type = type.getFieldList().get(p.i).getType(); } break; } } // Give precedence to namespace found, unless there // are no more identifier com.hazelcast.com.onents. if (type == null || id.names.size() == 1) { // See if there's a column with the name we seek in // precisely one of the namespaces in this scope. RelDataType colType = scope.resolveColumn(id.names.get(0), id); if (colType != null) { type = colType; } ++i; } if (type == null) { final SqlIdentifier last = id.getComponent(i - 1, i); throw newValidationError(last, RESOURCE.unknownIdentifier(last.toString())); } // Resolve rest of identifier for (; i < id.names.size(); i++) { String name = id.names.get(i); final RelDataTypeField field; if (name.equals("")) { // The wildcard "*" is represented as an empty name. It never // resolves to a field. name = "*"; field = null; } else { final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); field = nameMatcher.field(type, name); } if (field == null) { throw newValidationError(id.getComponent(i), RESOURCE.unknownField(name)); } type = field.getType(); } type = SqlTypeUtil.addCharsetAndCollation( type, getTypeFactory()); return type; } public RelDataType visit(SqlDataTypeSpec dataType) { // Q. How can a data type have a type? // A. When it appears in an expression. (Say as the 2nd arg to the // CAST operator.) validateDataType(dataType); return dataType.deriveType(SqlValidatorImpl.this); } public RelDataType visit(SqlDynamicParam param) { return unknownType; } public RelDataType visit(SqlIntervalQualifier intervalQualifier) { return typeFactory.createSqlIntervalType(intervalQualifier); } } /** * Converts an expression into canonical form by fully-qualifying any * identifiers. */ private static class Expander extends SqlScopedShuttle { protected final SqlValidatorImpl validator; Expander(SqlValidatorImpl validator, SqlValidatorScope scope) { super(scope); this.validator = validator; } @Override public SqlNode visit(SqlIdentifier id) { // First check for builtin functions which don't have // parentheses, like "LOCALTIME". final SqlCall call = validator.makeNullaryCall(id); if (call != null) { return call.accept(this); } final SqlIdentifier fqId = getScope().fullyQualify(id).identifier; SqlNode expandedExpr = expandDynamicStar(id, fqId); validator.setOriginal(expandedExpr, id); return expandedExpr; } @Override protected SqlNode visitScoped(SqlCall call) { switch (call.getKind()) { case SCALAR_QUERY: case CURRENT_VALUE: case NEXT_VALUE: case WITH: return call; } // Only visits arguments which are expressions. We don't want to // qualify non-expressions such as 'x' in 'empno * 5 AS x'. ArgHandler argHandler = new CallCopyingArgHandler(call, false); call.getOperator().acceptCall(this, call, true, argHandler); final SqlNode result = argHandler.result(); validator.setOriginal(result, call); return result; } protected SqlNode expandDynamicStar(SqlIdentifier id, SqlIdentifier fqId) { if (DynamicRecordType.isDynamicStarColName(Util.last(fqId.names)) && !DynamicRecordType.isDynamicStarColName(Util.last(id.names))) { // Convert a column ref into ITEM(*, 'col_name') // for a dynamic star field in dynTable's rowType. SqlNode[] inputs = new SqlNode[2]; inputs[0] = fqId; inputs[1] = SqlLiteral.createCharString( Util.last(id.names), id.getParserPosition()); return new SqlBasicCall( SqlStdOperatorTable.ITEM, inputs, id.getParserPosition()); } return fqId; } } /** * Shuttle which walks over an expression in the ORDER BY clause, replacing * usages of aliases with the underlying expression. */ class OrderExpressionExpander extends SqlScopedShuttle { private final List aliasList; private final SqlSelect select; private final SqlNode root; OrderExpressionExpander(SqlSelect select, SqlNode root) { super(getOrderScope(select)); this.select = select; this.root = root; this.aliasList = getNamespace(select).getRowType().getFieldNames(); } public SqlNode go() { return root.accept(this); } public SqlNode visit(SqlLiteral literal) { // Ordinal markers, e.g. 'select a, b from t order by 2'. // Only recognize them if they are the whole expression, // and if the dialect permits. if (literal == root && config.sqlConformance().isSortByOrdinal()) { switch (literal.getTypeName()) { case DECIMAL: case DOUBLE: final int intValue = literal.intValue(false); if (intValue >= 0) { if (intValue < 1 || intValue > aliasList.size()) { 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; } } return super.visit(literal); } /** * Returns the ordinalth 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( select.getSelectList(), 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); } public SqlNode visit(SqlIdentifier id) { // Aliases, e.g. 'select a as x, b from t order by x'. if (id.isSimple() && config.sqlConformance().isSortByAlias()) { String alias = id.getSimple(); final SqlValidatorNamespace selectNs = getNamespace(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; } protected 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 com.hazelcast.com.on columns in USING, it will be converted to * COALESCE(A.col, B.col) AS col. */ static class SelectExpander extends Expander { final SqlSelect select; SelectExpander(SqlValidatorImpl validator, SelectScope scope, SqlSelect select) { super(validator, scope); this.select = select; } @Override public SqlNode visit(SqlIdentifier id) { final SqlNode node = expandCommonColumn(select, id, (SelectScope) getScope(), validator); if (node != id) { return node; } else { return super.visit(id); } } } /** * 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 boolean havingExpr; ExtendedExpander(SqlValidatorImpl validator, SqlValidatorScope scope, SqlSelect select, SqlNode root, boolean havingExpr) { super(validator, scope); this.select = select; this.root = root; this.havingExpr = havingExpr; } @Override public SqlNode visit(SqlIdentifier id) { if (id.isSimple() && (havingExpr ? validator.config().sqlConformance().isHavingAlias() : validator.config().sqlConformance().isGroupByAlias())) { String name = id.getSimple(); SqlNode expr = null; final SqlNameMatcher nameMatcher = validator.catalogReader.nameMatcher(); int n = 0; for (SqlNode s : select.getSelectList()) { final String alias = SqlValidatorUtil.getAlias(s, -1); 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)); } if (havingExpr && validator.isAggregate(root)) { return super.visit(id); } expr = stripAs(expr); if (expr instanceof SqlIdentifier) { SqlIdentifier sid = (SqlIdentifier) expr; final SqlIdentifier fqId = getScope().fullyQualify(sid).identifier; expr = expandDynamicStar(sid, fqId); } return expr; } if (id.isSimple()) { final SelectScope scope = validator.getRawSelectScope(select); SqlNode node = expandCommonColumn(select, id, scope, validator); if (node != id) { return node; } } return super.visit(id); } public SqlNode visit(SqlLiteral literal) { if (havingExpr || !validator.config().sqlConformance().isGroupByOrdinal()) { return super.visit(literal); } boolean isOrdinalLiteral = literal == root; switch (root.getKind()) { case GROUPING_SETS: case ROLLUP: case CUBE: if (root instanceof SqlBasicCall) { List operandList = ((SqlBasicCall) root).getOperandList(); for (SqlNode node : operandList) { if (node.equals(literal)) { isOrdinalLiteral = true; break; } } } break; } if (isOrdinalLiteral) { switch (literal.getTypeName()) { case DECIMAL: case DOUBLE: final int intValue = literal.intValue(false); if (intValue >= 0) { if (intValue < 1 || intValue > select.getSelectList().size()) { throw validator.newValidationError(literal, RESOURCE.orderByOrdinalOutOfRange()); } // SQL ordinals are 1-based, but Sort's are 0-based int ordinal = intValue - 1; return SqlUtil.stripAs(select.getSelectList().get(ordinal)); } break; } } return super.visit(literal); } } /** Information about an identifier in a particular scope. */ protected static class IdInfo { public final SqlValidatorScope scope; public final SqlIdentifier id; public IdInfo(SqlValidatorScope scope, SqlIdentifier id) { this.scope = scope; this.id = id; } } /** * Utility object used to maintain information about the parameters in a * function call. */ protected static class FunctionParamInfo { /** * Maps a cursor (based on its position relative to other cursor * parameters within a function call) to the SELECT associated with the * cursor. */ public final Map cursorPosToSelectMap; /** * Maps a column list parameter to the parent cursor parameter it * references. The parameters are id'd by their names. */ public final Map columnListParamToParentCursorMap; public FunctionParamInfo() { cursorPosToSelectMap = new HashMap<>(); columnListParamToParentCursorMap = new HashMap<>(); } } /** * Modify the nodes in navigation function * such as FIRST, LAST, PREV AND NEXT. */ private static class NavigationModifier extends SqlShuttle { public SqlNode go(SqlNode node) { return node.accept(this); } } /** * Shuttle that expands navigation expressions in a MATCH_RECOGNIZE clause. * *

Examples: * *

    *
  • {@code PREV(A.price + A.amount)} → * {@code PREV(A.price) + PREV(A.amount)} * *
  • {@code FIRST(A.price * 2)} → {@code FIRST(A.PRICE) * 2} *
*/ private static class NavigationExpander extends NavigationModifier { final SqlOperator op; final SqlNode offset; NavigationExpander() { this(null, null); } NavigationExpander(SqlOperator operator, SqlNode offset) { this.offset = offset; this.op = operator; } @Override public SqlNode visit(SqlCall call) { SqlKind kind = call.getKind(); List operands = call.getOperandList(); List newOperands = new ArrayList<>(); if (call.getFunctionQuantifier() != null && call.getFunctionQuantifier().getValue() == SqlSelectKeyword.DISTINCT) { final SqlParserPos pos = call.getParserPosition(); throw SqlUtil.newContextException(pos, Static.RESOURCE.functionQuantifierNotAllowed(call.toString())); } if (isLogicalNavigation(kind) || isPhysicalNavigation(kind)) { SqlNode inner = operands.get(0); SqlNode offset = operands.get(1); // merge two straight prev/next, update offset if (isPhysicalNavigation(kind)) { SqlKind innerKind = inner.getKind(); if (isPhysicalNavigation(innerKind)) { List innerOperands = ((SqlCall) inner).getOperandList(); SqlNode innerOffset = innerOperands.get(1); SqlOperator newOperator = innerKind == kind ? SqlStdOperatorTable.PLUS : SqlStdOperatorTable.MINUS; offset = newOperator.createCall(SqlParserPos.ZERO, offset, innerOffset); inner = call.getOperator().createCall(SqlParserPos.ZERO, innerOperands.get(0), offset); } } SqlNode newInnerNode = inner.accept(new NavigationExpander(call.getOperator(), offset)); if (op != null) { newInnerNode = op.createCall(SqlParserPos.ZERO, newInnerNode, this.offset); } return newInnerNode; } if (operands.size() > 0) { for (SqlNode node : operands) { if (node != null) { SqlNode newNode = node.accept(new NavigationExpander()); if (op != null) { newNode = op.createCall(SqlParserPos.ZERO, newNode, offset); } newOperands.add(newNode); } else { newOperands.add(null); } } return call.getOperator().createCall(SqlParserPos.ZERO, newOperands); } else { if (op == null) { return call; } else { return op.createCall(SqlParserPos.ZERO, call, offset); } } } @Override public SqlNode visit(SqlIdentifier id) { if (op == null) { return id; } else { return op.createCall(SqlParserPos.ZERO, id, offset); } } } /** * Shuttle that replaces {@code A as A.price > PREV(B.price)} with * {@code PREV(A.price, 0) > LAST(B.price, 0)}. * *

Replacing {@code A.price} with {@code PREV(A.price, 0)} makes the * implementation of * {@link RexVisitor#visitPatternFieldRef(RexPatternFieldRef)} more unified. * Otherwise, it's difficult to implement this method. If it returns the * specified field, then the navigation such as {@code PREV(A.price, 1)} * becomes impossible; if not, then com.hazelcast.com.arisons such as * {@code A.price > PREV(A.price, 1)} become meaningless. */ private static class NavigationReplacer extends NavigationModifier { private final String alpha; NavigationReplacer(String alpha) { this.alpha = alpha; } @Override public SqlNode visit(SqlCall call) { SqlKind kind = call.getKind(); if (isLogicalNavigation(kind) || isAggregation(kind) || isRunningOrFinal(kind)) { return call; } switch (kind) { case PREV: final List operands = call.getOperandList(); if (operands.get(0) instanceof SqlIdentifier) { String name = ((SqlIdentifier) operands.get(0)).names.get(0); return name.equals(alpha) ? call : SqlStdOperatorTable.LAST.createCall(SqlParserPos.ZERO, operands); } } return super.visit(call); } @Override public SqlNode visit(SqlIdentifier id) { if (id.isSimple()) { return id; } SqlOperator operator = id.names.get(0).equals(alpha) ? SqlStdOperatorTable.PREV : SqlStdOperatorTable.LAST; return operator.createCall(SqlParserPos.ZERO, id, SqlLiteral.createExactNumeric("0", SqlParserPos.ZERO)); } } /** * Within one navigation function, the pattern var should be same */ private class PatternValidator extends SqlBasicVisitor> { private final boolean isMeasure; int firstLastCount; int prevNextCount; int aggregateCount; PatternValidator(boolean isMeasure) { this(isMeasure, 0, 0, 0); } PatternValidator(boolean isMeasure, int firstLastCount, int prevNextCount, int aggregateCount) { this.isMeasure = isMeasure; this.firstLastCount = firstLastCount; this.prevNextCount = prevNextCount; this.aggregateCount = aggregateCount; } @Override public Set visit(SqlCall call) { boolean isSingle = false; Set vars = new HashSet<>(); SqlKind kind = call.getKind(); List operands = call.getOperandList(); if (isSingleVarRequired(kind)) { isSingle = true; if (isPhysicalNavigation(kind)) { if (isMeasure) { throw newValidationError(call, Static.RESOURCE.patternPrevFunctionInMeasure(call.toString())); } if (firstLastCount != 0) { throw newValidationError(call, Static.RESOURCE.patternPrevFunctionOrder(call.toString())); } prevNextCount++; } else if (isLogicalNavigation(kind)) { if (firstLastCount != 0) { throw newValidationError(call, Static.RESOURCE.patternPrevFunctionOrder(call.toString())); } firstLastCount++; } else if (isAggregation(kind)) { // cannot apply aggregation in PREV/NEXT, FIRST/LAST if (firstLastCount != 0 || prevNextCount != 0) { throw newValidationError(call, Static.RESOURCE.patternAggregationInNavigation(call.toString())); } if (kind == SqlKind.COUNT && call.getOperandList().size() > 1) { throw newValidationError(call, Static.RESOURCE.patternCountFunctionArg()); } aggregateCount++; } } if (isRunningOrFinal(kind) && !isMeasure) { throw newValidationError(call, Static.RESOURCE.patternRunningFunctionInDefine(call.toString())); } for (SqlNode node : operands) { if (node != null) { vars.addAll( node.accept( new PatternValidator(isMeasure, firstLastCount, prevNextCount, aggregateCount))); } } if (isSingle) { switch (kind) { case COUNT: if (vars.size() > 1) { throw newValidationError(call, Static.RESOURCE.patternCountFunctionArg()); } break; default: if (operands.size() == 0 || !(operands.get(0) instanceof SqlCall) || ((SqlCall) operands.get(0)).getOperator() != SqlStdOperatorTable.CLASSIFIER) { if (vars.isEmpty()) { throw newValidationError(call, Static.RESOURCE.patternFunctionNullCheck(call.toString())); } if (vars.size() != 1) { throw newValidationError(call, Static.RESOURCE.patternFunctionVariableCheck(call.toString())); } } break; } } return vars; } @Override public Set visit(SqlIdentifier identifier) { boolean check = prevNextCount > 0 || firstLastCount > 0 || aggregateCount > 0; Set vars = new HashSet<>(); if (identifier.names.size() > 1 && check) { vars.add(identifier.names.get(0)); } return vars; } @Override public Set visit(SqlLiteral literal) { return ImmutableSet.of(); } @Override public Set visit(SqlIntervalQualifier qualifier) { return ImmutableSet.of(); } @Override public Set visit(SqlDataTypeSpec type) { return ImmutableSet.of(); } @Override public Set visit(SqlDynamicParam param) { return ImmutableSet.of(); } } /** Permutation of fields in NATURAL JOIN or USING. */ private class Permute { final List sources; final RelDataType rowType; final boolean trivial; Permute(SqlNode from, int offset) { switch (from.getKind()) { case JOIN: final SqlJoin join = (SqlJoin) from; final Permute left = new Permute(join.getLeft(), offset); final int fieldCount = getValidatedNodeType(join.getLeft()).getFieldList().size(); final Permute right = new Permute(join.getRight(), offset + fieldCount); final List names = usingNames(join); final List sources = new ArrayList<>(); final Set sourceSet = new HashSet<>(); final RelDataTypeFactory.Builder b = typeFactory.builder(); if (names != null) { for (String name : names) { final RelDataTypeField f = left.field(name); final ImmutableIntList source = left.sources.get(f.getIndex()); sourceSet.add(source); final RelDataTypeField f2 = right.field(name); final ImmutableIntList source2 = right.sources.get(f2.getIndex()); sourceSet.add(source2); sources.add(source.appendAll(source2)); final boolean nullable = (f.getType().isNullable() || join.getJoinType().generatesNullsOnLeft()) && (f2.getType().isNullable() || join.getJoinType().generatesNullsOnRight()); b.add(f).nullable(nullable); } } for (RelDataTypeField f : left.rowType.getFieldList()) { final ImmutableIntList source = left.sources.get(f.getIndex()); if (sourceSet.add(source)) { sources.add(source); b.add(f); } } for (RelDataTypeField f : right.rowType.getFieldList()) { final ImmutableIntList source = right.sources.get(f.getIndex()); if (sourceSet.add(source)) { sources.add(source); b.add(f); } } rowType = b.build(); this.sources = ImmutableList.copyOf(sources); this.trivial = left.trivial && right.trivial && (names == null || names.isEmpty()); break; default: rowType = getValidatedNodeType(from); this.sources = Functions.generate(rowType.getFieldCount(), i -> ImmutableIntList.of(offset + i)); this.trivial = true; } } private RelDataTypeField field(String name) { return catalogReader.nameMatcher().field(rowType, name); } /** Moves fields according to the permutation. */ public void permute(List selectItems, List> fields) { if (trivial) { return; } final List oldSelectItems = ImmutableList.copyOf(selectItems); selectItems.clear(); final List> oldFields = ImmutableList.copyOf(fields); fields.clear(); for (ImmutableIntList source : sources) { final int p0 = source.get(0); Map.Entry field = oldFields.get(p0); final String name = field.getKey(); RelDataType type = field.getValue(); SqlNode selectItem = oldSelectItems.get(p0); for (int p1 : Util.skip(source)) { final Map.Entry field1 = oldFields.get(p1); final SqlNode selectItem1 = oldSelectItems.get(p1); final RelDataType type1 = field1.getValue(); // output is nullable only if both inputs are final boolean nullable = type.isNullable() && type1.isNullable(); final RelDataType type2 = SqlTypeUtil.leastRestrictiveForComparison(typeFactory, type, type1); selectItem = SqlStdOperatorTable.AS.createCall(SqlParserPos.ZERO, SqlStdOperatorTable.COALESCE.createCall(SqlParserPos.ZERO, maybeCast(selectItem, type, type2), maybeCast(selectItem1, type1, type2)), new SqlIdentifier(name, SqlParserPos.ZERO)); type = typeFactory.createTypeWithNullability(type2, nullable); } fields.add(Pair.of(name, type)); selectItems.add(selectItem); } } } //~ Enums ------------------------------------------------------------------ /** * Validation status. */ public enum Status { /** * Validation has not started for this scope. */ UNVALIDATED, /** * Validation is in progress for this scope. */ IN_PROGRESS, /** * Validation has com.hazelcast.com.leted (perhaps unsuccessfully). */ VALID } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy