From dfb5fac9f0d9c13f1128206b73e683b515fe3d72 Mon Sep 17 00:00:00 2001 From: Billy Liu Date: Thu, 19 Jan 2017 14:54:20 +0800 Subject: [PATCH 1/2] KYLIN-2394 Upgrade Calcite to 1.11 and Avatica to 1.9.0 --- atopcalcite/pom.xml | 11 +- .../apache/calcite/sql2rel/SqlToRelConverter.java | 3941 ++++++++++++-------- jdbc/pom.xml | 12 +- .../main/java/org/apache/kylin/jdbc/KylinMeta.java | 2 +- kylin-it/pom.xml | 14 +- .../org/apache/kylin/jdbc/ITJDBCDriverTest.java | 1 + pom.xml | 10 +- query/pom.xml | 10 +- 8 files changed, 2430 insertions(+), 1571 deletions(-) diff --git a/atopcalcite/pom.xml b/atopcalcite/pom.xml index b916df2..1b327fe 100644 --- a/atopcalcite/pom.xml +++ b/atopcalcite/pom.xml @@ -36,7 +36,16 @@ org.apache.calcite calcite-core + + + org.apache.calcite.avatica + avatica-core + + + + + org.apache.calcite.avatica + avatica - diff --git a/atopcalcite/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/atopcalcite/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java index e0782ce..171e299 100644 --- a/atopcalcite/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/atopcalcite/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -16,24 +16,6 @@ */ package org.apache.calcite.sql2rel; -import static org.apache.calcite.sql.SqlUtil.stripAs; -import static org.apache.calcite.util.Static.RESOURCE; - -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.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - import org.apache.calcite.avatica.util.Spaces; import org.apache.calcite.linq4j.Ord; import org.apache.calcite.plan.Convention; @@ -85,7 +67,6 @@ 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.RelDataTypeFactory.FieldInfoBuilder; import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexCall; @@ -116,6 +97,7 @@ 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; @@ -177,17 +159,37 @@ 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 org.slf4j.Logger; import com.google.common.base.Function; import com.google.common.base.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 com.google.common.collect.Sets; + +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.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; /* * The code has synced with calcite. Hope one day, we could remove the hardcode override point. @@ -209,42 +211,56 @@ import com.google.common.collect.Sets; 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_SUBQUERY_THRESHOLD = 20; - protected static final Logger SQL2REL_LOGGER = CalciteTrace.getSqlToRelTracer(); - private static final BigDecimal TWO = BigDecimal.valueOf(2L); + 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 -------------------------------------------------------- - public final SqlToRelConverter.Config config; - public final RelOptTable.ViewExpander viewExpander; + protected final SqlValidator validator; protected final RexBuilder rexBuilder; protected final Prepare.CatalogReader catalogReader; protected final RelOptCluster cluster; + private DefaultValueFactory defaultValueFactory; + private SubQueryConverter subQueryConverter; protected final List leaves = new ArrayList<>(); - protected final RelDataTypeFactory typeFactory; private final List 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 subqueries. + * Fields used in name resolution for correlated sub-queries. */ - private final Map mapCorrelToDeferred = new HashMap<>(); + private final Map mapCorrelToDeferred = + new HashMap<>(); + /** * Stack of names of datasets requested by the * TABLE(SAMPLE(<datasetName>, <query>)) construct. */ private final Deque datasetStack = new ArrayDeque<>(); + /** - * Mapping of non-correlated subqueries that have been converted to their - * equivalent constants. Used to avoid re-evaluating the subquery if it's + * 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 mapConvertedNonCorrSubqs = new HashMap<>(); - private DefaultValueFactory defaultValueFactory; - private SubqueryConverter subqueryConverter; - private int explainParamCount; + private final Map mapConvertedNonCorrSubqs = + new HashMap<>(); + + public final RelOptTable.ViewExpander viewExpander; //~ Constructors ----------------------------------------------------------- /** @@ -258,23 +274,46 @@ public class SqlToRelConverter { * @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); + 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); + 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) { + 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.opTab = + (validator + == null) ? SqlStdOperatorTable.instance() + : validator.getOperatorTable(); this.validator = validator; this.catalogReader = catalogReader; this.defaultValueFactory = new NullDefaultValueFactory(); - this.subqueryConverter = new NoOpSubqueryConverter(); + this.subQueryConverter = new NoOpSubQueryConverter(); this.rexBuilder = cluster.getRexBuilder(); this.typeFactory = rexBuilder.getTypeFactory(); this.cluster = Preconditions.checkNotNull(cluster); @@ -285,157 +324,6 @@ public class SqlToRelConverter { //~ Methods ---------------------------------------------------------------- - 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; - } - } - - /** - * Returns whether a given node contains a {@link SqlInOperator}. - * - * @param node a RexNode tree - */ - private static boolean containsInOperator(SqlNode node) { - try { - SqlVisitor visitor = new SqlBasicVisitor() { - 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 sqlNode the root node from which to look for NOT operators - * @return the transformed SqlNode representation with NOT pushed down. - */ - private static SqlNode pushDownNotForIn(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(sqlOperands[i]); - } - return 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] = SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, andOperands[i]); - } - for (int i = 0; i < orOperands.length; i++) { - orOperands[i] = pushDownNotForIn(orOperands[i]); - } - return 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] = SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, orOperands[i]); - } - for (int i = 0; i < andOperands.length; i++) { - andOperands[i] = pushDownNotForIn(andOperands[i]); - } - return 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(notOperands[0]); - } else if (childSqlCall.getOperator() instanceof SqlInOperator) { - SqlNode[] inOperands = childSqlCall.getOperands(); - SqlInOperator inOp = (SqlInOperator) childSqlCall.getOperator(); - if (inOp.isNotIn()) { - return SqlStdOperatorTable.IN.createCall(SqlParserPos.ZERO, inOperands[0], inOperands[1]); - } else { - return 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; - } - } - - 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; - } - - 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); - } - } - - private static boolean desc(RelFieldCollation.Direction direction) { - switch (direction) { - case DESCENDING: - case STRICTLY_DESCENDING: - return true; - default: - return false; - } - } - - /** Creates a builder for a {@link Config}. */ - public static ConfigBuilder configBuilder() { - return new ConfigBuilder(); - } - /** * @return the RelOptCluster in use. */ @@ -490,7 +378,7 @@ public class SqlToRelConverter { } /** - * @return mapping of non-correlated subqueries that have been converted to + * @return mapping of non-correlated sub-queries that have been converted to * the constants that they evaluate to */ public Map getMapConvertedNonCorrSubqs() { @@ -498,13 +386,14 @@ public class SqlToRelConverter { } /** - * Adds to the current map of non-correlated converted subqueries the - * elements from another map that contains non-correlated subqueries that + * 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 alreadyConvertedNonCorrSubqs) { + public void addConvertedNonCorrSubqs( + Map alreadyConvertedNonCorrSubqs) { mapConvertedNonCorrSubqs.putAll(alreadyConvertedNonCorrSubqs); } @@ -519,13 +408,13 @@ public class SqlToRelConverter { } /** - * Sets a new SubqueryConverter. To have any effect, this must be called + * Sets a new SubQueryConverter. To have any effect, this must be called * before any convert method. * - * @param converter new SubqueryConverter + * @param converter new SubQueryConverter */ - public void setSubqueryConverter(SubqueryConverter converter) { - subqueryConverter = converter; + public void setSubQueryConverter(SubQueryConverter converter) { + subQueryConverter = converter; } /** @@ -548,24 +437,42 @@ public class SqlToRelConverter { // SQL statement is something like an INSERT which has no // validator type information associated with its result, // hence the namespace check above.) - final List validatedFields = validator.getValidatedNodeType(query).getFieldList(); - final RelDataType validatedRowType = validator.getTypeFactory().createStructType(Pair.right(validatedFields), SqlValidatorUtil.uniquify(Pair.left(validatedFields), catalogReader.isCaseSensitive())); - - final List 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)); + final List validatedFields = + validator.getValidatedNodeType(query).getFieldList(); + final RelDataType validatedRowType = + validator.getTypeFactory().createStructType( + Pair.right(validatedFields), + SqlValidatorUtil.uniquify(Pair.left(validatedFields), + catalogReader.isCaseSensitive())); + + final List 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()); - return typeFlattener.rewrite(rootRel, restructure); + public RelNode flattenTypes( + RelNode rootRel, + boolean restructure) { + RelStructuredTypeFlattener typeFlattener = + new RelStructuredTypeFlattener(rexBuilder, createToRelContext(), restructure); + return typeFlattener.rewrite(rootRel); } /** - * If subquery is correlated and decorrelation is enabled, performs + * If sub-query is correlated and decorrelation is enabled, performs * decorrelation. * * @param query Query @@ -606,14 +513,21 @@ public class SqlToRelConverter { // Trim fields that are not used by their consumer. if (isTrimUnusedFields()) { final RelFieldTrimmer trimmer = newFieldTrimmer(); - final List collations = rootRel.getTraitSet().getTraits(RelCollationTraitDef.INSTANCE); + final List 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); + 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, false, SqlExplainLevel.EXPPLAN_ATTRIBUTES)); + SQL2REL_LOGGER.debug( + RelOptUtil.dumpPlan("Plan after trimming unused fields", rootRel, + SqlExplainFormat.TEXT, SqlExplainLevel.EXPPLAN_ATTRIBUTES)); } } return rootRel; @@ -625,7 +539,8 @@ public class SqlToRelConverter { * @return Field trimmer */ protected RelFieldTrimmer newFieldTrimmer() { - final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(cluster, null); + final RelBuilder relBuilder = + RelFactories.LOGICAL_BUILDER.create(cluster, null); return new RelFieldTrimmer(validator, relBuilder); } @@ -640,14 +555,18 @@ public class SqlToRelConverter { * will become a JDBC result set; false if * the query will be part of a view. */ - public RelRoot convertQuery(SqlNode query, final boolean needsValidation, final boolean top) { + public RelRoot convertQuery( + SqlNode query, + final boolean needsValidation, + final boolean top) { SqlNode origQuery = query; /* OVERRIDE POINT */ - + if (needsValidation) { query = validator.validate(query); } - RelMetadataQuery.THREAD_PROVIDERS.set(JaninoRelMetadataProvider.of(cluster.getMetadataProvider())); + RelMetadataQuery.THREAD_PROVIDERS.set( + JaninoRelMetadataProvider.of(cluster.getMetadataProvider())); RelNode result = convertQueryRecursive(query, top, null).rel; if (top) { if (isStream(query)) { @@ -663,18 +582,23 @@ public class SqlToRelConverter { checkConvertedType(query, result); if (SQL2REL_LOGGER.isDebugEnabled()) { - SQL2REL_LOGGER.debug(RelOptUtil.dumpPlan("Plan after converting SqlNode to RelNode", result, false, SqlExplainLevel.EXPPLAN_ATTRIBUTES)); + SQL2REL_LOGGER.debug( + RelOptUtil.dumpPlan("Plan after converting SqlNode to RelNode", + result, SqlExplainFormat.TEXT, + SqlExplainLevel.EXPPLAN_ATTRIBUTES)); } final RelDataType validatedRowType = validator.getValidatedNodeType(query); - return hackSelectStar(origQuery, RelRoot.of(result, validatedRowType, query.getKind()).withCollation(collation)); + RelRoot origResult = RelRoot.of(result, validatedRowType, query.getKind()) + .withCollation(collation); + return hackSelectStar(origQuery, origResult); } /* OVERRIDE POINT */ private RelRoot hackSelectStar(SqlNode query, RelRoot root) { /* * Rel tree is like: - * + * * LogicalSort (optional) * |- LogicalProject * |- LogicalFilter (optional) @@ -690,7 +614,7 @@ public class SqlToRelConverter { } else { return root; } - + RelNode input = rootPrj.getInput(); if (!(// input.getClass().getSimpleName().equals("OLAPTableScan")// @@ -700,13 +624,13 @@ public class SqlToRelConverter { if (rootPrj.getRowType().getFieldCount() < input.getRowType().getFieldCount()) return root; - + RelDataType inType = rootPrj.getRowType(); List inFields = inType.getFieldNames(); List projExp = new ArrayList<>(); List> projFields = new ArrayList<>(); - FieldInfoBuilder projTypeBuilder = getCluster().getTypeFactory().builder(); - FieldInfoBuilder validTypeBuilder = getCluster().getTypeFactory().builder(); + RelDataTypeFactory.FieldInfoBuilder projTypeBuilder = getCluster().getTypeFactory().builder(); + RelDataTypeFactory.FieldInfoBuilder validTypeBuilder = getCluster().getTypeFactory().builder(); for (int i = 0; i < inFields.size(); i++) { if (!inFields.get(i).startsWith("_KY_")) { projExp.add(rootPrj.getProjects().get(i)); @@ -721,15 +645,34 @@ public class SqlToRelConverter { if (rootSort != null) { rootSort = (LogicalSort) rootSort.copy(rootSort.getTraitSet(), rootPrj, rootSort.collation, rootSort.offset, rootSort.fetch); } - + RelDataType validRowType = getCluster().getTypeFactory().createStructType(validTypeBuilder); root = new RelRoot(rootSort == null ? rootPrj : rootSort, validRowType, root.kind, projFields, root.collation); - + validator.setValidatedNodeType(query, validRowType); - + return root; } + 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; @@ -756,7 +699,8 @@ public class SqlToRelConverter { /** * Factory method for creating translation workspace. */ - protected Blackboard createBlackboard(SqlValidatorScope scope, Map nameToNodeMap, boolean top) { + protected Blackboard createBlackboard(SqlValidatorScope scope, + Map nameToNodeMap, boolean top) { return new Blackboard(scope, nameToNodeMap, top); } @@ -764,25 +708,45 @@ public class SqlToRelConverter { * 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()); + protected void convertSelectImpl( + final Blackboard bb, + SqlSelect select) { + convertFrom( + bb, + select.getFrom()); + convertWhere( + bb, + select.getWhere()); final List orderExprList = new ArrayList<>(); final List collationList = new ArrayList<>(); - gatherOrderExprs(bb, select, select.getOrderList(), orderExprList, collationList); - final RelCollation collation = cluster.traitSet().canonize(RelCollations.of(collationList)); + gatherOrderExprs( + bb, + select, + select.getOrderList(), + orderExprList, + collationList); + final RelCollation collation = + cluster.traitSet().canonize(RelCollations.of(collationList)); if (validator.isAggregate(select)) { - convertAgg(bb, select, orderExprList); + convertAgg( + bb, + select, + orderExprList); } else { - convertSelectList(bb, select, orderExprList); + convertSelectList( + bb, + select, + orderExprList); } if (select.isDistinct()) { distinctify(bb, true); } - convertOrder(select, bb, collation, orderExprList, select.getOffset(), select.getFetch()); + convertOrder( + select, bb, collation, orderExprList, select.getOffset(), + select.getFetch()); bb.setRoot(bb.root, true); } @@ -798,7 +762,9 @@ public class SqlToRelConverter { * @param bb Blackboard * @param checkForDupExprs Check for duplicate expressions */ - private void distinctify(Blackboard bb, boolean checkForDupExprs) { + 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]} @@ -832,7 +798,9 @@ public class SqlToRelConverter { newProjects.add(RexInputRef.of2(i, fields)); } } - rel = LogicalProject.create(rel, Pair.left(newProjects), Pair.right(newProjects)); + rel = + LogicalProject.create(rel, Pair.left(newProjects), + Pair.right(newProjects)); bb.root = rel; distinctify(bb, false); rel = bb.root; @@ -843,21 +811,34 @@ public class SqlToRelConverter { 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())); + 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); + 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. of()); - - bb.setRoot(rel, false); + final ImmutableBitSet groupSet = + ImmutableBitSet.range(rel.getRowType().getFieldCount()); + rel = + createAggregate(bb, false, groupSet, ImmutableList.of(groupSet), + ImmutableList.of()); + + bb.setRoot( + rel, + false); } private int findExpr(RexNode seek, List exprs, int count) { @@ -882,16 +863,29 @@ public class SqlToRelConverter { * returning first row * @param fetch Expression for number of rows to fetch */ - protected void convertOrder(SqlSelect select, Blackboard bb, RelCollation collation, List orderExprList, SqlNode offset, SqlNode fetch) { - if (select.getOrderList() == null || select.getOrderList().getList().isEmpty()) { + protected void convertOrder( + SqlSelect select, + Blackboard bb, + RelCollation collation, + List 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) { + 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); + 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 @@ -901,300 +895,492 @@ public class SqlToRelConverter { if (orderExprList.size() > 0 && !bb.top) { final List exprs = new ArrayList<>(); final RelDataType rowType = bb.root.getRowType(); - final int fieldCount = rowType.getFieldCount() - orderExprList.size(); + 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); + bb.setRoot( + LogicalProject.create(bb.root, exprs, + rowType.getFieldNames().subList(0, fieldCount)), + false); } } /** - * Converts a WHERE clause. + * Returns whether a given node contains a {@link SqlInOperator}. * - * @param bb Blackboard - * @param where WHERE clause, may be null + * @param node a RexNode tree */ - private void convertWhere(final Blackboard bb, final SqlNode where) { - if (where == null) { - return; - } - SqlNode newWhere = pushDownNotForIn(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 RelNode filter = RelOptUtil.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 static boolean containsInOperator( + SqlNode node) { + try { + SqlVisitor visitor = + new SqlBasicVisitor() { + 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; } } - 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 Pair 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 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 isNotIn = ((SqlInOperator) call.getOperator()).isNotIn(); - if (query instanceof SqlNodeList) { - SqlNodeList valueList = (SqlNodeList) query; - if (!containsNullLiteral(valueList) && valueList.size() < getInSubqueryThreshold()) { - // We're under the threshold, so convert to OR. - subQuery.expr = convertInToOr(bb, leftKeys, valueList, isNotIn); - 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 boolean outerJoin = bb.subqueryNeedsOuterJoin || isNotIn || subQuery.logic == RelOptUtil.Logic.TRUE_FALSE_UNKNOWN; - final RelDataType targetRowType = SqlTypeUtil.promoteToRowType(typeFactory, validator.getValidatedNodeType(leftKeyNode), null); - converted = convertExists(query, RelOptUtil.SubqueryType.IN, subQuery.logic, outerJoin, targetRowType); - if (converted.right) { - // 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.left.getInput(0); // fragile - final int keyCount = leftKeys.size(); - final List args = ImmutableIntList.range(0, keyCount); - LogicalAggregate aggregate = LogicalAggregate.create(seek, false, ImmutableBitSet.of(), null, ImmutableList.of(AggregateCall.create(SqlStdOperatorTable.COUNT, false, ImmutableList. of(), -1, longType, null), AggregateCall.create(SqlStdOperatorTable.COUNT, false, args, -1, longType, null))); - LogicalJoin join = LogicalJoin.create(bb.root, aggregate, rexBuilder.makeLiteral(true), ImmutableSet. of(), JoinRelType.INNER); - bb.setRoot(join, false); - } - RexNode rex = bb.register(converted.left, outerJoin ? JoinRelType.LEFT : JoinRelType.INNER, leftKeys); - - subQuery.expr = translateIn(subQuery, bb.root, rex); - if (isNotIn) { - subQuery.expr = rexBuilder.makeCall(SqlStdOperatorTable.NOT, subQuery.expr); + /** + * Push down all the NOT logical operators into any IN/NOT IN operators. + * + * @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(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(sqlOperands[i]); + } + return 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] = + SqlStdOperatorTable.NOT.createCall( + SqlParserPos.ZERO, + andOperands[i]); + } + for (int i = 0; i < orOperands.length; i++) { + orOperands[i] = pushDownNotForIn(orOperands[i]); + } + return 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] = + SqlStdOperatorTable.NOT.createCall( + SqlParserPos.ZERO, + orOperands[i]); + } + for (int i = 0; i < andOperands.length; i++) { + andOperands[i] = pushDownNotForIn(andOperands[i]); + } + return 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(notOperands[0]); + } else if (childSqlCall.getOperator() instanceof SqlInOperator) { + SqlNode[] inOperands = childSqlCall.getOperands(); + SqlInOperator inOp = + (SqlInOperator) childSqlCall.getOperator(); + if (inOp.isNotIn()) { + return SqlStdOperatorTable.IN.createCall( + SqlParserPos.ZERO, + inOperands[0], + inOperands[1]); + } else { + return 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; + } + } + + /** + * 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(where); + replaceSubQueries(bb, newWhere, RelOptUtil.Logic.UNKNOWN_AS_FALSE); + final RexNode convertedWhere = bb.convertExpression(newWhere); - case EXISTS: - // "select from emp where exists (select a from T)" - // - // is converted to the following if the subquery 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 subquery returned 0 or >= 1 row. - call = (SqlBasicCall) subQuery.node; - query = call.operand(0); - if (!config.isExpand()) { + // 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; - } - converted = convertExists(query, RelOptUtil.SubqueryType.EXISTS, subQuery.logic, true, null); - assert !converted.right; - if (convertNonCorrelatedSubQuery(subQuery, bb, converted.left, true)) { + + 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; - } - subQuery.expr = bb.register(converted.left, JoinRelType.LEFT); - return; - case SCALAR_QUERY: - // Convert the subquery. If it's non-correlated, convert it - // to a constant expression. - if (!config.isExpand()) { + 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 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 args = ImmutableIntList.range(0, keyCount); + LogicalAggregate aggregate = + LogicalAggregate.create(seek, false, ImmutableBitSet.of(), null, + ImmutableList.of( + AggregateCall.create(SqlStdOperatorTable.COUNT, false, + ImmutableList.of(), -1, longType, null), + AggregateCall.create(SqlStdOperatorTable.COUNT, false, + args, -1, longType, null))); + LogicalJoin join = + LogicalJoin.create(bb.root, aggregate, rexBuilder.makeLiteral(true), + ImmutableSet.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; - } - call = (SqlBasicCall) subQuery.node; - query = call.operand(0); - converted = convertExists(query, RelOptUtil.SubqueryType.SCALAR, subQuery.logic, true, null); - assert !converted.right; - if (convertNonCorrelatedSubQuery(subQuery, bb, converted.left, false)) { + + 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; - } - rel = convertToSingleValueSubq(query, converted.left); - 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.right; - subQuery.expr = bb.register(converted.left, 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 Util.newInternal("unexpected kind of subquery :" + subQuery.node); + default: + throw Util.newInternal("unexpected kind of sub-query :" + subQuery.node); } } - private RexNode translateIn(SubQuery subQuery, RelNode root, final RexNode rex) { - switch (subQuery.logic) { - case TRUE: - return rexBuilder.makeLiteral(true); + 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(SqlTypeName.BOOLEAN); + + final ImmutableList.Builder 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); - 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 subquery 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 nullableBooleanType = typeFactory.createTypeWithNullability(typeFactory.createSqlType(SqlTypeName.BOOLEAN), true); - 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(SqlTypeName.BOOLEAN); - - final ImmutableList.Builder 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()); - return rexBuilder.makeCall(nullableBooleanType, SqlStdOperatorTable.CASE, args.build()); + default: + throw new AssertionError(logic); + } + } - default: - throw new AssertionError(subQuery.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 subquery is non-correlated and if so, converts it to a + * Determines if a sub-query is non-correlated and if so, converts it to a * constant. * - * @param subQuery the call that references the subquery - * @param bb blackboard used to convert the subquery - * @param converted RelNode tree corresponding to the subquery - * @param isExists true if the subquery is part of an EXISTS expression - * @return if the subquery can be converted 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) { + 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 subquery has already been converted - // because it's a nested subquery. If so, don't re-evaluate + 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()); + constExpr = + subQueryConverter.convertSubQuery( + call, + this, + isExists, + config.isExplain()); } if (constExpr != null) { subQuery.expr = constExpr; @@ -1213,14 +1399,17 @@ public class SqlToRelConverter { * @param plan the original RelNode tree corresponding to the statement * @return the converted RelNode tree */ - public RelNode convertToSingleValueSubq(SqlNode query, RelNode plan) { + 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))) { + if ((selectList.size() == 1) + && ((groupList == null) || (groupList.size() == 0))) { SqlNode selectExpr = selectList.get(0); if (selectExpr instanceof SqlCall) { SqlCall selectExprCall = (SqlCall) selectExpr; @@ -1231,7 +1420,8 @@ public class SqlToRelConverter { // 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) { + if (select.getFetch() != null + && select.getFetch() instanceof SqlNumericLiteral) { SqlNumericLiteral limitNum = (SqlNumericLiteral) select.getFetch(); if (((BigDecimal) limitNum.getValue()).intValue() < 2) { return plan; @@ -1243,13 +1433,17 @@ public class SqlToRelConverter { // 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)) { + if (exprCall.getOperator() + instanceof SqlValuesOperator + && Util.isSingleValue(exprCall)) { return plan; } } // If not, project SingleValueAgg - return RelOptUtil.createSingleValueAggRel(cluster, plan); + return RelOptUtil.createSingleValueAggRel( + cluster, + plan); } /** @@ -1260,30 +1454,52 @@ public class SqlToRelConverter { * @param isNotIn is this a NOT IN operator * @return converted expression */ - private RexNode convertInToOr(final Blackboard bb, final List leftKeys, SqlNodeList valuesList, boolean isNotIn) { + private RexNode convertInToOr( + final Blackboard bb, + final List leftKeys, + SqlNodeList valuesList, + boolean isNotIn) { final List 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))); + 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, RexNode>() { - public RexNode apply(Pair pair) { - return rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, pair.left, ensureSqlType(pair.left.getType(), bb.convertExpression(pair.right))); - } - }), false); + assert (call.getOperator() instanceof SqlRowOperator) + && call.operandCount() == leftKeys.size(); + rexComparison = + RexUtil.composeConjunction( + rexBuilder, + Iterables.transform( + Pair.zip(leftKeys, call.getOperandList()), + new Function, RexNode>() { + public RexNode apply(Pair 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); + RexNode result = + RexUtil.composeDisjunction(rexBuilder, comparisons, true); assert result != null; if (isNotIn) { - result = rexBuilder.makeCall(SqlStdOperatorTable.NOT, result); + result = + rexBuilder.makeCall( + SqlStdOperatorTable.NOT, + result); } return result; @@ -1293,7 +1509,9 @@ public class SqlToRelConverter { * 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)) { + if (type.getSqlTypeName() == node.getType().getSqlTypeName() + || (type.getSqlTypeName() == SqlTypeName.VARCHAR + && node.getType().getSqlTypeName() == SqlTypeName.CHAR)) { return node; } return rexBuilder.ensureType(type, node, true); @@ -1307,17 +1525,17 @@ public class SqlToRelConverter { * 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_SUBQUERY_THRESHOLD} + * @return threshold, default {@link #DEFAULT_IN_SUB_QUERY_THRESHOLD} */ @Deprecated // to be removed before 2.0 protected int getInSubqueryThreshold() { - //return config.getInSubqueryThreshold(); - /* OVERRIDE POINT */ + //return config.getInSubQueryThreshold(); + /* OVERRIDE POINT */ return Integer.MAX_VALUE; } /** - * Converts an EXISTS or IN predicate into a join. For EXISTS, the subquery + * 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, @@ -1325,23 +1543,34 @@ public class SqlToRelConverter { * * @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 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 needsOuterJoin Whether an outer join is needed + * @param notIn Whether the operation is NOT IN * @return join expression * @pre extraExpr == null || extraName != null */ - private Pair convertExists(SqlNode seek, RelOptUtil.SubqueryType subqueryType, RelOptUtil.Logic logic, boolean needsOuterJoin, RelDataType targetDataType) { - final SqlValidatorScope seekScope = (seek instanceof SqlSelect) ? validator.getSelectScope((SqlSelect) seek) : null; + 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, needsOuterJoin); + return RelOptUtil.createExistsPlan(seekRel, subQueryType, logic, notIn); } - private RelNode convertQueryOrInList(Blackboard bb, SqlNode seek, RelDataType targetRowType) { + 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 @@ -1349,25 +1578,40 @@ public class SqlToRelConverter { // 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); + return convertRowValues( + bb, + seek, + ((SqlNodeList) seek).getList(), + false, + targetRowType); } else { return convertQueryRecursive(seek, false, null).project(); } } - private RelNode convertRowValues(Blackboard bb, SqlNode rowList, Collection rows, boolean allowLiteralsOnly, RelDataType targetRowType) { + private RelNode convertRowValues( + Blackboard bb, + SqlNode rowList, + Collection 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 - // subqueries), we union each row in as a projection on top of a + // sub-queries), we union each row in as a projection on top of a // LogicalOneRow. - final ImmutableList.Builder> tupleList = ImmutableList.builder(); + final ImmutableList.Builder> tupleList = + ImmutableList.builder(); final RelDataType rowType; if (targetRowType != null) { rowType = targetRowType; } else { - rowType = SqlTypeUtil.promoteToRowType(typeFactory, validator.getValidatedNodeType(rowList), null); + rowType = + SqlTypeUtil.promoteToRowType( + typeFactory, + validator.getValidatedNodeType(rowList), + null); } final List unionInputs = new ArrayList<>(); @@ -1377,7 +1621,12 @@ public class SqlToRelConverter { call = (SqlBasicCall) node; ImmutableList.Builder tuple = ImmutableList.builder(); for (Ord operand : Ord.zip(call.operands)) { - RexLiteral rexLiteral = convertLiteralInValuesList(operand.e, bb, rowType, operand.i); + RexLiteral rexLiteral = + convertLiteralInValuesList( + operand.e, + bb, + rowType, + operand.i); if ((rexLiteral == null) && allowLiteralsOnly) { return null; } @@ -1393,7 +1642,12 @@ public class SqlToRelConverter { continue; } } else { - RexLiteral rexLiteral = convertLiteralInValuesList(node, bb, rowType, 0); + RexLiteral rexLiteral = + convertLiteralInValuesList( + node, + bb, + rowType, + 0); if ((rexLiteral != null) && config.isCreateValuesRel()) { tupleList.add(ImmutableList.of(rexLiteral)); continue; @@ -1404,11 +1658,15 @@ public class SqlToRelConverter { } // convert "1" to "row(1)" - call = (SqlBasicCall) SqlStdOperatorTable.ROW.createCall(SqlParserPos.ZERO, node); + call = + (SqlBasicCall) SqlStdOperatorTable.ROW.createCall( + SqlParserPos.ZERO, + node); } unionInputs.add(convertRowConstructor(bb, call)); } - LogicalValues values = LogicalValues.create(cluster, rowType, tupleList.build()); + LogicalValues values = + LogicalValues.create(cluster, rowType, tupleList.build()); RelNode resultRel; if (unionInputs.isEmpty()) { resultRel = values; @@ -1422,7 +1680,11 @@ public class SqlToRelConverter { return resultRel; } - private RexLiteral convertLiteralInValuesList(SqlNode sqlNode, Blackboard bb, RelDataType rowType, int iField) { + private RexLiteral convertLiteralInValuesList( + SqlNode sqlNode, + Blackboard bb, + RelDataType rowType, + int iField) { if (!(sqlNode instanceof SqlLiteral)) { return null; } @@ -1435,7 +1697,10 @@ public class SqlToRelConverter { return null; } - RexNode literalExpr = exprConverter.convertLiteral(bb, (SqlLiteral) sqlNode); + RexNode literalExpr = + exprConverter.convertLiteral( + bb, + (SqlLiteral) sqlNode); if (!(literalExpr instanceof RexLiteral)) { assert literalExpr.isA(SqlKind.CAST); @@ -1452,14 +1717,24 @@ public class SqlToRelConverter { Comparable value = literal.getValue(); if (SqlTypeUtil.isExactNumeric(type) && SqlTypeUtil.hasScale(type)) { - BigDecimal roundedValue = NumberUtil.rescaleBigDecimal((BigDecimal) value, type.getScale()); - return rexBuilder.makeExactLiteral(roundedValue, type); + BigDecimal roundedValue = + NumberUtil.rescaleBigDecimal( + (BigDecimal) value, + type.getScale()); + return rexBuilder.makeExactLiteral( + roundedValue, + type); } - if ((value instanceof NlsString) && (type.getSqlTypeName() == SqlTypeName.CHAR)) { + 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 rexBuilder.makeCharLiteral( + new NlsString( + Spaces.padRight(unpadded.getValue(), type.getPrecision()), + unpadded.getCharsetName(), + unpadded.getCollation())); } return literal; } @@ -1481,67 +1756,78 @@ public class SqlToRelConverter { * @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 + * @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 - * subquery + * sub-query */ - private void findSubqueries(Blackboard bb, SqlNode node, RelOptUtil.Logic logic, boolean registerOnlyScalarSubqueries) { + 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) { + 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; - case NOT: - logic = logic.negate(); - break; + break; } if (node instanceof SqlCall) { - if (kind == SqlKind.OR || kind == SqlKind.NOT) { - // It's always correct to outer join subquery with - // containing query; however, when predicates involve Or - // or NOT, outer join might be necessary. - bb.subqueryNeedsOuterJoin = true; - } for (SqlNode operand : ((SqlCall) node).getOperandList()) { if (operand != null) { // In the case of an IN expression, locate scalar - // subqueries so we can convert them to constants - findSubqueries(bb, operand, logic, kind == SqlKind.IN || registerOnlyScalarSubqueries); + // 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); + findSubQueries( + bb, + child, + logic, + kind == SqlKind.IN || registerOnlyScalarSubQueries); } } - // Now that we've located any scalar subqueries inside the IN + // Now that we've located any scalar sub-queries inside the IN // expression, register the IN expression itself. We need to - // register the scalar subqueries first so they can be converted + // register the scalar sub-queries first so they can be converted // before the IN expression is converted. if (kind == SqlKind.IN) { - if (logic == RelOptUtil.Logic.TRUE_FALSE_UNKNOWN && !validator.getValidatedNodeType(node).isNullable()) { - logic = RelOptUtil.Logic.UNKNOWN_AS_FALSE; - } - // TODO: This conversion is only valid in the WHERE clause - if (logic == RelOptUtil.Logic.UNKNOWN_AS_FALSE && !bb.subqueryNeedsOuterJoin) { - logic = RelOptUtil.Logic.TRUE; + 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); + bb.registerSubQuery(node, logic); } } @@ -1551,9 +1837,11 @@ public class SqlToRelConverter { * @param node Expression to translate * @return Converted expression */ - public RexNode convertExpression(SqlNode node) { + public RexNode convertExpression( + SqlNode node) { Map nameToTypeMap = Collections.emptyMap(); - final ParameterScope scope = new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap); + final ParameterScope scope = + new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap); final Blackboard bb = createBlackboard(scope, null, false); return bb.convertExpression(node); } @@ -1569,12 +1857,15 @@ public class SqlToRelConverter { * this map * @return Converted expression */ - public RexNode convertExpression(SqlNode node, Map nameToNodeMap) { + public RexNode convertExpression( + SqlNode node, + Map nameToNodeMap) { final Map nameToTypeMap = new HashMap<>(); for (Map.Entry entry : nameToNodeMap.entrySet()) { nameToTypeMap.put(entry.getKey(), entry.getValue().getType()); } - final ParameterScope scope = new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap); + final ParameterScope scope = + new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap); final Blackboard bb = createBlackboard(scope, nameToNodeMap, false); return bb.convertExpression(node); } @@ -1590,7 +1881,9 @@ public class SqlToRelConverter { * @param bb Blackboard * @return null to proceed with the usual expression translation process */ - protected RexNode convertExtendedExpression(SqlNode node, Blackboard bb) { + protected RexNode convertExtendedExpression( + SqlNode node, + Blackboard bb) { return null; } @@ -1598,7 +1891,8 @@ public class SqlToRelConverter { SqlCall call = (SqlCall) node; SqlCall aggCall = call.operand(0); SqlNode windowOrRef = call.operand(1); - final SqlWindow window = validator.resolveWindow(windowOrRef, bb.scope, true); + final SqlWindow window = + validator.resolveWindow(windowOrRef, bb.scope, true); // ROW_NUMBER() expects specific kind of framing. if (aggCall.getKind() == SqlKind.ROW_NUMBER) { @@ -1607,7 +1901,8 @@ public class SqlToRelConverter { window.setRows(SqlLiteral.createBoolean(true, SqlParserPos.ZERO)); } final SqlNodeList partitionList = window.getPartitionList(); - final ImmutableList.Builder partitionKeys = ImmutableList.builder(); + final ImmutableList.Builder partitionKeys = + ImmutableList.builder(); for (SqlNode partition : partitionList) { partitionKeys.add(bb.convertExpression(partition)); } @@ -1620,10 +1915,12 @@ public class SqlToRelConverter { // have failed validation. orderList = bb.scope.getOrderList(); if (orderList == null) { - throw new AssertionError("Relation should have sort key for implicit ORDER BY"); + throw new AssertionError( + "Relation should have sort key for implicit ORDER BY"); } } - final ImmutableList.Builder orderKeys = ImmutableList.builder(); + final ImmutableList.Builder orderKeys = + ImmutableList.builder(); final Set flags = EnumSet.noneOf(SqlKind.class); for (SqlNode order : orderList) { flags.clear(); @@ -1634,12 +1931,19 @@ public class SqlToRelConverter { Util.permAssert(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); + 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); + 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; @@ -1663,7 +1967,9 @@ public class SqlToRelConverter { *
  • or any combination of the above. * */ - protected void convertFrom(Blackboard bb, SqlNode from) { + protected void convertFrom( + Blackboard bb, + SqlNode from) { if (from == null) { bb.setRoot(LogicalValues.createOneRow(cluster), false); return; @@ -1672,136 +1978,182 @@ public class SqlToRelConverter { final SqlCall call; final SqlNode[] operands; switch (from.getKind()) { - case AS: - convertFrom(bb, ((SqlCall) from).operand(0)); - return; - - case WITH_ITEM: - convertFrom(bb, ((SqlWithItem) from).query); - return; + case AS: + convertFrom(bb, ((SqlCall) from).operand(0)); + return; - case WITH: - convertFrom(bb, ((SqlWith) from).body); - return; + case WITH_ITEM: + convertFrom(bb, ((SqlWithItem) from).query); + 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 Util.newInternal("unknown TABLESAMPLE type: " + sampleSpec); - } - return; + case WITH: + convertFrom(bb, ((SqlWith) from).body); + return; - case IDENTIFIER: - final SqlValidatorNamespace fromNamespace = validator.getNamespace(from).resolve(); - if (fromNamespace.getNode() != null) { - convertFrom(bb, fromNamespace.getNode()); + 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 Util.newInternal( + "unknown TABLESAMPLE type: " + sampleSpec); + } return; - } - final String datasetName = datasetStack.isEmpty() ? null : datasetStack.peek(); - boolean[] usedDataset = { false }; - RelOptTable table = SqlValidatorUtil.getRelOptTable(fromNamespace, catalogReader, datasetName, usedDataset); - 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); - } - 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 columnList = SqlValidatorUtil.deriveNaturalJoinColumnList(leftRowType, rightRowType); - conditionExp = convertUsing(leftNamespace, rightNamespace, columnList); - } else { - conditionExp = convertJoinCondition(fromBlackboard, leftNamespace, rightNamespace, join.getCondition(), join.getConditionType(), leftRel, rightRel); - } + case IDENTIFIER: + final SqlValidatorNamespace fromNamespace = + validator.getNamespace(from).resolve(); + if (fromNamespace.getNode() != null) { + convertFrom(bb, fromNamespace.getNode()); + return; + } + final String datasetName = + datasetStack.isEmpty() ? null : datasetStack.peek(); + boolean[] usedDataset = {false}; + RelOptTable table = + SqlValidatorUtil.getRelOptTable( + fromNamespace, + catalogReader, + datasetName, + usedDataset); + 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); + } + return; - final RelNode joinRel = createJoin(fromBlackboard, leftRel, rightRel, conditionExp, convertedJoinType); - bb.setRoot(joinRel, false); - 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 columnList = + SqlValidatorUtil.deriveNaturalJoinColumnList(leftRowType, + rightRowType); + conditionExp = convertUsing(leftNamespace, rightNamespace, + columnList); + } else { + conditionExp = + convertJoinCondition( + fromBlackboard, + leftNamespace, + rightNamespace, + join.getCondition(), + join.getConditionType(), + leftRel, + rightRel); + } - case SELECT: - case INTERSECT: - case EXCEPT: - case UNION: - final RelNode rel = convertQueryRecursive(from, false, null).project(); - bb.setRoot(rel, true); - return; + final RelNode joinRel = + createJoin( + fromBlackboard, + leftRel, + rightRel, + conditionExp, + convertedJoinType); + bb.setRoot(joinRel, false); + return; - case VALUES: - convertValuesImpl(bb, (SqlCall) from, null); - return; + case SELECT: + case INTERSECT: + case EXCEPT: + case UNION: + final RelNode rel = convertQueryRecursive(from, false, null).project(); + bb.setRoot(rel, true); + return; - case UNNEST: - call = (SqlCall) from; - final List nodes = call.getOperandList(); - final SqlUnnestOperator operator = (SqlUnnestOperator) call.getOperator(); - for (SqlNode node : nodes) { - replaceSubqueries(bb, node, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); - } - final List exprs = new ArrayList<>(); - final List fieldNames = new ArrayList<>(); - for (Ord 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); + case VALUES: + convertValuesImpl(bb, (SqlCall) from, null); + return; - Uncollect uncollect = new Uncollect(cluster, cluster.traitSetOf(Convention.NONE), input, operator.withOrdinality); - bb.setRoot(uncollect, true); - return; + case UNNEST: + call = (SqlCall) from; + final List nodes = call.getOperandList(); + final SqlUnnestOperator operator = (SqlUnnestOperator) call.getOperator(); + for (SqlNode node : nodes) { + replaceSubQueries(bb, node, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); + } + final List exprs = new ArrayList<>(); + final List fieldNames = new ArrayList<>(); + for (Ord 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; + 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; + // 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 Util.newInternal("not a join operator " + from); + default: + throw Util.newInternal("not a join operator " + from); } } - protected void convertCollectionTable(Blackboard bb, SqlCall call) { + 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)); @@ -1813,16 +2165,20 @@ public class SqlToRelConverter { datasetStack.pop(); return; } - replaceSubqueries(bb, call, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); + 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); + 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 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); + RelOptTable relOptTable = RelOptTableImpl.create(null, rowType, table, + udf.getNameAsId().names); RelNode converted = toRel(relOptTable); bb.setRoot(converted, true); return; @@ -1838,13 +2194,24 @@ public class SqlToRelConverter { RexNode rexCall = bb.convertExpression(call); final List inputs = bb.retrieveCursors(); - Set columnMappings = getColumnMappings(operator); - LogicalTableFunctionScan callRel = LogicalTableFunctionScan.create(cluster, inputs, rexCall, elementType, validator.getValidatedNodeType(call), columnMappings); + Set 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) { + protected void afterTableFunction( + SqlToRelConverter.Blackboard bb, + SqlCall call, + LogicalTableFunctionScan callRel) { } private Set getColumnMappings(SqlOperator op) { @@ -1853,32 +2220,44 @@ public class SqlToRelConverter { return null; } if (rti instanceof TableFunctionReturnTypeInference) { - TableFunctionReturnTypeInference tfrti = (TableFunctionReturnTypeInference) rti; + TableFunctionReturnTypeInference tfrti = + (TableFunctionReturnTypeInference) rti; return tfrti.getColumnMappings(); } else { return null; } } - protected RelNode createJoin(Blackboard bb, RelNode leftRel, RelNode rightRel, RexNode joinCond, JoinRelType joinType) { + 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)); + LogicalCorrelate corr = LogicalCorrelate.create(leftRel, p.r, + p.id, p.requiredColumns, SemiJoinType.of(joinType)); if (!joinCond.isAlwaysTrue()) { - return RelOptUtil.createFilter(corr, joinCond); + 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. of(), joinType, false); + final Join originalJoin = + (Join) RelFactories.DEFAULT_JOIN_FACTORY.createJoin(leftRel, rightRel, + joinCond, ImmutableSet.of(), joinType, false); return RelOptUtil.pushDownJoinConditions(originalJoin); } private CorrelationUse getCorrelationUse(Blackboard bb, final RelNode r0) { - final Set correlatedVariables = RelOptUtil.getVariablesUsed(r0); + final Set correlatedVariables = + RelOptUtil.getVariablesUsed(r0); if (correlatedVariables.isEmpty()) { return null; } @@ -1892,13 +2271,16 @@ public class SqlToRelConverter { SqlValidatorNamespace prevNs = null; for (CorrelationId correlName : correlatedVariables) { - DeferredLookup lookup = mapCorrelToDeferred.get(correlName); + DeferredLookup lookup = + mapCorrelToDeferred.get(correlName); RexFieldAccess fieldAccess = lookup.getFieldAccess(correlName); String originalRelName = lookup.getOriginalRelName(); String originalFieldName = fieldAccess.getField().getName(); - SqlValidatorScope.ResolvedImpl resolved = new SqlValidatorScope.ResolvedImpl(); - lookup.bb.scope.resolve(ImmutableList.of(originalRelName), false, resolved); + SqlValidatorScope.ResolvedImpl resolved = + new SqlValidatorScope.ResolvedImpl(); + lookup.bb.scope.resolve(ImmutableList.of(originalRelName), false, + resolved); assert resolved.count() == 1; final SqlValidatorScope.Resolve resolve = resolved.only(); final SqlValidatorNamespace foundNs = resolve.namespace; @@ -1913,7 +2295,10 @@ public class SqlToRelConverter { if (prevNs == null) { prevNs = foundNs; } else { - assert prevNs == foundNs : "All correlation variables should resolve" + " to the same namespace." + " Prev ns=" + prevNs + ", new ns=" + foundNs; + assert prevNs == foundNs : "All correlation variables should resolve" + + " to the same namespace." + + " Prev ns=" + prevNs + + ", new ns=" + foundNs; } int namespaceOffset = 0; @@ -1921,33 +2306,43 @@ public class SqlToRelConverter { // If not the first child, need to figure out the width // of output types from all the preceding namespaces assert ancestorScope instanceof ListScope; - List children = ((ListScope) ancestorScope).getChildren(); + List children = + ((ListScope) ancestorScope).getChildren(); for (int i = 0; i < childNamespaceIndex; i++) { SqlValidatorNamespace child = children.get(i); - namespaceOffset += child.getRowType().getFieldCount(); + namespaceOffset += + child.getRowType().getFieldCount(); } } - final RelDataTypeField field = foundNs.getRowType().getFieldList().get(fieldAccess.getField().getIndex() - namespaceOffset); + RexFieldAccess topLevelFieldAccess = fieldAccess; + while (topLevelFieldAccess.getReferenceExpr() instanceof RexFieldAccess) { + topLevelFieldAccess = (RexFieldAccess) topLevelFieldAccess.getReferenceExpr(); + } + final RelDataTypeField field = foundNs.getRowType().getFieldList() + .get(topLevelFieldAccess.getField().getIndex() - namespaceOffset); int pos = namespaceOffset + field.getIndex(); - assert field.getType() == lookup.getFieldAccess(correlName).getField().getType(); + 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 exprProjection = bb.mapRootRelToFieldProjection.get(bb.root); + Map exprProjection = + bb.mapRootRelToFieldProjection.get(bb.root); - // subquery can reference group by keys projected from + // 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"); + throw new AssertionError("Identifier '" + originalRelName + "." + + originalFieldName + "' is not a group expr"); } } @@ -1964,21 +2359,22 @@ public class SqlToRelConverter { 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); + r = DeduplicateCorrelateVariables.go(rexBuilder, correlNames.get(0), + Util.skip(correlNames), r0); } return new CorrelationUse(correlNames.get(0), requiredColumns.build(), r); } /** - * Determines whether a subquery is non-correlated. Note that a - * non-correlated subquery can contain correlated references, provided those + * 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 - * subquery. + * sub-query. * - * @param subq the subquery - * @param bb blackboard used while converting the subquery, i.e., the - * blackboard of the parent query of this subquery - * @return true if the subquery is non-correlated. + * @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 correlatedVariables = RelOptUtil.getVariablesUsed(subq); @@ -1986,13 +2382,15 @@ public class SqlToRelConverter { DeferredLookup lookup = mapCorrelToDeferred.get(correlName); String originalRelName = lookup.getOriginalRelName(); - final SqlValidatorScope.ResolvedImpl resolved = new SqlValidatorScope.ResolvedImpl(); - lookup.bb.scope.resolve(ImmutableList.of(originalRelName), false, resolved); + final SqlValidatorScope.ResolvedImpl resolved = + new SqlValidatorScope.ResolvedImpl(); + lookup.bb.scope.resolve(ImmutableList.of(originalRelName), false, + resolved); SqlValidatorScope ancestorScope = resolved.only().scope; // If the correlated reference is in a scope that's "above" the - // subquery, then this is a correlated subquery. + // sub-query, then this is a correlated sub-query. SqlValidatorScope parentScope = bb.scope; do { if (ancestorScope == parentScope) { @@ -2017,27 +2415,33 @@ public class SqlToRelConverter { return Collections.emptyList(); } - private RexNode convertJoinCondition(Blackboard bb, SqlValidatorNamespace leftNamespace, SqlValidatorNamespace rightNamespace, SqlNode condition, JoinConditionType conditionType, RelNode leftRel, RelNode rightRel) { + 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); + 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 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); + case ON: + bb.setRoot(ImmutableList.of(leftRel, rightRel)); + return bb.convertExpression(condition); + case USING: + final SqlNodeList list = (SqlNodeList) condition; + final List 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); } } @@ -2052,15 +2456,20 @@ public class SqlToRelConverter { * @return Expression to match columns from name list, or true if name list * is empty */ - private RexNode convertUsing(SqlValidatorNamespace leftNamespace, SqlValidatorNamespace rightNamespace, List nameList) { + private RexNode convertUsing(SqlValidatorNamespace leftNamespace, + SqlValidatorNamespace rightNamespace, + List nameList) { final List list = Lists.newArrayList(); for (String name : nameList) { List operands = new ArrayList<>(); int offset = 0; - for (SqlValidatorNamespace n : ImmutableList.of(leftNamespace, rightNamespace)) { + for (SqlValidatorNamespace n : ImmutableList.of(leftNamespace, + rightNamespace)) { final RelDataType rowType = n.getRowType(); final RelDataTypeField field = catalogReader.field(rowType, name); - operands.add(rexBuilder.makeInputRef(field.getType(), offset + field.getIndex())); + operands.add( + rexBuilder.makeInputRef(field.getType(), + offset + field.getIndex())); offset += rowType.getFieldList().size(); } list.add(rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, operands)); @@ -2068,6 +2477,23 @@ public class SqlToRelConverter { 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. * @@ -2080,17 +2506,32 @@ public class SqlToRelConverter { * @param select Query * @param orderExprList Additional expressions needed to implement ORDER BY */ - protected void convertAgg(Blackboard bb, SqlSelect select, List orderExprList) { + protected void convertAgg( + Blackboard bb, + SqlSelect select, + List 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); + createAggImpl( + bb, + aggConverter, + selectList, + groupList, + having, + orderExprList); } - protected final void createAggImpl(Blackboard bb, final AggConverter aggConverter, SqlNodeList selectList, SqlNodeList groupList, SqlNode having, List orderExprList) { + protected final void createAggImpl( + Blackboard bb, + final AggConverter aggConverter, + SqlNodeList selectList, + SqlNodeList groupList, + SqlNode having, + List orderExprList) { // Find aggregate functions in SELECT and HAVING clause final AggregateFinder aggregateFinder = new AggregateFinder(); selectList.accept(aggregateFinder); @@ -2098,16 +2539,17 @@ public class SqlToRelConverter { having.accept(aggregateFinder); } - // first replace the subqueries inside the aggregates + // 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); + 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); + replaceSubQueries(bb, groupList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); // register the group exprs @@ -2153,14 +2595,22 @@ public class SqlToRelConverter { // Special case for COUNT(*), where we can end up with no inputs // at all. The rest of the system doesn't like 0-tuples, so we // select a dummy constant here. - preExprs = ImmutableList. of(rexBuilder.makeExactLiteral(BigDecimal.ZERO)); + preExprs = + ImmutableList.of( + rexBuilder.makeExactLiteral(BigDecimal.ZERO)); preNames = Collections.singletonList(null); } final RelNode inputRel = bb.root; // Project the expressions required by agg and having. - bb.setRoot(RelOptUtil.createProject(inputRel, preExprs, preNames, true), false); + bb.setRoot( + RelOptUtil.createProject( + inputRel, + preExprs, + preNames, + true), + false); bb.mapRootRelToFieldProjection.put(bb.root, r.groupExprProjection); // REVIEW jvs 31-Oct-2007: doesn't the declaration of @@ -2170,11 +2620,15 @@ public class SqlToRelConverter { // Tell bb which of group columns are sorted. bb.columnMonotonicities.clear(); for (SqlNode groupItem : groupList) { - bb.columnMonotonicities.add(bb.scope.getMonotonicity(groupItem)); + bb.columnMonotonicities.add( + bb.scope.getMonotonicity(groupItem)); } // Add the aggregator - bb.setRoot(createAggregate(bb, r.indicator, r.groupSet, r.groupSets, aggConverter.getAggCalls()), false); + bb.setRoot( + createAggregate(bb, r.indicator, r.groupSet, r.groupSets, + aggConverter.getAggCalls()), + false); // Generate NULL values for rolled-up not-null fields. final Aggregate aggregate = (Aggregate) bb.root; @@ -2189,37 +2643,45 @@ public class SqlToRelConverter { if (i < groupCount && r.isNullable(i)) { ++converted; - rex = rexBuilder.makeCall(SqlStdOperatorTable.CASE, rexBuilder.makeInputRef(aggregate, groupCount + i), rexBuilder.makeCast(typeFactory.createTypeWithNullability(field.getType(), true), rexBuilder.constantNull()), rexBuilder.makeInputRef(aggregate, i)); + rex = rexBuilder.makeCall(SqlStdOperatorTable.CASE, + rexBuilder.makeInputRef(aggregate, groupCount + i), + rexBuilder.makeCast( + typeFactory.createTypeWithNullability( + field.getType(), true), + rexBuilder.constantNull()), + rexBuilder.makeInputRef(aggregate, i)); } else { rex = rexBuilder.makeInputRef(aggregate, i); } projects2.add(Pair.of(rex, field.getName())); } if (converted > 0) { - bb.setRoot(RelOptUtil.createProject(bb.root, projects2, true), false); + bb.setRoot( + RelOptUtil.createProject(bb.root, projects2, true), + false); } } bb.mapRootRelToFieldProjection.put(bb.root, r.groupExprProjection); - // Replace subqueries in having here and modify having to use + // Replace sub-queries in having here and modify having to use // the replaced expressions if (having != null) { SqlNode newHaving = pushDownNotForIn(having); - replaceSubqueries(bb, newHaving, RelOptUtil.Logic.UNKNOWN_AS_FALSE); + replaceSubQueries(bb, newHaving, RelOptUtil.Logic.UNKNOWN_AS_FALSE); havingExpr = bb.convertExpression(newHaving); if (havingExpr.isAlwaysTrue()) { havingExpr = null; } } - // Now convert the other subqueries in the select list. - // This needs to be done separately from the subquery inside + // Now convert the other sub-queries in the select list. + // This needs to be done separately from the sub-query inside // any aggregate in the select list, and after the aggregate rel // is allocated. - replaceSubqueries(bb, selectList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); + replaceSubQueries(bb, selectList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); - // Now subqueries in the entire select list have been converted. + // Now sub-queries in the entire select list have been converted. // Convert the select expressions to get the final list to be // projected. int k = 0; @@ -2230,17 +2692,26 @@ public class SqlToRelConverter { // validator. This is especially the case when there are system // fields; system fields appear in the relnode's rowtype but do not // (yet) appear in the validator type. - final SelectScope selectScope = SqlValidatorUtil.getEnclosingSelectScope(bb.scope); + final SelectScope selectScope = + SqlValidatorUtil.getEnclosingSelectScope(bb.scope); assert selectScope != null; - final SqlValidatorNamespace selectNamespace = validator.getNamespace(selectScope.getNode()); - final List names = selectNamespace.getRowType().getFieldNames(); + final SqlValidatorNamespace selectNamespace = + validator.getNamespace(selectScope.getNode()); + final List names = + selectNamespace.getRowType().getFieldNames(); int sysFieldCount = selectList.size() - names.size(); for (SqlNode expr : selectList) { - projects.add(Pair.of(bb.convertExpression(expr), k < sysFieldCount ? validator.deriveAlias(expr, k++) : names.get(k++ - sysFieldCount))); + projects.add( + Pair.of(bb.convertExpression(expr), + k < sysFieldCount + ? validator.deriveAlias(expr, k++) + : names.get(k++ - sysFieldCount))); } for (SqlNode expr : orderExprList) { - projects.add(Pair.of(bb.convertExpression(expr), validator.deriveAlias(expr, k++))); + projects.add( + Pair.of(bb.convertExpression(expr), + validator.deriveAlias(expr, k++))); } } finally { bb.agg = null; @@ -2248,16 +2719,24 @@ public class SqlToRelConverter { // implement HAVING (we have already checked that it is non-trivial) if (havingExpr != null) { - bb.setRoot(RelOptUtil.createFilter(bb.root, havingExpr), false); + final RelFactories.FilterFactory factory = + RelFactories.DEFAULT_FILTER_FACTORY; + bb.setRoot(factory.createFilter(bb.root, havingExpr), false); } // implement the SELECT list - bb.setRoot(RelOptUtil.createProject(bb.root, projects, true), false); + bb.setRoot( + RelOptUtil.createProject( + bb.root, + projects, + true), + false); // Tell bb which of group columns are sorted. bb.columnMonotonicities.clear(); for (SqlNode selectItem : selectList) { - bb.columnMonotonicities.add(bb.scope.getMonotonicity(selectItem)); + bb.columnMonotonicities.add( + bb.scope.getMonotonicity(selectItem)); } } @@ -2280,11 +2759,15 @@ public class SqlToRelConverter { * @param aggCalls Array of calls to aggregate functions * @return LogicalAggregate */ - protected RelNode createAggregate(Blackboard bb, boolean indicator, ImmutableBitSet groupSet, ImmutableList groupSets, List aggCalls) { - return LogicalAggregate.create(bb.root, indicator, groupSet, groupSets, aggCalls); + protected RelNode createAggregate(Blackboard bb, boolean indicator, + ImmutableBitSet groupSet, ImmutableList groupSets, + List aggCalls) { + return LogicalAggregate.create( + bb.root, indicator, groupSet, groupSets, aggCalls); } - public RexDynamicParam convertDynamicParam(final SqlDynamicParam dynamicParam) { + public RexDynamicParam convertDynamicParam( + final SqlDynamicParam dynamicParam) { // REVIEW jvs 8-Jan-2005: dynamic params may be encountered out of // order. Should probably cross-check with the count from the parser // at the end and make sure they all got filled in. Why doesn't List @@ -2293,8 +2776,12 @@ public class SqlToRelConverter { dynamicParamSqlNodes.add(null); } - dynamicParamSqlNodes.set(dynamicParam.getIndex(), dynamicParam); - return rexBuilder.makeDynamicParam(getDynamicParamType(dynamicParam.getIndex()), dynamicParam.getIndex()); + dynamicParamSqlNodes.set( + dynamicParam.getIndex(), + dynamicParam); + return rexBuilder.makeDynamicParam( + getDynamicParamType(dynamicParam.getIndex()), + dynamicParam.getIndex()); } /** @@ -2311,7 +2798,12 @@ public class SqlToRelConverter { * clause (output) * @param collationList List of collations (output) */ - protected void gatherOrderExprs(Blackboard bb, SqlSelect select, SqlNodeList orderList, List extraOrderExprs, List collationList) { + protected void gatherOrderExprs( + Blackboard bb, + SqlSelect select, + SqlNodeList orderList, + List extraOrderExprs, + List collationList) { // TODO: add validation rules to SqlValidator also assert bb.root != null : "precondition: child != null"; assert select != null; @@ -2319,27 +2811,54 @@ public class SqlToRelConverter { return; } for (SqlNode orderItem : orderList) { - collationList.add(convertOrderItem(select, orderItem, extraOrderExprs, RelFieldCollation.Direction.ASCENDING, RelFieldCollation.NullDirection.UNSPECIFIED)); + collationList.add( + convertOrderItem( + select, + orderItem, + extraOrderExprs, + RelFieldCollation.Direction.ASCENDING, + RelFieldCollation.NullDirection.UNSPECIFIED)); } } - protected RelFieldCollation convertOrderItem(SqlSelect select, SqlNode orderItem, List extraExprs, RelFieldCollation.Direction direction, RelFieldCollation.NullDirection nullDirection) { + protected RelFieldCollation convertOrderItem( + SqlSelect select, + SqlNode orderItem, List extraExprs, + RelFieldCollation.Direction direction, + RelFieldCollation.NullDirection nullDirection) { assert select != null; // Handle DESC keyword, e.g. 'select a, b from t order by a desc'. switch (orderItem.getKind()) { - case DESCENDING: - return convertOrderItem(select, ((SqlCall) orderItem).operand(0), extraExprs, RelFieldCollation.Direction.DESCENDING, nullDirection); - case NULLS_FIRST: - return convertOrderItem(select, ((SqlCall) orderItem).operand(0), extraExprs, direction, RelFieldCollation.NullDirection.FIRST); - case NULLS_LAST: - return convertOrderItem(select, ((SqlCall) orderItem).operand(0), extraExprs, direction, RelFieldCollation.NullDirection.LAST); + case DESCENDING: + return convertOrderItem( + select, + ((SqlCall) orderItem).operand(0), + extraExprs, + RelFieldCollation.Direction.DESCENDING, + nullDirection); + case NULLS_FIRST: + return convertOrderItem( + select, + ((SqlCall) orderItem).operand(0), + extraExprs, + direction, + RelFieldCollation.NullDirection.FIRST); + case NULLS_LAST: + return convertOrderItem( + select, + ((SqlCall) orderItem).operand(0), + extraExprs, + direction, + RelFieldCollation.NullDirection.LAST); } SqlNode converted = validator.expandOrderExpr(select, orderItem); switch (nullDirection) { - case UNSPECIFIED: - nullDirection = validator.getDefaultNullCollation().last(desc(direction)) ? RelFieldCollation.NullDirection.LAST : RelFieldCollation.NullDirection.FIRST; + case UNSPECIFIED: + nullDirection = validator.getDefaultNullCollation().last(desc(direction)) + ? RelFieldCollation.NullDirection.LAST + : RelFieldCollation.NullDirection.FIRST; } // Scan the select list and order exprs for an identical expression. @@ -2366,9 +2885,19 @@ public class SqlToRelConverter { return new RelFieldCollation(ordinal + 1, direction, nullDirection); } + private static boolean desc(RelFieldCollation.Direction direction) { + switch (direction) { + case DESCENDING: + case STRICTLY_DESCENDING: + return true; + default: + return false; + } + } + @Deprecated // to be removed before 2.0 protected boolean enableDecorrelation() { - // disable subquery decorrelation when needed. + // disable sub-query decorrelation when needed. // e.g. if outer joins are not supported. return config.isDecorrelationEnabled(); } @@ -2398,29 +2927,30 @@ public class SqlToRelConverter { * @param targetRowType Target row type, or null * @return Relational expression */ - protected RelRoot convertQueryRecursive(SqlNode query, boolean top, RelDataType targetRowType) { + protected RelRoot convertQueryRecursive(SqlNode query, boolean top, + RelDataType targetRowType) { final SqlKind kind = query.getKind(); switch (kind) { - case SELECT: - return RelRoot.of(convertSelect((SqlSelect) query, top), kind); - case INSERT: - return RelRoot.of(convertInsert((SqlInsert) query), kind); - case DELETE: - return RelRoot.of(convertDelete((SqlDelete) query), kind); - case UPDATE: - return RelRoot.of(convertUpdate((SqlUpdate) query), kind); - case MERGE: - return RelRoot.of(convertMerge((SqlMerge) query), kind); - case UNION: - case INTERSECT: - case EXCEPT: - return RelRoot.of(convertSetOp((SqlCall) query), kind); - case WITH: - return convertWith((SqlWith) query, top); - case VALUES: - return RelRoot.of(convertValues((SqlCall) query, targetRowType), kind); - default: - throw Util.newInternal("not a query: " + query); + case SELECT: + return RelRoot.of(convertSelect((SqlSelect) query, top), kind); + case INSERT: + return RelRoot.of(convertInsert((SqlInsert) query), kind); + case DELETE: + return RelRoot.of(convertDelete((SqlDelete) query), kind); + case UPDATE: + return RelRoot.of(convertUpdate((SqlUpdate) query), kind); + case MERGE: + return RelRoot.of(convertMerge((SqlMerge) query), kind); + case UNION: + case INTERSECT: + case EXCEPT: + return RelRoot.of(convertSetOp((SqlCall) query), kind); + case WITH: + return convertWith((SqlWith) query, top); + case VALUES: + return RelRoot.of(convertValues((SqlCall) query, targetRowType), kind); + default: + throw Util.newInternal("not a query: " + query); } } @@ -2432,43 +2962,37 @@ public class SqlToRelConverter { * @return Relational expression */ protected RelNode convertSetOp(SqlCall call) { - final RelNode left = convertQueryRecursive(call.operand(0), false, null).project(); - final RelNode right = convertQueryRecursive(call.operand(1), false, null).project(); - boolean all = false; - if (call.getOperator() instanceof SqlSetOperator) { - all = ((SqlSetOperator) (call.getOperator())).isAll(); - } + final RelNode left = + convertQueryRecursive(call.operand(0), false, null).project(); + final RelNode right = + convertQueryRecursive(call.operand(1), false, null).project(); switch (call.getKind()) { - case UNION: - return LogicalUnion.create(ImmutableList.of(left, right), all); + case UNION: + return LogicalUnion.create(ImmutableList.of(left, right), all(call)); - case INTERSECT: - // TODO: all - if (!all) { - return LogicalIntersect.create(ImmutableList.of(left, right), all); - } else { - throw Util.newInternal("set operator INTERSECT ALL not suported"); - } + case INTERSECT: + return LogicalIntersect.create(ImmutableList.of(left, right), all(call)); - case EXCEPT: - // TODO: all - if (!all) { - return LogicalMinus.create(ImmutableList.of(left, right), all); - } else { - throw Util.newInternal("set operator EXCEPT ALL not suported"); - } + case EXCEPT: + return LogicalMinus.create(ImmutableList.of(left, right), all(call)); - default: - throw Util.unexpected(call.getKind()); + default: + throw Util.unexpected(call.getKind()); } } + private boolean all(SqlCall call) { + return ((SqlSetOperator) call.getOperator()).isAll(); + } + protected RelNode convertInsert(SqlInsert call) { RelOptTable targetTable = getTargetTable(call); - final RelDataType targetRowType = validator.getValidatedNodeType(call); + final RelDataType targetRowType = + validator.getValidatedNodeType(call); assert targetRowType != null; - RelNode sourceRel = convertQueryRecursive(call.getSource(), false, targetRowType).project(); + RelNode sourceRel = + convertQueryRecursive(call.getSource(), false, targetRowType).project(); RelNode massagedRel = convertColumnList(call, sourceRel); return createModify(targetTable, massagedRel); @@ -2476,19 +3000,27 @@ public class SqlToRelConverter { /** Creates a relational expression to modify a table or modifiable view. */ private RelNode createModify(RelOptTable targetTable, RelNode source) { - final ModifiableTable modifiableTable = targetTable.unwrap(ModifiableTable.class); + final ModifiableTable modifiableTable = + targetTable.unwrap(ModifiableTable.class); if (modifiableTable != null) { - return modifiableTable.toModificationRel(cluster, targetTable, catalogReader, source, LogicalTableModify.Operation.INSERT, null, false); + return modifiableTable.toModificationRel(cluster, targetTable, + catalogReader, source, LogicalTableModify.Operation.INSERT, null, + null, false); } - final ModifiableView modifiableView = targetTable.unwrap(ModifiableView.class); + final ModifiableView modifiableView = + targetTable.unwrap(ModifiableView.class); if (modifiableView != null) { final Table delegateTable = modifiableView.getTable(); final RelDataType delegateRowType = delegateTable.getRowType(typeFactory); - final RelOptTable delegateRelOptTable = RelOptTableImpl.create(null, delegateRowType, delegateTable, modifiableView.getTablePath()); - final RelNode newSource = createSource(targetTable, source, modifiableView, delegateRowType); + final RelOptTable delegateRelOptTable = + RelOptTableImpl.create(null, delegateRowType, delegateTable, + modifiableView.getTablePath()); + final RelNode newSource = + createSource(targetTable, source, modifiableView, delegateRowType); return createModify(delegateRelOptTable, newSource); } - return LogicalTableModify.create(targetTable, catalogReader, source, LogicalTableModify.Operation.INSERT, null, false); + return LogicalTableModify.create(targetTable, catalogReader, source, + LogicalTableModify.Operation.INSERT, null, null, false); } /** Wraps a relational expression in the projects and filters implied by @@ -2500,7 +3032,8 @@ public class SqlToRelConverter { * *

    In principle, the delegate table of a view might be another modifiable * view, and if so, the process can be repeated. */ - private RelNode createSource(RelOptTable targetTable, RelNode source, ModifiableView modifiableView, RelDataType delegateRowType) { + private RelNode createSource(RelOptTable targetTable, RelNode source, + ModifiableView modifiableView, RelDataType delegateRowType) { final ImmutableIntList mapping = modifiableView.getColumnMapping(); assert mapping.size() == targetTable.getRowType().getFieldCount(); @@ -2520,7 +3053,8 @@ public class SqlToRelConverter { // // If a column has multiple constraints, the extra ones will become a // filter. - final RexNode constraint = modifiableView.getConstraint(rexBuilder, delegateRowType); + final RexNode constraint = + modifiableView.getConstraint(rexBuilder, delegateRowType); RelOptUtil.inferViewPredicates(projectMap, filters, constraint); final List> projects = new ArrayList<>(); for (RelDataTypeField field : delegateRowType.getFieldList()) { @@ -2528,7 +3062,9 @@ public class SqlToRelConverter { if (node == null) { node = rexBuilder.makeNullLiteral(field.getType().getSqlTypeName()); } - projects.add(Pair.of(rexBuilder.ensureType(field.getType(), node, false), field.getName())); + projects.add( + Pair.of(rexBuilder.ensureType(field.getType(), node, false), + field.getName())); } source = RelOptUtil.createProject(source, projects, true); @@ -2544,8 +3080,11 @@ public class SqlToRelConverter { return cluster; } - @Override - public RelRoot expandView(RelDataType rowType, String queryString, List schemaPath, List viewPath) { + @Override public RelRoot expandView( + RelDataType rowType, + String queryString, + List schemaPath, + List viewPath) { return viewExpander.expandView(rowType, queryString, schemaPath, viewPath); } @@ -2576,18 +3115,26 @@ public class SqlToRelConverter { * @param sourceRel Source relational expression * @return Converted INSERT statement */ - protected RelNode convertColumnList(SqlInsert call, RelNode sourceRel) { + protected RelNode convertColumnList( + SqlInsert call, + RelNode sourceRel) { RelDataType sourceRowType = sourceRel.getRowType(); - final RexNode sourceRef = rexBuilder.makeRangeReference(sourceRowType, 0, false); + final RexNode sourceRef = + rexBuilder.makeRangeReference(sourceRowType, 0, false); final List targetColumnNames = new ArrayList<>(); final List columnExprs = new ArrayList<>(); collectInsertTargets(call, sourceRef, targetColumnNames, columnExprs); final RelOptTable targetTable = getTargetTable(call); final RelDataType targetRowType = targetTable.getRowType(); - final List targetFields = targetRowType.getFieldList(); - final List sourceExps = new ArrayList<>(Collections. nCopies(targetFields.size(), null)); - final List fieldNames = new ArrayList<>(Collections. nCopies(targetFields.size(), null)); + final List targetFields = + targetRowType.getFieldList(); + final List sourceExps = + new ArrayList<>( + Collections.nCopies(targetFields.size(), null)); + final List fieldNames = + new ArrayList<>( + Collections.nCopies(targetFields.size(), null)); // Walk the name list and place the associated value in the // expression list according to the ordinal value returned from @@ -2611,10 +3158,14 @@ public class SqlToRelConverter { } continue; } - sourceExps.set(i, defaultValueFactory.newColumnDefaultValue(targetTable, i)); + sourceExps.set( + i, defaultValueFactory.newColumnDefaultValue(targetTable, i)); // bare nulls are dangerous in the wrong hands - sourceExps.set(i, castNullLiteralIfNeeded(sourceExps.get(i), field.getType())); + sourceExps.set( + i, + castNullLiteralIfNeeded( + sourceExps.get(i), field.getType())); } return RelOptUtil.createProject(sourceRel, sourceExps, fieldNames, true); @@ -2637,7 +3188,11 @@ public class SqlToRelConverter { * @param targetColumnNames List of target column names, to be populated * @param columnExprs List of expressions, to be populated */ - protected void collectInsertTargets(SqlInsert call, final RexNode sourceRef, final List targetColumnNames, List columnExprs) { + protected void collectInsertTargets( + SqlInsert call, + final RexNode sourceRef, + final List targetColumnNames, + List columnExprs) { final RelOptTable targetTable = getTargetTable(call); final RelDataType targetRowType = targetTable.getRowType(); SqlNodeList targetColumnList = call.getTargetColumnList(); @@ -2646,7 +3201,11 @@ public class SqlToRelConverter { } else { for (int i = 0; i < targetColumnList.size(); i++) { SqlIdentifier id = (SqlIdentifier) targetColumnList.get(i); - targetColumnNames.add(id.getSimple()); + RelDataTypeField field = + SqlValidatorUtil.getTargetField( + targetRowType, typeFactory, id, catalogReader, targetTable); + assert field != null : "column " + id.toString() + " not found"; + targetColumnNames.add(field.getName()); } } @@ -2659,23 +3218,39 @@ public class SqlToRelConverter { private RelNode convertDelete(SqlDelete call) { RelOptTable targetTable = getTargetTable(call); RelNode sourceRel = convertSelect(call.getSourceSelect(), false); - return LogicalTableModify.create(targetTable, catalogReader, sourceRel, LogicalTableModify.Operation.DELETE, null, false); + return LogicalTableModify.create(targetTable, catalogReader, sourceRel, + LogicalTableModify.Operation.DELETE, null, null, false); } private RelNode convertUpdate(SqlUpdate call) { + final SqlValidatorScope scope = validator.getWhereScope(call.getSourceSelect()); + Blackboard bb = createBlackboard(scope, null, false); + + Builder rexNodeSourceExpressionListBuilder = ImmutableList.builder(); + for (SqlNode n : call.getSourceExpressionList()) { + RexNode rn = bb.convertExpression(n); + rexNodeSourceExpressionListBuilder.add(rn); + } + RelOptTable targetTable = getTargetTable(call); // convert update column list from SqlIdentifier to String final List targetColumnNameList = new ArrayList<>(); + final RelDataType targetRowType = targetTable.getRowType(); for (SqlNode node : call.getTargetColumnList()) { SqlIdentifier id = (SqlIdentifier) node; - String name = id.getSimple(); - targetColumnNameList.add(name); + RelDataTypeField field = + SqlValidatorUtil.getTargetField( + targetRowType, typeFactory, id, catalogReader, targetTable); + assert field != null : "column " + id.toString() + " not found"; + targetColumnNameList.add(field.getName()); } RelNode sourceRel = convertSelect(call.getSourceSelect(), false); - return LogicalTableModify.create(targetTable, catalogReader, sourceRel, LogicalTableModify.Operation.UPDATE, targetColumnNameList, false); + return LogicalTableModify.create(targetTable, catalogReader, sourceRel, + LogicalTableModify.Operation.UPDATE, targetColumnNameList, + rexNodeSourceExpressionListBuilder.build(), false); } private RelNode convertMerge(SqlMerge call) { @@ -2683,12 +3258,16 @@ public class SqlToRelConverter { // convert update column list from SqlIdentifier to String final List targetColumnNameList = new ArrayList<>(); + final RelDataType targetRowType = targetTable.getRowType(); SqlUpdate updateCall = call.getUpdateCall(); if (updateCall != null) { for (SqlNode targetColumn : updateCall.getTargetColumnList()) { SqlIdentifier id = (SqlIdentifier) targetColumn; - String name = id.getSimple(); - targetColumnNameList.add(name); + RelDataTypeField field = + SqlValidatorUtil.getTargetField( + targetRowType, typeFactory, id, catalogReader, targetTable); + assert field != null : "column " + id.toString() + " not found"; + targetColumnNameList.add(field.getName()); } } @@ -2719,9 +3298,12 @@ public class SqlToRelConverter { // provided, in which case, the expression is the default value for // the column; or if the expressions directly map to the source // table - level1InsertExprs = ((LogicalProject) insertRel.getInput(0)).getProjects(); + level1InsertExprs = + ((LogicalProject) insertRel.getInput(0)).getProjects(); if (insertRel.getInput(0).getInput(0) instanceof LogicalProject) { - level2InsertExprs = ((LogicalProject) insertRel.getInput(0).getInput(0)).getProjects(); + level2InsertExprs = + ((LogicalProject) insertRel.getInput(0).getInput(0)) + .getProjects(); } nLevel1Exprs = level1InsertExprs.size(); } @@ -2730,8 +3312,10 @@ public class SqlToRelConverter { int nSourceFields = join.getLeft().getRowType().getFieldCount(); final List projects = new ArrayList<>(); for (int level1Idx = 0; level1Idx < nLevel1Exprs; level1Idx++) { - if ((level2InsertExprs != null) && (level1InsertExprs.get(level1Idx) instanceof RexInputRef)) { - int level2Idx = ((RexInputRef) level1InsertExprs.get(level1Idx)).getIndex(); + if ((level2InsertExprs != null) + && (level1InsertExprs.get(level1Idx) instanceof RexInputRef)) { + int level2Idx = + ((RexInputRef) level1InsertExprs.get(level1Idx)).getIndex(); projects.add(level2InsertExprs.get(level2Idx)); } else { projects.add(level1InsertExprs.get(level1Idx)); @@ -2739,19 +3323,24 @@ public class SqlToRelConverter { } if (updateCall != null) { final LogicalProject project = (LogicalProject) mergeSourceRel; - projects.addAll(Util.skip(project.getProjects(), nSourceFields)); + projects.addAll( + Util.skip(project.getProjects(), nSourceFields)); } - RelNode massagedRel = RelOptUtil.createProject(join, projects, null, true); + RelNode massagedRel = + RelOptUtil.createProject(join, projects, null, true); - return LogicalTableModify.create(targetTable, catalogReader, massagedRel, LogicalTableModify.Operation.MERGE, targetColumnNameList, false); + return LogicalTableModify.create(targetTable, catalogReader, massagedRel, + LogicalTableModify.Operation.MERGE, targetColumnNameList, null, false); } /** * Converts an identifier into an expression in a given scope. For example, * the "empno" in "select empno from emp join dept" becomes "emp.empno". */ - private RexNode convertIdentifier(Blackboard bb, SqlIdentifier identifier) { + private RexNode convertIdentifier( + Blackboard bb, + SqlIdentifier identifier) { // first check for reserved identifiers like CURRENT_USER final SqlCall call = SqlUtil.makeCall(opTab, identifier); if (call != null) { @@ -2782,7 +3371,9 @@ public class SqlToRelConverter { if (e0.left instanceof RexCorrelVariable) { assert e instanceof RexFieldAccess; - final RexNode prev = bb.mapCorrelateToRex.put(((RexCorrelVariable) e0.left).id, (RexFieldAccess) e); + final RexNode prev = + bb.mapCorrelateToRex.put(((RexCorrelVariable) e0.left).id, + (RexFieldAccess) e); assert prev == null; } return e; @@ -2797,10 +3388,14 @@ public class SqlToRelConverter { * @param inputRef Input ref * @return Adjusted input ref */ - protected RexNode adjustInputRef(Blackboard bb, RexInputRef inputRef) { + protected RexNode adjustInputRef( + Blackboard bb, + RexInputRef inputRef) { RelDataTypeField field = bb.getRootField(inputRef); if (field != null) { - return rexBuilder.makeInputRef(field.getType(), inputRef.getIndex()); + return rexBuilder.makeInputRef( + field.getType(), + inputRef.getIndex()); } return inputRef; } @@ -2813,7 +3408,9 @@ public class SqlToRelConverter { * @return Relational expression which returns a single row. * @pre isRowConstructor(rowConstructor) */ - private RelNode convertRowConstructor(Blackboard bb, SqlCall rowConstructor) { + private RelNode convertRowConstructor( + Blackboard bb, + SqlCall rowConstructor) { assert isRowConstructor(rowConstructor) : rowConstructor; final List operands = rowConstructor.getOperandList(); return convertMultisets(operands, bb); @@ -2826,11 +3423,15 @@ public class SqlToRelConverter { RelNode converted = convertQuery(query, false, false).rel; int iCursor = bb.cursors.size(); bb.cursors.add(converted); - subQuery.expr = new RexInputRef(iCursor, converted.getRowType()); + subQuery.expr = + new RexInputRef( + iCursor, + converted.getRowType()); return converted; } - private RelNode convertMultisets(final List operands, Blackboard bb) { + private RelNode convertMultisets(final List operands, + Blackboard bb) { // NOTE: Wael 2/04/05: this implementation is not the most efficient in // terms of planning since it generates XOs that can be reduced. final List joinList = new ArrayList<>(); @@ -2845,39 +3446,49 @@ public class SqlToRelConverter { final SqlCall call = (SqlCall) operand; final RelNode input; switch (call.getKind()) { - case MULTISET_VALUE_CONSTRUCTOR: - case ARRAY_VALUE_CONSTRUCTOR: - final SqlNodeList list = new SqlNodeList(call.getOperandList(), call.getParserPosition()); - CollectNamespace nss = (CollectNamespace) validator.getNamespace(call); - Blackboard usedBb; - if (null != nss) { - usedBb = createBlackboard(nss.getScope(), null, false); - } else { - usedBb = createBlackboard(new ListScope(bb.scope) { - public SqlNode getNode() { - return call; - } - }, null, false); - } - RelDataType multisetType = validator.getValidatedNodeType(call); - validator.setValidatedNodeType(list, multisetType.getComponentType()); - input = convertQueryOrInList(usedBb, list, null); - break; - case MULTISET_QUERY_CONSTRUCTOR: - case ARRAY_QUERY_CONSTRUCTOR: - final RelRoot root = convertQuery(call.operand(0), false, true); - input = root.rel; - break; - default: - lastList.add(operand); - continue; + case MULTISET_VALUE_CONSTRUCTOR: + case ARRAY_VALUE_CONSTRUCTOR: + final SqlNodeList list = + new SqlNodeList(call.getOperandList(), call.getParserPosition()); + CollectNamespace nss = + (CollectNamespace) validator.getNamespace(call); + Blackboard usedBb; + if (null != nss) { + usedBb = createBlackboard(nss.getScope(), null, false); + } else { + usedBb = + createBlackboard(new ListScope(bb.scope) { + public SqlNode getNode() { + return call; + } + }, null, false); + } + RelDataType multisetType = validator.getValidatedNodeType(call); + validator.setValidatedNodeType( + list, + multisetType.getComponentType()); + input = convertQueryOrInList(usedBb, list, null); + break; + case MULTISET_QUERY_CONSTRUCTOR: + case ARRAY_QUERY_CONSTRUCTOR: + final RelRoot root = convertQuery(call.operand(0), false, true); + input = root.rel; + break; + default: + lastList.add(operand); + continue; } if (lastList.size() > 0) { joinList.add(lastList); } lastList = new ArrayList<>(); - Collect collect = new Collect(cluster, cluster.traitSetOf(Convention.NONE), input, validator.deriveAlias(call, i)); + Collect collect = + new Collect( + cluster, + cluster.traitSetOf(Convention.NONE), + input, + validator.deriveAlias(call, i)); joinList.add(collect); } @@ -2906,7 +3517,11 @@ public class SqlToRelConverter { fieldNameList.add(SqlUtil.deriveAliasFromOrdinal(j)); } - RelNode projRel = RelOptUtil.createProject(LogicalValues.createOneRow(cluster), selectList, fieldNameList); + RelNode projRel = + RelOptUtil.createProject( + LogicalValues.createOneRow(cluster), + selectList, + fieldNameList); joinList.set(i, projRel); } @@ -2915,16 +3530,26 @@ public class SqlToRelConverter { RelNode ret = (RelNode) joinList.get(0); for (int i = 1; i < joinList.size(); i++) { RelNode relNode = (RelNode) joinList.get(i); - ret = RelFactories.DEFAULT_JOIN_FACTORY.createJoin(ret, relNode, rexBuilder.makeLiteral(true), ImmutableSet. of(), JoinRelType.INNER, false); + ret = + RelFactories.DEFAULT_JOIN_FACTORY.createJoin( + ret, + relNode, + rexBuilder.makeLiteral(true), + ImmutableSet.of(), + JoinRelType.INNER, + false); } return ret; } - private void convertSelectList(Blackboard bb, SqlSelect select, List orderList) { + private void convertSelectList( + Blackboard bb, + SqlSelect select, + List orderList) { SqlNodeList selectList = select.getSelectList(); selectList = validator.expandStar(selectList, select, false); - replaceSubqueries(bb, selectList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); + replaceSubQueries(bb, selectList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); List fieldNames = new ArrayList<>(); final List exprs = new ArrayList<>(); @@ -2933,7 +3558,13 @@ public class SqlToRelConverter { // Project any system fields. (Must be done before regular select items, // because offsets may be affected.) final List columnMonotonicityList = new ArrayList<>(); - extraSelectItems(bb, select, exprs, fieldNames, aliases, columnMonotonicityList); + extraSelectItems( + bb, + select, + exprs, + fieldNames, + aliases, + columnMonotonicityList); // Project select clause. int i = -1; @@ -2953,12 +3584,15 @@ public class SqlToRelConverter { fieldNames = SqlValidatorUtil.uniquify(fieldNames, catalogReader.isCaseSensitive()); - bb.setRoot(RelOptUtil.createProject(bb.root, exprs, fieldNames), false); + bb.setRoot( + RelOptUtil.createProject(bb.root, exprs, fieldNames), + false); assert bb.columnMonotonicities.isEmpty(); bb.columnMonotonicities.addAll(columnMonotonicityList); for (SqlNode selectItem : selectList) { - bb.columnMonotonicities.add(selectItem.getMonotonicity(bb.scope)); + bb.columnMonotonicities.add( + selectItem.getMonotonicity(bb.scope)); } } @@ -2975,10 +3609,19 @@ public class SqlToRelConverter { * already * @param columnMonotonicityList List of monotonicity, one per column */ - protected void extraSelectItems(Blackboard bb, SqlSelect select, List exprList, List nameList, Collection aliasList, List columnMonotonicityList) { + protected void extraSelectItems( + Blackboard bb, + SqlSelect select, + List exprList, + List nameList, + Collection aliasList, + List columnMonotonicityList) { } - private String deriveAlias(final SqlNode node, Collection aliases, final int ordinal) { + private String deriveAlias( + final SqlNode node, + Collection aliases, + final int ordinal) { String alias = validator.deriveAlias(node, ordinal); if ((alias == null) || aliases.contains(alias)) { String aliasBase = (alias == null) ? "EXPR$" : alias; @@ -3003,7 +3646,9 @@ public class SqlToRelConverter { /** * Converts a SELECT statement's parse tree into a relational expression. */ - public RelNode convertValues(SqlCall values, RelDataType targetRowType) { + public RelNode convertValues( + SqlCall values, + RelDataType targetRowType) { final SqlValidatorScope scope = validator.getOverScope(values); assert scope != null; final Blackboard bb = createBlackboard(scope, null, false); @@ -3011,8 +3656,6 @@ public class SqlToRelConverter { return bb.root; } - //~ Inner Classes ---------------------------------------------------------- - /** * Converts a values clause (as in "INSERT INTO T(x,y) VALUES (1,2)") into a * relational expression. @@ -3021,10 +3664,19 @@ public class SqlToRelConverter { * @param values Call to SQL VALUES operator * @param targetRowType Target row type */ - private void convertValuesImpl(Blackboard bb, SqlCall values, RelDataType targetRowType) { + private void convertValuesImpl( + Blackboard bb, + SqlCall values, + RelDataType targetRowType) { // Attempt direct conversion to LogicalValues; if that fails, deal with - // fancy stuff like subqueries below. - RelNode valuesRel = convertRowValues(bb, values, values.getOperandList(), true, targetRowType); + // fancy stuff like sub-queries below. + RelNode valuesRel = + convertRowValues( + bb, + values, + values.getOperandList(), + true, + targetRowType); if (valuesRel != null) { bb.setRoot(valuesRel, true); return; @@ -3034,21 +3686,37 @@ public class SqlToRelConverter { for (SqlNode rowConstructor1 : values.getOperandList()) { SqlCall rowConstructor = (SqlCall) rowConstructor1; Blackboard tmpBb = createBlackboard(bb.scope, null, false); - replaceSubqueries(tmpBb, rowConstructor, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); + replaceSubQueries(tmpBb, rowConstructor, + RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); final List> exps = new ArrayList<>(); for (Ord operand : Ord.zip(rowConstructor.getOperandList())) { - exps.add(Pair.of(tmpBb.convertExpression(operand.e), validator.deriveAlias(operand.e, operand.i))); - } - RelNode in = (null == tmpBb.root) ? LogicalValues.createOneRow(cluster) : tmpBb.root; - unionRels.add(RelOptUtil.createProject(in, Pair.left(exps), Pair.right(exps), true)); + exps.add( + Pair.of( + tmpBb.convertExpression(operand.e), + validator.deriveAlias(operand.e, operand.i))); + } + RelNode in = + (null == tmpBb.root) + ? LogicalValues.createOneRow(cluster) + : tmpBb.root; + unionRels.add( + RelOptUtil.createProject( + in, + Pair.left(exps), + Pair.right(exps), + true)); } if (unionRels.size() == 0) { throw Util.newInternal("empty values clause"); } else if (unionRels.size() == 1) { - bb.setRoot(unionRels.get(0), true); + bb.setRoot( + unionRels.get(0), + true); } else { - bb.setRoot(LogicalUnion.create(unionRels, true), true); + bb.setRoot( + LogicalUnion.create(unionRels, true), + true); } // REVIEW jvs 22-Jan-2004: should I add @@ -3056,422 +3724,193 @@ public class SqlToRelConverter { // ? } + //~ Inner Classes ---------------------------------------------------------- + /** - * Interface to define the configuration for a SqlToRelConverter. - * Provides methods to set each configuration option. - * - * @see ConfigBuilder - * @see SqlToRelConverter#configBuilder() + * Workspace for translating an individual SELECT statement (or sub-SELECT). */ - public interface Config { - /** Default configuration. */ - Config DEFAULT = configBuilder().build(); - - /** Returns the {@code convertTableAccess} option. Controls whether table - * access references are converted to physical rels immediately. The - * optimizer doesn't like leaf rels to have {@link Convention#NONE}. - * However, if we are doing further conversion passes (e.g. - * {@link RelStructuredTypeFlattener}), then we may need to defer - * conversion. */ - boolean isConvertTableAccess(); - - /** Returns the {@code decorrelationEnabled} option. Controls whether to - * disable subquery decorrelation when needed. e.g. if outer joins are not - * supported. */ - boolean isDecorrelationEnabled(); - - /** Returns the {@code trimUnusedFields} option. Controls whether to trim - * unused fields as part of the conversion process. */ - boolean isTrimUnusedFields(); - - /** Returns the {@code createValuesRel} option. Controls whether instances - * of {@link org.apache.calcite.rel.logical.LogicalValues} are generated. - * These may not be supported by all physical implementations. */ - boolean isCreateValuesRel(); - - /** Returns the {@code explain} option. Describes whether the current - * statement is part of an EXPLAIN PLAN statement. */ - boolean isExplain(); + protected class Blackboard implements SqlRexContext, SqlVisitor { + /** + * Collection of {@link RelNode} objects which correspond to a SELECT + * statement. + */ + public final SqlValidatorScope scope; + private final Map nameToNodeMap; + public RelNode root; + private List inputs; + private final Map mapCorrelateToRex = + new HashMap<>(); - /** Returns the {@code expand} option. Controls whether to expand - * sub-queries. If false, each sub-query becomes a - * {@link org.apache.calcite.rex.RexSubQuery}. */ - boolean isExpand(); + final List cursors = new ArrayList<>(); - /** Returns the {@code inSubqueryThreshold} option, - * default {@link #DEFAULT_IN_SUBQUERY_THRESHOLD}. Controls 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 {@link Integer#MAX_VALUE} forces usage of OR in all - * cases. */ - int getInSubqueryThreshold(); - } + /** + * List of IN and EXISTS nodes inside this + * SELECT statement (but not inside sub-queries). + */ + private final Set subQueryList = new LinkedHashSet<>(); - /** Deferred lookup. */ - private static class DeferredLookup { - Blackboard bb; - String originalRelName; + /** + * Workspace for building aggregates. + */ + AggConverter agg; - DeferredLookup(Blackboard bb, String originalRelName) { - this.bb = bb; - this.originalRelName = originalRelName; - } + /** + * When converting window aggregate, we need to know if the window is + * guaranteed to be non-empty. + */ + SqlWindow window; - public RexFieldAccess getFieldAccess(CorrelationId name) { - return (RexFieldAccess) bb.mapCorrelateToRex.get(name); - } + /** + * Project the groupby expressions out of the root of this sub-select. + * Sub-queries can reference group by expressions projected from the + * "right" to the sub-query. + */ + private final Map> + mapRootRelToFieldProjection = new HashMap<>(); - public String getOriginalRelName() { - return originalRelName; - } - } + private final List columnMonotonicities = + new ArrayList<>(); - /** - * Context to find a relational expression to a field offset. - */ - private static class LookupContext { - private final List> relOffsetList = new ArrayList<>(); + private final List systemFieldList = new ArrayList<>(); + final boolean top; /** - * Creates a LookupContext with multiple input relational expressions. + * Creates a Blackboard. * - * @param bb Context for translating this subquery - * @param rels Relational expressions - * @param systemFieldCount Number of system fields + * @param scope Name-resolution scope for expressions validated + * within this query. Can be null if this Blackboard is + * for a leaf node, say + * @param nameToNodeMap Map which translates the expression to map a + * given parameter into, if translating expressions; + * null otherwise + * @param top Whether this is the root of the query */ - LookupContext(Blackboard bb, List rels, int systemFieldCount) { - bb.flatten(rels, systemFieldCount, new int[] { 0 }, relOffsetList); + protected Blackboard(SqlValidatorScope scope, + Map nameToNodeMap, boolean top) { + this.scope = scope; + this.nameToNodeMap = nameToNodeMap; + this.top = top; + } + + public RexNode register( + RelNode rel, + JoinRelType joinType) { + return register(rel, joinType, null); } /** - * Returns the relational expression with a given offset, and the - * ordinal in the combined row of its first field. - * - *

    For example, in {@code Emp JOIN Dept}, findRel(1) returns the - * relational expression for {@code Dept} and offset 6 (because - * {@code Emp} has 6 fields, therefore the first field of {@code Dept} - * is field 6. + * Registers a relational expression. * - * @param offset Offset of relational expression in FROM clause - * @return Relational expression and the ordinal of its first field + * @param rel Relational expression + * @param joinType Join type + * @param leftKeys LHS of IN clause, or null for expressions + * other than IN + * @return Expression with which to refer to the row (or partial row) + * coming from this relational expression's side of the join */ - Pair findRel(int offset) { - return relOffsetList.get(offset); - } - } + public RexNode register( + RelNode rel, + JoinRelType joinType, + List leftKeys) { + assert joinType != null; + if (root == null) { + assert leftKeys == null; + setRoot(rel, false); + return rexBuilder.makeRangeReference( + root.getRowType(), + 0, + false); + } - /** A sub-query, whether it needs to be translated using 2- or 3-valued - * logic. */ - private static class SubQuery { - final SqlNode node; - final RelOptUtil.Logic logic; - RexNode expr; + final RexNode joinCond; + final int origLeftInputCount = root.getRowType().getFieldCount(); + if (leftKeys != null) { + List newLeftInputExpr = Lists.newArrayList(); + for (int i = 0; i < origLeftInputCount; i++) { + newLeftInputExpr.add(rexBuilder.makeInputRef(root, i)); + } - private SubQuery(SqlNode node, RelOptUtil.Logic logic) { - this.node = node; - this.logic = logic; - } - } + final List leftJoinKeys = Lists.newArrayList(); + for (RexNode leftKey : leftKeys) { + int index = newLeftInputExpr.indexOf(leftKey); + if (index < 0 || joinType == JoinRelType.LEFT) { + index = newLeftInputExpr.size(); + newLeftInputExpr.add(leftKey); + } + leftJoinKeys.add(index); + } - /** - * Visitor that collects all aggregate functions in a {@link SqlNode} tree. - */ - private static class AggregateFinder extends SqlBasicVisitor { - final SqlNodeList list = new SqlNodeList(SqlParserPos.ZERO); + RelNode newLeftInput = + RelOptUtil.createProject( + root, + newLeftInputExpr, + null, + true); - @Override - public Void visit(SqlCall call) { - // ignore window aggregates and ranking functions (associated with OVER operator) - if (call.getOperator().getKind() == SqlKind.OVER) { - return null; - } - if (call.getOperator().isAggregator()) { - list.add(call); - return null; - } + // maintain the group by mapping in the new LogicalProject + if (mapRootRelToFieldProjection.containsKey(root)) { + mapRootRelToFieldProjection.put( + newLeftInput, + mapRootRelToFieldProjection.get(root)); + } - // Don't traverse into sub-queries, even if they contain aggregate - // functions. - if (call instanceof SqlSelect) { - return null; - } + setRoot(newLeftInput, false); - return call.getOperator().acceptCall(this, call); - } - } + // right fields appear after the LHS fields. + final int rightOffset = root.getRowType().getFieldCount() + - newLeftInput.getRowType().getFieldCount(); + final List rightKeys = + Util.range(rightOffset, rightOffset + leftKeys.size()); + + joinCond = + RelOptUtil.createEquiJoinCondition(newLeftInput, leftJoinKeys, + rel, rightKeys, rexBuilder); + } else { + joinCond = rexBuilder.makeLiteral(true); + } - /** Use of a row as a correlating variable by a given relational - * expression. */ - private static class CorrelationUse { - private final CorrelationId id; - private final ImmutableBitSet requiredColumns; - private final RelNode r; + int leftFieldCount = root.getRowType().getFieldCount(); + final RelNode join = + createJoin( + this, + root, + rel, + joinCond, + joinType); - CorrelationUse(CorrelationId id, ImmutableBitSet requiredColumns, RelNode r) { - this.id = id; - this.requiredColumns = requiredColumns; - this.r = r; - } - } + setRoot(join, false); - /** Builder for a {@link Config}. */ - public static class ConfigBuilder { - private boolean convertTableAccess = true; - private boolean decorrelationEnabled = true; - private boolean trimUnusedFields = false; - private boolean createValuesRel = true; - private boolean explain; - private boolean expand = true; - private int inSubqueryThreshold = DEFAULT_IN_SUBQUERY_THRESHOLD; + if (leftKeys != null + && joinType == JoinRelType.LEFT) { + final int leftKeyCount = leftKeys.size(); + int rightFieldLength = rel.getRowType().getFieldCount(); + assert leftKeyCount == rightFieldLength - 1; - private ConfigBuilder() { - } - - /** Sets configuration identical to a given {@link Config}. */ - public ConfigBuilder withConfig(Config config) { - this.convertTableAccess = config.isConvertTableAccess(); - this.decorrelationEnabled = config.isDecorrelationEnabled(); - this.trimUnusedFields = config.isTrimUnusedFields(); - this.createValuesRel = config.isCreateValuesRel(); - this.explain = config.isExplain(); - this.expand = config.isExpand(); - this.inSubqueryThreshold = config.getInSubqueryThreshold(); - return this; - } - - public ConfigBuilder withConvertTableAccess(boolean convertTableAccess) { - this.convertTableAccess = convertTableAccess; - return this; - } - - public ConfigBuilder withDecorrelationEnabled(boolean enabled) { - this.decorrelationEnabled = enabled; - return this; - } - - public ConfigBuilder withTrimUnusedFields(boolean trimUnusedFields) { - this.trimUnusedFields = trimUnusedFields; - return this; - } - - public ConfigBuilder withCreateValuesRel(boolean createValuesRel) { - this.createValuesRel = createValuesRel; - return this; - } - - public ConfigBuilder withExplain(boolean explain) { - this.explain = explain; - return this; - } - - public ConfigBuilder withExpand(boolean expand) { - this.expand = expand; - return this; - } - - public ConfigBuilder withInSubqueryThreshold(int inSubqueryThreshold) { - this.inSubqueryThreshold = inSubqueryThreshold; - return this; - } - - /** Builds a {@link Config}. */ - public Config build() { - return new ConfigImpl(convertTableAccess, decorrelationEnabled, trimUnusedFields, createValuesRel, explain, expand, inSubqueryThreshold); - } - } - - /** Implementation of {@link Config}. - * Called by builder; all values are in private final fields. */ - private static class ConfigImpl implements Config { - private final boolean convertTableAccess; - private final boolean decorrelationEnabled; - private final boolean trimUnusedFields; - private final boolean createValuesRel; - private final boolean explain; - private final int inSubqueryThreshold; - private final boolean expand; - - private ConfigImpl(boolean convertTableAccess, boolean decorrelationEnabled, boolean trimUnusedFields, boolean createValuesRel, boolean explain, boolean expand, int inSubqueryThreshold) { - this.convertTableAccess = convertTableAccess; - this.decorrelationEnabled = decorrelationEnabled; - this.trimUnusedFields = trimUnusedFields; - this.createValuesRel = createValuesRel; - this.explain = explain; - this.expand = expand; - this.inSubqueryThreshold = inSubqueryThreshold; - } - - public boolean isConvertTableAccess() { - return convertTableAccess; - } - - public boolean isDecorrelationEnabled() { - return decorrelationEnabled; - } - - public boolean isTrimUnusedFields() { - return trimUnusedFields; - } - - public boolean isCreateValuesRel() { - return createValuesRel; - } - - public boolean isExplain() { - return explain; - } - - public boolean isExpand() { - return expand; - } - - public int getInSubqueryThreshold() { - return inSubqueryThreshold; - } - } - - /** - * Workspace for translating an individual SELECT statement (or sub-SELECT). - */ - protected class Blackboard implements SqlRexContext, SqlVisitor { - /** - * Collection of {@link RelNode} objects which correspond to a SELECT - * statement. - */ - public final SqlValidatorScope scope; - final List cursors = new ArrayList<>(); - final boolean top; - private final Map nameToNodeMap; - private final Map mapCorrelateToRex = new HashMap<>(); - /** - * List of IN and EXISTS nodes inside this - * SELECT statement (but not inside sub-queries). - */ - private final Set subqueryList = Sets.newLinkedHashSet(); - /** - * Project the groupby expressions out of the root of this sub-select. - * Subqueries can reference group by expressions projected from the - * "right" to the subquery. - */ - private final Map> mapRootRelToFieldProjection = new HashMap<>(); - private final List columnMonotonicities = new ArrayList<>(); - private final List systemFieldList = new ArrayList<>(); - public RelNode root; - /** - * Workspace for building aggregates. - */ - AggConverter agg; - /** - * When converting window aggregate, we need to know if the window is - * guaranteed to be non-empty. - */ - SqlWindow window; - private List inputs; - private boolean subqueryNeedsOuterJoin; - - /** - * Creates a Blackboard. - * - * @param scope Name-resolution scope for expressions validated - * within this query. Can be null if this Blackboard is - * for a leaf node, say - * @param nameToNodeMap Map which translates the expression to map a - * given parameter into, if translating expressions; - * null otherwise - * @param top Whether this is the root of the query - */ - protected Blackboard(SqlValidatorScope scope, Map nameToNodeMap, boolean top) { - this.scope = scope; - this.nameToNodeMap = nameToNodeMap; - this.top = top; - subqueryNeedsOuterJoin = false; - } - - public RexNode register(RelNode rel, JoinRelType joinType) { - return register(rel, joinType, null); - } - - /** - * Registers a relational expression. - * - * @param rel Relational expression - * @param joinType Join type - * @param leftKeys LHS of IN clause, or null for expressions - * other than IN - * @return Expression with which to refer to the row (or partial row) - * coming from this relational expression's side of the join - */ - public RexNode register(RelNode rel, JoinRelType joinType, List leftKeys) { - assert joinType != null; - if (root == null) { - assert leftKeys == null; - setRoot(rel, false); - return rexBuilder.makeRangeReference(root.getRowType(), 0, false); - } - - final RexNode joinCond; - final int origLeftInputCount = root.getRowType().getFieldCount(); - if (leftKeys != null) { - List newLeftInputExpr = Lists.newArrayList(); - for (int i = 0; i < origLeftInputCount; i++) { - newLeftInputExpr.add(rexBuilder.makeInputRef(root, i)); - } - - final List leftJoinKeys = Lists.newArrayList(); - for (RexNode leftKey : leftKeys) { - int index = newLeftInputExpr.indexOf(leftKey); - if (index < 0 || joinType == JoinRelType.LEFT) { - index = newLeftInputExpr.size(); - newLeftInputExpr.add(leftKey); - } - leftJoinKeys.add(index); - } - - RelNode newLeftInput = RelOptUtil.createProject(root, newLeftInputExpr, null, true); - - // maintain the group by mapping in the new LogicalProject - if (mapRootRelToFieldProjection.containsKey(root)) { - mapRootRelToFieldProjection.put(newLeftInput, mapRootRelToFieldProjection.get(root)); - } - - setRoot(newLeftInput, false); - - // right fields appear after the LHS fields. - final int rightOffset = root.getRowType().getFieldCount() - newLeftInput.getRowType().getFieldCount(); - final List rightKeys = Util.range(rightOffset, rightOffset + leftKeys.size()); - - joinCond = RelOptUtil.createEquiJoinCondition(newLeftInput, leftJoinKeys, rel, rightKeys, rexBuilder); - } else { - joinCond = rexBuilder.makeLiteral(true); - } - - int leftFieldCount = root.getRowType().getFieldCount(); - final RelNode join = createJoin(this, root, rel, joinCond, joinType); - - setRoot(join, false); - - if (leftKeys != null && joinType == JoinRelType.LEFT) { - final int leftKeyCount = leftKeys.size(); - int rightFieldLength = rel.getRowType().getFieldCount(); - assert leftKeyCount == rightFieldLength - 1; - - final int rexRangeRefLength = leftKeyCount + rightFieldLength; - RelDataType returnType = typeFactory.createStructType(new AbstractList>() { - public Map.Entry get(int index) { - return join.getRowType().getFieldList().get(origLeftInputCount + index); - } - - public int size() { - return rexRangeRefLength; - } - }); - - return rexBuilder.makeRangeReference(returnType, origLeftInputCount, false); - } else { - return rexBuilder.makeRangeReference(rel.getRowType(), leftFieldCount, joinType.generatesNullsOnRight()); - } + final int rexRangeRefLength = leftKeyCount + rightFieldLength; + RelDataType returnType = + typeFactory.createStructType( + new AbstractList>() { + public Map.Entry get( + int index) { + return join.getRowType().getFieldList() + .get(origLeftInputCount + index); + } + + public int size() { + return rexRangeRefLength; + } + }); + + return rexBuilder.makeRangeReference( + returnType, + origLeftInputCount, + false); + } else { + return rexBuilder.makeRangeReference( + rel.getRowType(), + leftFieldCount, + joinType.generatesNullsOnRight()); + } } /** @@ -3482,19 +3921,23 @@ public class SqlToRelConverter { * @param leaf Whether the relational expression is a leaf, that is, * derived from an atomic relational expression such as a table * name in the from clause, or the projection on top of a - * select-subquery. In particular, relational expressions + * select-sub-query. In particular, relational expressions * derived from JOIN operators are not leaves, but set * expressions are. */ public void setRoot(RelNode root, boolean leaf) { - setRoot(Collections.singletonList(root), root, root instanceof LogicalJoin); + setRoot( + Collections.singletonList(root), root, root instanceof LogicalJoin); if (leaf) { leaves.add(root); } this.columnMonotonicities.clear(); } - private void setRoot(List inputs, RelNode root, boolean hasSystemFields) { + private void setRoot( + List inputs, + RelNode root, + boolean hasSystemFields) { this.inputs = inputs; this.root = root; this.systemFieldList.clear(); @@ -3531,11 +3974,13 @@ public class SqlToRelConverter { if (nameToNodeMap != null && qualified.prefixLength == 1) { RexNode node = nameToNodeMap.get(qualified.identifier.names.get(0)); if (node == null) { - throw Util.newInternal("Unknown identifier '" + qualified.identifier + "' encountered while expanding expression"); + throw Util.newInternal("Unknown identifier '" + qualified.identifier + + "' encountered while expanding expression"); } return Pair.of(node, null); } - final SqlValidatorScope.ResolvedImpl resolved = new SqlValidatorScope.ResolvedImpl(); + final SqlValidatorScope.ResolvedImpl resolved = + new SqlValidatorScope.ResolvedImpl(); scope.resolve(qualified.prefix(), false, resolved); if (!(resolved.count() == 1)) { return null; @@ -3549,7 +3994,8 @@ public class SqlToRelConverter { final SqlValidatorScope ancestorScope = resolve.scope; boolean isParent = ancestorScope != scope; if ((inputs != null) && !isParent) { - final LookupContext rels = new LookupContext(this, inputs, systemFieldList.size()); + final LookupContext rels = + new LookupContext(this, inputs, systemFieldList.size()); final RexNode node = lookup(resolve.path.steps().get(0).i, rels); if (node == null) { return null; @@ -3561,15 +4007,19 @@ public class SqlToRelConverter { // converted yet. This occurs when from items are correlated, // e.g. "select from emp as emp join emp.getDepts() as dept". // Create a temporary expression. - DeferredLookup lookup = new DeferredLookup(this, qualified.identifier.names.get(0)); + DeferredLookup lookup = + new DeferredLookup(this, qualified.identifier.names.get(0)); final CorrelationId correlId = cluster.createCorrel(); mapCorrelToDeferred.put(correlId, lookup); if (resolve.path.steps().get(0).i < 0) { - return Pair.of(rexBuilder.makeCorrel(foundNs.getRowType(), correlId), null); + return Pair.of(rexBuilder.makeCorrel(foundNs.getRowType(), correlId), + null); } else { - final RelDataTypeFactory.FieldInfoBuilder builder = typeFactory.builder(); + final RelDataTypeFactory.FieldInfoBuilder builder = + typeFactory.builder(); final ListScope ancestorScope1 = (ListScope) resolve.scope; - final ImmutableMap.Builder fields = ImmutableMap.builder(); + final ImmutableMap.Builder fields = + ImmutableMap.builder(); int i = 0; int offset = 0; for (SqlValidatorNamespace c : ancestorScope1.getChildren()) { @@ -3582,8 +4032,9 @@ public class SqlToRelConverter { ++i; offset += c.getRowType().getFieldCount(); } - final RexNode c = rexBuilder.makeCorrel(builder.uniquify().build(), correlId); - return Pair.> of(c, fields.build()); + final RexNode c = + rexBuilder.makeCorrel(builder.uniquify().build(), correlId); + return Pair.>of(c, fields.build()); } } } @@ -3592,9 +4043,14 @@ public class SqlToRelConverter { * Creates an expression with which to reference the expression whose * offset in its from-list is {@code offset}. */ - RexNode lookup(int offset, LookupContext lookupContext) { + RexNode lookup( + int offset, + LookupContext lookupContext) { Pair pair = lookupContext.findRel(offset); - return rexBuilder.makeRangeReference(pair.left.getRowType(), pair.right, false); + return rexBuilder.makeRangeReference( + pair.left.getRowType(), + pair.right, + false); } RelDataTypeField getRootField(RexInputRef inputRef) { @@ -3614,31 +4070,41 @@ public class SqlToRelConverter { throw new AssertionError(); } - public void flatten(List rels, int systemFieldCount, int[] start, List> relOffsetList) { + public void flatten( + List rels, + int systemFieldCount, + int[] start, + List> relOffsetList) { for (RelNode rel : rels) { if (leaves.contains(rel)) { - relOffsetList.add(Pair.of(rel, start[0])); + relOffsetList.add( + Pair.of(rel, start[0])); start[0] += rel.getRowType().getFieldCount(); } else { - if (rel instanceof LogicalJoin || rel instanceof LogicalAggregate) { + if (rel instanceof LogicalJoin + || rel instanceof LogicalAggregate) { start[0] += systemFieldCount; } - flatten(rel.getInputs(), systemFieldCount, start, relOffsetList); + flatten( + rel.getInputs(), + systemFieldCount, + start, + relOffsetList); } } } - void registerSubquery(SqlNode node, RelOptUtil.Logic logic) { - for (SubQuery subQuery : subqueryList) { + void registerSubQuery(SqlNode node, RelOptUtil.Logic logic) { + for (SubQuery subQuery : subQueryList) { if (node.equalsDeep(subQuery.node, Litmus.IGNORE)) { return; } } - subqueryList.add(new SubQuery(node, logic)); + subQueryList.add(new SubQuery(node, logic)); } - SubQuery getSubquery(SqlNode expr) { - for (SubQuery subQuery : subqueryList) { + SubQuery getSubQuery(SqlNode expr) { + for (SubQuery subQuery : subQueryList) { if (expr.equalsDeep(subQuery.node, Litmus.IGNORE)) { return subQuery; } @@ -3688,88 +4154,101 @@ public class SqlToRelConverter { final SqlNode query; final RelRoot root; switch (kind) { - case IN: - call = (SqlCall) expr; - query = call.operand(1); - if (!(query instanceof SqlNodeList)) { - final SqlInOperator op = (SqlInOperator) call.getOperator(); - root = convertQueryRecursive(query, false, null); - final SqlNode operand = call.operand(0); - List nodes; - switch (operand.getKind()) { - case ROW: - nodes = ((SqlCall) operand).getOperandList(); - break; - default: - nodes = ImmutableList.of(operand); - } - final ImmutableList.Builder builder = ImmutableList.builder(); - for (SqlNode node : nodes) { - builder.add(convertExpression(node)); + case IN: + call = (SqlCall) expr; + query = call.operand(1); + if (!(query instanceof SqlNodeList)) { + final SqlInOperator op = (SqlInOperator) call.getOperator(); + root = convertQueryRecursive(query, false, null); + final SqlNode operand = call.operand(0); + List nodes; + switch (operand.getKind()) { + case ROW: + nodes = ((SqlCall) operand).getOperandList(); + break; + default: + nodes = ImmutableList.of(operand); + } + final ImmutableList.Builder builder = + ImmutableList.builder(); + for (SqlNode node : nodes) { + builder.add(convertExpression(node)); + } + final RexSubQuery in = RexSubQuery.in(root.rel, builder.build()); + return op.isNotIn() + ? rexBuilder.makeCall(SqlStdOperatorTable.NOT, in) + : in; } - final RexSubQuery in = RexSubQuery.in(root.rel, builder.build()); - return op.isNotIn() ? rexBuilder.makeCall(SqlStdOperatorTable.NOT, in) : in; - } - break; + break; - case EXISTS: - call = (SqlCall) expr; - query = Iterables.getOnlyElement(call.getOperandList()); - root = convertQueryRecursive(query, false, null); - RelNode rel = root.rel; - while (rel instanceof Project || rel instanceof Sort && ((Sort) rel).fetch == null && ((Sort) rel).offset == null) { - rel = ((SingleRel) rel).getInput(); - } - return RexSubQuery.exists(rel); + case EXISTS: + call = (SqlCall) expr; + query = Iterables.getOnlyElement(call.getOperandList()); + root = convertQueryRecursive(query, false, null); + RelNode rel = root.rel; + while (rel instanceof Project + || rel instanceof Sort + && ((Sort) rel).fetch == null + && ((Sort) rel).offset == null) { + rel = ((SingleRel) rel).getInput(); + } + return RexSubQuery.exists(rel); - case SCALAR_QUERY: - call = (SqlCall) expr; - query = Iterables.getOnlyElement(call.getOperandList()); - root = convertQueryRecursive(query, false, null); - return RexSubQuery.scalar(root.rel); + case SCALAR_QUERY: + call = (SqlCall) expr; + query = Iterables.getOnlyElement(call.getOperandList()); + root = convertQueryRecursive(query, false, null); + return RexSubQuery.scalar(root.rel); } } switch (kind) { - case CURSOR: - case IN: - subQuery = getSubquery(expr); - - assert subQuery != null; - rex = subQuery.expr; - assert rex != null : "rex != null"; - return rex; - - case SELECT: - case EXISTS: - case SCALAR_QUERY: - subQuery = getSubquery(expr); - assert subQuery != null; - rex = subQuery.expr; - assert rex != null : "rex != null"; - - if (((kind == SqlKind.SCALAR_QUERY) || (kind == SqlKind.EXISTS)) && isConvertedSubq(rex)) { - // scalar subquery or EXISTS has been converted to a - // constant - return rex; - } + case CURSOR: + case IN: + subQuery = Preconditions.checkNotNull(getSubQuery(expr)); + rex = Preconditions.checkNotNull(subQuery.expr); + return StandardConvertletTable.castToValidatedType(expr, rex, + validator, rexBuilder); - // The indicator column is the last field of the subquery. - RexNode fieldAccess = rexBuilder.makeFieldAccess(rex, rex.getType().getFieldCount() - 1); + case SELECT: + case EXISTS: + case SCALAR_QUERY: + subQuery = getSubQuery(expr); + assert subQuery != null; + rex = subQuery.expr; + assert rex != null : "rex != null"; + + if (((kind == SqlKind.SCALAR_QUERY) + || (kind == SqlKind.EXISTS)) + && isConvertedSubq(rex)) { + // scalar sub-query or EXISTS has been converted to a + // constant + return rex; + } - // The indicator column will be nullable if it comes from - // the null-generating side of the join. For EXISTS, add an - // "IS TRUE" check so that the result is "BOOLEAN NOT NULL". - if (fieldAccess.getType().isNullable() && kind == SqlKind.EXISTS) { - fieldAccess = rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, fieldAccess); - } - return fieldAccess; + // The indicator column is the last field of the sub-query. + RexNode fieldAccess = + rexBuilder.makeFieldAccess( + rex, + rex.getType().getFieldCount() - 1); + + // The indicator column will be nullable if it comes from + // the null-generating side of the join. For EXISTS, add an + // "IS TRUE" check so that the result is "BOOLEAN NOT NULL". + if (fieldAccess.getType().isNullable() + && kind == SqlKind.EXISTS) { + fieldAccess = + rexBuilder.makeCall( + SqlStdOperatorTable.IS_NOT_NULL, + fieldAccess); + } + return fieldAccess; - case OVER: - return convertOver(this, expr); + case OVER: + return convertOver(this, expr); - default: - // fall through + default: + // fall through } // Apply standard conversions. @@ -3784,19 +4263,19 @@ public class SqlToRelConverter { */ public RexNode convertSortExpression(SqlNode expr, Set flags) { switch (expr.getKind()) { - case DESCENDING: - case NULLS_LAST: - case NULLS_FIRST: - flags.add(expr.getKind()); - final SqlNode operand = ((SqlCall) expr).operand(0); - return convertSortExpression(operand, flags); - default: - return convertExpression(expr); + case DESCENDING: + case NULLS_LAST: + case NULLS_FIRST: + flags.add(expr.getKind()); + final SqlNode operand = ((SqlCall) expr).operand(0); + return convertSortExpression(operand, flags); + default: + return convertExpression(expr); } } /** - * Determines whether a RexNode corresponds to a subquery that's been + * Determines whether a RexNode corresponds to a sub-query that's been * converted to a constant. * * @param rex the expression to be examined @@ -3804,7 +4283,8 @@ public class SqlToRelConverter { * a literal that is being cast */ private boolean isConvertedSubq(RexNode rex) { - if ((rex instanceof RexLiteral) || (rex instanceof RexDynamicParam)) { + if ((rex instanceof RexLiteral) + || (rex instanceof RexDynamicParam)) { return true; } if (rex instanceof RexCall) { @@ -3819,7 +4299,6 @@ public class SqlToRelConverter { return false; } - // implement SqlRexContext public int getGroupCount() { if (agg != null) { return agg.groupExprs.size(); @@ -3830,34 +4309,28 @@ public class SqlToRelConverter { return -1; } - // implement SqlRexContext public RexBuilder getRexBuilder() { return rexBuilder; } - // implement SqlRexContext - public RexRangeRef getSubqueryExpr(SqlCall call) { - final SubQuery subQuery = getSubquery(call); + public RexRangeRef getSubQueryExpr(SqlCall call) { + final SubQuery subQuery = getSubQuery(call); assert subQuery != null; return (RexRangeRef) subQuery.expr; } - // implement SqlRexContext public RelDataTypeFactory getTypeFactory() { return typeFactory; } - // implement SqlRexContext public DefaultValueFactory getDefaultValueFactory() { return defaultValueFactory; } - // implement SqlRexContext public SqlValidator getValidator() { return validator; } - // implement SqlRexContext public RexNode convertLiteral(SqlLiteral literal) { return exprConverter.convertLiteral(this, literal); } @@ -3866,43 +4339,38 @@ public class SqlToRelConverter { return exprConverter.convertInterval(this, intervalQualifier); } - // implement SqlVisitor public RexNode visit(SqlLiteral literal) { return exprConverter.convertLiteral(this, literal); } - // implement SqlVisitor public RexNode visit(SqlCall call) { if (agg != null) { final SqlOperator op = call.getOperator(); - if (window == null && (op.isAggregator() || op.getKind() == SqlKind.FILTER)) { + if (window == null + && (op.isAggregator() || op.getKind() == SqlKind.FILTER)) { return agg.lookupAggregates(call); } } - return exprConverter.convertCall(this, new SqlCallBinding(validator, scope, call).permutedCall()); + return exprConverter.convertCall(this, + new SqlCallBinding(validator, scope, call).permutedCall()); } - // implement SqlVisitor public RexNode visit(SqlNodeList nodeList) { throw new UnsupportedOperationException(); } - // implement SqlVisitor public RexNode visit(SqlIdentifier id) { return convertIdentifier(this, id); } - // implement SqlVisitor public RexNode visit(SqlDataTypeSpec type) { throw new UnsupportedOperationException(); } - // implement SqlVisitor public RexNode visit(SqlDynamicParam param) { return convertDynamicParam(param); } - // implement SqlVisitor public RexNode visit(SqlIntervalQualifier intervalQualifier) { return convertInterval(intervalQualifier); } @@ -3913,34 +4381,65 @@ public class SqlToRelConverter { } + /** Deferred lookup. */ + private static class DeferredLookup { + Blackboard bb; + String originalRelName; + + DeferredLookup( + Blackboard bb, + String originalRelName) { + this.bb = bb; + this.originalRelName = originalRelName; + } + + public RexFieldAccess getFieldAccess(CorrelationId name) { + return (RexFieldAccess) bb.mapCorrelateToRex.get(name); + } + + public String getOriginalRelName() { + return originalRelName; + } + } + /** * An implementation of DefaultValueFactory which always supplies NULL. */ class NullDefaultValueFactory implements DefaultValueFactory { - public boolean isGeneratedAlways(RelOptTable table, int iColumn) { + public boolean isGeneratedAlways( + RelOptTable table, + int iColumn) { return false; } - public RexNode newColumnDefaultValue(RelOptTable table, int iColumn) { + public RexNode newColumnDefaultValue( + RelOptTable table, + int iColumn) { return rexBuilder.constantNull(); } - public RexNode newAttributeInitializer(RelDataType type, SqlFunction constructor, int iAttribute, List constructorArgs) { + public RexNode newAttributeInitializer( + RelDataType type, + SqlFunction constructor, + int iAttribute, + List constructorArgs) { return rexBuilder.constantNull(); } } /** - * A default implementation of SubqueryConverter that does no conversion. + * A default implementation of SubQueryConverter that does no conversion. */ - private class NoOpSubqueryConverter implements SubqueryConverter { - // implement SubqueryConverter - public boolean canConvertSubquery() { + private class NoOpSubQueryConverter implements SubQueryConverter { + public boolean canConvertSubQuery() { return false; } - // implement SubqueryConverter - public RexNode convertSubquery(SqlCall subquery, SqlToRelConverter parentConverter, boolean isExists, boolean isExplain) { + public RexNode convertSubQuery( + SqlCall subQuery, + SqlToRelConverter parentConverter, + boolean isExists, + boolean isExplain) { throw new IllegalArgumentException(); } } @@ -3965,14 +4464,16 @@ public class SqlToRelConverter { * */ protected class AggConverter implements SqlVisitor { - public final AggregatingSelectScope aggregatingSelectScope; private final Blackboard bb; + public final AggregatingSelectScope aggregatingSelectScope; + private final Map nameMap = Maps.newHashMap(); /** * The group-by expressions, in {@link SqlNode} format. */ - private final SqlNodeList groupExprs = new SqlNodeList(SqlParserPos.ZERO); + private final SqlNodeList groupExprs = + new SqlNodeList(SqlParserPos.ZERO); /** * Input expressions for the group columns and aggregates, in @@ -3990,7 +4491,8 @@ public class SqlToRelConverter { private final List aggCalls = Lists.newArrayList(); private final Map aggMapping = Maps.newHashMap(); - private final Map aggCallMapping = Maps.newHashMap(); + private final Map aggCallMapping = + Maps.newHashMap(); /** Are we directly inside a windowed aggregate? */ private boolean inOver = false; @@ -4006,7 +4508,8 @@ public class SqlToRelConverter { */ public AggConverter(Blackboard bb, SqlSelect select) { this.bb = bb; - this.aggregatingSelectScope = (AggregatingSelectScope) bb.getValidator().getSelectScope(select); + this.aggregatingSelectScope = + (AggregatingSelectScope) bb.getValidator().getSelectScope(select); // Collect all expressions used in the select list so that aggregate // calls can be named correctly. @@ -4014,7 +4517,9 @@ public class SqlToRelConverter { for (int i = 0; i < selectList.size(); i++) { SqlNode selectItem = selectList.get(i); String name = null; - if (SqlUtil.isCallTo(selectItem, SqlStdOperatorTable.AS)) { + if (SqlUtil.isCallTo( + selectItem, + SqlStdOperatorTable.AS)) { final SqlCall call = (SqlCall) selectItem; selectItem = call.operand(0); name = call.operand(1).toString(); @@ -4088,13 +4593,13 @@ public class SqlToRelConverter { public Void visit(SqlCall call) { switch (call.getKind()) { - case FILTER: - translateAgg((SqlCall) call.operand(0), call.operand(1), call); - return null; - case SELECT: - // rchen 2006-10-17: - // for now do not detect aggregates in subqueries. - return null; + case FILTER: + translateAgg((SqlCall) call.operand(0), call.operand(1), call); + return null; + case SELECT: + // rchen 2006-10-17: + // for now do not detect aggregates in sub-queries. + return null; } final boolean prevInOver = inOver; // Ignore window aggregates and ranking functions (associated with OVER @@ -4131,7 +4636,10 @@ public class SqlToRelConverter { assert bb.agg == this; final List args = new ArrayList<>(); int filterArg = -1; - final List argTypes = call.getOperator() instanceof SqlCountAggFunction ? new ArrayList(call.getOperandList().size()) : null; + final List argTypes = + call.getOperator() instanceof SqlCountAggFunction + ? new ArrayList(call.getOperandList().size()) + : null; try { // switch out of agg mode bb.agg = null; @@ -4158,7 +4666,8 @@ public class SqlToRelConverter { RexNode convertedExpr = bb.convertExpression(filter); assert convertedExpr != null; if (convertedExpr.getType().isNullable()) { - convertedExpr = rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE, convertedExpr); + convertedExpr = + rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE, convertedExpr); } filterArg = lookupOrCreateGroupExpr(convertedExpr); } @@ -4167,16 +4676,33 @@ public class SqlToRelConverter { bb.agg = this; } - final SqlAggFunction aggFunction = (SqlAggFunction) call.getOperator(); + final SqlAggFunction aggFunction = + (SqlAggFunction) call.getOperator(); RelDataType type = validator.deriveType(bb.scope, call); boolean distinct = false; SqlLiteral quantifier = call.getFunctionQuantifier(); - if ((null != quantifier) && (quantifier.getValue() == SqlSelectKeyword.DISTINCT)) { + if ((null != quantifier) + && (quantifier.getValue() == SqlSelectKeyword.DISTINCT)) { distinct = true; } - final AggregateCall aggCall = AggregateCall.create(aggFunction, distinct, args, filterArg, type, nameMap.get(outerCall.toString())); - final AggregatingSelectScope.Resolved r = aggregatingSelectScope.resolved.get(); - RexNode rex = rexBuilder.addAggCall(aggCall, groupExprs.size(), r.indicator, aggCalls, aggCallMapping, argTypes); + final AggregateCall aggCall = + AggregateCall.create( + aggFunction, + distinct, + args, + filterArg, + type, + nameMap.get(outerCall.toString())); + final AggregatingSelectScope.Resolved r = + aggregatingSelectScope.resolved.get(); + RexNode rex = + rexBuilder.addAggCall( + aggCall, + groupExprs.size(), + r.indicator, + aggCalls, + aggCallMapping, + argTypes); aggMapping.put(outerCall, rex); } @@ -4226,55 +4752,62 @@ public class SqlToRelConverter { assert bb.agg == this; switch (call.getKind()) { - case GROUPING: - case GROUPING_ID: - case GROUP_ID: - final RelDataType type = validator.getValidatedNodeType(call); - if (!aggregatingSelectScope.resolved.get().indicator) { - return rexBuilder.makeExactLiteral(TWO.pow(effectiveArgCount(call)).subtract(BigDecimal.ONE), type); - } else { - final List operands; - switch (call.getKind()) { - case GROUP_ID: - operands = ImmutableIntList.range(0, groupExprs.size()); - break; - default: - operands = Lists.newArrayList(); - for (SqlNode operand : call.getOperandList()) { - final int x = lookupGroupExpr(operand); - assert x >= 0; - operands.add(x); + case GROUPING: + case GROUPING_ID: + case GROUP_ID: + final RelDataType type = validator.getValidatedNodeType(call); + if (!aggregatingSelectScope.resolved.get().indicator) { + return rexBuilder.makeExactLiteral( + TWO.pow(effectiveArgCount(call)).subtract(BigDecimal.ONE), type); + } else { + final List operands; + switch (call.getKind()) { + case GROUP_ID: + operands = ImmutableIntList.range(0, groupExprs.size()); + break; + default: + operands = Lists.newArrayList(); + for (SqlNode operand : call.getOperandList()) { + final int x = lookupGroupExpr(operand); + assert x >= 0; + operands.add(x); + } } + RexNode node = null; + int shift = operands.size(); + for (int operand : operands) { + node = bitValue(node, type, operand, --shift); + } + return node; } - RexNode node = null; - int shift = operands.size(); - for (int operand : operands) { - node = bitValue(node, type, operand, --shift); - } - return node; - } } return aggMapping.get(call); } private int effectiveArgCount(SqlCall call) { switch (call.getKind()) { - case GROUPING: - return 1; - case GROUPING_ID: - return call.operandCount(); - case GROUP_ID: - return groupExprs.size(); - default: - throw new AssertionError(call.getKind()); - } - } - - private RexNode bitValue(RexNode previous, RelDataType type, int x, int shift) { - final AggregatingSelectScope.Resolved r = aggregatingSelectScope.resolved.get(); - RexNode node = rexBuilder.makeCall(SqlStdOperatorTable.CASE, rexBuilder.makeInputRef(bb.root, r.groupExprList.size() + x), rexBuilder.makeExactLiteral(BigDecimal.ONE, type), rexBuilder.makeExactLiteral(BigDecimal.ZERO, type)); + case GROUPING: + return 1; + case GROUPING_ID: + return call.operandCount(); + case GROUP_ID: + return groupExprs.size(); + default: + throw new AssertionError(call.getKind()); + } + } + + private RexNode bitValue(RexNode previous, RelDataType type, int x, + int shift) { + final AggregatingSelectScope.Resolved r = + aggregatingSelectScope.resolved.get(); + RexNode node = rexBuilder.makeCall(SqlStdOperatorTable.CASE, + rexBuilder.makeInputRef(bb.root, r.groupExprList.size() + x), + rexBuilder.makeExactLiteral(BigDecimal.ONE, type), + rexBuilder.makeExactLiteral(BigDecimal.ZERO, type)); if (shift > 0) { - node = rexBuilder.makeCall(SqlStdOperatorTable.MULTIPLY, node, rexBuilder.makeExactLiteral(TWO.pow(shift), type)); + node = rexBuilder.makeCall(SqlStdOperatorTable.MULTIPLY, node, + rexBuilder.makeExactLiteral(TWO.pow(shift), type)); } if (previous != null) { node = rexBuilder.makeCall(SqlStdOperatorTable.PLUS, previous, node); @@ -4300,6 +4833,41 @@ public class SqlToRelConverter { } /** + * Context to find a relational expression to a field offset. + */ + private static class LookupContext { + private final List> relOffsetList = + new ArrayList<>(); + + /** + * Creates a LookupContext with multiple input relational expressions. + * + * @param bb Context for translating this sub-query + * @param rels Relational expressions + * @param systemFieldCount Number of system fields + */ + LookupContext(Blackboard bb, List rels, int systemFieldCount) { + bb.flatten(rels, systemFieldCount, new int[]{0}, relOffsetList); + } + + /** + * Returns the relational expression with a given offset, and the + * ordinal in the combined row of its first field. + * + *

    For example, in {@code Emp JOIN Dept}, findRel(1) returns the + * relational expression for {@code Dept} and offset 6 (because + * {@code Emp} has 6 fields, therefore the first field of {@code Dept} + * is field 6. + * + * @param offset Offset of relational expression in FROM clause + * @return Relational expression and the ordinal of its first field + */ + Pair findRel(int offset) { + return relOffsetList.get(offset); + } + } + + /** * Shuttle which walks over a tree of {@link RexNode}s and applies 'over' to * all agg functions. * @@ -4339,7 +4907,11 @@ public class SqlToRelConverter { private final RexWindowBound upperBound; private final SqlWindow window; - HistogramShuttle(List partitionKeys, ImmutableList orderKeys, RexWindowBound lowerBound, RexWindowBound upperBound, SqlWindow window) { + HistogramShuttle( + List partitionKeys, + ImmutableList orderKeys, + RexWindowBound lowerBound, RexWindowBound upperBound, + SqlWindow window) { this.partitionKeys = partitionKeys; this.orderKeys = orderKeys; this.lowerBound = lowerBound; @@ -4356,7 +4928,9 @@ public class SqlToRelConverter { final RelDataType type = call.getType(); List exprs = call.getOperands(); - SqlFunction histogramOp = !ENABLE_HISTOGRAM_AGG ? null : getHistogramOp(aggOp); + SqlFunction histogramOp = !ENABLE_HISTOGRAM_AGG + ? null + : getHistogramOp(aggOp); if (histogramOp != null) { final RelDataType histogramType = computeHistogramType(type); @@ -4364,36 +4938,81 @@ public class SqlToRelConverter { // For DECIMAL, since it's already represented as a bigint we // want to do a reinterpretCast instead of a cast to avoid // losing any precision. - boolean reinterpretCast = type.getSqlTypeName() == SqlTypeName.DECIMAL; + boolean reinterpretCast = + type.getSqlTypeName() == SqlTypeName.DECIMAL; // Replace original expression with CAST of not one // of the supported types if (histogramType != type) { exprs = new ArrayList<>(exprs); - exprs.set(0, reinterpretCast ? rexBuilder.makeReinterpretCast(histogramType, exprs.get(0), rexBuilder.makeLiteral(false)) : rexBuilder.makeCast(histogramType, exprs.get(0))); + exprs.set( + 0, + reinterpretCast + ? rexBuilder.makeReinterpretCast(histogramType, exprs.get(0), + rexBuilder.makeLiteral(false)) + : rexBuilder.makeCast(histogramType, exprs.get(0))); } - RexCallBinding bind = new RexCallBinding(rexBuilder.getTypeFactory(), SqlStdOperatorTable.HISTOGRAM_AGG, exprs, ImmutableList. of()); - - RexNode over = rexBuilder.makeOver(SqlStdOperatorTable.HISTOGRAM_AGG.inferReturnType(bind), SqlStdOperatorTable.HISTOGRAM_AGG, exprs, partitionKeys, orderKeys, lowerBound, upperBound, window.isRows(), window.isAllowPartial(), false); - - RexNode histogramCall = rexBuilder.makeCall(histogramType, histogramOp, ImmutableList.of(over)); + RexCallBinding bind = + new RexCallBinding( + rexBuilder.getTypeFactory(), + SqlStdOperatorTable.HISTOGRAM_AGG, + exprs, + ImmutableList.of()); + + RexNode over = + rexBuilder.makeOver( + SqlStdOperatorTable.HISTOGRAM_AGG + .inferReturnType(bind), + SqlStdOperatorTable.HISTOGRAM_AGG, + exprs, + partitionKeys, + orderKeys, + lowerBound, + upperBound, + window.isRows(), + window.isAllowPartial(), + false); + + RexNode histogramCall = + rexBuilder.makeCall( + histogramType, + histogramOp, + ImmutableList.of(over)); // If needed, post Cast result back to original // type. if (histogramType != type) { if (reinterpretCast) { - histogramCall = rexBuilder.makeReinterpretCast(type, histogramCall, rexBuilder.makeLiteral(false)); + histogramCall = + rexBuilder.makeReinterpretCast( + type, + histogramCall, + rexBuilder.makeLiteral(false)); } else { - histogramCall = rexBuilder.makeCast(type, histogramCall); + histogramCall = + rexBuilder.makeCast(type, histogramCall); } } return histogramCall; } else { - boolean needSum0 = aggOp == SqlStdOperatorTable.SUM && type.isNullable(); - SqlAggFunction aggOpToUse = needSum0 ? SqlStdOperatorTable.SUM0 : aggOp; - return rexBuilder.makeOver(type, aggOpToUse, exprs, partitionKeys, orderKeys, lowerBound, upperBound, window.isRows(), window.isAllowPartial(), needSum0); + boolean needSum0 = aggOp == SqlStdOperatorTable.SUM + && type.isNullable(); + SqlAggFunction aggOpToUse = + needSum0 ? SqlStdOperatorTable.SUM0 + : aggOp; + return rexBuilder.makeOver( + type, + aggOpToUse, + exprs, + partitionKeys, + orderKeys, + lowerBound, + upperBound, + window.isRows(), + window.isAllowPartial(), + needSum0); } } @@ -4427,15 +5046,253 @@ public class SqlToRelConverter { * type or an an approximation to it. */ private RelDataType computeHistogramType(RelDataType type) { - if (SqlTypeUtil.isExactNumeric(type) && type.getSqlTypeName() != SqlTypeName.BIGINT) { + if (SqlTypeUtil.isExactNumeric(type) + && type.getSqlTypeName() != SqlTypeName.BIGINT) { return typeFactory.createSqlType(SqlTypeName.BIGINT); - } else if (SqlTypeUtil.isApproximateNumeric(type) && type.getSqlTypeName() != SqlTypeName.DOUBLE) { + } else if (SqlTypeUtil.isApproximateNumeric(type) + && type.getSqlTypeName() != SqlTypeName.DOUBLE) { return typeFactory.createSqlType(SqlTypeName.DOUBLE); } else { return type; } } } + + /** A sub-query, whether it needs to be translated using 2- or 3-valued + * logic. */ + private static class SubQuery { + final SqlNode node; + final RelOptUtil.Logic logic; + RexNode expr; + + private SubQuery(SqlNode node, RelOptUtil.Logic logic) { + this.node = node; + this.logic = logic; + } + } + + /** + * Visitor that collects all aggregate functions in a {@link SqlNode} tree. + */ + private static class AggregateFinder extends SqlBasicVisitor { + final SqlNodeList list = new SqlNodeList(SqlParserPos.ZERO); + + @Override public Void visit(SqlCall call) { + // ignore window aggregates and ranking functions (associated with OVER operator) + if (call.getOperator().getKind() == SqlKind.OVER) { + return null; + } + if (call.getOperator().isAggregator()) { + list.add(call); + return null; + } + + // Don't traverse into sub-queries, even if they contain aggregate + // functions. + if (call instanceof SqlSelect) { + return null; + } + + return call.getOperator().acceptCall(this, call); + } + } + + /** Use of a row as a correlating variable by a given relational + * expression. */ + private static class CorrelationUse { + private final CorrelationId id; + private final ImmutableBitSet requiredColumns; + private final RelNode r; + + CorrelationUse(CorrelationId id, ImmutableBitSet requiredColumns, + RelNode r) { + this.id = id; + this.requiredColumns = requiredColumns; + this.r = r; + } + } + + /** Creates a builder for a {@link Config}. */ + public static ConfigBuilder configBuilder() { + return new ConfigBuilder(); + } + + /** + * Interface to define the configuration for a SqlToRelConverter. + * Provides methods to set each configuration option. + * + * @see ConfigBuilder + * @see SqlToRelConverter#configBuilder() + */ + public interface Config { + /** Default configuration. */ + Config DEFAULT = configBuilder().build(); + + /** Returns the {@code convertTableAccess} option. Controls whether table + * access references are converted to physical rels immediately. The + * optimizer doesn't like leaf rels to have {@link Convention#NONE}. + * However, if we are doing further conversion passes (e.g. + * {@link RelStructuredTypeFlattener}), then we may need to defer + * conversion. */ + boolean isConvertTableAccess(); + + /** Returns the {@code decorrelationEnabled} option. Controls whether to + * disable sub-query decorrelation when needed. e.g. if outer joins are not + * supported. */ + boolean isDecorrelationEnabled(); + + /** Returns the {@code trimUnusedFields} option. Controls whether to trim + * unused fields as part of the conversion process. */ + boolean isTrimUnusedFields(); + + /** Returns the {@code createValuesRel} option. Controls whether instances + * of {@link org.apache.calcite.rel.logical.LogicalValues} are generated. + * These may not be supported by all physical implementations. */ + boolean isCreateValuesRel(); + + /** Returns the {@code explain} option. Describes whether the current + * statement is part of an EXPLAIN PLAN statement. */ + boolean isExplain(); + + /** Returns the {@code expand} option. Controls whether to expand + * sub-queries. If false, each sub-query becomes a + * {@link org.apache.calcite.rex.RexSubQuery}. */ + boolean isExpand(); + + /** Returns the {@code inSubQueryThreshold} option, + * default {@link #DEFAULT_IN_SUB_QUERY_THRESHOLD}. Controls 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 {@link Integer#MAX_VALUE} forces usage of OR in all + * cases. */ + int getInSubQueryThreshold(); + } + + /** Builder for a {@link Config}. */ + public static class ConfigBuilder { + private boolean convertTableAccess = true; + private boolean decorrelationEnabled = true; + private boolean trimUnusedFields = false; + private boolean createValuesRel = true; + private boolean explain; + private boolean expand = true; + private int inSubQueryThreshold = DEFAULT_IN_SUB_QUERY_THRESHOLD; + + private ConfigBuilder() {} + + /** Sets configuration identical to a given {@link Config}. */ + public ConfigBuilder withConfig(Config config) { + this.convertTableAccess = config.isConvertTableAccess(); + this.decorrelationEnabled = config.isDecorrelationEnabled(); + this.trimUnusedFields = config.isTrimUnusedFields(); + this.createValuesRel = config.isCreateValuesRel(); + this.explain = config.isExplain(); + this.expand = config.isExpand(); + this.inSubQueryThreshold = config.getInSubQueryThreshold(); + return this; + } + + public ConfigBuilder withConvertTableAccess(boolean convertTableAccess) { + this.convertTableAccess = convertTableAccess; + return this; + } + + public ConfigBuilder withDecorrelationEnabled(boolean enabled) { + this.decorrelationEnabled = enabled; + return this; + } + + public ConfigBuilder withTrimUnusedFields(boolean trimUnusedFields) { + this.trimUnusedFields = trimUnusedFields; + return this; + } + + public ConfigBuilder withCreateValuesRel(boolean createValuesRel) { + this.createValuesRel = createValuesRel; + return this; + } + + public ConfigBuilder withExplain(boolean explain) { + this.explain = explain; + return this; + } + + public ConfigBuilder withExpand(boolean expand) { + this.expand = expand; + return this; + } + + @Deprecated // to be removed before 2.0 + public ConfigBuilder withInSubqueryThreshold(int inSubQueryThreshold) { + return withInSubQueryThreshold(inSubQueryThreshold); + } + + public ConfigBuilder withInSubQueryThreshold(int inSubQueryThreshold) { + this.inSubQueryThreshold = inSubQueryThreshold; + return this; + } + + /** Builds a {@link Config}. */ + public Config build() { + return new ConfigImpl(convertTableAccess, decorrelationEnabled, + trimUnusedFields, createValuesRel, explain, expand, + inSubQueryThreshold); + } + } + + /** Implementation of {@link Config}. + * Called by builder; all values are in private final fields. */ + private static class ConfigImpl implements Config { + private final boolean convertTableAccess; + private final boolean decorrelationEnabled; + private final boolean trimUnusedFields; + private final boolean createValuesRel; + private final boolean explain; + private final int inSubQueryThreshold; + private final boolean expand; + + private ConfigImpl(boolean convertTableAccess, boolean decorrelationEnabled, + boolean trimUnusedFields, boolean createValuesRel, boolean explain, + boolean expand, int inSubQueryThreshold) { + this.convertTableAccess = convertTableAccess; + this.decorrelationEnabled = decorrelationEnabled; + this.trimUnusedFields = trimUnusedFields; + this.createValuesRel = createValuesRel; + this.explain = explain; + this.expand = expand; + this.inSubQueryThreshold = inSubQueryThreshold; + } + + public boolean isConvertTableAccess() { + return convertTableAccess; + } + + public boolean isDecorrelationEnabled() { + return decorrelationEnabled; + } + + public boolean isTrimUnusedFields() { + return trimUnusedFields; + } + + public boolean isCreateValuesRel() { + return createValuesRel; + } + + public boolean isExplain() { + return explain; + } + + public boolean isExpand() { + return expand; + } + + public int getInSubQueryThreshold() { + return inSubQueryThreshold; + } + } } // End SqlToRelConverter.java \ No newline at end of file diff --git a/jdbc/pom.xml b/jdbc/pom.xml index cd784f4..874ead6 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -66,13 +66,15 @@ false + - - com.fasterxml.jackson.core:* + com.google.protobuf:* + commons-logging:* + commons-codec:* + org.apache.calcite @@ -86,10 +88,6 @@ org.apache.http ${shadeBase}.org.apache.http - - org.apache.commons - ${shadeBase}.org.apache.commons - diff --git a/jdbc/src/main/java/org/apache/kylin/jdbc/KylinMeta.java b/jdbc/src/main/java/org/apache/kylin/jdbc/KylinMeta.java index a1b9aef..8e69e68 100644 --- a/jdbc/src/main/java/org/apache/kylin/jdbc/KylinMeta.java +++ b/jdbc/src/main/java/org/apache/kylin/jdbc/KylinMeta.java @@ -209,7 +209,7 @@ public class KylinMeta extends MetaImpl { } catch (NoSuchFieldException e) { throw new RuntimeException(e); } - columns.add(columnMetaData(name, index, field.getType())); + columns.add(columnMetaData(name, index, field.getType(), true)); fields.add(field); fieldNames.add(fieldName); } diff --git a/kylin-it/pom.xml b/kylin-it/pom.xml index 080558b..80af108 100644 --- a/kylin-it/pom.xml +++ b/kylin-it/pom.xml @@ -43,6 +43,12 @@ org.apache.kylin atopcalcite + + + avatica-core + org.apache.calcite.avatica + + org.apache.kylin @@ -74,14 +80,6 @@ org.apache.kylin kylin-query - - org.apache.calcite - calcite-linq4j - - - commons-cli - commons-cli - diff --git a/kylin-it/src/test/java/org/apache/kylin/jdbc/ITJDBCDriverTest.java b/kylin-it/src/test/java/org/apache/kylin/jdbc/ITJDBCDriverTest.java index df6eb2f..05f615f 100644 --- a/kylin-it/src/test/java/org/apache/kylin/jdbc/ITJDBCDriverTest.java +++ b/kylin-it/src/test/java/org/apache/kylin/jdbc/ITJDBCDriverTest.java @@ -21,6 +21,7 @@ package org.apache.kylin.jdbc; import java.io.File; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.Driver; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; diff --git a/pom.xml b/pom.xml index ff4c4e8..57b7752 100644 --- a/pom.xml +++ b/pom.xml @@ -112,8 +112,8 @@ 1.8.9 - 1.10.0 - 1.8.0 + 1.11.0 + 1.9.0 jacoco @@ -450,15 +450,9 @@ - org.apache.calcite - calcite-linq4j - ${calcite.version} - - org.apache.calcite.avatica avatica ${avatica.version} - diff --git a/query/pom.xml b/query/pom.xml index 1dc05d1..6ab74a7 100644 --- a/query/pom.xml +++ b/query/pom.xml @@ -36,16 +36,18 @@ org.apache.kylin atopcalcite + + + org.apache.calcite.avatica + avatica-core + + org.apache.kylin kylin-core-storage - org.apache.calcite - calcite-core - - commons-cli commons-cli -- 2.10.1 (Apple Git-78)