You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by ja...@apache.org on 2020/03/31 02:15:20 UTC
[flink] 06/13: [FLINK-14338][table-planner][table-planner-blink]
Remove redundant code copy because the bugs are already fixed in Calcite
This is an automated email from the ASF dual-hosted git repository.
jark pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
commit 10c8392b9041d636f194b80492afc492eae806b7
Author: yuzhao.cyz <yu...@gmail.com>
AuthorDate: Tue Mar 17 21:02:21 2020 +0800
[FLINK-14338][table-planner][table-planner-blink] Remove redundant code copy because the bugs are already fixed in Calcite
* SqlValidatorImpl.java was removed because of CALCITE-2707
* ParameterScope.java was removed because of CALCITE-3476
* SqlFunction.java was removed because of CALCITE-3360
---
.../java/org/apache/calcite/sql/SqlFunction.java | 322 -
.../calcite/sql/validate/ParameterScope.java | 72 -
.../calcite/sql/validate/SqlValidatorImpl.java | 6451 -------------------
.../java/org/apache/calcite/sql/SqlFunction.java | 322 -
.../calcite/sql/validate/ParameterScope.java | 72 -
.../calcite/sql/validate/SqlValidatorImpl.java | 6452 --------------------
6 files changed, 13691 deletions(-)
diff --git a/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/sql/SqlFunction.java b/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/sql/SqlFunction.java
deleted file mode 100644
index fda4cd5..0000000
--- a/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/sql/SqlFunction.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.calcite.sql;
-
-import com.google.common.collect.ImmutableList;
-import org.apache.calcite.linq4j.function.Functions;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.sql.type.SqlOperandTypeChecker;
-import org.apache.calcite.sql.type.SqlOperandTypeInference;
-import org.apache.calcite.sql.type.SqlReturnTypeInference;
-import org.apache.calcite.sql.validate.SqlValidator;
-import org.apache.calcite.sql.validate.SqlValidatorScope;
-import org.apache.calcite.sql.validate.implicit.TypeCoercion;
-import org.apache.calcite.util.Util;
-
-import javax.annotation.Nonnull;
-
-import java.util.List;
-import java.util.Objects;
-
-import static org.apache.calcite.util.Static.RESOURCE;
-
-/**
- * A <code>SqlFunction</code> is a type of operator which has conventional
- * function-call syntax.
- *
- * <p>This file is copied from Apache Calcite and should be removed once CALCITE-3360
- * has been fixed. Changed lines: 279-293
- */
-public class SqlFunction extends SqlOperator {
-
- //~ Instance fields --------------------------------------------------------
-
- private final SqlFunctionCategory category;
-
- private final SqlIdentifier sqlIdentifier;
-
- private final List<RelDataType> paramTypes;
-
- //~ Constructors -----------------------------------------------------------
-
- /**
- * Creates a new SqlFunction for a call to a builtin function.
- *
- * @param name Name of builtin function
- * @param kind kind of operator implemented by function
- * @param returnTypeInference strategy to use for return type inference
- * @param operandTypeInference strategy to use for parameter type inference
- * @param operandTypeChecker strategy to use for parameter type checking
- * @param category categorization for function
- */
- public SqlFunction(
- String name,
- SqlKind kind,
- SqlReturnTypeInference returnTypeInference,
- SqlOperandTypeInference operandTypeInference,
- SqlOperandTypeChecker operandTypeChecker,
- SqlFunctionCategory category) {
- // We leave sqlIdentifier as null to indicate
- // that this is a builtin. Same for paramTypes.
- this(name, null, kind, returnTypeInference, operandTypeInference,
- operandTypeChecker, null, category);
-
- assert !((category == SqlFunctionCategory.USER_DEFINED_CONSTRUCTOR)
- && (returnTypeInference == null));
- }
-
- /**
- * Creates a placeholder SqlFunction for an invocation of a function with a
- * possibly qualified name. This name must be resolved into either a builtin
- * function or a user-defined function.
- *
- * @param sqlIdentifier possibly qualified identifier for function
- * @param returnTypeInference strategy to use for return type inference
- * @param operandTypeInference strategy to use for parameter type inference
- * @param operandTypeChecker strategy to use for parameter type checking
- * @param paramTypes array of parameter types
- * @param funcType function category
- */
- public SqlFunction(
- SqlIdentifier sqlIdentifier,
- SqlReturnTypeInference returnTypeInference,
- SqlOperandTypeInference operandTypeInference,
- SqlOperandTypeChecker operandTypeChecker,
- List<RelDataType> paramTypes,
- SqlFunctionCategory funcType) {
- this(Util.last(sqlIdentifier.names), sqlIdentifier, SqlKind.OTHER_FUNCTION,
- returnTypeInference, operandTypeInference, operandTypeChecker,
- paramTypes, funcType);
- }
-
- /**
- * Internal constructor.
- */
- protected SqlFunction(
- String name,
- SqlIdentifier sqlIdentifier,
- SqlKind kind,
- SqlReturnTypeInference returnTypeInference,
- SqlOperandTypeInference operandTypeInference,
- SqlOperandTypeChecker operandTypeChecker,
- List<RelDataType> paramTypes,
- SqlFunctionCategory category) {
- super(name, kind, 100, 100, returnTypeInference, operandTypeInference,
- operandTypeChecker);
-
- this.sqlIdentifier = sqlIdentifier;
- this.category = Objects.requireNonNull(category);
- this.paramTypes =
- paramTypes == null ? null : ImmutableList.copyOf(paramTypes);
- }
-
- //~ Methods ----------------------------------------------------------------
-
- public SqlSyntax getSyntax() {
- return SqlSyntax.FUNCTION;
- }
-
- /**
- * @return fully qualified name of function, or null for a builtin function
- */
- public SqlIdentifier getSqlIdentifier() {
- return sqlIdentifier;
- }
-
- @Override public SqlIdentifier getNameAsId() {
- if (sqlIdentifier != null) {
- return sqlIdentifier;
- }
- return super.getNameAsId();
- }
-
- /**
- * @return array of parameter types, or null for builtin function
- */
- public List<RelDataType> getParamTypes() {
- return paramTypes;
- }
-
- /**
- * Returns a list of parameter names.
- *
- * <p>The default implementation returns {@code [arg0, arg1, ..., argN]}.
- */
- public List<String> getParamNames() {
- return Functions.generate(paramTypes.size(), i -> "arg" + i);
- }
-
- public void unparse(
- SqlWriter writer,
- SqlCall call,
- int leftPrec,
- int rightPrec) {
- getSyntax().unparse(writer, this, call, leftPrec, rightPrec);
- }
-
- /**
- * @return function category
- */
- @Nonnull public SqlFunctionCategory getFunctionType() {
- return this.category;
- }
-
- /**
- * Returns whether this function allows a <code>DISTINCT</code> or <code>
- * ALL</code> quantifier. The default is <code>false</code>; some aggregate
- * functions return <code>true</code>.
- */
- public boolean isQuantifierAllowed() {
- return false;
- }
-
- public void validateCall(
- SqlCall call,
- SqlValidator validator,
- SqlValidatorScope scope,
- SqlValidatorScope operandScope) {
- // This implementation looks for the quantifier keywords DISTINCT or
- // ALL as the first operand in the list. If found then the literal is
- // not called to validate itself. Further the function is checked to
- // make sure that a quantifier is valid for that particular function.
- //
- // If the first operand does not appear to be a quantifier then the
- // parent ValidateCall is invoked to do normal function validation.
-
- super.validateCall(call, validator, scope, operandScope);
- validateQuantifier(validator, call);
- }
-
- /**
- * Throws a validation error if a DISTINCT or ALL quantifier is present but
- * not allowed.
- */
- protected void validateQuantifier(SqlValidator validator, SqlCall call) {
- if ((null != call.getFunctionQuantifier()) && !isQuantifierAllowed()) {
- throw validator.newValidationError(call.getFunctionQuantifier(),
- RESOURCE.functionQuantifierNotAllowed(call.getOperator().getName()));
- }
- }
-
- public RelDataType deriveType(
- SqlValidator validator,
- SqlValidatorScope scope,
- SqlCall call) {
- return deriveType(validator, scope, call, true);
- }
-
- private RelDataType deriveType(
- SqlValidator validator,
- SqlValidatorScope scope,
- SqlCall call,
- boolean convertRowArgToColumnList) {
- // Scope for operands. Usually the same as 'scope'.
- final SqlValidatorScope operandScope = scope.getOperandScope(call);
-
- // Indicate to the validator that we're validating a new function call
- validator.pushFunctionCall();
-
- final List<String> argNames = constructArgNameList(call);
-
- final List<SqlNode> args = constructOperandList(validator, call, argNames);
-
- final List<RelDataType> argTypes = constructArgTypeList(validator, scope,
- call, args, convertRowArgToColumnList);
-
- SqlFunction function =
- (SqlFunction) SqlUtil.lookupRoutine(validator.getOperatorTable(),
- getNameAsId(), argTypes, argNames, getFunctionType(),
- SqlSyntax.FUNCTION, getKind(),
- validator.getCatalogReader().nameMatcher(),
- false);
- try {
- // if we have a match on function name and parameter count, but
- // couldn't find a function with a COLUMN_LIST type, retry, but
- // this time, don't convert the row argument to a COLUMN_LIST type;
- // if we did find a match, go back and re-validate the row operands
- // (corresponding to column references), now that we can set the
- // scope to that of the source cursor referenced by that ColumnList
- // type
- if (convertRowArgToColumnList && containsRowArg(args)) {
- if (function == null
- && SqlUtil.matchRoutinesByParameterCount(
- validator.getOperatorTable(), getNameAsId(), argTypes,
- getFunctionType(),
- validator.getCatalogReader().nameMatcher())) {
- // remove the already validated node types corresponding to
- // row arguments before re-validating
- for (SqlNode operand : args) {
- if (operand.getKind() == SqlKind.ROW) {
- validator.removeValidatedNodeType(operand);
- }
- }
- return deriveType(validator, scope, call, false);
- } else if (function != null) {
- validator.validateColumnListParams(function, argTypes, args);
- }
- }
-
- if (getFunctionType() == SqlFunctionCategory.USER_DEFINED_CONSTRUCTOR) {
- return validator.deriveConstructorType(scope, call, this, function,
- argTypes);
- }
- if (function == null) {
- // try again if implicit type coercion is allowed.
- boolean changed = false;
- if (validator.isTypeCoercionEnabled()) {
- function = (SqlFunction) SqlUtil.lookupRoutine(validator.getOperatorTable(),
- getNameAsId(), argTypes, argNames, getFunctionType(), SqlSyntax.FUNCTION, getKind(),
- validator.getCatalogReader().nameMatcher(),
- true);
- // try to coerce the function arguments to the declared sql type name.
- // if we succeed, the arguments would be wrapped with CAST operator.
- if (function != null) {
- TypeCoercion typeCoercion = validator.getTypeCoercion();
- changed = typeCoercion.userDefinedFunctionCoercion(scope, call, function);
- }
- }
- if (!changed) {
- throw validator.handleUnresolvedFunction(call, this, argTypes,
- argNames);
- }
- }
-
- // REVIEW jvs 25-Mar-2005: This is, in a sense, expanding
- // identifiers, but we ignore shouldExpandIdentifiers()
- // because otherwise later validation code will
- // choke on the unresolved function.
- ((SqlBasicCall) call).setOperator(function);
- return function.validateOperands(
- validator,
- operandScope,
- call);
- } finally {
- validator.popFunctionCall();
- }
- }
-
- private boolean containsRowArg(List<SqlNode> args) {
- for (SqlNode operand : args) {
- if (operand.getKind() == SqlKind.ROW) {
- return true;
- }
- }
- return false;
- }
-}
-
-// End SqlFunction.java
diff --git a/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java b/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
deleted file mode 100644
index 414c928..0000000
--- a/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.calcite.sql.validate;
-
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.sql.SqlCall;
-import org.apache.calcite.sql.SqlIdentifier;
-import org.apache.calcite.sql.SqlNode;
-
-import java.util.Map;
-
-// This class is copied from Calcite's org.apache.calcite.sql.validate.ParameterScope,
-// can be removed after https://issues.apache.org/jira/browse/CALCITE-3476 is fixed.
-//
-// Modification:
-// - L66~L69: override resolveColumn method
-
-/**
- * A scope which contains nothing besides a few parameters. Like
- * {@link EmptyScope} (which is its base class), it has no parent scope.
- *
- * @see ParameterNamespace
- */
-public class ParameterScope extends EmptyScope {
- //~ Instance fields --------------------------------------------------------
-
- /**
- * Map from the simple names of the parameters to types of the parameters
- * ({@link RelDataType}).
- */
- private final Map<String, RelDataType> nameToTypeMap;
-
- //~ Constructors -----------------------------------------------------------
-
- public ParameterScope(
- SqlValidatorImpl validator,
- Map<String, RelDataType> nameToTypeMap) {
- super(validator);
- this.nameToTypeMap = nameToTypeMap;
- }
-
- //~ Methods ----------------------------------------------------------------
-
- public SqlQualified fullyQualify(SqlIdentifier identifier) {
- return SqlQualified.create(this, 1, null, identifier);
- }
-
- public SqlValidatorScope getOperandScope(SqlCall call) {
- return this;
- }
-
- @Override
- public RelDataType resolveColumn(String name, SqlNode ctx) {
- return nameToTypeMap.get(name);
- }
-}
-
-// End ParameterScope.java
diff --git a/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
deleted file mode 100644
index 05615f7..0000000
--- a/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ /dev/null
@@ -1,6451 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.calcite.sql.validate;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import org.apache.calcite.config.NullCollation;
-import org.apache.calcite.linq4j.Ord;
-import org.apache.calcite.linq4j.function.Function2;
-import org.apache.calcite.linq4j.function.Functions;
-import org.apache.calcite.plan.RelOptTable;
-import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.prepare.Prepare;
-import org.apache.calcite.rel.type.DynamicRecordType;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.rel.type.RelDataTypeSystem;
-import org.apache.calcite.rel.type.RelRecordType;
-import org.apache.calcite.rex.RexBuilder;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.rex.RexPatternFieldRef;
-import org.apache.calcite.rex.RexVisitor;
-import org.apache.calcite.runtime.CalciteContextException;
-import org.apache.calcite.runtime.CalciteException;
-import org.apache.calcite.runtime.Feature;
-import org.apache.calcite.runtime.Resources;
-import org.apache.calcite.schema.ColumnStrategy;
-import org.apache.calcite.schema.Table;
-import org.apache.calcite.schema.impl.ModifiableViewTable;
-import org.apache.calcite.sql.JoinConditionType;
-import org.apache.calcite.sql.JoinType;
-import org.apache.calcite.sql.SqlAccessEnum;
-import org.apache.calcite.sql.SqlAccessType;
-import org.apache.calcite.sql.SqlAggFunction;
-import org.apache.calcite.sql.SqlBasicCall;
-import org.apache.calcite.sql.SqlCall;
-import org.apache.calcite.sql.SqlCallBinding;
-import org.apache.calcite.sql.SqlDataTypeSpec;
-import org.apache.calcite.sql.SqlDelete;
-import org.apache.calcite.sql.SqlDynamicParam;
-import org.apache.calcite.sql.SqlExplain;
-import org.apache.calcite.sql.SqlFunction;
-import org.apache.calcite.sql.SqlFunctionCategory;
-import org.apache.calcite.sql.SqlIdentifier;
-import org.apache.calcite.sql.SqlInsert;
-import org.apache.calcite.sql.SqlIntervalLiteral;
-import org.apache.calcite.sql.SqlIntervalQualifier;
-import org.apache.calcite.sql.SqlJoin;
-import org.apache.calcite.sql.SqlKind;
-import org.apache.calcite.sql.SqlLiteral;
-import org.apache.calcite.sql.SqlMatchRecognize;
-import org.apache.calcite.sql.SqlMerge;
-import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlNodeList;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlOperatorTable;
-import org.apache.calcite.sql.SqlOrderBy;
-import org.apache.calcite.sql.SqlSampleSpec;
-import org.apache.calcite.sql.SqlSelect;
-import org.apache.calcite.sql.SqlSelectKeyword;
-import org.apache.calcite.sql.SqlSnapshot;
-import org.apache.calcite.sql.SqlSyntax;
-import org.apache.calcite.sql.SqlUnresolvedFunction;
-import org.apache.calcite.sql.SqlUpdate;
-import org.apache.calcite.sql.SqlUtil;
-import org.apache.calcite.sql.SqlWindow;
-import org.apache.calcite.sql.SqlWith;
-import org.apache.calcite.sql.SqlWithItem;
-import org.apache.calcite.sql.fun.SqlCase;
-import org.apache.calcite.sql.fun.SqlStdOperatorTable;
-import org.apache.calcite.sql.parser.SqlParserPos;
-import org.apache.calcite.sql.type.AssignableOperandTypeChecker;
-import org.apache.calcite.sql.type.ReturnTypes;
-import org.apache.calcite.sql.type.SqlOperandTypeInference;
-import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.calcite.sql.type.SqlTypeUtil;
-import org.apache.calcite.sql.util.SqlBasicVisitor;
-import org.apache.calcite.sql.util.SqlShuttle;
-import org.apache.calcite.sql.util.SqlVisitor;
-import org.apache.calcite.sql.validate.implicit.TypeCoercion;
-import org.apache.calcite.sql.validate.implicit.TypeCoercions;
-import org.apache.calcite.sql2rel.InitializerContext;
-import org.apache.calcite.util.BitString;
-import org.apache.calcite.util.Bug;
-import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.calcite.util.ImmutableIntList;
-import org.apache.calcite.util.ImmutableNullableList;
-import org.apache.calcite.util.Litmus;
-import org.apache.calcite.util.Pair;
-import org.apache.calcite.util.Static;
-import org.apache.calcite.util.Util;
-import org.apache.calcite.util.trace.CalciteTrace;
-import org.slf4j.Logger;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-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 static org.apache.calcite.sql.SqlUtil.stripAs;
-import static org.apache.calcite.util.Static.RESOURCE;
-
-/*
- * THIS FILE HAS BEEN COPIED FROM THE APACHE CALCITE PROJECT UNTIL CALCITE-2707 IS FIXED.
- * (Added lines: 6016-6022)
- */
-
-/**
- * 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<String, IdInfo> idPositions = new HashMap<>();
-
- /**
- * Maps {@link SqlNode query node} objects to the {@link SqlValidatorScope}
- * scope created from them.
- */
- protected final Map<SqlNode, SqlValidatorScope> scopes =
- new IdentityHashMap<>();
-
- /**
- * Maps a {@link SqlSelect} node to the scope used by its WHERE and HAVING
- * clauses.
- */
- private final Map<SqlSelect, SqlValidatorScope> whereScopes =
- new IdentityHashMap<>();
-
- /**
- * Maps a {@link SqlSelect} node to the scope used by its GROUP BY clause.
- */
- private final Map<SqlSelect, SqlValidatorScope> groupByScopes =
- new IdentityHashMap<>();
-
- /**
- * Maps a {@link SqlSelect} node to the scope used by its SELECT and HAVING
- * clauses.
- */
- private final Map<SqlSelect, SqlValidatorScope> selectScopes =
- new IdentityHashMap<>();
-
- /**
- * Maps a {@link SqlSelect} node to the scope used by its ORDER BY clause.
- */
- private final Map<SqlSelect, SqlValidatorScope> 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<SqlSelect, SqlValidatorScope> 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<SqlNode, SqlValidatorNamespace> 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<SqlNode> 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<FunctionParamInfo> 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<SqlNode, RelDataType> nodeToTypeMap =
- new IdentityHashMap<>();
- private final AggFinder aggFinder;
- private final AggFinder aggOrOverFinder;
- private final AggFinder aggOrOverOrGroupFinder;
- private final AggFinder groupFinder;
- private final AggFinder overFinder;
- private final SqlConformance conformance;
- private final Map<SqlNode, SqlNode> originalExprs = new HashMap<>();
-
- private SqlNode top;
-
- // REVIEW jvs 30-June-2006: subclasses may override shouldExpandIdentifiers
- // in a way that ignores this; we should probably get rid of the protected
- // method and always use this variable (or better, move preferences like
- // this to a separate "parameter" class)
- protected boolean expandIdentifiers;
-
- protected boolean expandColumnReferences;
-
- private boolean rewriteCalls;
-
- 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 ValidationErrorFunction validationErrorFunction =
- new ValidationErrorFunction();
-
- // TypeCoercion instance used for implicit type coercion.
- private TypeCoercion typeCoercion;
-
- // Flag saying if we enable the implicit type coercion.
- private boolean enableTypeCoercion;
-
- //~ Constructors -----------------------------------------------------------
-
- /**
- * Creates a validator.
- *
- * @param opTab Operator table
- * @param catalogReader Catalog reader
- * @param typeFactory Type factory
- * @param conformance Compatibility mode
- */
- protected SqlValidatorImpl(
- SqlOperatorTable opTab,
- SqlValidatorCatalogReader catalogReader,
- RelDataTypeFactory typeFactory,
- SqlConformance conformance) {
- this.opTab = Objects.requireNonNull(opTab);
- this.catalogReader = Objects.requireNonNull(catalogReader);
- this.typeFactory = Objects.requireNonNull(typeFactory);
- this.conformance = Objects.requireNonNull(conformance);
-
- unknownType = typeFactory.createUnknownType();
- booleanType = typeFactory.createSqlType(SqlTypeName.BOOLEAN);
-
- rewriteCalls = true;
- expandColumnReferences = true;
- 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.enableTypeCoercion = catalogReader.getConfig() == null
- || catalogReader.getConfig().typeCoercion();
- this.typeCoercion = TypeCoercions.getTypeCoercion(this, conformance);
- }
-
- //~ Methods ----------------------------------------------------------------
-
- public SqlConformance getConformance() {
- return conformance;
- }
-
- 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<SqlNode> list = new ArrayList<>();
- final List<Map.Entry<String, RelDataType>> 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<Integer, SqlSelect> 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<String, String> parentCursorMap =
- funcParamInfo.columnListParamToParentCursorMap;
- return parentCursorMap.get(columnListParamName);
- }
-
- /**
- * If <code>selectItem</code> 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<SqlNode> selectItems,
- Set<String> aliases,
- List<Map.Entry<String, RelDataType>> 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 = expand(selectItem, scope);
- 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 boolean expandStar(List<SqlNode> selectItems, Set<String> aliases,
- List<Map.Entry<String, RelDataType>> 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<String, RelDataType> 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 currentType.equals(desiredType)
- || (currentType.isNullable() != desiredType.isNullable()
- && typeFactory.createTypeWithNullability(currentType,
- desiredType.isNullable()).equals(desiredType))
- ? node
- : SqlStdOperatorTable.CAST.createCall(SqlParserPos.ZERO,
- node, SqlTypeUtil.convertTypeToSpec(desiredType));
- }
-
- private boolean addOrExpandField(List<SqlNode> selectItems, Set<String> aliases,
- List<Map.Entry<String, RelDataType>> 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<SqlMoniker> 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<SqlMoniker> 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 completion 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
- * completion 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<SqlMoniker> 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<SqlMoniker> 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<SqlMoniker> 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<SqlMoniker> 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<SqlMoniker> 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<String> names,
- SqlParserPos pos,
- Collection<SqlMoniker> hintList) {
- // Remove the last part of name - it is a dummy
- List<String> 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 completion 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<String> names,
- SqlValidator validator,
- Collection<SqlMoniker> result) {
- final List<SqlMoniker> 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<String> names,
- SqlValidator validator,
- Collection<SqlMoniker> 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.replaceAll("'", "");
- result.add(
- new SqlMonikerImpl(
- sig,
- SqlMonikerType.FUNCTION));
- continue;
- }
- result.add(
- new SqlMonikerImpl(
- op.getName(),
- SqlMonikerType.FUNCTION));
- }
- }
- }
- }
-
- public SqlNode validateParameterizedExpression(
- SqlNode topNode,
- final Map<String, RelDataType> 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<SqlNode> 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);
-
- 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<String> qualifiedName = ns.getTable().getQualifiedName();
- String tableName = qualifiedName.get(qualifiedName.size() - 1);
- throw newValidationError(snapshot.getTableRef(),
- Static.RESOURCE.notTemporalTable(tableName));
- }
- }
- }
-
- /**
- * 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 EXTEND:
- final SqlIdentifier id = (SqlIdentifier) call.getOperandList().get(0);
- final DelegatingScope idScope = (DelegatingScope) scope;
- return getNamespace(id, idScope);
- case AS:
- final SqlNode nested = call.getOperandList().get(0);
- switch (nested.getKind()) {
- 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 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<SqlNode> 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<SqlOperator> 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 (rewriteCalls) {
- 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);
- }
-
- 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<SqlNode> 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);
- }
-
- 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);
- }
-
- 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);
- 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);
- 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);
- 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);
- }
-
- /**
- * 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);
- }
-
- /**
- * Returns null if there is no common type. E.g. if the rows have a
- * different number of columns.
- */
- RelDataType getTableConstructorRowType(
- SqlCall values,
- SqlValidatorScope scope) {
- final List<SqlNode> rows = values.getOperandList();
- assert rows.size() >= 1;
- final List<RelDataType> 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<String> aliasList = new ArrayList<>();
- final List<RelDataType> typeList = new ArrayList<>();
- for (Ord<SqlNode> 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.
- *
- * <p>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<SqlOperator> 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<RelDataType> 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 (shouldExpandIdentifiers()) {
- 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<RelDataType> argTypes,
- List<String> argNames) {
- // For builtins, we can give a better error message
- final List<SqlOperator> 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) {
- 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<SqlNode> 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<SqlNode> list,
- Set<String> aliases,
- List<Map.Entry<String, RelDataType>> fieldList,
- SqlNode exp,
- SqlValidatorScope 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.expandIdentifiers = expandIdentifiers;
- }
-
- // implement SqlValidator
- public void setColumnReferenceExpansion(
- boolean expandColumnReferences) {
- this.expandColumnReferences = expandColumnReferences;
- }
-
- // implement SqlValidator
- public boolean getColumnReferenceExpansion() {
- return expandColumnReferences;
- }
-
- public void setDefaultNullCollation(NullCollation nullCollation) {
- this.nullCollation = Objects.requireNonNull(nullCollation);
- }
-
- public NullCollation getDefaultNullCollation() {
- return nullCollation;
- }
-
- // implement SqlValidator
- public void setCallRewrite(boolean rewriteCalls) {
- this.rewriteCalls = rewriteCalls;
- }
-
- public boolean shouldExpandIdentifiers() {
- return expandIdentifiers;
- }
-
- 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.
- *
- * <p>{@code parentScope} and {@code usingScope} are often the same. They
- * differ when the namespace are not visible within the parent. (Example
- * needed.)
- *
- * <p>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 (<code>AS alias</code>) 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 (shouldExpandIdentifiers()) {
- 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 (shouldExpandIdentifiers()) {
- // 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 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(
- tableScope == null ? parentScope : tableScope,
- usingScope,
- register,
- operand,
- enclosingNode,
- alias,
- extendList,
- forceNullable,
- true);
- if (newOperand != operand) {
- call.setOperand(0, newOperand);
- }
- scopes.put(node, parentScope);
- 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 <code>SELECT</code> 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 (<code>UNION</code>, <code>
- * INTERSECT</code>, or <code>EXCEPT</code>). 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<SqlNode> 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.
- *
- * <p>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<SqlNode> 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 (expandColumnReferences) {
- // 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:
- 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<String> naturalColumnNames =
- SqlValidatorUtil.deriveNaturalJoinColumnList(nameMatcher,
- leftRowType, rightRowType);
-
- // Check compatibility 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 (!conformance.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<String> 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 (conformance.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<SqlNode> children = ((SqlCall) 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<String, String> findTableColumnPair(SqlIdentifier identifier,
- SqlValidatorScope scope) {
- final SqlCall call = makeNullaryCall(identifier);
- if (call != null) {
- return null;
- }
- SqlQualified qualified = scope.fullyQualify(identifier);
- List<String> 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<String, String> 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<String, String> 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<SqlWindow> 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 components
- 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<String> 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);
- }
-
- @Override
- public SqlValidator setEnableTypeCoercion(boolean enabled) {
- this.enableTypeCoercion = enabled;
- return this;
- }
-
- @Override
- public boolean isTypeCoercionEnabled() {
- return this.enableTypeCoercion;
- }
-
- @Override
- public void setTypeCoercion(TypeCoercion typeCoercion) {
- Objects.requireNonNull(typeCoercion);
- this.typeCoercion = typeCoercion;
- }
-
- @Override
- public TypeCoercion getTypeCoercion() {
- assert isTypeCoercionEnabled();
- return this.typeCoercion;
- }
-
- /**
- * 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<SqlNode> 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<SqlNode> 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 (getConformance().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<SqlNode> expandedSelectItems = new ArrayList<>();
- final Set<String> aliases = new HashSet<>();
- final List<Map.Entry<String, RelDataType>> fieldList = new ArrayList<>();
-
- for (int i = 0; i < selectItems.size(); i++) {
- SqlNode selectItem = selectItems.get(i);
- if (selectItem instanceof SqlSelect) {
- handleScalarSubQuery(
- select,
- (SqlSelect) selectItem,
- expandedSelectItems,
- aliases,
- fieldList);
- } else {
- expandSelectItem(
- selectItem,
- select,
- targetRowType.isStruct()
- && targetRowType.getFieldCount() >= i
- ? targetRowType.getFieldList().get(i).getType()
- : unknownType,
- 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 (shouldExpandIdentifiers()) {
- 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<SqlNode> expandedSelectItems,
- Set<String> aliasList,
- List<Map.Entry<String, RelDataType>> 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 <code>
- * baseRowType</code>
- * @return Rowtype
- */
- protected RelDataType createTargetRowType(
- SqlValidatorTable table,
- SqlNodeList targetColumnList,
- boolean append) {
- RelDataType baseRowType = table.getRowType();
- if (targetColumnList == null) {
- return baseRowType;
- }
- List<RelDataTypeField> targetFields = baseRowType.getFieldList();
- final List<Map.Entry<String, RelDataType>> fields = new ArrayList<>();
- if (append) {
- for (RelDataTypeField targetField : targetFields) {
- fields.add(
- Pair.of(SqlUtil.deriveAliasFromOrdinal(fields.size()),
- targetField.getType()));
- }
- }
- final Set<Integer> 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);
-
- checkFieldCount(insert.getTargetTable(), table, source,
- logicalSourceRowType, logicalTargetRowType);
-
- checkTypeAssignment(logicalSourceRowType, logicalTargetRowType, insert);
-
- checkConstraint(table, source, logicalTargetRowType);
-
- validateAccess(insert.getTargetTable(), table, SqlAccessEnum.INSERT);
- }
-
- /**
- * 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<RelDataTypeField> tableFields = tableRowType.getFieldList();
-
- // Get the mapping from column indexes of the underlying table
- // to the target columns and view constraints.
- final Map<Integer, RelDataTypeField> tableIndexToTargetField =
- SqlValidatorUtil.getIndexToFieldMap(tableFields, targetRowType);
- final Map<Integer, RexNode> 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<SqlNode> 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<Integer, RexNode> projectMap =
- RelOptUtil.getColumnConstraints(modifiableViewTable, targetRowType,
- typeFactory);
- final Map<String, Integer> nameToIndex =
- SqlValidatorUtil.mapNameToIndex(tableRowType.getFieldList());
-
- // Validate update values against the view constraint.
- final List<SqlNode> targets = update.getTargetColumnList().getList();
- final List<SqlNode> sources = update.getSourceExpressionList().getList();
- for (final Pair<SqlNode, SqlNode> 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);
- }
- }
- }
- }
-
- private void checkFieldCount(SqlNode node, SqlValidatorTable table,
- SqlNode source, RelDataType logicalSourceRowType,
- RelDataType logicalTargetRowType) {
- final int sourceFieldCount = logicalSourceRowType.getFieldCount();
- final int targetFieldCount = logicalTargetRowType.getFieldCount();
- if (sourceFieldCount != targetFieldCount) {
- throw newValidationError(node,
- RESOURCE.unmatchInsertColumn(targetFieldCount, sourceFieldCount));
- }
- // Ensure that non-nullable fields are targeted.
- final InitializerContext rexBuilder =
- new InitializerContext() {
- public RexBuilder getRexBuilder() {
- return new RexBuilder(typeFactory);
- }
-
- public RexNode convertExpression(SqlNode e) {
- throw new UnsupportedOperationException();
- }
- };
- final List<ColumnStrategy> strategies =
- table.unwrap(RelOptTable.class).getColumnStrategies();
- for (final RelDataTypeField field : table.getRowType().getFieldList()) {
- final RelDataTypeField targetField =
- logicalTargetRowType.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
- && conformance.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;
- }
-
- protected void checkTypeAssignment(
- 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
- List<RelDataTypeField> sourceFields = sourceRowType.getFieldList();
- List<RelDataTypeField> 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)) {
- // FRG-255: account for UPDATE rewrite; there's
- // probably a better way to do this.
- int iAdjusted = i;
- if (query instanceof SqlUpdate) {
- int nUpdateColumns =
- ((SqlUpdate) query).getTargetColumnList().size();
- assert sourceFields.size() >= nUpdateColumns;
- iAdjusted -= sourceFields.size() - nUpdateColumns;
- }
- SqlNode node = getNthExpr(query, iAdjusted, sourceCount);
- 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.getTargetColumnList() != null) {
- return update.getTargetColumnList().get(ordinal);
- } else 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 = getNamespace(call).getRowType();
- checkTypeAssignment(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 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<SqlNode> 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 (conformance.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<SqlNode, RelDataTypeField> 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 compatible.
- // 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 compatible
- for (int col = 0; col < columnCount; col++) {
- final int c = col;
- final RelDataType type =
- typeFactory.leastRestrictive(
- new AbstractList<RelDataType>() {
- 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<CalciteContextException> {
- private final SqlNode sqlNode;
- private final Resources.ExInst<SqlValidatorException> validatorException;
-
- ValidationError(SqlNode sqlNode,
- Resources.ExInst<SqlValidatorException> 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<SqlNode, Resources.ExInst<SqlValidatorException>,
- CalciteContextException> {
- @Override public CalciteContextException apply(
- SqlNode v0, Resources.ExInst<SqlValidatorException> v1) {
- return newValidationError(v0, v1);
- }
- }
-
- public ValidationErrorFunction getValidationErrorFunction() {
- return validationErrorFunction;
- }
-
- public CalciteContextException newValidationError(SqlNode node,
- Resources.ExInst<SqlValidatorException> 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,
- boolean populateBounds) {
- 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);
- }
-
- if (populateBounds) {
- window.populateBounds();
- }
- 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<SqlNode> 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<Map.Entry<String, RelDataType>> measureColumns =
- validateMeasure(matchRecognize, scope, allRows);
- for (Map.Entry<String, RelDataType> 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<Map.Entry<String, RelDataType>> validateMeasure(SqlMatchRecognize mr,
- MatchRecognizeScope scope, boolean allRows) {
- final List<String> aliases = new ArrayList<>();
- final List<SqlNode> sqlNodes = new ArrayList<>();
- final SqlNodeList measures = mr.getMeasureList();
- final List<Map.Entry<String, RelDataType>> 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<String> prefix = node.accept(new PatternValidator(true));
- Util.discard(prefix);
- final List<SqlNode> 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<String> 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<SqlNode> 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<String> 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()
- && !conformance.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 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<List<String>> 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<String>> list = new ArrayList<>();
- for (int i = 0; i < fieldCount; i++) {
- list.add(getFieldOrigin(sqlQuery, i));
- }
- return ImmutableNullableList.copyOf(list);
- }
-
- private List<String> getFieldOrigin(SqlNode sqlQuery, int i) {
- if (sqlQuery instanceof SqlSelect) {
- SqlSelect sqlSelect = (SqlSelect) sqlQuery;
- final SelectScope scope = getRawSelectScope(sqlSelect);
- final List<SqlNode> 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<String> 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<RelDataType> 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<SqlNode> 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<String>() {
- @Override public String get(int index) {
- return "?" + index;
- }
-
- @Override public int size() {
- return types.size();
- }
- });
- }
-
- public void validateColumnListParams(
- SqlFunction function,
- List<RelDataType> argTypes,
- List<SqlNode> 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<Void> {
- 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}.
- *
- * <p>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<RelDataType> {
- 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 components.
- 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<SqlNode> 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<String> 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 && getConformance().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 <code>ordinal</code>th item in the select list.
- */
- private SqlNode nthSelectItem(int ordinal, final SqlParserPos pos) {
- // TODO: Don't expand the list every time. Maybe keep an expanded
- // version of each expression -- select lists and identifiers -- in
- // the validator.
-
- SqlNodeList expandedSelectList =
- expandStar(
- 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()
- && getConformance().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);
- }
- }
-
- /**
- * 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.getConformance().isHavingAlias()
- : validator.getConformance().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;
- }
- return super.visit(id);
- }
-
- public SqlNode visit(SqlLiteral literal) {
- if (havingExpr || !validator.getConformance().isGroupByOrdinal()) {
- return super.visit(literal);
- }
- boolean isOrdinalLiteral = literal == root;
- switch (root.getKind()) {
- case GROUPING_SETS:
- case ROLLUP:
- case CUBE:
- if (root instanceof SqlBasicCall) {
- List<SqlNode> 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<Integer, SqlSelect> cursorPosToSelectMap;
-
- /**
- * Maps a column list parameter to the parent cursor parameter it
- * references. The parameters are id'd by their names.
- */
- public final Map<String, String> 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.
- *
- * <p>Examples:
- *
- * <ul>
- * <li>{@code PREV(A.price + A.amount)} →
- * {@code PREV(A.price) + PREV(A.amount)}
- *
- * <li>{@code FIRST(A.price * 2)} → {@code FIRST(A.PRICE) * 2}
- * </ul>
- */
- 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<SqlNode> operands = call.getOperandList();
- List<SqlNode> newOperands = new ArrayList<>();
-
- // This code is a workaround for CALCITE-2707
- if (call.getFunctionQuantifier() != null
- && call.getFunctionQuantifier().getValue() == SqlSelectKeyword.DISTINCT) {
- final SqlParserPos pos = call.getParserPosition();
- throw SqlUtil.newContextException(pos, Static.RESOURCE.functionQuantifierNotAllowed(call.toString()));
- }
- // This code is a workaround for CALCITE-2707
-
- 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<SqlNode> 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)}.
- *
- * <p>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 comparisons 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<SqlNode> 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<Set<String>> {
- 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<String> visit(SqlCall call) {
- boolean isSingle = false;
- Set<String> vars = new HashSet<>();
- SqlKind kind = call.getKind();
- List<SqlNode> 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<String> visit(SqlIdentifier identifier) {
- boolean check = prevNextCount > 0 || firstLastCount > 0 || aggregateCount > 0;
- Set<String> vars = new HashSet<>();
- if (identifier.names.size() > 1 && check) {
- vars.add(identifier.names.get(0));
- }
- return vars;
- }
-
- @Override public Set<String> visit(SqlLiteral literal) {
- return ImmutableSet.of();
- }
-
- @Override public Set<String> visit(SqlIntervalQualifier qualifier) {
- return ImmutableSet.of();
- }
-
- @Override public Set<String> visit(SqlDataTypeSpec type) {
- return ImmutableSet.of();
- }
-
- @Override public Set<String> visit(SqlDynamicParam param) {
- return ImmutableSet.of();
- }
- }
-
- /** Permutation of fields in NATURAL JOIN or USING. */
- private class Permute {
- final List<ImmutableIntList> 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<String> names = usingNames(join);
- final List<ImmutableIntList> sources = new ArrayList<>();
- final Set<ImmutableIntList> 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);
- }
-
- /** Returns the set of field names in the join condition specified by USING
- * or implicitly by NATURAL, de-duplicated and in order. */
- private List<String> usingNames(SqlJoin join) {
- switch (join.getConditionType()) {
- case USING:
- final ImmutableList.Builder<String> list = ImmutableList.builder();
- final Set<String> 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;
- }
-
- /** Moves fields according to the permutation. */
- public void permute(List<SqlNode> selectItems,
- List<Map.Entry<String, RelDataType>> fields) {
- if (trivial) {
- return;
- }
-
- final List<SqlNode> oldSelectItems = ImmutableList.copyOf(selectItems);
- selectItems.clear();
- final List<Map.Entry<String, RelDataType>> oldFields =
- ImmutableList.copyOf(fields);
- fields.clear();
- for (ImmutableIntList source : sources) {
- final int p0 = source.get(0);
- Map.Entry<String, RelDataType> 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<String, RelDataType> 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 completed (perhaps unsuccessfully).
- */
- VALID
- }
-
-}
-
-// End SqlValidatorImpl.java
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/SqlFunction.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/SqlFunction.java
deleted file mode 100644
index 5bb2bf5..0000000
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/SqlFunction.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.calcite.sql;
-
-import org.apache.calcite.linq4j.function.Functions;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.sql.type.SqlOperandTypeChecker;
-import org.apache.calcite.sql.type.SqlOperandTypeInference;
-import org.apache.calcite.sql.type.SqlReturnTypeInference;
-import org.apache.calcite.sql.validate.SqlValidator;
-import org.apache.calcite.sql.validate.SqlValidatorScope;
-import org.apache.calcite.sql.validate.implicit.TypeCoercion;
-import org.apache.calcite.util.Util;
-
-import com.google.common.collect.ImmutableList;
-
-import java.util.List;
-import java.util.Objects;
-import javax.annotation.Nonnull;
-
-import static org.apache.calcite.util.Static.RESOURCE;
-
-/**
- * A <code>SqlFunction</code> is a type of operator which has conventional
- * function-call syntax.
- *
- * <p>This file is copied from Apache Calcite and should be removed once CALCITE-3360
- * has been fixed. Changed lines: 279-293
- */
-public class SqlFunction extends SqlOperator {
-
- //~ Instance fields --------------------------------------------------------
-
- private final SqlFunctionCategory category;
-
- private final SqlIdentifier sqlIdentifier;
-
- private final List<RelDataType> paramTypes;
-
- //~ Constructors -----------------------------------------------------------
-
- /**
- * Creates a new SqlFunction for a call to a builtin function.
- *
- * @param name Name of builtin function
- * @param kind kind of operator implemented by function
- * @param returnTypeInference strategy to use for return type inference
- * @param operandTypeInference strategy to use for parameter type inference
- * @param operandTypeChecker strategy to use for parameter type checking
- * @param category categorization for function
- */
- public SqlFunction(
- String name,
- SqlKind kind,
- SqlReturnTypeInference returnTypeInference,
- SqlOperandTypeInference operandTypeInference,
- SqlOperandTypeChecker operandTypeChecker,
- SqlFunctionCategory category) {
- // We leave sqlIdentifier as null to indicate
- // that this is a builtin. Same for paramTypes.
- this(name, null, kind, returnTypeInference, operandTypeInference,
- operandTypeChecker, null, category);
-
- assert !((category == SqlFunctionCategory.USER_DEFINED_CONSTRUCTOR)
- && (returnTypeInference == null));
- }
-
- /**
- * Creates a placeholder SqlFunction for an invocation of a function with a
- * possibly qualified name. This name must be resolved into either a builtin
- * function or a user-defined function.
- *
- * @param sqlIdentifier possibly qualified identifier for function
- * @param returnTypeInference strategy to use for return type inference
- * @param operandTypeInference strategy to use for parameter type inference
- * @param operandTypeChecker strategy to use for parameter type checking
- * @param paramTypes array of parameter types
- * @param funcType function category
- */
- public SqlFunction(
- SqlIdentifier sqlIdentifier,
- SqlReturnTypeInference returnTypeInference,
- SqlOperandTypeInference operandTypeInference,
- SqlOperandTypeChecker operandTypeChecker,
- List<RelDataType> paramTypes,
- SqlFunctionCategory funcType) {
- this(Util.last(sqlIdentifier.names), sqlIdentifier, SqlKind.OTHER_FUNCTION,
- returnTypeInference, operandTypeInference, operandTypeChecker,
- paramTypes, funcType);
- }
-
- /**
- * Internal constructor.
- */
- protected SqlFunction(
- String name,
- SqlIdentifier sqlIdentifier,
- SqlKind kind,
- SqlReturnTypeInference returnTypeInference,
- SqlOperandTypeInference operandTypeInference,
- SqlOperandTypeChecker operandTypeChecker,
- List<RelDataType> paramTypes,
- SqlFunctionCategory category) {
- super(name, kind, 100, 100, returnTypeInference, operandTypeInference,
- operandTypeChecker);
-
- this.sqlIdentifier = sqlIdentifier;
- this.category = Objects.requireNonNull(category);
- this.paramTypes =
- paramTypes == null ? null : ImmutableList.copyOf(paramTypes);
- }
-
- //~ Methods ----------------------------------------------------------------
-
- public SqlSyntax getSyntax() {
- return SqlSyntax.FUNCTION;
- }
-
- /**
- * @return fully qualified name of function, or null for a builtin function
- */
- public SqlIdentifier getSqlIdentifier() {
- return sqlIdentifier;
- }
-
- @Override public SqlIdentifier getNameAsId() {
- if (sqlIdentifier != null) {
- return sqlIdentifier;
- }
- return super.getNameAsId();
- }
-
- /**
- * @return array of parameter types, or null for builtin function
- */
- public List<RelDataType> getParamTypes() {
- return paramTypes;
- }
-
- /**
- * Returns a list of parameter names.
- *
- * <p>The default implementation returns {@code [arg0, arg1, ..., argN]}.
- */
- public List<String> getParamNames() {
- return Functions.generate(paramTypes.size(), i -> "arg" + i);
- }
-
- public void unparse(
- SqlWriter writer,
- SqlCall call,
- int leftPrec,
- int rightPrec) {
- getSyntax().unparse(writer, this, call, leftPrec, rightPrec);
- }
-
- /**
- * @return function category
- */
- @Nonnull public SqlFunctionCategory getFunctionType() {
- return this.category;
- }
-
- /**
- * Returns whether this function allows a <code>DISTINCT</code> or <code>
- * ALL</code> quantifier. The default is <code>false</code>; some aggregate
- * functions return <code>true</code>.
- */
- public boolean isQuantifierAllowed() {
- return false;
- }
-
- public void validateCall(
- SqlCall call,
- SqlValidator validator,
- SqlValidatorScope scope,
- SqlValidatorScope operandScope) {
- // This implementation looks for the quantifier keywords DISTINCT or
- // ALL as the first operand in the list. If found then the literal is
- // not called to validate itself. Further the function is checked to
- // make sure that a quantifier is valid for that particular function.
- //
- // If the first operand does not appear to be a quantifier then the
- // parent ValidateCall is invoked to do normal function validation.
-
- super.validateCall(call, validator, scope, operandScope);
- validateQuantifier(validator, call);
- }
-
- /**
- * Throws a validation error if a DISTINCT or ALL quantifier is present but
- * not allowed.
- */
- protected void validateQuantifier(SqlValidator validator, SqlCall call) {
- if ((null != call.getFunctionQuantifier()) && !isQuantifierAllowed()) {
- throw validator.newValidationError(call.getFunctionQuantifier(),
- RESOURCE.functionQuantifierNotAllowed(call.getOperator().getName()));
- }
- }
-
- public RelDataType deriveType(
- SqlValidator validator,
- SqlValidatorScope scope,
- SqlCall call) {
- return deriveType(validator, scope, call, true);
- }
-
- private RelDataType deriveType(
- SqlValidator validator,
- SqlValidatorScope scope,
- SqlCall call,
- boolean convertRowArgToColumnList) {
- // Scope for operands. Usually the same as 'scope'.
- final SqlValidatorScope operandScope = scope.getOperandScope(call);
-
- // Indicate to the validator that we're validating a new function call
- validator.pushFunctionCall();
-
- final List<String> argNames = constructArgNameList(call);
-
- final List<SqlNode> args = constructOperandList(validator, call, argNames);
-
- final List<RelDataType> argTypes = constructArgTypeList(validator, scope,
- call, args, convertRowArgToColumnList);
-
- SqlFunction function =
- (SqlFunction) SqlUtil.lookupRoutine(validator.getOperatorTable(),
- getNameAsId(), argTypes, argNames, getFunctionType(),
- SqlSyntax.FUNCTION, getKind(),
- validator.getCatalogReader().nameMatcher(),
- false);
- try {
- // if we have a match on function name and parameter count, but
- // couldn't find a function with a COLUMN_LIST type, retry, but
- // this time, don't convert the row argument to a COLUMN_LIST type;
- // if we did find a match, go back and re-validate the row operands
- // (corresponding to column references), now that we can set the
- // scope to that of the source cursor referenced by that ColumnList
- // type
- if (convertRowArgToColumnList && containsRowArg(args)) {
- if (function == null
- && SqlUtil.matchRoutinesByParameterCount(
- validator.getOperatorTable(), getNameAsId(), argTypes,
- getFunctionType(),
- validator.getCatalogReader().nameMatcher())) {
- // remove the already validated node types corresponding to
- // row arguments before re-validating
- for (SqlNode operand : args) {
- if (operand.getKind() == SqlKind.ROW) {
- validator.removeValidatedNodeType(operand);
- }
- }
- return deriveType(validator, scope, call, false);
- } else if (function != null) {
- validator.validateColumnListParams(function, argTypes, args);
- }
- }
-
- if (getFunctionType() == SqlFunctionCategory.USER_DEFINED_CONSTRUCTOR) {
- return validator.deriveConstructorType(scope, call, this, function,
- argTypes);
- }
- if (function == null) {
- // try again if implicit type coercion is allowed.
- boolean changed = false;
- if (validator.isTypeCoercionEnabled()) {
- function = (SqlFunction) SqlUtil.lookupRoutine(validator.getOperatorTable(),
- getNameAsId(), argTypes, argNames, getFunctionType(), SqlSyntax.FUNCTION, getKind(),
- validator.getCatalogReader().nameMatcher(),
- true);
- // try to coerce the function arguments to the declared sql type name.
- // if we succeed, the arguments would be wrapped with CAST operator.
- if (function != null) {
- TypeCoercion typeCoercion = validator.getTypeCoercion();
- changed = typeCoercion.userDefinedFunctionCoercion(scope, call, function);
- }
- }
- if (!changed) {
- throw validator.handleUnresolvedFunction(call, this, argTypes,
- argNames);
- }
- }
-
- // REVIEW jvs 25-Mar-2005: This is, in a sense, expanding
- // identifiers, but we ignore shouldExpandIdentifiers()
- // because otherwise later validation code will
- // choke on the unresolved function.
- ((SqlBasicCall) call).setOperator(function);
- return function.validateOperands(
- validator,
- operandScope,
- call);
- } finally {
- validator.popFunctionCall();
- }
- }
-
- private boolean containsRowArg(List<SqlNode> args) {
- for (SqlNode operand : args) {
- if (operand.getKind() == SqlKind.ROW) {
- return true;
- }
- }
- return false;
- }
-}
-
-// End SqlFunction.java
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
deleted file mode 100644
index 414c928..0000000
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.calcite.sql.validate;
-
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.sql.SqlCall;
-import org.apache.calcite.sql.SqlIdentifier;
-import org.apache.calcite.sql.SqlNode;
-
-import java.util.Map;
-
-// This class is copied from Calcite's org.apache.calcite.sql.validate.ParameterScope,
-// can be removed after https://issues.apache.org/jira/browse/CALCITE-3476 is fixed.
-//
-// Modification:
-// - L66~L69: override resolveColumn method
-
-/**
- * A scope which contains nothing besides a few parameters. Like
- * {@link EmptyScope} (which is its base class), it has no parent scope.
- *
- * @see ParameterNamespace
- */
-public class ParameterScope extends EmptyScope {
- //~ Instance fields --------------------------------------------------------
-
- /**
- * Map from the simple names of the parameters to types of the parameters
- * ({@link RelDataType}).
- */
- private final Map<String, RelDataType> nameToTypeMap;
-
- //~ Constructors -----------------------------------------------------------
-
- public ParameterScope(
- SqlValidatorImpl validator,
- Map<String, RelDataType> nameToTypeMap) {
- super(validator);
- this.nameToTypeMap = nameToTypeMap;
- }
-
- //~ Methods ----------------------------------------------------------------
-
- public SqlQualified fullyQualify(SqlIdentifier identifier) {
- return SqlQualified.create(this, 1, null, identifier);
- }
-
- public SqlValidatorScope getOperandScope(SqlCall call) {
- return this;
- }
-
- @Override
- public RelDataType resolveColumn(String name, SqlNode ctx) {
- return nameToTypeMap.get(name);
- }
-}
-
-// End ParameterScope.java
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
deleted file mode 100644
index c3ccc3f..0000000
--- a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ /dev/null
@@ -1,6452 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.calcite.sql.validate;
-
-import org.apache.calcite.config.NullCollation;
-import org.apache.calcite.linq4j.Ord;
-import org.apache.calcite.linq4j.function.Function2;
-import org.apache.calcite.linq4j.function.Functions;
-import org.apache.calcite.plan.RelOptTable;
-import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.prepare.Prepare;
-import org.apache.calcite.rel.type.DynamicRecordType;
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.rel.type.RelDataTypeSystem;
-import org.apache.calcite.rel.type.RelRecordType;
-import org.apache.calcite.rex.RexBuilder;
-import org.apache.calcite.rex.RexNode;
-import org.apache.calcite.rex.RexPatternFieldRef;
-import org.apache.calcite.rex.RexVisitor;
-import org.apache.calcite.runtime.CalciteContextException;
-import org.apache.calcite.runtime.CalciteException;
-import org.apache.calcite.runtime.Feature;
-import org.apache.calcite.runtime.Resources;
-import org.apache.calcite.schema.ColumnStrategy;
-import org.apache.calcite.schema.Table;
-import org.apache.calcite.schema.impl.ModifiableViewTable;
-import org.apache.calcite.sql.JoinConditionType;
-import org.apache.calcite.sql.JoinType;
-import org.apache.calcite.sql.SqlAccessEnum;
-import org.apache.calcite.sql.SqlAccessType;
-import org.apache.calcite.sql.SqlAggFunction;
-import org.apache.calcite.sql.SqlBasicCall;
-import org.apache.calcite.sql.SqlCall;
-import org.apache.calcite.sql.SqlCallBinding;
-import org.apache.calcite.sql.SqlDataTypeSpec;
-import org.apache.calcite.sql.SqlDelete;
-import org.apache.calcite.sql.SqlDynamicParam;
-import org.apache.calcite.sql.SqlExplain;
-import org.apache.calcite.sql.SqlFunction;
-import org.apache.calcite.sql.SqlFunctionCategory;
-import org.apache.calcite.sql.SqlIdentifier;
-import org.apache.calcite.sql.SqlInsert;
-import org.apache.calcite.sql.SqlIntervalLiteral;
-import org.apache.calcite.sql.SqlIntervalQualifier;
-import org.apache.calcite.sql.SqlJoin;
-import org.apache.calcite.sql.SqlKind;
-import org.apache.calcite.sql.SqlLiteral;
-import org.apache.calcite.sql.SqlMatchRecognize;
-import org.apache.calcite.sql.SqlMerge;
-import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlNodeList;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlOperatorTable;
-import org.apache.calcite.sql.SqlOrderBy;
-import org.apache.calcite.sql.SqlSampleSpec;
-import org.apache.calcite.sql.SqlSelect;
-import org.apache.calcite.sql.SqlSelectKeyword;
-import org.apache.calcite.sql.SqlSnapshot;
-import org.apache.calcite.sql.SqlSyntax;
-import org.apache.calcite.sql.SqlUnresolvedFunction;
-import org.apache.calcite.sql.SqlUpdate;
-import org.apache.calcite.sql.SqlUtil;
-import org.apache.calcite.sql.SqlWindow;
-import org.apache.calcite.sql.SqlWith;
-import org.apache.calcite.sql.SqlWithItem;
-import org.apache.calcite.sql.fun.SqlCase;
-import org.apache.calcite.sql.fun.SqlStdOperatorTable;
-import org.apache.calcite.sql.parser.SqlParserPos;
-import org.apache.calcite.sql.type.AssignableOperandTypeChecker;
-import org.apache.calcite.sql.type.ReturnTypes;
-import org.apache.calcite.sql.type.SqlOperandTypeInference;
-import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.calcite.sql.type.SqlTypeUtil;
-import org.apache.calcite.sql.util.SqlBasicVisitor;
-import org.apache.calcite.sql.util.SqlShuttle;
-import org.apache.calcite.sql.util.SqlVisitor;
-import org.apache.calcite.sql.validate.implicit.TypeCoercion;
-import org.apache.calcite.sql.validate.implicit.TypeCoercions;
-import org.apache.calcite.sql2rel.InitializerContext;
-import org.apache.calcite.util.BitString;
-import org.apache.calcite.util.Bug;
-import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.calcite.util.ImmutableIntList;
-import org.apache.calcite.util.ImmutableNullableList;
-import org.apache.calcite.util.Litmus;
-import org.apache.calcite.util.Pair;
-import org.apache.calcite.util.Static;
-import org.apache.calcite.util.Util;
-import org.apache.calcite.util.trace.CalciteTrace;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import 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 javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import static org.apache.calcite.sql.SqlUtil.stripAs;
-import static org.apache.calcite.util.Static.RESOURCE;
-
-/*
- * THIS FILE HAS BEEN COPIED FROM THE APACHE CALCITE PROJECT UNTIL CALCITE-2707 IS FIXED.
- * (Added lines: 6016-6022)
- */
-
-/**
- * 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<String, IdInfo> idPositions = new HashMap<>();
-
- /**
- * Maps {@link SqlNode query node} objects to the {@link SqlValidatorScope}
- * scope created from them.
- */
- protected final Map<SqlNode, SqlValidatorScope> scopes =
- new IdentityHashMap<>();
-
- /**
- * Maps a {@link SqlSelect} node to the scope used by its WHERE and HAVING
- * clauses.
- */
- private final Map<SqlSelect, SqlValidatorScope> whereScopes =
- new IdentityHashMap<>();
-
- /**
- * Maps a {@link SqlSelect} node to the scope used by its GROUP BY clause.
- */
- private final Map<SqlSelect, SqlValidatorScope> groupByScopes =
- new IdentityHashMap<>();
-
- /**
- * Maps a {@link SqlSelect} node to the scope used by its SELECT and HAVING
- * clauses.
- */
- private final Map<SqlSelect, SqlValidatorScope> selectScopes =
- new IdentityHashMap<>();
-
- /**
- * Maps a {@link SqlSelect} node to the scope used by its ORDER BY clause.
- */
- private final Map<SqlSelect, SqlValidatorScope> 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<SqlSelect, SqlValidatorScope> 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<SqlNode, SqlValidatorNamespace> 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<SqlNode> 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<FunctionParamInfo> 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<SqlNode, RelDataType> nodeToTypeMap =
- new IdentityHashMap<>();
- private final AggFinder aggFinder;
- private final AggFinder aggOrOverFinder;
- private final AggFinder aggOrOverOrGroupFinder;
- private final AggFinder groupFinder;
- private final AggFinder overFinder;
- private final SqlConformance conformance;
- private final Map<SqlNode, SqlNode> originalExprs = new HashMap<>();
-
- private SqlNode top;
-
- // REVIEW jvs 30-June-2006: subclasses may override shouldExpandIdentifiers
- // in a way that ignores this; we should probably get rid of the protected
- // method and always use this variable (or better, move preferences like
- // this to a separate "parameter" class)
- protected boolean expandIdentifiers;
-
- protected boolean expandColumnReferences;
-
- private boolean rewriteCalls;
-
- 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;
-
- // Flag saying if we enable the implicit type coercion.
- private boolean enableTypeCoercion;
-
- //~ Constructors -----------------------------------------------------------
-
- /**
- * Creates a validator.
- *
- * @param opTab Operator table
- * @param catalogReader Catalog reader
- * @param typeFactory Type factory
- * @param conformance Compatibility mode
- */
- protected SqlValidatorImpl(
- SqlOperatorTable opTab,
- SqlValidatorCatalogReader catalogReader,
- RelDataTypeFactory typeFactory,
- SqlConformance conformance) {
- this.opTab = Objects.requireNonNull(opTab);
- this.catalogReader = Objects.requireNonNull(catalogReader);
- this.typeFactory = Objects.requireNonNull(typeFactory);
- this.conformance = Objects.requireNonNull(conformance);
-
- unknownType = typeFactory.createUnknownType();
- booleanType = typeFactory.createSqlType(SqlTypeName.BOOLEAN);
-
- rewriteCalls = true;
- expandColumnReferences = true;
- 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.enableTypeCoercion = catalogReader.getConfig() == null
- || catalogReader.getConfig().typeCoercion();
- this.typeCoercion = TypeCoercions.getTypeCoercion(this, conformance);
- }
-
- //~ Methods ----------------------------------------------------------------
-
- public SqlConformance getConformance() {
- return conformance;
- }
-
- 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<SqlNode> list = new ArrayList<>();
- final List<Map.Entry<String, RelDataType>> 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<Integer, SqlSelect> 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<String, String> parentCursorMap =
- funcParamInfo.columnListParamToParentCursorMap;
- return parentCursorMap.get(columnListParamName);
- }
-
- /**
- * If <code>selectItem</code> 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<SqlNode> selectItems,
- Set<String> aliases,
- List<Map.Entry<String, RelDataType>> 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 = expand(selectItem, scope);
- 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 boolean expandStar(List<SqlNode> selectItems, Set<String> aliases,
- List<Map.Entry<String, RelDataType>> 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<String, RelDataType> 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 currentType.equals(desiredType)
- || (currentType.isNullable() != desiredType.isNullable()
- && typeFactory.createTypeWithNullability(currentType,
- desiredType.isNullable()).equals(desiredType))
- ? node
- : SqlStdOperatorTable.CAST.createCall(SqlParserPos.ZERO,
- node, SqlTypeUtil.convertTypeToSpec(desiredType));
- }
-
- private boolean addOrExpandField(List<SqlNode> selectItems, Set<String> aliases,
- List<Map.Entry<String, RelDataType>> 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<SqlMoniker> 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<SqlMoniker> 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 completion 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
- * completion 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<SqlMoniker> 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<SqlMoniker> 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<SqlMoniker> 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<SqlMoniker> 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<SqlMoniker> 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<String> names,
- SqlParserPos pos,
- Collection<SqlMoniker> hintList) {
- // Remove the last part of name - it is a dummy
- List<String> 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 completion 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<String> names,
- SqlValidator validator,
- Collection<SqlMoniker> result) {
- final List<SqlMoniker> 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<String> names,
- SqlValidator validator,
- Collection<SqlMoniker> 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.replaceAll("'", "");
- result.add(
- new SqlMonikerImpl(
- sig,
- SqlMonikerType.FUNCTION));
- continue;
- }
- result.add(
- new SqlMonikerImpl(
- op.getName(),
- SqlMonikerType.FUNCTION));
- }
- }
- }
- }
-
- public SqlNode validateParameterizedExpression(
- SqlNode topNode,
- final Map<String, RelDataType> 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<SqlNode> 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);
-
- 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<String> qualifiedName = ns.getTable().getQualifiedName();
- String tableName = qualifiedName.get(qualifiedName.size() - 1);
- throw newValidationError(snapshot.getTableRef(),
- Static.RESOURCE.notTemporalTable(tableName));
- }
- }
- }
-
- /**
- * 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 EXTEND:
- final SqlIdentifier id = (SqlIdentifier) call.getOperandList().get(0);
- final DelegatingScope idScope = (DelegatingScope) scope;
- return getNamespace(id, idScope);
- case AS:
- final SqlNode nested = call.getOperandList().get(0);
- switch (nested.getKind()) {
- 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 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<SqlNode> 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<SqlOperator> 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 (rewriteCalls) {
- 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);
- }
-
- 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<SqlNode> 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);
- }
-
- 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);
- }
-
- 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);
- 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);
- 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);
- 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);
- }
-
- /**
- * 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);
- }
-
- /**
- * Returns null if there is no common type. E.g. if the rows have a
- * different number of columns.
- */
- RelDataType getTableConstructorRowType(
- SqlCall values,
- SqlValidatorScope scope) {
- final List<SqlNode> rows = values.getOperandList();
- assert rows.size() >= 1;
- final List<RelDataType> 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<String> aliasList = new ArrayList<>();
- final List<RelDataType> typeList = new ArrayList<>();
- for (Ord<SqlNode> 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.
- *
- * <p>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<SqlOperator> 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<RelDataType> 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 (shouldExpandIdentifiers()) {
- 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<RelDataType> argTypes,
- List<String> argNames) {
- // For builtins, we can give a better error message
- final List<SqlOperator> 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) {
- 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<SqlNode> 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<SqlNode> list,
- Set<String> aliases,
- List<Map.Entry<String, RelDataType>> fieldList,
- SqlNode exp,
- SqlValidatorScope 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.expandIdentifiers = expandIdentifiers;
- }
-
- // implement SqlValidator
- public void setColumnReferenceExpansion(
- boolean expandColumnReferences) {
- this.expandColumnReferences = expandColumnReferences;
- }
-
- // implement SqlValidator
- public boolean getColumnReferenceExpansion() {
- return expandColumnReferences;
- }
-
- public void setDefaultNullCollation(NullCollation nullCollation) {
- this.nullCollation = Objects.requireNonNull(nullCollation);
- }
-
- public NullCollation getDefaultNullCollation() {
- return nullCollation;
- }
-
- // implement SqlValidator
- public void setCallRewrite(boolean rewriteCalls) {
- this.rewriteCalls = rewriteCalls;
- }
-
- public boolean shouldExpandIdentifiers() {
- return expandIdentifiers;
- }
-
- 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.
- *
- * <p>{@code parentScope} and {@code usingScope} are often the same. They
- * differ when the namespace are not visible within the parent. (Example
- * needed.)
- *
- * <p>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 (<code>AS alias</code>) 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 (shouldExpandIdentifiers()) {
- 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 (shouldExpandIdentifiers()) {
- // 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 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(
- tableScope == null ? parentScope : tableScope,
- usingScope,
- register,
- operand,
- enclosingNode,
- alias,
- extendList,
- forceNullable,
- true);
- if (newOperand != operand) {
- call.setOperand(0, newOperand);
- }
- scopes.put(node, parentScope);
- 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 <code>SELECT</code> 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 (<code>UNION</code>, <code>
- * INTERSECT</code>, or <code>EXCEPT</code>). 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<SqlNode> 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,
... 3745 lines suppressed ...