Package org.eigenbase.sql2rel

Source Code of org.eigenbase.sql2rel.SqlToRelConverter

/*
* 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(&lt;datasetName&gt;, &lt;query&gt;))</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
TOP

Related Classes of org.eigenbase.sql2rel.SqlToRelConverter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.