You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ch...@apache.org on 2019/09/05 07:42:27 UTC

[phoenix] branch 4.x-HBase-1.5 updated: PHOENIX-5451 Column pruning for Subselect

This is an automated email from the ASF dual-hosted git repository.

chenglei pushed a commit to branch 4.x-HBase-1.5
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/4.x-HBase-1.5 by this push:
     new 5c269e6  PHOENIX-5451 Column pruning for Subselect
5c269e6 is described below

commit 5c269e6264e8b0f3f1411513abb109c1266395d9
Author: chenglei <ch...@apache.org>
AuthorDate: Thu Sep 5 15:41:37 2019 +0800

    PHOENIX-5451 Column pruning for Subselect
---
 .../org/apache/phoenix/compile/FromCompiler.java   |  33 +++
 .../org/apache/phoenix/compile/JoinCompiler.java   | 157 ++++++++++----
 .../apache/phoenix/compile/SubselectRewriter.java  | 214 +++++++++++++++----
 .../apache/phoenix/optimize/QueryOptimizer.java    |  17 +-
 .../apache/phoenix/parse/ParseNodeRewriter.java    |  25 +++
 .../org/apache/phoenix/util/ParseNodeUtil.java     | 142 ++++++++++++
 .../java/org/apache/phoenix/util/SchemaUtil.java   |  13 +-
 .../phoenix/compile/JoinQueryCompilerTest.java     |  31 +--
 .../apache/phoenix/compile/QueryCompilerTest.java  | 237 ++++++++++++++++++++-
 .../java/org/apache/phoenix/util/TestUtil.java     |  28 +++
 10 files changed, 782 insertions(+), 115 deletions(-)

diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
index 0bdc748..98a58a9 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
@@ -231,6 +231,22 @@ public class FromCompiler {
         return visitor;
     }
 
