All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.phoenix.compile.QueryCompiler Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.phoenix.compile;

import static org.apache.phoenix.query.QueryServices.WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB;
import static org.apache.phoenix.query.QueryServicesOptions.DEFAULT_WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB;

import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.regionserver.ScanInfoUtil;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
import org.apache.phoenix.compile.JoinCompiler.JoinSpec;
import org.apache.phoenix.compile.JoinCompiler.JoinTable;
import org.apache.phoenix.compile.JoinCompiler.Table;
import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.exception.SQLExceptionInfo;
import org.apache.phoenix.execute.AggregatePlan;
import org.apache.phoenix.execute.BaseQueryPlan;
import org.apache.phoenix.execute.ClientAggregatePlan;
import org.apache.phoenix.execute.ClientScanPlan;
import org.apache.phoenix.execute.HashJoinPlan;
import org.apache.phoenix.execute.HashJoinPlan.HashSubPlan;
import org.apache.phoenix.execute.HashJoinPlan.WhereClauseSubPlan;
import org.apache.phoenix.execute.LiteralResultIterationPlan;
import org.apache.phoenix.execute.ScanPlan;
import org.apache.phoenix.execute.SortMergeJoinPlan;
import org.apache.phoenix.execute.TupleProjectionPlan;
import org.apache.phoenix.execute.TupleProjector;
import org.apache.phoenix.execute.UnionPlan;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.LiteralExpression;
import org.apache.phoenix.expression.RowValueConstructorExpression;
import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
import org.apache.phoenix.iterate.ParallelIteratorFactory;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixStatement;
import org.apache.phoenix.join.HashJoinInfo;
import org.apache.phoenix.optimize.Cost;
import org.apache.phoenix.parse.AliasedNode;
import org.apache.phoenix.parse.EqualParseNode;
import org.apache.phoenix.parse.HintNode.Hint;
import org.apache.phoenix.parse.JoinTableNode.JoinType;
import org.apache.phoenix.parse.OrderByNode;
import org.apache.phoenix.parse.ParseNode;
import org.apache.phoenix.parse.ParseNodeFactory;
import org.apache.phoenix.parse.SQLParser;
import org.apache.phoenix.parse.SelectStatement;
import org.apache.phoenix.parse.SubqueryParseNode;
import org.apache.phoenix.parse.TableNode;
import org.apache.phoenix.query.ConnectionQueryServices;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.query.QueryServicesOptions;
import org.apache.phoenix.schema.AmbiguousColumnException;
import org.apache.phoenix.schema.ColumnNotFoundException;
import org.apache.phoenix.schema.PDatum;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.RowValueConstructorOffsetNotCoercibleException;
import org.apache.phoenix.schema.TableNotFoundException;
import org.apache.phoenix.schema.TableRef;
import org.apache.phoenix.util.EnvironmentEdgeManager;
import org.apache.phoenix.util.ParseNodeUtil;
import org.apache.phoenix.util.ParseNodeUtil.RewriteResult;
import org.apache.phoenix.util.QueryUtil;
import org.apache.phoenix.util.ScanUtil;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.base.Optional;


/**
 *
 * Class used to build an executable query plan
 *
 *
 * @since 0.1
 */
public class QueryCompiler {
    private static final ParseNodeFactory NODE_FACTORY = new ParseNodeFactory();
    private final PhoenixStatement statement;
    private final Scan scan;
    private final Scan originalScan;
    private final ColumnResolver resolver;
    private final SelectStatement select;
    private final List targetColumns;
    private final ParallelIteratorFactory parallelIteratorFactory;
    private final SequenceManager sequenceManager;
    private final boolean projectTuples;
    private final boolean noChildParentJoinOptimization;
    private final boolean usePersistentCache;
    private final boolean optimizeSubquery;
    private final Map dataPlans;
    private final boolean costBased;

    public QueryCompiler(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver, boolean projectTuples, boolean optimizeSubquery, Map dataPlans) throws SQLException {
        this(statement, select, resolver, Collections.emptyList(), null, new SequenceManager(statement), projectTuples, optimizeSubquery, dataPlans);
    }

    public QueryCompiler(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver, List targetColumns, ParallelIteratorFactory parallelIteratorFactory, SequenceManager sequenceManager, boolean projectTuples, boolean optimizeSubquery, Map dataPlans) throws SQLException {
        this.statement = statement;
        this.select = select;
        this.resolver = resolver;
        this.scan = new Scan();
        this.targetColumns = targetColumns;
        this.parallelIteratorFactory = parallelIteratorFactory;
        this.sequenceManager = sequenceManager;
        this.projectTuples = projectTuples;
        this.noChildParentJoinOptimization = select.getHint().hasHint(Hint.NO_CHILD_PARENT_JOIN_OPTIMIZATION) || select.getHint().hasHint(Hint.USE_PERSISTENT_CACHE);
        this.usePersistentCache = select.getHint().hasHint(Hint.USE_PERSISTENT_CACHE);
        ConnectionQueryServices services = statement.getConnection().getQueryServices();
        this.costBased = services.getProps().getBoolean(QueryServices.COST_BASED_OPTIMIZER_ENABLED, QueryServicesOptions.DEFAULT_COST_BASED_OPTIMIZER_ENABLED);
        scan.setLoadColumnFamiliesOnDemand(true);
        if (select.getHint().hasHint(Hint.NO_CACHE)) {
            scan.setCacheBlocks(false);
        }

        scan.setCaching(statement.getFetchSize());
        this.originalScan = ScanUtil.newScan(scan);
        this.optimizeSubquery = optimizeSubquery;
        this.dataPlans = dataPlans == null ? Collections.emptyMap() : dataPlans;
    }

