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));
+ }
}