http://git-wip-us.apache.org/repos/asf/flink/blob/d5770fe8/flink-libraries/flink-table/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java ---------------------------------------------------------------------- diff --git a/flink-libraries/flink-table/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/flink-libraries/flink-table/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java deleted file mode 100644 index b7f9ce7..0000000 --- a/flink-libraries/flink-table/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ /dev/null @@ -1,5356 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.calcite.sql2rel; - -/* - * THIS FILE HAS BEEN COPIED FROM THE APACHE CALCITE PROJECT UNTIL CALCITE-1761 IS FIXED. - */ - -import org.apache.calcite.avatica.util.Spaces; -import org.apache.calcite.linq4j.Ord; -import org.apache.calcite.plan.Convention; -import org.apache.calcite.plan.RelOptCluster; -import org.apache.calcite.plan.RelOptPlanner; -import org.apache.calcite.plan.RelOptSamplingParameters; -import org.apache.calcite.plan.RelOptTable; -import org.apache.calcite.plan.RelOptUtil; -import org.apache.calcite.plan.RelTraitSet; -import org.apache.calcite.prepare.Prepare; -import org.apache.calcite.prepare.RelOptTableImpl; -import org.apache.calcite.rel.RelCollation; -import org.apache.calcite.rel.RelCollationTraitDef; -import org.apache.calcite.rel.RelCollations; -import org.apache.calcite.rel.RelFieldCollation; -import org.apache.calcite.rel.RelNode; -import org.apache.calcite.rel.RelRoot; -import org.apache.calcite.rel.SingleRel; -import org.apache.calcite.rel.core.Aggregate; -import org.apache.calcite.rel.core.AggregateCall; -import org.apache.calcite.rel.core.Collect; -import org.apache.calcite.rel.core.CorrelationId; -import org.apache.calcite.rel.core.Filter; -import org.apache.calcite.rel.core.Join; -import org.apache.calcite.rel.core.JoinInfo; -import org.apache.calcite.rel.core.JoinRelType; -import org.apache.calcite.rel.core.Project; -import org.apache.calcite.rel.core.RelFactories; -import org.apache.calcite.rel.core.Sample; -import org.apache.calcite.rel.core.Sort; -import org.apache.calcite.rel.core.Uncollect; -import org.apache.calcite.rel.logical.LogicalAggregate; -import org.apache.calcite.rel.logical.LogicalCorrelate; -import org.apache.calcite.rel.logical.LogicalFilter; -import org.apache.calcite.rel.logical.LogicalIntersect; -import org.apache.calcite.rel.logical.LogicalJoin; -import org.apache.calcite.rel.logical.LogicalMinus; -import org.apache.calcite.rel.logical.LogicalProject; -import org.apache.calcite.rel.logical.LogicalSort; -import org.apache.calcite.rel.logical.LogicalTableFunctionScan; -import org.apache.calcite.rel.logical.LogicalTableModify; -import org.apache.calcite.rel.logical.LogicalTableScan; -import org.apache.calcite.rel.logical.LogicalUnion; -import org.apache.calcite.rel.logical.LogicalValues; -import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider; -import org.apache.calcite.rel.metadata.RelColumnMapping; -import org.apache.calcite.rel.metadata.RelMetadataQuery; -import org.apache.calcite.rel.stream.Delta; -import org.apache.calcite.rel.stream.LogicalDelta; -import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.rel.type.RelDataTypeFactory; -import org.apache.calcite.rel.type.RelDataTypeField; -import org.apache.calcite.rex.RexBuilder; -import org.apache.calcite.rex.RexCall; -import org.apache.calcite.rex.RexCallBinding; -import org.apache.calcite.rex.RexCorrelVariable; -import org.apache.calcite.rex.RexDynamicParam; -import org.apache.calcite.rex.RexFieldAccess; -import org.apache.calcite.rex.RexFieldCollation; -import org.apache.calcite.rex.RexInputRef; -import org.apache.calcite.rex.RexLiteral; -import org.apache.calcite.rex.RexNode; -import org.apache.calcite.rex.RexRangeRef; -import org.apache.calcite.rex.RexShuttle; -import org.apache.calcite.rex.RexSubQuery; -import org.apache.calcite.rex.RexUtil; -import org.apache.calcite.rex.RexWindowBound; -import org.apache.calcite.schema.ModifiableTable; -import org.apache.calcite.schema.ModifiableView; -import org.apache.calcite.schema.Table; -import org.apache.calcite.schema.TranslatableTable; -import org.apache.calcite.schema.Wrapper; -import org.apache.calcite.sql.JoinConditionType; -import org.apache.calcite.sql.JoinType; -import org.apache.calcite.sql.SemiJoinType; -import org.apache.calcite.sql.SqlAggFunction; -import org.apache.calcite.sql.SqlBasicCall; -import org.apache.calcite.sql.SqlCall; -import org.apache.calcite.sql.SqlCallBinding; -import org.apache.calcite.sql.SqlDataTypeSpec; -import org.apache.calcite.sql.SqlDelete; -import org.apache.calcite.sql.SqlDynamicParam; -import org.apache.calcite.sql.SqlExplainFormat; -import org.apache.calcite.sql.SqlExplainLevel; -import org.apache.calcite.sql.SqlFunction; -import org.apache.calcite.sql.SqlIdentifier; -import org.apache.calcite.sql.SqlInsert; -import org.apache.calcite.sql.SqlIntervalQualifier; -import org.apache.calcite.sql.SqlJoin; -import org.apache.calcite.sql.SqlKind; -import org.apache.calcite.sql.SqlLiteral; -import org.apache.calcite.sql.SqlMatchRecognize; -import org.apache.calcite.sql.SqlMerge; -import org.apache.calcite.sql.SqlNode; -import org.apache.calcite.sql.SqlNodeList; -import org.apache.calcite.sql.SqlNumericLiteral; -import org.apache.calcite.sql.SqlOperator; -import org.apache.calcite.sql.SqlOperatorTable; -import org.apache.calcite.sql.SqlOrderBy; -import org.apache.calcite.sql.SqlSampleSpec; -import org.apache.calcite.sql.SqlSelect; -import org.apache.calcite.sql.SqlSelectKeyword; -import org.apache.calcite.sql.SqlSetOperator; -import org.apache.calcite.sql.SqlUnnestOperator; -import org.apache.calcite.sql.SqlUpdate; -import org.apache.calcite.sql.SqlUtil; -import org.apache.calcite.sql.SqlValuesOperator; -import org.apache.calcite.sql.SqlWindow; -import org.apache.calcite.sql.SqlWith; -import org.apache.calcite.sql.SqlWithItem; -import org.apache.calcite.sql.fun.SqlCountAggFunction; -import org.apache.calcite.sql.fun.SqlInOperator; -import org.apache.calcite.sql.fun.SqlRowOperator; -import org.apache.calcite.sql.fun.SqlStdOperatorTable; -import org.apache.calcite.sql.parser.SqlParserPos; -import org.apache.calcite.sql.type.SqlReturnTypeInference; -import org.apache.calcite.sql.type.SqlTypeName; -import org.apache.calcite.sql.type.SqlTypeUtil; -import org.apache.calcite.sql.type.TableFunctionReturnTypeInference; -import org.apache.calcite.sql.util.SqlBasicVisitor; -import org.apache.calcite.sql.util.SqlVisitor; -import org.apache.calcite.sql.validate.AggregatingSelectScope; -import org.apache.calcite.sql.validate.CollectNamespace; -import org.apache.calcite.sql.validate.DelegatingScope; -import org.apache.calcite.sql.validate.ListScope; -import org.apache.calcite.sql.validate.ParameterScope; -import org.apache.calcite.sql.validate.SelectScope; -import org.apache.calcite.sql.validate.SqlMonotonicity; -import org.apache.calcite.sql.validate.SqlNameMatcher; -import org.apache.calcite.sql.validate.SqlQualified; -import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction; -import org.apache.calcite.sql.validate.SqlUserDefinedTableMacro; -import org.apache.calcite.sql.validate.SqlValidator; -import org.apache.calcite.sql.validate.SqlValidatorImpl; -import org.apache.calcite.sql.validate.SqlValidatorNamespace; -import org.apache.calcite.sql.validate.SqlValidatorScope; -import org.apache.calcite.sql.validate.SqlValidatorTable; -import org.apache.calcite.sql.validate.SqlValidatorUtil; -import org.apache.calcite.tools.RelBuilder; -import org.apache.calcite.util.ImmutableBitSet; -import org.apache.calcite.util.ImmutableIntList; -import org.apache.calcite.util.Litmus; -import org.apache.calcite.util.NlsString; -import org.apache.calcite.util.NumberUtil; -import org.apache.calcite.util.Pair; -import org.apache.calcite.util.Util; -import org.apache.calcite.util.trace.CalciteTrace; - -import com.google.common.base.Function; -import org.apache.flink.util.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import org.slf4j.Logger; - -import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.util.AbstractList; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -import static org.apache.calcite.sql.SqlUtil.stripAs; -import static org.apache.calcite.util.Static.RESOURCE; - -/** - * Converts a SQL parse tree (consisting of - * {@link org.apache.calcite.sql.SqlNode} objects) into a relational algebra - * expression (consisting of {@link org.apache.calcite.rel.RelNode} objects). - * - * <p>The public entry points are: {@link #convertQuery}, - * {@link #convertExpression(SqlNode)}. - */ -public class SqlToRelConverter { - //~ Static fields/initializers --------------------------------------------- - - protected static final Logger SQL2REL_LOGGER = - CalciteTrace.getSqlToRelTracer(); - - private static final BigDecimal TWO = BigDecimal.valueOf(2L); - - /** Size of the smallest IN list that will be converted to a semijoin to a - * static table. */ - public static final int DEFAULT_IN_SUB_QUERY_THRESHOLD = 20; - - @Deprecated // to be removed before 2.0 - public static final int DEFAULT_IN_SUBQUERY_THRESHOLD = - DEFAULT_IN_SUB_QUERY_THRESHOLD; - - //~ Instance fields -------------------------------------------------------- - - protected final SqlValidator validator; - protected final RexBuilder rexBuilder; - protected final Prepare.CatalogReader catalogReader; - protected final RelOptCluster cluster; - private SubQueryConverter subQueryConverter; - protected final List<RelNode> leaves = new ArrayList<>(); - private final List<SqlDynamicParam> dynamicParamSqlNodes = new ArrayList<>(); - private final SqlOperatorTable opTab; - protected final RelDataTypeFactory typeFactory; - private final SqlNodeToRexConverter exprConverter; - private int explainParamCount; - public final SqlToRelConverter.Config config; - - /** - * Fields used in name resolution for correlated sub-queries. - */ - private final Map<CorrelationId, DeferredLookup> mapCorrelToDeferred = - new HashMap<>(); - - /** - * Stack of names of datasets requested by the <code> - * TABLE(SAMPLE(<datasetName>, <query>))</code> construct. - */ - private final Deque<String> datasetStack = new ArrayDeque<>(); - - /** - * Mapping of non-correlated sub-queries that have been converted to their - * equivalent constants. Used to avoid re-evaluating the sub-query if it's - * already been evaluated. - */ - private final Map<SqlNode, RexNode> mapConvertedNonCorrSubqs = - new HashMap<>(); - - public final RelOptTable.ViewExpander viewExpander; - - //~ Constructors ----------------------------------------------------------- - /** - * Creates a converter. - * - * @param viewExpander Preparing statement - * @param validator Validator - * @param catalogReader Schema - * @param planner Planner - * @param rexBuilder Rex builder - * @param convertletTable Expression converter - */ - @Deprecated // to be removed before 2.0 - public SqlToRelConverter( - RelOptTable.ViewExpander viewExpander, - SqlValidator validator, - Prepare.CatalogReader catalogReader, - RelOptPlanner planner, - RexBuilder rexBuilder, - SqlRexConvertletTable convertletTable) { - this(viewExpander, validator, catalogReader, - RelOptCluster.create(planner, rexBuilder), convertletTable, - Config.DEFAULT); - } - - @Deprecated // to be removed before 2.0 - public SqlToRelConverter( - RelOptTable.ViewExpander viewExpander, - SqlValidator validator, - Prepare.CatalogReader catalogReader, - RelOptCluster cluster, - SqlRexConvertletTable convertletTable) { - this(viewExpander, validator, catalogReader, cluster, convertletTable, - Config.DEFAULT); - } - - /* Creates a converter. */ - public SqlToRelConverter( - RelOptTable.ViewExpander viewExpander, - SqlValidator validator, - Prepare.CatalogReader catalogReader, - RelOptCluster cluster, - SqlRexConvertletTable convertletTable, - Config config) { - this.viewExpander = viewExpander; - this.opTab = - (validator - == null) ? SqlStdOperatorTable.instance() - : validator.getOperatorTable(); - this.validator = validator; - this.catalogReader = catalogReader; - this.subQueryConverter = new NoOpSubQueryConverter(); - this.rexBuilder = cluster.getRexBuilder(); - this.typeFactory = rexBuilder.getTypeFactory(); - this.cluster = Preconditions.checkNotNull(cluster); - this.exprConverter = new SqlNodeToRexConverterImpl(convertletTable); - this.explainParamCount = 0; - this.config = new ConfigBuilder().withConfig(config).build(); - } - - //~ Methods ---------------------------------------------------------------- - - /** - * @return the RelOptCluster in use. - */ - public RelOptCluster getCluster() { - return cluster; - } - - /** - * Returns the row-expression builder. - */ - public RexBuilder getRexBuilder() { - return rexBuilder; - } - - /** - * Returns the number of dynamic parameters encountered during translation; - * this must only be called after {@link #convertQuery}. - * - * @return number of dynamic parameters - */ - public int getDynamicParamCount() { - return dynamicParamSqlNodes.size(); - } - - /** - * Returns the type inferred for a dynamic parameter. - * - * @param index 0-based index of dynamic parameter - * @return inferred type, never null - */ - public RelDataType getDynamicParamType(int index) { - SqlNode sqlNode = dynamicParamSqlNodes.get(index); - if (sqlNode == null) { - throw Util.needToImplement("dynamic param type inference"); - } - return validator.getValidatedNodeType(sqlNode); - } - - /** - * Returns the current count of the number of dynamic parameters in an - * EXPLAIN PLAN statement. - * - * @param increment if true, increment the count - * @return the current count before the optional increment - */ - public int getDynamicParamCountInExplain(boolean increment) { - int retVal = explainParamCount; - if (increment) { - ++explainParamCount; - } - return retVal; - } - - /** - * @return mapping of non-correlated sub-queries that have been converted to - * the constants that they evaluate to - */ - public Map<SqlNode, RexNode> getMapConvertedNonCorrSubqs() { - return mapConvertedNonCorrSubqs; - } - - /** - * Adds to the current map of non-correlated converted sub-queries the - * elements from another map that contains non-correlated sub-queries that - * have been converted by another SqlToRelConverter. - * - * @param alreadyConvertedNonCorrSubqs the other map - */ - public void addConvertedNonCorrSubqs( - Map<SqlNode, RexNode> alreadyConvertedNonCorrSubqs) { - mapConvertedNonCorrSubqs.putAll(alreadyConvertedNonCorrSubqs); - } - - /** - * Sets a new SubQueryConverter. To have any effect, this must be called - * before any convert method. - * - * @param converter new SubQueryConverter - */ - public void setSubQueryConverter(SubQueryConverter converter) { - subQueryConverter = converter; - } - - /** - * Sets the number of dynamic parameters in the current EXPLAIN PLAN - * statement. - * - * @param explainParamCount number of dynamic parameters in the statement - */ - public void setDynamicParamCountInExplain(int explainParamCount) { - assert config.isExplain(); - this.explainParamCount = explainParamCount; - } - - private void checkConvertedType(SqlNode query, RelNode result) { - if (query.isA(SqlKind.DML)) { - return; - } - // Verify that conversion from SQL to relational algebra did - // not perturb any type information. (We can't do this if the - // SQL statement is something like an INSERT which has no - // validator type information associated with its result, - // hence the namespace check above.) - final List<RelDataTypeField> validatedFields = - validator.getValidatedNodeType(query).getFieldList(); - final RelDataType validatedRowType = - validator.getTypeFactory().createStructType( - Pair.right(validatedFields), - SqlValidatorUtil.uniquify(Pair.left(validatedFields), - catalogReader.nameMatcher().isCaseSensitive())); - - final List<RelDataTypeField> convertedFields = - result.getRowType().getFieldList().subList(0, validatedFields.size()); - final RelDataType convertedRowType = - validator.getTypeFactory().createStructType(convertedFields); - - if (!RelOptUtil.equal("validated row type", validatedRowType, - "converted row type", convertedRowType, Litmus.IGNORE)) { - throw new AssertionError("Conversion to relational algebra failed to " - + "preserve datatypes:\n" - + "validated type:\n" - + validatedRowType.getFullTypeString() - + "\nconverted type:\n" - + convertedRowType.getFullTypeString() - + "\nrel:\n" - + RelOptUtil.toString(result)); - } - } - - public RelNode flattenTypes( - RelNode rootRel, - boolean restructure) { - RelStructuredTypeFlattener typeFlattener = - new RelStructuredTypeFlattener(rexBuilder, createToRelContext(), restructure); - return typeFlattener.rewrite(rootRel); - } - - /** - * If sub-query is correlated and decorrelation is enabled, performs - * decorrelation. - * - * @param query Query - * @param rootRel Root relational expression - * @return New root relational expression after decorrelation - */ - public RelNode decorrelate(SqlNode query, RelNode rootRel) { - if (!enableDecorrelation()) { - return rootRel; - } - final RelNode result = decorrelateQuery(rootRel); - if (result != rootRel) { - checkConvertedType(query, result); - } - return result; - } - - /** - * Walks over a tree of relational expressions, replacing each - * {@link RelNode} with a 'slimmed down' relational expression that projects - * only the fields required by its consumer. - * - * <p>This may make things easier for the optimizer, by removing crud that - * would expand the search space, but is difficult for the optimizer itself - * to do it, because optimizer rules must preserve the number and type of - * fields. Hence, this transform that operates on the entire tree, similar - * to the {@link RelStructuredTypeFlattener type-flattening transform}. - * - * <p>Currently this functionality is disabled in farrago/luciddb; the - * default implementation of this method does nothing. - * - * @param ordered Whether the relational expression must produce results in - * a particular order (typically because it has an ORDER BY at top level) - * @param rootRel Relational expression that is at the root of the tree - * @return Trimmed relational expression - */ - public RelNode trimUnusedFields(boolean ordered, RelNode rootRel) { - // Trim fields that are not used by their consumer. - if (isTrimUnusedFields()) { - final RelFieldTrimmer trimmer = newFieldTrimmer(); - final List<RelCollation> collations = - rootRel.getTraitSet().getTraits(RelCollationTraitDef.INSTANCE); - rootRel = trimmer.trim(rootRel); - if (!ordered - && collations != null - && !collations.isEmpty() - && !collations.equals(ImmutableList.of(RelCollations.EMPTY))) { - final RelTraitSet traitSet = rootRel.getTraitSet() - .replace(RelCollationTraitDef.INSTANCE, collations); - rootRel = rootRel.copy(traitSet, rootRel.getInputs()); - } - if (SQL2REL_LOGGER.isDebugEnabled()) { - SQL2REL_LOGGER.debug( - RelOptUtil.dumpPlan("Plan after trimming unused fields", rootRel, - SqlExplainFormat.TEXT, SqlExplainLevel.EXPPLAN_ATTRIBUTES)); - } - } - return rootRel; - } - - /** - * Creates a RelFieldTrimmer. - * - * @return Field trimmer - */ - protected RelFieldTrimmer newFieldTrimmer() { - final RelBuilder relBuilder = - RelFactories.LOGICAL_BUILDER.create(cluster, null); - return new RelFieldTrimmer(validator, relBuilder); - } - - /** - * Converts an unvalidated query's parse tree into a relational expression. - * - * @param query Query to convert - * @param needsValidation Whether to validate the query before converting; - * <code>false</code> if the query has already been - * validated. - * @param top Whether the query is top-level, say if its result - * will become a JDBC result set; <code>false</code> if - * the query will be part of a view. - */ - public RelRoot convertQuery( - SqlNode query, - final boolean needsValidation, - final boolean top) { - if (needsValidation) { - query = validator.validate(query); - } - - RelMetadataQuery.THREAD_PROVIDERS.set( - JaninoRelMetadataProvider.of(cluster.getMetadataProvider())); - RelNode result = convertQueryRecursive(query, top, null).rel; - if (top) { - if (isStream(query)) { - result = new LogicalDelta(cluster, result.getTraitSet(), result); - } - } - RelCollation collation = RelCollations.EMPTY; - if (!query.isA(SqlKind.DML)) { - if (isOrdered(query)) { - collation = requiredCollation(result); - } - } - checkConvertedType(query, result); - - if (SQL2REL_LOGGER.isDebugEnabled()) { - SQL2REL_LOGGER.debug( - RelOptUtil.dumpPlan("Plan after converting SqlNode to RelNode", - result, SqlExplainFormat.TEXT, - SqlExplainLevel.EXPPLAN_ATTRIBUTES)); - } - - final RelDataType validatedRowType = validator.getValidatedNodeType(query); - return RelRoot.of(result, validatedRowType, query.getKind()) - .withCollation(collation); - } - - private static boolean isStream(SqlNode query) { - return query instanceof SqlSelect - && ((SqlSelect) query).isKeywordPresent(SqlSelectKeyword.STREAM); - } - - public static boolean isOrdered(SqlNode query) { - switch (query.getKind()) { - case SELECT: - return ((SqlSelect) query).getOrderList() != null - && ((SqlSelect) query).getOrderList().size() > 0; - case WITH: - return isOrdered(((SqlWith) query).body); - case ORDER_BY: - return ((SqlOrderBy) query).orderList.size() > 0; - default: - return false; - } - } - - private RelCollation requiredCollation(RelNode r) { - if (r instanceof Sort) { - return ((Sort) r).collation; - } - if (r instanceof Project) { - return requiredCollation(((Project) r).getInput()); - } - if (r instanceof Delta) { - return requiredCollation(((Delta) r).getInput()); - } - throw new AssertionError(); - } - - /** - * 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 Blackboard bb = createBlackboard(selectScope, null, top); - convertSelectImpl(bb, select); - return bb.root; - } - - /** - * Factory method for creating translation workspace. - */ - protected Blackboard createBlackboard(SqlValidatorScope scope, - Map<String, RexNode> nameToNodeMap, boolean top) { - return new Blackboard(scope, nameToNodeMap, top); - } - - /** - * Implementation of {@link #convertSelect(SqlSelect, boolean)}; - * derived class may override. - */ - protected void convertSelectImpl( - final Blackboard bb, - SqlSelect select) { - convertFrom( - bb, - select.getFrom()); - convertWhere( - bb, - select.getWhere()); - - final List<SqlNode> orderExprList = new ArrayList<>(); - final List<RelFieldCollation> collationList = new ArrayList<>(); - gatherOrderExprs( - bb, - select, - select.getOrderList(), - orderExprList, - collationList); - final RelCollation collation = - cluster.traitSet().canonize(RelCollations.of(collationList)); - - if (validator.isAggregate(select)) { - convertAgg( - bb, - select, - orderExprList); - } else { - convertSelectList( - bb, - select, - orderExprList); - } - - if (select.isDistinct()) { - distinctify(bb, true); - } - convertOrder( - select, bb, collation, orderExprList, select.getOffset(), - select.getFetch()); - bb.setRoot(bb.root, true); - } - - /** - * Having translated 'SELECT ... FROM ... [GROUP BY ...] [HAVING ...]', adds - * a relational expression to make the results unique. - * - * <p>If the SELECT clause contains duplicate expressions, adds - * {@link org.apache.calcite.rel.logical.LogicalProject}s so that we are - * grouping on the minimal set of keys. The performance gain isn't huge, but - * it is difficult to detect these duplicate expressions later. - * - * @param bb Blackboard - * @param checkForDupExprs Check for duplicate expressions - */ - private void distinctify( - Blackboard bb, - boolean checkForDupExprs) { - // Look for duplicate expressions in the project. - // Say we have 'select x, y, x, z'. - // Then dups will be {[2, 0]} - // and oldToNew will be {[0, 0], [1, 1], [2, 0], [3, 2]} - RelNode rel = bb.root; - if (checkForDupExprs && (rel instanceof LogicalProject)) { - LogicalProject project = (LogicalProject) rel; - final List<RexNode> projectExprs = project.getProjects(); - final List<Integer> origins = new ArrayList<>(); - int dupCount = 0; - for (int i = 0; i < projectExprs.size(); i++) { - int x = findExpr(projectExprs.get(i), projectExprs, i); - if (x >= 0) { - origins.add(x); - ++dupCount; - } else { - origins.add(i); - } - } - if (dupCount == 0) { - distinctify(bb, false); - return; - } - - final Map<Integer, Integer> squished = Maps.newHashMap(); - final List<RelDataTypeField> fields = rel.getRowType().getFieldList(); - final List<Pair<RexNode, String>> newProjects = Lists.newArrayList(); - for (int i = 0; i < fields.size(); i++) { - if (origins.get(i) == i) { - squished.put(i, newProjects.size()); - newProjects.add(RexInputRef.of2(i, fields)); - } - } - rel = - LogicalProject.create(rel, Pair.left(newProjects), - Pair.right(newProjects)); - bb.root = rel; - distinctify(bb, false); - rel = bb.root; - - // Create the expressions to reverse the mapping. - // Project($0, $1, $0, $2). - final List<Pair<RexNode, String>> undoProjects = Lists.newArrayList(); - for (int i = 0; i < fields.size(); i++) { - final int origin = origins.get(i); - RelDataTypeField field = fields.get(i); - undoProjects.add( - Pair.of( - (RexNode) new RexInputRef( - squished.get(origin), field.getType()), - field.getName())); - } - - rel = - LogicalProject.create(rel, Pair.left(undoProjects), - Pair.right(undoProjects)); - bb.setRoot( - rel, - false); - - return; - } - - // Usual case: all of the expressions in the SELECT clause are - // different. - final ImmutableBitSet groupSet = - ImmutableBitSet.range(rel.getRowType().getFieldCount()); - rel = - createAggregate(bb, false, groupSet, ImmutableList.of(groupSet), - ImmutableList.<AggregateCall>of()); - - bb.setRoot( - rel, - false); - } - - private int findExpr(RexNode seek, List<RexNode> exprs, int count) { - for (int i = 0; i < count; i++) { - RexNode expr = exprs.get(i); - if (expr.toString().equals(seek.toString())) { - return i; - } - } - return -1; - } - - /** - * Converts a query's ORDER BY clause, if any. - * - * @param select Query - * @param bb Blackboard - * @param collation Collation list - * @param orderExprList Method populates this list with orderBy expressions - * not present in selectList - * @param offset Expression for number of rows to discard before - * returning first row - * @param fetch Expression for number of rows to fetch - */ - protected void convertOrder( - SqlSelect select, - Blackboard bb, - RelCollation collation, - List<SqlNode> orderExprList, - SqlNode offset, - SqlNode fetch) { - if (select.getOrderList() == null - || select.getOrderList().getList().isEmpty()) { - assert collation.getFieldCollations().isEmpty(); - if ((offset == null - || ((SqlLiteral) offset).bigDecimalValue().equals(BigDecimal.ZERO)) - && fetch == null) { - return; - } - } - - // Create a sorter using the previously constructed collations. - bb.setRoot( - LogicalSort.create(bb.root, collation, - offset == null ? null : convertExpression(offset), - fetch == null ? null : convertExpression(fetch)), - false); - - // If extra expressions were added to the project list for sorting, - // add another project to remove them. But make the collation empty, because - // we can't represent the real collation. - // - // If it is the top node, use the real collation, but don't trim fields. - if (orderExprList.size() > 0 && !bb.top) { - final List<RexNode> exprs = new ArrayList<>(); - final RelDataType rowType = bb.root.getRowType(); - final int fieldCount = - rowType.getFieldCount() - orderExprList.size(); - for (int i = 0; i < fieldCount; i++) { - exprs.add(rexBuilder.makeInputRef(bb.root, i)); - } - bb.setRoot( - LogicalProject.create(bb.root, exprs, - rowType.getFieldNames().subList(0, fieldCount)), - false); - } - } - - /** - * Returns whether a given node contains a {@link SqlInOperator}. - * - * @param node a RexNode tree - */ - private static boolean containsInOperator( - SqlNode node) { - try { - SqlVisitor<Void> visitor = - new SqlBasicVisitor<Void>() { - public Void visit(SqlCall call) { - if (call.getOperator() instanceof SqlInOperator) { - throw new Util.FoundOne(call); - } - return super.visit(call); - } - }; - node.accept(visitor); - return false; - } catch (Util.FoundOne e) { - Util.swallow(e, null); - return true; - } - } - - /** - * Push down all the NOT logical operators into any IN/NOT IN operators. - * - * @param scope Scope where {@code sqlNode} occurs - * @param sqlNode the root node from which to look for NOT operators - * @return the transformed SqlNode representation with NOT pushed down. - */ - private static SqlNode pushDownNotForIn(SqlValidatorScope scope, - SqlNode sqlNode) { - if ((sqlNode instanceof SqlCall) && containsInOperator(sqlNode)) { - SqlCall sqlCall = (SqlCall) sqlNode; - if ((sqlCall.getOperator() == SqlStdOperatorTable.AND) - || (sqlCall.getOperator() == SqlStdOperatorTable.OR)) { - SqlNode[] sqlOperands = ((SqlBasicCall) sqlCall).operands; - for (int i = 0; i < sqlOperands.length; i++) { - sqlOperands[i] = pushDownNotForIn(scope, sqlOperands[i]); - } - return reg(scope, sqlNode); - } else if (sqlCall.getOperator() == SqlStdOperatorTable.NOT) { - SqlNode childNode = sqlCall.operand(0); - assert childNode instanceof SqlCall; - SqlBasicCall childSqlCall = (SqlBasicCall) childNode; - if (childSqlCall.getOperator() == SqlStdOperatorTable.AND) { - SqlNode[] andOperands = childSqlCall.getOperands(); - SqlNode[] orOperands = new SqlNode[andOperands.length]; - for (int i = 0; i < orOperands.length; i++) { - orOperands[i] = reg(scope, - SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, - andOperands[i])); - } - for (int i = 0; i < orOperands.length; i++) { - orOperands[i] = pushDownNotForIn(scope, orOperands[i]); - } - return reg(scope, - SqlStdOperatorTable.OR.createCall(SqlParserPos.ZERO, - orOperands[0], orOperands[1])); - } else if (childSqlCall.getOperator() == SqlStdOperatorTable.OR) { - SqlNode[] orOperands = childSqlCall.getOperands(); - SqlNode[] andOperands = new SqlNode[orOperands.length]; - for (int i = 0; i < andOperands.length; i++) { - andOperands[i] = reg(scope, - SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, - orOperands[i])); - } - for (int i = 0; i < andOperands.length; i++) { - andOperands[i] = pushDownNotForIn(scope, andOperands[i]); - } - return reg(scope, - SqlStdOperatorTable.AND.createCall(SqlParserPos.ZERO, - andOperands[0], andOperands[1])); - } else if (childSqlCall.getOperator() == SqlStdOperatorTable.NOT) { - SqlNode[] notOperands = childSqlCall.getOperands(); - assert notOperands.length == 1; - return pushDownNotForIn(scope, notOperands[0]); - } else if (childSqlCall.getOperator() instanceof SqlInOperator) { - SqlNode[] inOperands = childSqlCall.getOperands(); - SqlInOperator inOp = - (SqlInOperator) childSqlCall.getOperator(); - if (inOp.isNotIn()) { - return reg(scope, - SqlStdOperatorTable.IN.createCall(SqlParserPos.ZERO, - inOperands[0], inOperands[1])); - } else { - return reg(scope, - SqlStdOperatorTable.NOT_IN.createCall(SqlParserPos.ZERO, - inOperands[0], inOperands[1])); - } - } else { - // childSqlCall is "leaf" node in a logical expression tree - // (only considering AND, OR, NOT) - return sqlNode; - } - } else { - // sqlNode is "leaf" node in a logical expression tree - // (only considering AND, OR, NOT) - return sqlNode; - } - } else { - // tree rooted at sqlNode does not contain inOperator - return sqlNode; - } - } - - /** Registers with the validator a {@link SqlNode} that has been created - * during the Sql-to-Rel process. */ - private static SqlNode reg(SqlValidatorScope scope, SqlNode e) { - scope.getValidator().deriveType(scope, e); - return e; - } - - /** - * Converts a WHERE clause. - * - * @param bb Blackboard - * @param where WHERE clause, may be null - */ - private void convertWhere( - final Blackboard bb, - final SqlNode where) { - if (where == null) { - return; - } - SqlNode newWhere = pushDownNotForIn(bb.scope, where); - replaceSubQueries(bb, newWhere, RelOptUtil.Logic.UNKNOWN_AS_FALSE); - final RexNode convertedWhere = bb.convertExpression(newWhere); - - // only allocate filter if the condition is not TRUE - if (convertedWhere.isAlwaysTrue()) { - return; - } - - final RelFactories.FilterFactory factory = - RelFactories.DEFAULT_FILTER_FACTORY; - final RelNode filter = factory.createFilter(bb.root, convertedWhere); - final RelNode r; - final CorrelationUse p = getCorrelationUse(bb, filter); - if (p != null) { - assert p.r instanceof Filter; - Filter f = (Filter) p.r; - r = LogicalFilter.create(f.getInput(), f.getCondition(), - ImmutableSet.of(p.id)); - } else { - r = filter; - } - - bb.setRoot(r, false); - } - - private void replaceSubQueries( - final Blackboard bb, - final SqlNode expr, - RelOptUtil.Logic logic) { - findSubQueries(bb, expr, logic, false); - for (SubQuery node : bb.subQueryList) { - substituteSubQuery(bb, node); - } - } - - private void substituteSubQuery(Blackboard bb, SubQuery subQuery) { - final RexNode expr = subQuery.expr; - if (expr != null) { - // Already done. - return; - } - - final SqlBasicCall call; - final RelNode rel; - final SqlNode query; - final RelOptUtil.Exists converted; - switch (subQuery.node.getKind()) { - case CURSOR: - convertCursor(bb, subQuery); - return; - - case MULTISET_QUERY_CONSTRUCTOR: - case MULTISET_VALUE_CONSTRUCTOR: - case ARRAY_QUERY_CONSTRUCTOR: - rel = convertMultisets(ImmutableList.of(subQuery.node), bb); - subQuery.expr = bb.register(rel, JoinRelType.INNER); - return; - - case IN: - call = (SqlBasicCall) subQuery.node; - query = call.operand(1); - if (!config.isExpand() && !(query instanceof SqlNodeList)) { - return; - } - final SqlNode leftKeyNode = call.operand(0); - - final List<RexNode> leftKeys; - switch (leftKeyNode.getKind()) { - case ROW: - leftKeys = Lists.newArrayList(); - for (SqlNode sqlExpr : ((SqlBasicCall) leftKeyNode).getOperandList()) { - leftKeys.add(bb.convertExpression(sqlExpr)); - } - break; - default: - leftKeys = ImmutableList.of(bb.convertExpression(leftKeyNode)); - } - - final boolean notIn = ((SqlInOperator) call.getOperator()).isNotIn(); - if (query instanceof SqlNodeList) { - SqlNodeList valueList = (SqlNodeList) query; - if (!containsNullLiteral(valueList) - && valueList.size() < config.getInSubQueryThreshold()) { - // We're under the threshold, so convert to OR. - subQuery.expr = - convertInToOr( - bb, - leftKeys, - valueList, - notIn); - return; - } - - // Otherwise, let convertExists translate - // values list into an inline table for the - // reference to Q below. - } - - // Project out the search columns from the left side - - // Q1: - // "select from emp where emp.deptno in (select col1 from T)" - // - // is converted to - // - // "select from - // emp inner join (select distinct col1 from T)) q - // on emp.deptno = q.col1 - // - // Q2: - // "select from emp where emp.deptno not in (Q)" - // - // is converted to - // - // "select from - // emp left outer join (select distinct col1, TRUE from T) q - // on emp.deptno = q.col1 - // where emp.deptno <> null - // and q.indicator <> TRUE" - // - final RelDataType targetRowType = - SqlTypeUtil.promoteToRowType(typeFactory, - validator.getValidatedNodeType(leftKeyNode), null); - converted = - convertExists(query, RelOptUtil.SubQueryType.IN, subQuery.logic, - notIn, targetRowType); - if (converted.indicator) { - // Generate - // emp CROSS JOIN (SELECT COUNT(*) AS c, - // COUNT(deptno) AS ck FROM dept) - final RelDataType longType = - typeFactory.createSqlType(SqlTypeName.BIGINT); - final RelNode seek = converted.r.getInput(0); // fragile - final int keyCount = leftKeys.size(); - final List<Integer> args = ImmutableIntList.range(0, keyCount); - LogicalAggregate aggregate = - LogicalAggregate.create(seek, false, ImmutableBitSet.of(), null, - ImmutableList.of( - AggregateCall.create(SqlStdOperatorTable.COUNT, false, - ImmutableList.<Integer>of(), -1, longType, null), - AggregateCall.create(SqlStdOperatorTable.COUNT, false, - args, -1, longType, null))); - LogicalJoin join = - LogicalJoin.create(bb.root, aggregate, rexBuilder.makeLiteral(true), - ImmutableSet.<CorrelationId>of(), JoinRelType.INNER); - bb.setRoot(join, false); - } - final RexNode rex = - bb.register(converted.r, - converted.outerJoin ? JoinRelType.LEFT : JoinRelType.INNER, - leftKeys); - - RelOptUtil.Logic logic = subQuery.logic; - switch (logic) { - case TRUE_FALSE_UNKNOWN: - case UNKNOWN_AS_TRUE: - if (!converted.indicator) { - logic = RelOptUtil.Logic.TRUE_FALSE; - } - } - subQuery.expr = translateIn(logic, bb.root, rex); - if (notIn) { - subQuery.expr = - rexBuilder.makeCall(SqlStdOperatorTable.NOT, subQuery.expr); - } - return; - - case EXISTS: - // "select from emp where exists (select a from T)" - // - // is converted to the following if the sub-query is correlated: - // - // "select from emp left outer join (select AGG_TRUE() as indicator - // from T group by corr_var) q where q.indicator is true" - // - // If there is no correlation, the expression is replaced with a - // boolean indicating whether the sub-query returned 0 or >= 1 row. - call = (SqlBasicCall) subQuery.node; - query = call.operand(0); - if (!config.isExpand()) { - return; - } - converted = convertExists(query, RelOptUtil.SubQueryType.EXISTS, - subQuery.logic, true, null); - assert !converted.indicator; - if (convertNonCorrelatedSubQuery(subQuery, bb, converted.r, true)) { - return; - } - subQuery.expr = bb.register(converted.r, JoinRelType.LEFT); - return; - - case SCALAR_QUERY: - // Convert the sub-query. If it's non-correlated, convert it - // to a constant expression. - if (!config.isExpand()) { - return; - } - call = (SqlBasicCall) subQuery.node; - query = call.operand(0); - converted = convertExists(query, RelOptUtil.SubQueryType.SCALAR, - subQuery.logic, true, null); - assert !converted.indicator; - if (convertNonCorrelatedSubQuery(subQuery, bb, converted.r, false)) { - return; - } - rel = convertToSingleValueSubq(query, converted.r); - subQuery.expr = bb.register(rel, JoinRelType.LEFT); - return; - - case SELECT: - // This is used when converting multiset queries: - // - // select * from unnest(select multiset[deptno] from emps); - // - converted = convertExists(subQuery.node, RelOptUtil.SubQueryType.SCALAR, - subQuery.logic, true, null); - assert !converted.indicator; - subQuery.expr = bb.register(converted.r, JoinRelType.LEFT); - return; - - default: - throw new AssertionError("unexpected kind of sub-query: " - + subQuery.node); - } - } - - private RexNode translateIn(RelOptUtil.Logic logic, RelNode root, - final RexNode rex) { - switch (logic) { - case TRUE: - return rexBuilder.makeLiteral(true); - - case TRUE_FALSE: - case UNKNOWN_AS_FALSE: - assert rex instanceof RexRangeRef; - final int fieldCount = rex.getType().getFieldCount(); - RexNode rexNode = rexBuilder.makeFieldAccess(rex, fieldCount - 1); - rexNode = rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE, rexNode); - - // Then append the IS NOT NULL(leftKeysForIn). - // - // RexRangeRef contains the following fields: - // leftKeysForIn, - // rightKeysForIn (the original sub-query select list), - // nullIndicator - // - // The first two lists contain the same number of fields. - final int k = (fieldCount - 1) / 2; - for (int i = 0; i < k; i++) { - rexNode = - rexBuilder.makeCall( - SqlStdOperatorTable.AND, - rexNode, - rexBuilder.makeCall( - SqlStdOperatorTable.IS_NOT_NULL, - rexBuilder.makeFieldAccess(rex, i))); - } - return rexNode; - - case TRUE_FALSE_UNKNOWN: - case UNKNOWN_AS_TRUE: - // select e.deptno, - // case - // when ct.c = 0 then false - // when dt.i is not null then true - // when e.deptno is null then null - // when ct.ck < ct.c then null - // else false - // end - // from e - // cross join (select count(*) as c, count(deptno) as ck from v) as ct - // left join (select distinct deptno, true as i from v) as dt - // on e.deptno = dt.deptno - final Join join = (Join) root; - final Project left = (Project) join.getLeft(); - final RelNode leftLeft = ((Join) left.getInput()).getLeft(); - final int leftLeftCount = leftLeft.getRowType().getFieldCount(); - final RelDataType longType = - typeFactory.createSqlType(SqlTypeName.BIGINT); - final RexNode cRef = rexBuilder.makeInputRef(root, leftLeftCount); - final RexNode ckRef = rexBuilder.makeInputRef(root, leftLeftCount + 1); - final RexNode iRef = - rexBuilder.makeInputRef(root, root.getRowType().getFieldCount() - 1); - - final RexLiteral zero = - rexBuilder.makeExactLiteral(BigDecimal.ZERO, longType); - final RexLiteral trueLiteral = rexBuilder.makeLiteral(true); - final RexLiteral falseLiteral = rexBuilder.makeLiteral(false); - final RexNode unknownLiteral = - rexBuilder.makeNullLiteral(trueLiteral.getType()); - - final ImmutableList.Builder<RexNode> args = ImmutableList.builder(); - args.add(rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, cRef, zero), - falseLiteral, - rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, iRef), - trueLiteral); - final JoinInfo joinInfo = join.analyzeCondition(); - for (int leftKey : joinInfo.leftKeys) { - final RexNode kRef = rexBuilder.makeInputRef(root, leftKey); - args.add(rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, kRef), - unknownLiteral); - } - args.add(rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ckRef, cRef), - unknownLiteral, - falseLiteral); - - return rexBuilder.makeCall(SqlStdOperatorTable.CASE, args.build()); - - default: - throw new AssertionError(logic); - } - } - - private static boolean containsNullLiteral(SqlNodeList valueList) { - for (SqlNode node : valueList.getList()) { - if (node instanceof SqlLiteral) { - SqlLiteral lit = (SqlLiteral) node; - if (lit.getValue() == null) { - return true; - } - } - } - return false; - } - - /** - * Determines if a sub-query is non-correlated and if so, converts it to a - * constant. - * - * @param subQuery the call that references the sub-query - * @param bb blackboard used to convert the sub-query - * @param converted RelNode tree corresponding to the sub-query - * @param isExists true if the sub-query is part of an EXISTS expression - * @return Whether the sub-query can be converted to a constant - */ - private boolean convertNonCorrelatedSubQuery( - SubQuery subQuery, - Blackboard bb, - RelNode converted, - boolean isExists) { - SqlCall call = (SqlBasicCall) subQuery.node; - if (subQueryConverter.canConvertSubQuery() - && isSubQueryNonCorrelated(converted, bb)) { - // First check if the sub-query has already been converted - // because it's a nested sub-query. If so, don't re-evaluate - // it again. - RexNode constExpr = mapConvertedNonCorrSubqs.get(call); - if (constExpr == null) { - constExpr = - subQueryConverter.convertSubQuery( - call, - this, - isExists, - config.isExplain()); - } - if (constExpr != null) { - subQuery.expr = constExpr; - mapConvertedNonCorrSubqs.put(call, constExpr); - return true; - } - } - return false; - } - - /** - * Converts the RelNode tree for a select statement to a select that - * produces a single value. - * - * @param query the query - * @param plan the original RelNode tree corresponding to the statement - * @return the converted RelNode tree - */ - public RelNode convertToSingleValueSubq( - SqlNode query, - RelNode plan) { - // Check whether query is guaranteed to produce a single value. - if (query instanceof SqlSelect) { - SqlSelect select = (SqlSelect) query; - SqlNodeList selectList = select.getSelectList(); - SqlNodeList groupList = select.getGroup(); - - if ((selectList.size() == 1) - && ((groupList == null) || (groupList.size() == 0))) { - SqlNode selectExpr = selectList.get(0); - if (selectExpr instanceof SqlCall) { - SqlCall selectExprCall = (SqlCall) selectExpr; - if (Util.isSingleValue(selectExprCall)) { - return plan; - } - } - - // If there is a limit with 0 or 1, - // it is ensured to produce a single value - if (select.getFetch() != null - && select.getFetch() instanceof SqlNumericLiteral) { - SqlNumericLiteral limitNum = (SqlNumericLiteral) select.getFetch(); - if (((BigDecimal) limitNum.getValue()).intValue() < 2) { - return plan; - } - } - } - } else if (query instanceof SqlCall) { - // If the query is (values ...), - // it is necessary to look into the operands to determine - // whether SingleValueAgg is necessary - SqlCall exprCall = (SqlCall) query; - if (exprCall.getOperator() - instanceof SqlValuesOperator - && Util.isSingleValue(exprCall)) { - return plan; - } - } - - // If not, project SingleValueAgg - return RelOptUtil.createSingleValueAggRel( - cluster, - plan); - } - - /** - * Converts "x IN (1, 2, ...)" to "x=1 OR x=2 OR ...". - * - * @param leftKeys LHS - * @param valuesList RHS - * @param isNotIn is this a NOT IN operator - * @return converted expression - */ - private RexNode convertInToOr( - final Blackboard bb, - final List<RexNode> leftKeys, - SqlNodeList valuesList, - boolean isNotIn) { - final List<RexNode> comparisons = new ArrayList<>(); - for (SqlNode rightVals : valuesList) { - RexNode rexComparison; - if (leftKeys.size() == 1) { - rexComparison = - rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, - leftKeys.get(0), - ensureSqlType(leftKeys.get(0).getType(), - bb.convertExpression(rightVals))); - } else { - assert rightVals instanceof SqlCall; - final SqlBasicCall call = (SqlBasicCall) rightVals; - assert (call.getOperator() instanceof SqlRowOperator) - && call.operandCount() == leftKeys.size(); - rexComparison = - RexUtil.composeConjunction( - rexBuilder, - Iterables.transform( - Pair.zip(leftKeys, call.getOperandList()), - new Function<Pair<RexNode, SqlNode>, RexNode>() { - public RexNode apply(Pair<RexNode, SqlNode> pair) { - return rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, - pair.left, - ensureSqlType(pair.left.getType(), - bb.convertExpression(pair.right))); - } - }), - false); - } - comparisons.add(rexComparison); - } - - RexNode result = - RexUtil.composeDisjunction(rexBuilder, comparisons, true); - assert result != null; - - if (isNotIn) { - result = - rexBuilder.makeCall( - SqlStdOperatorTable.NOT, - result); - } - - return result; - } - - /** Ensures that an expression has a given {@link SqlTypeName}, applying a - * cast if necessary. If the expression already has the right type family, - * returns the expression unchanged. */ - private RexNode ensureSqlType(RelDataType type, RexNode node) { - if (type.getSqlTypeName() == node.getType().getSqlTypeName() - || (type.getSqlTypeName() == SqlTypeName.VARCHAR - && node.getType().getSqlTypeName() == SqlTypeName.CHAR)) { - return node; - } - return rexBuilder.ensureType(type, node, true); - } - - /** - * Gets the list size threshold under which {@link #convertInToOr} is used. - * Lists of this size or greater will instead be converted to use a join - * against an inline table - * ({@link org.apache.calcite.rel.logical.LogicalValues}) rather than a - * predicate. A threshold of 0 forces usage of an inline table in all cases; a - * threshold of Integer.MAX_VALUE forces usage of OR in all cases - * - * @return threshold, default {@link #DEFAULT_IN_SUB_QUERY_THRESHOLD} - */ - @Deprecated // to be removed before 2.0 - protected int getInSubqueryThreshold() { - return config.getInSubQueryThreshold(); - } - - /** - * Converts an EXISTS or IN predicate into a join. For EXISTS, the sub-query - * produces an indicator variable, and the result is a relational expression - * which outer joins that indicator to the original query. After performing - * the outer join, the condition will be TRUE if the EXISTS condition holds, - * NULL otherwise. - * - * @param seek A query, for example 'select * from emp' or - * 'values (1,2,3)' or '('Foo', 34)'. - * @param subQueryType Whether sub-query is IN, EXISTS or scalar - * @param logic Whether the answer needs to be in full 3-valued logic (TRUE, - * FALSE, UNKNOWN) will be required, or whether we can accept an - * approximation (say representing UNKNOWN as FALSE) - * @param notIn Whether the operation is NOT IN - * @return join expression - */ - private RelOptUtil.Exists convertExists( - SqlNode seek, - RelOptUtil.SubQueryType subQueryType, - RelOptUtil.Logic logic, - boolean notIn, - RelDataType targetDataType) { - final SqlValidatorScope seekScope = - (seek instanceof SqlSelect) - ? validator.getSelectScope((SqlSelect) seek) - : null; - final Blackboard seekBb = createBlackboard(seekScope, null, false); - RelNode seekRel = convertQueryOrInList(seekBb, seek, targetDataType); - - return RelOptUtil.createExistsPlan(seekRel, subQueryType, logic, notIn); - } - - private RelNode convertQueryOrInList( - Blackboard bb, - SqlNode seek, - RelDataType targetRowType) { - // NOTE: Once we start accepting single-row queries as row constructors, - // there will be an ambiguity here for a case like X IN ((SELECT Y FROM - // Z)). The SQL standard resolves the ambiguity by saying that a lone - // select should be interpreted as a table expression, not a row - // expression. The semantic difference is that a table expression can - // return multiple rows. - if (seek instanceof SqlNodeList) { - return convertRowValues( - bb, - seek, - ((SqlNodeList) seek).getList(), - false, - targetRowType); - } else { - return convertQueryRecursive(seek, false, null).project(); - } - } - - private RelNode convertRowValues( - Blackboard bb, - SqlNode rowList, - Collection<SqlNode> rows, - boolean allowLiteralsOnly, - RelDataType targetRowType) { - // NOTE jvs 30-Apr-2006: We combine all rows consisting entirely of - // literals into a single LogicalValues; this gives the optimizer a smaller - // input tree. For everything else (computed expressions, row - // sub-queries), we union each row in as a projection on top of a - // LogicalOneRow. - - final ImmutableList.Builder<ImmutableList<RexLiteral>> tupleList = - ImmutableList.builder(); - final RelDataType rowType; - if (targetRowType != null) { - rowType = targetRowType; - } else { - rowType = - SqlTypeUtil.promoteToRowType( - typeFactory, - validator.getValidatedNodeType(rowList), - null); - } - - final List<RelNode> unionInputs = new ArrayList<>(); - for (SqlNode node : rows) { - SqlBasicCall call; - if (isRowConstructor(node)) { - call = (SqlBasicCall) node; - ImmutableList.Builder<RexLiteral> tuple = ImmutableList.builder(); - for (Ord<SqlNode> operand : Ord.zip(call.operands)) { - RexLiteral rexLiteral = - convertLiteralInValuesList( - operand.e, - bb, - rowType, - operand.i); - if ((rexLiteral == null) && allowLiteralsOnly) { - return null; - } - if ((rexLiteral == null) || !config.isCreateValuesRel()) { - // fallback to convertRowConstructor - tuple = null; - break; - } - tuple.add(rexLiteral); - } - if (tuple != null) { - tupleList.add(tuple.build()); - continue; - } - } else { - RexLiteral rexLiteral = - convertLiteralInValuesList( - node, - bb, - rowType, - 0); - if ((rexLiteral != null) && config.isCreateValuesRel()) { - tupleList.add(ImmutableList.of(rexLiteral)); - continue; - } else { - if ((rexLiteral == null) && allowLiteralsOnly) { - return null; - } - } - - // convert "1" to "row(1)" - call = - (SqlBasicCall) SqlStdOperatorTable.ROW.createCall( - SqlParserPos.ZERO, - node); - } - unionInputs.add(convertRowConstructor(bb, call)); - } - LogicalValues values = - LogicalValues.create(cluster, rowType, tupleList.build()); - RelNode resultRel; - if (unionInputs.isEmpty()) { - resultRel = values; - } else { - if (!values.getTuples().isEmpty()) { - unionInputs.add(values); - } - resultRel = LogicalUnion.create(unionInputs, true); - } - leaves.add(resultRel); - return resultRel; - } - - private RexLiteral convertLiteralInValuesList( - SqlNode sqlNode, - Blackboard bb, - RelDataType rowType, - int iField) { - if (!(sqlNode instanceof SqlLiteral)) { - return null; - } - RelDataTypeField field = rowType.getFieldList().get(iField); - RelDataType type = field.getType(); - if (type.isStruct()) { - // null literals for weird stuff like UDT's need - // special handling during type flattening, so - // don't use LogicalValues for those - return null; - } - - RexNode literalExpr = - exprConverter.convertLiteral( - bb, - (SqlLiteral) sqlNode); - - if (!(literalExpr instanceof RexLiteral)) { - assert literalExpr.isA(SqlKind.CAST); - RexNode child = ((RexCall) literalExpr).getOperands().get(0); - assert RexLiteral.isNullLiteral(child); - - // NOTE jvs 22-Nov-2006: we preserve type info - // in LogicalValues digest, so it's OK to lose it here - return (RexLiteral) child; - } - - RexLiteral literal = (RexLiteral) literalExpr; - - Comparable value = literal.getValue(); - - if (SqlTypeUtil.isExactNumeric(type) && SqlTypeUtil.hasScale(type)) { - BigDecimal roundedValue = - NumberUtil.rescaleBigDecimal( - (BigDecimal) value, - type.getScale()); - return rexBuilder.makeExactLiteral( - roundedValue, - type); - } - - if ((value instanceof NlsString) - && (type.getSqlTypeName() == SqlTypeName.CHAR)) { - // pad fixed character type - NlsString unpadded = (NlsString) value; - return rexBuilder.makeCharLiteral( - new NlsString( - Spaces.padRight(unpadded.getValue(), type.getPrecision()), - unpadded.getCharsetName(), - unpadded.getCollation())); - } - return literal; - } - - private boolean isRowConstructor(SqlNode node) { - if (!(node.getKind() == SqlKind.ROW)) { - return false; - } - SqlCall call = (SqlCall) node; - return call.getOperator().getName().equalsIgnoreCase("row"); - } - - /** - * Builds a list of all <code>IN</code> or <code>EXISTS</code> operators - * inside SQL parse tree. Does not traverse inside queries. - * - * @param bb blackboard - * @param node the SQL parse tree - * @param logic Whether the answer needs to be in full 3-valued logic (TRUE, - * FALSE, UNKNOWN) will be required, or whether we can accept - * an approximation (say representing UNKNOWN as FALSE) - * @param registerOnlyScalarSubQueries if set to true and the parse tree - * corresponds to a variation of a select - * node, only register it if it's a scalar - * sub-query - */ - private void findSubQueries( - Blackboard bb, - SqlNode node, - RelOptUtil.Logic logic, - boolean registerOnlyScalarSubQueries) { - final SqlKind kind = node.getKind(); - switch (kind) { - case EXISTS: - case SELECT: - case MULTISET_QUERY_CONSTRUCTOR: - case MULTISET_VALUE_CONSTRUCTOR: - case ARRAY_QUERY_CONSTRUCTOR: - case CURSOR: - case SCALAR_QUERY: - if (!registerOnlyScalarSubQueries - || (kind == SqlKind.SCALAR_QUERY)) { - bb.registerSubQuery(node, RelOptUtil.Logic.TRUE_FALSE); - } - return; - case IN: - if (((SqlCall) node).getOperator() == SqlStdOperatorTable.NOT_IN) { - logic = logic.negate(); - } - break; - case NOT: - logic = logic.negate(); - break; - } - if (node instanceof SqlCall) { - for (SqlNode operand : ((SqlCall) node).getOperandList()) { - if (operand != null) { - // In the case of an IN expression, locate scalar - // sub-queries so we can convert them to constants - findSubQueries( - bb, - operand, - logic, - kind == SqlKind.IN || registerOnlyScalarSubQueries); - } - } - } else if (node instanceof SqlNodeList) { - for (SqlNode child : (SqlNodeList) node) { - findSubQueries( - bb, - child, - logic, - kind == SqlKind.IN || registerOnlyScalarSubQueries); - } - } - - // Now that we've located any scalar sub-queries inside the IN - // expression, register the IN expression itself. We need to - // register the scalar sub-queries first so they can be converted - // before the IN expression is converted. - if (kind == SqlKind.IN) { - switch (logic) { - case TRUE_FALSE_UNKNOWN: - if (validator.getValidatedNodeType(node).isNullable()) { - break; - } else if (true) { - break; - } - // fall through - case UNKNOWN_AS_FALSE: - logic = RelOptUtil.Logic.TRUE; - } - bb.registerSubQuery(node, logic); - } - } - - /** - * Converts an expression from {@link SqlNode} to {@link RexNode} format. - * - * @param node Expression to translate - * @return Converted expression - */ - public RexNode convertExpression( - SqlNode node) { - Map<String, RelDataType> nameToTypeMap = Collections.emptyMap(); - final ParameterScope scope = - new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap); - final Blackboard bb = createBlackboard(scope, null, false); - return bb.convertExpression(node); - } - - /** - * Converts an expression from {@link SqlNode} to {@link RexNode} format, - * mapping identifier references to predefined expressions. - * - * @param node Expression to translate - * @param nameToNodeMap map from String to {@link RexNode}; when an - * {@link SqlIdentifier} is encountered, it is used as a - * key and translated to the corresponding value from - * this map - * @return Converted expression - */ - public RexNode convertExpression( - SqlNode node, - Map<String, RexNode> nameToNodeMap) { - final Map<String, RelDataType> nameToTypeMap = new HashMap<>(); - for (Map.Entry<String, RexNode> entry : nameToNodeMap.entrySet()) { - nameToTypeMap.put(entry.getKey(), entry.getValue().getType()); - } - final ParameterScope scope = - new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap); - final Blackboard bb = createBlackboard(scope, nameToNodeMap, false); - return bb.convertExpression(node); - } - - /** - * Converts a non-standard expression. - * - * <p>This method is an extension-point that derived classes can override. If - * this method returns a null result, the normal expression translation - * process will proceed. The default implementation always returns null. - * - * @param node Expression - * @param bb Blackboard - * @return null to proceed with the usual expression translation process - */ - protected RexNode convertExtendedExpression( - SqlNode node, - Blackboard bb) { - return null; - } - - private RexNode convertOver(Blackboard bb, SqlNode node) { - SqlCall call = (SqlCall) node; - SqlCall aggCall = call.operand(0); - SqlNode windowOrRef = call.operand(1); - final SqlWindow window = - validator.resolveWindow(windowOrRef, bb.scope, true); - - // ROW_NUMBER() expects specific kind of framing. - if (aggCall.getKind() == SqlKind.ROW_NUMBER) { - window.setLowerBound(SqlWindow.createUnboundedPreceding(SqlParserPos.ZERO)); - window.setUpperBound(SqlWindow.createCurrentRow(SqlParserPos.ZERO)); - window.setRows(SqlLiteral.createBoolean(true, SqlParserPos.ZERO)); - } - final SqlNodeList partitionList = window.getPartitionList(); - final ImmutableList.Builder<RexNode> partitionKeys = - ImmutableList.builder(); - for (SqlNode partition : partitionList) { - partitionKeys.add(bb.convertExpression(partition)); - } - RexNode lowerBound = bb.convertExpression(window.getLowerBound()); - RexNode upperBound = bb.convertExpression(window.getUpperBound()); - SqlNodeList orderList = window.getOrderList(); - if ((orderList.size() == 0) && !window.isRows()) { - // A logical range requires an ORDER BY clause. Use the implicit - // ordering of this relation. There must be one, otherwise it would - // have failed validation. - orderList = bb.scope.getOrderList(); - if (orderList == null) { - throw new AssertionError( - "Relation should have sort key for implicit ORDER BY"); - } - } - final ImmutableList.Builder<RexFieldCollation> orderKeys = - ImmutableList.builder(); - final Set<SqlKind> flags = EnumSet.noneOf(SqlKind.class); - for (SqlNode order : orderList) { - flags.clear(); - RexNode e = bb.convertSortExpression(order, flags); - orderKeys.add(new RexFieldCollation(e, flags)); - } - try { - Preconditions.checkArgument(bb.window == null, - "already in window agg mode"); - bb.window = window; - RexNode rexAgg = exprConverter.convertCall(bb, aggCall); - rexAgg = - rexBuilder.ensureType( - validator.getValidatedNodeType(call), rexAgg, false); - - // Walk over the tree and apply 'over' to all agg functions. This is - // necessary because the returned expression is not necessarily a call - // to an agg function. For example, AVG(x) becomes SUM(x) / COUNT(x). - final RexShuttle visitor = - new HistogramShuttle( - partitionKeys.build(), orderKeys.build(), - RexWindowBound.create(window.getLowerBound(), lowerBound), - RexWindowBound.create(window.getUpperBound(), upperBound), - window); - return rexAgg.accept(visitor); - } finally { - bb.window = null; - } - } - - /** - * Converts a FROM clause into a relational expression. - * - * @param bb Scope within which to resolve identifiers - * @param from FROM clause of a query. Examples include: - * - * <ul> - * <li>a single table ("SALES.EMP"), - * <li>an aliased table ("EMP AS E"), - * <li>a list of tables ("EMP, DEPT"), - * <li>an ANSI Join expression ("EMP JOIN DEPT ON EMP.DEPTNO = - * DEPT.DEPTNO"), - * <li>a VALUES clause ("VALUES ('Fred', 20)"), - * <li>a query ("(SELECT * FROM EMP WHERE GENDER = 'F')"), - * <li>or any combination of the above. - * </ul> - */ - protected void convertFrom( - Blackboard bb, - SqlNode from) { - if (from == null) { - bb.setRoot(LogicalValues.createOneRow(cluster), false); - return; - } - - final SqlCall call; - final SqlNode[] operands; - switch (from.getKind()) { - case MATCH_RECOGNIZE: - convertMatchRecognize(bb, (SqlCall) from); - return; - - case AS: - convertFrom(bb, ((SqlCall) from).operand(0)); - return; - - case WITH_ITEM: - convertFrom(bb, ((SqlWithItem) from).query); - return; - - case WITH: - convertFrom(bb, ((SqlWith) from).body); - return; - - case TABLESAMPLE: - operands = ((SqlBasicCall) from).getOperands(); - SqlSampleSpec sampleSpec = SqlLiteral.sampleValue(operands[1]); - if (sampleSpec instanceof SqlSampleSpec.SqlSubstitutionSampleSpec) { - String sampleName = - ((SqlSampleSpec.SqlSubstitutionSampleSpec) sampleSpec) - .getName(); - datasetStack.push(sampleName); - convertFrom(bb, operands[0]); - datasetStack.pop(); - } else if (sampleSpec instanceof SqlSampleSpec.SqlTableSampleSpec) { - SqlSampleSpec.SqlTableSampleSpec tableSampleSpec = - (SqlSampleSpec.SqlTableSampleSpec) sampleSpec; - convertFrom(bb, operands[0]); - RelOptSamplingParameters params = - new RelOptSamplingParameters( - tableSampleSpec.isBernoulli(), - tableSampleSpec.getSamplePercentage(), - tableSampleSpec.isRepeatable(), - tableSampleSpec.getRepeatableSeed()); - bb.setRoot(new Sample(cluster, bb.root, params), false); - } else { - throw new AssertionError("unknown TABLESAMPLE type: " + sampleSpec); - } - return; - - case IDENTIFIER: - convertIdentifier(bb, (SqlIdentifier) from, null); - return; - - case EXTEND: - call = (SqlCall) from; - SqlIdentifier id = (SqlIdentifier) call.getOperandList().get(0); - SqlNodeList extendedColumns = (SqlNodeList) call.getOperandList().get(1); - convertIdentifier(bb, id, extendedColumns); - return; - - case JOIN: - final SqlJoin join = (SqlJoin) from; - final SqlValidatorScope scope = validator.getJoinScope(from); - final Blackboard fromBlackboard = createBlackboard(scope, null, false); - SqlNode left = join.getLeft(); - SqlNode right = join.getRight(); - final boolean isNatural = join.isNatural(); - final JoinType joinType = join.getJoinType(); - final SqlValidatorScope leftScope = - Util.first(validator.getJoinScope(left), - ((DelegatingScope) bb.scope).getParent()); - final Blackboard leftBlackboard = - createBlackboard(leftScope, null, false); - final SqlValidatorScope rightScope = - Util.first(validator.getJoinScope(right), - ((DelegatingScope) bb.scope).getParent()); - final Blackboard rightBlackboard = - createBlackboard(rightScope, null, false); - convertFrom(leftBlackboard, left); - RelNode leftRel = leftBlackboard.root; - convertFrom(rightBlackboard, right); - RelNode rightRel = rightBlackboard.root; - JoinRelType convertedJoinType = convertJoinType(joinType); - RexNode conditionExp; - final SqlValidatorNamespace leftNamespace = validator.getNamespace(left); - final SqlValidatorNamespace rightNamespace = validator.getNamespace(right); - if (isNatural) { - final RelDataType leftRowType = leftNamespace.getRowType(); - final RelDataType rightRowType = rightNamespace.getRowType(); - final List<String> columnList = - SqlValidatorUtil.deriveNaturalJoinColumnList(leftRowType, - rightRowType); - conditionExp = convertUsing(leftNamespace, rightNamespace, - columnList); - } else { - conditionExp = - convertJoinCondition( - fromBlackboard, - leftNamespace, - rightNamespace, - join.getCondition(), - join.getConditionType(), - leftRel, - rightRel); - } - - final RelNode joinRel = - createJoin( - fromBlackboard, - leftRel, - rightRel, - conditionExp, - convertedJoinType); - bb.setRoot(joinRel, false); - return; - - case SELECT: - case INTERSECT: - case EXCEPT: - case UNION: - final RelNode rel = convertQueryRecursive(from, false, null).project(); - bb.setRoot(rel, true); - return; - - case VALUES: - convertValuesImpl(bb, (SqlCall) from, null); - return; - - case UNNEST: - call = (SqlCall) from; - final List<SqlNode> nodes = call.getOperandList(); - final SqlUnnestOperator operator = (SqlUnnestOperator) call.getOperator(); - for (SqlNode node : nodes) { - replaceSubQueries(bb, node, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); - } - final List<RexNode> exprs = new ArrayList<>(); - final List<String> fieldNames = new ArrayList<>(); - for (Ord<SqlNode> node : Ord.zip(nodes)) { - exprs.add(bb.convertExpression(node.e)); - fieldNames.add(validator.deriveAlias(node.e, node.i)); - } - final RelNode input = - RelOptUtil.createProject( - (null != bb.root) ? bb.root : LogicalValues.createOneRow(cluster), - exprs, fieldNames, true); - - Uncollect uncollect = - new Uncollect(cluster, cluster.traitSetOf(Convention.NONE), - input, operator.withOrdinality); - bb.setRoot(uncollect, true); - return; - - case COLLECTION_TABLE: - call = (SqlCall) from; - - // Dig out real call; TABLE() wrapper is just syntactic. - assert call.getOperandList().size() == 1; - final SqlCall call2 = call.operand(0); - convertCollectionTable(bb, call2); - return; - - default: - throw new AssertionError("not a join operator " + from); - } - } - - protected void convertMatchRecognize(Blackboard bb, SqlCall call) { - final SqlMatchRecognize matchRecognize = (SqlMatchRecognize) call; - final SqlValidatorNamespace ns = validator.getNamespace(matchRecognize); - final SqlValidatorScope scope = validator.getMatchRecognizeScope(matchRecognize); - - final Blackboard mrBlackBoard = createBlackboard(scope, null, false); - final RelDataType rowType = ns.getRowType(); - // convert inner query, could be a table name or a derived table - SqlNode expr = matchRecognize.getTableRef(); - convertFrom(mrBlackBoard, expr); - final RelNode input = mrBlackBoard.root; - - // convert pattern - final Set<String> patternVarsSet = new HashSet<>(); - SqlNode pattern = matchRecognize.getPattern(); - final SqlBasicVisitor<RexNode> patternVarVisitor = - new SqlBasicVisitor<RexNode>() { - @Override public RexNode visit(SqlCall call) { - List<SqlNode> operands = call.getOperandList(); - List<RexNode> newOperands = Lists.newArrayList(); - for (SqlNode node : operands) { - newOperands.add(node.accept(this)); - } - return rexBuilder.makeCall( - validator.getUnknownType(), call.getOperator(), newOperands); - } - - @Override public RexNode visit(SqlIdentifier id) { - assert id.isSimple(); - patternVarsSet.add(id.getSimple()); - return rexBuilder.makeLiteral(id.getSimple()); - } - - @Override public RexNode visit(SqlLiteral literal) { - if (literal instanceof SqlNumericLiteral) { - return rexBuilder.makeExactLiteral(BigDecimal.valueOf(literal.intValue(true))); - } else { - return rexBuilder.makeLiteral(literal.booleanValue()); - } - } - }; - final RexNode patternNode = pattern.accept(patternVarVisitor); - - mrBlackBoard.setPatternVarRef(true); - - // convert definitions - final ImmutableMap.Builder<String, RexNode> definitionNodes = - ImmutableMap.builder(); - for (SqlNode def : matchRecognize.getPatternDefList()) { - List<SqlNode> operands = ((SqlCall) def).getOperandList(); - String alias = ((SqlIdentifier) operands.get(1)).getSimple(); - RexNode rex = mrBlackBoard.convertExpression(operands.get(0)); - definitionNodes.put(alias, rex); - } - - mrBlackBoard.setPatternVarRef(false); - - final RelFactories.MatchFactory factory = - RelFactories.DEFAULT_MATCH_FACTORY; - final RelNode rel = - factory.createMatchRecognize(input, patternNode, - matchRecognize.getStrictStart().booleanValue(), - matchRecognize.getStrictEnd().booleanValue(), - definitionNodes.build(), - rowType); - bb.setRoot(rel, false); - } - - private void convertIdentifier(Blackboard bb, SqlIdentifier id, - SqlNodeList extendedColumns) { - final SqlValidatorNamespace fromNamespace = - validator.getNamespace(id).resolve(); - if (fromNamespace.getNode() != null) { - convertFrom(bb, fromNamespace.getNode()); - return; - } - final String datasetName = - datasetStack.isEmpty() ? null : datasetStack.peek(); - final boolean[] usedDataset = {false}; - RelOptTable table = - SqlValidatorUtil.getRelOptTable(fromNamespace, catalogReader, - datasetName, usedDataset); - if (extendedColumns != null && extendedColumns.size() > 0) { - assert table != null; - final SqlValidatorTable validatorTable = - table.unwrap(SqlValidatorTable.class); - final List<RelDataTypeField> extendedFields = - SqlValidatorUtil.getExtendedColumns(validator, validatorTable, - extendedColumns); - table = table.extend(extendedFields); - } - final RelNode tableRel; - if (config.isConvertTableAccess()) { - tableRel = toRel(table); - } else { - tableRel = LogicalTableScan.create(cluster, table); - } - bb.setRoot(tableRel, true); - if (usedDataset[0]) { - bb.setDataset(datasetName); - } - } - - protected void convertCollectionTable( - Blackboard bb, - SqlCall call) { - final SqlOperator operator = call.getOperator(); - if (operator == SqlStdOperatorTable.TABLESAMPLE) { - final String sampleName = (String) SqlLiteral.value(call.operand(0)); - datasetStack.push(sampleName); - SqlCall cursorCall = call.operand(1); - SqlNode query = cursorCall.operand(0); - RelNode converted = convertQuery(query, false, false).rel; - bb.setRoot(converted, false); - datasetStack.pop(); - return; - } - replaceSubQueries(bb, call, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); - - // Expand table macro if possible. It's more efficient than - // LogicalTableFunctionScan. - final SqlCallBinding callBinding = - new SqlCallBinding(bb.scope.getValidator(), bb.scope, call); - if (operator instanceof SqlUserDefinedTableMacro) { - final SqlUserDefinedTableMacro udf = - (SqlUserDefinedTableMacro) operator; - final TranslatableTable table = - udf.getTable(typeFactory, callBinding.operands()); - final RelDataType rowType = table.getRowType(typeFactory); - RelOptTable relOptTable = RelOptTableImpl.create(null, rowType, table, - udf.getNameAsId().names); - RelNode converted = toRel(relOptTable); - bb.setRoot(converted, true); - return; - } - - Type elementType; - if (operator instanceof SqlUserDefinedTableFunction) { - SqlUserDefinedTableFunction udtf = (SqlUserDefinedTableFunction) operator; - elementType = udtf.getElementType(typeFactory, callBinding.operands()); - } else { - elementType = null; - } - - RexNode rexCall = bb.convertExpression(call); - final List<RelNode> inputs = bb.retrieveCursors(); - Set<RelColumnMapping> columnMappings = - getColumnMappings(operator); - LogicalTableFunctionScan callRel = - LogicalTableFunctionScan.create( - cluster, - inputs, - rexCall, - elementType, - validator.getValidatedNodeType(call), - columnMappings); - bb.setRoot(callRel, true); - afterTableFunction(bb, call, callRel); - } - - protected void afterTableFunction( - SqlToRelConverter.Blackboard bb, - SqlCall call, - LogicalTableFunctionScan callRel) { - } - - private Set<RelColumnMapping> getColumnMappings(SqlOperator op) { - SqlReturnTypeInference rti = op.getReturnTypeInference(); - if (rti == null) { - return null; - } - if (rti instanceof TableFunctionReturnTypeInference) { - TableFunctionReturnTypeInference tfrti = - (TableFunctionReturnTypeInference) rti; - return tfrti.getColumnMappings(); - } else { - return null; - } - } - - protected RelNode createJoin( - Blackboard bb, - RelNode leftRel, - RelNode rightRel, - RexNode joinCond, - JoinRelType joinType) { - assert joinCond != null; - - final CorrelationUse p = getCorrelationUse(bb, rightRel); - if (p != null) { - LogicalCorrelate corr = LogicalCorrelate.create(leftRel, p.r, - p.id, p.requiredColumns, SemiJoinType.of(joinType)); - if (!joinCond.isAlwaysTrue()) { - final RelFactories.FilterFactory factory = - RelFactories.DEFAULT_FILTER_FACTORY; - return factory.createFilter(corr, joinCond); - } - return corr; - } - - final Join originalJoin = - (Join) RelFactories.DEFAULT_JOIN_FACTORY.createJoin(leftRel, rightRel, - joinCond, ImmutableSet.<CorrelationId>of(), joinType, false); - - return RelOptUtil.pushDownJoinConditions(originalJoin); - } - - private CorrelationUse getCorrelationUse(Blackboard bb, final RelNode r0) { - final Set<CorrelationId> correlatedVariables = - RelOptUtil.getVariablesUsed(r0); - if (correlatedVariables.isEmpty()) { - return null; - } - final ImmutableBitSet.Builder requiredColumns = ImmutableBitSet.builder(); - final List<CorrelationId> correlNames = Lists.newArrayList(); - - // All correlations must refer the same namespace since correlation - // produces exactly one correlation source. - // The same source might be referenced by different variables since - // DeferredLookups are not de-duplicated at create time. - SqlValidatorNamespace prevNs = null; - - for (CorrelationId correlName : correlatedVariables) { - DeferredLookup lookup = - mapCorrelToDeferred.get(correlName); - RexFieldAccess fieldAccess = lookup.getFieldAccess(correlName); - String originalRelName = lookup.getOriginalRelName(); - String originalFieldName = fieldAccess.getField().getName(); - - final SqlNameMatcher nameMatcher = - lookup.bb.scope.getValidator().getCatalogReader().nameMatcher(); - final SqlValidatorScope.ResolvedImpl resolved = - new SqlValidatorScope.ResolvedImpl(); - lookup.bb.scope.resolve(ImmutableList.of(originalRelName), - nameMatcher, false, resolved); - assert resolved.count() == 1; - final SqlValidatorScope.Resolve resolve = resolved.only(); - final SqlValidatorNamespace foundNs = resolve.namespace; - final RelDataType rowType = resolve.rowType(); - final int childNamespaceIndex = resolve.path.steps().get(0).i; - final SqlValidatorScope ancestorScope = resolve.scope; - boolean correlInCurrentScope = ancestorScope == bb.scope; - - if (!correlInCurrentScope) { - continue; - } - - if (prevNs == null) { - prevNs = foundNs; - } else { - assert prevNs == foundNs : "All correlation variables should resolve" - + " to the same namespace." - + " Prev ns=" + prevNs - + ", new ns=" + foundNs; - } - - int namespaceOffset = 0; - if (childNamespaceIndex > 0) { - // If not the first child, need to figure out the width - // of output types from all the preceding namespaces - assert ancestorScope instanceof ListScope; - List<SqlValidatorNamespace> children = - ((ListScope) ancestorScope).getChildren(); - - for (int i = 0; i < childNamespaceIndex; i++) { - SqlValidatorNamespace child = children.get(i); - namespaceOffset += - child.getRowType().getFieldCount(); - } - } - - RexFieldAccess topLevelFieldAccess = fieldAccess; - while (topLevelFieldAccess.getReferenceExpr() instanceof RexFieldAccess) { - topLevelFieldAccess = (RexFieldAccess) topLevelFieldAccess.getReferenceExpr(); - } - final RelDataTypeField field = rowType.getFieldList() - .get(topLevelFieldAccess.getField().getIndex() - namespaceOffset); - int pos = namespaceOffset + field.getIndex(); - - assert field.getType() - == topLevelFieldAccess.getField().getType(); - - assert pos != -1; - - if (bb.mapRootRelToFieldProjection.containsKey(bb.root)) { - // bb.root is an aggregate and only projects group by - // keys. - Map<Integer, Integer> exprProjection = - bb.mapRootRelToFieldProjection.get(bb.root); - - // sub-query can reference group by keys projected from - // the root of the outer relation. - if (exprProjection.containsKey(pos)) { - pos = exprProjection.get(pos); - } else { - // correl not grouped - throw new AssertionError("Identifier '" + originalRelName + "." - + originalFieldName + "' is not a group expr"); - } - } - - requiredColumns.set(pos); - correlNames.add(correlName); - } - - if (correlNames.isEmpty()) { - // None of the correlating variables originated in this scope. - return null; - } - - RelNode r = r0; - if (correlNames.size() > 1) { - // The same table was referenced more than once. - // So we deduplicate - r = DeduplicateCorrelateVariables.go(rexBuilder, correlNames.get(0), - Util.skip(correlNames), r0); - } - return new CorrelationUse(correlNames.get(0), requiredColumns.build(), r); - } - - /** - * Determines whether a sub-query is non-correlated. Note that a - * non-correlated sub-query can contain correlated references, provided those - * references do not reference select statements that are parents of the - * sub-query. - * - * @param subq the sub-query - * @param bb blackboard used while converting the sub-query, i.e., the - * blackboard of the parent query of this sub-query - * @return true if the sub-query is non-correlated - */ - private boolean isSubQueryNonCorrelated(RelNode subq, Blackboard bb) { - Set<CorrelationId> correlatedVariables = RelOptUtil.getVariablesUsed(subq); - for (CorrelationId correlName : correlatedVariables) { - DeferredLookup lookup = mapCorrelToDeferred.get(correlName); - String originalRelName = lookup.getOriginalRelName(); - - final SqlNameMatcher nameMatcher = - lookup.bb.scope.getValidator().getCatalogReader().nameMatcher(); - final SqlValidatorScope.ResolvedImpl resolved = - new SqlValidatorScope.ResolvedImpl(); - lookup.bb.scope.resolve(ImmutableList.of(originalRelName), nameMatcher, - false, resolved); - - SqlValidatorScope ancestorScope = resolved.only().scope; - - // If the correlated reference is in a scope that's "above" the - // sub-query, then this is a correlated sub-query. - SqlValidatorScope parentScope = bb.scope; - do { - if (ancestorScope == parentScope) { - return false; - } - if (parentScope instanceof DelegatingScope) { - parentScope = ((DelegatingScope) parentScope).getParent(); - } else { - break; - } - } while (parentScope != null); - } - return true; - } - - /** - * Returns a list of fields to be prefixed to each relational expression. - * - * @return List of system fields - */ - protected List<RelDataTypeField> getSystemFields() { - return Collections.emptyList(); - } - - private RexNode convertJoinCondition(Blackboard bb, - SqlValidatorNamespace leftNamespace, - SqlValidatorNamespace rightNamespace, - SqlNode condition, - JoinConditionType conditionType, - RelNode leftRel, - RelNode rightRel) { - if (condition == null) { - return rexBuilder.makeLiteral(true); - } - bb.setRoot(ImmutableList.of(leftRel, rightRel)); - replaceSubQueries(bb, condition, RelOptUtil.Logic.UNKNOWN_AS_FALSE); - switch (conditionType) { - case ON: - bb.setRoot(ImmutableList.of(leftRel, rightRel)); - return bb.convertExpression(condition); - case USING: - final SqlNodeList list = (SqlNodeList) condition; - final List<String> nameList = new ArrayList<>(); - for (SqlNode columnName : list) { - final SqlIdentifier id = (SqlIdentifier) columnName; - String name = id.getSimple(); - nameList.add(name); - } - return convertUsing(leftNamespace, rightNamespace, nameList); - default: - throw Util.unexpected(conditionType); - } - } - - /** - * Returns an expression for matching columns of a USING clause or inferred - * from NATURAL JOIN. "a JOIN b USING (x, y)" becomes "a.x = b.x AND a.y = - * b.y". Returns null if the column list is empty. - * - * @param leftNamespace Namespace of left input to join - * @param rightNamespace Namespace of right input to join - * @param nameList List of column names to join on - * @return Expression to match columns from name list, or true if name list - * is empty - */ - private RexNode convertUsing(SqlValidatorNamespace leftNamespace, - SqlValidatorNamespace rightNamespace, - List<String> nameList) { - final SqlNameMatcher nameMatcher = catalogReader.nameMatcher(); - final List<RexNode> list = Lists.newArrayList(); - for (String name : nameList) { - List<RexNode> operands = new ArrayList<>(); - int offset = 0; - for (SqlValidatorNamespace n : ImmutableList.of(leftNamespace, - rightNamespace)) { - final RelDataType rowType = n.getRowType(); - final RelDataTypeField field = nameMatcher.field(rowType, name); - operands.add( - rexBuilder.makeInputRef(field.getType(), - offset + field.getIndex())); - offset += rowType.getFieldList().size(); - } - list.add(rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, operands)); - } - return RexUtil.composeConjunction(rexBuilder, list, false); - } - - private static JoinRelType convertJoinType(JoinType joinType) { - switch (joinType) { - case COMMA: - case INNER: - case CROSS: - return JoinRelType.INNER; - case FULL: - return JoinRelType.FULL; - case LEFT: - return JoinRelType.LEFT; - case RIGHT: - return JoinRelType.RIGHT; - default: - throw Util.unexpected(joinType); - } - } - - /** - * Converts the SELECT, GROUP BY and HAVING clauses of an aggregate query. - * - * <p>This method extracts SELECT, GROUP BY and HAVING clauses, and creates - * an {@link AggConverter}, then delegates to {@link #createAggImpl}. - * Derived class may override this method to change any of those clauses or - * specify a different {@link AggConverter}. - * - * @param bb Scope within which to resolve identifiers - * @param select Query - * @param orderExprList Additional expressions needed to implement ORDER BY - */ - protected void convertAgg( - Blackboard bb, - SqlSelect select, - List<SqlNode> orderExprList) { - assert bb.root != null : "precondition: child != null"; - SqlNodeList groupList = select.getGroup(); - SqlNodeList selectList = select.getSelectList(); - SqlNode having = select.getHaving(); - - final AggConverter aggConverter = new AggConverter(bb, select); - createAggImpl( - bb, - aggConverter, - selectList, - groupList, - having, - orderExprList); - } - - protected final void createAggImpl( - Blackboard bb, - final AggConverter aggConverter, - SqlNodeList selectList, - SqlNodeList groupList, - SqlNode having, - List<SqlNode> orderExprList) { - // Find aggregate functions in SELECT and HAVING clause - final AggregateFinder aggregateFinder = new AggregateFinder(); - selectList.accept(aggregateFinder); - if (having != null) { - having.accept(aggregateFinder); - } - - // first replace the sub-queries inside the aggregates - // because they will provide input rows to the aggregates. - replaceSubQueries(bb, aggregateFinder.list, - RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); - - // If group-by clause is missing, pretend that it has zero elements. - if (groupList == null) { - groupList = SqlNodeList.EMPTY; - } - - replaceSubQueries(bb, groupList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); - - // register the group e
<TRUNCATED>