    public QueryCompiler(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver, List targetColumns, ParallelIteratorFactory parallelIteratorFactory, SequenceManager sequenceManager) throws SQLException {
        this(statement, select, resolver, targetColumns, parallelIteratorFactory, sequenceManager, true, false, null);
    }

    /**
     * Builds an executable query plan from a parsed SQL statement
     * @return executable query plan
     * @throws SQLException if mismatched types are found, bind value do not match binds,
     * or invalid function arguments are encountered.
     * @throws SQLFeatureNotSupportedException if an unsupported construct is encountered
     * @throws TableNotFoundException if table name not found in schema
     * @throws ColumnNotFoundException if column name could not be resolved
     * @throws AmbiguousColumnException if an unaliased column name is ambiguous across multiple tables
     */
    public QueryPlan compile() throws SQLException{
        verifySCN();
        QueryPlan plan;
        if (select.isUnion()) {
            plan = compileUnionAll(select);
        } else {
            plan = compileSelect(select);
        }
        return plan;
    }

    private void verifySCN() throws SQLException {
        PhoenixConnection conn = statement.getConnection();
        if (conn.isRunningUpgrade()) {
            // PHOENIX-6179 : if upgrade is going on, we don't need to
            // perform MaxLookBackAge check
            return;
        }
        Long scn = conn.getSCN();
        if (scn == null) {
            return;
        }
        ColumnResolver resolver =
            FromCompiler.getResolverForQuery(select, conn);
        int maxLookBackAge = conn.getQueryServices().
            getConfiguration().getInt(ScanInfoUtil.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
            ScanInfoUtil.DEFAULT_PHOENIX_MAX_LOOKBACK_AGE);
        long now = EnvironmentEdgeManager.currentTimeMillis();
        if (maxLookBackAge > 0 && now - maxLookBackAge * 1000L > scn){
            throw new SQLExceptionInfo.Builder(
                SQLExceptionCode.CANNOT_QUERY_TABLE_WITH_SCN_OLDER_THAN_MAX_LOOKBACK_AGE)
                .build().buildException();
        }
    }

    public QueryPlan compileUnionAll(SelectStatement select) throws SQLException { 
        List unionAllSelects = select.getSelects();
        List plans = new ArrayList();

        for (int i=0; i < unionAllSelects.size(); i++ ) {
            SelectStatement subSelect = unionAllSelects.get(i);
            // Push down order-by and limit into sub-selects.
            if (!select.getOrderBy().isEmpty() || select.getLimit() != null) {
                if (select.getOffset() == null) {
                    subSelect = NODE_FACTORY.select(subSelect, select.getOrderBy(), select.getLimit(), null);
                } else {
                    subSelect = NODE_FACTORY.select(subSelect, select.getOrderBy(), null, null);
                }
            }
            QueryPlan subPlan = compileSubquery(subSelect, true);
            plans.add(subPlan);
        }
        TableRef tableRef = UnionCompiler.contructSchemaTable(statement, plans,
            select.hasWildcard() ? null : select.getSelect());
        ColumnResolver resolver = FromCompiler.getResolver(tableRef);
        StatementContext context = new StatementContext(statement, resolver, scan, sequenceManager);
        QueryPlan plan = compileSingleFlatQuery(
                context,
                select,
                statement.getParameters(),
                false,
                false,
                null,
                false,
                true);
        plan = new UnionPlan(context, select, tableRef, plan.getProjector(), plan.getLimit(),
            plan.getOffset(), plan.getOrderBy(), GroupBy.EMPTY_GROUP_BY, plans,
            context.getBindManager().getParameterMetaData());
        return plan;
    }

    public QueryPlan compileSelect(SelectStatement select) throws SQLException{
        List binds = statement.getParameters();
        StatementContext context = new StatementContext(statement, resolver, scan, sequenceManager);
        if (select.isJoin()) {
            JoinTable joinTable = JoinCompiler.compile(statement, select, context.getResolver());
            return compileJoinQuery(context, binds, joinTable, false, false, null);
        } else {
            return compileSingleQuery(context, select, binds, false, true);
        }
    }

