You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ma...@apache.org on 2018/03/14 04:37:39 UTC

[8/9] phoenix git commit: PHOENIX-4585 Prune local index regions used for join queries

PHOENIX-4585 Prune local index regions used for join queries


Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/babda325
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/babda325
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/babda325

Branch: refs/heads/4.x-HBase-0.98
Commit: babda3258921fdf4de595ba734d972860d58a0a4
Parents: 6914d54
Author: maryannxue <ma...@gmail.com>
Authored: Fri Feb 16 11:29:25 2018 -0800
Committer: maryannxue <ma...@gmail.com>
Committed: Tue Mar 13 21:31:00 2018 -0700

----------------------------------------------------------------------
 .../apache/phoenix/compile/JoinCompiler.java    |  37 ++--
 .../apache/phoenix/compile/QueryCompiler.java   |  60 +++---
 .../phoenix/compile/QueryCompilerTest.java      | 186 ++++++++++++++++++-
 3 files changed, 238 insertions(+), 45 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/babda325/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
index f5a7e39..4020cf9 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
@@ -1199,7 +1199,8 @@ public class JoinCompiler {
         return AndExpression.create(expressions);
     }
 
-    public static SelectStatement optimize(PhoenixStatement statement, SelectStatement select, final ColumnResolver resolver) throws SQLException {
+    public static Pair<SelectStatement, Map<TableRef, QueryPlan>> optimize(
+            PhoenixStatement statement, SelectStatement select, final ColumnResolver resolver) throws SQLException {
         TableRef groupByTableRef = null;
         TableRef orderByTableRef = null;
         if (select.getGroupBy() != null && !select.getGroupBy().isEmpty()) {
@@ -1226,7 +1227,7 @@ public class JoinCompiler {
             QueryCompiler compiler = new QueryCompiler(statement, select, resolver, false, null);
             List<Object> binds = statement.getParameters();
             StatementContext ctx = new StatementContext(statement, resolver, new Scan(), new SequenceManager(statement));
-            QueryPlan plan = compiler.compileJoinQuery(ctx, binds, join, false, false, null);
+            QueryPlan plan = compiler.compileJoinQuery(ctx, binds, join, false, false, null, Collections.<TableRef, QueryPlan>emptyMap());
             TableRef table = plan.getTableRef();
             if (groupByTableRef != null && !groupByTableRef.equals(table)) {
                 groupByTableRef = null;
@@ -1236,7 +1237,8 @@ public class JoinCompiler {
             }
         }
 
-        final Map<TableRef, TableRef> replacement = new HashMap<TableRef, TableRef>();
+        Map<TableRef, TableRef> replacementMap = null;
+        Map<TableRef, QueryPlan> dataPlanMap = null;
 
         for (Table table : join.getTables()) {
             if (table.isSubselect())
@@ -1245,19 +1247,30 @@ public class JoinCompiler {
             List<ParseNode> groupBy = tableRef.equals(groupByTableRef) ? select.getGroupBy() : null;
             List<OrderByNode> orderBy = tableRef.equals(orderByTableRef) ? select.getOrderBy() : null;
             SelectStatement stmt = getSubqueryForOptimizedPlan(select.getHint(), table.getDynamicColumns(), table.getTableSamplingRate(), tableRef, join.getColumnRefs(), table.getPreFiltersCombined(), groupBy, orderBy, table.isWildCardSelect(), select.hasSequence(), select.getUdfParseNodes());
-            // TODO: As port of PHOENIX-4585, we need to make sure this plan has a pointer to the data plan
-            // when an index is used instead of the data table, and that this method returns that
-            // state for downstream processing.
             // TODO: It seems inefficient to be recompiling the statement again and again inside of this optimize call
-            QueryPlan plan = statement.getConnection().getQueryServices().getOptimizer().optimize(statement, stmt);
-            if (!plan.getTableRef().equals(tableRef)) {
-                replacement.put(tableRef, plan.getTableRef());
+            QueryPlan dataPlan =
+                    new QueryCompiler(
+                            statement, stmt,
+                            FromCompiler.getResolverForQuery(stmt, statement.getConnection()),
+                            false, null)
+                    .compile();
+            QueryPlan plan = statement.getConnection().getQueryServices().getOptimizer().optimize(statement, dataPlan);
+            TableRef newTableRef = plan.getTableRef();
+            if (!newTableRef.equals(tableRef)) {
+                if (replacementMap == null) {
+                    replacementMap = new HashMap<TableRef, TableRef>();
+                    dataPlanMap = new HashMap<TableRef, QueryPlan>();
+                }
+                replacementMap.put(tableRef, newTableRef);
+                dataPlanMap.put(newTableRef, dataPlan);
             }
         }
 
-        if (replacement.isEmpty())
-            return select;
+        if (replacementMap == null)
+            return new Pair<SelectStatement, Map<TableRef, QueryPlan>>(
+                    select, Collections.<TableRef, QueryPlan> emptyMap());
 
+        final Map<TableRef, TableRef> replacement = replacementMap;
         TableNode from = select.getFrom();
         TableNode newFrom = from.accept(new TableNodeVisitor<TableNode>() {
             private TableRef resolveTable(String alias, TableName name) throws SQLException {
@@ -1319,7 +1332,7 @@ public class JoinCompiler {
             // replace expressions with corresponding matching columns for functional indexes
             indexSelect = ParseNodeRewriter.rewrite(indexSelect, new  IndexExpressionParseNodeRewriter(indexTableRef.getTable(), indexTableRef.getTableAlias(), statement.getConnection(), indexSelect.getUdfParseNodes()));
         } 
-        return indexSelect;
+        return new Pair<SelectStatement, Map<TableRef, QueryPlan>>(indexSelect, dataPlanMap);
     }
 
     private static SelectStatement getSubqueryForOptimizedPlan(HintNode hintNode, List<ColumnDef> dynamicCols, Double tableSamplingRate, TableRef tableRef, Map<ColumnRef, ColumnRefType> columnRefs, ParseNode where, List<ParseNode> groupBy,

http://git-wip-us.apache.org/repos/asf/phoenix/blob/babda325/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java
index c8650b9..855b143 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java
@@ -22,9 +22,9 @@ 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.Query;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
@@ -184,7 +184,7 @@ public class QueryCompiler {
             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, null, false);
+        QueryPlan plan = compileSingleFlatQuery(context, select, statement.getParameters(), false, false, null, null, false, null);
         plan = new UnionPlan(context, select, tableRef, plan.getProjector(), plan.getLimit(),
             plan.getOffset(), plan.getOrderBy(), GroupBy.EMPTY_GROUP_BY, plans,
             context.getBindManager().getParameterMetaData());
@@ -195,15 +195,18 @@ public class QueryCompiler {
         List<Object> binds = statement.getParameters();
         StatementContext context = new StatementContext(statement, resolver, scan, sequenceManager);
         if (select.isJoin()) {
-            select = JoinCompiler.optimize(statement, select, resolver);
-            if (this.select != select) {
-                ColumnResolver resolver = FromCompiler.getResolverForQuery(select, statement.getConnection());
+            Pair<SelectStatement, Map<TableRef, QueryPlan>> optimized =
+                    JoinCompiler.optimize(statement, select, resolver);
+            SelectStatement optimizedSelect = optimized.getFirst();
+            if (select != optimizedSelect) {
+                ColumnResolver resolver = FromCompiler.getResolverForQuery(optimizedSelect, statement.getConnection());
                 context = new StatementContext(statement, resolver, scan, sequenceManager);
             }
-            JoinTable joinTable = JoinCompiler.compile(statement, select, context.getResolver());
-            return compileJoinQuery(context, binds, joinTable, false, false, null);
+            JoinTable joinTable = JoinCompiler.compile(statement, optimizedSelect, context.getResolver());
+            return compileJoinQuery(
+                    context, binds, joinTable, false, false, null, optimized.getSecond());
         } else {
-            return compileSingleQuery(context, select, binds, false, true);
+            return compileSingleQuery(context, select, binds, false, true, dataPlan);
         }
     }
 
@@ -216,7 +219,7 @@ public class QueryCompiler {
      *      2) Otherwise, return the join plan compiled with the default strategy.
      * @see JoinCompiler.JoinTable#getApplicableJoinStrategies()
      */
-    protected QueryPlan compileJoinQuery(StatementContext context, List<Object> binds, JoinTable joinTable, boolean asSubquery, boolean projectPKColumns, List<OrderByNode> orderBy) throws SQLException {
+    protected QueryPlan compileJoinQuery(StatementContext context, List<Object> binds, JoinTable joinTable, boolean asSubquery, boolean projectPKColumns, List<OrderByNode> orderBy, Map<TableRef, QueryPlan> dataPlans) throws SQLException {
         if (joinTable.getJoinSpecs().isEmpty()) {
             Table table = joinTable.getTable();
             SelectStatement subquery = table.getAsSubquery(orderBy);
@@ -227,7 +230,8 @@ public class QueryCompiler {
                 TupleProjector.serializeProjectorIntoScan(context.getScan(), projector);
                 context.setResolver(FromCompiler.getResolverForProjectedTable(projectedTable, context.getConnection(), subquery.getUdfParseNodes()));
                 table.projectColumns(context.getScan());
-                return compileSingleFlatQuery(context, subquery, binds, asSubquery, !asSubquery, null, projectPKColumns ? projector : null, true);
+                QueryPlan dataPlan = dataPlans.get(table.getTableRef());
+                return compileSingleFlatQuery(context, subquery, binds, asSubquery, !asSubquery, null, projectPKColumns ? projector : null, true, dataPlan);
             }
             QueryPlan plan = compileSubquery(subquery, false);
             PTable projectedTable = table.createProjectedTable(plan.getProjector());
@@ -239,7 +243,7 @@ public class QueryCompiler {
         assert strategies.size() > 0;
         if (!costBased || strategies.size() == 1) {
             return compileJoinQuery(
-                    strategies.get(0), context, binds, joinTable, asSubquery, projectPKColumns, orderBy);
+                    strategies.get(0), context, binds, joinTable, asSubquery, projectPKColumns, orderBy, dataPlans);
         }
 
         QueryPlan bestPlan = null;
@@ -248,7 +252,7 @@ public class QueryCompiler {
             StatementContext newContext = new StatementContext(
                     context.getStatement(), context.getResolver(), new Scan(), context.getSequenceManager());
             QueryPlan plan = compileJoinQuery(
-                    strategy, newContext, binds, joinTable, asSubquery, projectPKColumns, orderBy);
+                    strategy, newContext, binds, joinTable, asSubquery, projectPKColumns, orderBy, dataPlans);
             Cost cost = plan.getCost();
             if (bestPlan == null || cost.compareTo(bestCost) < 0) {
                 bestPlan = plan;
@@ -260,7 +264,7 @@ public class QueryCompiler {
         return bestPlan;
     }
 
-    protected QueryPlan compileJoinQuery(JoinCompiler.Strategy strategy, StatementContext context, List<Object> binds, JoinTable joinTable, boolean asSubquery, boolean projectPKColumns, List<OrderByNode> orderBy) throws SQLException {
+    protected QueryPlan compileJoinQuery(JoinCompiler.Strategy strategy, StatementContext context, List<Object> binds, JoinTable joinTable, boolean asSubquery, boolean projectPKColumns, List<OrderByNode> orderBy, Map<TableRef, QueryPlan> dataPlans) throws SQLException {
         byte[] emptyByteArray = new byte[0];
         List<JoinSpec> joinSpecs = joinTable.getJoinSpecs();
         switch (strategy) {
@@ -303,7 +307,7 @@ public class QueryCompiler {
                     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.getJoinTable(), true, true, null);
+                    subPlans[i] = compileJoinQuery(subContexts[i], binds, joinSpec.getJoinTable(), true, true, null, dataPlans);
                     boolean hasPostReference = joinSpec.getJoinTable().hasPostReference();
                     if (hasPostReference) {
                         tables[i] = subContexts[i].getResolver().getTables().get(0).getTable();
@@ -330,7 +334,8 @@ public class QueryCompiler {
                     hashPlans[i] = new HashSubPlan(i, subPlans[i], optimized ? null : hashExpressions, joinSpec.isSingleValueOnly(), keyRangeLhsExpression, keyRangeRhsExpression);
                 }
                 TupleProjector.serializeProjectorIntoScan(context.getScan(), tupleProjector);
-                QueryPlan plan = compileSingleFlatQuery(context, query, binds, asSubquery, !asSubquery && joinTable.isAllLeftJoin(), null, !table.isSubselect() && projectPKColumns ? tupleProjector : null, true);
+                QueryPlan dataPlan = dataPlans.get(tableRef);
+                QueryPlan plan = compileSingleFlatQuery(context, query, binds, asSubquery, !asSubquery && joinTable.isAllLeftJoin(), null, !table.isSubselect() && projectPKColumns ? tupleProjector : null, true, dataPlan);
                 Expression postJoinFilterExpression = joinTable.compilePostFilterExpression(context, table);
                 Integer limit = null;
                 Integer offset = null;
@@ -350,7 +355,7 @@ public class QueryCompiler {
                 JoinTable lhsJoin = joinTable.getSubJoinTableWithoutPostFilters();
                 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);
+                QueryPlan lhsPlan = compileJoinQuery(lhsCtx, binds, lhsJoin, true, true, null, dataPlans);
                 PTable rhsProjTable;
                 TableRef rhsTableRef;
                 SelectStatement rhs;
@@ -383,7 +388,8 @@ public class QueryCompiler {
                 PTable projectedTable = needsMerge ? JoinCompiler.joinProjectedTables(rhsProjTable, lhsTable, type == JoinType.Right ? JoinType.Left : type) : rhsProjTable;
                 TupleProjector.serializeProjectorIntoScan(context.getScan(), tupleProjector);
                 context.setResolver(FromCompiler.getResolverForProjectedTable(projectedTable, context.getConnection(), rhs.getUdfParseNodes()));
-                QueryPlan rhsPlan = compileSingleFlatQuery(context, rhs, binds, asSubquery, !asSubquery && type == JoinType.Right, null, !rhsTable.isSubselect() && projectPKColumns ? tupleProjector : null, true);
+                QueryPlan dataPlan = dataPlans.get(rhsTableRef);
+                QueryPlan rhsPlan = compileSingleFlatQuery(context, rhs, binds, asSubquery, !asSubquery && type == JoinType.Right, null, !rhsTable.isSubselect() && projectPKColumns ? tupleProjector : null, true, dataPlan);
                 Expression postJoinFilterExpression = joinTable.compilePostFilterExpression(context, rhsTable);
                 Integer limit = null;
                 Integer offset = null;
@@ -420,13 +426,13 @@ public class QueryCompiler {
                 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);
+                QueryPlan lhsPlan = compileJoinQuery(lhsCtx, binds, lhsJoin, true, !preserveRowkey, lhsOrderBy, dataPlans);
                 PTable lhsProjTable = lhsCtx.getResolver().getTables().get(0).getTable();
                 boolean isInRowKeyOrder = preserveRowkey && lhsPlan.getOrderBy().getOrderByExpressions().isEmpty();
 
                 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);
+                QueryPlan rhsPlan = compileJoinQuery(rhsCtx, binds, rhsJoin, true, true, rhsOrderBy, dataPlans);
                 PTable rhsProjTable = rhsCtx.getResolver().getTables().get(0).getTable();
 
                 Pair<List<Expression>, List<Expression>> joinConditions = lastJoinSpec.compileJoinConditions(type == JoinType.Right ? rhsCtx : lhsCtx, type == JoinType.Right ? lhsCtx : rhsCtx, strategy);
@@ -453,7 +459,7 @@ public class QueryCompiler {
                         joinTable.getStatement().getUdfParseNodes())
                         : NODE_FACTORY.select(joinTable.getStatement(), from, where);
 
-                return compileSingleFlatQuery(context, select, binds, asSubquery, false, innerPlan, null, isInRowKeyOrder);
+                return compileSingleFlatQuery(context, select, binds, asSubquery, false, innerPlan, null, isInRowKeyOrder, null);
             }
             default:
                 throw new IllegalArgumentException("Invalid join strategy '" + strategy + "'");
@@ -506,16 +512,16 @@ public class QueryCompiler {
         }
         int maxRows = this.statement.getMaxRows();
         this.statement.setMaxRows(pushDownMaxRows ? maxRows : 0); // overwrite maxRows to avoid its impact on inner queries.
-        QueryPlan plan = new QueryCompiler(this.statement, subquery, resolver, false, dataPlan).compile();
+        QueryPlan plan = new QueryCompiler(this.statement, subquery, resolver, false, null).compile();
         plan = statement.getConnection().getQueryServices().getOptimizer().optimize(statement, plan);
         this.statement.setMaxRows(maxRows); // restore maxRows.
         return plan;
     }
-    
-    protected QueryPlan compileSingleQuery(StatementContext context, SelectStatement select, List<Object> binds, boolean asSubquery, boolean allowPageFilter) throws SQLException{
+
+    protected QueryPlan compileSingleQuery(StatementContext context, SelectStatement select, List<Object> binds, boolean asSubquery, boolean allowPageFilter, QueryPlan dataPlan) throws SQLException{
         SelectStatement innerSelect = select.getInnerSelectStatement();
         if (innerSelect == null) {
-            return compileSingleFlatQuery(context, select, binds, asSubquery, allowPageFilter, null, null, true);
+            return compileSingleFlatQuery(context, select, binds, asSubquery, allowPageFilter, null, null, true, dataPlan);
         }
         
         QueryPlan innerPlan = compileSubquery(innerSelect, false);
@@ -530,10 +536,10 @@ public class QueryCompiler {
         context.setCurrentTable(tableRef);
         boolean isInRowKeyOrder = innerPlan.getGroupBy() == GroupBy.EMPTY_GROUP_BY && innerPlan.getOrderBy() == OrderBy.EMPTY_ORDER_BY;
 
-        return compileSingleFlatQuery(context, select, binds, asSubquery, allowPageFilter, innerPlan, tupleProjector, isInRowKeyOrder);
+        return compileSingleFlatQuery(context, select, binds, asSubquery, allowPageFilter, innerPlan, tupleProjector, isInRowKeyOrder, null);
     }
-    
-    protected QueryPlan compileSingleFlatQuery(StatementContext context, SelectStatement select, List<Object> binds, boolean asSubquery, boolean allowPageFilter, QueryPlan innerPlan, TupleProjector innerPlanTupleProjector, boolean isInRowKeyOrder) throws SQLException{
+
+    protected QueryPlan compileSingleFlatQuery(StatementContext context, SelectStatement select, List<Object> binds, boolean asSubquery, boolean allowPageFilter, QueryPlan innerPlan, TupleProjector innerPlanTupleProjector, boolean isInRowKeyOrder, QueryPlan dataPlan) throws SQLException{
         PTable projectedTable = null;
         if (this.projectTuples) {
             projectedTable = TupleProjectionCompiler.createProjectedTable(select, context);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/babda325/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
index 05358d4..73cd69c 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
@@ -43,6 +43,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
 
+import com.google.common.collect.Lists;
 import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.filter.Filter;
@@ -51,12 +52,8 @@ import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.coprocessor.BaseScannerRegionObserver;
 import org.apache.phoenix.exception.SQLExceptionCode;
-import org.apache.phoenix.execute.AggregatePlan;
-import org.apache.phoenix.execute.ClientScanPlan;
-import org.apache.phoenix.execute.HashJoinPlan;
-import org.apache.phoenix.execute.ScanPlan;
-import org.apache.phoenix.execute.SortMergeJoinPlan;
-import org.apache.phoenix.execute.TupleProjectionPlan;
+import org.apache.phoenix.execute.*;
+import org.apache.phoenix.execute.visitor.QueryPlanVisitor;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.LiteralExpression;
 import org.apache.phoenix.expression.aggregator.Aggregator;
@@ -88,6 +85,7 @@ import org.apache.phoenix.util.PropertiesUtil;
 import org.apache.phoenix.util.QueryUtil;
 import org.apache.phoenix.util.ScanUtil;
 import org.apache.phoenix.util.SchemaUtil;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import com.google.common.collect.Lists;
@@ -4458,4 +4456,180 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
         }
     }
 
+    @Test
+    public void testLocalIndexPruningInSortMergeJoin() throws SQLException {
+        verifyLocalIndexPruningWithMultipleTables("SELECT /*+ USE_SORT_MERGE_JOIN*/ *\n" +
+                "FROM T1 JOIN T2 ON T1.A = T2.A\n" +
+                "WHERE T1.A = 'B' and T1.C='C' and T2.A IN ('A','G') and T2.B = 'A' and T2.D = 'D'");
+    }
+
+    @Ignore("Blocked by PHOENIX-4614")
+    @Test
+    public void testLocalIndexPruningInLeftOrInnerHashJoin() throws SQLException {
+        verifyLocalIndexPruningWithMultipleTables("SELECT *\n" +
+                "FROM T1 JOIN T2 ON T1.A = T2.A\n" +
+                "WHERE T1.A = 'B' and T1.C='C' and T2.A IN ('A','G') and T2.B = 'A' and T2.D = 'D'");
+    }
+
+    @Ignore("Blocked by PHOENIX-4614")
+    @Test
+    public void testLocalIndexPruningInRightHashJoin() throws SQLException {
+        verifyLocalIndexPruningWithMultipleTables("SELECT *\n" +
+                "FROM (\n" +
+                "    SELECT A, B, C, D FROM T2 WHERE T2.A IN ('A','G') and T2.B = 'A' and T2.D = 'D'\n" +
+                ") T2\n" +
+                "RIGHT JOIN T1 ON T2.A = T1.A\n" +
+                "WHERE T1.A = 'B' and T1.C='C'");
+    }
+
+    @Test
+    public void testLocalIndexPruningInUinon() throws SQLException {
+        verifyLocalIndexPruningWithMultipleTables("SELECT A, B, C FROM T1\n" +
+                "WHERE A = 'B' and C='C'\n" +
+                "UNION ALL\n" +
+                "SELECT A, B, C FROM T2\n" +
+                "WHERE A IN ('A','G') and B = 'A' and D = 'D'");
+    }
+
+    private void verifyLocalIndexPruningWithMultipleTables(String query) throws SQLException {
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+            conn.createStatement().execute("CREATE TABLE T1 (\n" +
+                    "    A CHAR(1) NOT NULL,\n" +
+                    "    B CHAR(1) NOT NULL,\n" +
+                    "    C CHAR(1) NOT NULL,\n" +
+                    "    CONSTRAINT PK PRIMARY KEY (\n" +
+                    "        A,\n" +
+                    "        B,\n" +
+                    "        C\n" +
+                    "    )\n" +
+                    ") SPLIT ON ('A','C','E','G','I')");
+            conn.createStatement().execute("CREATE LOCAL INDEX IDX1 ON T1(A,C)");
+            conn.createStatement().execute("CREATE TABLE T2 (\n" +
+                    "    A CHAR(1) NOT NULL,\n" +
+                    "    B CHAR(1) NOT NULL,\n" +
+                    "    C CHAR(1) NOT NULL,\n" +
+                    "    D CHAR(1) NOT NULL,\n" +
+                    "    CONSTRAINT PK PRIMARY KEY (\n" +
+                    "        A,\n" +
+                    "        B,\n" +
+                    "        C,\n" +
+                    "        D\n" +
+                    "    )\n" +
+                    ") SPLIT ON ('A','C','E','G','I')");
+            conn.createStatement().execute("CREATE LOCAL INDEX IDX2 ON T2(A,B,D)");
+            PhoenixStatement statement = conn.createStatement().unwrap(PhoenixStatement.class);
+            QueryPlan plan = statement.optimizeQuery(query);
+            List<QueryPlan> childPlans = plan.accept(new MultipleChildrenExtractor());
+            assertEquals(2, childPlans.size());
+            // Check left child
+            assertEquals("IDX1", childPlans.get(0).getContext().getCurrentTable().getTable().getName().getString());
+            childPlans.get(0).iterator();
+            List<List<Scan>> outerScansL = childPlans.get(0).getScans();
+            assertEquals(1, outerScansL.size());
+            List<Scan> innerScansL = outerScansL.get(0);
+            assertEquals(1, innerScansL.size());
+            Scan scanL = innerScansL.get(0);
+            assertEquals("A", Bytes.toString(scanL.getStartRow()).trim());
+            assertEquals("C", Bytes.toString(scanL.getStopRow()).trim());
+            // Check right child
+            assertEquals("IDX2", childPlans.get(1).getContext().getCurrentTable().getTable().getName().getString());
+            childPlans.get(1).iterator();
+            List<List<Scan>> outerScansR = childPlans.get(1).getScans();
+            assertEquals(2, outerScansR.size());
+            List<Scan> innerScansR1 = outerScansR.get(0);
+            assertEquals(1, innerScansR1.size());
+            Scan scanR1 = innerScansR1.get(0);
+            assertEquals("A", Bytes.toString(scanR1.getStartRow()).trim());
+            assertEquals("C", Bytes.toString(scanR1.getStopRow()).trim());
+            List<Scan> innerScansR2 = outerScansR.get(1);
+            assertEquals(1, innerScansR2.size());
+            Scan scanR2 = innerScansR2.get(0);
+            assertEquals("G", Bytes.toString(scanR2.getStartRow()).trim());
+            assertEquals("I", Bytes.toString(scanR2.getStopRow()).trim());
+        }
+    }
+
+    private static class MultipleChildrenExtractor implements QueryPlanVisitor<List<QueryPlan>> {
+
+        @Override
+        public List<QueryPlan> defaultReturn(QueryPlan plan) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<QueryPlan> visit(AggregatePlan plan) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<QueryPlan> visit(ScanPlan plan) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<QueryPlan> visit(ClientAggregatePlan plan) {
+            return plan.getDelegate().accept(this);
+        }
+
+        @Override
+        public List<QueryPlan> visit(ClientScanPlan plan) {
+            return plan.getDelegate().accept(this);
+        }
+
+        @Override
+        public List<QueryPlan> visit(LiteralResultIterationPlan plan) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<QueryPlan> visit(TupleProjectionPlan plan) {
+            return plan.getDelegate().accept(this);
+        }
+
+        @Override
+        public List<QueryPlan> visit(HashJoinPlan plan) {
+            List<QueryPlan> children = new ArrayList<QueryPlan>(plan.getSubPlans().length + 1);
+            children.add(plan.getDelegate());
+            for (HashJoinPlan.SubPlan subPlan : plan.getSubPlans()) {
+                children.add(subPlan.getInnerPlan());
+            }
+            return children;
+        }
+
+        @Override
+        public List<QueryPlan> visit(SortMergeJoinPlan plan) {
+            return Lists.newArrayList(plan.getLhsPlan(), plan.getRhsPlan());
+        }
+
+        @Override
+        public List<QueryPlan> visit(UnionPlan plan) {
+            return plan.getSubPlans();
+        }
+
+        @Override
+        public List<QueryPlan> visit(UnnestArrayPlan plan) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<QueryPlan> visit(CorrelatePlan plan) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<QueryPlan> visit(CursorFetchPlan plan) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<QueryPlan> visit(ListJarsQueryPlan plan) {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<QueryPlan> visit(TraceQueryPlan plan) {
+            return Collections.emptyList();
+        }
+    }
 }