http://git-wip-us.apache.org/repos/asf/kylin/blob/19585846/atopcalcite/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java ---------------------------------------------------------------------- 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 fe3c55e..8085a70 100644 --- a/atopcalcite/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/atopcalcite/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -17,26 +17,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.HashSet; -import java.util.LinkedHashSet; -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; @@ -68,6 +48,7 @@ import org.apache.calcite.rel.core.Project; import org.apache.calcite.rel.core.RelFactories; import org.apache.calcite.rel.core.Sample; import org.apache.calcite.rel.core.Sort; +import org.apache.calcite.rel.core.TableScan; import org.apache.calcite.rel.core.Uncollect; import org.apache.calcite.rel.logical.LogicalAggregate; import org.apache.calcite.rel.logical.LogicalCorrelate; @@ -185,7 +166,6 @@ 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; @@ -197,6 +177,28 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import org.slf4j.Logger; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.AbstractList; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import static org.apache.calcite.sql.SqlUtil.stripAs; +import static org.apache.calcite.util.Static.RESOURCE; + /* * The code has synced with calcite. Hope one day, we could remove the hardcode override point. * OVERRIDE POINT: @@ -217,7 +219,8 @@ import com.google.common.collect.Maps; public class SqlToRelConverter { //~ Static fields/initializers --------------------------------------------- - protected static final Logger SQL2REL_LOGGER = CalciteTrace.getSqlToRelTracer(); + protected static final Logger SQL2REL_LOGGER = + CalciteTrace.getSqlToRelTracer(); private static final BigDecimal TWO = BigDecimal.valueOf(2L); @@ -227,7 +230,8 @@ public class SqlToRelConverter { public static final int DEFAULT_IN_SUB_QUERY_THRESHOLD = Integer.MAX_VALUE; @Deprecated // to be removed before 2.0 - public static final int DEFAULT_IN_SUBQUERY_THRESHOLD = DEFAULT_IN_SUB_QUERY_THRESHOLD; + public static final int DEFAULT_IN_SUBQUERY_THRESHOLD = + DEFAULT_IN_SUB_QUERY_THRESHOLD; //~ Instance fields -------------------------------------------------------- @@ -247,7 +251,8 @@ public class SqlToRelConverter { /** * Fields used in name resolution for correlated sub-queries. */ - private final Map<CorrelationId, DeferredLookup> mapCorrelToDeferred = new HashMap<>(); + private final Map<CorrelationId, DeferredLookup> mapCorrelToDeferred = + new HashMap<>(); /** * Stack of names of datasets requested by the <code> @@ -260,7 +265,8 @@ public class SqlToRelConverter { * equivalent constants. Used to avoid re-evaluating the sub-query if it's * already been evaluated. */ - private final Map<SqlNode, RexNode> mapConvertedNonCorrSubqs = new HashMap<>(); + private final Map<SqlNode, RexNode> mapConvertedNonCorrSubqs = + new HashMap<>(); public final RelOptTable.ViewExpander viewExpander; @@ -276,25 +282,42 @@ 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.subQueryConverter = new NoOpSubQueryConverter(); @@ -376,7 +399,8 @@ public class SqlToRelConverter { * * @param alreadyConvertedNonCorrSubqs the other map */ - public void addConvertedNonCorrSubqs(Map<SqlNode, RexNode> alreadyConvertedNonCorrSubqs) { + public void addConvertedNonCorrSubqs( + Map<SqlNode, RexNode> alreadyConvertedNonCorrSubqs) { mapConvertedNonCorrSubqs.putAll(alreadyConvertedNonCorrSubqs); } @@ -410,25 +434,37 @@ 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<RelDataTypeField> validatedFields = validator.getValidatedNodeType(query).getFieldList(); - final RelDataType validatedRowType = validator.getTypeFactory().createStructType(Pair.right(validatedFields), - SqlValidatorUtil.uniquify(Pair.left(validatedFields), catalogReader.nameMatcher().isCaseSensitive())); - - final List<RelDataTypeField> convertedFields = result.getRowType().getFieldList().subList(0, - validatedFields.size()); - final RelDataType convertedRowType = validator.getTypeFactory().createStructType(convertedFields); - - if (!RelOptUtil.equal("validated row type", validatedRowType, "converted row type", convertedRowType, - Litmus.IGNORE)) { - throw new AssertionError("Conversion to relational algebra failed to " + "preserve datatypes:\n" - + "validated type:\n" + validatedRowType.getFullTypeString() + "\nconverted type:\n" - + convertedRowType.getFullTypeString() + "\nrel:\n" + RelOptUtil.toString(result)); + final List<RelDataTypeField> validatedFields = + validator.getValidatedNodeType(query).getFieldList(); + final RelDataType validatedRowType = + validator.getTypeFactory().createStructType( + Pair.right(validatedFields), + SqlValidatorUtil.uniquify(Pair.left(validatedFields), + catalogReader.nameMatcher().isCaseSensitive())); + + final List<RelDataTypeField> convertedFields = + result.getRowType().getFieldList().subList(0, validatedFields.size()); + final RelDataType convertedRowType = + validator.getTypeFactory().createStructType(convertedFields); + + if (!RelOptUtil.equal("validated row type", validatedRowType, + "converted row type", convertedRowType, Litmus.IGNORE)) { + throw new AssertionError("Conversion to relational algebra failed to " + + "preserve datatypes:\n" + + "validated type:\n" + + validatedRowType.getFullTypeString() + + "\nconverted type:\n" + + convertedRowType.getFullTypeString() + + "\nrel:\n" + + RelOptUtil.toString(result)); } } - public RelNode flattenTypes(RelNode rootRel, boolean restructure) { - RelStructuredTypeFlattener typeFlattener = new RelStructuredTypeFlattener(rexBuilder, createToRelContext(), - restructure); + public RelNode flattenTypes( + RelNode rootRel, + boolean restructure) { + RelStructuredTypeFlattener typeFlattener = + new RelStructuredTypeFlattener(rexBuilder, createToRelContext(), restructure); return typeFlattener.rewrite(rootRel); } @@ -474,15 +510,20 @@ public class SqlToRelConverter { // Trim fields that are not used by their consumer. if (isTrimUnusedFields()) { final RelFieldTrimmer trimmer = newFieldTrimmer(); - final List<RelCollation> collations = rootRel.getTraitSet().getTraits(RelCollationTraitDef.INSTANCE); + final List<RelCollation> collations = + rootRel.getTraitSet().getTraits(RelCollationTraitDef.INSTANCE); rootRel = trimmer.trim(rootRel); - if (!ordered && collations != null && !collations.isEmpty() - && !collations.equals(ImmutableList.of(RelCollations.EMPTY))) { - final RelTraitSet traitSet = rootRel.getTraitSet().replace(RelCollationTraitDef.INSTANCE, collations); + 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, + SQL2REL_LOGGER.debug( + RelOptUtil.dumpPlan("Plan after trimming unused fields", rootRel, SqlExplainFormat.TEXT, SqlExplainLevel.EXPPLAN_ATTRIBUTES)); } } @@ -495,7 +536,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); } @@ -510,15 +552,19 @@ public class SqlToRelConverter { * will become a JDBC result set; <code>false</code> if * the query will be part of a view. */ - public RelRoot convertQuery(SqlNode query, final boolean needsValidation, final boolean top) { + 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)) { @@ -534,15 +580,19 @@ public class SqlToRelConverter { checkConvertedType(query, result); if (SQL2REL_LOGGER.isDebugEnabled()) { - SQL2REL_LOGGER.debug(RelOptUtil.dumpPlan("Plan after converting SqlNode to RelNode", result, - SqlExplainFormat.TEXT, 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); - RelRoot origResult = 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) { // /* @@ -579,26 +629,26 @@ public class SqlToRelConverter { List<String> inFields = inType.getFieldNames(); List<RexNode> projExp = new ArrayList<>(); List<Pair<Integer, String>> projFields = new ArrayList<>(); - Map<Integer, Integer> projFieldMapping = new HashMap<>(); + Map<Integer,Integer> projFieldMapping = new HashMap<>(); RelDataTypeFactory.FieldInfoBuilder projTypeBuilder = getCluster().getTypeFactory().builder(); RelDataTypeFactory.FieldInfoBuilder validTypeBuilder = getCluster().getTypeFactory().builder(); - + boolean hiddenColumnExists = false; for (int i = 0; i < root.validatedRowType.getFieldList().size(); i++) { - if (root.validatedRowType.getFieldNames().get(i).startsWith("_KY_")) + if (root.validatedRowType.getFieldNames().get(i).startsWith("_KY_")) hiddenColumnExists = true; } - if (!hiddenColumnExists) { + if(!hiddenColumnExists) { return root; } - + for (int i = 0; i < inFields.size(); i++) { if (!inFields.get(i).startsWith("_KY_")) { projExp.add(rootPrj.getProjects().get(i)); projFieldMapping.put(i, projFields.size()); projFields.add(Pair.of(projFields.size(), inFields.get(i))); projTypeBuilder.add(inType.getFieldList().get(i)); - + if (i < root.validatedRowType.getFieldList().size()) //for cases like kylin-it/src/test/resources/query/sql_verifyCount/query10.sql validTypeBuilder.add(root.validatedRowType.getFieldList().get(i)); } @@ -613,7 +663,7 @@ public class SqlToRelConverter { List<RelFieldCollation> fieldCollations = originalCollation.getFieldCollations(); ImmutableList.Builder<RelFieldCollation> newFieldCollations = ImmutableList.builder(); for (RelFieldCollation fieldCollation : fieldCollations) { - if (projFieldMapping.containsKey(fieldCollation.getFieldIndex())) { + if(projFieldMapping.containsKey(fieldCollation.getFieldIndex())) { newFieldCollations.add(fieldCollation.copy(projFieldMapping.get(fieldCollation.getFieldIndex()))); } else { newFieldCollations.add(fieldCollation); @@ -624,28 +674,30 @@ public class SqlToRelConverter { } RelDataType validRowType = getCluster().getTypeFactory().createStructType(validTypeBuilder); - root = new RelRoot(rootSort == null ? rootPrj : rootSort, validRowType, root.kind, projFields, - rootSort == null ? root.collation : rootSort.getCollation()); + root = new RelRoot(rootSort == null ? rootPrj : rootSort, validRowType, root.kind, projFields, rootSort == null ? root.collation : rootSort.getCollation()); validator.setValidatedNodeType(query, validRowType); return root; } + private static boolean isStream(SqlNode query) { - return query instanceof SqlSelect && ((SqlSelect) query).isKeywordPresent(SqlSelectKeyword.STREAM); + 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; + 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; } } @@ -675,7 +727,8 @@ public class SqlToRelConverter { /** * Factory method for creating translation workspace. */ - protected Blackboard createBlackboard(SqlValidatorScope scope, Map<String, RexNode> nameToNodeMap, boolean top) { + protected Blackboard createBlackboard(SqlValidatorScope scope, + Map<String, RexNode> nameToNodeMap, boolean top) { return new Blackboard(scope, nameToNodeMap, top); } @@ -683,25 +736,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<SqlNode> orderExprList = new ArrayList<>(); final List<RelFieldCollation> collationList = new ArrayList<>(); - gatherOrderExprs(bb, select, select.getOrderList(), orderExprList, collationList); - final RelCollation collation = cluster.traitSet().canonize(RelCollations.of(collationList)); + 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); } @@ -717,7 +790,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]} @@ -751,7 +826,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; @@ -763,21 +840,33 @@ public class SqlToRelConverter { 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())); + 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.<AggregateCall> of()); - - bb.setRoot(rel, false); + final ImmutableBitSet groupSet = + ImmutableBitSet.range(rel.getRowType().getFieldCount()); + rel = + createAggregate(bb, false, groupSet, ImmutableList.of(groupSet), + ImmutableList.<AggregateCall>of()); + + bb.setRoot( + rel, + false); } private int findExpr(RexNode seek, List<RexNode> exprs, int count) { @@ -802,18 +891,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<SqlNode> orderExprList, - SqlNode offset, SqlNode fetch) { - if (select.getOrderList() == null || select.getOrderList().getList().isEmpty()) { + protected void convertOrder( + SqlSelect select, + Blackboard bb, + RelCollation collation, + List<SqlNode> orderExprList, + SqlNode offset, + SqlNode fetch) { + if (select.getOrderList() == null + || select.getOrderList().getList().isEmpty()) { assert collation.getFieldCollations().isEmpty(); - if ((offset == null || ((SqlLiteral) offset).bigDecimalValue().equals(BigDecimal.ZERO)) && fetch == null) { + 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 @@ -823,11 +923,15 @@ public class SqlToRelConverter { if (orderExprList.size() > 0 && !bb.top) { final List<RexNode> exprs = new ArrayList<>(); final RelDataType rowType = bb.root.getRowType(); - final int fieldCount = rowType.getFieldCount() - orderExprList.size(); + 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); } } @@ -836,16 +940,18 @@ public class SqlToRelConverter { * * @param node a RexNode tree */ - private static boolean containsInOperator(SqlNode node) { + private static boolean containsInOperator( + SqlNode node) { try { - SqlVisitor<Void> visitor = new SqlBasicVisitor<Void>() { - public Void visit(SqlCall call) { - if (call.getOperator() instanceof SqlInOperator) { - throw new Util.FoundOne(call); + SqlVisitor<Void> visitor = + new SqlBasicVisitor<Void>() { + public Void visit(SqlCall call) { + if (call.getOperator() instanceof SqlInOperator) { + throw new Util.FoundOne(call); + } + return super.visit(call); } - return super.visit(call); - } - }; + }; node.accept(visitor); return false; } catch (Util.FoundOne e) { @@ -861,11 +967,12 @@ public class SqlToRelConverter { * @param sqlNode the root node from which to look for NOT operators * @return the transformed SqlNode representation with NOT pushed down. */ - private static SqlNode pushDownNotForIn(SqlValidatorScope scope, SqlNode sqlNode) { + private static SqlNode pushDownNotForIn(SqlValidatorScope scope, + SqlNode sqlNode) { if ((sqlNode instanceof SqlCall) && containsInOperator(sqlNode)) { SqlCall sqlCall = (SqlCall) sqlNode; if ((sqlCall.getOperator() == SqlStdOperatorTable.AND) - || (sqlCall.getOperator() == SqlStdOperatorTable.OR)) { + || (sqlCall.getOperator() == SqlStdOperatorTable.OR)) { SqlNode[] sqlOperands = ((SqlBasicCall) sqlCall).operands; for (int i = 0; i < sqlOperands.length; i++) { sqlOperands[i] = pushDownNotForIn(scope, sqlOperands[i]); @@ -880,38 +987,45 @@ public class SqlToRelConverter { SqlNode[] orOperands = new SqlNode[andOperands.length]; for (int i = 0; i < orOperands.length; i++) { orOperands[i] = reg(scope, - SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, andOperands[i])); + SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, + andOperands[i])); } for (int i = 0; i < orOperands.length; i++) { orOperands[i] = pushDownNotForIn(scope, orOperands[i]); } return reg(scope, - SqlStdOperatorTable.OR.createCall(SqlParserPos.ZERO, orOperands[0], orOperands[1])); + SqlStdOperatorTable.OR.createCall(SqlParserPos.ZERO, + orOperands[0], orOperands[1])); } else if (childSqlCall.getOperator() == SqlStdOperatorTable.OR) { SqlNode[] orOperands = childSqlCall.getOperands(); SqlNode[] andOperands = new SqlNode[orOperands.length]; for (int i = 0; i < andOperands.length; i++) { andOperands[i] = reg(scope, - SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, orOperands[i])); + SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, + orOperands[i])); } for (int i = 0; i < andOperands.length; i++) { andOperands[i] = pushDownNotForIn(scope, andOperands[i]); } return reg(scope, - SqlStdOperatorTable.AND.createCall(SqlParserPos.ZERO, andOperands[0], andOperands[1])); + SqlStdOperatorTable.AND.createCall(SqlParserPos.ZERO, + andOperands[0], andOperands[1])); } else if (childSqlCall.getOperator() == SqlStdOperatorTable.NOT) { SqlNode[] notOperands = childSqlCall.getOperands(); assert notOperands.length == 1; return pushDownNotForIn(scope, notOperands[0]); } else if (childSqlCall.getOperator() instanceof SqlInOperator) { SqlNode[] inOperands = childSqlCall.getOperands(); - SqlInOperator inOp = (SqlInOperator) childSqlCall.getOperator(); + SqlInOperator inOp = + (SqlInOperator) childSqlCall.getOperator(); if (inOp.isNotIn()) { return reg(scope, - SqlStdOperatorTable.IN.createCall(SqlParserPos.ZERO, inOperands[0], inOperands[1])); + SqlStdOperatorTable.IN.createCall(SqlParserPos.ZERO, + inOperands[0], inOperands[1])); } else { return reg(scope, - SqlStdOperatorTable.NOT_IN.createCall(SqlParserPos.ZERO, inOperands[0], inOperands[1])); + SqlStdOperatorTable.NOT_IN.createCall(SqlParserPos.ZERO, + inOperands[0], inOperands[1])); } } else { // childSqlCall is "leaf" node in a logical expression tree @@ -942,7 +1056,9 @@ public class SqlToRelConverter { * @param bb Blackboard * @param where WHERE clause, may be null */ - private void convertWhere(final Blackboard bb, final SqlNode where) { + private void convertWhere( + final Blackboard bb, + final SqlNode where) { if (where == null) { return; } @@ -955,14 +1071,16 @@ public class SqlToRelConverter { return; } - final RelFactories.FilterFactory factory = RelFactories.DEFAULT_FILTER_FACTORY; + 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)); + r = LogicalFilter.create(f.getInput(), f.getCondition(), + ImmutableSet.of(p.id)); } else { r = filter; } @@ -970,7 +1088,10 @@ public class SqlToRelConverter { bb.setRoot(r, false); } - private void replaceSubQueries(final Blackboard bb, final SqlNode expr, RelOptUtil.Logic logic) { + 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); @@ -989,234 +1110,269 @@ public class SqlToRelConverter { final SqlNode query; final RelOptUtil.Exists converted; switch (subQuery.node.getKind()) { - case CURSOR: - convertCursor(bb, subQuery); - return; - - case MULTISET_QUERY_CONSTRUCTOR: - case MULTISET_VALUE_CONSTRUCTOR: - case ARRAY_QUERY_CONSTRUCTOR: - rel = convertMultisets(ImmutableList.of(subQuery.node), bb); - subQuery.expr = bb.register(rel, JoinRelType.INNER); - return; + case CURSOR: + convertCursor(bb, subQuery); + return; - case IN: - call = (SqlBasicCall) subQuery.node; - query = call.operand(1); - if (!config.isExpand() && !(query instanceof SqlNodeList)) { + 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; - } - final SqlNode leftKeyNode = call.operand(0); - - final List<RexNode> leftKeys; - switch (leftKeyNode.getKind()) { - case ROW: - leftKeys = Lists.newArrayList(); - for (SqlNode sqlExpr : ((SqlBasicCall) leftKeyNode).getOperandList()) { - leftKeys.add(bb.convertExpression(sqlExpr)); - } - break; - default: - leftKeys = ImmutableList.of(bb.convertExpression(leftKeyNode)); - } - final boolean notIn = ((SqlInOperator) call.getOperator()).isNotIn(); - if (query instanceof SqlNodeList) { - SqlNodeList valueList = (SqlNodeList) query; - if (!containsNullLiteral(valueList) && valueList.size() < config.getInSubQueryThreshold()) { - // We're under the threshold, so convert to OR. - subQuery.expr = convertInToOr(bb, leftKeys, valueList, notIn); + case IN: + call = (SqlBasicCall) subQuery.node; + query = call.operand(1); + if (!config.isExpand() && !(query instanceof SqlNodeList)) { return; } + final SqlNode leftKeyNode = call.operand(0); + + final List<RexNode> leftKeys; + switch (leftKeyNode.getKind()) { + case ROW: + leftKeys = Lists.newArrayList(); + for (SqlNode sqlExpr : ((SqlBasicCall) leftKeyNode).getOperandList()) { + leftKeys.add(bb.convertExpression(sqlExpr)); + } + break; + default: + leftKeys = ImmutableList.of(bb.convertExpression(leftKeyNode)); + } - // Otherwise, let convertExists translate - // values list into an inline table for the - // reference to Q below. - } - - // Project out the search columns from the left side - - // Q1: - // "select from emp where emp.deptno in (select col1 from T)" - // - // is converted to - // - // "select from - // emp inner join (select distinct col1 from T)) q - // on emp.deptno = q.col1 - // - // Q2: - // "select from emp where emp.deptno not in (Q)" - // - // is converted to - // - // "select from - // emp left outer join (select distinct col1, TRUE from T) q - // on emp.deptno = q.col1 - // where emp.deptno <> null - // and q.indicator <> TRUE" - // - final RelDataType targetRowType = SqlTypeUtil.promoteToRowType(typeFactory, - validator.getValidatedNodeType(leftKeyNode), null); - converted = convertExists(query, RelOptUtil.SubQueryType.IN, subQuery.logic, notIn, targetRowType); - if (converted.indicator) { - // Generate - // emp CROSS JOIN (SELECT COUNT(*) AS c, - // COUNT(deptno) AS ck FROM dept) - final RelDataType longType = typeFactory.createSqlType(SqlTypeName.BIGINT); - final RelNode seek = converted.r.getInput(0); // fragile - final int keyCount = leftKeys.size(); - final List<Integer> args = ImmutableIntList.range(0, keyCount); - LogicalAggregate aggregate = LogicalAggregate.create(seek, false, ImmutableBitSet.of(), null, - ImmutableList.of( - AggregateCall.create(SqlStdOperatorTable.COUNT, false, ImmutableList.<Integer> of(), -1, - longType, null), - AggregateCall.create(SqlStdOperatorTable.COUNT, false, args, -1, longType, null))); - LogicalJoin join = LogicalJoin.create(bb.root, aggregate, rexBuilder.makeLiteral(true), - ImmutableSet.<CorrelationId> of(), JoinRelType.INNER); - bb.setRoot(join, false); - } - final RexNode rex = bb.register(converted.r, converted.outerJoin ? JoinRelType.LEFT : JoinRelType.INNER, - leftKeys); + 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; + } - RelOptUtil.Logic logic = subQuery.logic; - switch (logic) { - case TRUE_FALSE_UNKNOWN: - case UNKNOWN_AS_TRUE: - if (!converted.indicator) { - logic = RelOptUtil.Logic.TRUE_FALSE; + // Otherwise, let convertExists translate + // values list into an inline table for the + // reference to Q below. } - } - subQuery.expr = translateIn(logic, bb.root, rex); - if (notIn) { - subQuery.expr = rexBuilder.makeCall(SqlStdOperatorTable.NOT, subQuery.expr); - } - return; - case EXISTS: - // "select from emp where exists (select a from T)" - // - // is converted to the following if the sub-query is correlated: - // - // "select from emp left outer join (select AGG_TRUE() as indicator - // from T group by corr_var) q where q.indicator is true" - // - // If there is no correlation, the expression is replaced with a - // boolean indicating whether the sub-query returned 0 or >= 1 row. - call = (SqlBasicCall) subQuery.node; - query = call.operand(0); - if (!config.isExpand()) { - return; - } - converted = convertExists(query, RelOptUtil.SubQueryType.EXISTS, subQuery.logic, true, null); - assert !converted.indicator; - if (convertNonCorrelatedSubQuery(subQuery, bb, converted.r, true)) { + // Project out the search columns from the left side + + // Q1: + // "select from emp where emp.deptno in (select col1 from T)" + // + // is converted to + // + // "select from + // emp inner join (select distinct col1 from T)) q + // on emp.deptno = q.col1 + // + // Q2: + // "select from emp where emp.deptno not in (Q)" + // + // is converted to + // + // "select from + // emp left outer join (select distinct col1, TRUE from T) q + // on emp.deptno = q.col1 + // where emp.deptno <> null + // and q.indicator <> TRUE" + // + final RelDataType targetRowType = + SqlTypeUtil.promoteToRowType(typeFactory, + validator.getValidatedNodeType(leftKeyNode), null); + converted = + convertExists(query, RelOptUtil.SubQueryType.IN, subQuery.logic, + notIn, targetRowType); + if (converted.indicator) { + // Generate + // emp CROSS JOIN (SELECT COUNT(*) AS c, + // COUNT(deptno) AS ck FROM dept) + final RelDataType longType = + typeFactory.createSqlType(SqlTypeName.BIGINT); + final RelNode seek = converted.r.getInput(0); // fragile + final int keyCount = leftKeys.size(); + final List<Integer> args = ImmutableIntList.range(0, keyCount); + LogicalAggregate aggregate = + LogicalAggregate.create(seek, false, ImmutableBitSet.of(), null, + ImmutableList.of( + AggregateCall.create(SqlStdOperatorTable.COUNT, false, + ImmutableList.<Integer>of(), -1, longType, null), + AggregateCall.create(SqlStdOperatorTable.COUNT, false, + args, -1, longType, null))); + LogicalJoin join = + LogicalJoin.create(bb.root, aggregate, rexBuilder.makeLiteral(true), + ImmutableSet.<CorrelationId>of(), JoinRelType.INNER); + bb.setRoot(join, false); + } + final RexNode rex = + bb.register(converted.r, + converted.outerJoin ? JoinRelType.LEFT : JoinRelType.INNER, + leftKeys); + + RelOptUtil.Logic logic = subQuery.logic; + switch (logic) { + case TRUE_FALSE_UNKNOWN: + case UNKNOWN_AS_TRUE: + if (!converted.indicator) { + logic = RelOptUtil.Logic.TRUE_FALSE; + } + } + subQuery.expr = translateIn(logic, bb.root, rex); + if (notIn) { + subQuery.expr = + rexBuilder.makeCall(SqlStdOperatorTable.NOT, subQuery.expr); + } return; - } - subQuery.expr = bb.register(converted.r, JoinRelType.LEFT); - return; - case SCALAR_QUERY: - // Convert the sub-query. If it's non-correlated, convert it - // to a constant expression. - if (!config.isExpand()) { + 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; - } - 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)) { + + 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; - } - 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; + case SELECT: + // This is used when converting multiset queries: + // + // select * from unnest(select multiset[deptno] from emps); + // + converted = convertExists(subQuery.node, RelOptUtil.SubQueryType.SCALAR, + subQuery.logic, true, null); + assert !converted.indicator; + subQuery.expr = bb.register(converted.r, JoinRelType.LEFT); + return; - default: - throw new AssertionError("unexpected kind of sub-query: " + subQuery.node); + default: + throw new AssertionError("unexpected kind of sub-query: " + + subQuery.node); } } - private RexNode translateIn(RelOptUtil.Logic logic, RelNode root, final RexNode rex) { + private RexNode translateIn(RelOptUtil.Logic logic, RelNode root, + final RexNode rex) { switch (logic) { - case TRUE: - return rexBuilder.makeLiteral(true); + case TRUE: + return rexBuilder.makeLiteral(true); - case TRUE_FALSE: - case UNKNOWN_AS_FALSE: - assert rex instanceof RexRangeRef; - final int fieldCount = rex.getType().getFieldCount(); - RexNode rexNode = rexBuilder.makeFieldAccess(rex, fieldCount - 1); - rexNode = rexBuilder.makeCall(SqlStdOperatorTable.IS_TRUE, rexNode); - - // Then append the IS NOT NULL(leftKeysForIn). - // - // RexRangeRef contains the following fields: - // leftKeysForIn, - // rightKeysForIn (the original sub-query select list), - // nullIndicator - // - // The first two lists contain the same number of fields. - final int k = (fieldCount - 1) / 2; - for (int i = 0; i < k; i++) { - rexNode = rexBuilder.makeCall(SqlStdOperatorTable.AND, rexNode, - rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, rexBuilder.makeFieldAccess(rex, i))); - } - return rexNode; - - case TRUE_FALSE_UNKNOWN: - case UNKNOWN_AS_TRUE: - // select e.deptno, - // case - // when ct.c = 0 then false - // when dt.i is not null then true - // when e.deptno is null then null - // when ct.ck < ct.c then null - // else false - // end - // from e - // cross join (select count(*) as c, count(deptno) as ck from v) as ct - // left join (select distinct deptno, true as i from v) as dt - // on e.deptno = dt.deptno - final Join join = (Join) root; - final Project left = (Project) join.getLeft(); - final RelNode leftLeft = ((Join) left.getInput()).getLeft(); - final int leftLeftCount = leftLeft.getRowType().getFieldCount(); - final RelDataType longType = typeFactory.createSqlType(SqlTypeName.BIGINT); - final RexNode cRef = rexBuilder.makeInputRef(root, leftLeftCount); - final RexNode ckRef = rexBuilder.makeInputRef(root, leftLeftCount + 1); - final RexNode iRef = rexBuilder.makeInputRef(root, root.getRowType().getFieldCount() - 1); - - final RexLiteral zero = rexBuilder.makeExactLiteral(BigDecimal.ZERO, longType); - final RexLiteral trueLiteral = rexBuilder.makeLiteral(true); - final RexLiteral falseLiteral = rexBuilder.makeLiteral(false); - final RexNode unknownLiteral = rexBuilder.makeNullLiteral(trueLiteral.getType()); - - final ImmutableList.Builder<RexNode> args = ImmutableList.builder(); - args.add(rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, cRef, zero), falseLiteral, - rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, iRef), trueLiteral); - final JoinInfo joinInfo = join.analyzeCondition(); - for (int leftKey : joinInfo.leftKeys) { - final RexNode kRef = rexBuilder.makeInputRef(root, leftKey); - args.add(rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, kRef), unknownLiteral); - } - args.add(rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ckRef, cRef), unknownLiteral, falseLiteral); + 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; - return rexBuilder.makeCall(SqlStdOperatorTable.CASE, args.build()); + case TRUE_FALSE_UNKNOWN: + case UNKNOWN_AS_TRUE: + // select e.deptno, + // case + // when ct.c = 0 then false + // when dt.i is not null then true + // when e.deptno is null then null + // when ct.ck < ct.c then null + // else false + // end + // from e + // cross join (select count(*) as c, count(deptno) as ck from v) as ct + // left join (select distinct deptno, true as i from v) as dt + // on e.deptno = dt.deptno + final Join join = (Join) root; + final Project left = (Project) join.getLeft(); + final RelNode leftLeft = ((Join) left.getInput()).getLeft(); + final int leftLeftCount = leftLeft.getRowType().getFieldCount(); + final RelDataType longType = + typeFactory.createSqlType(SqlTypeName.BIGINT); + final RexNode cRef = rexBuilder.makeInputRef(root, leftLeftCount); + final RexNode ckRef = rexBuilder.makeInputRef(root, leftLeftCount + 1); + final RexNode iRef = + rexBuilder.makeInputRef(root, root.getRowType().getFieldCount() - 1); + + final RexLiteral zero = + rexBuilder.makeExactLiteral(BigDecimal.ZERO, longType); + final RexLiteral trueLiteral = rexBuilder.makeLiteral(true); + final RexLiteral falseLiteral = rexBuilder.makeLiteral(false); + final RexNode unknownLiteral = + rexBuilder.makeNullLiteral(trueLiteral.getType()); + + final ImmutableList.Builder<RexNode> args = ImmutableList.builder(); + args.add(rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, cRef, zero), + falseLiteral, + rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, iRef), + trueLiteral); + final JoinInfo joinInfo = join.analyzeCondition(); + for (int leftKey : joinInfo.leftKeys) { + final RexNode kRef = rexBuilder.makeInputRef(root, leftKey); + args.add(rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, kRef), + unknownLiteral); + } + args.add(rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ckRef, cRef), + unknownLiteral, + falseLiteral); - default: - throw new AssertionError(logic); + return rexBuilder.makeCall(SqlStdOperatorTable.CASE, args.build()); + + default: + throw new AssertionError(logic); } } @@ -1242,16 +1398,25 @@ public class SqlToRelConverter { * @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)) { + 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; @@ -1270,14 +1435,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; @@ -1288,7 +1456,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; @@ -1300,13 +1469,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); } /** @@ -1317,34 +1490,52 @@ public class SqlToRelConverter { * @param isNotIn is this a NOT IN operator * @return converted expression */ - private RexNode convertInToOr(final Blackboard bb, final List<RexNode> leftKeys, SqlNodeList valuesList, - boolean isNotIn) { + private RexNode convertInToOr( + final Blackboard bb, + final List<RexNode> leftKeys, + SqlNodeList valuesList, + boolean isNotIn) { final List<RexNode> comparisons = new ArrayList<>(); for (SqlNode rightVals : valuesList) { RexNode rexComparison; if (leftKeys.size() == 1) { - rexComparison = rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, leftKeys.get(0), - ensureSqlType(leftKeys.get(0).getType(), bb.convertExpression(rightVals))); + rexComparison = + rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, + leftKeys.get(0), + ensureSqlType(leftKeys.get(0).getType(), + bb.convertExpression(rightVals))); } else { assert rightVals instanceof SqlCall; final SqlBasicCall call = (SqlBasicCall) rightVals; - assert (call.getOperator() instanceof SqlRowOperator) && call.operandCount() == leftKeys.size(); - rexComparison = RexUtil.composeConjunction(rexBuilder, Iterables.transform( - Pair.zip(leftKeys, call.getOperandList()), new Function<Pair<RexNode, SqlNode>, RexNode>() { - public RexNode apply(Pair<RexNode, SqlNode> pair) { - return rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, pair.left, - ensureSqlType(pair.left.getType(), bb.convertExpression(pair.right))); - } - }), false); + assert (call.getOperator() instanceof SqlRowOperator) + && call.operandCount() == leftKeys.size(); + rexComparison = + RexUtil.composeConjunction( + rexBuilder, + Iterables.transform( + Pair.zip(leftKeys, call.getOperandList()), + new Function<Pair<RexNode, SqlNode>, RexNode>() { + public RexNode apply(Pair<RexNode, SqlNode> pair) { + return rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, + pair.left, + ensureSqlType(pair.left.getType(), + bb.convertExpression(pair.right))); + } + }), + false); } comparisons.add(rexComparison); } - RexNode result = RexUtil.composeDisjunction(rexBuilder, comparisons, true); + 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; @@ -1354,8 +1545,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); @@ -1392,9 +1584,15 @@ public class SqlToRelConverter { * @param notIn Whether the operation is NOT IN * @return join expression */ - private RelOptUtil.Exists convertExists(SqlNode seek, RelOptUtil.SubQueryType subQueryType, RelOptUtil.Logic logic, - boolean notIn, RelDataType targetDataType) { - final SqlValidatorScope seekScope = (seek instanceof SqlSelect) ? validator.getSelectScope((SqlSelect) seek) + 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); @@ -1402,7 +1600,10 @@ public class SqlToRelConverter { 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 @@ -1410,26 +1611,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<SqlNode> rows, - boolean allowLiteralsOnly, RelDataType targetRowType) { + private RelNode convertRowValues( + Blackboard bb, + SqlNode rowList, + Collection<SqlNode> rows, + boolean allowLiteralsOnly, + RelDataType targetRowType) { // NOTE jvs 30-Apr-2006: We combine all rows consisting entirely of // literals into a single LogicalValues; this gives the optimizer a smaller // input tree. For everything else (computed expressions, row // sub-queries), we union each row in as a projection on top of a // LogicalOneRow. - final ImmutableList.Builder<ImmutableList<RexLiteral>> tupleList = ImmutableList.builder(); + final ImmutableList.Builder<ImmutableList<RexLiteral>> 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<RelNode> unionInputs = new ArrayList<>(); @@ -1439,7 +1654,12 @@ public class SqlToRelConverter { call = (SqlBasicCall) node; ImmutableList.Builder<RexLiteral> tuple = ImmutableList.builder(); for (Ord<SqlNode> operand : Ord.zip(call.operands)) { - RexLiteral rexLiteral = convertLiteralInValuesList(operand.e, bb, rowType, operand.i); + RexLiteral rexLiteral = + convertLiteralInValuesList( + operand.e, + bb, + rowType, + operand.i); if ((rexLiteral == null) && allowLiteralsOnly) { return null; } @@ -1455,7 +1675,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; @@ -1466,11 +1691,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; @@ -1484,7 +1713,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; } @@ -1497,7 +1730,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); @@ -1514,15 +1750,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; } @@ -1549,41 +1794,53 @@ public class SqlToRelConverter { * node, only register it if it's a scalar * 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) { for (SqlNode operand : ((SqlCall) node).getOperandList()) { if (operand != null) { // In the case of an IN expression, locate scalar // sub-queries so we can convert them to constants - findSubQueries(bb, operand, logic, kind == SqlKind.IN || registerOnlyScalarSubQueries); + 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); } } @@ -1593,15 +1850,15 @@ public class SqlToRelConverter { // before the IN expression is converted. if (kind == SqlKind.IN) { switch (logic) { - case TRUE_FALSE_UNKNOWN: - if (validator.getValidatedNodeType(node).isNullable()) { - break; - } else if (true) { - break; - } - // fall through - case UNKNOWN_AS_FALSE: - logic = RelOptUtil.Logic.TRUE; + 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); } @@ -1613,9 +1870,11 @@ public class SqlToRelConverter { * @param node Expression to translate * @return Converted expression */ - public RexNode convertExpression(SqlNode node) { + public RexNode convertExpression( + SqlNode node) { Map<String, RelDataType> 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); } @@ -1631,12 +1890,15 @@ public class SqlToRelConverter { * this map * @return Converted expression */ - public RexNode convertExpression(SqlNode node, Map<String, RexNode> nameToNodeMap) { + public RexNode convertExpression( + SqlNode node, + Map<String, RexNode> nameToNodeMap) { final Map<String, RelDataType> nameToTypeMap = new HashMap<>(); for (Map.Entry<String, RexNode> entry : nameToNodeMap.entrySet()) { nameToTypeMap.put(entry.getKey(), entry.getValue().getType()); } - final ParameterScope scope = new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap); + final ParameterScope scope = + new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap); final Blackboard bb = createBlackboard(scope, nameToNodeMap, false); return bb.convertExpression(node); } @@ -1652,7 +1914,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; } @@ -1660,7 +1924,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) { @@ -1669,7 +1934,8 @@ public class SqlToRelConverter { window.setRows(SqlLiteral.createBoolean(true, SqlParserPos.ZERO)); } final SqlNodeList partitionList = window.getPartitionList(); - final ImmutableList.Builder<RexNode> partitionKeys = ImmutableList.builder(); + final ImmutableList.Builder<RexNode> partitionKeys = + ImmutableList.builder(); for (SqlNode partition : partitionList) { partitionKeys.add(bb.convertExpression(partition)); } @@ -1682,10 +1948,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<RexFieldCollation> orderKeys = ImmutableList.builder(); + final ImmutableList.Builder<RexFieldCollation> orderKeys = + ImmutableList.builder(); final Set<SqlKind> flags = EnumSet.noneOf(SqlKind.class); for (SqlNode order : orderList) { flags.clear(); @@ -1693,17 +1961,23 @@ public class SqlToRelConverter { orderKeys.add(new RexFieldCollation(e, flags)); } try { - Preconditions.checkArgument(bb.window == null, "already in window agg mode"); + Preconditions.checkArgument(bb.window == null, + "already in window agg mode"); bb.window = window; RexNode rexAgg = exprConverter.convertCall(bb, aggCall); - rexAgg = rexBuilder.ensureType(validator.getValidatedNodeType(call), rexAgg, false); + 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(), + final RexShuttle visitor = + new HistogramShuttle( + partitionKeys.build(), orderKeys.build(), RexWindowBound.create(window.getLowerBound(), lowerBound), - RexWindowBound.create(window.getUpperBound(), upperBound), window); + RexWindowBound.create(window.getUpperBound(), upperBound), + window); return rexAgg.accept(visitor); } finally { bb.window = null; @@ -1727,7 +2001,9 @@ public class SqlToRelConverter { * <li>or any combination of the above. * </ul> */ - protected void convertFrom(Blackboard bb, SqlNode from) { + protected void convertFrom( + Blackboard bb, + SqlNode from) { if (from == null) { bb.setRoot(LogicalValues.createOneRow(cluster), false); return; @@ -1736,133 +2012,162 @@ public class SqlToRelConverter { final SqlCall call; final SqlNode[] operands; switch (from.getKind()) { - case MATCH_RECOGNIZE: - convertMatchRecognize(bb, (SqlCall) from); - return; - - case AS: - convertFrom(bb, ((SqlCall) from).operand(0)); - return; + case MATCH_RECOGNIZE: + convertMatchRecognize(bb, (SqlCall) from); + 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 new AssertionError("unknown TABLESAMPLE type: " + sampleSpec); - } - return; + case WITH: + convertFrom(bb, ((SqlWith) from).body); + return; - case IDENTIFIER: - convertIdentifier(bb, (SqlIdentifier) from, null); - return; + case TABLESAMPLE: + operands = ((SqlBasicCall) from).getOperands(); + SqlSampleSpec sampleSpec = SqlLiteral.sampleValue(operands[1]); + if (sampleSpec instanceof SqlSampleSpec.SqlSubstitutionSampleSpec) { + String sampleName = + ((SqlSampleSpec.SqlSubstitutionSampleSpec) sampleSpec) + .getName(); + datasetStack.push(sampleName); + convertFrom(bb, operands[0]); + datasetStack.pop(); + } else if (sampleSpec instanceof SqlSampleSpec.SqlTableSampleSpec) { + SqlSampleSpec.SqlTableSampleSpec tableSampleSpec = + (SqlSampleSpec.SqlTableSampleSpec) sampleSpec; + convertFrom(bb, operands[0]); + RelOptSamplingParameters params = + new RelOptSamplingParameters( + tableSampleSpec.isBernoulli(), + tableSampleSpec.getSamplePercentage(), + tableSampleSpec.isRepeatable(), + tableSampleSpec.getRepeatableSeed()); + bb.setRoot(new Sample(cluster, bb.root, params), false); + } else { + throw new AssertionError("unknown TABLESAMPLE type: " + sampleSpec); + } + return; - case EXTEND: - call = (SqlCall) from; - SqlIdentifier id = (SqlIdentifier) call.getOperandList().get(0); - SqlNodeList extendedColumns = (SqlNodeList) call.getOperandList().get(1); - convertIdentifier(bb, id, extendedColumns); - return; + case IDENTIFIER: + convertIdentifier(bb, (SqlIdentifier) from, null); + return; - case JOIN: - final SqlJoin join = (SqlJoin) from; - final SqlValidatorScope scope = validator.getJoinScope(from); - final Blackboard fromBlackboard = createBlackboard(scope, null, false); - SqlNode left = join.getLeft(); - SqlNode right = join.getRight(); - final boolean isNatural = join.isNatural(); - final JoinType joinType = join.getJoinType(); - final SqlValidatorScope leftScope = Util.first(validator.getJoinScope(left), - ((DelegatingScope) bb.scope).getParent()); - final Blackboard leftBlackboard = createBlackboard(leftScope, null, false); - final SqlValidatorScope rightScope = Util.first(validator.getJoinScope(right), - ((DelegatingScope) bb.scope).getParent()); - final Blackboard rightBlackboard = createBlackboard(rightScope, null, false); - convertFrom(leftBlackboard, left); - RelNode leftRel = leftBlackboard.root; - convertFrom(rightBlackboard, right); - RelNode rightRel = rightBlackboard.root; - JoinRelType convertedJoinType = convertJoinType(joinType); - RexNode conditionExp; - final SqlValidatorNamespace leftNamespace = validator.getNamespace(left); - final SqlValidatorNamespace rightNamespace = validator.getNamespace(right); - if (isNatural) { - final RelDataType leftRowType = leftNamespace.getRowType(); - final RelDataType rightRowType = rightNamespace.getRowType(); - final List<String> columnList = SqlValidatorUtil.deriveNaturalJoinColumnList(leftRowType, rightRowType); - conditionExp = convertUsing(leftNamespace, rightNamespace, columnList); - } else { - conditionExp = convertJoinCondition(fromBlackboard, leftNamespace, rightNamespace, join.getCondition(), - join.getConditionType(), leftRel, rightRel); - } + case EXTEND: + call = (SqlCall) from; + SqlIdentifier id = (SqlIdentifier) call.getOperandList().get(0); + SqlNodeList extendedColumns = (SqlNodeList) call.getOperandList().get(1); + convertIdentifier(bb, id, extendedColumns); + return; - final RelNode joinRel = createJoin(fromB
<TRUNCATED>