    /**
     * Call compileJoinQuery() for join queries recursively down to the leaf JoinTable nodes.
     * If it is a leaf node, call compileSingleFlatQuery() or compileSubquery(), otherwise:
     *      1) If option COST_BASED_OPTIMIZER_ENABLED is on and stats are available, return the
     *         join plan with the best cost. Note that the "best" plan is only locally optimal,
     *         and might or might not be globally optimal.
     *      2) Otherwise, return the join plan compiled with the default strategy.
     * @see JoinCompiler.JoinTable#getApplicableJoinStrategies()
     */
    protected QueryPlan compileJoinQuery(StatementContext context, List binds, JoinTable joinTable, boolean asSubquery, boolean projectPKColumns, List orderBy) throws SQLException {
        if (joinTable.getJoinSpecs().isEmpty()) {
            Table table = joinTable.getLeftTable();
            SelectStatement subquery = table.getAsSubquery(orderBy);
            if (!table.isSubselect()) {
                context.setCurrentTable(table.getTableRef());
                PTable projectedTable = table.createProjectedTable(!projectPKColumns, context);
                TupleProjector projector = new TupleProjector(projectedTable);
                boolean wildcardIncludesDynamicCols = context.getConnection().getQueryServices()
                        .getConfiguration().getBoolean(WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB,
                                DEFAULT_WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB);
                TupleProjector.serializeProjectorIntoScan(context.getScan(), projector,
                        wildcardIncludesDynamicCols);
                context.setResolver(FromCompiler.getResolverForProjectedTable(projectedTable, context.getConnection(), subquery.getUdfParseNodes()));
                table.projectColumns(context.getScan());
                return compileSingleFlatQuery(
                        context,
                        subquery,
                        binds,
                        asSubquery,
                        !asSubquery,
                        null,
                        true,
                        false);
            }
            QueryPlan plan = compileSubquery(subquery, false);
            PTable projectedTable = table.createProjectedTable(plan.getProjector());
            context.setResolver(FromCompiler.getResolverForProjectedTable(projectedTable, context.getConnection(), subquery.getUdfParseNodes()));
            return new TupleProjectionPlan(
                    plan,
                    new TupleProjector(plan.getProjector()),
                    context,
                    null);
        }

        List strategies = joinTable.getApplicableJoinStrategies();
        assert strategies.size() > 0;
        if (!costBased || strategies.size() == 1) {
            return compileJoinQuery(
                    strategies.get(0), context, binds, joinTable, asSubquery, projectPKColumns, orderBy);
        }

        QueryPlan bestPlan = null;
        Cost bestCost = null;
        for (JoinCompiler.Strategy strategy : strategies) {
            StatementContext newContext = new StatementContext(
                    context.getStatement(), context.getResolver(), new Scan(), context.getSequenceManager());
            QueryPlan plan = compileJoinQuery(
                    strategy, newContext, binds, joinTable, asSubquery, projectPKColumns, orderBy);
            Cost cost = plan.getCost();
            if (bestPlan == null || cost.compareTo(bestCost) < 0) {
                bestPlan = plan;
                bestCost = cost;
            }
        }
        context.setResolver(bestPlan.getContext().getResolver());
        context.setCurrentTable(bestPlan.getContext().getCurrentTable());
        return bestPlan;
    }

