Repository: phoenix Updated Branches: refs/heads/4.x-HBase-1.3 fa7ff4f4e -> 39301abca
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/39301abc Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/39301abc Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/39301abc Branch: refs/heads/4.x-HBase-1.3 Commit: 39301abca2732cc93d6fd2e0d98df2911e8e7423 Parents: fa7ff4f Author: maryannxue <maryann....@gmail.com> Authored: Fri Feb 16 11:45:37 2018 -0800 Committer: maryannxue <maryann....@gmail.com> Committed: Fri Feb 16 11:45:37 2018 -0800 ---------------------------------------------------------------------- .../apache/phoenix/compile/JoinCompiler.java | 37 ++-- .../apache/phoenix/compile/QueryCompiler.java | 56 +++--- .../phoenix/compile/QueryCompilerTest.java | 187 ++++++++++++++++++- 3 files changed, 237 insertions(+), 43 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/phoenix/blob/39301abc/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/39301abc/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 243f03e..729e439 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/39301abc/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 568fa8a..9386bd2 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; @@ -4483,4 +4481,181 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest { assertEquals(6, outerScans.size()); } } + + @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(); + } + } }