+    /**
+     * Refresh the inner state of {@link MultiTableColumnResolver} for the derivedTableNode when
+     * the derivedTableNode is changed for some sql optimization.
+     * @param columnResolver
+     * @param derivedTableNode
+     * @return
+     * @throws SQLException
+     */
+    public static TableRef refreshDerivedTableNode(
+            ColumnResolver columnResolver, DerivedTableNode derivedTableNode) throws SQLException {
+        if(!(columnResolver instanceof MultiTableColumnResolver)) {
+            throw new UnsupportedOperationException();
+        }
+        return ((MultiTableColumnResolver)columnResolver).refreshDerivedTableNode(derivedTableNode);
+    }
+
     public static ColumnResolver getResolverForSchema(UseSchemaStatement statement, PhoenixConnection connection)
             throws SQLException {
         return new SchemaResolver(connection, SchemaUtil.normalizeIdentifier(statement.getSchemaName()), true);
@@ -899,6 +915,23 @@ public class FromCompiler {
             return null;
         }
 
+        /**
+         * Invoke the {@link #visit(DerivedTableNode)} again to refresh the inner state.
+         * @param derivedTableNode
+         * @return
+         * @throws SQLException
+         */
+        public TableRef refreshDerivedTableNode(DerivedTableNode derivedTableNode) throws SQLException {
+              String tableAlias = derivedTableNode.getAlias();
+              List<TableRef> removedTableRefs = this.tableMap.removeAll(tableAlias);
+              if(removedTableRefs == null || removedTableRefs.isEmpty()) {
+                  return null;
+              }
+              tables.removeAll(removedTableRefs);
+              this.visit(derivedTableNode);
+              return this.resolveTable(null, tableAlias);
+        }
+
         private static class ColumnFamilyRef {
             private final TableRef tableRef;
             private final PColumnFamily family;
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 f932a32..f0a2f36 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
@@ -52,7 +52,6 @@ import org.apache.phoenix.parse.AndBooleanParseNodeVisitor;
 import org.apache.phoenix.parse.AndParseNode;
 import org.apache.phoenix.parse.AndRewriterBooleanParseNodeVisitor;
 import org.apache.phoenix.parse.BindTableNode;
-import org.apache.phoenix.parse.BooleanParseNodeVisitor;
 import org.apache.phoenix.parse.ColumnDef;
 import org.apache.phoenix.parse.ColumnParseNode;
 import org.apache.phoenix.parse.ComparisonParseNode;
@@ -100,6 +99,7 @@ import org.apache.phoenix.schema.types.PVarbinary;
 import org.apache.phoenix.schema.types.PVarchar;
 import org.apache.phoenix.util.EncodedColumnsUtil;
 import org.apache.phoenix.util.IndexUtil;
+import org.apache.phoenix.util.ParseNodeUtil;
 import org.apache.phoenix.util.SchemaUtil;
 
 import com.google.common.base.Preconditions;
@@ -138,6 +138,16 @@ public class JoinCompiler {
         this.useSortMergeJoin = select.getHint().hasHint(Hint.USE_SORT_MERGE_JOIN);
     }
 
+    /**
+     * After this method is called, the inner state of the parameter resolver may be changed by
+     * {@link FromCompiler#refreshDerivedTableNode} because of some sql optimization,
+     * see also {@link Table#pruneSubselectAliasedNodes()}.
+     * @param statement
+     * @param select
+     * @param resolver
+     * @return
+     * @throws SQLException
+     */
     public static JoinTable compile(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver) throws SQLException {
         JoinCompiler compiler = new JoinCompiler(statement, select, resolver);
         JoinTableConstructor constructor = compiler.new JoinTableConstructor();
@@ -149,26 +159,10 @@ public class JoinCompiler {
 
         ColumnRefParseNodeVisitor generalRefVisitor = new ColumnRefParseNodeVisitor(resolver, statement.getConnection());
         ColumnRefParseNodeVisitor joinLocalRefVisitor = new ColumnRefParseNodeVisitor(resolver, statement.getConnection());
-        ColumnRefParseNodeVisitor prefilterRefVisitor = new ColumnRefParseNodeVisitor(resolver, statement.getConnection());
 
-        joinTable.pushDownColumnRefVisitors(generalRefVisitor, joinLocalRefVisitor, prefilterRefVisitor);
+        joinTable.pushDownColumnRefVisitors(generalRefVisitor, joinLocalRefVisitor);
 
-        for (AliasedNode node : select.getSelect()) {
-            node.getNode().accept(generalRefVisitor);
-        }
-        if (select.getGroupBy() != null) {
-            for (ParseNode node : select.getGroupBy()) {
-                node.accept(generalRefVisitor);
-            }
-        }
-        if (select.getHaving() != null) {
-            select.getHaving().accept(generalRefVisitor);
-        }
-        if (select.getOrderBy() != null) {
-            for (OrderByNode node : select.getOrderBy()) {
-                node.getNode().accept(generalRefVisitor);
-            }
-        }
+        ParseNodeUtil.applyParseNodeVisitor(select, generalRefVisitor, false);
 
         compiler.columnNodes.putAll(joinLocalRefVisitor.getColumnRefMap());
         compiler.columnNodes.putAll(generalRefVisitor.getColumnRefMap());
@@ -181,6 +175,12 @@ public class JoinCompiler {
                 compiler.columnRefs.put(ref, ColumnRefType.JOINLOCAL);
         }
 
+        /**
+         * After {@link ColumnRefParseNodeVisitor} is pushed down,
+         * pruning columns for each {@link JoinCompiler.Table} if
+         * {@link @link JoinCompiler.Table#isSubselect()}.
+         */
+        joinTable.pruneSubselectAliasedNodes();
         return joinTable;
     }
 
@@ -335,7 +335,7 @@ public class JoinCompiler {
             this.postFilters.add(parseNode);
         }
 
-        public void addLeftTableFilter(ParseNode parseNode) {
+        public void addLeftTableFilter(ParseNode parseNode) throws SQLException {
             if (isPrefilterAccepted) {
                 leftTable.addFilter(parseNode);
             } else {
@@ -365,12 +365,9 @@ public class JoinCompiler {
             filter.accept(visitor);
         }
 
-        public void pushDownColumnRefVisitors(ColumnRefParseNodeVisitor generalRefVisitor,
-                ColumnRefParseNodeVisitor joinLocalRefVisitor,
-                ColumnRefParseNodeVisitor prefilterRefVisitor) throws SQLException {
-            for (ParseNode node : leftTable.getPreFilters()) {
-                node.accept(prefilterRefVisitor);
-            }
+        public void pushDownColumnRefVisitors(
+                ColumnRefParseNodeVisitor generalRefVisitor,
+                ColumnRefParseNodeVisitor joinLocalRefVisitor) throws SQLException {
             for (ParseNode node : leftTable.getPostFilters()) {
                 node.accept(generalRefVisitor);
             }
@@ -388,7 +385,20 @@ public class JoinCompiler {
                         node.getRHS().accept(joinLocalRefVisitor);
                     }
                 }
-                joinTable.pushDownColumnRefVisitors(generalRefVisitor, joinLocalRefVisitor, prefilterRefVisitor);
+                joinTable.pushDownColumnRefVisitors(generalRefVisitor, joinLocalRefVisitor);
+            }
+        }
+
+        /**
+         * Pruning columns for each {@link JoinCompiler.Table} if
+         * {@link @link JoinCompiler.Table#isSubselect()}.
+         * @throws SQLException
+         */
+        public void pruneSubselectAliasedNodes() throws SQLException {
+            this.leftTable.pruneSubselectAliasedNodes();
+            for (JoinSpec joinSpec : joinSpecs) {
+                JoinTable rhsJoinTablesContext = joinSpec.getRhsJoinTable();;
+                rhsJoinTablesContext.pruneSubselectAliasedNodes();
             }
         }
 
@@ -764,15 +774,15 @@ public class JoinCompiler {
     }
 
     public class Table {
-        private final TableNode tableNode;
+        private TableNode tableNode;
         private final boolean isWildcard;
         private final List<ColumnDef> dynamicColumns;
         private final Double tableSamplingRate;
-        private final SelectStatement subselect;
-        private final TableRef tableRef;
+        private SelectStatement subselect;
+        private TableRef tableRef;
         private final List<ParseNode> preFilters;
         private final List<ParseNode> postFilters;
-        private final boolean isPostFilterConvertible;
+        private final boolean filterCanPushDownToSubselect;
 
         private Table(TableNode tableNode, boolean isWildcard, List<ColumnDef> dynamicColumns,
                       Double tableSamplingRate, TableRef tableRef) {
@@ -784,7 +794,7 @@ public class JoinCompiler {
             this.tableRef = tableRef;
             this.preFilters = new ArrayList<ParseNode>();
             this.postFilters = Collections.<ParseNode>emptyList();
-            this.isPostFilterConvertible = false;
+            this.filterCanPushDownToSubselect = false;
         }
 
         private Table(DerivedTableNode tableNode, boolean isWildcard, TableRef tableRef) throws SQLException {
@@ -796,7 +806,7 @@ public class JoinCompiler {
             this.tableRef = tableRef;
             this.preFilters = new ArrayList<ParseNode>();
             this.postFilters = new ArrayList<ParseNode>();
-            this.isPostFilterConvertible = SubselectRewriter.isPostFilterConvertible(subselect);
+            this.filterCanPushDownToSubselect = SubselectRewriter.isFilterCanPushDownToSelect(subselect);
         }
 
         public TableNode getTableNode() {
@@ -815,6 +825,62 @@ public class JoinCompiler {
             return subselect != null;
         }
 
+        public SelectStatement getSubselect() {
+            return this.subselect;
+        }
+
+        /**
+         * Pruning columns if {@link #isSubselect()}.
+         * Note: If some columns are pruned, the {@link JoinCompiler#origResolver} should be refreshed.
+         * @throws SQLException
+         */
+        public void pruneSubselectAliasedNodes() throws SQLException {
+            if(!this.isSubselect()) {
+                return;
+            }
+            Set<String> referencedColumnNames = this.getReferencedColumnNames();
+            SelectStatement newSubselectStatement =
+                    SubselectRewriter.pruneSelectAliasedNodes(
+                            this.subselect,
+                            referencedColumnNames,
+                            statement.getConnection());
+            if(!newSubselectStatement.getSelect().equals(this.subselect.getSelect())) {
+                /**
+                 * The columns are pruned, so {@link ColumnResolver} should be refreshed.
+                 */
+                DerivedTableNode newDerivedTableNode =
+                        NODE_FACTORY.derivedTable(this.tableNode.getAlias(), newSubselectStatement);
+                TableRef newTableRef =
+                        FromCompiler.refreshDerivedTableNode(origResolver, newDerivedTableNode);
+                assert newTableRef != null;
+                this.subselect = newSubselectStatement;
+                this.tableRef = newTableRef;
+                this.tableNode = newDerivedTableNode;
+            }
+        }
+
+        /**
+         * Collect the referenced columns of this {@link Table}
+         * according to {@link JoinCompiler#columnNodes}.
+         * @return
+         * @throws SQLException
+         */
+        private Set<String> getReferencedColumnNames() throws SQLException {
+            assert(this.isSubselect());
+            if (isWildCardSelect()) {
+                return null;
+            }
+            Set<String> referencedColumnNames = new HashSet<String>();
+            for (Map.Entry<ColumnRef, ColumnParseNode> entry : columnNodes.entrySet()) {
+                if (tableRef.equals(entry.getKey().getTableRef())) {
+                    ColumnParseNode columnParseNode = entry.getValue();
+                    String normalizedColumnName = SchemaUtil.getNormalizedColumnName(columnParseNode);
+                    referencedColumnNames.add(normalizedColumnName);
+                }
+            }
+            return referencedColumnNames;
+        }
+
         /**
          * Returns all the basic select nodes, no aggregation.
          */
@@ -847,14 +913,31 @@ public class JoinCompiler {
             return tableRef;
         }
 
-        public void addFilter(ParseNode filter) {
-            if (!isSubselect() || isPostFilterConvertible) {
-                preFilters.add(filter);
+        public void addFilter(ParseNode filter) throws SQLException {
+            if (!isSubselect() || filterCanPushDownToSubselect) {
+                this.addPreFilter(filter);
             } else {
                 postFilters.add(filter);
             }
         }
 
+        /**
+         * If {@link #isSubselect()}, preFilterParseNode is at first rewritten by
+         * {@link SubselectRewriter#rewritePreFilterForSubselect}
+         * @param preFilterParseNode
+         * @throws SQLException
+         */
+        private void addPreFilter(ParseNode preFilterParseNode) throws SQLException {
+            if(this.isSubselect()) {
+                preFilterParseNode =
+                        SubselectRewriter.rewritePreFilterForSubselect(
+                                preFilterParseNode,
+                                this.subselect,
+                                tableNode.getAlias());
+            }
+            preFilters.add(preFilterParseNode);
+        }
+
         public ParseNode getPreFiltersCombined() {
             return combine(preFilters);
         }
@@ -867,7 +950,7 @@ public class JoinCompiler {
                         tableNode.getAlias(),
                         postFilters);
             }
-            //for table, postFilters is empty , because it can safely pushed down as preFilters.
+            //for flat table, postFilters is empty , because it can safely pushed down as preFilters.
             assert postFilters == null || postFilters.isEmpty();
             return NODE_FACTORY.select(tableNode, select.getHint(), false, getSelectNodes(), getPreFiltersCombined(), null,
                     null, orderBy, null, null, 0, false, select.hasSequence(),
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/SubselectRewriter.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/SubselectRewriter.java
index 468bd1f..385df50 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/SubselectRewriter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/SubselectRewriter.java
@@ -19,6 +19,7 @@
 package org.apache.phoenix.compile;
 
 import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -26,11 +27,13 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.parse.AliasedNode;
 import org.apache.phoenix.parse.ColumnParseNode;
 import org.apache.phoenix.parse.DerivedTableNode;
+import org.apache.phoenix.parse.FamilyWildcardParseNode;
 import org.apache.phoenix.parse.HintNode;
 import org.apache.phoenix.parse.LimitNode;
 import org.apache.phoenix.parse.OffsetNode;
@@ -42,6 +45,7 @@ import org.apache.phoenix.parse.SelectStatement;
 import org.apache.phoenix.parse.TableNode;
 import org.apache.phoenix.parse.TableWildcardParseNode;
 import org.apache.phoenix.parse.WildcardParseNode;
+import org.apache.phoenix.util.ParseNodeUtil;
 import org.apache.phoenix.util.SchemaUtil;
 
 import com.google.common.collect.Lists;
@@ -55,23 +59,67 @@ public class SubselectRewriter extends ParseNodeRewriter {
     private final String tableAlias;
     private final Map<String, ParseNode> aliasMap;
     private boolean removeAlias = false;
-    
-    public static SelectStatement applyPreFiltersForSubselect(SelectStatement statement, List<ParseNode> preFilterParseNodes, String subqueryAlias) throws SQLException {
-        if (preFilterParseNodes.isEmpty())
-            return statement;
-        
-        assert(isPostFilterConvertible(statement));
+
+    /**
+     * Add the preFilterParseNodes to Where statement or Having statement of the subselectStatement,
+     * depending on whether having GroupBy statement.
+     * Note: the preFilterParseNodes parameter must have already been rewritten by {@link #rewritePreFilterForSubselect}.
+     * @param subselectStatement
+     * @param preFilterParseNodes
+     * @param subselectAlias
+     * @return
+     * @throws SQLException
+     */
+    public static SelectStatement applyPreFiltersForSubselect(
+            SelectStatement subselectStatement,
+            List<ParseNode> preFilterParseNodes,
+            String subselectAlias) throws SQLException {
+
+        if (preFilterParseNodes.isEmpty()) {
+            return subselectStatement;
+        }
+
+        assert(isFilterCanPushDownToSelect(subselectStatement));
+
+        List<ParseNode> newFilterParseNodes = Lists.<ParseNode> newArrayList(preFilterParseNodes);
+        if (subselectStatement.getGroupBy().isEmpty()) {
+            ParseNode where = subselectStatement.getWhere();
+            if (where != null) {
+                newFilterParseNodes.add(where);
+            }
+            return NODE_FACTORY.select(subselectStatement, combine(newFilterParseNodes));
+        }
         
-        return new SubselectRewriter(null, statement.getSelect(), subqueryAlias).applyPreFilters(statement, preFilterParseNodes);
+        ParseNode having = subselectStatement.getHaving();
+        if (having != null) {
+            newFilterParseNodes.add(having);
+        }
+        return NODE_FACTORY.select(subselectStatement, subselectStatement.getWhere(), combine(newFilterParseNodes));
     }
-    
-    public static boolean isPostFilterConvertible(SelectStatement statement) throws SQLException {
+
+    public static ParseNode rewritePreFilterForSubselect(ParseNode preFilterParseNode, SelectStatement subselectStatement, String subselectAlias) throws SQLException {
+        SubselectRewriter subselectRewriter = new SubselectRewriter(
+            null,
+            subselectStatement.getSelect(),
+            subselectAlias);
+        return preFilterParseNode.accept(subselectRewriter);
+    }
+
+    /**
+     * Check if a filter can push down to the statement as a preFilter,
+     * if true, the filter can be rewritten by {@link #rewritePreFilterForSubselect} and
+     * added to the statement by {@link #applyPreFiltersForSubselect}.
+     * @param statement
+     * @return
+     * @throws SQLException
+     */
+    public static boolean isFilterCanPushDownToSelect(SelectStatement statement) throws SQLException {
         return statement.getLimit() == null && (!statement.isAggregate() || !statement.getGroupBy().isEmpty());        
     }
     
     /**
      * <pre>
-     * only append orderByNodes and postFilters, the optimization is left to {@link #flatten(SelectStatement, SelectStatement)}.
+     * Only append orderByNodes and postFilters, the optimization is left to {@link #flatten(SelectStatement, SelectStatement)}.
      * an example :
      * when the subselectStatment is : (SELECT reverse(loc_id), \"supplier_id\", name FROM " + JOIN_SUPPLIER_TABLE + " LIMIT 5) AS supp
      * orderByNodes is  : supp.\"supplier_id\"
@@ -142,19 +190,24 @@ public class SubselectRewriter extends ParseNodeRewriter {
            subselectAliasFullNameToNewColumnParseNode.put(
                    SchemaUtil.getColumnName(subselectTableAliasName, SchemaUtil.normalizeIdentifier(aliasName)),
                    newColumnParseNode);
-           AliasedNode newOuterSelectAliasNode = NODE_FACTORY.aliasedNode(null, newColumnParseNode);
+           /**
+            * The alias of AliasedNode is set to the same as newColumnParseNode, so when the rewritten
+            * selectStatement is flattened by {@link SubselectRewriter#flatten} later,the {@link AliasedNode#getAlias}
+            * could remain the same even if the {@link AliasedNode#getNode} is rewritten by {@link SubselectRewriter#flatten}.
+            */
+           AliasedNode newOuterSelectAliasNode = NODE_FACTORY.aliasedNode(aliasName, newColumnParseNode);
            newOuterSelectAliasedNodes.add(newOuterSelectAliasNode);
            index++;
        }
 
-       SubselectRewriter rewriter = new SubselectRewriter(subselectAliasFullNameToNewColumnParseNode);
+       SubselectRewriter subselectRewriter = new SubselectRewriter(subselectAliasFullNameToNewColumnParseNode);
        List<OrderByNode> rewrittenOrderByNodes = null;
        if(orderByNodes.size() > 0) {
            rewrittenOrderByNodes = new ArrayList<OrderByNode>(orderByNodes.size());
            for (OrderByNode orderByNode : orderByNodes) {
                ParseNode parseNode = orderByNode.getNode();
                rewrittenOrderByNodes.add(NODE_FACTORY.orderBy(
-                       parseNode.accept(rewriter),
+                       parseNode.accept(subselectRewriter),
                        orderByNode.isNullsLast(),
                        orderByNode.isAscending()));
            }
@@ -165,7 +218,7 @@ public class SubselectRewriter extends ParseNodeRewriter {
            List<ParseNode> rewrittenPostFilterParseNodes =
                    new ArrayList<ParseNode>(postFilterParseNodes.size());
            for(ParseNode postFilterParseNode : postFilterParseNodes) {
-               rewrittenPostFilterParseNodes.add(postFilterParseNode.accept(rewriter));
+               rewrittenPostFilterParseNodes.add(postFilterParseNode.accept(subselectRewriter));
            }
            newWhereParseNode = combine(rewrittenPostFilterParseNodes);
        }
@@ -191,26 +244,124 @@ public class SubselectRewriter extends ParseNodeRewriter {
                subselectStatementToUse.hasSequence(),
                Collections.<SelectStatement> emptyList(),
                subselectStatementToUse.getUdfParseNodes());
-   }
-    
+    }
+
+    /**
+     * If the selectStatement has a DerivedTableNode, pruning column of the
+     * {@link DerivedTableNode#getSelect()}.
+     * @param selectStatement
+     * @param pheonixConnection
+     * @return
+     * @throws SQLException
+     */
+    private static SelectStatement pruneInnerSubselectAliasedNodes(
+            SelectStatement selectStatement,
+            PhoenixConnection pheonixConnection) throws SQLException {
+        TableNode fromTableNode = selectStatement.getFrom();
+        if (fromTableNode == null || !(fromTableNode instanceof DerivedTableNode)) {
+            return selectStatement;
+        }
+
+        DerivedTableNode derivedTableNode = (DerivedTableNode) fromTableNode;
+        SelectStatement subSelectStatement = derivedTableNode.getSelect();
+        if (subSelectStatement.isUnion()) {
+            return selectStatement;
+        }
+        Set<String> referencedColumnNames =
+                ParseNodeUtil.collectReferencedColumnNamesForSingleTable(selectStatement);
+        SelectStatement newSubselectStatement = pruneSelectAliasedNodes(subSelectStatement, referencedColumnNames, pheonixConnection);
+        if(newSubselectStatement != subSelectStatement) {
+            return NODE_FACTORY.select(
+                    selectStatement,
+                    NODE_FACTORY.derivedTable(derivedTableNode.getAlias(), newSubselectStatement));
+        }
+        return selectStatement;
+    }
+
+    /**
+     * Pruning selectAliasedNodes according to referencedColumnNames,
+     * Note: the selectStatement is supposed to be a {@link DerivedTableNode} of an Outer SelectStatement,
+     * so according to {@link FromCompiler.MultiTableColumnResolver#visit(DerivedTableNode)},
+     * wildcard in selectAliasedNode is not supported.
+     * @param selectStatement
+     * @param referencedColumnNames
+     * @param phoenixConnection
+     * @return
+     * @throws SQLException
+     */
+    public static SelectStatement pruneSelectAliasedNodes(
+            SelectStatement selectStatement,
+            Set<String> referencedColumnNames,
+            PhoenixConnection phoenixConnection) throws SQLException {
+
+        if(referencedColumnNames == null || referencedColumnNames.isEmpty()) {
+            return selectStatement;
+        }
+        if(selectStatement.isDistinct()) {
+            return selectStatement;
+        }
+        /**
+         * We must resolve the inner alias at first before column pruning, because the resolve may fail
+         * if the column is pruned.
+         */
+        selectStatement = ParseNodeRewriter.resolveInternalAlias(selectStatement, phoenixConnection);
+        List<AliasedNode> selectAliasedNodes = selectStatement.getSelect();
+        List<AliasedNode> newSelectAliasedNodes = new ArrayList<AliasedNode>(selectAliasedNodes.size());
+        for (AliasedNode selectAliasedNode : selectAliasedNodes) {
+            String aliasName = selectAliasedNode.getAlias();
+            ParseNode aliasParseNode = selectAliasedNode.getNode();
+            if (aliasParseNode instanceof WildcardParseNode ||
+                aliasParseNode instanceof TableWildcardParseNode ||
+                aliasParseNode instanceof FamilyWildcardParseNode) {
+                /**
+                 * Wildcard in subselect is not supported.
+                 * See also {@link FromCompiler.MultiTableColumnResolver#visit(DerivedTableNode)}.
+                 */
+                throw new SQLFeatureNotSupportedException("Wildcard in subqueries not supported.");
+            }
+            if (aliasName == null) {
+                aliasName = aliasParseNode.getAlias();
+            }
+            if(aliasName != null) {
+                aliasName = SchemaUtil.normalizeIdentifier(aliasName);
+                if(referencedColumnNames.contains(aliasName)) {
+                    newSelectAliasedNodes.add(selectAliasedNode);
+                }
+            }
+        }
+
+        if(newSelectAliasedNodes.isEmpty() || newSelectAliasedNodes.equals(selectAliasedNodes)) {
+            //if the newSelectAliasedNodes.isEmpty(), the outer select may be wildcard or constant,
+            //so remain the same.
+            return selectStatement;
+        }
+        return NODE_FACTORY.select(
+                        selectStatement,
+                        selectStatement.isDistinct(),
+                        newSelectAliasedNodes);
+    }
+
     public static SelectStatement flatten(SelectStatement select, PhoenixConnection connection) throws SQLException {
         TableNode from = select.getFrom();
         while (from != null && from instanceof DerivedTableNode) {
             DerivedTableNode derivedTable = (DerivedTableNode) from;
             SelectStatement subselect = derivedTable.getSelect();
-            if (subselect.isUnion())
+            if (subselect.isUnion()) {
                 break;
+            }
             ColumnResolver resolver = FromCompiler.getResolverForQuery(subselect, connection);
             SubselectRewriter rewriter = new SubselectRewriter(resolver, subselect.getSelect(), derivedTable.getAlias());
             SelectStatement ret = rewriter.flatten(select, subselect);
-            if (ret == select)
+            if (ret == select) {
                 break;
-            
+            }
             select = ret;
             from = select.getFrom();
         }
-        
-        return select;
+        /**
+         * Pruning column for subselect after flatten.
+         */
+        return pruneInnerSubselectAliasedNodes(select, connection);
     }
     
     private SubselectRewriter(ColumnResolver resolver, List<AliasedNode> aliasedNodes, String tableAlias) {
@@ -421,27 +572,6 @@ public class SubselectRewriter extends ParseNodeRewriter {
         return stmt;
     }
 
-    private SelectStatement applyPreFilters(SelectStatement statement, List<ParseNode> preFilterParseNodes) throws SQLException {
-        List<ParseNode> rewrittenPreFilterParseNodes = Lists.<ParseNode>newArrayListWithExpectedSize(preFilterParseNodes.size());
-        for (ParseNode preFilterParseNode : preFilterParseNodes) {
-            rewrittenPreFilterParseNodes.add(preFilterParseNode.accept(this));
-        }
-        
-        if (statement.getGroupBy().isEmpty()) {
-            ParseNode where = statement.getWhere();
-            if (where != null) {
-                rewrittenPreFilterParseNodes.add(where);
-            }
-            return NODE_FACTORY.select(statement, combine(rewrittenPreFilterParseNodes));
-        }
-        
-        ParseNode having = statement.getHaving();
-        if (having != null) {
-            rewrittenPreFilterParseNodes.add(having);
-        }
-        return NODE_FACTORY.select(statement, statement.getWhere(), combine(rewrittenPreFilterParseNodes));
-    }
-
     @Override
     public ParseNode visit(ColumnParseNode node) throws SQLException {
         if (node.getTableName() == null)
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
index 7cad7ec..2fc500f 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
@@ -135,13 +135,12 @@ public class QueryOptimizer {
             return getApplicablePlansForSingleFlatQuery(dataPlan, statement, targetColumns, parallelIteratorFactory, stopAtBestPlan);
         }
 
-        ColumnResolver resolver = FromCompiler.getResolverForQuery(select, statement.getConnection());
         Map<TableRef, QueryPlan> dataPlans = null;
-
         // Find the optimal index plan for each join tables in a join query or a
         // non-correlated sub-query, then rewrite the query with found index tables.
         if (select.isJoin()
                 || (select.getWhere() != null && select.getWhere().hasSubquery())) {
+            ColumnResolver resolver = FromCompiler.getResolverForQuery(select, statement.getConnection());
             JoinCompiler.JoinTable join = JoinCompiler.compile(statement, select, resolver);
             Map<TableRef, TableRef> replacement = null;
             for (JoinCompiler.Table table : join.getAllTables()) {
@@ -179,15 +178,21 @@ public class QueryOptimizer {
             if (replacement != null) {
                 select = rewriteQueryWithIndexReplacement(
                         statement.getConnection(), resolver, select, replacement);
-                resolver = FromCompiler.getResolverForQuery(select, statement.getConnection());
             }
         }
 
         // Re-compile the plan with option "optimizeSubquery" turned on, so that enclosed
         // sub-queries can be optimized recursively.
-        QueryCompiler compiler = new QueryCompiler(statement, select, resolver,
-                targetColumns, parallelIteratorFactory, dataPlan.getContext().getSequenceManager(),
-                true, true, dataPlans);
+        QueryCompiler compiler = new QueryCompiler(
+                statement,
+                select,
+                FromCompiler.getResolverForQuery(select, statement.getConnection()),
+                targetColumns,
+                parallelIteratorFactory,
+                dataPlan.getContext().getSequenceManager(),
+                true,
+                true,
+                dataPlans);
         return Collections.singletonList(compiler.compile());
     }
 
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
index 7a31110..473383d 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
@@ -18,14 +18,19 @@
 package org.apache.phoenix.parse;
 
 import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.phoenix.compile.ColumnResolver;
+import org.apache.phoenix.compile.FromCompiler;
+import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.schema.AmbiguousColumnException;
 import org.apache.phoenix.schema.ColumnNotFoundException;
 import org.apache.phoenix.schema.ColumnRef;
+import org.apache.phoenix.util.SchemaUtil;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -50,6 +55,26 @@ public class ParseNodeRewriter extends TraverseAllParseNodeVisitor<ParseNode> {
     }
 
     /**
+     * <pre>
+     * Resolve the inner alias for the selectStament.
+     * For following sql:
+     *   select aid,sum(age) agesum from merge where age >=11 and age<=33 group by aid order by agesum
+     * "agesum" is an alias of "sum(age)", so for this method, the above sql is rewritten to:
+     *   select aid,sum(age) agesum from merge where age >=11 and age<=33 group by aid order by sum(age)
+     * </pre>
+     * @param selectStament
+     * @param phoenixConnection
+     * @return
+     * @throws SQLException
+     */
+    public static SelectStatement resolveInternalAlias(
+            SelectStatement selectStament, PhoenixConnection phoenixConnection) throws SQLException {
+         ColumnResolver columnResolver = FromCompiler.getResolverForQuery(selectStament, phoenixConnection);
+         ParseNodeRewriter parseNodeRewriter =
+                 new ParseNodeRewriter(columnResolver, selectStament.getSelect().size());
+         return ParseNodeRewriter.rewrite(selectStament, parseNodeRewriter);
+    }
+    /**
      * Rewrite the select statement by switching any constants to the right hand side
      * of the expression.
      * @param statement the select statement
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/ParseNodeUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/ParseNodeUtil.java
new file mode 100644
index 0000000..1c6ca36
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/ParseNodeUtil.java
@@ -0,0 +1,142 @@
+/*
+ * 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.util;
+
+import java.sql.SQLException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.phoenix.parse.ColumnParseNode;
+import org.apache.phoenix.parse.FamilyWildcardParseNode;
+import org.apache.phoenix.parse.OrderByNode;
+import org.apache.phoenix.parse.ParseNodeVisitor;
+import org.apache.phoenix.parse.SelectStatement;
+import org.apache.phoenix.parse.AliasedNode;
+import org.apache.phoenix.parse.ParseNode;
+import org.apache.phoenix.parse.StatelessTraverseAllParseNodeVisitor;
+import org.apache.phoenix.parse.TableWildcardParseNode;
+import org.apache.phoenix.parse.WildcardParseNode;
+
+public class ParseNodeUtil {
+
+    /**
+     * Apply the {@link ParseNodeVisitor} to every part of the {@link SelectStatement}.
+     * @param selectStatement
+     * @param parseNodeVisitor
+     * @throws SQLException
+     */
+    public static <T> void applyParseNodeVisitor(SelectStatement selectStatement, ParseNodeVisitor<T> parseNodeVisitor) throws SQLException {
+        applyParseNodeVisitor(selectStatement, parseNodeVisitor, true);
+    }
+
+    /**
+     * Apply the {@link ParseNodeVisitor} to every part of the {@link SelectStatement}.
+     * @param selectStatement
+     * @param parseNodeVisitor
+     * @param applyWhere
+     * @throws SQLException
+     */
+    public static <T> void applyParseNodeVisitor(
+            SelectStatement selectStatement,
+            ParseNodeVisitor<T> parseNodeVisitor,
+            boolean applyWhere) throws SQLException {
+
+        for (AliasedNode selectAliasedNode : selectStatement.getSelect()) {
+            selectAliasedNode.getNode().accept(parseNodeVisitor);
+        }
+
+        if (selectStatement.getGroupBy() != null) {
+            for (ParseNode groupByParseNode : selectStatement.getGroupBy()) {
+                groupByParseNode.accept(parseNodeVisitor);
+            }
+        }
+
+        if (selectStatement.getHaving() != null) {
+            selectStatement.getHaving().accept(parseNodeVisitor);
+        }
+
+        if (selectStatement.getOrderBy() != null) {
+            for (OrderByNode orderByNode : selectStatement.getOrderBy()) {
+                orderByNode.getNode().accept(parseNodeVisitor);
+            }
+        }
+
+        if(applyWhere && selectStatement.getWhere() != null) {
+            selectStatement.getWhere().accept(parseNodeVisitor);
+        }
+    }
+
+    /**
+     * Collect referenced columnNames in selectStatement, the selectStatement is a single table query, not a join.
+     * @param selectStatement
+     * @return
+     * @throws SQLException
+     */
+    public static Set<String> collectReferencedColumnNamesForSingleTable(SelectStatement selectStatement) throws SQLException{
+        SingleTableCollectColumnNameParseNodeVisitor collectColumnNameParseNodeVisitor =
+                new SingleTableCollectColumnNameParseNodeVisitor();
+        applyParseNodeVisitor(selectStatement, collectColumnNameParseNodeVisitor);
+        boolean isWildcard = collectColumnNameParseNodeVisitor.isWildcard();
+        if(isWildcard) {
+            return null;
+        }
+        return collectColumnNameParseNodeVisitor.getReferenceColumnNames();
+    }
+
+    private static class SingleTableCollectColumnNameParseNodeVisitor extends StatelessTraverseAllParseNodeVisitor {
+        private final Set<String> referenceColumnNames;
+        private boolean wildcard = false;
+
+        public SingleTableCollectColumnNameParseNodeVisitor() {
+            this.referenceColumnNames = new HashSet<String>();
+        }
+
+        public Set<String> getReferenceColumnNames() {
+            return this.referenceColumnNames;
+        }
+
+        public boolean isWildcard() {
+            return wildcard;
+        }
+
+        @Override
+        public Void visit(ColumnParseNode columnParseNode) throws SQLException {
+            String normalizedColumnName = SchemaUtil.getNormalizedColumnName(columnParseNode);
+            referenceColumnNames.add(normalizedColumnName);
+            return null;
+        }
+
+        @Override
+        public Void visit(WildcardParseNode node) throws SQLException {
+            this.wildcard = true;
+            return null;
+        }
+
+        @Override
+        public Void visit(TableWildcardParseNode node) throws SQLException {
+            this.wildcard = true;
+            return null;
+        }
+
+        @Override
+        public Void visit(FamilyWildcardParseNode node) throws SQLException {
+            this.wildcard = true;
+            return null;
+        }
+    }
+}
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/SchemaUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/SchemaUtil.java
index b2140e7..9a23886 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/SchemaUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/SchemaUtil.java
@@ -62,7 +62,9 @@ import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+import org.apache.phoenix.parse.ColumnParseNode;
 import org.apache.phoenix.parse.LiteralParseNode;
+import org.apache.phoenix.parse.NamedNode;
 import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.query.QueryServices;
@@ -1198,6 +1200,15 @@ public class SchemaUtil {
     public static boolean isLogTable(String schemaName, String tableName) {
         return PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA.equals(schemaName) && PhoenixDatabaseMetaData.SYSTEM_LOG_TABLE.equals(tableName);
     }
-    
 
+    /**
+     * Return the normalized columnName for {@link ColumnParseNode},
+     * because {@link ColumnParseNode} ctor have already called {SchemaUtil#normalizeIdentifier}
+     * for {@link ColumnParseNode#getName},so just return {@link ColumnParseNode#getName}.
+     * @param columnParseNode
+     * @return
+     */
+    public static String getNormalizedColumnName(ColumnParseNode columnParseNode) {
+        return columnParseNode.getName();
+    }
 }
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/JoinQueryCompilerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/JoinQueryCompilerTest.java
index d091e69..14c22ea 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/JoinQueryCompilerTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/JoinQueryCompilerTest.java
@@ -41,6 +41,7 @@ import org.apache.phoenix.parse.SelectStatement;
 import org.apache.phoenix.query.BaseConnectionlessQueryTest;
 import org.apache.phoenix.util.PropertiesUtil;
 import org.apache.phoenix.util.QueryUtil;
+import org.apache.phoenix.util.TestUtil;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -113,66 +114,52 @@ public class JoinQueryCompilerTest extends BaseConnectionlessQueryTest {
                 + "WHERE t1.\"item_id\" = '0000000001' AND t2.\"item_id\" = '0000000002' AND t3.\"item_id\" = '0000000003'";
 
         String query = String.format(queryTemplate, "INNER", "INNER");
-        JoinTable joinTable = getJoinTable(query, pconn);
+        JoinTable joinTable = TestUtil.getJoinTable(query, pconn);
         assertEquals(1, joinTable.getLeftTable().getPreFilters().size());
         assertEquals(1, joinTable.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable().getPreFilters().size());
         assertEquals(1, joinTable.getJoinSpecs().get(1).getRhsJoinTable().getLeftTable().getPreFilters().size());
 
         query = String.format(queryTemplate, "INNER", "LEFT");
-        joinTable = getJoinTable(query, pconn);
+        joinTable = TestUtil.getJoinTable(query, pconn);
         assertEquals(1, joinTable.getLeftTable().getPreFilters().size());
         assertEquals(1, joinTable.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable().getPreFilters().size());
         assertEquals(0, joinTable.getJoinSpecs().get(1).getRhsJoinTable().getLeftTable().getPreFilters().size());
 
         query = String.format(queryTemplate, "INNER", "RIGHT");
-        joinTable = getJoinTable(query, pconn);
+        joinTable = TestUtil.getJoinTable(query, pconn);
         assertEquals(0, joinTable.getLeftTable().getPreFilters().size());
         assertEquals(0, joinTable.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable().getPreFilters().size());
         assertEquals(1, joinTable.getJoinSpecs().get(1).getRhsJoinTable().getLeftTable().getPreFilters().size());
 
         query = String.format(queryTemplate, "LEFT", "INNER");
-        joinTable = getJoinTable(query, pconn);
+        joinTable = TestUtil.getJoinTable(query, pconn);
         assertEquals(1, joinTable.getLeftTable().getPreFilters().size());
         assertEquals(0, joinTable.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable().getPreFilters().size());
         assertEquals(1, joinTable.getJoinSpecs().get(1).getRhsJoinTable().getLeftTable().getPreFilters().size());
 
         query = String.format(queryTemplate, "LEFT", "LEFT");
-        joinTable = getJoinTable(query, pconn);
+        joinTable = TestUtil.getJoinTable(query, pconn);
         assertEquals(1, joinTable.getLeftTable().getPreFilters().size());
         assertEquals(0, joinTable.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable().getPreFilters().size());
         assertEquals(0, joinTable.getJoinSpecs().get(1).getRhsJoinTable().getLeftTable().getPreFilters().size());
 
         query = String.format(queryTemplate, "LEFT", "RIGHT");
-        joinTable = getJoinTable(query, pconn);
+        joinTable = TestUtil.getJoinTable(query, pconn);
         assertEquals(0, joinTable.getLeftTable().getPreFilters().size());
         assertEquals(0, joinTable.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable().getPreFilters().size());
         assertEquals(1, joinTable.getJoinSpecs().get(1).getRhsJoinTable().getLeftTable().getPreFilters().size());
 
         query = String.format(queryTemplate, "RIGHT", "INNER");
-        joinTable = getJoinTable(query, pconn);
+        joinTable = TestUtil.getJoinTable(query, pconn);
         assertEquals(0, joinTable.getLeftTable().getPreFilters().size());
         assertEquals(1, joinTable.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable().getPreFilters().size());
         assertEquals(1, joinTable.getJoinSpecs().get(1).getRhsJoinTable().getLeftTable().getPreFilters().size());
 
         query = String.format(queryTemplate, "RIGHT", "RIGHT");
-        joinTable = getJoinTable(query, pconn);
+        joinTable = TestUtil.getJoinTable(query, pconn);
         assertEquals(0, joinTable.getLeftTable().getPreFilters().size());
         assertEquals(0, joinTable.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable().getPreFilters().size());
         assertEquals(1, joinTable.getJoinSpecs().get(1).getRhsJoinTable().getLeftTable().getPreFilters().size());
     }
-    
-    private static JoinTable getJoinTable(String query, PhoenixConnection connection) throws SQLException {
-        SQLParser parser = new SQLParser(query);
-        SelectStatement select = SubselectRewriter.flatten(parser.parseQuery(), connection);
-        ColumnResolver resolver = FromCompiler.getResolverForQuery(select, connection);
-        select = StatementNormalizer.normalize(select, resolver);
-        SelectStatement transformedSelect = SubqueryRewriter.transform(select, resolver, connection);
-        if (transformedSelect != select) {
-            resolver = FromCompiler.getResolverForQuery(transformedSelect, connection);
-            select = StatementNormalizer.normalize(transformedSelect, resolver);
-        }
-        PhoenixStatement stmt = connection.createStatement().unwrap(PhoenixStatement.class);
-        return JoinCompiler.compile(stmt, select, resolver);        
-    }
 }
 
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 340c7c8..e6337fa 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
@@ -50,6 +50,8 @@ import org.apache.hadoop.hbase.filter.Filter;
 import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
 import org.apache.hadoop.hbase.filter.PageFilter;
 import org.apache.hadoop.hbase.util.Bytes;
+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.coprocessor.BaseScannerRegionObserver;
 import org.apache.phoenix.exception.SQLExceptionCode;
@@ -64,6 +66,7 @@ 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.execute.UnnestArrayPlan;
 import org.apache.phoenix.execute.visitor.QueryPlanVisitor;
@@ -5330,7 +5333,7 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
 
             ClientScanPlan rhsOuterPlan=(ClientScanPlan)((TupleProjectionPlan)(sortMergeJoinPlan.getRhsPlan())).getDelegate();
             String tableAlias = rhsOuterPlan.getTableRef().getTableAlias();
-            String rewrittenSql = "SELECT "+tableAlias+".BID,"+tableAlias+".CODE FROM (SELECT BID,CODE FROM MERGE2  ORDER BY CODE LIMIT 1) "+tableAlias+" WHERE "+tableAlias+".CODE > 50 ORDER BY "+tableAlias+".BID";
+            String rewrittenSql = "SELECT "+tableAlias+".BID BID,"+tableAlias+".CODE CODE FROM (SELECT BID,CODE FROM MERGE2  ORDER BY CODE LIMIT 1) "+tableAlias+" WHERE "+tableAlias+".CODE > 50 ORDER BY "+tableAlias+".BID";
             assertTrue(rhsOuterPlan.getStatement().toString().equals(rewrittenSql));
 
             orderBy=rhsOuterPlan.getOrderBy();
@@ -5362,7 +5365,7 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
 
             rhsOuterPlan=(ClientScanPlan)((TupleProjectionPlan)(sortMergeJoinPlan.getRhsPlan())).getDelegate();
             tableAlias = rhsOuterPlan.getTableRef().getTableAlias();
-            rewrittenSql = "SELECT "+tableAlias+".BID,"+tableAlias+".CODESUM FROM (SELECT BID, SUM(CODE) CODESUM FROM MERGE2  GROUP BY BID ORDER BY CODESUM LIMIT 1) "+tableAlias+" WHERE "+tableAlias+".CODESUM > 50 ORDER BY "+tableAlias+".BID";
+            rewrittenSql = "SELECT "+tableAlias+".BID BID,"+tableAlias+".CODESUM CODESUM FROM (SELECT BID, SUM(CODE) CODESUM FROM MERGE2  GROUP BY BID ORDER BY  SUM(CODE) LIMIT 1) "+tableAlias+" WHERE "+tableAlias+".CODESUM > 50 ORDER BY "+tableAlias+".BID";
             assertTrue(rhsOuterPlan.getStatement().toString().equals(rewrittenSql));
 
             orderBy=rhsOuterPlan.getOrderBy();
@@ -5394,7 +5397,7 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
 
             lhsOuterPlan=(ClientScanPlan)((TupleProjectionPlan)(sortMergeJoinPlan.getLhsPlan())).getDelegate();
             tableAlias = lhsOuterPlan.getTableRef().getTableAlias();
-            rewrittenSql = "SELECT "+tableAlias+".AID,"+tableAlias+".CODE FROM (SELECT A.AID,B.CODE FROM MERGE1 A  Inner JOIN MERGE2 B  ON (A.AID = B.BID) WHERE (B.CODE >= 44 AND B.CODE <= 66) ORDER BY B.CODE LIMIT 3) "+
+            rewrittenSql = "SELECT "+tableAlias+".AID AID,"+tableAlias+".CODE CODE FROM (SELECT A.AID,B.CODE FROM MERGE1 A  Inner JOIN MERGE2 B  ON (A.AID = B.BID) WHERE (B.CODE >= 44 AND B.CODE <= 66) ORDER BY B.CODE LIMIT 3) "+
                            tableAlias+" WHERE "+tableAlias+".CODE > 50 ORDER BY "+tableAlias+".AID";
             assertTrue(lhsOuterPlan.getStatement().toString().equals(rewrittenSql));
 
@@ -5429,7 +5432,7 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
 
             lhsOuterPlan=(ClientScanPlan)((TupleProjectionPlan)(sortMergeJoinPlan.getLhsPlan())).getDelegate();
             tableAlias = lhsOuterPlan.getTableRef().getTableAlias();
-            rewrittenSql = "SELECT "+tableAlias+".AID,"+tableAlias+".CODESUM FROM (SELECT A.AID, SUM(B.CODE) CODESUM FROM MERGE1 A  Inner JOIN MERGE2 B  ON (A.AID = B.BID) WHERE (B.CODE >= 44 AND B.CODE <= 66) GROUP BY A.AID ORDER BY CODESUM LIMIT 3) "+tableAlias+
+            rewrittenSql = "SELECT "+tableAlias+".AID AID,"+tableAlias+".CODESUM CODESUM FROM (SELECT A.AID, SUM(B.CODE) CODESUM FROM MERGE1 A  Inner JOIN MERGE2 B  ON (A.AID = B.BID) WHERE (B.CODE >= 44 AND B.CODE <= 66) GROUP BY A.AID ORDER BY  SUM(B.CODE) LIMIT 3) "+tableAlias+
                            " WHERE "+tableAlias+".CODESUM >= 40 ORDER BY "+tableAlias+".AID";
             assertTrue(lhsOuterPlan.getStatement().toString().equals(rewrittenSql));
 
@@ -5444,7 +5447,7 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
 
             rhsOuterPlan=(ClientScanPlan)((TupleProjectionPlan)(sortMergeJoinPlan.getRhsPlan())).getDelegate();
             tableAlias = rhsOuterPlan.getTableRef().getTableAlias();
-            rewrittenSql = "SELECT "+tableAlias+".AID,"+tableAlias+".REGIONSUM FROM (SELECT A.AID, SUM(C.REGION) REGIONSUM FROM MERGE1 A  Inner JOIN MERGE3 C  ON (A.AID = C.CID) WHERE (C.REGION >= 77 AND C.REGION <= 99) GROUP BY A.AID ORDER BY REGIONSUM DESC LIMIT 2) "+tableAlias+
+            rewrittenSql = "SELECT "+tableAlias+".AID AID,"+tableAlias+".REGIONSUM REGIONSUM FROM (SELECT A.AID, SUM(C.REGION) REGIONSUM FROM MERGE1 A  Inner JOIN MERGE3 C  ON (A.AID = C.CID) WHERE (C.REGION >= 77 AND C.REGION <= 99) GROUP BY A.AID ORDER BY  SUM(C.REGION) DESC LIMIT 2) "+tableAlias+
                            " WHERE "+tableAlias+".REGIONSUM >= 90 ORDER BY "+tableAlias+".AID";
             assertTrue(rhsOuterPlan.getStatement().toString().equals(rewrittenSql));
 
@@ -5472,7 +5475,7 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
 
             lhsOuterPlan = (ClientScanPlan)(((TupleProjectionPlan)sortMergeJoinPlan.getLhsPlan()).getDelegate());
             tableAlias = lhsOuterPlan.getTableRef().getTableAlias();
-            rewrittenSql = "SELECT "+tableAlias+".AID,"+tableAlias+".CODESUM FROM (SELECT A.AID, SUM(B.CODE) CODESUM FROM MERGE1 A  Inner JOIN MERGE2 B  ON (A.AID = B.BID) WHERE (B.CODE >= 44 AND B.CODE <= 66) GROUP BY A.AID ORDER BY A.AID,CODESUM LIMIT 3) "+tableAlias+
+            rewrittenSql = "SELECT "+tableAlias+".AID AID,"+tableAlias+".CODESUM CODESUM FROM (SELECT A.AID, SUM(B.CODE) CODESUM FROM MERGE1 A  Inner JOIN MERGE2 B  ON (A.AID = B.BID) WHERE (B.CODE >= 44 AND B.CODE <= 66) GROUP BY A.AID ORDER BY A.AID, SUM(B.CODE) LIMIT 3) "+tableAlias+
                            " WHERE "+tableAlias+".CODESUM >= 40";
             assertTrue(lhsOuterPlan.getStatement().toString().equals(rewrittenSql));
 
@@ -5485,7 +5488,7 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
 
             rhsOuterPlan=(ClientScanPlan)((TupleProjectionPlan)(sortMergeJoinPlan.getRhsPlan())).getDelegate();
             tableAlias = rhsOuterPlan.getTableRef().getTableAlias();
-            rewrittenSql = "SELECT "+tableAlias+".AID,"+tableAlias+".REGIONSUM FROM (SELECT A.AID, SUM(C.REGION) REGIONSUM FROM MERGE1 A  Inner JOIN MERGE3 C  ON (A.AID = C.CID) WHERE (C.REGION >= 77 AND C.REGION <= 99) GROUP BY A.AID ORDER BY A.AID DESC,REGIONSUM DESC LIMIT 2) "+tableAlias+
+            rewrittenSql = "SELECT "+tableAlias+".AID AID,"+tableAlias+".REGIONSUM REGIONSUM FROM (SELECT A.AID, SUM(C.REGION) REGIONSUM FROM MERGE1 A  Inner JOIN MERGE3 C  ON (A.AID = C.CID) WHERE (C.REGION >= 77 AND C.REGION <= 99) GROUP BY A.AID ORDER BY A.AID DESC, SUM(C.REGION) DESC LIMIT 2) "+tableAlias+
                            " WHERE "+tableAlias+".REGIONSUM >= 90 ORDER BY "+tableAlias+".AID";
             assertTrue(rhsOuterPlan.getStatement().toString().equals(rewrittenSql));
 
@@ -6185,4 +6188,224 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
         lhsClientScanPlan = (ClientScanPlan)sortMergeJoinPlan.getLhsPlan();
         assertTrue(lhsClientScanPlan.getWhere() == null);
     }
+
+    @Test
+    public void testSubselectColumnPruneForJoinBug5451() throws Exception {
+        PhoenixConnection conn = null;
+        try {
+            conn = DriverManager.getConnection(getUrl()).unwrap(PhoenixConnection.class);
+            String sql = null;
+            QueryPlan queryPlan = null;
+            //testNestedDerivedTable require index with same name be created
+            String tableName = "testA";
+            sql = "create table " + tableName +
+            "   (organization_id char(15) not null, \n" +
+            "    entity_id char(15) not null,\n" +
+            "    a_string varchar(100),\n" +
+            "    b_string varchar(100),\n" +
+            "    a_integer integer,\n" +
+            "    a_date date,\n" +
+            "    a_time time,\n" +
+            "    a_timestamp timestamp,\n" +
+            "    x_decimal decimal(31,10),\n" +
+            "    x_long bigint,\n" +
+            "    x_integer integer,\n" +
+            "    y_integer integer,\n" +
+            "    a_byte tinyint,\n" +
+            "    a_short smallint,\n" +
+            "    a_float float,\n" +
+            "    a_double double,\n" +
+            "    a_unsigned_float unsigned_float,\n" +
+            "    a_unsigned_double unsigned_double\n" +
+            "    CONSTRAINT pk PRIMARY KEY (organization_id, entity_id)\n" +
+            ") ";
+            conn.createStatement().execute(sql);
+
+            //test for subquery
+            sql = "SELECT q.id, q.x10 * 10 FROM " +
+                  "(SELECT t.eid id, t.x + 9 x10, t.astr a, t.bstr b, aint ai, adouble ad FROM "+
+                    "(SELECT entity_id eid, a_string astr, b_string bstr, a_integer aint, a_double adouble, a_byte + 1 x FROM " + tableName + " WHERE a_byte + 1 < 9 limit 2) AS t "+
+                  "ORDER BY b, id limit 3) AS q WHERE q.a = 'a' OR q.b = 'b' OR q.b = 'c'";
+            queryPlan = TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            ClientScanPlan clientScanPlan = (ClientScanPlan)queryPlan;
+            TestUtil.assertSelectStatement(clientScanPlan.getStatement(),
+                    "SELECT Q.ID,(Q.X10 * 10) FROM "+
+                    "(SELECT T.EID ID,(T.X + 9) X10,T.ASTR A,T.BSTR B FROM "+
+                     "(SELECT ENTITY_ID EID,A_STRING ASTR,B_STRING BSTR,A_INTEGER AINT,A_DOUBLE ADOUBLE,(A_BYTE + 1) X FROM TESTA  WHERE (A_BYTE + 1) < 9 LIMIT 2) T "+
+                    "ORDER BY T.BSTR,T.EID LIMIT 3) Q WHERE (Q.A = 'a' OR Q.B = 'b' OR Q.B = 'c')");
+            clientScanPlan =
+                    (ClientScanPlan)((TupleProjectionPlan)clientScanPlan.getDelegate()).getDelegate();
+            TestUtil.assertSelectStatement(clientScanPlan.getStatement(),
+                    "SELECT T.EID ID,(T.X + 9) X10,T.ASTR A,T.BSTR B FROM "+
+                    "(SELECT ENTITY_ID EID,A_STRING ASTR,B_STRING BSTR,(A_BYTE + 1) X FROM TESTA  WHERE (A_BYTE + 1) < 9 LIMIT 2) T "+
+                    "ORDER BY T.BSTR,T.EID LIMIT 3");
+            ScanPlan scanPlan =
+                    (ScanPlan)((TupleProjectionPlan)clientScanPlan.getDelegate()).getDelegate();
+            TestUtil.assertSelectStatement(
+                    scanPlan.getStatement(),
+                    "SELECT ENTITY_ID EID,A_STRING ASTR,B_STRING BSTR,(A_BYTE + 1) X FROM TESTA  WHERE (A_BYTE + 1) < 9 LIMIT 2");
+
+            //test for subquery with wildcard
+            sql = "SELECT * FROM " +
+                  "(SELECT t.eid id, t.x + 9 x10, t.astr a, t.bstr b, aint ai, adouble ad FROM "+
+                    "(SELECT entity_id eid, a_string astr, b_string bstr, a_integer aint, a_double adouble, a_byte + 1 x FROM " + tableName + " WHERE a_byte + 1 < 9 limit 2) AS t "+
+                  "ORDER BY b, id limit 3) AS q WHERE q.a = 'a' OR q.b = 'b' OR q.b = 'c'";
+            queryPlan = TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            clientScanPlan = (ClientScanPlan)queryPlan;
+            TestUtil.assertSelectStatement(clientScanPlan.getStatement(),
+                    "SELECT  *  FROM "+
+                    "(SELECT T.EID ID,(T.X + 9) X10,T.ASTR A,T.BSTR B,AINT AI,ADOUBLE AD FROM "+
+                     "(SELECT ENTITY_ID EID,A_STRING ASTR,B_STRING BSTR,A_INTEGER AINT,A_DOUBLE ADOUBLE,(A_BYTE + 1) X FROM TESTA  WHERE (A_BYTE + 1) < 9 LIMIT 2) T "+
+                    "ORDER BY B,ID LIMIT 3) Q WHERE (Q.A = 'a' OR Q.B = 'b' OR Q.B = 'c')");
+            clientScanPlan = (ClientScanPlan)((TupleProjectionPlan)clientScanPlan.getDelegate()).getDelegate();
+            TestUtil.assertSelectStatement(clientScanPlan.getStatement(),
+                    "SELECT T.EID ID,(T.X + 9) X10,T.ASTR A,T.BSTR B,AINT AI,ADOUBLE AD FROM "+
+                    "(SELECT ENTITY_ID EID,A_STRING ASTR,B_STRING BSTR,A_INTEGER AINT,A_DOUBLE ADOUBLE,(A_BYTE + 1) X FROM TESTA  WHERE (A_BYTE + 1) < 9 LIMIT 2) T "+
+                    "ORDER BY T.BSTR,T.EID LIMIT 3");
+            scanPlan = (ScanPlan)((TupleProjectionPlan)clientScanPlan.getDelegate()).getDelegate();
+            TestUtil.assertSelectStatement(
+                    scanPlan.getStatement(),
+                    "SELECT ENTITY_ID EID,A_STRING ASTR,B_STRING BSTR,A_INTEGER AINT,A_DOUBLE ADOUBLE,(A_BYTE + 1) X FROM TESTA  WHERE (A_BYTE + 1) < 9 LIMIT 2");
+
+            //test for some trival cases of subquery.
+            sql = "SELECT count(*) FROM (SELECT count(*) c FROM "+tableName+" ) AS t";
+            queryPlan = TestUtil.getOptimizeQueryPlan(conn, sql);
+            ClientAggregatePlan clientAggregatePlan = (ClientAggregatePlan)queryPlan;
+            TestUtil.assertSelectStatement(clientAggregatePlan.getStatement(), "SELECT  COUNT(1) FROM (SELECT  COUNT(1) C FROM TESTA ) T");
+            AggregatePlan aggregatePlan =
+                    (AggregatePlan)((TupleProjectionPlan)clientAggregatePlan.getDelegate()).getDelegate();
+            TestUtil.assertSelectStatement(aggregatePlan.getStatement(), "SELECT  COUNT(1) C FROM TESTA");
+
+            sql = "SELECT count(*) FROM (SELECT count(*) c FROM "+tableName+" GROUP BY a_string) AS t";
+            queryPlan = TestUtil.getOptimizeQueryPlan(conn, sql);
+            clientAggregatePlan = (ClientAggregatePlan)queryPlan;
+            TestUtil.assertSelectStatement(
+                    clientAggregatePlan.getStatement(),
+                    "SELECT  COUNT(1) FROM (SELECT  COUNT(1) C FROM TESTA  GROUP BY A_STRING) T");
+            aggregatePlan =
+                    (AggregatePlan)((TupleProjectionPlan)clientAggregatePlan.getDelegate()).getDelegate();
+            TestUtil.assertSelectStatement(
+                    aggregatePlan.getStatement(),
+                    "SELECT  COUNT(1) C FROM TESTA  GROUP BY A_STRING");
+
+            sql = "SELECT 1 FROM (SELECT count(*) c FROM "+tableName+" GROUP BY a_string) AS t";
+            queryPlan = TestUtil.getOptimizeQueryPlan(conn, sql);
+            aggregatePlan = (AggregatePlan)queryPlan;
+            TestUtil.assertSelectStatement(aggregatePlan.getStatement(), "SELECT 1 FROM TESTA  GROUP BY A_STRING");
+
+            sql = "SELECT count(*) FROM (SELECT DISTINCT a_string FROM "+tableName+") AS t";
+            queryPlan = TestUtil.getOptimizeQueryPlan(conn, sql);
+            clientAggregatePlan = (ClientAggregatePlan)queryPlan;
+            TestUtil.assertSelectStatement(clientAggregatePlan.getStatement(), "SELECT  COUNT(1) FROM (SELECT DISTINCT A_STRING FROM TESTA ) T");
+            aggregatePlan =
+                    (AggregatePlan)((TupleProjectionPlan)clientAggregatePlan.getDelegate()).getDelegate();
+            TestUtil.assertSelectStatement(aggregatePlan.getStatement(), "SELECT DISTINCT A_STRING FROM TESTA");
+
+            //test for hash join
+            sql = "SELECT q1.id, q2.id FROM (SELECT t.eid id, t.astr a, t.bstr b FROM (SELECT entity_id eid, a_string astr, b_string bstr, a_byte abyte FROM "+tableName+") AS t WHERE t.abyte >= 8) AS q1"
+                    + " JOIN (SELECT t.eid id, t.astr a, t.bstr b, t.abyte x FROM (SELECT entity_id eid, a_string astr, b_string bstr, a_byte abyte FROM "+tableName+") AS t) AS q2 ON q1.a = q2.b"
+                    + " WHERE q2.x != 5 ORDER BY q1.id, q2.id DESC";
+            JoinTable joinTablesContext = TestUtil.getJoinTable(sql, conn);
+            Table leftmostTableContext = joinTablesContext.getLeftTable();
+            TestUtil.assertSelectStatement(
+                    leftmostTableContext.getSubselect(),
+                    "SELECT ENTITY_ID ID,A_STRING A FROM TESTA  WHERE A_BYTE >= 8");
+            assertTrue(leftmostTableContext.getPreFilters().isEmpty());
+
+            Table rightTableContext = joinTablesContext.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable();
+            TestUtil.assertSelectStatement(rightTableContext.getSubselect(), "SELECT ENTITY_ID ID,B_STRING B FROM TESTA");
+            assertTrue(rightTableContext.getPreFilters().size() == 1);
+            assertTrue(rightTableContext.getPreFilters().get(0).toString().equals("A_BYTE != 5"));
+
+            queryPlan = TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            HashJoinPlan hashJoinPlan = (HashJoinPlan)queryPlan;
+            Scan scan = hashJoinPlan.getContext().getScan();
+            TupleProjector tupleColumnProjector =
+                    TupleProjector.deserializeProjectorFromScan(scan);
+            Expression[] expressions = tupleColumnProjector.getExpressions();
+            assertTrue(expressions.length == 2);
+
+            TestUtil.assertSelectStatement(
+                    hashJoinPlan.getDelegate().getStatement(),
+                    "SELECT Q1.ID,Q2.ID FROM TESTA  WHERE A_BYTE >= 8 ORDER BY Q1.ID,Q2.ID DESC");
+            HashSubPlan[] hashSubPlans = (HashSubPlan[])hashJoinPlan.getSubPlans();
+            assertTrue(hashSubPlans.length == 1);
+            scanPlan =(ScanPlan)((TupleProjectionPlan)(hashSubPlans[0].getInnerPlan())).getDelegate();
+            TestUtil.assertSelectStatement(
+                    scanPlan.getStatement(),
+                    "SELECT ENTITY_ID ID,B_STRING B FROM TESTA  WHERE A_BYTE != 5");
+
+            //test for hash join with wildcard
+            sql = "SELECT * FROM (SELECT t.eid id, t.astr a, t.bstr b FROM (SELECT entity_id eid, a_string astr, b_string bstr, a_byte abyte FROM "+tableName+") AS t WHERE t.abyte >= 8) AS q1"
+                    + " JOIN (SELECT t.eid id, t.astr a, t.bstr b, t.abyte x FROM (SELECT entity_id eid, a_string astr, b_string bstr, a_byte abyte FROM "+tableName+") AS t) AS q2 ON q1.a = q2.b"
+                    + " WHERE q2.x != 5 ORDER BY q1.id, q2.id DESC";
+            joinTablesContext = TestUtil.getJoinTable(sql, conn);
+            leftmostTableContext = joinTablesContext.getLeftTable();
+            TestUtil.assertSelectStatement(
+                    leftmostTableContext.getSubselect(),
+                    "SELECT ENTITY_ID ID,A_STRING A,B_STRING B FROM TESTA  WHERE A_BYTE >= 8");
+            assertTrue(leftmostTableContext.getPreFilters().isEmpty());
+
+            rightTableContext = joinTablesContext.getJoinSpecs().get(0).getRhsJoinTable().getLeftTable();
+            TestUtil.assertSelectStatement(
+                    rightTableContext.getSubselect(),
+                    "SELECT ENTITY_ID ID,A_STRING A,B_STRING B,A_BYTE X FROM TESTA");
+            assertTrue(rightTableContext.getPreFilters().size() == 1);
+            assertTrue(rightTableContext.getPreFilters().get(0).toString().equals("A_BYTE != 5"));
+
+            queryPlan = TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            hashJoinPlan = (HashJoinPlan)queryPlan;
+            scan = hashJoinPlan.getContext().getScan();
+            tupleColumnProjector =
+                    TupleProjector.deserializeProjectorFromScan(scan);
+            expressions = tupleColumnProjector.getExpressions();
+            assertTrue(expressions.length == 3);
+
+            TestUtil.assertSelectStatement(
+                    hashJoinPlan.getDelegate().getStatement(),
+                    "SELECT Q1.*,Q2.* FROM TESTA  WHERE A_BYTE >= 8 ORDER BY Q1.ID,Q2.ID DESC");
+            hashSubPlans = (HashSubPlan[])hashJoinPlan.getSubPlans();
+            assertTrue(hashSubPlans.length == 1);
+            scanPlan = (ScanPlan)((TupleProjectionPlan)(hashSubPlans[0].getInnerPlan())).getDelegate();
+            TestUtil.assertSelectStatement(
+                    scanPlan.getStatement(),
+                    "SELECT ENTITY_ID ID,A_STRING A,B_STRING B,A_BYTE X FROM TESTA  WHERE A_BYTE != 5");
+
+            //test for sortmergejoin
+            sql = "SELECT /*+ USE_SORT_MERGE_JOIN */ q1.id, q2.id FROM " +
+                  "(SELECT t.eid id, t.astr a, t.bstr b FROM (SELECT entity_id eid, a_string astr, b_string bstr, a_byte abyte FROM "+tableName+") AS t WHERE t.abyte >= 8) AS q1 " +
+                  "JOIN (SELECT t.eid id, t.astr a, t.bstr b, t.abyte x FROM (SELECT entity_id eid, a_string astr, b_string bstr, a_byte abyte FROM "+tableName+") AS t) AS q2 "+
+                  "ON q1.a = q2.b WHERE q2.x != 5 ORDER BY q1.id, q2.id DESC";
+            queryPlan = TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            clientScanPlan = (ClientScanPlan)queryPlan;
+            SortMergeJoinPlan sortMergeJoinPlan = (SortMergeJoinPlan)clientScanPlan.getDelegate();
+            ScanPlan lhsPlan =
+                    (ScanPlan)((TupleProjectionPlan)sortMergeJoinPlan.getLhsPlan()).getDelegate();
+            TestUtil.assertSelectStatement(
+                    lhsPlan.getStatement(),
+                    "SELECT ENTITY_ID ID,A_STRING A FROM TESTA  WHERE A_BYTE >= 8 ORDER BY A_STRING");
+            ScanPlan rhsPlan =
+                    (ScanPlan)((TupleProjectionPlan)sortMergeJoinPlan.getRhsPlan()).getDelegate();
+            TestUtil.assertSelectStatement(
+                    rhsPlan.getStatement(),
+                    "SELECT ENTITY_ID ID,B_STRING B FROM TESTA  WHERE A_BYTE != 5 ORDER BY B_STRING");
+
+            //test for sortmergejoin with wildcard
+            sql = "SELECT /*+ USE_SORT_MERGE_JOIN */ * FROM "+
+                  "(SELECT t.eid id, t.astr a, t.bstr b FROM (SELECT entity_id eid, a_string astr, b_string bstr, a_byte abyte FROM "+tableName+") AS t WHERE t.abyte >= 8) AS q1 "+
+                  "JOIN (SELECT t.eid id, t.astr a, t.bstr b, t.abyte x FROM (SELECT entity_id eid, a_string astr, b_string bstr, a_byte abyte FROM "+tableName+") AS t) AS q2 "+
+                  "ON q1.a = q2.b WHERE q2.x != 5 ORDER BY q1.id, q2.id DESC";
+            queryPlan = TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            clientScanPlan = (ClientScanPlan)queryPlan;
+            sortMergeJoinPlan = (SortMergeJoinPlan)clientScanPlan.getDelegate();
+            lhsPlan = (ScanPlan)((TupleProjectionPlan)sortMergeJoinPlan.getLhsPlan()).getDelegate();
+            TestUtil.assertSelectStatement(lhsPlan.getStatement(),
+                    "SELECT ENTITY_ID ID,A_STRING A,B_STRING B FROM TESTA  WHERE A_BYTE >= 8 ORDER BY A_STRING");
+            rhsPlan = (ScanPlan)((TupleProjectionPlan)sortMergeJoinPlan.getRhsPlan()).getDelegate();
+            TestUtil.assertSelectStatement(rhsPlan.getStatement(),
+                   "SELECT ENTITY_ID ID,A_STRING A,B_STRING B,A_BYTE X FROM TESTA  WHERE A_BYTE != 5 ORDER BY B_STRING");
+        } finally {
+            conn.close();
+        }
+    }
 }
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java b/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
index fbe047c..5662e4c 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
@@ -72,9 +72,16 @@ import org.apache.hadoop.hbase.ipc.BlockingRpcCallback;
 import org.apache.hadoop.hbase.ipc.ServerRpcController;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.phoenix.compile.AggregationManager;
+import org.apache.phoenix.compile.ColumnResolver;
+import org.apache.phoenix.compile.FromCompiler;
+import org.apache.phoenix.compile.JoinCompiler;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.compile.SequenceManager;
 import org.apache.phoenix.compile.StatementContext;
+import org.apache.phoenix.compile.StatementNormalizer;
+import org.apache.phoenix.compile.SubqueryRewriter;
+import org.apache.phoenix.compile.SubselectRewriter;
+import org.apache.phoenix.compile.JoinCompiler.JoinTable;
 import org.apache.phoenix.coprocessor.generated.MetaDataProtos.ClearCacheRequest;
 import org.apache.phoenix.coprocessor.generated.MetaDataProtos.ClearCacheResponse;
 import org.apache.phoenix.coprocessor.generated.MetaDataProtos.MetaDataService;
@@ -104,6 +111,9 @@ import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
 import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
 import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.parse.FilterableStatement;
+import org.apache.phoenix.parse.SQLParser;
+import org.apache.phoenix.parse.SelectStatement;
 import org.apache.phoenix.parse.LikeParseNode.LikeType;
 import org.apache.phoenix.query.ConnectionQueryServices;
 import org.apache.phoenix.query.KeyRange;
@@ -1166,4 +1176,22 @@ public class TestUtil {
         }
         return false;
     }
+
+    public static JoinTable getJoinTable(String query, PhoenixConnection connection) throws SQLException {
+        SQLParser parser = new SQLParser(query);
+        SelectStatement select = SubselectRewriter.flatten(parser.parseQuery(), connection);
+        ColumnResolver resolver = FromCompiler.getResolverForQuery(select, connection);
+        select = StatementNormalizer.normalize(select, resolver);
+        SelectStatement transformedSelect = SubqueryRewriter.transform(select, resolver, connection);
+        if (transformedSelect != select) {
+            resolver = FromCompiler.getResolverForQuery(transformedSelect, connection);
+            select = StatementNormalizer.normalize(transformedSelect, resolver);
+        }
+        PhoenixStatement stmt = connection.createStatement().unwrap(PhoenixStatement.class);
+        return JoinCompiler.compile(stmt, select, resolver);
+    }
+
+    public static void assertSelectStatement(FilterableStatement selectStatement , String sql) {
+        assertTrue(selectStatement.toString().trim().equals(sql));
+    }
 }