    protected QueryPlan compileJoinQuery(JoinCompiler.Strategy strategy, StatementContext context, List binds, JoinTable joinTable, boolean asSubquery, boolean projectPKColumns, List orderBy) throws SQLException {
        byte[] emptyByteArray = new byte[0];
        List joinSpecs = joinTable.getJoinSpecs();
        boolean wildcardIncludesDynamicCols = context.getConnection().getQueryServices()
                .getConfiguration().getBoolean(WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB,
                        DEFAULT_WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB);
        switch (strategy) {
            case HASH_BUILD_RIGHT: {
                boolean[] starJoinVector = joinTable.getStarJoinVector();
                Table table = joinTable.getLeftTable();
                PTable initialProjectedTable;
                TableRef tableRef;
                SelectStatement query;
                TupleProjector tupleProjector;
                if (!table.isSubselect()) {
                    context.setCurrentTable(table.getTableRef());
                    initialProjectedTable = table.createProjectedTable(!projectPKColumns, context);
                    tableRef = table.getTableRef();
                    table.projectColumns(context.getScan());
                    query = joinTable.getAsSingleSubquery(table.getAsSubquery(orderBy), asSubquery);
                    tupleProjector = new TupleProjector(initialProjectedTable);
                } else {
                    SelectStatement subquery = table.getAsSubquery(orderBy);
                    QueryPlan plan = compileSubquery(subquery, false);
                    initialProjectedTable = table.createProjectedTable(plan.getProjector());
                    tableRef = plan.getTableRef();
                    context.getScan().setFamilyMap(plan.getContext().getScan().getFamilyMap());
                    query = joinTable.getAsSingleSubquery((SelectStatement) plan.getStatement(), asSubquery);
                    tupleProjector = new TupleProjector(plan.getProjector());
                }
                context.setCurrentTable(tableRef);
                PTable projectedTable = initialProjectedTable;
                int count = joinSpecs.size();
                ImmutableBytesPtr[] joinIds = new ImmutableBytesPtr[count];
                List[] joinExpressions = new List[count];
                JoinType[] joinTypes = new JoinType[count];
                PTable[] tables = new PTable[count];
                int[] fieldPositions = new int[count];
                StatementContext[] subContexts = new StatementContext[count];
                QueryPlan[] subPlans = new QueryPlan[count];
                HashSubPlan[] hashPlans = new HashSubPlan[count];
                fieldPositions[0] = projectedTable.getColumns().size() - projectedTable.getPKColumns().size();
                for (int i = 0; i < count; i++) {
                    JoinSpec joinSpec = joinSpecs.get(i);
                    Scan subScan = ScanUtil.newScan(originalScan);
                    subContexts[i] = new StatementContext(statement, context.getResolver(), subScan, new SequenceManager(statement));
                    subPlans[i] = compileJoinQuery(
                            subContexts[i],
                            binds,
                            joinSpec.getRhsJoinTable(),
                            true,
                            true,
                            null);
                    boolean hasPostReference = joinSpec.getRhsJoinTable().hasPostReference();
                    if (hasPostReference) {
                        tables[i] = subContexts[i].getResolver().getTables().get(0).getTable();
                        projectedTable = JoinCompiler.joinProjectedTables(projectedTable, tables[i], joinSpec.getType());
                    } else {
                        tables[i] = null;
                    }
                }
                for (int i = 0; i < count; i++) {
                    JoinSpec joinSpec = joinSpecs.get(i);
                    context.setResolver(FromCompiler.getResolverForProjectedTable(projectedTable, context.getConnection(), query.getUdfParseNodes()));
                    joinIds[i] = new ImmutableBytesPtr(emptyByteArray); // place-holder
                    Pair, List> joinConditions = joinSpec.compileJoinConditions(context, subContexts[i], strategy);
                    joinExpressions[i] = joinConditions.getFirst();
                    List hashExpressions = joinConditions.getSecond();
                    Pair keyRangeExpressions = new Pair(null, null);
                    boolean optimized = getKeyExpressionCombinations(
                            keyRangeExpressions,
                            context,
                            joinTable.getOriginalJoinSelectStatement(),
                            tableRef,
                            joinSpec.getType(),
                            joinExpressions[i],
                            hashExpressions);
                    Expression keyRangeLhsExpression = keyRangeExpressions.getFirst();
                    Expression keyRangeRhsExpression = keyRangeExpressions.getSecond();
                    joinTypes[i] = joinSpec.getType();
                    if (i < count - 1) {
                        fieldPositions[i + 1] = fieldPositions[i] + (tables[i] == null ? 0 : (tables[i].getColumns().size() - tables[i].getPKColumns().size()));
                    }
                    hashPlans[i] = new HashSubPlan(i, subPlans[i], optimized ? null : hashExpressions, joinSpec.isSingleValueOnly(), usePersistentCache, keyRangeLhsExpression, keyRangeRhsExpression);
                }
                TupleProjector.serializeProjectorIntoScan(context.getScan(), tupleProjector,
                        wildcardIncludesDynamicCols);
                QueryPlan plan = compileSingleFlatQuery(
                        context,
                        query,
                        binds,
                        asSubquery,
                        !asSubquery && joinTable.isAllLeftJoin(),
                        null, true, false);
                Expression postJoinFilterExpression = joinTable.compilePostFilterExpression(context);
                Integer limit = null;
                Integer offset = null;
                if (!query.isAggregate() && !query.isDistinct() && query.getOrderBy().isEmpty()) {
                    limit = plan.getLimit();
                    offset = plan.getOffset();
                }
                HashJoinInfo joinInfo = new HashJoinInfo(projectedTable, joinIds, joinExpressions, joinTypes,
                        starJoinVector, tables, fieldPositions, postJoinFilterExpression, QueryUtil.getOffsetLimit(limit, offset));
                return HashJoinPlan.create(joinTable.getOriginalJoinSelectStatement(), plan, joinInfo, hashPlans);
            }
            case HASH_BUILD_LEFT: {
                JoinSpec lastJoinSpec = joinSpecs.get(joinSpecs.size() - 1);
                JoinType type = lastJoinSpec.getType();
                JoinTable rhsJoinTable = lastJoinSpec.getRhsJoinTable();
                Table rhsTable = rhsJoinTable.getLeftTable();
                JoinTable lhsJoin = joinTable.createSubJoinTable(statement.getConnection());
                Scan subScan = ScanUtil.newScan(originalScan);
                StatementContext lhsCtx = new StatementContext(statement, context.getResolver(), subScan, new SequenceManager(statement));
                QueryPlan lhsPlan = compileJoinQuery(lhsCtx, binds, lhsJoin, true, true, null);
                PTable rhsProjTable;
                TableRef rhsTableRef;
                SelectStatement rhs;
                TupleProjector tupleProjector;
                if (!rhsTable.isSubselect()) {
                    context.setCurrentTable(rhsTable.getTableRef());
                    rhsProjTable = rhsTable.createProjectedTable(!projectPKColumns, context);
                    rhsTableRef = rhsTable.getTableRef();
                    rhsTable.projectColumns(context.getScan());
                    rhs = rhsJoinTable.getAsSingleSubquery(rhsTable.getAsSubquery(orderBy), asSubquery);
                    tupleProjector = new TupleProjector(rhsProjTable);
                } else {
                    SelectStatement subquery = rhsTable.getAsSubquery(orderBy);
                    QueryPlan plan = compileSubquery(subquery, false);
                    rhsProjTable = rhsTable.createProjectedTable(plan.getProjector());
                    rhsTableRef = plan.getTableRef();
                    context.getScan().setFamilyMap(plan.getContext().getScan().getFamilyMap());
                    rhs = rhsJoinTable.getAsSingleSubquery((SelectStatement) plan.getStatement(), asSubquery);
                    tupleProjector = new TupleProjector(plan.getProjector());
                }
                context.setCurrentTable(rhsTableRef);
                context.setResolver(FromCompiler.getResolverForProjectedTable(rhsProjTable, context.getConnection(), rhs.getUdfParseNodes()));
                ImmutableBytesPtr[] joinIds = new ImmutableBytesPtr[]{new ImmutableBytesPtr(emptyByteArray)};
                Pair, List> joinConditions = lastJoinSpec.compileJoinConditions(lhsCtx, context, strategy);
                List joinExpressions = joinConditions.getSecond();
                List hashExpressions = joinConditions.getFirst();
                boolean needsMerge = lhsJoin.hasPostReference();
                PTable lhsTable = needsMerge ? lhsCtx.getResolver().getTables().get(0).getTable() : null;
                int fieldPosition = needsMerge ? rhsProjTable.getColumns().size() - rhsProjTable.getPKColumns().size() : 0;
                PTable projectedTable = needsMerge ? JoinCompiler.joinProjectedTables(rhsProjTable, lhsTable, type == JoinType.Right ? JoinType.Left : type) : rhsProjTable;
                TupleProjector.serializeProjectorIntoScan(context.getScan(), tupleProjector,
                        wildcardIncludesDynamicCols);
                context.setResolver(FromCompiler.getResolverForProjectedTable(projectedTable, context.getConnection(), rhs.getUdfParseNodes()));
                QueryPlan rhsPlan = compileSingleFlatQuery(
                        context,
                        rhs,
                        binds,
                        asSubquery,
                        !asSubquery && type == JoinType.Right,
                        null,
                        true,
                        false);
                Expression postJoinFilterExpression = joinTable.compilePostFilterExpression(context);
                Integer limit = null;
                Integer offset = null;
                if (!rhs.isAggregate() && !rhs.isDistinct() && rhs.getOrderBy().isEmpty()) {
                    limit = rhsPlan.getLimit();
                    offset = rhsPlan.getOffset();
                }
                HashJoinInfo joinInfo = new HashJoinInfo(projectedTable, joinIds, new List[]{joinExpressions},
                        new JoinType[]{type == JoinType.Right ? JoinType.Left : type}, new boolean[]{true},
                        new PTable[]{lhsTable}, new int[]{fieldPosition}, postJoinFilterExpression, QueryUtil.getOffsetLimit(limit, offset));
                boolean usePersistentCache = joinTable.getOriginalJoinSelectStatement().getHint().hasHint(Hint.USE_PERSISTENT_CACHE);
                Pair keyRangeExpressions = new Pair(null, null);
                getKeyExpressionCombinations(
                        keyRangeExpressions,
                        context,
                        joinTable.getOriginalJoinSelectStatement(),
                        rhsTableRef,
                        type,
                        joinExpressions,
                        hashExpressions);
                return HashJoinPlan.create(
                        joinTable.getOriginalJoinSelectStatement(),
                        rhsPlan,
                        joinInfo,
                        new HashSubPlan[]{
                                new HashSubPlan(
                                        0,
                                        lhsPlan,
                                        hashExpressions,
                                        false,
                                        usePersistentCache,
                                        keyRangeExpressions.getFirst(),
                                        keyRangeExpressions.getSecond())});
            }
            case SORT_MERGE: {
                JoinTable lhsJoin =  joinTable.createSubJoinTable(statement.getConnection());
                JoinSpec lastJoinSpec = joinSpecs.get(joinSpecs.size() - 1);
                JoinType type = lastJoinSpec.getType();
                JoinTable rhsJoin = lastJoinSpec.getRhsJoinTable();
                if (type == JoinType.Right) {
                    JoinTable temp = lhsJoin;
                    lhsJoin = rhsJoin;
                    rhsJoin = temp;
                }

                List joinConditionNodes = lastJoinSpec.getOnConditions();
                List lhsOrderBy = Lists.newArrayListWithExpectedSize(joinConditionNodes.size());
                List rhsOrderBy = Lists.newArrayListWithExpectedSize(joinConditionNodes.size());
                for (EqualParseNode condition : joinConditionNodes) {
                    lhsOrderBy.add(NODE_FACTORY.orderBy(type == JoinType.Right ? condition.getRHS() : condition.getLHS(), false, true));
                    rhsOrderBy.add(NODE_FACTORY.orderBy(type == JoinType.Right ? condition.getLHS() : condition.getRHS(), false, true));
                }

                Scan lhsScan = ScanUtil.newScan(originalScan);
                StatementContext lhsCtx = new StatementContext(statement, context.getResolver(), lhsScan, new SequenceManager(statement));
                boolean preserveRowkey = !projectPKColumns && type != JoinType.Full;
                QueryPlan lhsPlan = compileJoinQuery(lhsCtx, binds, lhsJoin, true, !preserveRowkey, lhsOrderBy);
                PTable lhsProjTable = lhsCtx.getResolver().getTables().get(0).getTable();

                Scan rhsScan = ScanUtil.newScan(originalScan);
                StatementContext rhsCtx = new StatementContext(statement, context.getResolver(), rhsScan, new SequenceManager(statement));
                QueryPlan rhsPlan = compileJoinQuery(rhsCtx, binds, rhsJoin, true, true, rhsOrderBy);
                PTable rhsProjTable = rhsCtx.getResolver().getTables().get(0).getTable();

                Pair, List> joinConditions = lastJoinSpec.compileJoinConditions(type == JoinType.Right ? rhsCtx : lhsCtx, type == JoinType.Right ? lhsCtx : rhsCtx, strategy);
                List lhsKeyExpressions = type == JoinType.Right ? joinConditions.getSecond() : joinConditions.getFirst();
                List rhsKeyExpressions = type == JoinType.Right ? joinConditions.getFirst() : joinConditions.getSecond();

                boolean needsMerge = rhsJoin.hasPostReference();
                int fieldPosition = needsMerge ? lhsProjTable.getColumns().size() - lhsProjTable.getPKColumns().size() : 0;
                PTable projectedTable = needsMerge ? JoinCompiler.joinProjectedTables(lhsProjTable, rhsProjTable, type == JoinType.Right ? JoinType.Left : type) : lhsProjTable;

                ColumnResolver resolver = FromCompiler.getResolverForProjectedTable(projectedTable, context.getConnection(), joinTable.getOriginalJoinSelectStatement().getUdfParseNodes());
                TableRef tableRef = resolver.getTables().get(0);
                StatementContext subCtx = new StatementContext(statement, resolver, ScanUtil.newScan(originalScan), new SequenceManager(statement));
                subCtx.setCurrentTable(tableRef);
                QueryPlan innerPlan = new SortMergeJoinPlan(
                        subCtx,
                        joinTable.getOriginalJoinSelectStatement(),
                        tableRef,
                        type == JoinType.Right ? JoinType.Left : type,
                        lhsPlan,
                        rhsPlan,
                        new Pair,List>(lhsKeyExpressions, rhsKeyExpressions),
                        rhsKeyExpressions,
                        projectedTable,
                        lhsProjTable,
                        needsMerge ? rhsProjTable : null,
                        fieldPosition,
                        lastJoinSpec.isSingleValueOnly(),
                        new Pair,List>(lhsOrderBy, rhsOrderBy));
                context.setCurrentTable(tableRef);
                context.setResolver(resolver);
                TableNode from = NODE_FACTORY.namedTable(tableRef.getTableAlias(), NODE_FACTORY.table(tableRef.getTable().getSchemaName().getString(), tableRef.getTable().getTableName().getString()));
                ParseNode where = joinTable.getPostFiltersCombined();
                SelectStatement select = asSubquery ?
                        NODE_FACTORY.select(
                                from,
                                joinTable.getOriginalJoinSelectStatement().getHint(),
                                false,
                                Collections.emptyList(),
                                where,
                                null,
                                null,
                                orderBy,
                                null,
                                null,
                                0,
                                false,
                                joinTable.getOriginalJoinSelectStatement().hasSequence(),
                                Collections.emptyList(),
                                joinTable.getOriginalJoinSelectStatement().getUdfParseNodes()) :
                         NODE_FACTORY.select(
                                 joinTable.getOriginalJoinSelectStatement(),
                                 from,
                                 where);

                return compileSingleFlatQuery(
                        context,
                        select,
                        binds,
                        asSubquery,
                        false,
                        innerPlan,
                        true,
                        false);
            }
            default:
                throw new IllegalArgumentException("Invalid join strategy '" + strategy + "'");
        }
    }

