diff --git a/core/src/main/java/org/apache/calcite/jdbc/ContextSqlValidator.java b/core/src/main/java/org/apache/calcite/jdbc/ContextSqlValidator.java index df5ebc44756..c87b017c96d 100644 --- a/core/src/main/java/org/apache/calcite/jdbc/ContextSqlValidator.java +++ b/core/src/main/java/org/apache/calcite/jdbc/ContextSqlValidator.java @@ -19,6 +19,7 @@ import org.apache.calcite.config.CalciteConnectionConfig; import org.apache.calcite.prepare.CalciteCatalogReader; import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.validate.SqlCluster; import org.apache.calcite.sql.validate.SqlValidatorImpl; import com.google.common.collect.ImmutableList; @@ -45,8 +46,9 @@ public class ContextSqlValidator extends SqlValidatorImpl { * @param mutable Whether to get the mutable schema. */ public ContextSqlValidator(CalcitePrepare.Context context, boolean mutable) { - super(SqlStdOperatorTable.instance(), getCatalogReader(context, mutable), - context.getTypeFactory(), Config.DEFAULT); + super( + new SqlCluster(SqlStdOperatorTable.instance(), getCatalogReader(context, mutable), + context.getTypeFactory()), Config.DEFAULT); } private static CalciteCatalogReader getCatalogReader( diff --git a/core/src/main/java/org/apache/calcite/prepare/CalciteSqlValidator.java b/core/src/main/java/org/apache/calcite/prepare/CalciteSqlValidator.java index 8033173882a..4fc393aa5b1 100644 --- a/core/src/main/java/org/apache/calcite/prepare/CalciteSqlValidator.java +++ b/core/src/main/java/org/apache/calcite/prepare/CalciteSqlValidator.java @@ -20,6 +20,7 @@ import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.sql.SqlInsert; import org.apache.calcite.sql.SqlOperatorTable; +import org.apache.calcite.sql.validate.SqlCluster; import org.apache.calcite.sql.validate.SqlValidatorImpl; /** Validator. @@ -29,7 +30,7 @@ public class CalciteSqlValidator extends SqlValidatorImpl { public CalciteSqlValidator(SqlOperatorTable opTab, CalciteCatalogReader catalogReader, JavaTypeFactory typeFactory, Config config) { - super(opTab, catalogReader, typeFactory, config); + super(new SqlCluster(opTab, catalogReader, typeFactory), config); } @Override protected RelDataType getLogicalSourceRowType( diff --git a/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java b/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java index bf0bee7743d..236093b1ae3 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java @@ -114,7 +114,7 @@ public SqlCallBinding(SqlValidator validator, SqlValidatorScope scope, } return n; } - return validator.isAggregate(select) ? 0 : -1; + return validator.getValidatorAggStuff().isAggregate(select) ? 0 : -1; } /** @@ -355,7 +355,7 @@ public SqlCall permutedCall() { @Override public RelDataType getOperandType(int ordinal) { final SqlNode operand = call.operand(ordinal); final RelDataType type = SqlTypeUtil.deriveType(this, operand); - final SqlValidatorNamespace namespace = validator.getNamespace(operand); + final SqlValidatorNamespace namespace = validator.getScopeMap().getNamespace(operand); if (namespace != null) { return namespace.getType(); } diff --git a/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java index 8754c4d7cad..a29b099fee0 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlWithinGroupOperator.java @@ -123,7 +123,8 @@ private PercentileDiscCallBinding(SqlValidator validator, @Override public RelDataType getCollationType() { final RelDataType type = SqlTypeUtil.deriveType(this, collationColumn); - final SqlValidatorNamespace namespace = super.getValidator().getNamespace(collationColumn); + final SqlValidatorNamespace namespace = super.getValidator().getScopeMap() + .getNamespace(collationColumn); return namespace != null ? namespace.getType() : type; } } diff --git a/core/src/main/java/org/apache/calcite/sql/advise/SqlAdvisorValidator.java b/core/src/main/java/org/apache/calcite/sql/advise/SqlAdvisorValidator.java index ddcfe9a6a9b..36bd6f7b73e 100644 --- a/core/src/main/java/org/apache/calcite/sql/advise/SqlAdvisorValidator.java +++ b/core/src/main/java/org/apache/calcite/sql/advise/SqlAdvisorValidator.java @@ -28,6 +28,7 @@ import org.apache.calcite.sql.type.SqlTypeUtil; import org.apache.calcite.sql.validate.OverScope; import org.apache.calcite.sql.validate.SelectScope; +import org.apache.calcite.sql.validate.SqlCluster; import org.apache.calcite.sql.validate.SqlModality; import org.apache.calcite.sql.validate.SqlValidatorCatalogReader; import org.apache.calcite.sql.validate.SqlValidatorImpl; @@ -67,7 +68,7 @@ public SqlAdvisorValidator( SqlValidatorCatalogReader catalogReader, RelDataTypeFactory typeFactory, Config config) { - super(opTab, catalogReader, typeFactory, config); + super(new SqlCluster(opTab, catalogReader, typeFactory), config); } //~ Methods ---------------------------------------------------------------- @@ -169,11 +170,11 @@ private void registerId(SqlIdentifier id, SqlValidatorScope scope) { @Override protected void validateOver(SqlCall call, SqlValidatorScope scope) { try { - final OverScope overScope = (OverScope) getOverScope(call); + final OverScope overScope = (OverScope) getScopeMap().getOverScope(call); final SqlNode relation = call.operand(0); validateFrom(relation, unknownType, scope); final SqlNode window = call.operand(1); - SqlValidatorScope opScope = scopes.get(relation); + SqlValidatorScope opScope = getScopeMap().getScope(relation); if (opScope == null) { opScope = overScope; } @@ -199,7 +200,4 @@ private void registerId(SqlIdentifier id, SqlValidatorScope scope) { return true; } - @Override protected boolean shouldAllowOverRelation() { - return true; // no reason not to be lenient - } } diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlAbstractGroupFunction.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlAbstractGroupFunction.java index a4aa00477c7..2dd8b9535bd 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlAbstractGroupFunction.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlAbstractGroupFunction.java @@ -69,7 +69,7 @@ public SqlAbstractGroupFunction(String name, final SelectScope selectScope = requireNonNull(SqlValidatorUtil.getEnclosingSelectScope(scope)); final SqlSelect select = selectScope.getNode(); - if (!validator.isAggregate(select)) { + if (!validator.getValidatorAggStuff().isAggregate(select)) { throw validator.newValidationError(call, Static.RESOURCE.groupingInAggregate(getName())); } diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetQueryConstructor.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetQueryConstructor.java index 05ed42ca842..223d3cb18cc 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetQueryConstructor.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlMultisetQueryConstructor.java @@ -86,7 +86,7 @@ protected SqlMultisetQueryConstructor(String name, SqlKind kind, SqlSelect subSelect = call.operand(0); subSelect.validateExpr(validator, scope); final SqlValidatorNamespace ns = - requireNonNull(validator.getNamespace(subSelect), + requireNonNull(validator.getScopeMap().getNamespace(subSelect), () -> "namespace is missing for " + subSelect); final RelDataType rowType = requireNonNull(ns.getRowType(), "rowType"); final SqlCallBinding opBinding = new SqlCallBinding(validator, scope, call); diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java index a9544ca463e..0e961150da4 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java @@ -1732,7 +1732,7 @@ private static class LambdaFamilyOperandTypeChecker && !argFamilies.stream().allMatch(f -> f == SqlTypeFamily.ANY)) { // Replace the parameter types in the lambda expression. final SqlLambdaScope scope = - (SqlLambdaScope) validator.getLambdaScope(lambdaExpr); + (SqlLambdaScope) validator.getScopeMap().getLambdaScope(lambdaExpr); for (int i = 0; i < argFamilies.size(); i++) { final SqlNode param = lambdaExpr.getParameters().get(i); final RelDataType type = @@ -1794,7 +1794,7 @@ private static class LambdaRelOperandTypeChecker // Replace the parameter types in the lambda expression. final SqlValidator validator = callBinding.getValidator(); final SqlLambdaScope scope = - (SqlLambdaScope) validator.getLambdaScope(lambdaExpr); + (SqlLambdaScope) validator.getScopeMap().getLambdaScope(lambdaExpr); for (int i = 0; i < argTypes.size(); i++) { final SqlNode param = lambdaExpr.getParameters().get(i); final RelDataType type = argTypes.get(i); diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java index 27979e72ea0..f059932a6e0 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/AbstractNamespace.java @@ -235,4 +235,8 @@ protected RelDataType toStruct(RelDataType type, @Nullable SqlNode unnest) { .add(SqlValidatorUtil.alias(requireNonNull(unnest, "unnest"), 0), type) .build(); } + + protected ScopeMap getScopeMap() { + return getValidator().getScopeMap(); + } } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java b/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java index 43ccc3453b1..89e36570333 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java @@ -147,7 +147,7 @@ && isMeasureExp(id)) { if (scope instanceof AggregatingSelectScope) { final SqlSelect select = (SqlSelect) scope.getNode(); SelectScope selectScope = - requireNonNull(validator.getRawSelectScope(select), + requireNonNull(validator.getScopeMap().getRawSelectScope(select), () -> "rawSelectScope for " + scope.getNode()); List selectList = requireNonNull(selectScope.getExpandedSelectList(), diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java index 04296d98172..115b4a681a0 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java @@ -74,14 +74,14 @@ protected AliasNamespace( @Override public boolean supportsModality(SqlModality modality) { final List operands = call.getOperandList(); final SqlValidatorNamespace childNs = - validator.getNamespaceOrThrow(operands.get(0)); + getScopeMap().getNamespaceOrThrow(operands.get(0)); return childNs.supportsModality(modality); } @Override protected RelDataType validateImpl(RelDataType targetRowType) { final List operands = call.getOperandList(); final SqlValidatorNamespace childNs = - validator.getNamespaceOrThrow(operands.get(0)); + getScopeMap().getNamespaceOrThrow(operands.get(0)); final RelDataType rowType = childNs.getRowTypeSansSystemColumns(); final RelDataType aliasedType; if (operands.size() == 2) { diff --git a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java index 5153d3160e5..84696ab4453 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java @@ -234,9 +234,9 @@ protected void addColumnNames( @Override public SqlValidatorScope getOperandScope(SqlCall call) { if (call instanceof SqlSelect) { - return validator.getSelectScope((SqlSelect) call); + return getScopeMap().getSelectScope((SqlSelect) call); } else if (call instanceof SqlLambda) { - return validator.getLambdaScope((SqlLambda) call); + return getScopeMap().getLambdaScope((SqlLambda) call); } return this; } @@ -673,7 +673,7 @@ public SqlValidatorScope getParent() { return null; case 1: final SqlValidatorNamespace selectNs = - validator.getNamespaceOrThrow(select); + getScopeMap().getNamespaceOrThrow(select); return SqlQualified.create(this, 1, selectNs, identifier); default: // More than one column has this alias. @@ -682,6 +682,10 @@ public SqlValidatorScope getParent() { } } + protected ScopeMap getScopeMap() { + return validator.getScopeMap(); + } + /** Returns the number of columns in the SELECT clause that have {@code name} * as their implicit (e.g. {@code t.name}) or explicit (e.g. * {@code t.c as name}) alias. */ diff --git a/core/src/main/java/org/apache/calcite/sql/validate/JoinNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/JoinNamespace.java index c533796acd2..d159fd5ded8 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/JoinNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/JoinNamespace.java @@ -42,9 +42,9 @@ class JoinNamespace extends AbstractNamespace { @Override protected RelDataType validateImpl(RelDataType targetRowType) { RelDataType leftType = - validator.getNamespaceOrThrow(join.getLeft()).getRowType(); + getScopeMap().getNamespaceOrThrow(join.getLeft()).getRowType(); RelDataType rightType = - validator.getNamespaceOrThrow(join.getRight()).getRowType(); + getScopeMap().getNamespaceOrThrow(join.getRight()).getRowType(); final RelDataTypeFactory typeFactory = validator.getTypeFactory(); switch (join.getJoinType()) { case LEFT: diff --git a/core/src/main/java/org/apache/calcite/sql/validate/NamespaceBuilder.java b/core/src/main/java/org/apache/calcite/sql/validate/NamespaceBuilder.java new file mode 100644 index 00000000000..69f3da02494 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/validate/NamespaceBuilder.java @@ -0,0 +1,256 @@ +/* + * 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.sql.SqlCall; +import org.apache.calcite.sql.SqlDelete; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlInsert; +import org.apache.calcite.sql.SqlJoin; +import org.apache.calcite.sql.SqlLambda; +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.SqlPivot; +import org.apache.calcite.sql.SqlSelect; +import org.apache.calcite.sql.SqlUnpivot; +import org.apache.calcite.sql.SqlUpdate; +import org.apache.calcite.sql.SqlWith; +import org.apache.calcite.sql.SqlWithItem; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import static java.util.Objects.requireNonNull; + +/** + * A builder to allow for customization of namespaces down stream. + */ +public class NamespaceBuilder { + + private final SqlValidatorImpl sqlValidatorImpl; + + public NamespaceBuilder(SqlValidatorImpl sqlValidatorImpl) { + this.sqlValidatorImpl = sqlValidatorImpl; + } + + /** + * Creates a namespace for a SELECT node. Derived class may + * override this factory method. + * + * @param select Select node + * @param enclosingNode Enclosing node + * @return Select namespace + */ + public SelectNamespace createSelectNamespace( + SqlSelect select, + SqlNode enclosingNode) { + return new SelectNamespace(sqlValidatorImpl, select, enclosingNode); + } + + /** + * Creates a namespace for a set operation (UNION, + * INTERSECT, or EXCEPT). Derived class may override + * this factory method. + * + * @param call Call to set operation + * @param enclosingNode Enclosing node + * @return Set operation namespace + */ + public SetopNamespace createSetopNamespace( + SqlCall call, + SqlNode enclosingNode) { + return new SetopNamespace(sqlValidatorImpl, call, enclosingNode); + } + + + public MatchRecognizeNamespace createMatchRecognizeNameSpace( + SqlMatchRecognize call, + SqlNode enclosingNode) { + return new MatchRecognizeNamespace(sqlValidatorImpl, call, enclosingNode); + } + + public UnpivotNamespace createUnpivotNameSpace( + SqlUnpivot call, + SqlNode enclosingNode) { + return new UnpivotNamespace(sqlValidatorImpl, call, enclosingNode); + } + + + public PivotNamespace createPivotNameSpace( + SqlPivot call, + SqlNode enclosingNode) { + return new PivotNamespace(sqlValidatorImpl, call, enclosingNode); + } + + public DmlNamespace createDeleteNamespace(SqlDelete delete, + SqlNode enclosingNode, SqlValidatorScope parentScope) { + return new DeleteNamespace(sqlValidatorImpl, delete, enclosingNode, parentScope); + } + + public DmlNamespace createInsertNamespace(SqlInsert insert, + SqlNode enclosingNode, SqlValidatorScope parentScope) { + return new InsertNamespace(sqlValidatorImpl, insert, enclosingNode, parentScope); + } + + public DmlNamespace createMergeNamespace(SqlMerge merge, SqlNode enclosing, + SqlValidatorScope parentScope) { + return new MergeNamespace(sqlValidatorImpl, merge, enclosing, parentScope); + } + + public DmlNamespace createUpdate(SqlUpdate sqlUpdate, SqlNode enclosing, + SqlValidatorScope parentScope) { + return new UpdateNamespace(sqlValidatorImpl, sqlUpdate, enclosing, parentScope); + } + + public TableConstructorNamespace createTableConstructorNamespace( + SqlCall call, + SqlValidatorScope parentScope, + SqlNode enclosingNode) { + return new TableConstructorNamespace(sqlValidatorImpl, call, parentScope, enclosingNode); + } + + public LambdaNamespace createLambdaNamespace(SqlLambda call, SqlNode node) { + return new LambdaNamespace(sqlValidatorImpl, call, node); + } + + public UnnestNamespace createUnnestNamespace( + SqlCall call, + SqlValidatorScope parentScope, + SqlNode enclosingNode) { + return new UnnestNamespace(sqlValidatorImpl, call, parentScope, enclosingNode); + } + + public ProcedureNamespace createProcedureNamespace( + SqlValidatorScope parentScope, + SqlCall call, + SqlNode enclosingNode) { + return new ProcedureNamespace(sqlValidatorImpl, parentScope, call, enclosingNode); + } + + public WithNamespace createWithNamespace(SqlWith with, SqlNode enclosingNode) { + return new WithNamespace(sqlValidatorImpl, with, enclosingNode); + } + + public SqlValidatorNamespace createWithItemNamespace( + SqlWithItem withItem, + SqlNode enclosingNode) { + return new WithItemNamespace(sqlValidatorImpl, withItem, enclosingNode); + } + + public SqlValidatorNamespace createAliasNamespace(SqlCall call, SqlNode enclosingNode) { + return new AliasNamespace(sqlValidatorImpl, call, enclosingNode); + } + + JoinNamespace createJoinNamespace(SqlJoin join) { + return new JoinNamespace(sqlValidatorImpl, join); + } + + public IdentifierNamespace createIdentifierNamespace( + SqlIdentifier id, + @Nullable SqlNodeList extendList, + SqlNode enclosingNode, + SqlValidatorScope parentScope) { + return new IdentifierNamespace(sqlValidatorImpl, id, extendList, enclosingNode, parentScope); + } + + /** + * Interface for creating new {@link NamespaceBuilder}. + */ + public interface Factory { + NamespaceBuilder create(SqlValidatorImpl sqlValidatorImpl); + } + + + /** + * Common base class for DML statement namespaces. + */ + public abstract 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 = requireNonNull(node, "node"); + } + + @Override public @Nullable SqlNode 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 = requireNonNull(node, "node"); + } + + @Override public @Nullable SqlNode 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 = requireNonNull(node, "node"); + } + + @Override public @Nullable SqlNode 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 = requireNonNull(node, "node"); + } + + @Override public @Nullable SqlNode getNode() { + return node; + } + } +} diff --git a/core/src/main/java/org/apache/calcite/sql/validate/OrderByScope.java b/core/src/main/java/org/apache/calcite/sql/validate/OrderByScope.java index b8e58222ad9..53ab39aa782 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/OrderByScope.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/OrderByScope.java @@ -63,7 +63,7 @@ public class OrderByScope extends DelegatingScope { } @Override public void findAllColumnNames(List result) { - final SqlValidatorNamespace ns = validator.getNamespaceOrThrow(select); + final SqlValidatorNamespace ns = getScopeMap().getNamespaceOrThrow(select); addColumnNames(ns, result); } @@ -80,14 +80,15 @@ public class OrderByScope extends DelegatingScope { } @Override public @Nullable RelDataType resolveColumn(String name, SqlNode ctx) { - final SqlValidatorNamespace selectNs = validator.getNamespaceOrThrow(select); + final SqlValidatorNamespace selectNs = + getScopeMap().getNamespaceOrThrow(select); final RelDataType rowType = selectNs.getRowType(); final SqlNameMatcher nameMatcher = validator.catalogReader.nameMatcher(); final RelDataTypeField field = nameMatcher.field(rowType, name); if (field != null) { return field.getType(); } - final SqlValidatorScope selectScope = validator.getSelectScope(select); + final SqlValidatorScope selectScope = getScopeMap().getSelectScope(select); return selectScope.resolveColumn(name, ctx); } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/PivotScope.java b/core/src/main/java/org/apache/calcite/sql/validate/PivotScope.java index eb0436cbef4..4a0bd48629f 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/PivotScope.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/PivotScope.java @@ -39,7 +39,7 @@ public PivotScope(SqlValidatorScope parent, SqlPivot pivot) { * scope only has one namespace, and it is anonymous. */ public SqlValidatorNamespace getChild() { return requireNonNull( - validator.getNamespace(pivot.query), + getScopeMap().getNamespace(pivot.query), () -> "namespace for pivot.query " + pivot.query); } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/ScopeMap.java b/core/src/main/java/org/apache/calcite/sql/validate/ScopeMap.java new file mode 100644 index 00000000000..1696a2bee9c --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/validate/ScopeMap.java @@ -0,0 +1,274 @@ +/* + * 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.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlLambda; +import org.apache.calcite.sql.SqlMatchRecognize; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlSelect; +import org.apache.calcite.util.Util; + +import org.apiguardian.api.API; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Contains mapping of SqlNodes to SqlValidatorScope. + * + *

The methods {@link #getSelectScope}, {@link #getFromScope}, + * {@link #getWhereScope}, {@link #getGroupScope}, {@link #getHavingScope}, + * {@link #getOrderScope} and {@link #getJoinScope} get the correct scope + * to resolve names in a particular clause of a SQL statement. + */ +public interface ScopeMap { + SqlValidatorScope getCursorScope(SqlSelect select); + + /** + * Returns a scope containing the objects visible from the FROM clause of a + * query. + * + * @param select SELECT statement + * @return naming scope for FROM clause + */ + SqlValidatorScope getFromScope(SqlSelect select); + + /** + * Returns a scope containing the objects visible from the GROUP BY clause + * of a query. + * + * @param select SELECT statement + * @return naming scope for GROUP BY clause + */ + SqlValidatorScope getGroupScope(SqlSelect select); + + /** + * Returns a scope containing the objects visible from the HAVING clause of + * a query. + * + * @param select SELECT statement + * @return naming scope for HAVING clause + */ + SqlValidatorScope getHavingScope(SqlSelect select); + + /** + * Returns a scope containing the objects visible from the ON and USING + * sections of a JOIN clause. + * + * @param node The item in the FROM clause which contains the ON or USING + * expression + * @return naming scope for JOIN clause + * @see #getFromScope + */ + SqlValidatorScope getJoinScope(SqlNode node); + + /** + * Returns the lambda expression scope. + * + * @param node Lambda expression + * @return naming scope for lambda expression + */ + SqlValidatorScope getLambdaScope(SqlLambda node); + + /** + * Returns a scope match recognize clause. + * + * @param node Match recognize + * @return naming scope for Match recognize clause + */ + SqlValidatorScope getMatchRecognizeScope(SqlMatchRecognize node); + + SqlValidatorScope getMeasureScope(SqlSelect select); + + /** + * Returns the scope that expressions in the SELECT and HAVING clause of + * this query should use. This scope consists of the FROM clause and the + * enclosing scope. If the query is aggregating, only columns in the GROUP + * BY clause may be used. + * + * @param select SELECT statement + * @return naming scope for ORDER BY clause + */ + SqlValidatorScope getOrderScope(SqlSelect select); + + /** + * Returns the scope of an OVER or VALUES node. + * + * @param node Node + * @return Scope + */ + SqlValidatorScope getOverScope(SqlNode node); + + /** + * Returns the appropriate scope for validating a particular clause of a + * SELECT statement. + * + *

Consider + * + *

SELECT *
+   * FROM foo
+   * WHERE EXISTS (
+   *    SELECT deptno AS x
+   *    FROM emp
+   *       JOIN dept ON emp.deptno = dept.deptno
+   *    WHERE emp.deptno = 5
+   *    GROUP BY deptno
+   *    ORDER BY x)
+ * + *

What objects can be seen in each part of the sub-query? + * + *

    + *
  • In FROM ({@link #getFromScope} , you can only see 'foo'. + * + *
  • In WHERE ({@link #getWhereScope}), GROUP BY ({@link #getGroupScope}), + * SELECT ({@code getSelectScope}), and the ON clause of the JOIN + * ({@link #getJoinScope}) you can see 'emp', 'dept', and 'foo'. + * + *
  • In ORDER BY ({@link #getOrderScope}), you can see the column alias 'x'; + * and tables 'emp', 'dept', and 'foo'. + * + *
+ * + * @param select SELECT statement + * @return naming scope for SELECT statement + */ + SqlValidatorScope getSelectScope(SqlSelect select); + /** + * Returns the scope that expressions in the WHERE and GROUP BY clause of + * this query should use. This scope consists of the tables in the FROM + * clause, and the enclosing scope. + * + * @param select Query + * @return naming scope of WHERE clause + */ + SqlValidatorScope getWhereScope(SqlSelect select); + SqlValidatorScope getWithScope(SqlNode withItem); + + SqlValidatorScope getScopeOrThrow(SqlNode node); + + @Nullable SqlValidatorScope getScope(SqlNode sqlNode); + + SelectScope getRawSelectScopeNonNull(SqlSelect select); + + @Nullable + TableScope getTableScope(); + + + /** + * Finds the namespace corresponding to a given node. + * + *

For example, in the query SELECT * FROM (SELECT * FROM t), t1 AS + * alias, the both items in the FROM clause have a corresponding + * namespace. + * + * @param node Parse tree node + * @return namespace of node + */ + @Nullable SqlValidatorNamespace getNamespace(SqlNode node); + + /** + * Returns the scope for resolving the SELECT, GROUP BY and HAVING clauses. + * Always a {@link SelectScope}; if this is an aggregation query, the + * {@link AggregatingScope} is stripped away. + * + * @param select SELECT statement + * @return naming scope for SELECT statement, sans any aggregating scope + */ + @Nullable SelectScope getRawSelectScope(SqlSelect select); + + + /** + * Namespace for the given node. + * + * @param node node to compute the namespace for + * @param scope namespace scope + * @return namespace for the given node, never null + * @see #getNamespace(SqlNode) + */ + @API(since = "1.27", status = API.Status.INTERNAL) + SqlValidatorNamespace getNamespaceOrThrow(SqlNode node, SqlValidatorScope scope); + + /** + * Namespace for the given node. + * + * @param id identifier to resolve + * @param scope namespace scope + * @return namespace for the given node, never null + * @see ScopeMapImpl#getNamespace(SqlIdentifier, DelegatingScope) + */ + @API(since = "1.26", status = API.Status.INTERNAL) + SqlValidatorNamespace getNamespaceOrThrow(SqlIdentifier id, @Nullable DelegatingScope scope); + + /** + * Namespace for the given node. + * + * @param node node to compute the namespace for + * @return namespace for the given node, never null + * @see #getNamespace(SqlNode) + */ + @API(since = "1.27", status = API.Status.INTERNAL) + SqlValidatorNamespace getNamespaceOrThrow(SqlNode node); + + + /** Allows {@link ScopeMapImpl}.clauseScopes to have multiple values per SELECT. */ + enum Clause { + WHERE, + GROUP_BY, + SELECT, + MEASURE, + ORDER, + CURSOR, + HAVING, + QUALIFY; + + /** + * Determines if the extender should replace aliases with expanded values. + * For example: + * + *

{@code
+     * SELECT a + a as twoA
+     * GROUP BY twoA
+     * }
+ * + *

turns into + * + *

{@code
+     * SELECT a + a as twoA
+     * GROUP BY a + a
+     * }
+ * + *

This is determined both by the clause and the config. + * + * @param config The configuration + * @return Whether we should replace the alias with its expanded value + */ + public boolean shouldReplaceAliases(SqlValidator.Config config) { + switch (this) { + case GROUP_BY: + return config.conformance().isGroupByAlias(); + + case HAVING: + return config.conformance().isHavingAlias(); + + case QUALIFY: + return true; + + default: + throw Util.unexpected(this); + } + } + } +} diff --git a/core/src/main/java/org/apache/calcite/sql/validate/ScopeMapImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/ScopeMapImpl.java new file mode 100644 index 00000000000..e9792a9e443 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/validate/ScopeMapImpl.java @@ -0,0 +1,351 @@ +/* + * 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.sql.SqlCall; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlLambda; +import org.apache.calcite.sql.SqlMatchRecognize; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlSelect; +import org.apache.calcite.sql.util.IdPair; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; + +import static org.apache.calcite.sql.SqlUtil.stripAs; + +import static java.util.Objects.requireNonNull; + +/** + * An implementation of SqlQueryScopes with helper methods for building up the mapping. + */ +public class ScopeMapImpl implements ScopeMap { + + private final SqlValidatorCatalogReader catalogReader; + /** + * Maps {@link SqlNode query node} objects to the {@link SqlValidatorScope} + * scope created from them. + */ + protected final IdentityHashMap scopes = + new IdentityHashMap<>(); + + + /** + * Maps a {@link SqlSelect} and a {@link Clause} to the scope used by that + * clause. + */ + private final Map, SqlValidatorScope> + clauseScopes = new HashMap<>(); + + /** + * The name-resolution scope of a LATERAL TABLE clause. + */ + private @Nullable TableScope tableScope = null; + + + /** + * Maps a {@link SqlNode node} to the + * {@link SqlValidatorNamespace namespace} which describes what columns they + * contain. + */ + protected final IdentityHashMap namespaces = + new IdentityHashMap<>(); + + public ScopeMapImpl(SqlValidatorCatalogReader catalogReader) { + this.catalogReader = catalogReader; + } + + @Override public SqlValidatorScope getCursorScope(SqlSelect select) { + return getScope(select, Clause.CURSOR); + } + + public @Nullable SqlValidatorScope putCursorScope( + SqlSelect select, SelectScope selectScope) { + return putScope(select, Clause.CURSOR, selectScope); + } + + @Override public SqlValidatorScope getFromScope(SqlSelect select) { + return requireNonNull(scopes.get(select), + () -> "no scope for " + select); + } + + @Override public SqlValidatorScope getGroupScope(SqlSelect select) { + // Yes, it's the same as getWhereScope + return getScope(select, Clause.WHERE); + } + + + public @Nullable SqlValidatorScope putGroupByScope( + SqlSelect select, SqlValidatorScope sqlValidatorScope) { + // Why is the group by, but the get is where? + return putScope(select, Clause.GROUP_BY, sqlValidatorScope); + } + + + @Override public SqlValidatorScope getHavingScope(SqlSelect select) { + // Yes, it's the same as getSelectScope + return getScope(select, Clause.SELECT); + } + + @Override public SqlValidatorScope getJoinScope(SqlNode node) { + return requireNonNull(scopes.get(stripAs(node)), + () -> "scope for " + node); + } + + @Override public SqlValidatorScope getLambdaScope(SqlLambda node) { + return getScopeOrThrow(node); + } + + @Override public SqlValidatorScope getMatchRecognizeScope(SqlMatchRecognize node) { + return getScopeOrThrow(node); + } + + @Override public SqlValidatorScope getMeasureScope(SqlSelect select) { + return getScope(select, Clause.MEASURE); + } + + public @Nullable SqlValidatorScope putMeasureScope( + SqlSelect select, SqlValidatorScope sqlValidatorScope) { + return putScope(select, Clause.MEASURE, sqlValidatorScope); + } + + @Override public SqlValidatorScope getOrderScope(SqlSelect select) { + return getScope(select, Clause.ORDER); + } + + public @Nullable SqlValidatorScope putOrderScope( + SqlSelect select, SqlValidatorScope sqlValidatorScope) { + return putScope(select, Clause.ORDER, sqlValidatorScope); + } + + @Override public SqlValidatorScope getOverScope(SqlNode node) { + return getScopeOrThrow(node); + } + + @Override public SqlValidatorScope getSelectScope(SqlSelect select) { + return getScope(select, Clause.SELECT); + } + + public @Nullable SqlValidatorScope putSelectScope( + SqlSelect select, SqlValidatorScope sqlValidatorScope) { + return putScope(select, Clause.SELECT, sqlValidatorScope); + } + + @Override public SqlValidatorScope getWhereScope(SqlSelect select) { + return getScope(select, Clause.WHERE); + } + + public @Nullable SqlValidatorScope putWhereScope( + SqlSelect sqlNode, SelectScope selectScope) { + return putScope(sqlNode, Clause.WHERE, selectScope); + } + + @Override public SqlValidatorScope getWithScope(SqlNode withItem) { + assert withItem.getKind() == SqlKind.WITH_ITEM; + return getScopeOrThrow(withItem); + } + + @Override public SqlValidatorScope getScopeOrThrow(SqlNode node) { + return requireNonNull(scopes.get(node), () -> "scope for " + node); + } + + @Override public @Nullable SqlValidatorScope getScope(SqlNode sqlNode) { + return scopes.get(sqlNode); + } + + public @Nullable SqlValidatorScope putScope(SqlNode sqlNode, + SqlValidatorScope sqlValidatorScope) { + return scopes.put(sqlNode, sqlValidatorScope); + } + + public @Nullable SqlValidatorScope putIfAbsent(SqlNode sqlNode, + SqlValidatorScope sqlValidatorScope) { + return scopes.putIfAbsent(sqlNode, sqlValidatorScope); + } + + private @Nullable SqlValidatorScope putScope(SqlSelect select, Clause clause, + SqlValidatorScope sqlValidatorScope) { + return clauseScopes.put(IdPair.of(select, clause), sqlValidatorScope); + } + + @Override public @Nullable SelectScope getRawSelectScope(SqlSelect select) { + SqlValidatorScope scope = clauseScopes.get(IdPair.of(select, Clause.SELECT)); + if (scope instanceof AggregatingSelectScope) { + scope = ((AggregatingSelectScope) scope).getParent(); + } + return (SelectScope) scope; + } + + @Override public SelectScope getRawSelectScopeNonNull(SqlSelect select) { + return requireNonNull(getRawSelectScope(select), + () -> "getRawSelectScope for " + select); + } + + @Override public @Nullable TableScope getTableScope() { + return tableScope; + } + + public void setTableScope(@Nullable TableScope tableScope) { + this.tableScope = tableScope; + } + + + /** + * 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 + */ + public void registerNamespace( + @Nullable SqlValidatorScope usingScope, + @Nullable String alias, + SqlValidatorNamespace ns, + boolean forceNullable) { + SqlValidatorNamespace namespace = + namespaces.get(requireNonNull(ns.getNode(), () -> "ns.getNode() for " + ns)); + if (namespace == null) { + namespaces.put(requireNonNull(ns.getNode()), ns); + namespace = ns; + } + if (usingScope != null) { + if (alias == null) { + throw new IllegalArgumentException("Registering namespace " + ns + + ", into scope " + usingScope + ", so alias must not be null"); + } + usingScope.addChild(namespace, alias, forceNullable); + } + } + + + @Override public @Nullable SqlValidatorNamespace getNamespace(SqlNode node) { + switch (node.getKind()) { + case AS: + + // AS has a namespace if it has a column list 'AS t (c1, c2, ...)' + final SqlValidatorNamespace ns = namespaces.get(node); + if (ns != null) { + return ns; + } + // fall through + case TABLE_REF: + case SNAPSHOT: + case OVER: + case COLLECTION_TABLE: + case ORDER_BY: + case TABLESAMPLE: + return getNamespace(((SqlCall) node).operand(0)); + default: + return namespaces.get(node); + } + } + + public @Nullable SqlValidatorNamespace getNamespace(SqlNode node, + SqlValidatorScope scope) { + if (node instanceof SqlIdentifier && scope instanceof DelegatingScope) { + final SqlIdentifier id = (SqlIdentifier) node; + final DelegatingScope idScope = + (DelegatingScope) ((DelegatingScope) scope).getParent(); + return getNamespace(id, idScope); + } else if (node instanceof SqlCall) { + // Handle extended identifiers. + final SqlCall call = (SqlCall) node; + switch (call.getOperator().getKind()) { + case TABLE_REF: + return getNamespace(call.operand(0), scope); + case EXTEND: + final SqlNode operand0 = call.getOperandList().get(0); + final SqlIdentifier identifier = operand0.getKind() == SqlKind.TABLE_REF + ? ((SqlCall) operand0).operand(0) + : (SqlIdentifier) operand0; + final DelegatingScope idScope = (DelegatingScope) scope; + return getNamespace(identifier, idScope); + case AS: + final SqlNode nested = call.getOperandList().get(0); + switch (nested.getKind()) { + case TABLE_REF: + case EXTEND: + return getNamespace(nested, scope); + default: + break; + } + break; + default: + break; + } + } + return getNamespace(node); + } + + public @Nullable SqlValidatorNamespace getNamespace(SqlIdentifier id, + @Nullable DelegatingScope scope) { + if (id.isSimple()) { + final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); + final SqlValidatorScope.ResolvedImpl resolved = + new SqlValidatorScope.ResolvedImpl(); + requireNonNull(scope, () -> "scope needed to lookup " + id) + .resolve(id.names, nameMatcher, false, resolved); + if (resolved.count() == 1) { + return resolved.only().namespace; + } + } + return getNamespace(id); + } + + @Override public SqlValidatorNamespace getNamespaceOrThrow(SqlNode node) { + return requireNonNull( + getNamespace(node), + () -> "namespace for " + node); + } + + @Override public SqlValidatorNamespace getNamespaceOrThrow(SqlNode node, + SqlValidatorScope scope) { + return requireNonNull( + getNamespace(node, scope), + () -> "namespace for " + node + ", scope " + scope); + } + + @Override public SqlValidatorNamespace getNamespaceOrThrow(SqlIdentifier id, + @Nullable DelegatingScope scope) { + return requireNonNull( + getNamespace(id, scope), + () -> "namespace for " + id + ", scope " + scope); + } + + private SqlValidatorScope getScope(SqlSelect select, Clause clause) { + return requireNonNull( + clauseScopes.get(IdPair.of(select, clause)), + () -> "no " + clause + " scope for " + select); + } + + /** + * A factory interface for creating {@link ScopeMapImpl}. + */ + public interface Factory { + Factory DEFAULT = ScopeMapImpl::new; + ScopeMapImpl create(SqlValidatorCatalogReader catalogReader); + } +} diff --git a/core/src/main/java/org/apache/calcite/sql/validate/ScopePopulator.java b/core/src/main/java/org/apache/calcite/sql/validate/ScopePopulator.java new file mode 100644 index 00000000000..8c363b3c861 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/validate/ScopePopulator.java @@ -0,0 +1,1102 @@ +/* + * 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.sql.SqlBasicCall; +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlDelete; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlInsert; +import org.apache.calcite.sql.SqlJoin; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlLambda; +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.SqlPivot; +import org.apache.calcite.sql.SqlSelect; +import org.apache.calcite.sql.SqlUnpivot; +import org.apache.calcite.sql.SqlUpdate; +import org.apache.calcite.sql.SqlWindowTableFunction; +import org.apache.calcite.sql.SqlWith; +import org.apache.calcite.sql.SqlWithItem; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.util.Util; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; + +import static org.apache.calcite.sql.SqlUtil.stripAs; +import static org.apache.calcite.util.Static.RESOURCE; +import static org.apache.calcite.util.Util.first; + +import static java.util.Objects.requireNonNull; + +/** + * Populates {@link ScopeMap}. + */ +public class ScopePopulator { + + private final ScopeMapImpl scopeMap; + private final NamespaceBuilder namespaceBuilder; + private final SqlCluster sqlCluster; + private final ValidatorAggStuff validatorAggStuff; + private final boolean identifierExpansion; + + public ScopePopulator( + ScopeMapImpl scopeMap, + NamespaceBuilder namespaceBuilder, + SqlCluster sqlCluster, + ValidatorAggStuff validatorAggStuff, + boolean identifierExpansion) { + this.scopeMap = scopeMap; + this.namespaceBuilder = namespaceBuilder; + this.sqlCluster = sqlCluster; + this.validatorAggStuff = validatorAggStuff; + this.identifierExpansion = identifierExpansion; + } + + /** + * 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 + */ + public void registerQuery( + SqlValidatorScope parentScope, + @Nullable SqlValidatorScope usingScope, + SqlNode node, + SqlNode enclosingNode, + @Nullable String alias, + boolean forceNullable) { + 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, + @Nullable SqlValidatorScope usingScope, + SqlNode node, + SqlNode enclosingNode, + @Nullable String alias, + boolean forceNullable, + boolean checkUpdate) { + requireNonNull(node, "node"); + requireNonNull(enclosingNode, "enclosingNode"); + checkArgument(usingScope == null || alias != null); + + SqlCall call; + List operands; + switch (node.getKind()) { + case SELECT: + final SqlSelect select = (SqlSelect) node; + final SelectNamespace selectNs = + namespaceBuilder.createSelectNamespace(select, enclosingNode); + scopeMap.registerNamespace(usingScope, alias, selectNs, forceNullable); + final SqlValidatorScope windowParentScope = + first(usingScope, parentScope); + SelectScope selectScope = + new SelectScope(parentScope, windowParentScope, select); + scopeMap.putScope(select, selectScope); + + // Start by registering the WHERE clause + scopeMap.putWhereScope(select, selectScope); + registerOperandSubQueries( + selectScope, + select, + SqlSelect.WHERE_OPERAND); + + // Register subqueries in the QUALIFY clause + registerOperandSubQueries( + selectScope, + select, + SqlSelect.QUALIFY_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 aggregate query, the SELECT list and HAVING + // clause use a different scope, where you can only reference + // columns which are in the GROUP BY clause. + final SqlValidatorScope selectScope2 = + validatorAggStuff.isAggregate(select) + ? new AggregatingSelectScope(selectScope, select, false) + : selectScope; + scopeMap.putSelectScope(select, selectScope2); + scopeMap.putMeasureScope(select, new MeasureScope(selectScope, select)); + if (select.getGroup() != null) { + GroupByScope groupByScope = + new GroupByScope(selectScope, select.getGroup(), select); + scopeMap.putGroupByScope(select, groupByScope); + registerSubQueries(groupByScope, select.getGroup()); + } + registerOperandSubQueries( + selectScope2, + select, + SqlSelect.HAVING_OPERAND); + registerSubQueries(selectScope2, + SqlNonNullableAccessors.getSelectList(select)); + final SqlNodeList orderList = select.getOrderList(); + if (orderList != null) { + // If the query is 'SELECT DISTINCT', restrict the columns + // available to the ORDER BY clause. + final SqlValidatorScope selectScope3 = + select.isDistinct() + ? new AggregatingSelectScope(selectScope, select, true) + : selectScope2; + OrderByScope orderScope = + new OrderByScope(selectScope3, orderList, select); + scopeMap.putOrderScope(select, orderScope); + registerSubQueries(orderScope, orderList); + + if (!validatorAggStuff.isAggregate(select)) { + // Since this is not an aggregate query, + // there cannot be any aggregates in the ORDER BY clause. + SqlNode agg = validatorAggStuff.findAgg(orderList); + if (agg != null) { + throw sqlCluster.newValidationError(agg, RESOURCE.aggregateIllegalInOrderBy()); + } + } + } + break; + + case INTERSECT: + sqlCluster.validateFeature(RESOURCE.sQLFeature_F302(), node.getParserPosition()); + registerSetop( + parentScope, + usingScope, + node, + node, + alias, + forceNullable); + break; + + case EXCEPT: + sqlCluster.validateFeature(RESOURCE.sQLFeature_E071_03(), node.getParserPosition()); + registerSetop( + parentScope, + usingScope, + node, + node, + alias, + forceNullable); + break; + + case UNION: + registerSetop( + parentScope, + usingScope, + node, + enclosingNode, + alias, + forceNullable); + break; + + case LAMBDA: + call = (SqlCall) node; + SqlLambdaScope lambdaScope = + new SqlLambdaScope(parentScope, (SqlLambda) call); + scopeMap.putScope(call, lambdaScope); + final LambdaNamespace lambdaNamespace = + namespaceBuilder.createLambdaNamespace((SqlLambda) call, node); + scopeMap.registerNamespace( + usingScope, + alias, + lambdaNamespace, + forceNullable); + operands = call.getOperandList(); + for (int i = 0; i < operands.size(); i++) { + registerOperandSubQueries(parentScope, call, i); + } + break; + + case WITH: + registerWith(parentScope, usingScope, (SqlWith) node, enclosingNode, + alias, forceNullable, checkUpdate); + break; + + case VALUES: + call = (SqlCall) node; + scopeMap.putScope(call, parentScope); + final TableConstructorNamespace tableConstructorNamespace = + namespaceBuilder.createTableConstructorNamespace( + call, + parentScope, + enclosingNode); + scopeMap.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; + NamespaceBuilder.DmlNamespace insertNs = + namespaceBuilder.createInsertNamespace( + insertCall, + enclosingNode, + parentScope); + scopeMap.registerNamespace(usingScope, null, insertNs, forceNullable); + registerQuery( + parentScope, + usingScope, + insertCall.getSource(), + enclosingNode, + null, + false); + break; + + case DELETE: + SqlDelete deleteCall = (SqlDelete) node; + NamespaceBuilder.DmlNamespace deleteNs = + namespaceBuilder.createDeleteNamespace( + deleteCall, + enclosingNode, + parentScope); + scopeMap.registerNamespace(usingScope, null, deleteNs, forceNullable); + registerQuery( + parentScope, + usingScope, + SqlNonNullableAccessors.getSourceSelect(deleteCall), + enclosingNode, + null, + false); + break; + + case UPDATE: + if (checkUpdate) { + sqlCluster.validateFeature(RESOURCE.sQLFeature_E101_03(), + node.getParserPosition()); + } + SqlUpdate updateCall = (SqlUpdate) node; + NamespaceBuilder.DmlNamespace updateNs = + namespaceBuilder.createUpdate( + updateCall, + enclosingNode, + parentScope); + scopeMap.registerNamespace(usingScope, null, updateNs, forceNullable); + registerQuery( + parentScope, + usingScope, + SqlNonNullableAccessors.getSourceSelect(updateCall), + enclosingNode, + null, + false); + break; + + case MERGE: + sqlCluster.validateFeature(RESOURCE.sQLFeature_F312(), node.getParserPosition()); + SqlMerge mergeCall = (SqlMerge) node; + NamespaceBuilder.DmlNamespace mergeNs = + namespaceBuilder.createMergeNamespace( + mergeCall, + enclosingNode, + parentScope); + scopeMap.registerNamespace(usingScope, null, mergeNs, forceNullable); + registerQuery( + parentScope, + usingScope, + SqlNonNullableAccessors.getSourceSelect(mergeCall), + 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 + SqlUpdate mergeUpdateCall = mergeCall.getUpdateCall(); + if (mergeUpdateCall != null) { + registerQuery( + scopeMap.getWhereScope(SqlNonNullableAccessors.getSourceSelect(mergeCall)), + null, + mergeUpdateCall, + enclosingNode, + null, + false, + false); + } + SqlInsert mergeInsertCall = mergeCall.getInsertCall(); + if (mergeInsertCall != null) { + registerQuery( + parentScope, + null, + mergeInsertCall, + enclosingNode, + null, + false); + } + break; + + case UNNEST: + call = (SqlCall) node; + final UnnestNamespace unnestNs = + namespaceBuilder.createUnnestNamespace(call, parentScope, enclosingNode); + scopeMap.registerNamespace( + usingScope, + alias, + unnestNs, + forceNullable); + registerOperandSubQueries(parentScope, call, 0); + scopeMap.putScope(node, parentScope); + break; + case OTHER_FUNCTION: + call = (SqlCall) node; + ProcedureNamespace procNs = + namespaceBuilder.createProcedureNamespace( + parentScope, + call, + enclosingNode); + scopeMap.registerNamespace( + usingScope, + alias, + procNs, + forceNullable); + registerSubQueries(parentScope, call); + break; + + case MULTISET_QUERY_CONSTRUCTOR: + case MULTISET_VALUE_CONSTRUCTOR: + sqlCluster.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 = + SqlValidatorUtil.alias(node, sqlCluster.getAndIncermentNextGeneratedId()); + scopeMap.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, + @Nullable SqlValidatorScope usingScope, + SqlNode node, + SqlNode enclosingNode, + @Nullable String alias, + boolean forceNullable) { + SqlCall call = (SqlCall) node; + final SetopNamespace setopNamespace = + namespaceBuilder.createSetopNamespace(call, enclosingNode); + scopeMap.registerNamespace(usingScope, alias, setopNamespace, forceNullable); + + // A setop is in the same scope as its parent. + scopeMap.putScope(call, parentScope); + @NonNull SqlValidatorScope recursiveScope = parentScope; + if (enclosingNode.getKind() == SqlKind.WITH_ITEM) { + if (node.getKind() != SqlKind.UNION) { + throw sqlCluster.newValidationError(node, RESOURCE.recursiveWithMustHaveUnionSetOp()); + } else if (call.getOperandList().size() > 2) { + throw sqlCluster.newValidationError( + node, RESOURCE.recursiveWithMustHaveTwoChildUnionSetOp()); + } + final WithScope scope = (WithScope) scopeMap.getScope(enclosingNode); + // recursive scope is only set for the recursive queries. + recursiveScope = scope != null && scope.recursiveScope != null + ? requireNonNull(scope.recursiveScope) : parentScope; + } + for (int i = 0; i < call.getOperandList().size(); i++) { + SqlNode operand = call.getOperandList().get(i); + @NonNull SqlValidatorScope scope = i == 0 ? parentScope : recursiveScope; + registerQuery( + scope, + null, + operand, + operand, + null, + false); + } + } + + private void registerWith( + SqlValidatorScope parentScope, + @Nullable SqlValidatorScope usingScope, + SqlWith with, + SqlNode enclosingNode, + @Nullable String alias, + boolean forceNullable, + boolean checkUpdate) { + final WithNamespace withNamespace = + namespaceBuilder.createWithNamespace(with, enclosingNode); + scopeMap.registerNamespace(usingScope, alias, withNamespace, forceNullable); + scopeMap.putScope(with, parentScope); + + SqlValidatorScope scope = parentScope; + for (SqlNode withItem_ : with.withList) { + final SqlWithItem withItem = (SqlWithItem) withItem_; + + final boolean isRecursiveWith = withItem.recursive.booleanValue(); + final SqlValidatorScope withScope = + new WithScope(scope, withItem, + isRecursiveWith ? new WithRecursiveScope(scope, withItem) : null); + scopeMap.putScope(withItem, withScope); + + registerQuery(scope, null, withItem.query, + withItem.recursive.booleanValue() ? withItem : with, withItem.name.getSimple(), + forceNullable); + scopeMap.registerNamespace(null, alias, + namespaceBuilder.createWithItemNamespace(withItem, enclosingNode), + false); + scope = withScope; + } + registerQuery(scope, null, with.body, enclosingNode, alias, forceNullable, + checkUpdate); + } + + + /** + * Registers scopes and namespaces implied a relational expression in the + * FROM clause. + * + *

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

Likewise, {@code enclosingNode} and {@code node} are often the same. + * {@code enclosingNode} is the topmost node within the FROM clause, from + * which any decorations like an alias (AS alias) or a table + * sample clause are stripped away to get {@code node}. Both are recorded in + * the namespace. + * + * @param parentScope0 Parent scope that 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} + */ + // CHECKSTYLE: OFF + // CheckStyle thinks this method is too long + private SqlNode registerFrom( + SqlValidatorScope parentScope0, + SqlValidatorScope usingScope, + boolean register, + final SqlNode node, + SqlNode enclosingNode, + @Nullable String alias, + @Nullable 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 = SqlValidatorUtil.alias(node); + if (alias == null) { + alias = SqlValidatorUtil.alias(node, sqlCluster.getAndIncermentNextGeneratedId()); + } + if (identifierExpansion) { + newNode = SqlValidatorUtil.addAlias(node, alias); + } + break; + + case SELECT: + case UNION: + case INTERSECT: + case EXCEPT: + case VALUES: + case UNNEST: + case OTHER_FUNCTION: + case COLLECTION_TABLE: + case PIVOT: + case UNPIVOT: + case MATCH_RECOGNIZE: + case WITH: + // give this anonymous construct a name since later + // query processing stages rely on it + alias = SqlValidatorUtil.alias(node, sqlCluster.getAndIncermentNextGeneratedId()); + if (identifierExpansion) { + // Since we're expanding identifiers, we should make the + // aliases explicit too, otherwise the expanded query + // will not be consistent if we convert back to SQL, e.g. + // "select EXPR$1.EXPR$2 from values (1)". + newNode = SqlValidatorUtil.addAlias(node, alias); + } + break; + default: + break; + } + } + + final SqlValidatorScope parentScope; + 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(parentScope0, node2); + if (usingScope instanceof ListScope) { + for (ScopeChild child : ((ListScope) usingScope).children) { + tableScope.addChild(child.namespace, child.name, child.nullable); + } + } + parentScope = tableScope; + } else { + parentScope = parentScope0; + } + + SqlCall call; + SqlNode operand; + SqlNode newOperand; + + switch (kind) { + case AS: + call = (SqlCall) node; + if (alias == null) { + alias = String.valueOf(call.operand(1)); + } + expr = call.operand(0); + final boolean needAliasNamespace = call.operandCount() > 2 + || expr.getKind() == SqlKind.VALUES || expr.getKind() == SqlKind.UNNEST + || expr.getKind() == SqlKind.COLLECTION_TABLE; + newExpr = + registerFrom( + parentScope, + usingScope, + !needAliasNamespace, + 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 (needAliasNamespace) { + scopeMap.registerNamespace( + usingScope, + alias, + namespaceBuilder.createAliasNamespace(call, enclosingNode), + forceNullable); + } + return node; + + case MATCH_RECOGNIZE: + registerMatchRecognize(parentScope, usingScope, + (SqlMatchRecognize) node, enclosingNode, alias, forceNullable); + return node; + + case PIVOT: + registerPivot(parentScope, usingScope, (SqlPivot) node, enclosingNode, + alias, forceNullable); + return node; + + case UNPIVOT: + registerUnpivot(parentScope, usingScope, (SqlUnpivot) 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); + scopeMap.putScope(join, joinScope); + final SqlNode left = join.getLeft(); + final SqlNode right = join.getRight(); + boolean forceLeftNullable = forceNullable; + boolean forceRightNullable = forceNullable; + switch (join.getJoinType()) { + case LEFT: + case LEFT_ASOF: + forceRightNullable = true; + break; + case RIGHT: + forceLeftNullable = true; + break; + case FULL: + forceLeftNullable = true; + forceRightNullable = true; + break; + default: + 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); + } + scopeMap.putIfAbsent(stripAs(join.getRight()), parentScope); + scopeMap.putIfAbsent(stripAs(join.getLeft()), parentScope); + registerSubQueries(joinScope, join.getCondition()); + final JoinNamespace joinNamespace = namespaceBuilder.createJoinNamespace(join); + scopeMap.registerNamespace(null, null, joinNamespace, forceNullable); + return join; + + case IDENTIFIER: + final SqlIdentifier id = (SqlIdentifier) node; + final IdentifierNamespace newNs = + namespaceBuilder.createIdentifierNamespace(id, extendList, enclosingNode, + parentScope); + scopeMap.registerNamespace(register ? usingScope : null, alias, newNs, + forceNullable); + @Nullable TableScope tableScope = scopeMap.getTableScope(); + if (tableScope == null) { + tableScope = new TableScope(parentScope, node); + scopeMap.setTableScope(tableScope); + } + tableScope.addChild(newNs, requireNonNull(alias, "alias"), forceNullable); + if (extendList != null && !extendList.isEmpty()) { + 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); + } + // If the operator is SqlWindowTableFunction, restricts the scope as + // its first operand's (the table) scope. + if (operand instanceof SqlBasicCall) { + final SqlBasicCall call1 = (SqlBasicCall) operand; + final SqlOperator op = call1.getOperator(); + if (op instanceof SqlWindowTableFunction + && call1.operand(0).getKind() == SqlKind.SELECT) { + scopeMap.putScope(node, scopeMap.getSelectScope(call1.operand(0))); + return newNode; + } + } + // Put the usingScope which can be a JoinScope + // or a SelectScope, in order to see the left items + // of the JOIN tree. + scopeMap.putScope(node, usingScope); + 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 = SqlValidatorUtil.alias(node, sqlCluster.getAndIncermentNextGeneratedId()); + } + 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); + scopeMap.putScope(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) { + scopeMap.registerNamespace(register ? usingScope : null, child.name, + child.namespace, forceNullable); + } + + return newNode; + + case TABLE_REF: + call = (SqlCall) node; + registerFrom(parentScope, + usingScope, + register, + call.operand(0), + enclosingNode, + alias, + extendList, + forceNullable, + lateral); + if (extendList != null && !extendList.isEmpty()) { + return enclosingNode; + } + return newNode; + + case EXTEND: + final SqlCall extend = (SqlCall) node; + return registerFrom(parentScope, + usingScope, + true, + extend.getOperandList().get(0), + extend, + alias, + (SqlNodeList) extend.getOperandList().get(1), + forceNullable, + lateral); + + case SNAPSHOT: + call = (SqlCall) node; + operand = call.operand(0); + newOperand = + registerFrom(parentScope, + usingScope, + register, + operand, + enclosingNode, + alias, + extendList, + forceNullable, + lateral); + if (newOperand != operand) { + call.setOperand(0, newOperand); + } + // Put the usingScope which can be a JoinScope + // or a SelectScope, in order to see the left items + // of the JOIN tree. + scopeMap.putScope(node, usingScope); + return newNode; + + default: + throw Util.unexpected(kind); + } + } + // CHECKSTYLE: ON + + protected boolean shouldAllowOverRelation() { + return false; + } + + + + private void validateNodeFeature(SqlNode node) { + switch (node.getKind()) { + case MULTISET_VALUE_CONSTRUCTOR: + sqlCluster.validateFeature(RESOURCE.sQLFeature_S271(), node.getParserPosition()); + break; + default: + break; + } + } + + private void registerSubQueries( + SqlValidatorScope parentScope, + @Nullable SqlNode node) { + if (node == null) { + return; + } + if (node.getKind().belongsTo(SqlKind.QUERY) + || node.getKind() == SqlKind.LAMBDA + || 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 + } + } + + + private void registerMatchRecognize( + SqlValidatorScope parentScope, + SqlValidatorScope usingScope, + SqlMatchRecognize call, + SqlNode enclosingNode, + @Nullable String alias, + boolean forceNullable) { + + final MatchRecognizeNamespace matchRecognizeNamespace = + namespaceBuilder.createMatchRecognizeNameSpace(call, enclosingNode); + scopeMap.registerNamespace(usingScope, alias, matchRecognizeNamespace, forceNullable); + + final MatchRecognizeScope matchRecognizeScope = + new MatchRecognizeScope(parentScope, call); + scopeMap.putScope(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); + } + } + + private void registerPivot( + SqlValidatorScope parentScope, + SqlValidatorScope usingScope, + SqlPivot pivot, + SqlNode enclosingNode, + @Nullable String alias, + boolean forceNullable) { + final PivotNamespace namespace = namespaceBuilder.createPivotNameSpace(pivot, enclosingNode); + scopeMap.registerNamespace(usingScope, alias, namespace, forceNullable); + + final SqlValidatorScope scope = + new PivotScope(parentScope, pivot); + scopeMap.putScope(pivot, scope); + + // parse input query + SqlNode expr = pivot.query; + SqlNode newExpr = + registerFrom(parentScope, scope, true, expr, + expr, null, null, forceNullable, false); + if (expr != newExpr) { + pivot.setOperand(0, newExpr); + } + } + + private void registerUnpivot( + SqlValidatorScope parentScope, + SqlValidatorScope usingScope, + SqlUnpivot call, + SqlNode enclosingNode, + @Nullable String alias, + boolean forceNullable) { + final UnpivotNamespace namespace = + namespaceBuilder.createUnpivotNameSpace(call, enclosingNode); + scopeMap.registerNamespace(usingScope, alias, namespace, forceNullable); + + final SqlValidatorScope scope = + new UnpivotScope(parentScope, call); + scopeMap.putScope(call, scope); + + // parse input query + SqlNode expr = call.query; + SqlNode newExpr = + registerFrom(parentScope, scope, true, expr, + expr, null, null, forceNullable, false); + if (expr != newExpr) { + call.setOperand(0, newExpr); + } + } + + + /** + * 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); + } + +} diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SelectNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/SelectNamespace.java index 9819d28a584..b7be67640e9 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SelectNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SelectNamespace.java @@ -71,11 +71,11 @@ public SelectNamespace( final RelDataType rowType = this.getRowTypeSansSystemColumns(); final int field = SqlTypeUtil.findField(rowType, columnName); SelectScope selectScope = - requireNonNull(validator.getRawSelectScope(select), + requireNonNull(getScopeMap().getRawSelectScope(select), () -> "rawSelectScope for " + select); final SqlNode selectItem = requireNonNull(selectScope.getExpandedSelectList(), () -> "expandedSelectList for selectScope of " + select).get(field); - return validator.getSelectScope(select).getMonotonicity(selectItem); + return getScopeMap().getSelectScope(select).getMonotonicity(selectItem); } } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SetopNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/SetopNamespace.java index 58c381e80c3..932707a2422 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SetopNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SetopNamespace.java @@ -67,7 +67,7 @@ protected SetopNamespace( } for (SqlNode operand : call.getOperandList()) { final SqlValidatorNamespace namespace = - requireNonNull(validator.getNamespace(operand), + requireNonNull(getScopeMap().getNamespace(operand), () -> "namespace for " + operand); monotonicity = combine(monotonicity, @@ -103,7 +103,7 @@ private static SqlMonotonicity combine(@Nullable SqlMonotonicity m0, case INTERSECT: case EXCEPT: final SqlValidatorScope scope = - requireNonNull(validator.scopes.get(call), + requireNonNull(getScopeMap().getScope(call), () -> "scope for " + call); for (SqlNode operand : call.getOperandList()) { if (!operand.isA(SqlKind.QUERY)) { diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlCluster.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlCluster.java new file mode 100644 index 00000000000..27e912c077d --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlCluster.java @@ -0,0 +1,90 @@ +/* + * 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.RelDataTypeFactory; +import org.apache.calcite.runtime.CalciteContextException; +import org.apache.calcite.runtime.Feature; +import org.apache.calcite.runtime.Resources; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlOperatorTable; +import org.apache.calcite.sql.SqlUtil; +import org.apache.calcite.sql.parser.SqlParserPos; + +import static java.util.Objects.requireNonNull; + +/** + * Contains system state for validation. + */ +public class SqlCluster { + + private final SqlOperatorTable opTab; + private final RelDataTypeFactory relDataTypeFactory; + private final SqlValidatorCatalogReader catalogReader; + private int nextGeneratedId; + + + public SqlCluster( + SqlOperatorTable opTab, + SqlValidatorCatalogReader catalogReader, + RelDataTypeFactory typeFactory) { + this.opTab = opTab; + this.relDataTypeFactory = typeFactory; + this.catalogReader = catalogReader; + } + + public SqlOperatorTable getOpTab() { + return opTab; + } + + public RelDataTypeFactory getRelDataTypeFactory() { + return relDataTypeFactory; + } + + public SqlValidatorCatalogReader getCatalogReader() { + return catalogReader; + } + + public int getAndIncermentNextGeneratedId() { + return nextGeneratedId++; + } + + + public CalciteContextException newValidationError( + SqlNode node, + Resources.ExInst e) { + requireNonNull(node, "node"); + final SqlParserPos pos = node.getParserPosition(); + return SqlUtil.newContextException(pos, e); + } + + /** + * 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 + */ + public 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; + } +} diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlNonNullableAccessors.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlNonNullableAccessors.java index 7fbca5f888f..c2db4862544 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlNonNullableAccessors.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlNonNullableAccessors.java @@ -104,7 +104,7 @@ public static SqlValidatorScope getScope(SqlCallBinding callBinding) { @API(since = "1.27", status = API.Status.EXPERIMENTAL) public static SqlValidatorNamespace getNamespace(SqlCallBinding callBinding) { return requireNonNull( - callBinding.getValidator().getNamespace(callBinding.getCall()), + callBinding.getValidator().getScopeMap().getNamespace(callBinding.getCall()), () -> "scope is null for " + safeToString(callBinding)); } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java index b113dec80f2..b2301e9df54 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java @@ -35,7 +35,6 @@ import org.apache.calcite.sql.SqlIntervalQualifier; import org.apache.calcite.sql.SqlLambda; 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; @@ -109,12 +108,6 @@ *

The validator builds the map by making a quick scan over the query when * the root {@link SqlNode} is first provided. Thereafter, it supplies the * correct scope or namespace object when it calls validation methods. - * - *

The methods {@link #getSelectScope}, {@link #getFromScope}, - * {@link #getWhereScope}, {@link #getGroupScope}, {@link #getHavingScope}, - * {@link #getOrderScope} and {@link #getJoinScope} get the correct scope - * to resolve - * names in a particular clause of a SQL statement. */ @Value.Enclosing public interface SqlValidator { @@ -136,6 +129,14 @@ public interface SqlValidator { @Pure SqlOperatorTable getOperatorTable(); + /** + * Returns a mapping of sql nodes to scopes. + * + * @return sql query scopes + */ + @Pure + ScopeMap getScopeMap(); + /** * Validates an expression tree. You can call this method multiple times, * but not reentrantly. @@ -372,26 +373,6 @@ CalciteContextException newValidationError( SqlNode node, Resources.ExInst e); - /** - * Returns whether a SELECT statement is an aggregation. Criteria are: (1) - * contains GROUP BY, or (2) contains HAVING, or (3) SELECT or ORDER BY - * clause contains aggregate functions. (Windowed aggregate functions, such - * as SUM(x) OVER w, don't count.) - * - * @param select SELECT statement - * @return whether SELECT statement is an aggregation - */ - boolean isAggregate(SqlSelect select); - - /** - * Returns whether a select list expression is an aggregate function. - * - * @param selectNode Expression in SELECT clause - * @return whether expression is an aggregate function - */ - @Deprecated // to be removed before 2.0 - boolean isAggregate(SqlNode selectNode); - /** * Converts a window specification or window name into a fully-resolved * window specification. For example, in SELECT sum(x) OVER (PARTITION @@ -429,17 +410,6 @@ default SqlWindow resolveWindow( return resolveWindow(windowOrRef, scope); }; - /** - * Finds the namespace corresponding to a given node. - * - *

For example, in the query SELECT * FROM (SELECT * FROM t), t1 AS - * alias, the both items in the FROM clause have a corresponding - * namespace. - * - * @param node Parse tree node - * @return namespace of node - */ - @Nullable SqlValidatorNamespace getNamespace(SqlNode node); /** * Derives an alias for an expression. If no alias can be derived, returns @@ -467,18 +437,6 @@ default SqlWindow resolveWindow( SqlNodeList expandStar(SqlNodeList selectList, SqlSelect query, boolean includeSystemVars); - /** - * Returns the scope that expressions in the WHERE and GROUP BY clause of - * this query should use. This scope consists of the tables in the FROM - * clause, and the enclosing scope. - * - * @param select Query - * @return naming scope of WHERE clause - */ - SqlValidatorScope getWhereScope(SqlSelect select); - - SqlValidatorScope getMeasureScope(SqlSelect select); - /** * Returns the type factory used by this validator. * @@ -487,6 +445,9 @@ SqlNodeList expandStar(SqlNodeList selectList, SqlSelect query, @Pure RelDataTypeFactory getTypeFactory(); + @Pure + ValidatorAggStuff getValidatorAggStuff(); + /** * Saves the type of a {@link SqlNode}, now that it has been validated. * @@ -513,120 +474,10 @@ SqlNodeList expandStar(SqlNodeList selectList, SqlSelect query, */ RelDataType getUnknownType(); - /** - * Returns the appropriate scope for validating a particular clause of a - * SELECT statement. - * - *

Consider - * - *

SELECT *
-   * FROM foo
-   * WHERE EXISTS (
-   *    SELECT deptno AS x
-   *    FROM emp
-   *       JOIN dept ON emp.deptno = dept.deptno
-   *    WHERE emp.deptno = 5
-   *    GROUP BY deptno
-   *    ORDER BY x)
- * - *

What objects can be seen in each part of the sub-query? - * - *

    - *
  • In FROM ({@link #getFromScope} , you can only see 'foo'. - * - *
  • In WHERE ({@link #getWhereScope}), GROUP BY ({@link #getGroupScope}), - * SELECT ({@code getSelectScope}), and the ON clause of the JOIN - * ({@link #getJoinScope}) you can see 'emp', 'dept', and 'foo'. - * - *
  • In ORDER BY ({@link #getOrderScope}), you can see the column alias 'x'; - * and tables 'emp', 'dept', and 'foo'. - * - *
- * - * @param select SELECT statement - * @return naming scope for SELECT statement - */ - SqlValidatorScope getSelectScope(SqlSelect select); - - /** - * Returns the scope for resolving the SELECT, GROUP BY and HAVING clauses. - * Always a {@link SelectScope}; if this is an aggregation query, the - * {@link AggregatingScope} is stripped away. - * - * @param select SELECT statement - * @return naming scope for SELECT statement, sans any aggregating scope - */ - @Nullable SelectScope getRawSelectScope(SqlSelect select); - - /** - * Returns a scope containing the objects visible from the FROM clause of a - * query. - * - * @param select SELECT statement - * @return naming scope for FROM clause - */ - SqlValidatorScope getFromScope(SqlSelect select); - - /** - * Returns a scope containing the objects visible from the ON and USING - * sections of a JOIN clause. - * - * @param node The item in the FROM clause which contains the ON or USING - * expression - * @return naming scope for JOIN clause - * @see #getFromScope - */ - SqlValidatorScope getJoinScope(SqlNode node); - - /** - * Returns a scope containing the objects visible from the GROUP BY clause - * of a query. - * - * @param select SELECT statement - * @return naming scope for GROUP BY clause - */ - SqlValidatorScope getGroupScope(SqlSelect select); - - /** - * Returns a scope containing the objects visible from the HAVING clause of - * a query. - * - * @param select SELECT statement - * @return naming scope for HAVING clause - */ - SqlValidatorScope getHavingScope(SqlSelect select); - - /** - * Returns the scope that expressions in the SELECT and HAVING clause of - * this query should use. This scope consists of the FROM clause and the - * enclosing scope. If the query is aggregating, only columns in the GROUP - * BY clause may be used. - * - * @param select SELECT statement - * @return naming scope for ORDER BY clause - */ - SqlValidatorScope getOrderScope(SqlSelect select); - - /** - * Returns a scope match recognize clause. - * - * @param node Match recognize - * @return naming scope for Match recognize clause - */ - SqlValidatorScope getMatchRecognizeScope(SqlMatchRecognize node); - - /** - * Returns the lambda expression scope. - * - * @param node Lambda expression - * @return naming scope for lambda expression - */ - SqlValidatorScope getLambdaScope(SqlLambda node); - /** * Returns a scope that cannot see anything. */ - SqlValidatorScope getEmptyScope(); + SqlValidatorScope createEmptyScope(); /** * Declares a SELECT expression as a cursor. @@ -757,14 +608,6 @@ CalciteException handleUnresolvedFunction(SqlCall call, */ RelDataType getParameterRowType(SqlNode sqlQuery); - /** - * Returns the scope of an OVER or VALUES node. - * - * @param node Node - * @return Scope - */ - SqlValidatorScope getOverScope(SqlNode node); - /** * Validates that a query is capable of producing a return of given modality * (relational or streaming). @@ -784,8 +627,6 @@ boolean validateModality(SqlSelect select, SqlModality modality, void validateSequenceValue(SqlValidatorScope scope, SqlIdentifier id); - SqlValidatorScope getWithScope(SqlNode withItem); - /** Get the type coercion instance. */ TypeCoercion getTypeCoercion(); @@ -1014,6 +855,22 @@ default Config withNakedMeasures(boolean nakedMeasures) { return SqlConformanceEnum.DEFAULT; } + /** Set a factory for query scopes to allow for custom behavior downstream. */ + Config withScopeMapFactory(ScopeMapImpl.Factory scopeMapFactory); + + /** Returns a Factory of SqlQueryScopes. */ + @Value.Default default ScopeMapImpl.Factory scopeMapFactory() { + return ScopeMapImpl.Factory.DEFAULT; + } + + /** Set a factory for name space builder to allow for custom behavior downstream. */ + Config withNamespaceBuilderFactory(NamespaceBuilder.Factory factory); + + /** Returns a NamespaceBuilder factory. */ + @Value.Default default NamespaceBuilder.Factory namespaceBuilderFactory() { + return NamespaceBuilder::new; + } + /** Returns the SQL conformance. * * @deprecated Use {@link #conformance()} */ diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java index 706c8e6be0e..b75e8047225 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java @@ -36,7 +36,6 @@ 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.PairList; import org.apache.calcite.runtime.Resources; import org.apache.calcite.schema.ColumnStrategy; @@ -87,7 +86,6 @@ import org.apache.calcite.sql.SqlUpdate; import org.apache.calcite.sql.SqlUtil; import org.apache.calcite.sql.SqlWindow; -import org.apache.calcite.sql.SqlWindowTableFunction; import org.apache.calcite.sql.SqlWith; import org.apache.calcite.sql.SqlWithItem; import org.apache.calcite.sql.TableCharacteristic; @@ -104,10 +102,10 @@ import org.apache.calcite.sql.type.SqlTypeFamily; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.type.SqlTypeUtil; -import org.apache.calcite.sql.util.IdPair; 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.ScopeMap.Clause; import org.apache.calcite.sql.validate.implicit.TypeCoercion; import org.apache.calcite.util.BitString; import org.apache.calcite.util.Bug; @@ -126,9 +124,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; -import org.apiguardian.api.API; import org.checkerframework.checker.nullness.qual.KeyFor; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.PolyNull; import org.checkerframework.dataflow.qual.Pure; @@ -214,33 +210,6 @@ public class SqlValidatorImpl implements SqlValidatorWithHints { */ protected final Map idPositions = new HashMap<>(); - /** - * Maps {@link SqlNode query node} objects to the {@link SqlValidatorScope} - * scope created from them. - */ - protected final IdentityHashMap scopes = - new IdentityHashMap<>(); - - /** - * Maps a {@link SqlSelect} and a {@link Clause} to the scope used by that - * clause. - */ - private final Map, SqlValidatorScope> - clauseScopes = new HashMap<>(); - - /** - * The name-resolution scope of a LATERAL TABLE clause. - */ - private @Nullable TableScope tableScope = null; - - /** - * Maps a {@link SqlNode node} to the - * {@link SqlValidatorNamespace namespace} which describes what columns they - * contain. - */ - protected final IdentityHashMap 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 @@ -256,7 +225,6 @@ public class SqlValidatorImpl implements SqlValidatorWithHints { protected final Deque functionCallStack = new ArrayDeque<>(); - private int nextGeneratedId; protected final RelDataTypeFactory typeFactory; protected final RelDataType unknownType; private final RelDataType booleanType; @@ -275,11 +243,6 @@ public class SqlValidatorImpl implements SqlValidatorWithHints { public final IdentityHashMap> callToOperandTypesMap = new IdentityHashMap<>(); - private final AggFinder aggFinder; - private final AggFinder aggOrOverFinder; - private final AggFinder aggOrOverOrGroupFinder; - private final AggFinder groupFinder; - private final AggFinder overFinder; private Config config; @@ -298,47 +261,50 @@ public class SqlValidatorImpl implements SqlValidatorWithHints { // TypeCoercion instance used for implicit type coercion. private final TypeCoercion typeCoercion; + private final ScopeMapImpl scopeMap; + private final NamespaceBuilder namespaceBuilder; + private final SqlCluster sqlCluster; + private final ValidatorAggStuff validatorAggStuff; + private final ScopePopulator scopePopulator; //~ Constructors ----------------------------------------------------------- /** * Creates a validator. * - * @param opTab Operator table - * @param catalogReader Catalog reader - * @param typeFactory Type factory + * @param sqlCluster Sql cluster * @param config Config */ protected SqlValidatorImpl( - SqlOperatorTable opTab, - SqlValidatorCatalogReader catalogReader, - RelDataTypeFactory typeFactory, + SqlCluster sqlCluster, Config config) { - this.opTab = requireNonNull(opTab, "opTab"); - this.catalogReader = requireNonNull(catalogReader, "catalogReader"); - this.typeFactory = requireNonNull(typeFactory, "typeFactory"); + this.sqlCluster = sqlCluster; + this.config = requireNonNull(config, "config"); + + this.opTab = requireNonNull(sqlCluster.getOpTab(), "opTab"); + this.catalogReader = requireNonNull(sqlCluster.getCatalogReader(), "catalogReader"); + this.typeFactory = requireNonNull(sqlCluster.getRelDataTypeFactory(), "typeFactory"); + final RelDataTypeSystem typeSystem = typeFactory.getTypeSystem(); this.timeFrameSet = requireNonNull(typeSystem.deriveTimeFrameSet(TimeFrames.CORE), "timeFrameSet"); - this.config = requireNonNull(config, "config"); // It is assumed that unknown type is nullable by default unknownType = typeFactory.createTypeWithNullability(typeFactory.createUnknownType(), true); booleanType = typeFactory.createSqlType(SqlTypeName.BOOLEAN); - final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); - aggFinder = new AggFinder(opTab, false, true, false, null, nameMatcher); - aggOrOverFinder = - new AggFinder(opTab, true, true, false, null, nameMatcher); - overFinder = - new AggFinder(opTab, true, false, false, aggOrOverFinder, nameMatcher); - groupFinder = new AggFinder(opTab, false, false, true, null, nameMatcher); - aggOrOverOrGroupFinder = - new AggFinder(opTab, true, true, true, null, nameMatcher); @SuppressWarnings("argument.type.incompatible") TypeCoercion typeCoercion = config.typeCoercionFactory().create(typeFactory, this); this.typeCoercion = typeCoercion; + this.scopeMap = config.scopeMapFactory().create(catalogReader); + @SuppressWarnings("argument.type.incompatible") + NamespaceBuilder namespaceBuilder = config.namespaceBuilderFactory().create(this); + this.namespaceBuilder = namespaceBuilder; + this.validatorAggStuff = new ValidatorAggStuff(sqlCluster, scopeMap); + this.scopePopulator = + new ScopePopulator(scopeMap, namespaceBuilder, sqlCluster, validatorAggStuff, + config.identifierExpansion()); if (config.conformance().allowLenientCoercion()) { final SqlTypeCoercionRule rules = @@ -396,6 +362,10 @@ public SqlConformance getConformance() { return timeFrameSet; } + @Override public ValidatorAggStuff getValidatorAggStuff() { + return validatorAggStuff; + } + @Override public SqlNodeList expandStar(SqlNodeList selectList, SqlSelect select, boolean includeSystemVars) { final List list = new ArrayList<>(); @@ -406,7 +376,7 @@ public SqlConformance getConformance() { list, catalogReader.nameMatcher().createSet(), types, includeSystemVars); } - getRawSelectScopeNonNull(select).setExpandedSelectList(list); + scopeMap.getRawSelectScopeNonNull(select).setExpandedSelectList(list); return new SqlNodeList(list, SqlParserPos.ZERO); } @@ -426,11 +396,12 @@ public SqlConformance getConformance() { // that is the argument to the cursor constructor; register it // with a scope corresponding to the cursor SelectScope cursorScope = - new SelectScope(parentScope, getEmptyScope(), select); - clauseScopes.put(IdPair.of(select, Clause.CURSOR), cursorScope); - final SelectNamespace selectNs = createSelectNamespace(select, select); - final String alias = SqlValidatorUtil.alias(select, nextGeneratedId++); - registerNamespace(cursorScope, alias, selectNs, false); + new SelectScope(parentScope, createEmptyScope(), select); + scopeMap.putCursorScope(select, cursorScope); + final SelectNamespace selectNs = namespaceBuilder.createSelectNamespace(select, select); + final String alias = + SqlValidatorUtil.alias(select, sqlCluster.getAndIncermentNextGeneratedId()); + scopeMap.registerNamespace(cursorScope, alias, selectNs, false); } @Override public void pushFunctionCall() { @@ -468,10 +439,10 @@ private boolean expandSelectItem(final SqlNode selectItem, SqlSelect select, final SqlValidatorScope selectScope; SqlNode expanded; if (SqlValidatorUtil.isMeasure(selectItem)) { - selectScope = getMeasureScope(select); + selectScope = scopeMap.getMeasureScope(select); expanded = selectItem; } else { - final SelectScope scope = (SelectScope) getWhereScope(select); + final SelectScope scope = (SelectScope) scopeMap.getWhereScope(select); if (expandStar(selectItems, aliases, fields, includeSystemVars, scope, selectItem)) { return true; @@ -480,7 +451,7 @@ private boolean expandSelectItem(final SqlNode selectItem, SqlSelect select, // Expand the select item: fully-qualify columns, and convert // parentheses-free functions such as LOCALTIME into explicit function // calls. - selectScope = getSelectScope(select); + selectScope = scopeMap.getSelectScope(select); expanded = expandSelectExpr(selectItem, scope, select); } final String alias = @@ -614,8 +585,8 @@ private static Map getFieldAliases(final SelectScope scope) { private List deriveNaturalJoinColumnList(SqlJoin join) { return SqlValidatorUtil.deriveNaturalJoinColumnList( catalogReader.nameMatcher(), - getNamespaceOrThrow(join.getLeft()).getRowType(), - getNamespaceOrThrow(join.getRight()).getRowType()); + scopeMap.getNamespaceOrThrow(join.getLeft()).getRowType(), + scopeMap.getNamespaceOrThrow(join.getRight()).getRowType()); } private static SqlNode expandCommonColumn(SqlSelect sqlSelect, @@ -708,7 +679,7 @@ private boolean expandStar(List selectItems, Set aliases, includeSystemVars); } else { final SqlNode from2 = SqlNonNullableAccessors.getNode(child); - final SqlValidatorNamespace fromNs = getNamespaceOrThrow(from2, scope); + final SqlValidatorNamespace fromNs = scopeMap.getNamespaceOrThrow(from2, scope); final RelDataType rowType = fromNs.getRowType(); for (RelDataTypeField field : rowType.getFieldList()) { String columnName = field.getName(); @@ -860,7 +831,7 @@ private boolean addOrExpandField(List selectItems, Set aliases, SqlNode outermostNode = performUnconditionalRewrites(topNode, false); cursorSet.add(outermostNode); if (outermostNode.isA(SqlKind.TOP_LEVEL)) { - registerQuery( + scopePopulator.registerQuery( scope, null, outermostNode, @@ -868,7 +839,7 @@ private boolean addOrExpandField(List selectItems, Set aliases, null, false); } - final SqlValidatorNamespace ns = getNamespace(outermostNode); + final SqlValidatorNamespace ns = scopeMap.getNamespace(outermostNode); if (ns == null) { throw new AssertionError("Not a query: " + outermostNode); } @@ -905,7 +876,7 @@ void lookupSelectHints( IdInfo info = idPositions.get(pos.toString()); if (info == null) { SqlNode fromNode = select.getFrom(); - final SqlValidatorScope fromScope = getFromScope(select); + final SqlValidatorScope fromScope = scopeMap.getFromScope(select); lookupFromHints(fromNode, fromScope, pos, hintList); } else { lookupNameCompletionHints(info.scope, info.id.names, @@ -932,7 +903,7 @@ private void lookupFromHints( // This can happen in cases like "select * _suggest_", so from clause is absent return; } - final SqlValidatorNamespace ns = getNamespaceOrThrow(node); + final SqlValidatorNamespace ns = scopeMap.getNamespaceOrThrow(node); if (ns.isWrapperFor(IdentifierNamespace.class)) { IdentifierNamespace idNs = ns.unwrap(IdentifierNamespace.class); final SqlIdentifier id = idNs.getId(); @@ -984,7 +955,7 @@ private void lookupJoinHints( case ON: requireNonNull(condition, () -> "join.getCondition() for " + join) .findValidOptions(this, - getScopeOrThrow(join), + scopeMap.getScopeOrThrow(join), pos, hintList); return; default: @@ -1141,7 +1112,7 @@ private SqlNode validateScopedExpression( top = outermostNode; TRACER.trace("After unconditional rewrite: {}", outermostNode); if (outermostNode.isA(SqlKind.TOP_LEVEL)) { - registerQuery(scope, null, outermostNode, outermostNode, null, false); + scopePopulator.registerQuery(scope, null, outermostNode, outermostNode, null, false); } outermostNode.validate(this, scope); if (!outermostNode.isA(SqlKind.TOP_LEVEL)) { @@ -1155,7 +1126,7 @@ private SqlNode validateScopedExpression( @Override public void validateQuery(SqlNode node, SqlValidatorScope scope, RelDataType targetRowType) { - final SqlValidatorNamespace ns = getNamespaceOrThrow(node, scope); + final SqlValidatorNamespace ns = scopeMap.getNamespaceOrThrow(node, scope); if (node.getKind() == SqlKind.TABLESAMPLE) { List operands = ((SqlCall) node).getOperandList(); SqlSampleSpec sampleSpec = SqlLiteral.sampleValue(operands.get(1)); @@ -1169,10 +1140,10 @@ private SqlNode validateScopedExpression( throw SqlUtil.newContextException(node.getParserPosition(), RESOURCE.invalidSampleSize()); } - validateFeature(RESOURCE.sQLFeature_T613(), node.getParserPosition()); + sqlCluster.validateFeature(RESOURCE.sQLFeature_T613(), node.getParserPosition()); } else if (sampleSpec instanceof SqlSampleSpec.SqlSubstitutionSampleSpec) { - validateFeature(RESOURCE.sQLFeatureExt_T613_Substitution(), + sqlCluster.validateFeature(RESOURCE.sQLFeatureExt_T613_Substitution(), node.getParserPosition()); } } @@ -1235,209 +1206,16 @@ protected void validateNamespace(final SqlValidatorNamespace namespace, } } - @Override public SqlValidatorScope getEmptyScope() { - return new EmptyScope(this); - } - - private SqlValidatorScope getScope(SqlSelect select, Clause clause) { - return requireNonNull( - clauseScopes.get(IdPair.of(select, clause)), - () -> "no " + clause + " scope for " + select); - } - - public SqlValidatorScope getCursorScope(SqlSelect select) { - return getScope(select, Clause.CURSOR); - } - - @Override public SqlValidatorScope getWhereScope(SqlSelect select) { - return getScope(select, Clause.WHERE); - } - - @Override public SqlValidatorScope getSelectScope(SqlSelect select) { - return getScope(select, Clause.SELECT); - } - - @Override public SqlValidatorScope getMeasureScope(SqlSelect select) { - return getScope(select, Clause.MEASURE); - } - - @Override public @Nullable SelectScope getRawSelectScope(SqlSelect select) { - SqlValidatorScope scope = clauseScopes.get(IdPair.of(select, Clause.SELECT)); - if (scope instanceof AggregatingSelectScope) { - scope = ((AggregatingSelectScope) scope).getParent(); - } - return (SelectScope) scope; - } - - private SelectScope getRawSelectScopeNonNull(SqlSelect select) { - return requireNonNull(getRawSelectScope(select), - () -> "getRawSelectScope for " + select); - } - - @Override public SqlValidatorScope getHavingScope(SqlSelect select) { - // Yes, it's the same as getSelectScope - return getScope(select, Clause.SELECT); - } - - @Override public SqlValidatorScope getGroupScope(SqlSelect select) { - // Yes, it's the same as getWhereScope - return getScope(select, Clause.WHERE); - } - - @Override public SqlValidatorScope getFromScope(SqlSelect select) { - return requireNonNull(scopes.get(select), - () -> "no scope for " + select); - } - - @Override public SqlValidatorScope getOrderScope(SqlSelect select) { - return getScope(select, Clause.ORDER); - } - - @Override public SqlValidatorScope getMatchRecognizeScope(SqlMatchRecognize node) { - return getScopeOrThrow(node); - } - - @Override public SqlValidatorScope getLambdaScope(SqlLambda node) { - return getScopeOrThrow(node); - } - - @Override public SqlValidatorScope getJoinScope(SqlNode node) { - return requireNonNull(scopes.get(stripAs(node)), - () -> "scope for " + node); - } - - @Override public SqlValidatorScope getOverScope(SqlNode node) { - return getScopeOrThrow(node); - } - @Override public SqlValidatorScope getWithScope(SqlNode withItem) { - assert withItem.getKind() == SqlKind.WITH_ITEM; - return getScopeOrThrow(withItem); - } - - private SqlValidatorScope getScopeOrThrow(SqlNode node) { - return requireNonNull(scopes.get(node), () -> "scope for " + node); - } - - private @Nullable SqlValidatorNamespace getNamespace(SqlNode node, - SqlValidatorScope scope) { - if (node instanceof SqlIdentifier && scope instanceof DelegatingScope) { - final SqlIdentifier id = (SqlIdentifier) node; - final DelegatingScope idScope = - (DelegatingScope) ((DelegatingScope) scope).getParent(); - return getNamespace(id, idScope); - } else if (node instanceof SqlCall) { - // Handle extended identifiers. - final SqlCall call = (SqlCall) node; - switch (call.getOperator().getKind()) { - case TABLE_REF: - return getNamespace(call.operand(0), scope); - case EXTEND: - final SqlNode operand0 = call.getOperandList().get(0); - final SqlIdentifier identifier = operand0.getKind() == SqlKind.TABLE_REF - ? ((SqlCall) operand0).operand(0) - : (SqlIdentifier) operand0; - final DelegatingScope idScope = (DelegatingScope) scope; - return getNamespace(identifier, idScope); - case AS: - final SqlNode nested = call.getOperandList().get(0); - switch (nested.getKind()) { - case TABLE_REF: - case EXTEND: - return getNamespace(nested, scope); - default: - break; - } - break; - default: - break; - } - } - return getNamespace(node); - } - - private @Nullable SqlValidatorNamespace getNamespace(SqlIdentifier id, - @Nullable DelegatingScope scope) { - if (id.isSimple()) { - final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); - final SqlValidatorScope.ResolvedImpl resolved = - new SqlValidatorScope.ResolvedImpl(); - requireNonNull(scope, () -> "scope needed to lookup " + id) - .resolve(id.names, nameMatcher, false, resolved); - if (resolved.count() == 1) { - return resolved.only().namespace; - } - } - return getNamespace(id); - } - - @Override public @Nullable SqlValidatorNamespace getNamespace(SqlNode node) { - switch (node.getKind()) { - case AS: - - // AS has a namespace if it has a column list 'AS t (c1, c2, ...)' - final SqlValidatorNamespace ns = namespaces.get(node); - if (ns != null) { - return ns; - } - // fall through - case TABLE_REF: - case SNAPSHOT: - case OVER: - case COLLECTION_TABLE: - case ORDER_BY: - case TABLESAMPLE: - return getNamespace(((SqlCall) node).operand(0)); - default: - return namespaces.get(node); - } + @Override public SqlValidatorScope createEmptyScope() { + return new EmptyScope(this); } - /** - * Namespace for the given node. - * - * @param node node to compute the namespace for - * @return namespace for the given node, never null - * @see #getNamespace(SqlNode) - */ - @API(since = "1.27", status = API.Status.INTERNAL) - SqlValidatorNamespace getNamespaceOrThrow(SqlNode node) { - return requireNonNull( - getNamespace(node), - () -> "namespace for " + node); + @Override public ScopeMap getScopeMap() { + return scopeMap; } - /** - * Namespace for the given node. - * - * @param node node to compute the namespace for - * @param scope namespace scope - * @return namespace for the given node, never null - * @see #getNamespace(SqlNode) - */ - @API(since = "1.27", status = API.Status.INTERNAL) - SqlValidatorNamespace getNamespaceOrThrow(SqlNode node, - SqlValidatorScope scope) { - return requireNonNull( - getNamespace(node, scope), - () -> "namespace for " + node + ", scope " + scope); - } - /** - * Namespace for the given node. - * - * @param id identifier to resolve - * @param scope namespace scope - * @return namespace for the given node, never null - * @see #getNamespace(SqlIdentifier, DelegatingScope) - */ - @API(since = "1.26", status = API.Status.INTERNAL) - SqlValidatorNamespace getNamespaceOrThrow(SqlIdentifier id, - @Nullable DelegatingScope scope) { - return requireNonNull( - getNamespace(id, scope), - () -> "namespace for " + id + ", scope " + scope); - } private void handleOffsetFetch(@Nullable SqlNode offset, @Nullable SqlNode fetch) { if (offset instanceof SqlDynamicParam) { @@ -1574,7 +1352,7 @@ private void handleOffsetFetch(@Nullable SqlNode offset, @Nullable SqlNode fetch selectList.add(SqlIdentifier.star(SqlParserPos.ZERO)); final SqlNodeList orderList; SqlSelect innerSelect = getInnerSelect(node); - if (innerSelect != null && isAggregate(innerSelect)) { + if (innerSelect != null && validatorAggStuff.isAggregate(innerSelect)) { 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. @@ -1917,7 +1695,7 @@ protected SqlSelect createSourceSelectForDelete(SqlDelete call) { if (type != null) { return type; } - final SqlValidatorNamespace ns = getNamespace(node); + final SqlValidatorNamespace ns = scopeMap.getNamespace(node); if (ns != null) { return ns.getType(); } @@ -1989,7 +1767,7 @@ protected SqlSelect createSourceSelectForDelete(SqlDelete call) { if (type != null) { return type; } - final SqlValidatorNamespace ns = getNamespace(expr); + final SqlValidatorNamespace ns = scopeMap.getNamespace(expr); if (ns != null) { return ns.getType(); } @@ -2103,7 +1881,7 @@ protected void inferUnknownTypes( requireNonNull(inferredType, "inferredType"); requireNonNull(scope, "scope"); requireNonNull(node, "node"); - final SqlValidatorScope newScope = scopes.get(node); + final SqlValidatorScope newScope = scopeMap.getScope(node); if (newScope != null) { scope = newScope; } @@ -2186,7 +1964,7 @@ protected void inferUnknownTypes( // For MEASURE operator, use the measure scope (which has additional // aliases available) if (scope instanceof SelectScope) { - scope = getMeasureScope(((SelectScope) scope).getNode()); + scope = scopeMap.getMeasureScope(((SelectScope) scope).getNode()); } inferUnknownTypes(inferredType, scope, ((SqlCall) node).operand(0)); } else if (node instanceof SqlCall) { @@ -2248,1169 +2026,6 @@ protected boolean shouldAllowIntermediateOrderBy() { return true; } - private void registerMatchRecognize( - SqlValidatorScope parentScope, - SqlValidatorScope usingScope, - SqlMatchRecognize call, - SqlNode enclosingNode, - @Nullable 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); - } - - private void registerPivot( - SqlValidatorScope parentScope, - SqlValidatorScope usingScope, - SqlPivot pivot, - SqlNode enclosingNode, - @Nullable String alias, - boolean forceNullable) { - final PivotNamespace namespace = - createPivotNameSpace(pivot, enclosingNode); - registerNamespace(usingScope, alias, namespace, forceNullable); - - final SqlValidatorScope scope = - new PivotScope(parentScope, pivot); - scopes.put(pivot, scope); - - // parse input query - SqlNode expr = pivot.query; - SqlNode newExpr = - registerFrom(parentScope, scope, true, expr, - expr, null, null, forceNullable, false); - if (expr != newExpr) { - pivot.setOperand(0, newExpr); - } - } - - protected PivotNamespace createPivotNameSpace(SqlPivot call, - SqlNode enclosingNode) { - return new PivotNamespace(this, call, enclosingNode); - } - - private void registerUnpivot( - SqlValidatorScope parentScope, - SqlValidatorScope usingScope, - SqlUnpivot call, - SqlNode enclosingNode, - @Nullable String alias, - boolean forceNullable) { - final UnpivotNamespace namespace = - createUnpivotNameSpace(call, enclosingNode); - registerNamespace(usingScope, alias, namespace, forceNullable); - - final SqlValidatorScope scope = - new UnpivotScope(parentScope, call); - scopes.put(call, scope); - - // parse input query - SqlNode expr = call.query; - SqlNode newExpr = - registerFrom(parentScope, scope, true, expr, - expr, null, null, forceNullable, false); - if (expr != newExpr) { - call.setOperand(0, newExpr); - } - } - - protected UnpivotNamespace createUnpivotNameSpace(SqlUnpivot call, - SqlNode enclosingNode) { - return new UnpivotNamespace(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( - @Nullable SqlValidatorScope usingScope, - @Nullable String alias, - SqlValidatorNamespace ns, - boolean forceNullable) { - SqlValidatorNamespace namespace = - namespaces.get(requireNonNull(ns.getNode(), () -> "ns.getNode() for " + ns)); - if (namespace == null) { - namespaces.put(requireNonNull(ns.getNode()), ns); - namespace = ns; - } - if (usingScope != null) { - if (alias == null) { - throw new IllegalArgumentException("Registering namespace " + ns - + ", into scope " + usingScope + ", so alias must not be null"); - } - usingScope.addChild(namespace, alias, forceNullable); - } - } - - /** - * Registers scopes and namespaces implied a relational expression in the - * FROM clause. - * - *

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

Likewise, {@code enclosingNode} and {@code node} are often the same. - * {@code enclosingNode} is the topmost node within the FROM clause, from - * which any decorations like an alias (AS alias) or a table - * sample clause are stripped away to get {@code node}. Both are recorded in - * the namespace. - * - * @param parentScope0 Parent scope that 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} - */ - // CHECKSTYLE: OFF - // CheckStyle thinks this method is too long - private SqlNode registerFrom( - SqlValidatorScope parentScope0, - SqlValidatorScope usingScope, - boolean register, - final SqlNode node, - SqlNode enclosingNode, - @Nullable String alias, - @Nullable 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 = SqlValidatorUtil.alias(node); - if (alias == null) { - alias = SqlValidatorUtil.alias(node, nextGeneratedId++); - } - if (config.identifierExpansion()) { - newNode = SqlValidatorUtil.addAlias(node, alias); - } - break; - - case SELECT: - case UNION: - case INTERSECT: - case EXCEPT: - case VALUES: - case UNNEST: - case OTHER_FUNCTION: - case COLLECTION_TABLE: - case PIVOT: - case UNPIVOT: - case MATCH_RECOGNIZE: - case WITH: - // give this anonymous construct a name since later - // query processing stages rely on it - alias = SqlValidatorUtil.alias(node, nextGeneratedId++); - if (config.identifierExpansion()) { - // Since we're expanding identifiers, we should make the - // aliases explicit too, otherwise the expanded query - // will not be consistent if we convert back to SQL, e.g. - // "select EXPR$1.EXPR$2 from values (1)". - newNode = SqlValidatorUtil.addAlias(node, alias); - } - break; - default: - break; - } - } - - final SqlValidatorScope parentScope; - 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(parentScope0, node2); - if (usingScope instanceof ListScope) { - for (ScopeChild child : ((ListScope) usingScope).children) { - tableScope.addChild(child.namespace, child.name, child.nullable); - } - } - parentScope = tableScope; - } else { - parentScope = parentScope0; - } - - SqlCall call; - SqlNode operand; - SqlNode newOperand; - - switch (kind) { - case AS: - call = (SqlCall) node; - if (alias == null) { - alias = String.valueOf(call.operand(1)); - } - expr = call.operand(0); - final boolean needAliasNamespace = call.operandCount() > 2 - || expr.getKind() == SqlKind.VALUES || expr.getKind() == SqlKind.UNNEST - || expr.getKind() == SqlKind.COLLECTION_TABLE; - newExpr = - registerFrom( - parentScope, - usingScope, - !needAliasNamespace, - 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 (needAliasNamespace) { - 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 PIVOT: - registerPivot(parentScope, usingScope, (SqlPivot) node, enclosingNode, - alias, forceNullable); - return node; - - case UNPIVOT: - registerUnpivot(parentScope, usingScope, (SqlUnpivot) 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(); - boolean forceLeftNullable = forceNullable; - boolean forceRightNullable = forceNullable; - switch (join.getJoinType()) { - case LEFT: - case LEFT_ASOF: - forceRightNullable = true; - break; - case RIGHT: - forceLeftNullable = true; - break; - case FULL: - forceLeftNullable = true; - forceRightNullable = true; - break; - default: - 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); - } - scopes.putIfAbsent(stripAs(join.getRight()), parentScope); - scopes.putIfAbsent(stripAs(join.getLeft()), parentScope); - 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, requireNonNull(alias, "alias"), forceNullable); - if (extendList != null && !extendList.isEmpty()) { - 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); - } - // If the operator is SqlWindowTableFunction, restricts the scope as - // its first operand's (the table) scope. - if (operand instanceof SqlBasicCall) { - final SqlBasicCall call1 = (SqlBasicCall) operand; - final SqlOperator op = call1.getOperator(); - if (op instanceof SqlWindowTableFunction - && call1.operand(0).getKind() == SqlKind.SELECT) { - scopes.put(node, getSelectScope(call1.operand(0))); - return newNode; - } - } - // Put the usingScope which can be a JoinScope - // or a SelectScope, in order to see the left items - // of the JOIN tree. - scopes.put(node, usingScope); - return newNode; - - 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 = SqlValidatorUtil.alias(node, nextGeneratedId++); - } - registerQuery( - parentScope, - register ? usingScope : null, - node, - enclosingNode, - alias, - forceNullable); - return newNode; - - case OVER: - if (!shouldAllowOverRelation()) { - throw Util.unexpected(kind); - } - call = (SqlCall) node; - final OverScope overScope = new OverScope(usingScope, call); - scopes.put(call, overScope); - operand = call.operand(0); - newOperand = - registerFrom( - parentScope, - overScope, - true, - operand, - enclosingNode, - alias, - extendList, - forceNullable, - lateral); - if (newOperand != operand) { - call.setOperand(0, newOperand); - } - - for (ScopeChild child : overScope.children) { - registerNamespace(register ? usingScope : null, child.name, - child.namespace, forceNullable); - } - - return newNode; - - case TABLE_REF: - call = (SqlCall) node; - registerFrom(parentScope, - usingScope, - register, - call.operand(0), - enclosingNode, - alias, - extendList, - forceNullable, - lateral); - if (extendList != null && !extendList.isEmpty()) { - return enclosingNode; - } - return newNode; - - case EXTEND: - final SqlCall extend = (SqlCall) node; - return registerFrom(parentScope, - usingScope, - true, - extend.getOperandList().get(0), - extend, - alias, - (SqlNodeList) extend.getOperandList().get(1), - forceNullable, - lateral); - - case SNAPSHOT: - call = (SqlCall) node; - operand = call.operand(0); - newOperand = - registerFrom(parentScope, - usingScope, - register, - operand, - enclosingNode, - alias, - extendList, - forceNullable, - lateral); - if (newOperand != operand) { - call.setOperand(0, newOperand); - } - // Put the usingScope which can be a JoinScope - // or a SelectScope, in order to see the left items - // of the JOIN tree. - scopes.put(node, usingScope); - return newNode; - - default: - throw Util.unexpected(kind); - } - } - // CHECKSTYLE: ON - - protected boolean shouldAllowOverRelation() { - return false; - } - - /** - * Creates a namespace for a SELECT node. Derived class may - * override this factory method. - * - * @param select Select node - * @param enclosingNode Enclosing node - * @return Select namespace - */ - protected SelectNamespace createSelectNamespace( - SqlSelect select, - SqlNode enclosingNode) { - return new SelectNamespace(this, select, enclosingNode); - } - - /** - * Creates a namespace for a set operation (UNION, - * INTERSECT, or EXCEPT). Derived class may override - * this factory method. - * - * @param call Call to set operation - * @param enclosingNode Enclosing node - * @return Set operation namespace - */ - protected SetopNamespace createSetopNamespace( - SqlCall call, - SqlNode enclosingNode) { - return new SetopNamespace(this, call, enclosingNode); - } - - /** - * Registers a query in a parent scope. - * - * @param parentScope Parent scope which this scope turns to in order to - * resolve objects - * @param usingScope Scope whose child list this scope should add itself to - * @param node Query node - * @param alias Name of this query within its parent. Must be specified - * if usingScope != null - */ - protected void registerQuery( - SqlValidatorScope parentScope, - @Nullable SqlValidatorScope usingScope, - SqlNode node, - SqlNode enclosingNode, - @Nullable String alias, - boolean forceNullable) { - 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, - @Nullable SqlValidatorScope usingScope, - SqlNode node, - SqlNode enclosingNode, - @Nullable String alias, - boolean forceNullable, - boolean checkUpdate) { - requireNonNull(node, "node"); - requireNonNull(enclosingNode, "enclosingNode"); - checkArgument(usingScope == null || alias != null); - - SqlCall call; - List operands; - switch (node.getKind()) { - case SELECT: - final SqlSelect select = (SqlSelect) node; - final SelectNamespace selectNs = - createSelectNamespace(select, enclosingNode); - registerNamespace(usingScope, alias, selectNs, forceNullable); - final SqlValidatorScope windowParentScope = - first(usingScope, parentScope); - SelectScope selectScope = - new SelectScope(parentScope, windowParentScope, select); - scopes.put(select, selectScope); - - // Start by registering the WHERE clause - clauseScopes.put(IdPair.of(select, Clause.WHERE), selectScope); - registerOperandSubQueries( - selectScope, - select, - SqlSelect.WHERE_OPERAND); - - // Register subqueries in the QUALIFY clause - registerOperandSubQueries( - selectScope, - select, - SqlSelect.QUALIFY_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 aggregate query, the SELECT list and HAVING - // clause use a different scope, where you can only reference - // columns which are in the GROUP BY clause. - final SqlValidatorScope selectScope2 = - isAggregate(select) - ? new AggregatingSelectScope(selectScope, select, false) - : selectScope; - clauseScopes.put(IdPair.of(select, Clause.SELECT), selectScope2); - clauseScopes.put(IdPair.of(select, Clause.MEASURE), - new MeasureScope(selectScope, select)); - if (select.getGroup() != null) { - GroupByScope groupByScope = - new GroupByScope(selectScope, select.getGroup(), select); - clauseScopes.put(IdPair.of(select, Clause.GROUP_BY), groupByScope); - registerSubQueries(groupByScope, select.getGroup()); - } - registerOperandSubQueries( - selectScope2, - select, - SqlSelect.HAVING_OPERAND); - registerSubQueries(selectScope2, - SqlNonNullableAccessors.getSelectList(select)); - final SqlNodeList orderList = select.getOrderList(); - if (orderList != null) { - // If the query is 'SELECT DISTINCT', restrict the columns - // available to the ORDER BY clause. - final SqlValidatorScope selectScope3 = - select.isDistinct() - ? new AggregatingSelectScope(selectScope, select, true) - : selectScope2; - OrderByScope orderScope = - new OrderByScope(selectScope3, orderList, select); - clauseScopes.put(IdPair.of(select, Clause.ORDER), orderScope); - registerSubQueries(orderScope, orderList); - - if (!isAggregate(select)) { - // Since this is not an aggregate 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, - enclosingNode, - alias, - forceNullable); - break; - - case LAMBDA: - call = (SqlCall) node; - SqlLambdaScope lambdaScope = - new SqlLambdaScope(parentScope, (SqlLambda) call); - scopes.put(call, lambdaScope); - final LambdaNamespace lambdaNamespace = - new LambdaNamespace(this, (SqlLambda) call, node); - registerNamespace( - usingScope, - alias, - lambdaNamespace, - forceNullable); - operands = call.getOperandList(); - for (int i = 0; i < operands.size(); i++) { - registerOperandSubQueries(parentScope, call, i); - } - 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, - SqlNonNullableAccessors.getSourceSelect(deleteCall), - 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, - SqlNonNullableAccessors.getSourceSelect(updateCall), - 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, - SqlNonNullableAccessors.getSourceSelect(mergeCall), - 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 - SqlUpdate mergeUpdateCall = mergeCall.getUpdateCall(); - if (mergeUpdateCall != null) { - registerQuery( - getScope(SqlNonNullableAccessors.getSourceSelect(mergeCall), Clause.WHERE), - null, - mergeUpdateCall, - enclosingNode, - null, - false, - false); - } - SqlInsert mergeInsertCall = mergeCall.getInsertCall(); - if (mergeInsertCall != null) { - registerQuery( - parentScope, - null, - mergeInsertCall, - 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 = SqlValidatorUtil.alias(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, - @Nullable SqlValidatorScope usingScope, - SqlNode node, - SqlNode enclosingNode, - @Nullable 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); - @NonNull SqlValidatorScope recursiveScope = parentScope; - if (enclosingNode.getKind() == SqlKind.WITH_ITEM) { - if (node.getKind() != SqlKind.UNION) { - throw newValidationError(node, RESOURCE.recursiveWithMustHaveUnionSetOp()); - } else if (call.getOperandList().size() > 2) { - throw newValidationError(node, RESOURCE.recursiveWithMustHaveTwoChildUnionSetOp()); - } - final WithScope scope = (WithScope) scopes.get(enclosingNode); - // recursive scope is only set for the recursive queries. - recursiveScope = scope != null && scope.recursiveScope != null - ? requireNonNull(scope.recursiveScope) : parentScope; - } - for (int i = 0; i < call.getOperandList().size(); i++) { - SqlNode operand = call.getOperandList().get(i); - @NonNull SqlValidatorScope scope = i == 0 ? parentScope : recursiveScope; - registerQuery( - scope, - null, - operand, - operand, - null, - false); - } - } - - private void registerWith( - SqlValidatorScope parentScope, - @Nullable SqlValidatorScope usingScope, - SqlWith with, - SqlNode enclosingNode, - @Nullable String alias, - boolean forceNullable, - boolean checkUpdate) { - final WithNamespace withNamespace = - new WithNamespace(this, with, enclosingNode); - registerNamespace(usingScope, alias, withNamespace, forceNullable); - scopes.put(with, parentScope); - - SqlValidatorScope scope = parentScope; - for (SqlNode withItem_ : with.withList) { - final SqlWithItem withItem = (SqlWithItem) withItem_; - - final boolean isRecursiveWith = withItem.recursive.booleanValue(); - final SqlValidatorScope withScope = - new WithScope(scope, withItem, - isRecursiveWith ? new WithRecursiveScope(scope, withItem) : null); - scopes.put(withItem, withScope); - - registerQuery(scope, null, withItem.query, - withItem.recursive.booleanValue() ? withItem : with, withItem.name.getSimple(), - forceNullable); - registerNamespace(null, alias, - new WithItemNamespace(this, withItem, enclosingNode), - false); - scope = withScope; - } - registerQuery(scope, null, with.body, enclosingNode, alias, forceNullable, - checkUpdate); - } - - @Override public boolean isAggregate(SqlSelect select) { - if (getAggregate(select) != null) { - return true; - } - // Also when nested window aggregates are present - for (SqlCall call : overFinder.findAll(SqlNonNullableAccessors.getSelectList(select))) { - assert call.getKind() == SqlKind.OVER; - if (isNestedAggregateWindow(call.operand(0))) { - return true; - } - if (isOverAggregateWindow(call.operand(1))) { - return true; - } - } - return false; - } - - protected boolean isNestedAggregateWindow(SqlNode node) { - AggFinder nestedAggFinder = - new AggFinder(opTab, false, false, false, aggFinder, - catalogReader.nameMatcher()); - return nestedAggFinder.findAgg(node) != null; - } - - protected boolean isOverAggregateWindow(SqlNode node) { - return aggFinder.findAgg(node) != null; - } - - /** Returns the parse tree node (GROUP BY, HAVING, or an aggregate function - * call) that causes {@code select} to be an aggregate query, or null if it - * is not an aggregate query. - * - *

The node is useful context for error messages, - * but you cannot assume that the node is the only aggregate function. */ - protected @Nullable 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 @Nullable SqlNode getAgg(SqlSelect select) { - final SelectScope selectScope = getRawSelectScope(select); - if (selectScope != null) { - final List selectList = selectScope.getExpandedSelectList(); - if (selectList != null) { - return aggFinder.findAgg(selectList); - } - } - return aggFinder.findAgg(SqlNonNullableAccessors.getSelectList(select)); - } - - @Deprecated - @Override 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; - default: - break; - } - } - - private void registerSubQueries( - SqlValidatorScope parentScope, - @Nullable SqlNode node) { - if (node == null) { - return; - } - if (node.getKind().belongsTo(SqlKind.QUERY) - || node.getKind() == SqlKind.LAMBDA - || 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); - } - @Override public void validateIdentifier(SqlIdentifier id, SqlValidatorScope scope) { final SqlQualified fqId = scope.fullyQualify(id); if (this.config.columnReferenceExpansion()) { @@ -3610,7 +2225,7 @@ protected void validateFrom( // Validate the namespace representation of the node, just in case the // validation did not occur implicitly. - getNamespaceOrThrow(node, scope).validate(targetRowType); + scopeMap.getNamespaceOrThrow(node, scope).validate(targetRowType); } protected void validateTableFunction(SqlCall node, SqlValidatorScope scope, @@ -3687,7 +2302,7 @@ protected void validateUnnest(SqlCall call, SqlValidatorScope scope, private void checkRollUpInUsing(SqlIdentifier identifier, SqlNode leftOrRight, SqlValidatorScope scope) { - SqlValidatorNamespace namespace = getNamespace(leftOrRight, scope); + SqlValidatorNamespace namespace = scopeMap.getNamespace(leftOrRight, scope); if (namespace != null) { SqlValidatorTable sqlValidatorTable = namespace.getTable(); if (sqlValidatorTable != null) { @@ -3708,7 +2323,7 @@ protected void validateJoin(SqlJoin join, SqlValidatorScope scope) { final boolean natural = join.isNatural(); final JoinType joinType = join.getJoinType(); final JoinConditionType conditionType = join.getConditionType(); - final SqlValidatorScope joinScope = getScopeOrThrow(join); // getJoinScope? + final SqlValidatorScope joinScope = scopeMap.getScopeOrThrow(join); // getJoinScope? validateFrom(left, unknownType, joinScope); validateFrom(right, unknownType, joinScope); @@ -3974,7 +2589,7 @@ private RelDataType checkAndDeriveDataType(SqlIdentifier id, SqlNode node) { checkArgument(id.names.size() == 1); String name = id.names.get(0); SqlNameMatcher nameMatcher = getCatalogReader().nameMatcher(); - RelDataType rowType = getNamespaceOrThrow(node).getRowType(); + RelDataType rowType = scopeMap.getNamespaceOrThrow(node).getRowType(); final RelDataTypeField field = requireNonNull(nameMatcher.field(rowType, name), () -> "unable to find left field " + name + " in " + rowType); @@ -3987,7 +2602,7 @@ private RelDataType validateCommonInputJoinColumn(SqlIdentifier id, SqlNode leftOrRight, SqlValidatorScope scope, boolean natural) { checkArgument(id.names.size() == 1); final String name = id.names.get(0); - final SqlValidatorNamespace namespace = getNamespaceOrThrow(leftOrRight); + final SqlValidatorNamespace namespace = scopeMap.getNamespaceOrThrow(leftOrRight); final RelDataType rowType = namespace.getRowType(); final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); final RelDataTypeField field = nameMatcher.field(rowType, name); @@ -4024,7 +2639,7 @@ protected void validateSelect( // Namespace is either a select namespace or a wrapper around one. final SelectNamespace ns = - getNamespaceOrThrow(select).unwrap(SelectNamespace.class); + scopeMap.getNamespaceOrThrow(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 @@ -4033,7 +2648,7 @@ protected void validateSelect( SqlNode distinctNode = select.getModifierNode(SqlSelectKeyword.DISTINCT); if (distinctNode != null) { - validateFeature(RESOURCE.sQLFeature_E051_01(), + sqlCluster.validateFeature(RESOURCE.sQLFeature_E051_01(), distinctNode .getParserPosition()); } @@ -4056,7 +2671,7 @@ protected void validateSelect( } // Make sure that items in FROM clause have distinct aliases. - final SelectScope fromScope = (SelectScope) getFromScope(select); + final SelectScope fromScope = (SelectScope) scopeMap.getFromScope(select); List<@Nullable String> names = fromScope.getChildNames(); if (!catalogReader.nameMatcher().isCaseSensitive()) { //noinspection RedundantTypeArguments @@ -4115,11 +2730,11 @@ protected void validateSelect( } if (!qualifieds.isEmpty()) { if (select.getWhere() != null) { - forEachQualified(select.getWhere(), getWhereScope(select), + forEachQualified(select.getWhere(), scopeMap.getWhereScope(select), qualifieds::remove); } if (select.getHaving() != null) { - forEachQualified(select.getHaving(), getHavingScope(select), + forEachQualified(select.getHaving(), scopeMap.getHavingScope(select), qualifieds::remove); } @@ -4164,8 +2779,8 @@ protected void validateSelect( if (shouldCheckForRollUp(from)) { checkRollUpInSelectList(select); - checkRollUp(null, select, select.getWhere(), getWhereScope(select)); - checkRollUp(null, select, select.getHaving(), getHavingScope(select)); + checkRollUp(null, select, select.getWhere(), scopeMap.getWhereScope(select)); + checkRollUp(null, select, select.getHaving(), scopeMap.getHavingScope(select)); checkRollUpInWindowDecl(select); checkRollUpInGroupBy(select); checkRollUpInOrderBy(select); @@ -4186,7 +2801,7 @@ private static void forEachQualified(SqlNode node, SqlValidatorScope scope, } private void checkRollUpInSelectList(SqlSelect select) { - SqlValidatorScope scope = getSelectScope(select); + SqlValidatorScope scope = scopeMap.getSelectScope(select); for (SqlNode item : SqlNonNullableAccessors.getSelectList(select)) { if (SqlValidatorUtil.isMeasure(item)) { continue; @@ -4199,7 +2814,8 @@ private void checkRollUpInGroupBy(SqlSelect select) { SqlNodeList group = select.getGroup(); if (group != null) { for (SqlNode node : group) { - checkRollUp(null, select, node, getGroupScope(select), "GROUP BY"); + SqlValidatorScope groupScope = scopeMap.getGroupScope(select); + checkRollUp(null, select, node, groupScope, "GROUP BY"); } } } @@ -4208,7 +2824,8 @@ private void checkRollUpInOrderBy(SqlSelect select) { SqlNodeList orderList = select.getOrderList(); if (orderList != null) { for (SqlNode node : orderList) { - checkRollUp(null, select, node, getOrderScope(select), "ORDER BY"); + SqlValidatorScope orderScope = scopeMap.getOrderScope(select); + checkRollUp(null, select, node, orderScope, "ORDER BY"); } } } @@ -4227,7 +2844,7 @@ private void checkRollUpInWindow(@Nullable SqlWindow window, SqlValidatorScope s private void checkRollUpInWindowDecl(SqlSelect select) { for (SqlNode decl : select.getWindowList()) { - checkRollUpInWindow((SqlWindow) decl, getSelectScope(select)); + checkRollUpInWindow((SqlWindow) decl, scopeMap.getSelectScope(select)); } } @@ -4436,7 +3053,7 @@ private static SqlModality deduceModality(SqlNode query) { @Override public boolean validateModality(SqlSelect select, SqlModality modality, boolean fail) { - final SelectScope scope = getRawSelectScopeNonNull(select); + final SelectScope scope = scopeMap.getRawSelectScopeNonNull(select); switch (modality) { case STREAM: @@ -4486,7 +3103,7 @@ private static SqlModality deduceModality(SqlNode query) { } // Make sure that aggregation is possible. - final SqlNode aggregateNode = getAggregate(select); + final SqlNode aggregateNode = validatorAggStuff.getAggregate(select); if (aggregateNode != null) { switch (modality) { case STREAM: @@ -4561,7 +3178,7 @@ protected void validateWindowClause(SqlSelect select) { return; } - final SelectScope windowScope = (SelectScope) getFromScope(select); + final SelectScope windowScope = (SelectScope) scopeMap.getFromScope(select); // 1. ensure window names are simple // 2. ensure they are unique within this scope @@ -4614,7 +3231,7 @@ protected void validateQualifyClause(SqlSelect select) { return; } - SqlValidatorScope qualifyScope = getSelectScope(select); + SqlValidatorScope qualifyScope = scopeMap.getSelectScope(select); qualifyNode = extendedExpand(qualifyNode, qualifyScope, select, Clause.QUALIFY); select.setQualify(qualifyNode); @@ -4631,7 +3248,7 @@ protected void validateQualifyClause(SqlSelect select) { throw newValidationError(qualifyNode, RESOURCE.condMustBeBoolean("QUALIFY")); } - boolean qualifyContainsWindowFunction = overFinder.findAgg(qualifyNode) != null; + boolean qualifyContainsWindowFunction = validatorAggStuff.findOver(qualifyNode) != null; if (!qualifyContainsWindowFunction) { throw newValidationError(qualifyNode, RESOURCE.qualifyExpressionMustContainWindowFunction(qualifyNode.toString())); @@ -4639,7 +3256,7 @@ protected void validateQualifyClause(SqlSelect select) { } @Override public void validateWith(SqlWith with, SqlValidatorScope scope) { - final SqlValidatorNamespace namespace = getNamespaceOrThrow(with); + final SqlValidatorNamespace namespace = scopeMap.getNamespaceOrThrow(with); validateNamespace(namespace, unknownType); } @@ -4722,7 +3339,7 @@ protected void validateOrderList(SqlSelect select) { throw newValidationError(select, RESOURCE.invalidOrderByPos()); } } - final SqlValidatorScope orderScope = getOrderScope(select); + final SqlValidatorScope orderScope = scopeMap.getOrderScope(select); requireNonNull(orderScope, "orderScope"); List expandList = new ArrayList<>(); @@ -4747,7 +3364,7 @@ protected void validateOrderList(SqlSelect select) { * @param groupByItem GROUP BY clause item */ private void validateGroupByItem(SqlSelect select, SqlNode groupByItem) { - final SqlValidatorScope groupByScope = getGroupScope(select); + final SqlValidatorScope groupByScope = scopeMap.getGroupScope(select); validateGroupByExpr(groupByItem, groupByScope); groupByScope.validateExpr(groupByItem); } @@ -4783,7 +3400,7 @@ private void validateGroupByExpr(SqlNode groupByItem, private void validateOrderItem(SqlSelect select, SqlNode orderItem) { switch (orderItem.getKind()) { case DESCENDING: - validateFeature(RESOURCE.sQLConformance_OrderByDesc(), + sqlCluster.validateFeature(RESOURCE.sQLConformance_OrderByDesc(), orderItem.getParserPosition()); validateOrderItem(select, ((SqlCall) orderItem).operand(0)); @@ -4792,7 +3409,7 @@ private void validateOrderItem(SqlSelect select, SqlNode orderItem) { break; } - final SqlValidatorScope orderScope = getOrderScope(select); + final SqlValidatorScope orderScope = scopeMap.getOrderScope(select); validateExpr(orderItem, orderScope); } @@ -4803,7 +3420,7 @@ private void validateOrderItem(SqlSelect select, SqlNode orderItem) { return orderExpr2; } - final SqlValidatorScope scope = getOrderScope(select); + final SqlValidatorScope scope = scopeMap.getOrderScope(select); inferUnknownTypes(unknownType, scope, orderExpr2); final RelDataType type = deriveType(scope, orderExpr2); setValidatedNodeType(orderExpr2, type); @@ -4834,8 +3451,8 @@ protected void validateGroupClause(SqlSelect select) { return; } final String clause = "GROUP BY"; - validateNoAggs(aggOrOverFinder, groupList, clause); - final SqlValidatorScope groupScope = getGroupScope(select); + validateNoAggs(validatorAggStuff.getAggOrOverFinder(), groupList, clause); + final SqlValidatorScope groupScope = scopeMap.getGroupScope(select); // expand the expression in group list. List expandedList = new ArrayList<>(); @@ -4870,7 +3487,7 @@ protected void validateGroupClause(SqlSelect select) { // 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); + final SqlValidatorScope selectScope = scopeMap.getSelectScope(select); AggregatingSelectScope aggregatingScope = null; if (selectScope instanceof AggregatingSelectScope) { aggregatingScope = (AggregatingSelectScope) selectScope; @@ -4883,7 +3500,7 @@ protected void validateGroupClause(SqlSelect select) { validateGroupItem(groupScope, aggregatingScope, groupItem); } - SqlNode agg = aggFinder.findAgg(groupList); + SqlNode agg = validatorAggStuff.findAgg(groupList); if (agg != null) { throw newValidationError(agg, RESOURCE.aggregateIllegalInClause(clause)); } @@ -4925,7 +3542,7 @@ protected void validateWhereClause(SqlSelect select) { if (where == null) { return; } - final SqlValidatorScope whereScope = getWhereScope(select); + final SqlValidatorScope whereScope = scopeMap.getWhereScope(select); final SqlNode expandedWhere = expand(where, whereScope); select.setWhere(expandedWhere); validateWhereOrOn(whereScope, expandedWhere, "WHERE"); @@ -4935,7 +3552,7 @@ protected void validateWhereOrOn( SqlValidatorScope scope, SqlNode condition, String clause) { - validateNoAggs(aggOrOverOrGroupFinder, condition, clause); + validateNoAggs(validatorAggStuff.getAggOrOverOrGroupFinder(), condition, clause); inferUnknownTypes( booleanType, scope, @@ -4970,7 +3587,7 @@ protected void validateHavingClause(SqlSelect select) { } SqlNode originalHaving = having; final AggregatingScope havingScope = - (AggregatingScope) getSelectScope(select); + (AggregatingScope) scopeMap.getSelectScope(select); if (config.conformance().isHavingAlias()) { SqlNode newExpr = extendedExpand(having, havingScope, select, Clause.HAVING); if (having != newExpr) { @@ -4996,7 +3613,7 @@ protected RelDataType validateSelectList(final SqlNodeList selectItems, // are ignored. // Validate SELECT list. Expand terms of the form "*" or "TABLE.*". - final SqlValidatorScope selectScope = getSelectScope(select); + final SqlValidatorScope selectScope = scopeMap.getSelectScope(select); final List expandedSelectItems = new ArrayList<>(); final Set aliases = new HashSet<>(); final PairList fieldList = PairList.of(); @@ -5027,19 +3644,19 @@ protected RelDataType validateSelectList(final SqlNodeList selectItems, if (config.identifierExpansion()) { select.setSelectList(newSelectList); } - getRawSelectScopeNonNull(select).setExpandedSelectList(expandedSelectItems); + scopeMap.getRawSelectScopeNonNull(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); - final boolean aggregate = isAggregate(select) || select.isDistinct(); + final boolean aggregate = validatorAggStuff.isAggregate(select) || select.isDistinct(); for (SqlNode selectItem : expandedSelectItems) { if (SqlValidatorUtil.isMeasure(selectItem) && aggregate) { throw newValidationError(selectItem, RESOURCE.measureInAggregateQuery()); } - validateNoAggs(groupFinder, selectItem, "SELECT"); + validateNoAggs(validatorAggStuff.getGroupFinder(), selectItem, "SELECT"); validateExpr(selectItem, selectScope); } @@ -5075,7 +3692,7 @@ private void validateExpr(SqlNode expr, SqlValidatorScope scope) { } if (SqlValidatorUtil.isMeasure(expr) && scope instanceof SelectScope) { - scope = getMeasureScope(((SelectScope) scope).getNode()); + scope = scopeMap.getMeasureScope(((SelectScope) scope).getNode()); } // Call on the expression to validate itself. @@ -5114,7 +3731,7 @@ private void handleScalarSubQuery(SqlSelect parentSelect, SqlValidatorUtil.alias(selectItem, aliasList.size()); aliasList.add(alias); - final SelectScope scope = (SelectScope) getWhereScope(parentSelect); + final SelectScope scope = (SelectScope) scopeMap.getWhereScope(parentSelect); final RelDataType type = deriveType(scope, selectItem); setValidatedNodeType(selectItem, type); @@ -5176,7 +3793,7 @@ protected RelDataType createTargetRowType( } @Override public void validateInsert(SqlInsert insert) { - final SqlValidatorNamespace targetNamespace = getNamespaceOrThrow(insert); + final SqlValidatorNamespace targetNamespace = scopeMap.getNamespaceOrThrow(insert); validateNamespace(targetNamespace, unknownType); final RelOptTable relOptTable = SqlValidatorUtil.getRelOptTable(targetNamespace, @@ -5199,7 +3816,7 @@ protected RelDataType createTargetRowType( final SqlSelect sqlSelect = (SqlSelect) source; validateSelect(sqlSelect, targetRowType); } else { - final SqlValidatorScope scope = scopes.get(source); + final SqlValidatorScope scope = scopeMap.getScope(source); requireNonNull(scope, "scope"); validateQuery(source, scope, targetRowType); } @@ -5209,7 +3826,7 @@ protected RelDataType createTargetRowType( // 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 = getNamespaceOrThrow(source).getRowType(); + final RelDataType sourceRowType = scopeMap.getNamespaceOrThrow(source).getRowType(); final RelDataType logicalTargetRowType = getLogicalTargetRowType(targetRowType, insert); setValidatedNodeType(insert, logicalTargetRowType); @@ -5235,7 +3852,7 @@ protected RelDataType createTargetRowType( targetRowTypeToValidate, realTargetRowType, source, logicalSourceRowType, logicalTargetRowType); - checkTypeAssignment(scopes.get(source), + checkTypeAssignment(scopeMap.getScope(source), table, logicalSourceRowType, targetRowTypeToValidate, @@ -5444,14 +4061,14 @@ protected RelDataType getLogicalTargetRowType( && this.config.conformance().isInsertSubsetColumnsAllowed()) { // Target an implicit subset of columns. final SqlNode source = insert.getSource(); - final RelDataType sourceRowType = getNamespaceOrThrow(source).getRowType(); + final RelDataType sourceRowType = scopeMap.getNamespaceOrThrow(source).getRowType(); final RelDataType logicalSourceRowType = getLogicalSourceRowType(sourceRowType, insert); final RelDataType implicitTargetRowType = typeFactory.createStructType( targetRowType.getFieldList() .subList(0, logicalSourceRowType.getFieldCount())); - final SqlValidatorNamespace targetNamespace = getNamespaceOrThrow(insert); + final SqlValidatorNamespace targetNamespace = scopeMap.getNamespaceOrThrow(insert); validateNamespace(targetNamespace, implicitTargetRowType); return implicitTargetRowType; } else { @@ -5594,7 +4211,7 @@ private static SqlNode getNthExpr(SqlNode query, int ordinal, int sourceCount) { final SqlSelect sqlSelect = SqlNonNullableAccessors.getSourceSelect(call); validateSelect(sqlSelect, unknownType); - final SqlValidatorNamespace targetNamespace = getNamespaceOrThrow(call); + final SqlValidatorNamespace targetNamespace = scopeMap.getNamespaceOrThrow(call); validateNamespace(targetNamespace, unknownType); final SqlValidatorTable table = targetNamespace.getTable(); @@ -5602,7 +4219,7 @@ private static SqlNode getNthExpr(SqlNode query, int ordinal, int sourceCount) { } @Override public void validateUpdate(SqlUpdate call) { - final SqlValidatorNamespace targetNamespace = getNamespaceOrThrow(call); + final SqlValidatorNamespace targetNamespace = scopeMap.getNamespaceOrThrow(call); validateNamespace(targetNamespace, unknownType); final RelOptTable relOptTable = SqlValidatorUtil.getRelOptTable(targetNamespace, @@ -5619,7 +4236,7 @@ private static SqlNode getNthExpr(SqlNode query, int ordinal, int sourceCount) { validateSelect(select, targetRowType); final RelDataType sourceRowType = getValidatedNodeType(select); - checkTypeAssignment(scopes.get(select), table, sourceRowType, targetRowType, + checkTypeAssignment(scopeMap.getScope(select), table, sourceRowType, targetRowType, call); checkConstraint(table, call, targetRowType); @@ -5642,7 +4259,7 @@ private static SqlNode getNthExpr(SqlNode query, int ordinal, int sourceCount) { // since validateSelect() would bail. // Let's use the update/insert targetRowType when available. IdentifierNamespace targetNamespace = - (IdentifierNamespace) getNamespaceOrThrow(call.getTargetTable()); + (IdentifierNamespace) scopeMap.getNamespaceOrThrow(call.getTargetTable()); validateNamespace(targetNamespace, unknownType); SqlValidatorTable table = targetNamespace.getTable(); @@ -5982,10 +4599,10 @@ public void setOriginal(SqlNode expr, SqlNode original) { } @Override public void validateLambda(SqlLambda lambdaExpr) { - final SqlLambdaScope scope = (SqlLambdaScope) scopes.get(lambdaExpr); + final SqlLambdaScope scope = (SqlLambdaScope) scopeMap.getScope(lambdaExpr); requireNonNull(scope, "scope"); final LambdaNamespace ns = - getNamespaceOrThrow(lambdaExpr).unwrap(LambdaNamespace.class); + scopeMap.getNamespaceOrThrow(lambdaExpr).unwrap(LambdaNamespace.class); deriveType(scope, lambdaExpr.getExpression()); RelDataType type = deriveTypeImpl(scope, lambdaExpr); @@ -5996,10 +4613,10 @@ public void setOriginal(SqlNode expr, SqlNode original) { @Override public void validateMatchRecognize(SqlCall call) { final SqlMatchRecognize matchRecognize = (SqlMatchRecognize) call; final MatchRecognizeScope scope = - (MatchRecognizeScope) getMatchRecognizeScope(matchRecognize); + (MatchRecognizeScope) scopeMap.getMatchRecognizeScope(matchRecognize); final MatchRecognizeNamespace ns = - getNamespaceOrThrow(call).unwrap(MatchRecognizeNamespace.class); + scopeMap.getNamespaceOrThrow(call).unwrap(MatchRecognizeNamespace.class); assert ns.rowType == null; // rows per match @@ -6043,7 +4660,7 @@ public void setOriginal(SqlNode expr, SqlNode original) { if (allRows) { final SqlValidatorNamespace sqlNs = - getNamespaceOrThrow(matchRecognize.getTableRef()); + scopeMap.getNamespaceOrThrow(matchRecognize.getTableRef()); final RelDataType inputDataType = sqlNs.getRowType(); for (RelDataTypeField fs : inputDataType.getFieldList()) { if (!typeBuilder.nameExists(fs.getName())) { @@ -6135,7 +4752,7 @@ public void setOriginal(SqlNode expr, SqlNode original) { final RelDataType rowType; if (matchRecognize.getMeasureList().isEmpty()) { - rowType = getNamespaceOrThrow(matchRecognize.getTableRef()).getRowType(); + rowType = scopeMap.getNamespaceOrThrow(matchRecognize.getTableRef()).getRowType(); } else { rowType = typeBuilder.build(); } @@ -6251,10 +4868,10 @@ private static String alias(SqlNode item) { } public void validatePivot(SqlPivot pivot) { - final PivotScope scope = (PivotScope) getJoinScope(pivot); + final PivotScope scope = (PivotScope) scopeMap.getJoinScope(pivot); final PivotNamespace ns = - getNamespaceOrThrow(pivot).unwrap(PivotNamespace.class); + scopeMap.getNamespaceOrThrow(pivot).unwrap(PivotNamespace.class); assert ns.rowType == null; // Given @@ -6327,10 +4944,10 @@ public void validatePivot(SqlPivot pivot) { } public void validateUnpivot(SqlUnpivot unpivot) { - final UnpivotScope scope = (UnpivotScope) getJoinScope(unpivot); + final UnpivotScope scope = (UnpivotScope) scopeMap.getJoinScope(unpivot); final UnpivotNamespace ns = - getNamespaceOrThrow(unpivot).unwrap(UnpivotNamespace.class); + scopeMap.getNamespaceOrThrow(unpivot).unwrap(UnpivotNamespace.class); assert ns.rowType == null; // Given @@ -6370,7 +4987,7 @@ public void validateUnpivot(SqlUnpivot unpivot) { // What columns from the input are not referenced by a column in the IN // list? final SqlValidatorNamespace inputNs = - requireNonNull(getNamespace(unpivot.query)); + requireNonNull(scopeMap.getNamespace(unpivot.query)); final Set unusedColumnNames = catalogReader.nameMatcher().createSet(); unusedColumnNames.addAll(inputNs.getRowType().getFieldNames()); @@ -6487,9 +5104,9 @@ private SqlNode navigationInDefine(SqlNode node, String alpha) { // nesting level, throw an assert. final AggFinder a; if (inWindow) { - a = overFinder; + a = validatorAggStuff.getOverFinder(); } else { - a = aggOrOverFinder; + a = validatorAggStuff.getAggOrOverFinder(); } for (SqlNode param : aggCall.getOperandList()) { @@ -6610,21 +5227,6 @@ private SqlNode navigationInDefine(SqlNode node, String alpha) { 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; - } @Override public SqlLiteral resolveLiteral(SqlLiteral literal) { switch (literal.getTypeName()) { @@ -6705,7 +5307,7 @@ public SqlNode extendedExpandGroupBy(SqlNode expr, private @Nullable List getFieldOrigin(SqlNode sqlQuery, int i) { if (sqlQuery instanceof SqlSelect) { SqlSelect sqlSelect = (SqlSelect) sqlQuery; - final SelectScope scope = getRawSelectScopeNonNull(sqlSelect); + final SelectScope scope = scopeMap.getRawSelectScopeNonNull(sqlSelect); final List selectList = requireNonNull(scope.getExpandedSelectList(), () -> "expandedSelectList for " + scope); @@ -6720,7 +5322,7 @@ public SqlNode extendedExpandGroupBy(SqlNode expr, AliasNamespace aliasNs = namespace.unwrap(AliasNamespace.class); SqlNode aliased = requireNonNull(aliasNs.getNode(), () -> "sqlNode for aliasNs " + aliasNs); - namespace = getNamespaceOrThrow(stripAs(aliased)); + namespace = scopeMap.getNamespaceOrThrow(stripAs(aliased)); } final SqlValidatorTable table = namespace.getTable(); @@ -6812,84 +5414,6 @@ private static boolean isSingleVarRequired(SqlKind 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 = requireNonNull(node, "node"); - } - - @Override public @Nullable SqlNode 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 = requireNonNull(node, "node"); - } - - @Override public @Nullable SqlNode 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 = requireNonNull(node, "node"); - } - - @Override public @Nullable SqlNode 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 = requireNonNull(node, "node"); - } - - @Override public @Nullable SqlNode getNode() { - return node; - } - } - /** Visitor that retrieves pattern variables defined. */ private static class PatternVarVisitor implements SqlVisitor { private final MatchRecognizeScope scope; @@ -7146,10 +5670,10 @@ class OrderExpressionExpander extends SqlScopedShuttle { private final SqlNode root; OrderExpressionExpander(SqlSelect select, SqlNode root) { - super(getOrderScope(select)); + super(scopeMap.getOrderScope(select)); this.select = select; this.root = root; - this.aliasList = getNamespaceOrThrow(select).getRowType().getFieldNames(); + this.aliasList = scopeMap.getNamespaceOrThrow(select).getRowType().getFieldNames(); } public SqlNode go() { @@ -7214,7 +5738,7 @@ private SqlNode nthSelectItem(int ordinal, final SqlParserPos pos) { if (id.isSimple() && config.conformance().isSortByAlias()) { String alias = id.getSimple(); - final SqlValidatorNamespace selectNs = getNamespaceOrThrow(select); + final SqlValidatorNamespace selectNs = scopeMap.getNamespaceOrThrow(select); final RelDataType rowType = selectNs.getRowTypeSansSystemColumns(); final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); @@ -7289,7 +5813,7 @@ static class ExtendedExpander extends Expander { final boolean replaceAliases = clause.shouldReplaceAliases(validator.config); if (!replaceAliases) { - final SelectScope scope = validator.getRawSelectScopeNonNull(select); + final SelectScope scope = validator.scopeMap.getRawSelectScopeNonNull(select); SqlNode node = expandCommonColumn(select, id, scope, validator); if (node != id) { return node; @@ -7317,7 +5841,7 @@ static class ExtendedExpander extends Expander { throw validator.newValidationError(id, RESOURCE.columnAmbiguous(name)); } - Iterable allAggList = validator.aggFinder.findAll(ImmutableList.of(root)); + Iterable allAggList = validator.validatorAggStuff.findAllAgg(root); for (SqlCall agg : allAggList) { if (clause == Clause.HAVING && containsIdentifier(agg, id)) { return super.visit(id); @@ -7877,52 +6401,5 @@ public enum Status { VALID } - /** Allows {@link #clauseScopes} to have multiple values per SELECT. */ - private enum Clause { - WHERE, - GROUP_BY, - SELECT, - MEASURE, - ORDER, - CURSOR, - HAVING, - QUALIFY; - /** - * Determines if the extender should replace aliases with expanded values. - * For example: - * - *

{@code
-     * SELECT a + a as twoA
-     * GROUP BY twoA
-     * }
- * - *

turns into - * - *

{@code
-     * SELECT a + a as twoA
-     * GROUP BY a + a
-     * }
- * - *

This is determined both by the clause and the config. - * - * @param config The configuration - * @return Whether we should replace the alias with its expanded value - */ - boolean shouldReplaceAliases(Config config) { - switch (this) { - case GROUP_BY: - return config.conformance().isGroupByAlias(); - - case HAVING: - return config.conformance().isHavingAlias(); - - case QUALIFY: - return true; - - default: - throw Util.unexpected(this); - } - } - } } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorNamespace.java index 77a8dc070e2..c28de03ce63 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorNamespace.java @@ -42,7 +42,7 @@ * {@link IdentifierNamespace} for table names, {@link SelectNamespace} for * SELECT queries, {@link SetopNamespace} for UNION, EXCEPT and INTERSECT, and * so forth. But if you are looking at a SELECT query and call - * {@link SqlValidator#getNamespace(org.apache.calcite.sql.SqlNode)}, you may + * {@link ScopeMap#getNamespace(org.apache.calcite.sql.SqlNode)}, you may * not get a SelectNamespace. Why? Because the validator is allowed to wrap * namespaces in other objects which implement * {@link SqlValidatorNamespace}. Your SelectNamespace will be there somewhere, diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java index 4d59c96d150..cff78c3ee1e 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java @@ -317,7 +317,8 @@ class ResolvedImpl implements Resolved { boolean nullable, SqlValidatorScope scope, Path path, List remainingNames) { if (scope instanceof TableScope) { - scope = scope.getValidator().getSelectScope((SqlSelect) scope.getNode()); + scope = scope.getValidator().getScopeMap() + .getSelectScope((SqlSelect) scope.getNode()); } if (scope instanceof AggregatingSelectScope) { scope = ((AggregatingSelectScope) scope).parent; diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java index ac038e3dae6..8b0f9698f7a 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java @@ -58,6 +58,7 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable; import org.apache.calcite.sql.parser.SqlParserPos; import org.apache.calcite.sql.type.SqlTypeUtil; +import org.apache.calcite.sql.validate.NamespaceBuilder.DmlNamespace; import org.apache.calcite.util.ImmutableBitSet; import org.apache.calcite.util.Litmus; import org.apache.calcite.util.Pair; @@ -124,9 +125,9 @@ private SqlValidatorUtil() {} return getRelOptTable(tableNamespace, requireNonNull(catalogReader, "catalogReader"), datasetName, usedDataset, tableNamespace.extendedFields); - } else if (namespace.isWrapperFor(SqlValidatorImpl.DmlNamespace.class)) { - final SqlValidatorImpl.DmlNamespace dmlNamespace = - namespace.unwrap(SqlValidatorImpl.DmlNamespace.class); + } else if (namespace.isWrapperFor(DmlNamespace.class)) { + final DmlNamespace dmlNamespace = + namespace.unwrap(DmlNamespace.class); final SqlValidatorNamespace resolvedNamespace = dmlNamespace.resolve(); if (resolvedNamespace.isWrapperFor(TableNamespace.class)) { final TableNamespace tableNamespace = resolvedNamespace.unwrap(TableNamespace.class); @@ -382,7 +383,17 @@ public static SqlValidatorWithHints newValidator( SqlValidatorCatalogReader catalogReader, RelDataTypeFactory typeFactory, SqlValidator.Config config) { - return new SqlValidatorImpl(opTab, catalogReader, typeFactory, config); + SqlCluster sqlCluster = new SqlCluster(opTab, catalogReader, typeFactory); + return new SqlValidatorImpl(sqlCluster, config); + } + + /** + * Factory method for {@link SqlValidator}. + */ + public static SqlValidatorWithHints newValidator( + SqlCluster sqlCluster, + SqlValidator.Config config) { + return new SqlValidatorImpl(sqlCluster, config); } /** diff --git a/core/src/main/java/org/apache/calcite/sql/validate/TableScope.java b/core/src/main/java/org/apache/calcite/sql/validate/TableScope.java index 2bba7a22bb1..6de08069a04 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/TableScope.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/TableScope.java @@ -54,7 +54,7 @@ class TableScope extends ListScope { if (this == scope2) { return true; } - SqlValidatorScope s = getValidator().getSelectScope((SqlSelect) node); + SqlValidatorScope s = getValidator().getScopeMap().getSelectScope((SqlSelect) node); return s.isWithin(scope2); } } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/UnpivotScope.java b/core/src/main/java/org/apache/calcite/sql/validate/UnpivotScope.java index f44f14b39de..81bd39809cc 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/UnpivotScope.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/UnpivotScope.java @@ -39,7 +39,7 @@ public UnpivotScope(SqlValidatorScope parent, SqlUnpivot unpivot) { * scope only has one namespace, and it is anonymous. */ public SqlValidatorNamespace getChild() { return requireNonNull( - validator.getNamespace(unpivot.query), + getScopeMap().getNamespace(unpivot.query), () -> "namespace for unpivot.query " + unpivot.query); } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/ValidatorAggStuff.java b/core/src/main/java/org/apache/calcite/sql/validate/ValidatorAggStuff.java new file mode 100644 index 00000000000..a20ab575778 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/validate/ValidatorAggStuff.java @@ -0,0 +1,154 @@ +/* + * 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.sql.SqlCall; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlOperatorTable; +import org.apache.calcite.sql.SqlSelect; + +import com.google.common.collect.ImmutableList; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; + +/** + * WIP for extract agg helper functions. + */ +public class ValidatorAggStuff { + + + private final AggFinder aggFinder; + private final AggFinder aggOrOverFinder; + private final AggFinder aggOrOverOrGroupFinder; + private final AggFinder groupFinder; + private final AggFinder overFinder; + + private final SqlCluster sqlCluster; + private final ScopeMapImpl scopeMap; + + + public ValidatorAggStuff( + SqlCluster sqlCluster, + ScopeMapImpl scopeMap) { + + SqlNameMatcher nameMatcher = sqlCluster.getCatalogReader().nameMatcher(); + SqlOperatorTable opTab = sqlCluster.getOpTab(); + + this.sqlCluster = sqlCluster; + this.scopeMap = scopeMap; + + 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); + } + + public @Nullable SqlNode findAgg(SqlNode node) { + return aggFinder.findAgg(node); + } + + public Iterable findAllAgg(SqlNode sqlNode) { + return aggFinder.findAll(ImmutableList.of(sqlNode)); + } + + public @Nullable SqlNode findOver(SqlNode node) { + return overFinder.findAgg(node); + } + + public boolean isAggregate(SqlSelect select) { + if (getAggregate(select) != null) { + return true; + } + // Also when nested window aggregates are present + for (SqlCall call : overFinder.findAll(SqlNonNullableAccessors.getSelectList(select))) { + 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(sqlCluster.getOpTab(), false, false, false, aggFinder, + sqlCluster.getCatalogReader().nameMatcher()); + return nestedAggFinder.findAgg(node) != null; + } + + protected boolean isOverAggregateWindow(SqlNode node) { + return aggFinder.findAgg(node) != null; + } + + + /** Returns the parse tree node (GROUP BY, HAVING, or an aggregate function + * call) that causes {@code select} to be an aggregate query, or null if it + * is not an aggregate query. + * + *

The node is useful context for error messages, + * but you cannot assume that the node is the only aggregate function. */ + protected @Nullable 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 @Nullable SqlNode getAgg(SqlSelect select) { + final SelectScope selectScope = scopeMap.getRawSelectScope(select); + if (selectScope != null) { + final List selectList = selectScope.getExpandedSelectList(); + if (selectList != null) { + return aggFinder.findAgg(selectList); + } + } + return aggFinder.findAgg(SqlNonNullableAccessors.getSelectList(select)); + } + + public AggFinder getAggOrOverFinder() { + return aggOrOverFinder; + } + + public AggFinder getAggOrOverOrGroupFinder() { + return aggOrOverOrGroupFinder; + } + + public AggFinder getGroupFinder() { + return groupFinder; + } + + public AggFinder getOverFinder() { + return overFinder; + } +} diff --git a/core/src/main/java/org/apache/calcite/sql/validate/WithItemNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/WithItemNamespace.java index 5c0a85756fa..1ec571dc2ad 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/WithItemNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/WithItemNamespace.java @@ -39,7 +39,7 @@ class WithItemNamespace extends AbstractNamespace { @Override protected RelDataType validateImpl(RelDataType targetRowType) { final SqlValidatorNamespace childNs = - validator.getNamespaceOrThrow(getQuery()); + getScopeMap().getNamespaceOrThrow(getQuery()); final RelDataType rowType = childNs.getRowTypeSansSystemColumns(); mustFilterFields = childNs.getMustFilterFields(); SqlNodeList columnList = withItem.columnList; diff --git a/core/src/main/java/org/apache/calcite/sql/validate/WithNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/WithNamespace.java index bec57b715b1..c984b8beb1e 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/WithNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/WithNamespace.java @@ -57,9 +57,9 @@ public class WithNamespace extends AbstractNamespace { validator.validateWithItem((SqlWithItem) withItem); } final SqlValidatorScope scope2 = - validator.getWithScope(Util.last(with.withList)); + getScopeMap().getWithScope(Util.last(with.withList)); final SqlValidatorNamespace bodyNamespace = - requireNonNull(validator.getNamespace(with.body), "namespace"); + requireNonNull(getScopeMap().getNamespace(with.body), "namespace"); validator.validateQuery(with.body, scope2, targetRowType); final RelDataType rowType = validator.getValidatedNodeType(with.body); diff --git a/core/src/main/java/org/apache/calcite/sql/validate/WithRecursiveScope.java b/core/src/main/java/org/apache/calcite/sql/validate/WithRecursiveScope.java index 1fb33e64344..657d930674f 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/WithRecursiveScope.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/WithRecursiveScope.java @@ -53,7 +53,7 @@ class WithRecursiveScope extends ListScope { @Override public @Nullable SqlValidatorNamespace getTableNamespace(List names) { if (names.size() == 1 && names.get(0).equals(withItem.name.getSimple())) { - return validator.getNamespace(withItem); + return getScopeMap().getNamespace(withItem); } return super.getTableNamespace(names); } @@ -62,7 +62,7 @@ class WithRecursiveScope extends ListScope { SqlNameMatcher nameMatcher, Path path, Resolved resolved) { if (names.size() == 1 && names.equals(withItem.name.names)) { - final SqlValidatorNamespace ns = validator.getNamespaceOrThrow(withItem); + final SqlValidatorNamespace ns = getScopeMap().getNamespaceOrThrow(withItem); // create a recursive name space so that we can create a WITH_ITEM_TABLE_REFs final SqlValidatorNamespace recursiveNS = new WithItemRecursiveNamespace(this.validator, withItem, ns.getEnclosingNode()); diff --git a/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java b/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java index 7d622989575..5345cdf43b2 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java @@ -56,7 +56,7 @@ class WithScope extends ListScope { @Override public @Nullable SqlValidatorNamespace getTableNamespace(List names) { if (names.size() == 1 && names.get(0).equals(withItem.name.getSimple())) { - return validator.getNamespace(withItem); + return getScopeMap().getNamespace(withItem); } return super.getTableNamespace(names); } @@ -65,7 +65,7 @@ class WithScope extends ListScope { SqlNameMatcher nameMatcher, Path path, Resolved resolved) { if (names.size() == 1 && names.equals(withItem.name.names)) { - final SqlValidatorNamespace ns = validator.getNamespaceOrThrow(withItem); + final SqlValidatorNamespace ns = getScopeMap().getNamespaceOrThrow(withItem); final Step path2 = path .plus(ns.getRowType(), 0, names.get(0), StructKind.FULLY_QUALIFIED); resolved.found(ns, false, this, path2, ImmutableList.of()); diff --git a/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java b/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java index 84757977171..a2f4ebba714 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java @@ -323,7 +323,7 @@ private static SqlNode castTo(SqlNode node, RelDataType type) { */ protected void updateInferredType(SqlNode node, RelDataType type) { validator.setValidatedNodeType(node, type); - final SqlValidatorNamespace namespace = validator.getNamespace(node); + final SqlValidatorNamespace namespace = validator.getScopeMap().getNamespace(node); if (namespace != null) { namespace.setType(type); } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java index 73de170e5c1..6e6566b4ca9 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java @@ -98,7 +98,7 @@ public TypeCoercionImpl(RelDataTypeFactory typeFactory, SqlValidator validator) switch (kind) { case SELECT: SqlSelect selectNode = (SqlSelect) query; - SqlValidatorScope scope1 = validator.getSelectScope(selectNode); + SqlValidatorScope scope1 = validator.getScopeMap().getSelectScope(selectNode); if (!coerceColumnType(scope1, getSelectList(selectNode), columnIndex, targetType)) { return false; } @@ -118,7 +118,8 @@ public TypeCoercionImpl(RelDataTypeFactory typeFactory, SqlValidator validator) return coerceValues; case WITH: SqlNode body = ((SqlWith) query).body; - return rowTypeCoercion(validator.getOverScope(query), body, columnIndex, targetType); + return rowTypeCoercion( + validator.getScopeMap().getOverScope(query), body, columnIndex, targetType); case UNION: case INTERSECT: case EXCEPT: @@ -563,7 +564,7 @@ protected boolean booleanEquality(SqlCallBinding binding, } else { // Another sub-query. SqlValidatorScope scope1 = node2 instanceof SqlSelect - ? validator.getSelectScope((SqlSelect) node2) + ? validator.getScopeMap().getSelectScope((SqlSelect) node2) : scope; coerced = rowTypeCoercion(scope1, node2, i, desired) || coerced; } diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java index 12222b218b0..300014cf0af 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -163,7 +163,9 @@ import org.apache.calcite.sql.validate.ListScope; import org.apache.calcite.sql.validate.MatchRecognizeScope; import org.apache.calcite.sql.validate.MeasureScope; +import org.apache.calcite.sql.validate.NamespaceBuilder.DmlNamespace; import org.apache.calcite.sql.validate.ParameterScope; +import org.apache.calcite.sql.validate.ScopeMap; import org.apache.calcite.sql.validate.SelectScope; import org.apache.calcite.sql.validate.SqlLambdaScope; import org.apache.calcite.sql.validate.SqlMonotonicity; @@ -386,6 +388,10 @@ private SqlValidator validator() { return requireNonNull(validator, "validator"); } + private ScopeMap sqlQueryScopes() { + return requireNonNull(validator().getScopeMap(), "sqlQueryScopes"); + } + private T getNamespace(SqlNode node) { return requireNonNull(getNamespaceOrNull(node), () -> "Namespace is not found for " + node); @@ -393,7 +399,7 @@ private T getNamespace(SqlNode node) { @SuppressWarnings("unchecked") private @Nullable T getNamespaceOrNull(SqlNode node) { - return (@Nullable T) validator().getNamespace(node); + return (@Nullable T) sqlQueryScopes().getNamespace(node); } /** Returns the RelOptCluster in use. */ @@ -727,9 +733,9 @@ private static RelCollation requiredCollation(RelNode r) { * Converts a SELECT statement's parse tree into a relational expression. */ public RelNode convertSelect(SqlSelect select, boolean top) { - final SqlValidatorScope selectScope = validator().getWhereScope(select); + final SqlValidatorScope selectScope = sqlQueryScopes().getWhereScope(select); final MeasureScope measureScope = - (MeasureScope) validator().getMeasureScope(select); + (MeasureScope) sqlQueryScopes().getMeasureScope(select); final Blackboard bb = createBlackboard(selectScope, null, top); final Blackboard measureBb = new MeasureBlackboard(measureScope, bb); convertSelectImpl(bb, measureBb, select); @@ -782,7 +788,7 @@ protected void convertSelectImpl( // if there are other nodes, which will cause the view to be in the // sub-query. if (!bb.top - || validator().isAggregate(select) + || validator().getValidatorAggStuff().isAggregate(select) || select.isDistinct() || select.hasOrderBy() || select.getFetch() != null @@ -806,7 +812,7 @@ protected void convertSelectImpl( final RelCollation collation = cluster.traitSet().canonize(RelCollations.of(collationList)); - if (validator().isAggregate(select)) { + if (validator().getValidatorAggStuff().isAggregate(select)) { convertAgg( bb, select, @@ -1362,8 +1368,8 @@ private void substituteSubQuery(Blackboard bb, SubQuery subQuery) { query = call.operand(0); final SqlValidatorScope seekScope = (query instanceof SqlSelect) - ? validator().getSelectScope((SqlSelect) query) - : validator().getEmptyScope(); + ? sqlQueryScopes().getSelectScope((SqlSelect) query) + : validator().createEmptyScope(); final Blackboard seekBb = createBlackboard(seekScope, null, false); final RelNode seekRel = convertQueryOrInList(seekBb, query, null); requireNonNull(seekRel, () -> "seekRel is null for query " + query); @@ -1444,8 +1450,8 @@ private void substituteSubQueryOfSetSemanticsInputTable( query = call.operand(0); final SqlValidatorScope innerTableScope = (query instanceof SqlSelect) - ? validator().getSelectScope((SqlSelect) query) - : validator().getEmptyScope(); + ? sqlQueryScopes().getSelectScope((SqlSelect) query) + : validator().createEmptyScope(); final Blackboard setSemanticsTableBb = createBlackboard(innerTableScope, null, false); final RelNode inputOfSetSemanticsTable = @@ -1846,8 +1852,8 @@ private RelOptUtil.Exists convertExists( @Nullable RelDataType targetDataType) { final SqlValidatorScope seekScope = (seek instanceof SqlSelect) - ? validator().getSelectScope((SqlSelect) seek) - : validator().getEmptyScope(); + ? sqlQueryScopes().getSelectScope((SqlSelect) seek) + : validator().createEmptyScope(); final Blackboard seekBb = createBlackboard(seekScope, null, false); RelNode seekRel = convertQueryOrInList(seekBb, seek, targetDataType); requireNonNull(seekRel, () -> "seekRel is null for query " + seek); @@ -2220,7 +2226,7 @@ public RexNode convertExpression( */ private RexNode convertLambda(Blackboard bb, SqlNode node) { final SqlLambda call = (SqlLambda) node; - final SqlLambdaScope scope = (SqlLambdaScope) validator().getLambdaScope(call); + final SqlLambdaScope scope = (SqlLambdaScope) sqlQueryScopes().getLambdaScope(call); final Map nameToNodeMap = new HashMap<>(); final List parameters = new ArrayList<>(scope.getParameterTypes().size()); @@ -2553,7 +2559,7 @@ private void convertUnnest(Blackboard bb, SqlCall call, @Nullable List f protected void convertMatchRecognize(Blackboard bb, SqlMatchRecognize matchRecognize) { final SqlValidatorNamespace ns = getNamespace(matchRecognize); - final SqlValidatorScope scope = validator().getMatchRecognizeScope(matchRecognize); + final SqlValidatorScope scope = sqlQueryScopes().getMatchRecognizeScope(matchRecognize); final Blackboard matchBb = createBlackboard(scope, null, false); final RelDataType rowType = ns.getRowType(); @@ -2712,7 +2718,7 @@ protected void convertMatchRecognize(Blackboard bb, } protected void convertPivot(Blackboard bb, SqlPivot pivot) { - final SqlValidatorScope scope = validator().getJoinScope(pivot); + final SqlValidatorScope scope = sqlQueryScopes().getJoinScope(pivot); final Blackboard pivotBb = createBlackboard(scope, null, false); // Convert input @@ -2794,7 +2800,7 @@ protected void convertPivot(Blackboard bb, SqlPivot pivot) { } protected void convertUnpivot(Blackboard bb, SqlUnpivot unpivot) { - final SqlValidatorScope scope = validator().getJoinScope(unpivot); + final SqlValidatorScope scope = sqlQueryScopes().getJoinScope(unpivot); final Blackboard unpivotBb = createBlackboard(scope, null, false); // Convert input @@ -3268,17 +3274,16 @@ protected List getSystemFields() { } private void convertJoin(Blackboard bb, SqlJoin join) { - SqlValidator validator = validator(); - final SqlValidatorScope scope = validator.getJoinScope(join); + final SqlValidatorScope scope = sqlQueryScopes().getJoinScope(join); final Blackboard fromBlackboard = createBlackboard(scope, null, false); SqlNode left = join.getLeft(); SqlNode right = join.getRight(); JoinType joinType = join.getJoinType(); - final SqlValidatorScope leftScope = validator.getJoinScope(left); + final SqlValidatorScope leftScope = sqlQueryScopes().getJoinScope(left); final Blackboard leftBlackboard = createBlackboard(leftScope, null, false); - final SqlValidatorScope rightScope = validator.getJoinScope(right); + final SqlValidatorScope rightScope = sqlQueryScopes().getJoinScope(right); final Blackboard rightBlackboard = createBlackboard(rightScope, null, false); convertFrom(leftBlackboard, left); @@ -3492,7 +3497,7 @@ protected void convertAgg(Blackboard bb, SqlSelect select, final AggConverter aggConverter = AggConverter.create(bb, - (AggregatingSelectScope) validator().getSelectScope(select)); + (AggregatingSelectScope) sqlQueryScopes().getSelectScope(select)); createAggImpl(bb, aggConverter, selectList, groupList, having, orderExprList); } @@ -3818,7 +3823,7 @@ protected RelFieldCollation convertOrderItem( // Scan the select list and order exprs for an identical expression. final SelectScope selectScope = - requireNonNull(validator.getRawSelectScope(select), + requireNonNull(sqlQueryScopes().getRawSelectScope(select), () -> "getRawSelectScope is not found for " + select); int ordinal = -1; List expandedSelectList = selectScope.getExpandedSelectList(); @@ -3913,7 +3918,7 @@ protected RelRoot convertQueryRecursive(SqlNode query, boolean top, private RelNode createUnion(SqlCall call, RelNode left, RelNode right) { - SqlValidatorNamespace nameSpace = this.validator().getNamespace(call); + SqlValidatorNamespace nameSpace = sqlQueryScopes().getNamespace(call); boolean all = all(call); if (nameSpace != null) { SqlNode enclosingNode = nameSpace.getEnclosingNode(); @@ -4108,8 +4113,8 @@ public RelNode toRel(final RelOptTable table, final List hints) { protected RelOptTable getTargetTable(SqlNode call) { final SqlValidatorNamespace targetNs = getNamespace(call); SqlValidatorNamespace namespace; - if (targetNs.isWrapperFor(SqlValidatorImpl.DmlNamespace.class)) { - namespace = targetNs.unwrap(SqlValidatorImpl.DmlNamespace.class); + if (targetNs.isWrapperFor(DmlNamespace.class)) { + namespace = targetNs.unwrap(DmlNamespace.class); } else { namespace = targetNs.resolve(); } @@ -4219,7 +4224,7 @@ private Blackboard createInsertBlackboard(RelOptTable targetTable, rexBuilder.makeFieldAccess(sourceRef, j++)); } } - return createBlackboard(validator().getEmptyScope(), nameToNodeMap, false); + return createBlackboard(validator().createEmptyScope(), nameToNodeMap, false); } private static InitializerExpressionFactory getInitializerFactory( @@ -4341,7 +4346,7 @@ private RelNode convertUpdate(SqlUpdate call) { final SqlSelect sourceSelect = requireNonNull(call.getSourceSelect(), () -> "sourceSelect for " + call); - final SqlValidatorScope scope = validator().getWhereScope(sourceSelect); + final SqlValidatorScope scope = sqlQueryScopes().getWhereScope(sourceSelect); Blackboard bb = createBlackboard(scope, null, false); replaceSubQueries(bb, call, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); @@ -4936,7 +4941,7 @@ public RelNode convertValues( SqlCall values, @Nullable RelDataType targetRowType) { final SqlValidatorScope scope = - requireNonNull(validator().getOverScope(values)); + requireNonNull(sqlQueryScopes().getOverScope(values)); final Blackboard bb = createBlackboard(scope, null, false); convertValuesImpl(bb, values, targetRowType); return bb.root(); diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorFeatureTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorFeatureTest.java index 24cdf1935d1..9b7a9a083b7 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorFeatureTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorFeatureTest.java @@ -22,8 +22,11 @@ import org.apache.calcite.runtime.Feature; import org.apache.calcite.sql.SqlOperatorTable; import org.apache.calcite.sql.parser.SqlParserPos; +import org.apache.calcite.sql.validate.SqlCluster; +import org.apache.calcite.sql.validate.SqlValidator; import org.apache.calcite.sql.validate.SqlValidatorCatalogReader; import org.apache.calcite.sql.validate.SqlValidatorImpl; +import org.apache.calcite.sql.validate.SqlValidatorUtil; import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.jupiter.api.Test; @@ -43,7 +46,7 @@ class SqlValidatorFeatureTest extends SqlValidatorTestCase { @Override public SqlValidatorFixture fixture() { return super.fixture() - .withFactory(f -> f.withValidator(FeatureValidator::new)); + .withFactory(f -> f.withValidator(this::createValidator)); } @Test void testDistinct() { @@ -106,35 +109,29 @@ private void checkFeature(String sql, Feature feature) { } } - //~ Inner Classes ---------------------------------------------------------- - - /** Extension to {@link SqlValidatorImpl} that validates features. */ - public class FeatureValidator extends SqlValidatorImpl { - protected FeatureValidator( - SqlOperatorTable opTab, - SqlValidatorCatalogReader catalogReader, - RelDataTypeFactory typeFactory, - Config config) { - super(opTab, catalogReader, typeFactory, config); - } - - protected void validateFeature( - Feature feature, - SqlParserPos pos) { - requireNonNull(pos, "pos"); - if (feature.equals(disabledFeature)) { - CalciteException ex = - new CalciteException( - FEATURE_DISABLED, - null); - throw new CalciteContextException( - "location", - ex, - pos.getLineNum(), - pos.getColumnNum(), - pos.getEndLineNum(), - pos.getEndColumnNum()); + private SqlValidator createValidator(SqlOperatorTable opTab, + SqlValidatorCatalogReader catalogReader, + RelDataTypeFactory typeFactory, + SqlValidator.Config config) { + SqlCluster sqlCluster = new SqlCluster(opTab, catalogReader, typeFactory) { + /** Extension to {@link SqlValidatorImpl} that validates features. */ + @Override public void validateFeature(Feature feature, SqlParserPos pos) { + requireNonNull(pos, "pos"); + if (feature.equals(disabledFeature)) { + CalciteException ex = + new CalciteException( + FEATURE_DISABLED, + null); + throw new CalciteContextException( + "location", + ex, + pos.getLineNum(), + pos.getColumnNum(), + pos.getEndLineNum(), + pos.getEndColumnNum()); + } } - } + }; + return SqlValidatorUtil.newValidator(sqlCluster, config); } } diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java index c27e0124ab2..7e1d85ac94a 100644 --- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java +++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java @@ -42,6 +42,7 @@ import org.apache.calcite.sql.type.SqlTypeUtil; import org.apache.calcite.sql.util.SqlShuttle; import org.apache.calcite.sql.validate.SqlAbstractConformance; +import org.apache.calcite.sql.validate.SqlCluster; import org.apache.calcite.sql.validate.SqlConformance; import org.apache.calcite.sql.validate.SqlConformanceEnum; import org.apache.calcite.sql.validate.SqlDelegatingConformance; @@ -12440,7 +12441,7 @@ private static class UnexpandedToDeptValidator extends SqlValidatorImpl { UnexpandedToDeptValidator(SqlOperatorTable opTab, SqlValidatorCatalogReader catalogReader, RelDataTypeFactory typeFactory, Config config) { - super(opTab, catalogReader, typeFactory, config); + super(new SqlCluster(opTab, catalogReader, typeFactory), config); } @Override public SqlNode validate(SqlNode topNode) { diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index 41619a9d920..7edc42b28b0 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -15873,7 +15873,7 @@ void testCastTruncates(CastType castType, SqlOperatorFixture f) { final SqlOperatorFixture f = fixture(); final SqlValidatorImpl validator = (SqlValidatorImpl) f.getFactory().createValidator(); - final SqlValidatorScope scope = validator.getEmptyScope(); + final SqlValidatorScope scope = validator.createEmptyScope(); final RelDataTypeFactory typeFactory = validator.getTypeFactory(); final Builder builder = new Builder(typeFactory); builder.add0(SqlTypeName.BOOLEAN, true, false); diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlValidatorFixture.java b/testkit/src/main/java/org/apache/calcite/test/SqlValidatorFixture.java index c5515872930..3ffde008ce3 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlValidatorFixture.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlValidatorFixture.java @@ -273,7 +273,7 @@ public SqlValidatorFixture assertMonotonicity( (sap, validator, n) -> { final RelDataType rowType = validator.getValidatedNodeType(n); final SqlValidatorNamespace selectNamespace = - validator.getNamespace(n); + validator.getScopeMap().getNamespace(n); final String field0 = rowType.getFieldList().get(0).getName(); final SqlMonotonicity monotonicity = selectNamespace.getMonotonicity(field0); @@ -406,7 +406,7 @@ public SqlValidatorFixture rewritesTo(String expected) { public SqlValidatorFixture isAggregate(Matcher matcher) { tester.validateAndThen(factory, toSql(false), (sap, validator, validatedNode) -> - assertThat(validator.isAggregate((SqlSelect) validatedNode), + assertThat(validator.getValidatorAggStuff().isAggregate((SqlSelect) validatedNode), matcher)); return this; }