/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to you under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eigenbase.sql2rel; import java.lang.reflect.Type; import java.math.*; import java.util.*; import java.util.logging.*; import org.eigenbase.rel.*; import org.eigenbase.rel.metadata.*; import org.eigenbase.relopt.*; import org.eigenbase.reltype.*; import org.eigenbase.rex.*; import org.eigenbase.sql.*; import org.eigenbase.sql.fun.*; import org.eigenbase.sql.parser.*; import org.eigenbase.sql.type.*; import org.eigenbase.sql.util.*; import org.eigenbase.sql.validate.*; import org.eigenbase.trace.*; import org.eigenbase.util.*; import org.eigenbase.util.mapping.Mappings; import org.eigenbase.util14.*; import net.hydromatic.linq4j.Ord; import net.hydromatic.optiq.ModifiableTable; import net.hydromatic.optiq.TranslatableTable; import net.hydromatic.optiq.prepare.Prepare; import net.hydromatic.optiq.prepare.RelOptTableImpl; import net.hydromatic.optiq.util.BitSets; import com.google.common.base.Function; import com.google.common.collect.*; import static org.eigenbase.sql.SqlUtil.stripAs; import static org.eigenbase.util.Static.RESOURCE; /** * Converts a SQL parse tree (consisting of {@link org.eigenbase.sql.SqlNode} * objects) into a relational algebra expression (consisting of * {@link org.eigenbase.rel.RelNode} objects). * * <p>The public entry points are: {@link #convertQuery}, * {@link #convertExpression(SqlNode)}. */ public class SqlToRelConverter { //~ Static fields/initializers --------------------------------------------- protected static final Logger SQL2REL_LOGGER = EigenbaseTrace.getSqlToRelTracer(); private static final Function<SubQuery, SqlNode> FN = new Function<SubQuery, SqlNode>() { public SqlNode apply(SubQuery input) { return input.node; } }; //~ Instance fields -------------------------------------------------------- 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<RelNode> leaves = new ArrayList<RelNode>(); private final List<SqlDynamicParam> dynamicParamSqlNodes = new ArrayList<SqlDynamicParam>(); private final SqlOperatorTable opTab; private boolean shouldConvertTableAccess; protected final RelDataTypeFactory typeFactory; private final SqlNodeToRexConverter exprConverter; private boolean decorrelationEnabled; private boolean trimUnusedFields; private boolean shouldCreateValuesRel; private boolean isExplain; private int nDynamicParamsInExplain; /** * Fields used in name resolution for correlated subqueries. */ private final Map<String, DeferredLookup> mapCorrelToDeferred = new HashMap<String, DeferredLookup>(); private int nextCorrel = 0; private static final String CORREL_PREFIX = "$cor"; /** * Stack of names of datasets requested by the <code> * TABLE(SAMPLE(<datasetName>, <query>))</code> construct. */ private final Stack<String> datasetStack = new Stack<String>(); /** * Mapping of non-correlated subqueries that have been converted to their * equivalent constants. Used to avoid re-evaluating the subquery if it's * already been evaluated. */ private final Map<SqlNode, RexNode> mapConvertedNonCorrSubqs = new HashMap<SqlNode, RexNode>(); public final RelOptTable.ViewExpander viewExpander; //~ Constructors ----------------------------------------------------------- /** * Creates a converter. * * @param viewExpander Preparing statement * @param validator Validator * @param catalogReader Schema * @param planner Planner * @param rexBuilder Rex builder * @param convertletTable Expression converter */ public SqlToRelConverter( RelOptTable.ViewExpander viewExpander, SqlValidator validator, Prepare.CatalogReader catalogReader, RelOptPlanner planner, RexBuilder rexBuilder, SqlRexConvertletTable convertletTable) { this.viewExpander = viewExpander; this.opTab = (validator == null) ? SqlStdOperatorTable.instance() : validator.getOperatorTable(); this.validator = validator; this.catalogReader = catalogReader; this.defaultValueFactory = new NullDefaultValueFactory(); this.subqueryConverter = new NoOpSubqueryConverter(); this.rexBuilder = rexBuilder; this.typeFactory = rexBuilder.getTypeFactory(); RelOptQuery query = new RelOptQuery(planner); this.cluster = query.createCluster(typeFactory, rexBuilder); this.shouldConvertTableAccess = true; this.exprConverter = new SqlNodeToRexConverterImpl(convertletTable); decorrelationEnabled = true; trimUnusedFields = false; shouldCreateValuesRel = true; isExplain = false; nDynamicParamsInExplain = 0; } //~ Methods ---------------------------------------------------------------- /** * @return the RelOptCluster in use. */ public RelOptCluster getCluster() { return cluster; } /** * Returns the row-expression builder. */ public RexBuilder getRexBuilder() { return rexBuilder; } /** * Returns the number of dynamic parameters encountered during translation; * this must only be called after {@link #convertQuery}. * * @return number of dynamic parameters */ public int getDynamicParamCount() { return dynamicParamSqlNodes.size(); } /** * Returns the type inferred for a dynamic parameter. * * @param index 0-based index of dynamic parameter * @return inferred type, never null */ public RelDataType getDynamicParamType(int index) { SqlNode sqlNode = dynamicParamSqlNodes.get(index); if (sqlNode == null) { throw Util.needToImplement("dynamic param type inference"); } return validator.getValidatedNodeType(sqlNode); } /** * Returns the current count of the number of dynamic parameters in an * EXPLAIN PLAN statement. * * @param increment if true, increment the count * @return the current count before the optional increment */ public int getDynamicParamCountInExplain(boolean increment) { int retVal = nDynamicParamsInExplain; if (increment) { ++nDynamicParamsInExplain; } return retVal; } /** * @return mapping of non-correlated subqueries that have been converted to * the constants that they evaluate to */ public Map<SqlNode, RexNode> getMapConvertedNonCorrSubqs() { return mapConvertedNonCorrSubqs; } /** * Adds to the current map of non-correlated converted subqueries the * elements from another map that contains non-correlated subqueries that * have been converted by another SqlToRelConverter. * * @param alreadyConvertedNonCorrSubqs the other map */ public void addConvertedNonCorrSubqs( Map<SqlNode, RexNode> alreadyConvertedNonCorrSubqs) { mapConvertedNonCorrSubqs.putAll(alreadyConvertedNonCorrSubqs); } /** * Set a new DefaultValueFactory. To have any effect, this must be called * before any convert method. * * @param factory new DefaultValueFactory */ public void setDefaultValueFactory(DefaultValueFactory factory) { defaultValueFactory = factory; } /** * Sets a new SubqueryConverter. To have any effect, this must be called * before any convert method. * * @param converter new SubqueryConverter */ public void setSubqueryConverter(SubqueryConverter converter) { subqueryConverter = converter; } /** * Indicates that the current statement is part of an EXPLAIN PLAN statement * * @param nDynamicParams number of dynamic parameters in the statement */ public void setIsExplain(int nDynamicParams) { isExplain = true; nDynamicParamsInExplain = nDynamicParams; } /** * 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. To have any effect, this must be called before any * convert method. * * @param enabled true for immediate conversion (the default); false to * generate logical TableAccessRel instances */ public void enableTableAccessConversion(boolean enabled) { shouldConvertTableAccess = enabled; } /** * Controls whether instances of {@link ValuesRel} are generated. These may * not be supported by all physical implementations. To have any effect, * this must be called before any convert method. * * @param enabled true to allow ValuesRel to be generated (the default); * false to force substitution of ProjectRel+OneRowRel instead */ public void enableValuesRelCreation(boolean enabled) { shouldCreateValuesRel = enabled; } private void checkConvertedType(SqlNode query, RelNode result) { if (!query.isA(SqlKind.DML)) { // Verify that conversion from SQL to relational algebra did // not perturb any type information. (We can't do this if the // SQL statement is something like an INSERT which has no // validator type information associated with its result, // hence the namespace check above.) RelDataType convertedRowType = result.getRowType(); if (!checkConvertedRowType(query, convertedRowType)) { RelDataType validatedRowType = validator.getValidatedNodeType(query); validatedRowType = uniquifyFields(validatedRowType); throw Util.newInternal( "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); } /** * If subquery is correlated and decorrelation is enabled, performs * decorrelation. * * @param query Query * @param rootRel Root relational expression * @return New root relational expression after decorrelation */ public RelNode decorrelate(SqlNode query, RelNode rootRel) { if (!enableDecorrelation()) { return rootRel; } final RelNode result = decorrelateQuery(rootRel); if (result != rootRel) { checkConvertedType(query, result); } return result; } /** * Walks over a tree of relational expressions, replacing each * {@link RelNode} with a 'slimmed down' relational expression that projects * only the fields required by its consumer. * * <p>This may make things easier for the optimizer, by removing crud that * would expand the search space, but is difficult for the optimizer itself * to do it, because optimizer rules must preserve the number and type of * fields. Hence, this transform that operates on the entire tree, similar * to the {@link RelStructuredTypeFlattener type-flattening transform}. * * <p>Currently this functionality is disabled in farrago/luciddb; the * default implementation of this method does nothing. * * @param rootRel Relational expression that is at the root of the tree * @return Trimmed relational expression */ public RelNode trimUnusedFields(RelNode rootRel) { // Trim fields that are not used by their consumer. if (isTrimUnusedFields()) { final RelFieldTrimmer trimmer = newFieldTrimmer(); rootRel = trimmer.trim(rootRel); boolean dumpPlan = SQL2REL_LOGGER.isLoggable(Level.FINE); if (dumpPlan) { SQL2REL_LOGGER.fine( RelOptUtil.dumpPlan( "Plan after trimming unused fields", rootRel, false, SqlExplainLevel.EXPPLAN_ATTRIBUTES)); } } return rootRel; } /** * Creates a RelFieldTrimmer. * * @return Field trimmer */ protected RelFieldTrimmer newFieldTrimmer() { return new RelFieldTrimmer(validator); } /** * Converts an unvalidated query's parse tree into a relational expression. * * @param query Query to convert * @param needsValidation Whether to validate the query before converting; * <code>false</code> if the query has already been * validated. * @param top Whether the query is top-level, say if its result * will become a JDBC result set; <code>false</code> if * the query will be part of a view. */ public RelNode convertQuery( SqlNode query, final boolean needsValidation, final boolean top) { if (needsValidation) { query = validator.validate(query); } RelNode result = convertQueryRecursive(query, top, null); checkConvertedType(query, result); boolean dumpPlan = SQL2REL_LOGGER.isLoggable(Level.FINE); if (dumpPlan) { SQL2REL_LOGGER.fine( RelOptUtil.dumpPlan( "Plan after converting SqlNode to RelNode", result, false, SqlExplainLevel.EXPPLAN_ATTRIBUTES)); } return result; } protected boolean checkConvertedRowType( SqlNode query, RelDataType convertedRowType) { RelDataType validatedRowType = validator.getValidatedNodeType(query); validatedRowType = uniquifyFields(validatedRowType); return RelOptUtil.equal( "validated row type", validatedRowType, "converted row type", convertedRowType, false); } protected RelDataType uniquifyFields(RelDataType rowType) { return validator.getTypeFactory().createStructType( RelOptUtil.getFieldTypeList(rowType), SqlValidatorUtil.uniquify(rowType.getFieldNames())); } /** * Converts a SELECT statement's parse tree into a relational expression. */ public RelNode convertSelect(SqlSelect select) { final SqlValidatorScope selectScope = validator.getWhereScope(select); final Blackboard bb = createBlackboard(selectScope, null); convertSelectImpl(bb, select); return bb.root; } /** * Factory method for creating translation workspace. */ protected Blackboard createBlackboard( SqlValidatorScope scope, Map<String, RexNode> nameToNodeMap) { return new Blackboard(scope, nameToNodeMap); } /** * Implementation of {@link #convertSelect(SqlSelect)}; derived class may * override. */ protected void convertSelectImpl( final Blackboard bb, SqlSelect select) { convertFrom( bb, select.getFrom()); convertWhere( bb, select.getWhere()); List<SqlNode> orderExprList = new ArrayList<SqlNode>(); List<RelFieldCollation> collationList = new ArrayList<RelFieldCollation>(); gatherOrderExprs( bb, select, select.getOrderList(), orderExprList, collationList); final RelCollation collation = cluster.traitSetOf().canonize(RelCollationImpl.of(collationList)); if (validator.isAggregate(select)) { convertAgg( bb, select, orderExprList); } else { convertSelectList( bb, select, orderExprList); } if (select.isDistinct()) { distinctify(bb, true); } convertOrder( select, bb, collation, orderExprList, select.getOffset(), select.getFetch()); bb.setRoot(bb.root, true); } /** * Having translated 'SELECT ... FROM ... [GROUP BY ...] [HAVING ...]', adds * a relational expression to make the results unique. * * <p>If the SELECT clause contains duplicate expressions, adds {@link * ProjectRel}s so that we are grouping on the minimal set of keys. The * performance gain isn't huge, but it is difficult to detect these * duplicate expressions later. * * @param bb Blackboard * @param checkForDupExprs Check for duplicate expressions */ private void distinctify( Blackboard bb, boolean checkForDupExprs) { // Look for duplicate expressions in the project. // Say we have 'select x, y, x, z'. // Then dups will be {[2, 0]} // and oldToNew will be {[0, 0], [1, 1], [2, 0], [3, 2]} RelNode rel = bb.root; if (checkForDupExprs && (rel instanceof ProjectRel)) { ProjectRel project = (ProjectRel) rel; final List<RexNode> projectExprs = project.getProjects(); List<Integer> origins = new ArrayList<Integer>(); int dupCount = 0; for (int i = 0; i < projectExprs.size(); i++) { int x = findExpr(projectExprs.get(i), projectExprs, i); if (x >= 0) { origins.add(x); ++dupCount; } else { origins.add(i); } } if (dupCount == 0) { distinctify(bb, false); return; } final Map<Integer, Integer> squished = Maps.newHashMap(); final List<RelDataTypeField> fields = rel.getRowType().getFieldList(); final List<Pair<RexNode, String>> newProjects = Lists.newArrayList(); for (int i = 0; i < fields.size(); i++) { if (origins.get(i) == i) { squished.put(i, newProjects.size()); newProjects.add(RexInputRef.of2(i, fields)); } } rel = new ProjectRel( cluster, rel, Pair.left(newProjects), Pair.right(newProjects), ProjectRel.Flags.BOXED); bb.root = rel; distinctify(bb, false); rel = bb.root; // Create the expressions to reverse the mapping. // Project($0, $1, $0, $2). final List<Pair<RexNode, String>> undoProjects = Lists.newArrayList(); for (int i = 0; i < fields.size(); i++) { final int origin = origins.get(i); RelDataTypeField field = fields.get(i); undoProjects.add( Pair.of( (RexNode) new RexInputRef( squished.get(origin), field.getType()), field.getName())); } rel = new ProjectRel( cluster, rel, Pair.left(undoProjects), Pair.right(undoProjects), ProjectRel.Flags.BOXED); bb.setRoot( rel, false); return; } // Usual case: all of the expressions in the SELECT clause are // different. rel = createAggregate( bb, BitSets.range(rel.getRowType().getFieldCount()), ImmutableList.<AggregateCall>of()); bb.setRoot( rel, false); } private int findExpr(RexNode seek, List<RexNode> exprs, int count) { for (int i = 0; i < count; i++) { RexNode expr = exprs.get(i); if (expr.toString().equals(seek.toString())) { return i; } } return -1; } /** * Converts a query's ORDER BY clause, if any. * * @param select Query * @param bb Blackboard * @param collation Collation list * @param orderExprList Method populates this list with orderBy expressions * not present in selectList * @param offset Expression for number of rows to discard before * returning first row * @param fetch Expression for number of rows to fetch */ protected void convertOrder( SqlSelect select, Blackboard bb, RelCollation collation, List<SqlNode> orderExprList, SqlNode offset, SqlNode fetch) { if (select.getOrderList() == null) { assert collation.getFieldCollations().isEmpty(); if (offset == null && fetch == null) { return; } } // Create a sorter using the previously constructed collations. bb.setRoot( new SortRel( cluster, cluster.traitSetOf(Convention.NONE, collation), 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. if (orderExprList.size() > 0) { List<RexNode> exprs = new ArrayList<RexNode>(); final RelDataType rowType = bb.root.getRowType(); final int fieldCount = rowType.getFieldCount() - orderExprList.size(); for (int i = 0; i < fieldCount; i++) { exprs.add(rexBuilder.makeInputRef(bb.root, i)); } bb.setRoot( new ProjectRel( cluster, cluster.traitSetOf(RelCollationImpl.PRESERVE), bb.root, exprs, cluster.getTypeFactory().createStructType( rowType.getFieldList().subList(0, fieldCount)), ProjectRelBase.Flags.BOXED), false); } } /** * Returns whether a given node contains a {@link SqlInOperator}. * * @param node a RexNode tree */ private static boolean containsInOperator( SqlNode node) { try { SqlVisitor<Void> visitor = new SqlBasicVisitor<Void>() { public Void visit(SqlCall call) { if (call.getOperator() instanceof SqlInOperator) { throw new Util.FoundOne(call); } return super.visit(call); } }; node.accept(visitor); return false; } catch (Util.FoundOne e) { Util.swallow(e, null); return true; } } /** * Push down all the NOT logical operators into any IN/NOT IN operators. * * @param 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); // only allocate filter if the condition is not TRUE if (!convertedWhere.isAlwaysTrue()) { bb.setRoot( RelOptUtil.createFilter(bb.root, convertedWhere), 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 Pair<RelNode, Boolean> converted; switch (subQuery.node.getKind()) { case CURSOR: convertCursor(bb, subQuery); return; case MULTISET_QUERY_CONSTRUCTOR: case MULTISET_VALUE_CONSTRUCTOR: rel = convertMultisets(ImmutableList.of(subQuery.node), bb); subQuery.expr = bb.register(rel, JoinRelType.INNER); return; case IN: call = (SqlBasicCall) subQuery.node; final SqlNode[] operands = call.getOperands(); SqlNode leftKeyNode = operands[0]; query = operands[1]; 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 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; converted = convertExists(query, RelOptUtil.SubqueryType.IN, subQuery.logic, outerJoin); 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<Integer> args = ImmutableIntList.range(0, keyCount); AggregateRel aggregate = new AggregateRel(cluster, seek, BitSets.of(), ImmutableList.of( new AggregateCall(SqlStdOperatorTable.COUNT, false, ImmutableList.<Integer>of(), longType, null), new AggregateCall(SqlStdOperatorTable.COUNT, false, args, longType, null))); JoinRel join = new JoinRel(cluster, bb.root, aggregate, rexBuilder.makeLiteral(true), JoinRelType.INNER, ImmutableSet.<String>of()); 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); } return; 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.getOperands()[0]; converted = convertExists(query, RelOptUtil.SubqueryType.EXISTS, subQuery.logic, true); assert !converted.right; if (convertNonCorrelatedSubQuery(subQuery, bb, converted.left, true)) { 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. call = (SqlBasicCall) subQuery.node; query = call.getOperands()[0]; converted = convertExists(query, RelOptUtil.SubqueryType.SCALAR, subQuery.logic, true); assert !converted.right; if (convertNonCorrelatedSubQuery(subQuery, bb, converted.left, false)) { 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); assert !converted.right; subQuery.expr = bb.register(converted.left, JoinRelType.LEFT); return; default: throw Util.newInternal("unexpected kind of subquery :" + subQuery.node); } } private RexNode translateIn(SubQuery subQuery, RelNode root, final RexNode rex) { switch (subQuery.logic) { case TRUE: return rexBuilder.makeLiteral(true); 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 JoinRelBase join = (JoinRelBase) root; final ProjectRelBase left = (ProjectRelBase) join.getLeft(); final RelNode leftLeft = ((JoinRelBase) left.getInput(0)).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<RexNode> args = ImmutableList.builder(); args.add(rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, cRef, zero), falseLiteral, rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, iRef), trueLiteral); final JoinInfo joinInfo = join.analyzeCondition(); for (int leftKey : joinInfo.leftKeys) { final RexNode kRef = rexBuilder.makeInputRef(root, leftKey); args.add(rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, kRef), unknownLiteral); } args.add(rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ckRef, cRef), unknownLiteral, falseLiteral); return rexBuilder.makeCall( nullableBooleanType, SqlStdOperatorTable.CASE, args.build()); 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 * 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 */ private boolean convertNonCorrelatedSubQuery( SubQuery subQuery, Blackboard bb, RelNode converted, boolean isExists) { SqlCall call = (SqlBasicCall) subQuery.node; if (subqueryConverter.canConvertSubquery() && isSubqNonCorrelated(converted, bb)) { // First check if the subquery has already been converted // because it's a nested subquery. If so, don't re-evaluate // it again. RexNode constExpr = mapConvertedNonCorrSubqs.get(call); if (constExpr == null) { constExpr = subqueryConverter.convertSubquery( call, this, isExists, isExplain); } if (constExpr != null) { subQuery.expr = constExpr; mapConvertedNonCorrSubqs.put(call, constExpr); return true; } } return false; } /** * Converts the RelNode tree for a select statement to a select that * produces a single value. * * @param query the query * @param plan the original RelNode tree corresponding to the statement * @return the converted RelNode tree */ public RelNode convertToSingleValueSubq( SqlNode query, RelNode plan) { // Check whether query is guaranteed to produce a single value. if (query instanceof SqlSelect) { SqlSelect select = (SqlSelect) query; SqlNodeList selectList = select.getSelectList(); SqlNodeList groupList = select.getGroup(); if ((selectList.size() == 1) && ((groupList == null) || (groupList.size() == 0))) { SqlNode selectExpr = selectList.get(0); if (selectExpr instanceof SqlCall) { SqlCall selectExprCall = (SqlCall) selectExpr; if (selectExprCall.getOperator() instanceof SqlAggFunction) { return plan; } } } } // If not, project SingleValueAgg return RelOptUtil.createSingleValueAggRel( cluster, plan); } /** * Converts "x IN (1, 2, ...)" to "x=1 OR x=2 OR ...". * * @param leftKeys LHS * @param valuesList RHS * @param isNotIn is this a NOT IN operator * @return converted expression */ private RexNode convertInToOr( final Blackboard bb, final List<RexNode> leftKeys, SqlNodeList valuesList, boolean isNotIn) { List<RexNode> comparisons = new ArrayList<RexNode>(); for (SqlNode rightVals : valuesList) { RexNode rexComparison; if (leftKeys.size() == 1) { rexComparison = rexBuilder.makeCall( SqlStdOperatorTable.EQUALS, leftKeys.get(0), bb.convertExpression(rightVals)); } else { assert rightVals instanceof SqlCall; final SqlBasicCall call = (SqlBasicCall) rightVals; assert (call.getOperator() instanceof SqlRowOperator) && call.getOperands().length == 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, bb.convertExpression(pair.right)); } }), false); } comparisons.add(rexComparison); } RexNode result = RexUtil.composeDisjunction(rexBuilder, comparisons, true); assert result != null; if (isNotIn) { result = rexBuilder.makeCall( SqlStdOperatorTable.NOT, result); } return result; } /** * Gets the list size threshold under which {@link #convertInToOr} is used. * Lists of this size or greater will instead be converted to use a join * against an inline table ({@link ValuesRel}) rather than a predicate. A * threshold of 0 forces usage of an inline table in all cases; a threshold * of Integer.MAX_VALUE forces usage of OR in all cases * * @return threshold, default 20 */ protected int getInSubqueryThreshold() { return 20; } /** * Converts an EXISTS or IN predicate into a join. For EXISTS, the subquery * produces an indicator variable, and the result is a relational expression * which outer joins that indicator to the original query. After performing * the outer join, the condition will be TRUE if the EXISTS condition holds, * NULL otherwise. * * @param seek A query, for example 'select * from emp' or * 'values (1,2,3)' or '('Foo', 34)'. * @param subqueryType Whether sub-query is IN, EXISTS or scalar * @param logic Whether the answer needs to be in full 3-valued logic (TRUE, * FALSE, UNKNOWN) will be required, or whether we can accept an * approximation (say representing UNKNOWN as FALSE) * @param needsOuterJoin Whether an outer join is needed * @return join expression * @pre extraExpr == null || extraName != null */ private Pair<RelNode, Boolean> convertExists( SqlNode seek, RelOptUtil.SubqueryType subqueryType, RelOptUtil.Logic logic, boolean needsOuterJoin) { final SqlValidatorScope seekScope = (seek instanceof SqlSelect) ? validator.getSelectScope((SqlSelect) seek) : null; final Blackboard seekBb = createBlackboard(seekScope, null); RelNode seekRel = convertQueryOrInList(seekBb, seek); return RelOptUtil.createExistsPlan(seekRel, subqueryType, logic, needsOuterJoin); } private RelNode convertQueryOrInList( Blackboard bb, SqlNode seek) { // NOTE: Once we start accepting single-row queries as row constructors, // there will be an ambiguity here for a case like X IN ((SELECT Y FROM // Z)). The SQL standard resolves the ambiguity by saying that a lone // select should be interpreted as a table expression, not a row // expression. The semantic difference is that a table expression can // return multiple rows. if (seek instanceof SqlNodeList) { return convertRowValues( bb, seek, ((SqlNodeList) seek).getList(), false, null); } else { return convertQueryRecursive(seek, false, null); } } 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 ValuesRel; 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 // OneRowRel. final List<List<RexLiteral>> tupleList = new ArrayList<List<RexLiteral>>(); final RelDataType rowType; if (targetRowType != null) { rowType = targetRowType; } else { rowType = SqlTypeUtil.promoteToRowType( typeFactory, validator.getValidatedNodeType(rowList), null); } List<RelNode> unionInputs = new ArrayList<RelNode>(); for (SqlNode node : rows) { SqlBasicCall call; if (isRowConstructor(node)) { call = (SqlBasicCall) node; List<RexLiteral> tuple = new ArrayList<RexLiteral>(); for (SqlNode operand : call.operands) { RexLiteral rexLiteral = convertLiteralInValuesList( operand, bb, rowType, tuple.size()); if ((rexLiteral == null) && allowLiteralsOnly) { return null; } if ((rexLiteral == null) || !shouldCreateValuesRel) { // fallback to convertRowConstructor tuple = null; break; } tuple.add(rexLiteral); } if (tuple != null) { tupleList.add(tuple); continue; } } else { RexLiteral rexLiteral = convertLiteralInValuesList( node, bb, rowType, 0); if ((rexLiteral != null) && shouldCreateValuesRel) { tupleList.add( Collections.singletonList(rexLiteral)); continue; } else { if ((rexLiteral == null) && allowLiteralsOnly) { return null; } } // convert "1" to "row(1)" call = (SqlBasicCall) SqlStdOperatorTable.ROW.createCall( SqlParserPos.ZERO, node); } unionInputs.add(convertRowConstructor(bb, call)); } ValuesRel valuesRel = new ValuesRel( cluster, rowType, tupleList); RelNode resultRel; if (unionInputs.isEmpty()) { resultRel = valuesRel; } else { if (!tupleList.isEmpty()) { unionInputs.add(valuesRel); } UnionRel unionRel = new UnionRel( cluster, unionInputs, true); resultRel = unionRel; } leaves.add(resultRel); return resultRel; } private RexLiteral convertLiteralInValuesList( SqlNode sqlNode, Blackboard bb, RelDataType rowType, int iField) { if (!(sqlNode instanceof SqlLiteral)) { return null; } RelDataTypeField field = rowType.getFieldList().get(iField); RelDataType type = field.getType(); if (type.isStruct()) { // null literals for weird stuff like UDT's need // special handling during type flattening, so // don't use ValuesRel for those return null; } RexNode literalExpr = exprConverter.convertLiteral( bb, (SqlLiteral) sqlNode); if (!(literalExpr instanceof RexLiteral)) { assert literalExpr.isA(SqlKind.CAST); RexNode child = ((RexCall) literalExpr).getOperands().get(0); assert RexLiteral.isNullLiteral(child); // NOTE jvs 22-Nov-2006: we preserve type info // in ValuesRel digest, so it's OK to lose it here return (RexLiteral) child; } RexLiteral literal = (RexLiteral) literalExpr; Comparable value = literal.getValue(); if (SqlTypeUtil.isExactNumeric(type)) { BigDecimal roundedValue = NumberUtil.rescaleBigDecimal( (BigDecimal) value, type.getScale()); return rexBuilder.makeExactLiteral( roundedValue, type); } if ((value instanceof NlsString) && (type.getSqlTypeName() == SqlTypeName.CHAR)) { // pad fixed character type NlsString unpadded = (NlsString) value; return rexBuilder.makeCharLiteral( new NlsString( Util.rpad(unpadded.getValue(), type.getPrecision()), unpadded.getCharsetName(), unpadded.getCollation())); } return literal; } private boolean isRowConstructor(SqlNode node) { if (!(node.getKind() == SqlKind.ROW)) { return false; } SqlCall call = (SqlCall) node; return call.getOperator().getName().equalsIgnoreCase("row"); } /** * Builds a list of all <code>IN</code> or <code>EXISTS</code> operators * inside SQL parse tree. Does not traverse inside queries. * * @param bb blackboard * @param node the SQL parse tree * @param logic Whether the answer needs to be in full 3-valued logic (TRUE, * FALSE, UNKNOWN) will be required, or whether we can accept * an approximation (say representing UNKNOWN as FALSE) * @param registerOnlyScalarSubqueries if set to true and the parse tree * corresponds to a variation of a select * node, only register it if it's a scalar * subquery */ 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 CURSOR: case SCALAR_QUERY: if (!registerOnlyScalarSubqueries || (kind == SqlKind.SCALAR_QUERY)) { bb.registerSubquery(node, RelOptUtil.Logic.TRUE_FALSE); } return; case IN: if (((SqlCall) node).getOperator() == SqlStdOperatorTable.NOT_IN) { logic = logic.negate(); } break; case NOT: logic = logic.negate(); break; } if (node instanceof SqlCall) { 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); } } } else if (node instanceof SqlNodeList) { for (SqlNode child : (SqlNodeList) node) { findSubqueries( bb, child, logic, kind == SqlKind.IN || registerOnlyScalarSubqueries); } } // Now that we've located any scalar subqueries inside the IN // expression, register the IN expression itself. We need to // register the scalar subqueries 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; } bb.registerSubquery(node, logic); } } /** * Converts an expression from {@link SqlNode} to {@link RexNode} format. * * @param node Expression to translate * @return Converted expression */ public RexNode convertExpression( SqlNode node) { Map<String, RelDataType> nameToTypeMap = Collections.emptyMap(); Blackboard bb = createBlackboard( new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap), null); return bb.convertExpression(node); } /** * Converts an expression from {@link SqlNode} to {@link RexNode} format, * mapping identifier references to predefined expressions. * * @param node Expression to translate * @param nameToNodeMap map from String to {@link RexNode}; when an * {@link SqlIdentifier} is encountered, it is used as a * key and translated to the corresponding value from * this map * @return Converted expression */ public RexNode convertExpression( SqlNode node, Map<String, RexNode> nameToNodeMap) { final Map<String, RelDataType> nameToTypeMap = new HashMap<String, RelDataType>(); for (Map.Entry<String, RexNode> entry : nameToNodeMap.entrySet()) { nameToTypeMap.put(entry.getKey(), entry.getValue().getType()); } Blackboard bb = createBlackboard( new ParameterScope((SqlValidatorImpl) validator, nameToTypeMap), nameToNodeMap); return bb.convertExpression(node); } /** * Converts a non-standard expression. * * <p>This method is an extension-point that derived classes can override. If * this method returns a null result, the normal expression translation * process will proceed. The default implementation always returns null. * * @param node Expression * @param bb Blackboard * @return null to proceed with the usual expression translation process */ protected RexNode convertExtendedExpression( SqlNode node, Blackboard bb) { return null; } private RexNode convertOver(Blackboard bb, SqlNode node) { SqlCall call = (SqlCall) node; SqlCall aggCall = call.operand(0); SqlNode windowOrRef = call.operand(1); final SqlWindow window = validator.resolveWindow(windowOrRef, bb.scope, true); final SqlNodeList partitionList = window.getPartitionList(); final ImmutableList.Builder<RexNode> partitionKeys = ImmutableList.builder(); for (SqlNode partition : partitionList) { partitionKeys.add(bb.convertExpression(partition)); } RexNode lowerBound = bb.convertExpression(window.getLowerBound()); RexNode upperBound = bb.convertExpression(window.getUpperBound()); SqlNodeList orderList = window.getOrderList(); if ((orderList.size() == 0) && !window.isRows()) { // A logical range requires an ORDER BY clause. Use the implicit // ordering of this relation. There must be one, otherwise it would // have failed validation. orderList = bb.scope.getOrderList(); if (orderList == null) { throw new AssertionError( "Relation should have sort key for implicit ORDER BY"); } } final ImmutableList.Builder<RexFieldCollation> orderKeys = ImmutableList.builder(); final Set<SqlKind> flags = EnumSet.noneOf(SqlKind.class); for (SqlNode order : orderList) { flags.clear(); RexNode e = bb.convertSortExpression(order, flags); orderKeys.add(new RexFieldCollation(e, flags)); } try { 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); // Walk over the tree and apply 'over' to all agg functions. This is // necessary because the returned expression is not necessarily a call // to an agg function. For example, AVG(x) becomes SUM(x) / COUNT(x). final RexShuttle visitor = new HistogramShuttle( partitionKeys.build(), orderKeys.build(), RexWindowBound.create(window.getLowerBound(), lowerBound), RexWindowBound.create(window.getUpperBound(), upperBound), window); return rexAgg.accept(visitor); } finally { bb.window = null; } } /** * Converts a FROM clause into a relational expression. * * @param bb Scope within which to resolve identifiers * @param from FROM clause of a query. Examples include: * * <ul> * <li>a single table ("SALES.EMP"), * <li>an aliased table ("EMP AS E"), * <li>a list of tables ("EMP, DEPT"), * <li>an ANSI Join expression ("EMP JOIN DEPT ON EMP.DEPTNO = * DEPT.DEPTNO"), * <li>a VALUES clause ("VALUES ('Fred', 20)"), * <li>a query ("(SELECT * FROM EMP WHERE GENDER = 'F')"), * <li>or any combination of the above. * </ul> */ protected void convertFrom( Blackboard bb, SqlNode from) { SqlCall call; final SqlNode[] operands; switch (from.getKind()) { case AS: operands = ((SqlBasicCall) from).getOperands(); convertFrom(bb, operands[0]); return; case WITH_ITEM: convertFrom(bb, ((SqlWithItem) from).query); return; case WITH: convertFrom(bb, ((SqlWith) from).body); return; case TABLESAMPLE: operands = ((SqlBasicCall) from).getOperands(); SqlSampleSpec sampleSpec = SqlLiteral.sampleValue(operands[1]); if (sampleSpec instanceof SqlSampleSpec.SqlSubstitutionSampleSpec) { String sampleName = ((SqlSampleSpec.SqlSubstitutionSampleSpec) sampleSpec) .getName(); datasetStack.push(sampleName); convertFrom(bb, operands[0]); datasetStack.pop(); } else if (sampleSpec instanceof SqlSampleSpec.SqlTableSampleSpec) { SqlSampleSpec.SqlTableSampleSpec tableSampleSpec = (SqlSampleSpec.SqlTableSampleSpec) sampleSpec; convertFrom(bb, operands[0]); RelOptSamplingParameters params = new RelOptSamplingParameters( tableSampleSpec.isBernoulli(), tableSampleSpec.getSamplePercentage(), tableSampleSpec.isRepeatable(), tableSampleSpec.getRepeatableSeed()); bb.setRoot(new SamplingRel(cluster, bb.root, params), false); } else { throw Util.newInternal( "unknown TABLESAMPLE type: " + sampleSpec); } return; 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 (shouldConvertTableAccess) { tableRel = toRel(table); } else { tableRel = new TableAccessRel(cluster, table); } bb.setRoot(tableRel, true); if (usedDataset[0]) { bb.setDataset(datasetName); } return; case JOIN: final SqlJoin join = (SqlJoin) from; final Blackboard fromBlackboard = createBlackboard(validator.getJoinScope(from), null); SqlNode left = join.getLeft(); SqlNode right = join.getRight(); final boolean isNatural = join.isNatural(); final JoinType joinType = join.getJoinType(); final Blackboard leftBlackboard = createBlackboard( Util.first(validator.getJoinScope(left), ((DelegatingScope) bb.scope).getParent()), null); final Blackboard rightBlackboard = createBlackboard( Util.first(validator.getJoinScope(right), ((DelegatingScope) bb.scope).getParent()), null); convertFrom(leftBlackboard, left); RelNode leftRel = leftBlackboard.root; convertFrom(rightBlackboard, right); RelNode rightRel = rightBlackboard.root; JoinRelType convertedJoinType = convertJoinType(joinType); RexNode conditionExp; if (isNatural) { final List<String> columnList = SqlValidatorUtil.deriveNaturalJoinColumnList( validator.getNamespace(left).getRowType(), validator.getNamespace(right).getRowType()); conditionExp = convertUsing(leftRel, rightRel, columnList); } else { conditionExp = convertJoinCondition( fromBlackboard, join.getCondition(), join.getConditionType(), leftRel, rightRel); } final RelNode joinRel = createJoin( fromBlackboard, leftRel, rightRel, conditionExp, convertedJoinType); bb.setRoot(joinRel, false); return; case SELECT: case INTERSECT: case EXCEPT: case UNION: final RelNode rel = convertQueryRecursive(from, false, null); bb.setRoot(rel, true); return; case VALUES: convertValuesImpl(bb, (SqlCall) from, null); return; case UNNEST: final SqlNode node = ((SqlCall) from).operand(0); replaceSubqueries(bb, node, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); final RelNode childRel = RelOptUtil.createProject( (null != bb.root) ? bb.root : new OneRowRel(cluster), Collections.singletonList(bb.convertExpression(node)), Collections.singletonList(validator.deriveAlias(node, 0)), true); UncollectRel uncollectRel = new UncollectRel(cluster, cluster.traitSetOf(Convention.NONE), childRel); bb.setRoot(uncollectRel, true); return; case COLLECTION_TABLE: call = (SqlCall) from; // Dig out real call; TABLE() wrapper is just syntactic. assert call.getOperandList().size() == 1; call = call.operand(0); convertCollectionTable(bb, call); return; default: throw Util.newInternal("not a join operator " + from); } } protected void convertCollectionTable( Blackboard bb, SqlCall call) { final SqlOperator operator = call.getOperator(); if (operator == SqlStdOperatorTable.TABLESAMPLE) { final String sampleName = SqlLiteral.stringValue(call.operand(0)); datasetStack.push(sampleName); SqlCall cursorCall = call.operand(1); SqlNode query = cursorCall.operand(0); RelNode converted = convertQuery(query, false, false); bb.setRoot(converted, false); datasetStack.pop(); return; } replaceSubqueries(bb, call, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); // Expand table macro if possible. It's more efficient than // TableFunctionRel. if (operator instanceof SqlUserDefinedTableMacro) { final SqlUserDefinedTableMacro udf = (SqlUserDefinedTableMacro) operator; final TranslatableTable table = udf.getTable(typeFactory, call.getOperandList()); final RelDataType rowType = table.getRowType(typeFactory); RelOptTable relOptTable = RelOptTableImpl.create(null, rowType, table); RelNode converted = toRel(relOptTable); bb.setRoot(converted, true); return; } Type elementType; if (operator instanceof SqlUserDefinedTableFunction) { SqlUserDefinedTableFunction udtf = (SqlUserDefinedTableFunction) operator; elementType = udtf.getElementType(typeFactory, call.getOperandList()); } else { elementType = null; } RexNode rexCall = bb.convertExpression(call); final List<RelNode> inputs = bb.retrieveCursors(); Set<RelColumnMapping> columnMappings = getColumnMappings(operator); TableFunctionRel callRel = new TableFunctionRel( cluster, inputs, rexCall, elementType, validator.getValidatedNodeType(call), columnMappings); bb.setRoot(callRel, true); afterTableFunction(bb, call, callRel); } protected void afterTableFunction( SqlToRelConverter.Blackboard bb, SqlCall call, TableFunctionRel callRel) { } private Set<RelColumnMapping> getColumnMappings(SqlOperator op) { SqlReturnTypeInference rti = op.getReturnTypeInference(); if (rti == null) { return null; } if (rti instanceof TableFunctionReturnTypeInference) { TableFunctionReturnTypeInference tfrti = (TableFunctionReturnTypeInference) rti; return tfrti.getColumnMappings(); } else { return null; } } protected RelNode createJoin( Blackboard bb, RelNode leftRel, RelNode rightRel, RexNode joinCond, JoinRelType joinType) { assert joinCond != null; Set<String> correlatedVariables = RelOptUtil.getVariablesUsed(rightRel); if (correlatedVariables.size() > 0) { final List<Correlation> correlations = Lists.newArrayList(); for (String correlName : correlatedVariables) { DeferredLookup lookup = mapCorrelToDeferred.get(correlName); RexFieldAccess fieldAccess = lookup.getFieldAccess(correlName); String originalRelName = lookup.getOriginalRelName(); String originalFieldName = fieldAccess.getField().getName(); int[] nsIndexes = {-1}; final SqlValidatorScope[] ancestorScopes = {null}; SqlValidatorNamespace foundNs = lookup.bb.scope.resolve( originalRelName, ancestorScopes, nsIndexes); assert foundNs != null; assert nsIndexes.length == 1; int childNamespaceIndex = nsIndexes[0]; SqlValidatorScope ancestorScope = ancestorScopes[0]; boolean correlInCurrentScope = ancestorScope == bb.scope; if (correlInCurrentScope) { int namespaceOffset = 0; if (childNamespaceIndex > 0) { // If not the first child, need to figure out the width // of output types from all the preceding namespaces assert ancestorScope instanceof ListScope; List<SqlValidatorNamespace> children = ((ListScope) ancestorScope).getChildren(); for (int i = 0; i < childNamespaceIndex; i++) { SqlValidatorNamespace child = children.get(i); namespaceOffset += child.getRowType().getFieldCount(); } } RelDataTypeField field = catalogReader.field(foundNs.getRowType(), originalFieldName); int pos = namespaceOffset + field.getIndex(); assert field.getType() == lookup.getFieldAccess(correlName).getField().getType(); assert pos != -1; if (bb.mapRootRelToFieldProjection.containsKey(bb.root)) { // bb.root is an aggregate and only projects group by // keys. Map<Integer, Integer> exprProjection = bb.mapRootRelToFieldProjection.get(bb.root); // subquery 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 Util.newInternal( "Identifier '" + originalRelName + "." + originalFieldName + "' is not a group expr"); } } Correlation newCorVar = new Correlation( getCorrelOrdinal(correlName), pos); correlations.add(newCorVar); } } if (!correlations.isEmpty()) { return new CorrelatorRel( rightRel.getCluster(), leftRel, rightRel, joinCond, correlations, joinType); } } final List<RexNode> extraLeftExprs = new ArrayList<RexNode>(); final List<RexNode> extraRightExprs = new ArrayList<RexNode>(); final int leftCount = leftRel.getRowType().getFieldCount(); final int rightCount = rightRel.getRowType().getFieldCount(); if (!containsGet(joinCond)) { joinCond = pushDownJoinConditions( joinCond, leftCount, rightCount, extraLeftExprs, extraRightExprs); } if (!extraLeftExprs.isEmpty()) { final List<RelDataTypeField> fields = leftRel.getRowType().getFieldList(); leftRel = RelOptUtil.createProject( leftRel, new AbstractList<Pair<RexNode, String>>() { @Override public int size() { return leftCount + extraLeftExprs.size(); } @Override public Pair<RexNode, String> get(int index) { if (index < leftCount) { RelDataTypeField field = fields.get(index); return Pair.<RexNode, String>of( new RexInputRef(index, field.getType()), field.getName()); } else { return Pair.<RexNode, String>of( extraLeftExprs.get(index - leftCount), null); } } }, true); } if (!extraRightExprs.isEmpty()) { final List<RelDataTypeField> fields = rightRel.getRowType().getFieldList(); final int newLeftCount = leftCount + extraLeftExprs.size(); rightRel = RelOptUtil.createProject( rightRel, new AbstractList<Pair<RexNode, String>>() { @Override public int size() { return rightCount + extraRightExprs.size(); } @Override public Pair<RexNode, String> get(int index) { if (index < rightCount) { RelDataTypeField field = fields.get(index); return Pair.<RexNode, String>of( new RexInputRef(index, field.getType()), field.getName()); } else { return Pair.of( RexUtil.shift( extraRightExprs.get(index - rightCount), -newLeftCount), null); } } }, true); } RelNode join = createJoin( leftRel, rightRel, joinCond, joinType, ImmutableSet.<String>of()); if (!extraLeftExprs.isEmpty() || !extraRightExprs.isEmpty()) { Mappings.TargetMapping mapping = Mappings.createShiftMapping( leftCount + extraLeftExprs.size() + rightCount + extraRightExprs.size(), 0, 0, leftCount, leftCount, leftCount + extraLeftExprs.size(), rightCount); return RelOptUtil.project(join, mapping); } return join; } private static boolean containsGet(RexNode node) { try { node.accept( new RexVisitorImpl<Void>(true) { @Override public Void visitCall(RexCall call) { if (call.getOperator() == RexBuilder.GET_OPERATOR) { throw Util.FoundOne.NULL; } return super.visitCall(call); } }); return false; } catch (Util.FoundOne e) { return true; } } /** * Pushes down parts of a join condition. For example, given * "emp JOIN dept ON emp.deptno + 1 = dept.deptno", adds a project above * "emp" that computes the expression * "emp.deptno + 1". The resulting join condition is a simple combination * of AND, equals, and input fields. */ private RexNode pushDownJoinConditions( RexNode node, int leftCount, int rightCount, List<RexNode> extraLeftExprs, List<RexNode> extraRightExprs) { switch (node.getKind()) { case AND: case OR: case EQUALS: RexCall call = (RexCall) node; List<RexNode> list = new ArrayList<RexNode>(); List<RexNode> operands = Lists.newArrayList(call.getOperands()); for (int i = 0; i < operands.size(); i++) { RexNode operand = operands.get(i); final int left2 = leftCount + extraLeftExprs.size(); final int right2 = rightCount + extraRightExprs.size(); final RexNode e = pushDownJoinConditions( operand, leftCount, rightCount, extraLeftExprs, extraRightExprs); final List<RexNode> remainingOperands = Util.skip(operands, i + 1); final int left3 = leftCount + extraLeftExprs.size(); final int right3 = rightCount + extraRightExprs.size(); fix(remainingOperands, left2, left3); fix(list, left2, left3); list.add(e); } if (!list.equals(call.getOperands())) { return call.clone(call.getType(), list); } return call; case INPUT_REF: case LITERAL: return node; default: BitSet bits = RelOptUtil.InputFinder.bits(node); final int mid = leftCount + extraLeftExprs.size(); switch (Side.of(bits, mid)) { case LEFT: fix(extraRightExprs, mid, mid + 1); extraLeftExprs.add(node); return new RexInputRef(mid, node.getType()); case RIGHT: final int index2 = mid + rightCount + extraRightExprs.size(); extraRightExprs.add(node); return new RexInputRef(index2, node.getType()); case BOTH: case EMPTY: default: return node; } } } private void fix(List<RexNode> operands, int before, int after) { if (before == after) { return; } for (int i = 0; i < operands.size(); i++) { RexNode node = operands.get(i); operands.set(i, RexUtil.shift(node, before, after - before)); } } /** * Categorizes whether a bit set contains bits left and right of a * line. */ enum Side { LEFT, RIGHT, BOTH, EMPTY; static Side of(BitSet bitSet, int middle) { final int firstBit = bitSet.nextSetBit(0); if (firstBit < 0) { return EMPTY; } if (firstBit >= middle) { return RIGHT; } if (bitSet.nextSetBit(middle) < 0) { return LEFT; } return BOTH; } } /** * Determines whether a subquery is non-correlated. Note that a * non-correlated subquery can contain correlated references, provided those * references do not reference select statements that are parents of the * subquery. * * @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. */ private boolean isSubqNonCorrelated(RelNode subq, Blackboard bb) { Set<String> correlatedVariables = RelOptUtil.getVariablesUsed(subq); for (String correlName : correlatedVariables) { DeferredLookup lookup = mapCorrelToDeferred.get(correlName); String originalRelName = lookup.getOriginalRelName(); int[] nsIndexes = {-1}; final SqlValidatorScope[] ancestorScopes = {null}; SqlValidatorNamespace foundNs = lookup.bb.scope.resolve( originalRelName, ancestorScopes, nsIndexes); assert foundNs != null; assert nsIndexes.length == 1; SqlValidatorScope ancestorScope = ancestorScopes[0]; // If the correlated reference is in a scope that's "above" the // subquery, then this is a correlated subquery. SqlValidatorScope parentScope = bb.scope; do { if (ancestorScope == parentScope) { return false; } if (parentScope instanceof DelegatingScope) { parentScope = ((DelegatingScope) parentScope).getParent(); } else { break; } } while (parentScope != null); } return true; } /** * Returns a list of fields to be prefixed to each relational expression. * * @return List of system fields */ protected List<RelDataTypeField> getSystemFields() { return Collections.emptyList(); } private RexNode convertJoinCondition( Blackboard bb, SqlNode condition, JoinConditionType conditionType, RelNode leftRel, RelNode rightRel) { if (condition == null) { return rexBuilder.makeLiteral(true); } bb.setRoot(ImmutableList.of(leftRel, rightRel)); replaceSubqueries(bb, condition, RelOptUtil.Logic.UNKNOWN_AS_FALSE); switch (conditionType) { case ON: bb.setRoot(ImmutableList.of(leftRel, rightRel)); return bb.convertExpression(condition); case USING: SqlNodeList list = (SqlNodeList) condition; List<String> nameList = new ArrayList<String>(); for (SqlNode columnName : list) { final SqlIdentifier id = (SqlIdentifier) columnName; String name = id.getSimple(); nameList.add(name); } return convertUsing(leftRel, rightRel, nameList); default: throw Util.unexpected(conditionType); } } /** * Returns an expression for matching columns of a USING clause or inferred * from NATURAL JOIN. "a JOIN b USING (x, y)" becomes "a.x = b.x AND a.y = * b.y". Returns null if the column list is empty. * * @param leftRel Left input to the join * @param rightRel Right input to the join * @param nameList List of column names to join on * @return Expression to match columns from name list, or true if name list * is empty */ private RexNode convertUsing( RelNode leftRel, RelNode rightRel, List<String> nameList) { final List<RexNode> list = Lists.newArrayList(); for (String name : nameList) { final RelDataType leftRowType = leftRel.getRowType(); RelDataTypeField leftField = catalogReader.field(leftRowType, name); RexNode left = rexBuilder.makeInputRef( leftField.getType(), leftField.getIndex()); final RelDataType rightRowType = rightRel.getRowType(); RelDataTypeField rightField = catalogReader.field(rightRowType, name); RexNode right = rexBuilder.makeInputRef( rightField.getType(), leftRowType.getFieldList().size() + rightField.getIndex()); RexNode equalsCall = rexBuilder.makeCall( SqlStdOperatorTable.EQUALS, left, right); list.add(equalsCall); } return RexUtil.composeConjunction(rexBuilder, list, false); } private static JoinRelType convertJoinType(JoinType joinType) { switch (joinType) { case COMMA: case INNER: case CROSS: return JoinRelType.INNER; case FULL: return JoinRelType.FULL; case LEFT: return JoinRelType.LEFT; case RIGHT: return JoinRelType.RIGHT; default: throw Util.unexpected(joinType); } } /** * Converts the SELECT, GROUP BY and HAVING clauses of an aggregate query. * * <p>This method extracts SELECT, GROUP BY and HAVING clauses, and creates * an {@link AggConverter}, then delegates to {@link #createAggImpl}. * Derived class may override this method to change any of those clauses or * specify a different {@link AggConverter}. * * @param bb Scope within which to resolve identifiers * @param select Query * @param orderExprList Additional expressions needed to implement ORDER BY */ protected void convertAgg( Blackboard bb, SqlSelect select, List<SqlNode> orderExprList) { assert bb.root != null : "precondition: child != null"; SqlNodeList groupList = select.getGroup(); SqlNodeList selectList = select.getSelectList(); SqlNode having = select.getHaving(); final AggConverter aggConverter = new AggConverter(bb, select); createAggImpl( bb, aggConverter, selectList, groupList, having, orderExprList); } protected final void createAggImpl( Blackboard bb, AggConverter aggConverter, SqlNodeList selectList, SqlNodeList groupList, SqlNode having, List<SqlNode> orderExprList) { SqlNodeList aggList = new SqlNodeList(SqlParserPos.ZERO); for (SqlNode selectNode : selectList) { if (validator.isAggregate(selectNode)) { aggList.add(selectNode); } } // first replace the subqueries inside the aggregates // because they will provide input rows to the aggregates. replaceSubqueries(bb, aggList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); // If group-by clause is missing, pretend that it has zero elements. if (groupList == null) { groupList = SqlNodeList.EMPTY; } // register the group exprs // build a map to remember the projections from the top scope to the // output of the current root. // // Currently farrago allows expressions, not just column references in // group by list. This is not SQL 2003 compliant. Map<Integer, Integer> groupExprProjection = new HashMap<Integer, Integer>(); int i = -1; for (SqlNode groupExpr : groupList) { ++i; final SqlNode expandedGroupExpr = validator.expand(groupExpr, bb.scope); aggConverter.addGroupExpr(expandedGroupExpr); if (expandedGroupExpr instanceof SqlIdentifier) { // SQL 2003 does not allow expressions of column references SqlIdentifier expr = (SqlIdentifier) expandedGroupExpr; // column references should be fully qualified. assert expr.names.size() == 2; String originalRelName = expr.names.get(0); String originalFieldName = expr.names.get(1); int[] nsIndexes = {-1}; final SqlValidatorScope[] ancestorScopes = {null}; SqlValidatorNamespace foundNs = bb.scope.resolve( originalRelName, ancestorScopes, nsIndexes); assert foundNs != null; assert nsIndexes.length == 1; int childNamespaceIndex = nsIndexes[0]; int namespaceOffset = 0; if (childNamespaceIndex > 0) { // If not the first child, need to figure out the width of // output types from all the preceding namespaces assert ancestorScopes[0] instanceof ListScope; List<SqlValidatorNamespace> children = ((ListScope) ancestorScopes[0]).getChildren(); for (int j = 0; j < childNamespaceIndex; j++) { namespaceOffset += children.get(j).getRowType().getFieldCount(); } } RelDataTypeField field = catalogReader.field(foundNs.getRowType(), originalFieldName); int origPos = namespaceOffset + field.getIndex(); groupExprProjection.put(origPos, i); } } RexNode havingExpr = null; List<RexNode> selectExprs = new ArrayList<RexNode>(); List<String> selectNames = new ArrayList<String>(); try { Util.permAssert(bb.agg == null, "already in agg mode"); bb.agg = aggConverter; // convert the select and having expressions, so that the // agg converter knows which aggregations are required selectList.accept(aggConverter); for (SqlNode expr : orderExprList) { expr.accept(aggConverter); } if (having != null) { having.accept(aggConverter); } // compute inputs to the aggregator List<RexNode> preExprs = aggConverter.getPreExprs(); List<String> preNames = aggConverter.getPreNames(); if (preExprs.size() == 0) { // 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 = Collections.singletonList( (RexNode) 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.mapRootRelToFieldProjection.put(bb.root, groupExprProjection); // REVIEW jvs 31-Oct-2007: doesn't the declaration of // monotonicity here assume sort-based aggregation at // the physical level? // Tell bb which of group columns are sorted. bb.columnMonotonicities.clear(); for (SqlNode groupItem : groupList) { bb.columnMonotonicities.add( bb.scope.getMonotonicity(groupItem)); } // Add the aggregator bb.setRoot( createAggregate( bb, BitSets.range(aggConverter.groupExprs.size()), aggConverter.getAggCalls()), false); bb.mapRootRelToFieldProjection.put(bb.root, groupExprProjection); // Replace subqueries 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); 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 // any aggregate in the select list, and after the aggregate rel // is allocated. replaceSubqueries(bb, selectList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); // Now subqueries in the entire select list have been converted. // Convert the select expressions to get the final list to be // projected. int k = 0; // For select expressions, use the field names previously assigned // by the validator. If we derive afresh, we might generate names // like "EXPR$2" that don't match the names generated by the // 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 SqlValidatorNamespace selectNamespace = validator.getNamespace(selectScope.getNode()); final List<String> names = selectNamespace.getRowType().getFieldNames(); int sysFieldCount = selectList.size() - names.size(); for (SqlNode expr : selectList) { selectExprs.add(bb.convertExpression(expr)); selectNames.add( k < sysFieldCount ? validator.deriveAlias(expr, k++) : names.get(k++ - sysFieldCount)); } for (SqlNode expr : orderExprList) { selectExprs.add(bb.convertExpression(expr)); selectNames.add(validator.deriveAlias(expr, k++)); } } finally { bb.agg = null; } // implement HAVING (we have already checked that it is non-trivial) if (havingExpr != null) { bb.setRoot(RelOptUtil.createFilter(bb.root, havingExpr), false); } // implement the SELECT list bb.setRoot( RelOptUtil.createProject( bb.root, selectExprs, selectNames, true), false); // Tell bb which of group columns are sorted. bb.columnMonotonicities.clear(); for (SqlNode selectItem : selectList) { bb.columnMonotonicities.add( bb.scope.getMonotonicity(selectItem)); } } /** * Creates an AggregateRel. * * <p>In case the aggregate rel changes the order in which it projects * fields, the <code>groupExprProjection</code> parameter is provided, and * the implementation of this method may modify it. * * <p>The <code>sortedCount</code> parameter is the number of expressions * known to be monotonic. These expressions must be on the leading edge of * the grouping keys. The default implementation of this method ignores this * parameter. * * @param bb Blackboard * @param groupSet Bit set of ordinals of grouping columns * @param aggCalls Array of calls to aggregate functions * @return AggregateRel */ protected RelNode createAggregate( Blackboard bb, BitSet groupSet, List<AggregateCall> aggCalls) { return new AggregateRel( cluster, bb.root, groupSet, aggCalls); } 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 // have a resize() method?!? Make this a utility. while (dynamicParam.getIndex() >= dynamicParamSqlNodes.size()) { dynamicParamSqlNodes.add(null); } dynamicParamSqlNodes.set( dynamicParam.getIndex(), dynamicParam); return rexBuilder.makeDynamicParam( getDynamicParamType(dynamicParam.getIndex()), dynamicParam.getIndex()); } /** * Creates a list of collations required to implement the ORDER BY clause, * if there is one. Populates <code>extraOrderExprs</code> with any sort * expressions which are not in the select clause. * * @param bb Scope within which to resolve identifiers * @param select Select clause. Never null, because we invent a * dummy SELECT if ORDER BY is applied to a set * operation (UNION etc.) * @param orderList Order by clause, may be null * @param extraOrderExprs Sort expressions which are not in the select * clause (output) * @param collationList List of collations (output) */ protected void gatherOrderExprs( Blackboard bb, SqlSelect select, SqlNodeList orderList, List<SqlNode> extraOrderExprs, List<RelFieldCollation> collationList) { // TODO: add validation rules to SqlValidator also assert bb.root != null : "precondition: child != null"; assert select != null; if (orderList == null) { return; } for (SqlNode orderItem : orderList) { collationList.add( convertOrderItem( select, orderItem, extraOrderExprs, RelFieldCollation.Direction.ASCENDING, RelFieldCollation.NullDirection.UNSPECIFIED)); } } protected RelFieldCollation convertOrderItem( SqlSelect select, SqlNode orderItem, List<SqlNode> 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); } SqlNode converted = validator.expandOrderExpr(select, orderItem); // Scan the select list and order exprs for an identical expression. final SelectScope selectScope = validator.getRawSelectScope(select); int ordinal = -1; for (SqlNode selectItem : selectScope.getExpandedSelectList()) { ++ordinal; if (converted.equalsDeep(stripAs(selectItem), false)) { return new RelFieldCollation( ordinal, direction, nullDirection); } } for (SqlNode extraExpr : extraExprs) { ++ordinal; if (converted.equalsDeep(extraExpr, false)) { return new RelFieldCollation( ordinal, direction, nullDirection); } } // TODO: handle collation sequence // TODO: flag expressions as non-standard extraExprs.add(converted); return new RelFieldCollation(ordinal + 1, direction, nullDirection); } protected boolean enableDecorrelation() { // disable subquery decorrelation when needed. // e.g. if outer joins are not supported. return decorrelationEnabled; } protected RelNode decorrelateQuery(RelNode rootRel) { return RelDecorrelator.decorrelateQuery(rootRel); } /** * Sets whether to trim unused fields as part of the conversion process. * * @param trim Whether to trim unused fields */ public void setTrimUnusedFields(boolean trim) { this.trimUnusedFields = trim; } /** * Returns whether to trim unused fields as part of the conversion process. * * @return Whether to trim unused fields */ public boolean isTrimUnusedFields() { return trimUnusedFields; } /** * Recursively converts a query to a relational expression. * * @param query Query * @param top Whether this query is the top-level query of the * statement * @param targetRowType Target row type, or null * @return Relational expression */ protected RelNode convertQueryRecursive( SqlNode query, boolean top, RelDataType targetRowType) { switch (query.getKind()) { case SELECT: return convertSelect((SqlSelect) query); case INSERT: return convertInsert((SqlInsert) query); case DELETE: return convertDelete((SqlDelete) query); case UPDATE: return convertUpdate((SqlUpdate) query); case MERGE: return convertMerge((SqlMerge) query); case UNION: case INTERSECT: case EXCEPT: return convertSetOp((SqlCall) query); case WITH: return convertWith((SqlWith) query); case VALUES: return convertValues((SqlCall) query, targetRowType); default: throw Util.newInternal("not a query: " + query); } } /** * Converts a set operation (UNION, INTERSECT, MINUS) into relational * expressions. * * @param call Call to set operator * @return Relational expression */ protected RelNode convertSetOp(SqlCall call) { final RelNode left = convertQueryRecursive(call.operand(0), false, null); final RelNode right = convertQueryRecursive(call.operand(1), false, null); boolean all = false; if (call.getOperator() instanceof SqlSetOperator) { all = ((SqlSetOperator) (call.getOperator())).isAll(); } switch (call.getKind()) { case UNION: return new UnionRel( cluster, ImmutableList.of(left, right), all); case INTERSECT: // TODO: all if (!all) { return new IntersectRel( cluster, ImmutableList.of(left, right), all); } else { throw Util.newInternal( "set operator INTERSECT ALL not suported"); } case EXCEPT: // TODO: all if (!all) { return new MinusRel( cluster, ImmutableList.of(left, right), all); } else { throw Util.newInternal( "set operator EXCEPT ALL not suported"); } default: throw Util.unexpected(call.getKind()); } } protected RelNode convertInsert(SqlInsert call) { RelOptTable targetTable = getTargetTable(call); final RelDataType targetRowType = validator.getValidatedNodeType(call); assert targetRowType != null; RelNode sourceRel = convertQueryRecursive( call.getSource(), false, targetRowType); RelNode massagedRel = convertColumnList(call, sourceRel); final ModifiableTable modifiableTable = targetTable.unwrap(ModifiableTable.class); if (modifiableTable != null) { return modifiableTable.toModificationRel( cluster, targetTable, catalogReader, massagedRel, TableModificationRel.Operation.INSERT, null, false); } return new TableModificationRel( cluster, targetTable, catalogReader, massagedRel, TableModificationRel.Operation.INSERT, null, false); } private RelOptTable.ToRelContext createToRelContext() { return new RelOptTable.ToRelContext() { public RelOptCluster getCluster() { return cluster; } public RelNode expandView( RelDataType rowType, String queryString, List<String> schemaPath) { return viewExpander.expandView(rowType, queryString, schemaPath); } }; } public RelNode toRel(RelOptTable table) { return table.toRel(createToRelContext()); } protected RelOptTable getTargetTable(SqlNode call) { SqlValidatorNamespace targetNs = validator.getNamespace(call).resolve(); return SqlValidatorUtil.getRelOptTable(targetNs, catalogReader, null, null); } /** * Creates a source for an INSERT statement. * * <p>If the column list is not specified, source expressions match target * columns in order. * * <p>If the column list is specified, Source expressions are mapped to * target columns by name via targetColumnList, and may not cover the entire * target table. So, we'll make up a full row, using a combination of * default values and the source expressions provided. * * @param call Insert expression * @param sourceRel Source relational expression * @return Converted INSERT statement */ protected RelNode convertColumnList( SqlInsert call, RelNode sourceRel) { RelDataType sourceRowType = sourceRel.getRowType(); final RexNode sourceRef = rexBuilder.makeRangeReference(sourceRowType, 0, false); final List<String> targetColumnNames = new ArrayList<String>(); final List<RexNode> columnExprs = new ArrayList<RexNode>(); collectInsertTargets(call, sourceRef, targetColumnNames, columnExprs); final RelOptTable targetTable = getTargetTable(call); final RelDataType targetRowType = targetTable.getRowType(); final List<RelDataTypeField> targetFields = targetRowType.getFieldList(); final List<RexNode> sourceExps = new ArrayList<RexNode>( Collections.<RexNode>nCopies(targetFields.size(), null)); final List<String> fieldNames = new ArrayList<String>( Collections.<String>nCopies(targetFields.size(), null)); // Walk the name list and place the associated value in the // expression list according to the ordinal value returned from // the table construct, leaving nulls in the list for columns // that are not referenced. for (Pair<String, RexNode> p : Pair.zip(targetColumnNames, columnExprs)) { RelDataTypeField field = catalogReader.field(targetRowType, p.left); assert field != null : "column " + p.left + " not found"; sourceExps.set(field.getIndex(), p.right); } // Walk the expression list and get default values for any columns // that were not supplied in the statement. Get field names too. for (int i = 0; i < targetFields.size(); ++i) { final RelDataTypeField field = targetFields.get(i); final String fieldName = field.getName(); fieldNames.set(i, fieldName); if (sourceExps.get(i) != null) { if (defaultValueFactory.isGeneratedAlways(targetTable, i)) { throw RESOURCE.insertIntoAlwaysGenerated(fieldName).ex(); } continue; } sourceExps.set( i, defaultValueFactory.newColumnDefaultValue(targetTable, i)); // bare nulls are dangerous in the wrong hands sourceExps.set( i, castNullLiteralIfNeeded( sourceExps.get(i), field.getType())); } return RelOptUtil.createProject(sourceRel, sourceExps, fieldNames, true); } private RexNode castNullLiteralIfNeeded(RexNode node, RelDataType type) { if (!RexLiteral.isNullLiteral(node)) { return node; } return rexBuilder.makeCast(type, node); } /** * Given an INSERT statement, collects the list of names to be populated and * the expressions to put in them. * * @param call Insert statement * @param sourceRef Expression representing a row from the source * relational expression * @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<String> targetColumnNames, List<RexNode> columnExprs) { final RelOptTable targetTable = getTargetTable(call); final RelDataType targetRowType = targetTable.getRowType(); SqlNodeList targetColumnList = call.getTargetColumnList(); if (targetColumnList == null) { targetColumnNames.addAll(targetRowType.getFieldNames()); } else { for (int i = 0; i < targetColumnList.size(); i++) { SqlIdentifier id = (SqlIdentifier) targetColumnList.get(i); targetColumnNames.add(id.getSimple()); } } for (int i = 0; i < targetColumnNames.size(); i++) { final RexNode expr = rexBuilder.makeFieldAccess(sourceRef, i); columnExprs.add(expr); } } private RelNode convertDelete(SqlDelete call) { RelOptTable targetTable = getTargetTable(call); RelNode sourceRel = convertSelect(call.getSourceSelect()); return new TableModificationRel( cluster, targetTable, catalogReader, sourceRel, TableModificationRel.Operation.DELETE, null, false); } private RelNode convertUpdate(SqlUpdate call) { RelOptTable targetTable = getTargetTable(call); // convert update column list from SqlIdentifier to String List<String> targetColumnNameList = new ArrayList<String>(); for (SqlNode node : call.getTargetColumnList()) { SqlIdentifier id = (SqlIdentifier) node; String name = id.getSimple(); targetColumnNameList.add(name); } RelNode sourceRel = convertSelect(call.getSourceSelect()); return new TableModificationRel( cluster, targetTable, catalogReader, sourceRel, TableModificationRel.Operation.UPDATE, targetColumnNameList, false); } private RelNode convertMerge(SqlMerge call) { RelOptTable targetTable = getTargetTable(call); // convert update column list from SqlIdentifier to String List<String> targetColumnNameList = new ArrayList<String>(); SqlUpdate updateCall = call.getUpdateCall(); if (updateCall != null) { for (SqlNode targetColumn : updateCall.getTargetColumnList()) { SqlIdentifier id = (SqlIdentifier) targetColumn; String name = id.getSimple(); targetColumnNameList.add(name); } } // replace the projection of the source select with a // projection that contains the following: // 1) the expressions corresponding to the new insert row (if there is // an insert) // 2) all columns from the target table (if there is an update) // 3) the set expressions in the update call (if there is an update) // first, convert the merge's source select to construct the columns // from the target table and the set expressions in the update call RelNode mergeSourceRel = convertSelect(call.getSourceSelect()); // then, convert the insert statement so we can get the insert // values expressions SqlInsert insertCall = call.getInsertCall(); int nLevel1Exprs = 0; List<RexNode> level1InsertExprs = null; List<RexNode> level2InsertExprs = null; if (insertCall != null) { RelNode insertRel = convertInsert(insertCall); // if there are 2 level of projections in the insert source, combine // them into a single project; level1 refers to the topmost project; // the level1 projection contains references to the level2 // expressions, except in the case where no target expression was // provided, in which case, the expression is the default value for // the column; or if the expressions directly map to the source // table level1InsertExprs = ((ProjectRel) insertRel.getInput(0)).getProjects(); if (insertRel.getInput(0).getInput(0) instanceof ProjectRel) { level2InsertExprs = ((ProjectRel) insertRel.getInput(0).getInput(0)) .getProjects(); } nLevel1Exprs = level1InsertExprs.size(); } JoinRel joinRel = (JoinRel) mergeSourceRel.getInput(0); int nSourceFields = joinRel.getLeft().getRowType().getFieldCount(); List<RexNode> projects = new ArrayList<RexNode>(); for (int level1Idx = 0; level1Idx < nLevel1Exprs; level1Idx++) { 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)); } } if (updateCall != null) { final ProjectRel project = (ProjectRel) mergeSourceRel; projects.addAll( Util.skip(project.getProjects(), nSourceFields)); } RelNode massagedRel = RelOptUtil.createProject(joinRel, projects, null, true); return new TableModificationRel( cluster, targetTable, catalogReader, massagedRel, TableModificationRel.Operation.MERGE, targetColumnNameList, 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) { // first check for reserved identifiers like CURRENT_USER final SqlCall call = SqlUtil.makeCall(opTab, identifier); if (call != null) { return bb.convertExpression(call); } if (bb.agg != null) { throw Util.newInternal("Identifier '" + identifier + "' is not a group expr"); } SqlValidatorNamespace namespace = null; if (bb.scope != null) { identifier = bb.scope.fullyQualify(identifier); namespace = bb.scope.resolve(identifier.names.get(0), null, null); } RexNode e = bb.lookupExp(identifier.names.get(0)); final String correlationName; if (e instanceof RexCorrelVariable) { correlationName = ((RexCorrelVariable) e).getName(); } else { correlationName = null; } for (String name : Util.skip(identifier.names)) { if (namespace != null) { name = namespace.translate(name); namespace = null; } final boolean caseSensitive = true; // name already fully-qualified e = rexBuilder.makeFieldAccess(e, name, caseSensitive); } if (e instanceof RexInputRef) { // adjust the type to account for nulls introduced by outer joins e = adjustInputRef(bb, (RexInputRef) e); } if (null != correlationName) { // REVIEW: make mapCorrelateVariableToRexNode map to RexFieldAccess assert e instanceof RexFieldAccess; final RexNode prev = bb.mapCorrelateVariableToRexNode.put(correlationName, e); assert prev == null; } return e; } /** * Adjusts the type of a reference to an input field to account for nulls * introduced by outer joins; and adjusts the offset to match the physical * implementation. * * @param bb Blackboard * @param inputRef Input ref * @return Adjusted input ref */ protected RexNode adjustInputRef( Blackboard bb, RexInputRef inputRef) { RelDataTypeField field = bb.getRootField(inputRef); if (field != null) { return rexBuilder.makeInputRef( field.getType(), inputRef.getIndex()); } return inputRef; } /** * Converts a row constructor into a relational expression. * * @param bb Blackboard * @param rowConstructor Row constructor expression * @return Relational expression which returns a single row. * @pre isRowConstructor(rowConstructor) */ private RelNode convertRowConstructor( Blackboard bb, SqlCall rowConstructor) { assert isRowConstructor(rowConstructor) : rowConstructor; final List<SqlNode> operands = rowConstructor.getOperandList(); return convertMultisets(operands, bb); } private RelNode convertCursor(Blackboard bb, SubQuery subQuery) { final SqlCall cursorCall = (SqlCall) subQuery.node; assert cursorCall.operandCount() == 1; SqlNode query = cursorCall.operand(0); RelNode converted = convertQuery(query, false, false); int iCursor = bb.cursors.size(); bb.cursors.add(converted); subQuery.expr = new RexInputRef( iCursor, converted.getRowType()); return converted; } private RelNode convertMultisets(final List<SqlNode> 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. List<Object> joinList = new ArrayList<Object>(); List<SqlNode> lastList = new ArrayList<SqlNode>(); for (int i = 0; i < operands.size(); i++) { SqlNode operand = operands.get(i); if (!(operand instanceof SqlCall)) { lastList.add(operand); continue; } final SqlCall call = (SqlCall) operand; final SqlOperator op = call.getOperator(); if ((op != SqlStdOperatorTable.MULTISET_VALUE) && (op != SqlStdOperatorTable.MULTISET_QUERY)) { lastList.add(operand); continue; } final RelNode input; if (op == SqlStdOperatorTable.MULTISET_VALUE) { final SqlNodeList list = new SqlNodeList(call.getOperandList(), call.getParserPosition()); // assert bb.scope instanceof SelectScope : bb.scope; CollectNamespace nss = (CollectNamespace) validator.getNamespace(call); Blackboard usedBb; if (null != nss) { usedBb = createBlackboard(nss.getScope(), null); } else { usedBb = createBlackboard( new ListScope(bb.scope) { public SqlNode getNode() { return call; } }, null); } RelDataType multisetType = validator.getValidatedNodeType(call); validator.setValidatedNodeType( list, multisetType.getComponentType()); input = convertQueryOrInList(usedBb, list); } else { input = convertQuery(call.operand(0), false, true); } if (lastList.size() > 0) { joinList.add(lastList); } lastList = new ArrayList<SqlNode>(); CollectRel collectRel = new CollectRel( cluster, cluster.traitSetOf(Convention.NONE), input, validator.deriveAlias(call, i)); joinList.add(collectRel); } if (joinList.size() == 0) { joinList.add(lastList); } for (int i = 0; i < joinList.size(); i++) { Object o = joinList.get(i); if (o instanceof List) { List<SqlNode> projectList = (List<SqlNode>) o; final List<RexNode> selectList = new ArrayList<RexNode>(); final List<String> fieldNameList = new ArrayList<String>(); for (int j = 0; j < projectList.size(); j++) { SqlNode operand = projectList.get(j); selectList.add(bb.convertExpression(operand)); // REVIEW angel 5-June-2005: Use deriveAliasFromOrdinal // instead of deriveAlias to match field names from // SqlRowOperator. Otherwise, get error Type // 'RecordType(INTEGER EMPNO)' has no field 'EXPR$0' when // doing select * from unnest( select multiset[empno] // from sales.emps); fieldNameList.add(SqlUtil.deriveAliasFromOrdinal(j)); } RelNode projRel = RelOptUtil.createProject( new OneRowRel(cluster), selectList, fieldNameList); joinList.set(i, projRel); } } RelNode ret = (RelNode) joinList.get(0); for (int i = 1; i < joinList.size(); i++) { RelNode relNode = (RelNode) joinList.get(i); ret = createJoin( ret, relNode, rexBuilder.makeLiteral(true), JoinRelType.INNER, ImmutableSet.<String>of()); } return ret; } /** * Factory method that creates a join. * A subclass can override to use a different kind of join. * * @param left Left input * @param right Right input * @param condition Join condition * @param joinType Join type * @param variablesStopped Set of names of variables which are set by the * LHS and used by the RHS and are not available to * nodes above this JoinRel in the tree * @return A relational expression representing a join */ protected RelNode createJoin( RelNode left, RelNode right, RexNode condition, JoinRelType joinType, Set<String> variablesStopped) { return new JoinRel( cluster, left, right, condition, joinType, variablesStopped); } private void convertSelectList( Blackboard bb, SqlSelect select, List<SqlNode> orderList) { SqlNodeList selectList = select.getSelectList(); selectList = validator.expandStar(selectList, select, false); replaceSubqueries(bb, selectList, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); List<String> fieldNames = new ArrayList<String>(); List<RexNode> exprs = new ArrayList<RexNode>(); Collection<String> aliases = new TreeSet<String>(); // Project any system fields. (Must be done before regular select items, // because offsets may be affected.) final List<SqlMonotonicity> columnMonotonicityList = new ArrayList<SqlMonotonicity>(); extraSelectItems( bb, select, exprs, fieldNames, aliases, columnMonotonicityList); // Project select clause. int i = -1; for (SqlNode expr : selectList) { ++i; exprs.add(bb.convertExpression(expr)); fieldNames.add(deriveAlias(expr, aliases, i)); } // Project extra fields for sorting. for (SqlNode expr : orderList) { ++i; SqlNode expr2 = validator.expandOrderExpr(select, expr); exprs.add(bb.convertExpression(expr2)); fieldNames.add(deriveAlias(expr, aliases, i)); } fieldNames = SqlValidatorUtil.uniquify(fieldNames); RelNode inputRel = bb.root; 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)); } } /** * Adds extra select items. The default implementation adds nothing; derived * classes may add columns to exprList, nameList, aliasList and * columnMonotonicityList. * * @param bb Blackboard * @param select Select statement being translated * @param exprList List of expressions in select clause * @param nameList List of names, one per column * @param aliasList Collection of aliases that have been used * already * @param columnMonotonicityList List of monotonicity, one per column */ protected void extraSelectItems( Blackboard bb, SqlSelect select, List<RexNode> exprList, List<String> nameList, Collection<String> aliasList, List<SqlMonotonicity> columnMonotonicityList) { } private String deriveAlias( final SqlNode node, Collection<String> aliases, final int ordinal) { String alias = validator.deriveAlias(node, ordinal); if ((alias == null) || aliases.contains(alias)) { String aliasBase = (alias == null) ? "EXPR$" : alias; for (int j = 0;; j++) { alias = aliasBase + j; if (!aliases.contains(alias)) { break; } } } aliases.add(alias); return alias; } /** * Converts a WITH sub-query into a relational expression. */ public RelNode convertWith(SqlWith with) { return convertQuery(with.body, false, false); } /** * Converts a SELECT statement's parse tree into a relational expression. */ public RelNode convertValues( SqlCall values, RelDataType targetRowType) { final SqlValidatorScope scope = validator.getOverScope(values); assert scope != null; final Blackboard bb = createBlackboard(scope, null); convertValuesImpl(bb, values, targetRowType); return bb.root; } /** * Converts a values clause (as in "INSERT INTO T(x,y) VALUES (1,2)") into a * relational expression. * * @param bb Blackboard * @param values Call to SQL VALUES operator * @param targetRowType Target row type */ private void convertValuesImpl( Blackboard bb, SqlCall values, RelDataType targetRowType) { // Attempt direct conversion to ValuesRel; if that fails, deal with // fancy stuff like subqueries below. RelNode valuesRel = convertRowValues( bb, values, values.getOperandList(), true, targetRowType); if (valuesRel != null) { bb.setRoot(valuesRel, true); return; } List<RelNode> unionRels = new ArrayList<RelNode>(); for (SqlNode rowConstructor1 : values.getOperandList()) { SqlCall rowConstructor = (SqlCall) rowConstructor1; Blackboard tmpBb = createBlackboard(bb.scope, null); replaceSubqueries(tmpBb, rowConstructor, RelOptUtil.Logic.TRUE_FALSE_UNKNOWN); List<Pair<RexNode, String>> exps = new ArrayList<Pair<RexNode, String>>(); for (Ord<SqlNode> operand : Ord.zip(rowConstructor.getOperandList())) { exps.add( Pair.of( tmpBb.convertExpression(operand.e), validator.deriveAlias(operand.e, operand.i))); } RelNode in = (null == tmpBb.root) ? new OneRowRel(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); } else { bb.setRoot( new UnionRel( cluster, unionRels, true), true); } // REVIEW jvs 22-Jan-2004: should I add // mapScopeToLux.put(validator.getScope(values),bb.root); // ? } private String createCorrel() { int n = nextCorrel++; return CORREL_PREFIX + n; } private int getCorrelOrdinal(String correlName) { assert correlName.startsWith(CORREL_PREFIX); return Integer.parseInt(correlName.substring(CORREL_PREFIX.length())); } //~ Inner Classes ---------------------------------------------------------- /** * Workspace for translating an individual SELECT statement (or sub-SELECT). */ protected class Blackboard implements SqlRexContext, SqlVisitor<RexNode> { /** * Collection of {@link RelNode} objects which correspond to a SELECT * statement. */ public final SqlValidatorScope scope; private final Map<String, RexNode> nameToNodeMap; public RelNode root; private List<RelNode> inputs; private final Map<String, RexNode> mapCorrelateVariableToRexNode = new HashMap<String, RexNode>(); List<RelNode> cursors; /** * List of <code>IN</code> and <code>EXISTS</code> nodes inside this * <code>SELECT</code> statement (but not inside sub-queries). */ private final Set<SubQuery> subqueryList = Sets.newLinkedHashSet(); private final Map<SqlNode, SubQuery> subqueryMap = Util.asIndexMap(subqueryList, FN); private boolean subqueryNeedsOuterJoin; /** * 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; /** * 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<RelNode, Map<Integer, Integer>> mapRootRelToFieldProjection = new HashMap<RelNode, Map<Integer, Integer>>(); private final List<SqlMonotonicity> columnMonotonicities = new ArrayList<SqlMonotonicity>(); private final List<RelDataTypeField> systemFieldList = new ArrayList<RelDataTypeField>(); /** * 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 */ protected Blackboard( SqlValidatorScope scope, Map<String, RexNode> nameToNodeMap) { this.scope = scope; this.nameToNodeMap = nameToNodeMap; this.cursors = new ArrayList<RelNode>(); 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<RexNode> 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<RexNode> newLeftInputExpr = Lists.newArrayList(); for (int i = 0; i < origLeftInputCount; i++) { newLeftInputExpr.add(rexBuilder.makeInputRef(root, i)); } final List<Integer> leftJoinKeys = Lists.newArrayList(); for (RexNode leftKey : leftKeys) { newLeftInputExpr.add(leftKey); leftJoinKeys.add(origLeftInputCount + leftJoinKeys.size()); } ProjectRel newLeftInput = (ProjectRel) RelOptUtil.createProject( root, newLeftInputExpr, null, true); // maintain the group by mapping in the new ProjectRel 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<Integer> rightKeys = Util.range(rightOffset, rightOffset + leftJoinKeys.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<Map.Entry<String, RelDataType>>() { public Map.Entry<String, RelDataType> 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()); } } /** * Sets a new root relational expression, as the translation process * backs its way further up the tree. * * @param root New root relational expression * @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 * 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 JoinRel); if (leaf) { leaves.add(root); } this.columnMonotonicities.clear(); } private void setRoot( List<RelNode> inputs, RelNode root, boolean hasSystemFields) { this.inputs = inputs; this.root = root; this.systemFieldList.clear(); if (hasSystemFields) { this.systemFieldList.addAll(getSystemFields()); } } /** * Notifies this Blackboard that the root just set using {@link * #setRoot(RelNode, boolean)} was derived using dataset substitution. * * <p>The default implementation is not interested in such * notifications, and does nothing. * * @param datasetName Dataset name */ public void setDataset(String datasetName) { } void setRoot(List<RelNode> inputs) { setRoot(inputs, null, false); } /** * Returns an expression with which to reference a from-list item. * * @param name the alias of the from item * @return a {@link RexFieldAccess} or {@link RexRangeRef}, or null if * not found */ RexNode lookupExp(String name) { if (nameToNodeMap != null) { RexNode node = nameToNodeMap.get(name); if (node == null) { throw Util.newInternal( "Unknown identifier '" + name + "' encountered while expanding expression" + node); } return node; } int[] offsets = {-1}; final SqlValidatorScope[] ancestorScopes = {null}; SqlValidatorNamespace foundNs = scope.resolve(name, ancestorScopes, offsets); if (foundNs == null) { return null; } // Found in current query's from list. Find which from item. // We assume that the order of the from clause items has been // preserved. SqlValidatorScope ancestorScope = ancestorScopes[0]; boolean isParent = ancestorScope != scope; if ((inputs != null) && !isParent) { int offset = offsets[0]; final LookupContext rels = new LookupContext(this, inputs, systemFieldList.size()); return lookup(offset, rels); } else { // We're referencing a relational expression which has not been // 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. assert isParent; DeferredLookup lookup = new DeferredLookup(this, name); String correlName = createCorrel(); mapCorrelToDeferred.put(correlName, lookup); final RelDataType rowType = foundNs.getRowType(); return rexBuilder.makeCorrel(rowType, correlName); } } /** * Creates an expression with which to reference the expression whose * offset in its from-list is {@code offset}. */ RexNode lookup( int offset, LookupContext lookupContext) { Pair<RelNode, Integer> pair = lookupContext.findRel(offset); return rexBuilder.makeRangeReference( pair.left.getRowType(), pair.right, false); } RelDataTypeField getRootField(RexInputRef inputRef) { int fieldOffset = inputRef.getIndex(); for (RelNode input : inputs) { RelDataType rowType = input.getRowType(); if (rowType == null) { // TODO: remove this once leastRestrictive // is correctly implemented return null; } if (fieldOffset < rowType.getFieldCount()) { return rowType.getFieldList().get(fieldOffset); } fieldOffset -= rowType.getFieldCount(); } throw new AssertionError(); } public void flatten( List<RelNode> rels, int systemFieldCount, int[] start, List<Pair<RelNode, Integer>> relOffsetList) { for (RelNode rel : rels) { if (leaves.contains(rel)) { relOffsetList.add( Pair.of(rel, start[0])); start[0] += rel.getRowType().getFieldCount(); } else { if (rel instanceof JoinRel || rel instanceof AggregateRel) { start[0] += systemFieldCount; } flatten( rel.getInputs(), systemFieldCount, start, relOffsetList); } } } void registerSubquery(SqlNode node, RelOptUtil.Logic logic) { subqueryList.add(new SubQuery(node, logic)); } ImmutableList<RelNode> retrieveCursors() { try { return ImmutableList.copyOf(cursors); } finally { cursors.clear(); } } // implement SqlRexContext public RexNode convertExpression(SqlNode expr) { // If we're in aggregation mode and this is an expression in the // GROUP BY clause, return a reference to the field. if (agg != null) { final SqlNode expandedGroupExpr = validator.expand(expr, scope); RexNode rex = agg.lookupGroupExpr(expandedGroupExpr); if (rex != null) { return rex; } if (expr instanceof SqlCall) { rex = agg.lookupAggregates((SqlCall) expr); if (rex != null) { return rex; } } } // Allow the derived class chance to override the standard // behavior for special kinds of expressions. RexNode rex = convertExtendedExpression(expr, this); if (rex != null) { return rex; } boolean needTruthTest; // Sub-queries and OVER expressions are not like ordinary // expressions. final SqlKind kind = expr.getKind(); final SubQuery subQuery; switch (kind) { case CURSOR: case SELECT: case EXISTS: case SCALAR_QUERY: subQuery = subqueryMap.get(expr); assert subQuery != null; rex = subQuery.expr; assert rex != null : "rex != null"; if (kind == SqlKind.CURSOR) { // cursor reference is pre-baked return rex; } if (((kind == SqlKind.SCALAR_QUERY) || (kind == SqlKind.EXISTS)) && isConvertedSubq(rex)) { // scalar subquery or EXISTS has been converted to a // constant return rex; } RexNode fieldAccess; needTruthTest = false; // The indicator column is the last field of the subquery. 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()) { if (kind == SqlKind.EXISTS) { needTruthTest = true; } } if (needTruthTest) { fieldAccess = rexBuilder.makeCall( SqlStdOperatorTable.IS_NOT_NULL, fieldAccess); } return fieldAccess; case IN: subQuery = subqueryMap.get(expr); assert subQuery != null; assert subQuery.expr != null : "expr != null"; return subQuery.expr; case OVER: return convertOver(this, expr); default: // fall through } // Apply standard conversions. rex = expr.accept(this); Util.permAssert(rex != null, "conversion result not null"); return rex; } /** * Converts an item in an ORDER BY clause, extracting DESC, NULLS LAST * and NULLS FIRST flags first. */ public RexNode convertSortExpression(SqlNode expr, Set<SqlKind> 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); } } /** * Determines whether a RexNode corresponds to a subquery that's been * converted to a constant. * * @param rex the expression to be examined * @return true if the expression is a dynamic parameter, a literal, or * a literal that is being cast */ private boolean isConvertedSubq(RexNode rex) { if ((rex instanceof RexLiteral) || (rex instanceof RexDynamicParam)) { return true; } if (rex instanceof RexCall) { RexCall call = (RexCall) rex; if (call.getOperator() == SqlStdOperatorTable.CAST) { RexNode operand = call.getOperands().get(0); if (operand instanceof RexLiteral) { return true; } } } return false; } // implement SqlRexContext public int getGroupCount() { if (agg != null) { return agg.groupExprs.size(); } if (window != null) { return window.isAlwaysNonEmpty() ? 1 : 0; } return -1; } // implement SqlRexContext public RexBuilder getRexBuilder() { return rexBuilder; } // implement SqlRexContext public RexRangeRef getSubqueryExpr(SqlCall call) { final SubQuery subQuery = subqueryMap.get(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); } public RexNode convertInterval(SqlIntervalQualifier intervalQualifier) { 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 (op.isAggregator()) { return agg.lookupAggregates(call); } } return exprConverter.convertCall(this, call); } // 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); } public List<SqlMonotonicity> getColumnMonotonicities() { return columnMonotonicities; } } private static class DeferredLookup { Blackboard bb; String originalRelName; DeferredLookup( Blackboard bb, String originalRelName) { this.bb = bb; this.originalRelName = originalRelName; } public RexFieldAccess getFieldAccess(String name) { return (RexFieldAccess) bb.mapCorrelateVariableToRexNode.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) { return false; } public RexNode newColumnDefaultValue( RelOptTable table, int iColumn) { return rexBuilder.constantNull(); } public RexNode newAttributeInitializer( RelDataType type, SqlFunction constructor, int iAttribute, List<RexNode> constructorArgs) { return rexBuilder.constantNull(); } } /** * A default implementation of SubqueryConverter that does no conversion. */ private class NoOpSubqueryConverter implements SubqueryConverter { // implement SubqueryConverter public boolean canConvertSubquery() { return false; } // implement SubqueryConverter public RexNode convertSubquery( SqlCall subquery, SqlToRelConverter parentConverter, boolean isExists, boolean isExplain) { throw new IllegalArgumentException(); } } /** * Converts expressions to aggregates. * * <p>Consider the expression SELECT deptno, SUM(2 * sal) FROM emp GROUP BY * deptno Then * * <ul> * <li>groupExprs = {SqlIdentifier(deptno)}</li> * <li>convertedInputExprs = {RexInputRef(deptno), 2 * * RefInputRef(sal)}</li> * <li>inputRefs = {RefInputRef(#0), RexInputRef(#1)}</li> * <li>aggCalls = {AggCall(SUM, {1})}</li> * </ul> */ protected class AggConverter implements SqlVisitor<Void> { private final Blackboard bb; private final Map<String, String> nameMap = new HashMap<String, String>(); /** * The group-by expressions, in {@link SqlNode} format. */ private final SqlNodeList groupExprs = new SqlNodeList(SqlParserPos.ZERO); /** * Input expressions for the group columns and aggregates, in {@link * RexNode} format. The first elements of the list correspond to the * elements in {@link #groupExprs}; the remaining elements are for * aggregates. */ private final List<RexNode> convertedInputExprs = new ArrayList<RexNode>(); /** * Names of {@link #convertedInputExprs}, where the expressions are * simple mappings to input fields. */ private final List<String> convertedInputExprNames = new ArrayList<String>(); private final List<RexInputRef> inputRefs = new ArrayList<RexInputRef>(); private final List<AggregateCall> aggCalls = new ArrayList<AggregateCall>(); private final Map<SqlNode, RexNode> aggMapping = new HashMap<SqlNode, RexNode>(); private final Map<AggregateCall, RexNode> aggCallMapping = new HashMap<AggregateCall, RexNode>(); /** * Creates an AggConverter. * * <p>The <code>select</code> parameter provides enough context to name * aggregate calls which are top-level select list items. * * @param bb Blackboard * @param select Query being translated; provides context to give */ public AggConverter(Blackboard bb, SqlSelect select) { this.bb = bb; // Collect all expressions used in the select list so that aggregate // calls can be named correctly. final SqlNodeList selectList = select.getSelectList(); for (int i = 0; i < selectList.size(); i++) { SqlNode selectItem = selectList.get(i); String name = null; if (SqlUtil.isCallTo( selectItem, SqlStdOperatorTable.AS)) { final SqlCall call = (SqlCall) selectItem; selectItem = call.operand(0); name = call.operand(1).toString(); } if (name == null) { name = validator.deriveAlias(selectItem, i); } nameMap.put(selectItem.toString(), name); } } public void addGroupExpr(SqlNode expr) { RexNode convExpr = bb.convertExpression(expr); final RexNode rex = lookupGroupExpr(expr); if (rex != null) { return; // don't add duplicates, in e.g. "GROUP BY x, y, x" } groupExprs.add(expr); String name = nameMap.get(expr.toString()); addExpr(convExpr, name); final RelDataType type = convExpr.getType(); inputRefs.add(rexBuilder.makeInputRef(type, inputRefs.size())); } /** * Adds an expression, deducing an appropriate name if possible. * * @param expr Expression * @param name Suggested name */ private void addExpr(RexNode expr, String name) { convertedInputExprs.add(expr); if ((name == null) && (expr instanceof RexInputRef)) { final int i = ((RexInputRef) expr).getIndex(); name = bb.root.getRowType().getFieldList().get(i).getName(); } if (convertedInputExprNames.contains(name)) { // In case like 'SELECT ... GROUP BY x, y, x', don't add // name 'x' twice. name = null; } convertedInputExprNames.add(name); } // implement SqlVisitor public Void visit(SqlIdentifier id) { return null; } // implement SqlVisitor public Void visit(SqlNodeList nodeList) { for (int i = 0; i < nodeList.size(); i++) { nodeList.get(i).accept(this); } return null; } // implement SqlVisitor public Void visit(SqlLiteral lit) { return null; } // implement SqlVisitor public Void visit(SqlDataTypeSpec type) { return null; } // implement SqlVisitor public Void visit(SqlDynamicParam param) { return null; } // implement SqlVisitor public Void visit(SqlIntervalQualifier intervalQualifier) { return null; } public Void visit(SqlCall call) { if (call.getOperator().isAggregator()) { assert bb.agg == this; List<Integer> args = new ArrayList<Integer>(); List<RelDataType> argTypes = call.getOperator() instanceof SqlCountAggFunction ? new ArrayList<RelDataType>(call.getOperandList().size()) : null; try { // switch out of agg mode bb.agg = null; for (SqlNode operand : call.getOperandList()) { RexNode convertedExpr; // special case for COUNT(*): delete the * if (operand instanceof SqlIdentifier) { SqlIdentifier id = (SqlIdentifier) operand; if (id.isStar()) { assert call.operandCount() == 1; assert args.isEmpty(); break; } } convertedExpr = bb.convertExpression(operand); assert convertedExpr != null; if (argTypes != null) { argTypes.add(convertedExpr.getType()); } args.add(lookupOrCreateGroupExpr(convertedExpr)); } } finally { // switch back into agg mode bb.agg = this; } final Aggregation aggregation = (Aggregation) call.getOperator(); RelDataType type = validator.deriveType(bb.scope, call); boolean distinct = false; SqlLiteral quantifier = call.getFunctionQuantifier(); if ((null != quantifier) && (quantifier.getValue() == SqlSelectKeyword.DISTINCT)) { distinct = true; } final AggregateCall aggCall = new AggregateCall( aggregation, distinct, args, type, nameMap.get(call.toString())); RexNode rex = rexBuilder.addAggCall( aggCall, groupExprs.size(), aggCalls, aggCallMapping, argTypes); aggMapping.put(call, rex); } else if (call instanceof SqlSelect) { // rchen 2006-10-17: // for now do not detect aggregates in subqueries. return null; } else { for (SqlNode operand : call.getOperandList()) { // Operands are occasionally null, e.g. switched CASE arg 0. if (operand != null) { operand.accept(this); } } } return null; } private int lookupOrCreateGroupExpr(RexNode expr) { for (int i = 0; i < convertedInputExprs.size(); i++) { RexNode convertedInputExpr = convertedInputExprs.get(i); if (expr.toString().equals(convertedInputExpr.toString())) { return i; } } // not found -- add it int index = convertedInputExprs.size(); addExpr(expr, null); return index; } /** * If an expression is structurally identical to one of the group-by * expressions, returns a reference to the expression, otherwise returns * null. */ public RexNode lookupGroupExpr(SqlNode expr) { for (int i = 0; i < groupExprs.size(); i++) { SqlNode groupExpr = groupExprs.get(i); if (expr.equalsDeep(groupExpr, false)) { return inputRefs.get(i); } } return null; } public RexNode lookupAggregates(SqlCall call) { // assert call.getOperator().isAggregator(); assert bb.agg == this; return aggMapping.get(call); } public List<RexNode> getPreExprs() { return convertedInputExprs; } public List<String> getPreNames() { return convertedInputExprNames; } public List<AggregateCall> getAggCalls() { return aggCalls; } public RelDataTypeFactory getTypeFactory() { return typeFactory; } } /** * Context to find a relational expression to a field offset. */ private static class LookupContext { private final List<Pair<RelNode, Integer>> relOffsetList = new ArrayList<Pair<RelNode, Integer>>(); /** * Creates a LookupContext with multiple input relational expressions. * * @param bb Context for translating this subquery * @param rels Relational expressions * @param systemFieldCount Number of system fields */ LookupContext(Blackboard bb, List<RelNode> 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. * * <p>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<RelNode, Integer> findRel(int offset) { return relOffsetList.get(offset); } } /** * Shuttle which walks over a tree of {@link RexNode}s and applies 'over' to * all agg functions. * * <p>This is necessary because the returned expression is not necessarily a * call to an agg function. For example, * * <blockquote><code>AVG(x)</code></blockquote> * * becomes * * <blockquote><code>SUM(x) / COUNT(x)</code></blockquote> * * <p>Any aggregate functions are converted to calls to the internal <code> * $Histogram</code> aggregation function and accessors such as <code> * $HistogramMin</code>; for example, * * <blockquote><code>MIN(x), MAX(x)</code></blockquote> * * are converted to * * <blockquote><code>$HistogramMin($Histogram(x)), * $HistogramMax($Histogram(x))</code></blockquote> * * Common sub-expression elmination will ensure that only one histogram is * computed. */ private class HistogramShuttle extends RexShuttle { /** * Whether to convert calls to MIN(x) to HISTOGRAM_MIN(HISTOGRAM(x)). * Histograms allow rolling computation, but require more space. */ static final boolean ENABLE_HISTOGRAM_AGG = false; private final List<RexNode> partitionKeys; private final ImmutableList<RexFieldCollation> orderKeys; private final RexWindowBound lowerBound; private final RexWindowBound upperBound; private final SqlWindow window; HistogramShuttle( List<RexNode> partitionKeys, ImmutableList<RexFieldCollation> orderKeys, RexWindowBound lowerBound, RexWindowBound upperBound, SqlWindow window) { this.partitionKeys = partitionKeys; this.orderKeys = orderKeys; this.lowerBound = lowerBound; this.upperBound = upperBound; this.window = window; } public RexNode visitCall(RexCall call) { final SqlOperator op = call.getOperator(); if (!(op instanceof SqlAggFunction)) { return super.visitCall(call); } final SqlAggFunction aggOp = (SqlAggFunction) op; final RelDataType type = call.getType(); List<RexNode> exprs = call.getOperands(); SqlFunction histogramOp = !ENABLE_HISTOGRAM_AGG ? null : getHistogramOp(aggOp); if (histogramOp != null) { final RelDataType histogramType = computeHistogramType(type); // 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; // Replace original expression with CAST of not one // of the supported types if (histogramType != type) { exprs = new ArrayList<RexNode>(exprs); 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); 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)); } else { 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); } } /** * Returns the histogram operator corresponding to a given aggregate * function. * * <p>For example, <code>getHistogramOp({@link * SqlStdOperatorTable#MIN}}</code> returns {@link * SqlStdOperatorTable#HISTOGRAM_MIN}. * * @param aggFunction An aggregate function * @return Its histogram function, or null */ SqlFunction getHistogramOp(SqlAggFunction aggFunction) { if (aggFunction == SqlStdOperatorTable.MIN) { return SqlStdOperatorTable.HISTOGRAM_MIN; } else if (aggFunction == SqlStdOperatorTable.MAX) { return SqlStdOperatorTable.HISTOGRAM_MAX; } else if (aggFunction == SqlStdOperatorTable.FIRST_VALUE) { return SqlStdOperatorTable.HISTOGRAM_FIRST_VALUE; } else if (aggFunction == SqlStdOperatorTable.LAST_VALUE) { return SqlStdOperatorTable.HISTOGRAM_LAST_VALUE; } else { return null; } } /** * Returns the type for a histogram function. It is either the actual * type or an an approximation to it. */ private RelDataType computeHistogramType(RelDataType type) { if (SqlTypeUtil.isExactNumeric(type) && type.getSqlTypeName() != SqlTypeName.BIGINT) { return typeFactory.createSqlType(SqlTypeName.BIGINT); } 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; } } } // End SqlToRelConverter.java