    private boolean getKeyExpressionCombinations(Pair combination, StatementContext context, SelectStatement select, TableRef table, JoinType type, final List joinExpressions, final List hashExpressions) throws SQLException {
        if ((type != JoinType.Inner && type != JoinType.Semi) || this.noChildParentJoinOptimization)
            return false;

        Scan scanCopy = ScanUtil.newScan(context.getScan());
        StatementContext contextCopy = new StatementContext(statement, context.getResolver(), scanCopy, new SequenceManager(statement));
        contextCopy.setCurrentTable(table);
        List lhsCombination = Lists. newArrayList();
        boolean complete = WhereOptimizer.getKeyExpressionCombination(lhsCombination, contextCopy, select, joinExpressions);
        if (lhsCombination.isEmpty())
            return false;

        List rhsCombination = Lists.newArrayListWithExpectedSize(lhsCombination.size());
        for (int i = 0; i < lhsCombination.size(); i++) {
            Expression lhs = lhsCombination.get(i);
            for (int j = 0; j < joinExpressions.size(); j++) {
                if (lhs == joinExpressions.get(j)) {
                    rhsCombination.add(hashExpressions.get(j));
                    break;
                }
            }
        }

        if (lhsCombination.size() == 1) {
            combination.setFirst(lhsCombination.get(0));
            combination.setSecond(rhsCombination.get(0));
        } else {
            combination.setFirst(new RowValueConstructorExpression(lhsCombination, false));
            combination.setSecond(new RowValueConstructorExpression(rhsCombination, false));
        }

        return type == JoinType.Semi && complete;
    }

    protected QueryPlan compileSubquery(
            SelectStatement subquerySelectStatement,
            boolean pushDownMaxRows) throws SQLException {
        PhoenixConnection phoenixConnection = this.statement.getConnection();
        RewriteResult rewriteResult =
                ParseNodeUtil.rewrite(subquerySelectStatement, phoenixConnection);
        int maxRows = this.statement.getMaxRows();
        this.statement.setMaxRows(pushDownMaxRows ? maxRows : 0); // overwrite maxRows to avoid its impact on inner queries.
        QueryPlan queryPlan = new QueryCompiler(
                this.statement,
                rewriteResult.getRewrittenSelectStatement(),
                rewriteResult.getColumnResolver(),
                false,
                optimizeSubquery,
                null).compile();
        if (optimizeSubquery) {
            queryPlan = statement.getConnection().getQueryServices().getOptimizer().optimize(
                         statement,
                         queryPlan);
        }
        this.statement.setMaxRows(maxRows); // restore maxRows.
        return queryPlan;
    }

    protected QueryPlan compileSingleQuery(StatementContext context, SelectStatement select, List binds, boolean asSubquery, boolean allowPageFilter) throws SQLException{
        SelectStatement innerSelect = select.getInnerSelectStatement();
        if (innerSelect == null) {
            return compileSingleFlatQuery(context, select, binds, asSubquery, allowPageFilter, null, false, false);
        }

        if((innerSelect.getOffset() != null && (!innerSelect.getOffset().isIntegerOffset()) ||
                select.getOffset() != null && !select.getOffset().isIntegerOffset())) {
            throw new SQLException("RVC Offset not allowed with subqueries.");
        }

        QueryPlan innerPlan = compileSubquery(innerSelect, false);
        RowProjector innerQueryPlanRowProjector = innerPlan.getProjector();
        TupleProjector tupleProjector = new TupleProjector(innerQueryPlanRowProjector);

        // Replace the original resolver and table with those having compiled type info.
        TableRef tableRef = context.getResolver().getTables().get(0);
        ColumnResolver resolver = FromCompiler.getResolverForCompiledDerivedTable(statement.getConnection(), tableRef, innerQueryPlanRowProjector);
        context.setResolver(resolver);
        tableRef = resolver.getTables().get(0);
        context.setCurrentTable(tableRef);
        innerPlan = new TupleProjectionPlan(innerPlan, tupleProjector, context, null);

        return compileSingleFlatQuery(context, select, binds, asSubquery, allowPageFilter, innerPlan, false, false);
    }

    protected QueryPlan compileSingleFlatQuery(
            StatementContext context,
            SelectStatement select,
            List binds,
            boolean asSubquery,
            boolean allowPageFilter,
            QueryPlan innerPlan,
            boolean inJoin,
            boolean inUnion) throws SQLException {
        boolean isApplicable = true;
        PTable projectedTable = null;
        if (this.projectTuples) {
            projectedTable = TupleProjectionCompiler.createProjectedTable(select, context);
            if (projectedTable != null) {
                context.setResolver(FromCompiler.getResolverForProjectedTable(projectedTable, context.getConnection(), select.getUdfParseNodes()));
            }
        }
        
        ColumnResolver resolver = context.getResolver();
        TableRef tableRef = context.getCurrentTable();
        PTable table = tableRef.getTable();

        ParseNode viewWhere = null;
        if (table.getViewStatement() != null) {
            viewWhere = new SQLParser(table.getViewStatement()).parseQuery().getWhere();
        }
        Integer limit = LimitCompiler.compile(context, select);

        CompiledOffset compiledOffset = null;
        Integer offset = null;
        try {
            compiledOffset = OffsetCompiler.getOffsetCompiler().compile(context, select, inJoin, inUnion);
            offset = compiledOffset.getIntegerOffset().orNull();
        } catch(RowValueConstructorOffsetNotCoercibleException e){
            //This current plan is not executable
            compiledOffset = new CompiledOffset(Optional.absent(),Optional.absent());
            isApplicable = false;
        }

        GroupBy groupBy = GroupByCompiler.compile(context, select);
        // Optimize the HAVING clause by finding any group by expressions that can be moved
        // to the WHERE clause
        select = HavingCompiler.rewrite(context, select, groupBy);
        Expression having = HavingCompiler.compile(context, select, groupBy);
        // Don't pass groupBy when building where clause expression, because we do not want to wrap these
        // expressions as group by key expressions since they're pre, not post filtered.
        if (innerPlan == null && !tableRef.equals(resolver.getTables().get(0))) {
        	context.setResolver(FromCompiler.getResolver(context.getConnection(), tableRef, select.getUdfParseNodes()));
        }
        Set subqueries = Sets. newHashSet();
        Expression where = WhereCompiler.compile(context, select, viewWhere, subqueries, compiledOffset.getByteOffset());
        // Recompile GROUP BY now that we've figured out our ScanRanges so we know
        // definitively whether or not we'll traverse in row key order.
        groupBy = groupBy.compile(context, innerPlan, where);
        context.setResolver(resolver); // recover resolver
        boolean wildcardIncludesDynamicCols = context.getConnection().getQueryServices()
                .getConfiguration().getBoolean(WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB,
                        DEFAULT_WILDCARD_QUERY_DYNAMIC_COLS_ATTRIB);
        RowProjector projector = ProjectionCompiler.compile(context, select, groupBy,
                asSubquery ? Collections.emptyList() : targetColumns, where,
                wildcardIncludesDynamicCols);
        OrderBy orderBy = OrderByCompiler.compile(
                context,
                select,
                groupBy,
                limit,
                compiledOffset,
                projector,
                innerPlan,
                where);
        context.getAggregationManager().compile(context, groupBy);
        // Final step is to build the query plan
        if (!asSubquery) {
            int maxRows = statement.getMaxRows();
            if (maxRows > 0) {
                if (limit != null) {
                    limit = Math.min(limit, maxRows);
                } else {
                    limit = maxRows;
                }
            }
        }

        if (projectedTable != null) {
            TupleProjector.serializeProjectorIntoScan(context.getScan(),
                    new TupleProjector(projectedTable), wildcardIncludesDynamicCols &&
                            projector.projectDynColsInWildcardQueries());
        }
        
        QueryPlan plan = innerPlan;
        QueryPlan dataPlan = dataPlans.get(tableRef);
        if (plan == null) {
            ParallelIteratorFactory parallelIteratorFactory = asSubquery ? null : this.parallelIteratorFactory;
            plan = select.getFrom() == null
                    ? new LiteralResultIterationPlan(context, select, tableRef, projector, limit, offset, orderBy,
                            parallelIteratorFactory)
                    : (select.isAggregate() || select.isDistinct()
                            ? new AggregatePlan(context, select, tableRef, projector, limit, offset, orderBy,
                                    parallelIteratorFactory, groupBy, having, dataPlan)
                            : new ScanPlan(context, select, tableRef, projector, limit, offset, orderBy,
                                    parallelIteratorFactory, allowPageFilter, dataPlan, compiledOffset.getByteOffset()));
        }
        SelectStatement planSelect = asSubquery ? select : this.select;
        if (!subqueries.isEmpty()) {
            int count = subqueries.size();
            WhereClauseSubPlan[] subPlans = new WhereClauseSubPlan[count];
            int i = 0;
            for (SubqueryParseNode subqueryNode : subqueries) {
                SelectStatement stmt = subqueryNode.getSelectNode();
                subPlans[i++] = new WhereClauseSubPlan(compileSubquery(stmt, false), stmt, subqueryNode.expectSingleRow());
            }
            plan = HashJoinPlan.create(planSelect, plan, null, subPlans);
        }

        if (innerPlan != null) {
            if (LiteralExpression.isTrue(where)) {
                where = null; // we do not pass "true" as filter
            }
            plan = select.isAggregate() || select.isDistinct()
                    ? new ClientAggregatePlan(context, planSelect, tableRef, projector, limit, offset, where, orderBy,
                            groupBy, having, plan)
                    : new ClientScanPlan(context, planSelect, tableRef, projector, limit, offset, where, orderBy, plan);

        }

        if(plan instanceof BaseQueryPlan){
            ((BaseQueryPlan) plan).setApplicable(isApplicable);
        }
        return plan;
    }
}