Repository: phoenix Updated Branches: refs/heads/master 719eaf07a -> 909d97596
http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/compile/SubqueryRewriter.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/SubqueryRewriter.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/SubqueryRewriter.java new file mode 100644 index 0000000..42d060f --- /dev/null +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/SubqueryRewriter.java @@ -0,0 +1,401 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.phoenix.compile; + +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Collections; +import java.util.List; + +import org.apache.hadoop.hbase.filter.CompareFilter; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.jdbc.PhoenixConnection; +import org.apache.phoenix.parse.AliasedNode; +import org.apache.phoenix.parse.AndParseNode; +import org.apache.phoenix.parse.BooleanParseNodeVisitor; +import org.apache.phoenix.parse.ColumnParseNode; +import org.apache.phoenix.parse.ComparisonParseNode; +import org.apache.phoenix.parse.CompoundParseNode; +import org.apache.phoenix.parse.ExistsParseNode; +import org.apache.phoenix.parse.InParseNode; +import org.apache.phoenix.parse.JoinTableNode.JoinType; +import org.apache.phoenix.parse.LiteralParseNode; +import org.apache.phoenix.parse.ParseNode; +import org.apache.phoenix.parse.ParseNodeFactory; +import org.apache.phoenix.parse.ParseNodeRewriter; +import org.apache.phoenix.parse.RowValueConstructorParseNode; +import org.apache.phoenix.parse.SelectStatement; +import org.apache.phoenix.parse.StatelessTraverseAllParseNodeVisitor; +import org.apache.phoenix.parse.SubqueryParseNode; +import org.apache.phoenix.parse.TableName; +import org.apache.phoenix.parse.TableNode; +import org.apache.phoenix.schema.ColumnFamilyNotFoundException; +import org.apache.phoenix.schema.ColumnNotFoundException; + +import com.google.common.collect.Lists; + +/* + * Class for rewriting where-clause sub-queries into join queries. + * + * If the where-clause sub-query is one of those top-node conditions (being + * the only condition node or direct descendant of AND nodes), we convert the + * sub-query directly into semi-joins, anti-joins or inner-joins, and meanwhile + * remove the original condition node from the where clause. + * Otherwise, we convert the sub-query into left-joins and change the original + * condition node into a null test of a join table field (ONE if matched, NULL + * if not matched). + */ +public class SubqueryRewriter extends ParseNodeRewriter { + private static final ParseNodeFactory NODE_FACTORY = new ParseNodeFactory(); + + private final ColumnResolver resolver; + private final PhoenixConnection connection; + private TableNode tableNode; + private ParseNode topNode; + + public static SelectStatement transform(SelectStatement select, ColumnResolver resolver, PhoenixConnection connection) throws SQLException { + ParseNode where = select.getWhere(); + if (where == null) + return select; + + SubqueryRewriter rewriter = new SubqueryRewriter(select, resolver, connection); + ParseNode normWhere = rewrite(where, rewriter); + if (normWhere == where) + return select; + + return NODE_FACTORY.select(Collections.singletonList(rewriter.tableNode), select.getHint(), select.isDistinct(), select.getSelect(), normWhere, select.getGroupBy(), select.getHaving(), select.getOrderBy(), select.getLimit(), select.getBindCount(), select.isAggregate(), select.hasSequence()); + } + + protected SubqueryRewriter(SelectStatement select, ColumnResolver resolver, PhoenixConnection connection) { + this.resolver = resolver; + this.connection = connection; + this.tableNode = select.getFrom().get(0); + this.topNode = null; + } + + @Override + protected void enterParseNode(ParseNode node) { + if (topNode == null) { + topNode = node; + } + super.enterParseNode(node); + } + + @Override + protected ParseNode leaveCompoundNode(CompoundParseNode node, List<ParseNode> children, ParseNodeRewriter.CompoundNodeFactory factory) { + if (topNode == node) { + topNode = null; + } + + return super.leaveCompoundNode(node, children, factory); + } + + @Override + public boolean visitEnter(AndParseNode node) throws SQLException { + return true; + } + + @Override + public ParseNode visitLeave(AndParseNode node, List<ParseNode> l) throws SQLException { + return leaveCompoundNode(node, l, new CompoundNodeFactory() { + @Override + public ParseNode createNode(List<ParseNode> children) { + if (children.isEmpty()) { + return null; + } + if (children.size() == 1) { + return children.get(0); + } + return NODE_FACTORY.and(children); + } + }); + } + + @Override + public ParseNode visitLeave(InParseNode node, List<ParseNode> l) throws SQLException { + SubqueryParseNode subqueryNode = (SubqueryParseNode) l.get(1); + SelectStatement subquery = subqueryNode.getSelectNode(); + String rhsTableAlias = ParseNodeFactory.createTempAlias(); + List<AliasedNode> selectNodes = fixAliasedNodes(subquery.getSelect()); + subquery = NODE_FACTORY.select(subquery.getFrom(), subquery.getHint(), true, + selectNodes, subquery.getWhere(), subquery.getGroupBy(), subquery.getHaving(), subquery.getOrderBy(), + subquery.getLimit(), subquery.getBindCount(), subquery.isAggregate(), subquery.hasSequence()); + ParseNode onNode = getJoinConditionNode(l.get(0), selectNodes, rhsTableAlias); + TableNode rhsTable = NODE_FACTORY.derivedTable(rhsTableAlias, subquery); + JoinType joinType = topNode == node ? (node.isNegate() ? JoinType.Anti : JoinType.Semi) : JoinType.Left; + ParseNode ret = topNode == node ? null : NODE_FACTORY.isNull(NODE_FACTORY.column(NODE_FACTORY.table(null, rhsTableAlias), selectNodes.get(0).getAlias(), null), !node.isNegate()); + tableNode = NODE_FACTORY.join(joinType, tableNode, rhsTable, onNode); + + if (topNode == node) { + topNode = null; + } + + return ret; + } + + @Override + public ParseNode visitLeave(ExistsParseNode node, List<ParseNode> l) throws SQLException { + SubqueryParseNode subqueryNode = (SubqueryParseNode) l.get(0); + SelectStatement subquery = subqueryNode.getSelectNode(); + String rhsTableAlias = ParseNodeFactory.createTempAlias(); + JoinConditionExtractor conditionExtractor = new JoinConditionExtractor(subquery, resolver, connection, rhsTableAlias); + ParseNode where = subquery.getWhere() == null ? null : subquery.getWhere().accept(conditionExtractor); + if (where == subquery.getWhere()) { // non-correlated EXISTS subquery, add LIMIT 1 + subquery = NODE_FACTORY.select(subquery, NODE_FACTORY.limit(NODE_FACTORY.literal(1))); + subqueryNode = NODE_FACTORY.subquery(subquery, false); + node = NODE_FACTORY.exists(subqueryNode, node.isNegate()); + return super.visitLeave(node, Collections.<ParseNode> singletonList(subqueryNode)); + } + + List<AliasedNode> additionalSelectNodes = conditionExtractor.getAdditionalSelectNodes(); + List<AliasedNode> selectNodes = Lists.newArrayListWithExpectedSize(additionalSelectNodes.size() + 1); + selectNodes.add(NODE_FACTORY.aliasedNode(ParseNodeFactory.createTempAlias(), LiteralParseNode.ONE)); + selectNodes.addAll(additionalSelectNodes); + + subquery = NODE_FACTORY.select(subquery.getFrom(), subquery.getHint(), true, + selectNodes, where, subquery.getGroupBy(), subquery.getHaving(), subquery.getOrderBy(), + subquery.getLimit(), subquery.getBindCount(), subquery.isAggregate(), subquery.hasSequence()); + ParseNode onNode = conditionExtractor.getJoinCondition(); + TableNode rhsTable = NODE_FACTORY.derivedTable(rhsTableAlias, subquery); + JoinType joinType = topNode == node ? (node.isNegate() ? JoinType.Anti : JoinType.Semi) : JoinType.Left; + ParseNode ret = topNode == node ? null : NODE_FACTORY.isNull(NODE_FACTORY.column(NODE_FACTORY.table(null, rhsTableAlias), selectNodes.get(0).getAlias(), null), !node.isNegate()); + tableNode = NODE_FACTORY.join(joinType, tableNode, rhsTable, onNode); + + if (topNode == node) { + topNode = null; + } + + return ret; + } + + private List<AliasedNode> fixAliasedNodes(List<AliasedNode> nodes) { + List<AliasedNode> normNodes = Lists.<AliasedNode> newArrayListWithExpectedSize(nodes.size() + 1); + normNodes.add(NODE_FACTORY.aliasedNode(ParseNodeFactory.createTempAlias(), LiteralParseNode.ONE)); + for (int i = 0; i < nodes.size(); i++) { + AliasedNode aliasedNode = nodes.get(i); + normNodes.add(NODE_FACTORY.aliasedNode( + ParseNodeFactory.createTempAlias(), aliasedNode.getNode())); + } + + return normNodes; + } + + private ParseNode getJoinConditionNode(ParseNode lhs, List<AliasedNode> rhs, String rhsTableAlias) throws SQLException { + List<ParseNode> lhsNodes; + if (lhs instanceof RowValueConstructorParseNode) { + lhsNodes = ((RowValueConstructorParseNode) lhs).getChildren(); + } else { + lhsNodes = Collections.singletonList(lhs); + } + if (lhsNodes.size() != (rhs.size() - 1)) + throw new SQLExceptionInfo.Builder(SQLExceptionCode.SUBQUERY_RETURNS_DIFFERENT_NUMBER_OF_FIELDS).build().buildException(); + + int count = lhsNodes.size(); + TableName rhsTableName = NODE_FACTORY.table(null, rhsTableAlias); + List<ParseNode> equalNodes = Lists.newArrayListWithExpectedSize(count); + for (int i = 0; i < count; i++) { + ParseNode rhsNode = NODE_FACTORY.column(rhsTableName, rhs.get(i + 1).getAlias(), null); + equalNodes.add(NODE_FACTORY.equal(lhsNodes.get(i), rhsNode)); + } + + return count == 1 ? equalNodes.get(0) : NODE_FACTORY.and(equalNodes); + } + + private static class JoinConditionExtractor extends BooleanParseNodeVisitor<ParseNode> { + private final TableName tableName; + private ColumnResolveVisitor columnResolveVisitor; + private List<AliasedNode> additionalSelectNodes; + private List<ParseNode> joinConditions; + + public JoinConditionExtractor(SelectStatement subquery, ColumnResolver outerResolver, + PhoenixConnection connection, String tableAlias) throws SQLException { + this.tableName = NODE_FACTORY.table(null, tableAlias); + ColumnResolver localResolver = FromCompiler.getResolverForQuery(subquery, connection); + this.columnResolveVisitor = new ColumnResolveVisitor(localResolver, outerResolver); + this.additionalSelectNodes = Lists.<AliasedNode> newArrayList(); + this.joinConditions = Lists.<ParseNode> newArrayList(); + } + + public List<AliasedNode> getAdditionalSelectNodes() { + return this.additionalSelectNodes; + } + + public ParseNode getJoinCondition() { + if (this.joinConditions.isEmpty()) + return null; + + if (this.joinConditions.size() == 1) + return this.joinConditions.get(0); + + return NODE_FACTORY.and(this.joinConditions); + } + + @Override + public List<ParseNode> newElementList(int size) { + return Lists.<ParseNode> newArrayListWithExpectedSize(size); + } + + @Override + public void addElement(List<ParseNode> l, ParseNode element) { + if (element != null) { + l.add(element); + } + } + + @Override + public boolean visitEnter(AndParseNode node) throws SQLException { + return true; + } + + @Override + public ParseNode visitLeave(AndParseNode node, List<ParseNode> l) + throws SQLException { + if (l.equals(node.getChildren())) + return node; + + if (l.isEmpty()) + return null; + + if (l.size() == 1) + return l.get(0); + + return NODE_FACTORY.and(l); + } + + @Override + protected boolean enterBooleanNode(ParseNode node) throws SQLException { + return false; + } + + @Override + protected ParseNode leaveBooleanNode(ParseNode node, List<ParseNode> l) + throws SQLException { + columnResolveVisitor.reset(); + node.accept(columnResolveVisitor); + ColumnResolveVisitor.ColumnResolveType type = columnResolveVisitor.getColumnResolveType(); + if (type != ColumnResolveVisitor.ColumnResolveType.NONE + && type != ColumnResolveVisitor.ColumnResolveType.LOCAL) + throw new SQLFeatureNotSupportedException("Does not support non-standard or non-equi correlated-subquery conditions."); + + return node; + } + + @Override + protected boolean enterNonBooleanNode(ParseNode node) + throws SQLException { + return false; + } + + @Override + protected ParseNode leaveNonBooleanNode(ParseNode node, + List<ParseNode> l) throws SQLException { + return node; + } + + @Override + public ParseNode visitLeave(ComparisonParseNode node, List<ParseNode> l) throws SQLException { + if (node.getFilterOp() != CompareFilter.CompareOp.EQUAL) + return leaveBooleanNode(node, l); + + columnResolveVisitor.reset(); + node.getLHS().accept(columnResolveVisitor); + ColumnResolveVisitor.ColumnResolveType lhsType = columnResolveVisitor.getColumnResolveType(); + columnResolveVisitor.reset(); + node.getRHS().accept(columnResolveVisitor); + ColumnResolveVisitor.ColumnResolveType rhsType = columnResolveVisitor.getColumnResolveType(); + if ((lhsType == ColumnResolveVisitor.ColumnResolveType.NONE || lhsType == ColumnResolveVisitor.ColumnResolveType.LOCAL) + && (rhsType == ColumnResolveVisitor.ColumnResolveType.NONE || rhsType == ColumnResolveVisitor.ColumnResolveType.LOCAL)) { + return node; + } + if (lhsType == ColumnResolveVisitor.ColumnResolveType.LOCAL && rhsType == ColumnResolveVisitor.ColumnResolveType.OUTER) { + String alias = ParseNodeFactory.createTempAlias(); + this.additionalSelectNodes.add(NODE_FACTORY.aliasedNode(alias, node.getLHS())); + ParseNode lhsNode = NODE_FACTORY.column(tableName, alias, null); + this.joinConditions.add(NODE_FACTORY.equal(lhsNode, node.getRHS())); + return null; + } + if (lhsType == ColumnResolveVisitor.ColumnResolveType.OUTER && rhsType == ColumnResolveVisitor.ColumnResolveType.LOCAL) { + String alias = ParseNodeFactory.createTempAlias(); + this.additionalSelectNodes.add(NODE_FACTORY.aliasedNode(alias, node.getRHS())); + ParseNode rhsNode = NODE_FACTORY.column(tableName, alias, null); + this.joinConditions.add(NODE_FACTORY.equal(node.getLHS(), rhsNode)); + return null; + } + + throw new SQLFeatureNotSupportedException("Does not support non-standard or non-equi correlated-subquery conditions."); + } + } + + /* + * Class for resolving inner query column references + */ + private static class ColumnResolveVisitor extends StatelessTraverseAllParseNodeVisitor { + public enum ColumnResolveType {NONE, LOCAL, OUTER, MIXED}; + + private final ColumnResolver localResolver; + private final ColumnResolver outerResolver; + private ColumnResolveType type; + + public ColumnResolveVisitor(ColumnResolver localResolver, ColumnResolver outerResolver) { + this.localResolver = localResolver; + this.outerResolver = outerResolver; + this.type = ColumnResolveType.NONE; + } + + public void reset() { + this.type = ColumnResolveType.NONE; + } + + public ColumnResolveType getColumnResolveType() { + return this.type; + } + + @Override + public Void visit(ColumnParseNode node) throws SQLException { + // Inner query column definitions should shade those of outer query. + try { + localResolver.resolveColumn(node.getSchemaName(), node.getTableName(), node.getName()); + addType(true); + return null; + } catch (ColumnNotFoundException e) { + } catch (ColumnFamilyNotFoundException e) { + } + + outerResolver.resolveColumn(node.getSchemaName(), node.getTableName(), node.getName()); + addType(false); + return null; + } + + private void addType(boolean isLocal) { + switch (this.type) { + case NONE: + this.type = isLocal ? ColumnResolveType.LOCAL : ColumnResolveType.OUTER; + break; + case LOCAL: + this.type = isLocal ? ColumnResolveType.LOCAL : ColumnResolveType.MIXED; + break; + case OUTER: + this.type = isLocal ? ColumnResolveType.MIXED : ColumnResolveType.OUTER; + break; + default: // MIXED do nothing + break; + } + } + } +} http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java index f363bdc..3381aa8 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java @@ -372,6 +372,11 @@ public class UpsertCompiler { ColumnResolver selectResolver = FromCompiler.getResolverForQuery(select, connection); select = StatementNormalizer.normalize(select, selectResolver); select = prependTenantAndViewConstants(table, select, tenantId, addViewColumnsToBe); + SelectStatement transformedSelect = SubqueryRewriter.transform(select, selectResolver, connection); + if (transformedSelect != select) { + selectResolver = FromCompiler.getResolverForQuery(transformedSelect, connection); + select = StatementNormalizer.normalize(transformedSelect, selectResolver); + } sameTable = select.getFrom().size() == 1 && tableRefToBe.equals(selectResolver.getTables().get(0)); tableRefToBe = adjustTimestampToMinOfSameTable(tableRefToBe, selectResolver.getTables()); http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java index 5f652f1..64a49c8 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java @@ -311,12 +311,14 @@ public class WhereOptimizer { /** * Get an optimal combination of key expressions for hash join key range optimization. + * @return returns true if the entire combined expression is covered by key range optimization + * @param result the optimal combination of key expressions * @param context the temporary context to get scan ranges set by pushKeyExpressionsToScan() * @param statement the statement being compiled * @param expressions the join key expressions * @return the optimal list of key expressions */ - public static List<Expression> getKeyExpressionCombination(StatementContext context, FilterableStatement statement, List<Expression> expressions) throws SQLException { + public static boolean getKeyExpressionCombination(List<Expression> result, StatementContext context, FilterableStatement statement, List<Expression> expressions) throws SQLException { List<Integer> candidateIndexes = Lists.newArrayList(); final List<Integer> pkPositions = Lists.newArrayList(); for (int i = 0; i < expressions.size(); i++) { @@ -339,7 +341,7 @@ public class WhereOptimizer { } if (candidateIndexes.isEmpty()) - return Collections.<Expression> emptyList(); + return false; Collections.sort(candidateIndexes, new Comparator<Integer>() { @Override @@ -364,12 +366,13 @@ public class WhereOptimizer { int count = 0; int maxPkSpan = 0; + Expression remaining = null; while (count < candidates.size()) { Expression lhs = count == 0 ? candidates.get(0) : new RowValueConstructorExpression(candidates.subList(0, count + 1), false); Expression firstRhs = count == 0 ? sampleValues.get(0).get(0) : new RowValueConstructorExpression(sampleValues.get(0).subList(0, count + 1), true); Expression secondRhs = count == 0 ? sampleValues.get(1).get(0) : new RowValueConstructorExpression(sampleValues.get(1).subList(0, count + 1), true); Expression testExpression = InListExpression.create(Lists.newArrayList(lhs, firstRhs, secondRhs), false, context.getTempPtr()); - pushKeyExpressionsToScan(context, statement, testExpression); + remaining = pushKeyExpressionsToScan(context, statement, testExpression); int pkSpan = context.getScanRanges().getPkColumnSpan(); if (pkSpan <= maxPkSpan) { break; @@ -378,7 +381,11 @@ public class WhereOptimizer { count++; } - return candidates.subList(0, count); + result.addAll(candidates.subList(0, count)); + + return count == candidates.size() + && (context.getScanRanges().isPointLookup() || context.getScanRanges().useSkipScanFilter()) + && (remaining == null || remaining.equals(LiteralExpression.newConstant(true, Determinism.ALWAYS))); } private static class RemoveExtractedNodesVisitor extends TraverseNoExpressionVisitor<Expression> { http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/HashJoinRegionScanner.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/HashJoinRegionScanner.java b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/HashJoinRegionScanner.java index 47ffce7..02fc6e3 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/HashJoinRegionScanner.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/HashJoinRegionScanner.java @@ -72,7 +72,7 @@ public class HashJoinRegionScanner implements RegionScanner { this.limit = Long.MAX_VALUE; if (joinInfo != null) { for (JoinType type : joinInfo.getJoinTypes()) { - if (type != JoinType.Inner && type != JoinType.Left) + if (type != JoinType.Inner && type != JoinType.Left && type != JoinType.Semi && type != JoinType.Anti) throw new DoNotRetryIOException("Got join type '" + type + "'. Expect only INNER or LEFT with hash-joins."); } if (joinInfo.getLimit() != null) { @@ -85,6 +85,12 @@ public class HashJoinRegionScanner implements RegionScanner { TenantCache cache = GlobalCache.getTenantCache(env, tenantId); for (int i = 0; i < count; i++) { ImmutableBytesPtr joinId = joinInfo.getJoinIds()[i]; + if (joinId.getLength() == 0) { // semi-join optimized into skip-scan + hashCaches[i] = null; + tempSrcBitSet[i] = null; + tempTuples[i] = null; + continue; + } HashCache hashCache = (HashCache)cache.getServerCache(joinId); if (hashCache == null) throw new DoNotRetryIOException("Could not find hash cache for joinId: " @@ -119,12 +125,13 @@ public class HashJoinRegionScanner implements RegionScanner { int count = joinInfo.getJoinIds().length; boolean cont = true; for (int i = 0; i < count; i++) { - if (!(joinInfo.earlyEvaluation()[i])) + if (!(joinInfo.earlyEvaluation()[i]) || hashCaches[i] == null) continue; ImmutableBytesPtr key = TupleUtil.getConcatenatedValue(tuple, joinInfo.getJoinExpressions()[i]); tempTuples[i] = hashCaches[i].get(key); JoinType type = joinInfo.getJoinTypes()[i]; - if (type == JoinType.Inner && tempTuples[i] == null) { + if (((type == JoinType.Inner || type == JoinType.Semi) && tempTuples[i] == null) + || (type == JoinType.Anti && tempTuples[i] != null)) { cont = false; break; } @@ -146,7 +153,8 @@ public class HashJoinRegionScanner implements RegionScanner { resultQueue.offer(tuple); for (int i = 0; i < count; i++) { boolean earlyEvaluation = joinInfo.earlyEvaluation()[i]; - if (earlyEvaluation && tempTuples[i] == null) + JoinType type = joinInfo.getJoinTypes()[i]; + if (earlyEvaluation && (tempTuples[i] == null || type == JoinType.Semi)) continue; int j = resultQueue.size(); while (j-- > 0) { @@ -155,7 +163,7 @@ public class HashJoinRegionScanner implements RegionScanner { ImmutableBytesPtr key = TupleUtil.getConcatenatedValue(lhs, joinInfo.getJoinExpressions()[i]); tempTuples[i] = hashCaches[i].get(key); if (tempTuples[i] == null) { - if (joinInfo.getJoinTypes()[i] != JoinType.Inner) { + if (type != JoinType.Inner && type != JoinType.Semi) { resultQueue.offer(lhs); } continue; http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java index bbb09dc..c99d14c 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java @@ -78,7 +78,8 @@ public enum SQLExceptionCode { SERVER_ARITHMETIC_ERROR(212, "22012", "Arithmetic error on server."), VALUE_OUTSIDE_RANGE(213,"22003","Value outside range."), VALUE_IN_LIST_NOT_CONSTANT(214, "22008", "Values in IN must evaluate to a constant."), - SINGLE_ROW_SUBQUERY_RETURNS_MULTIPLE_ROWS(215, "22015", "Single-row subquery returns more than one row."), + SINGLE_ROW_SUBQUERY_RETURNS_MULTIPLE_ROWS(215, "22015", "Single-row sub-query returns more than one row."), + SUBQUERY_RETURNS_DIFFERENT_NUMBER_OF_FIELDS(216, "22016", "Sub-query must return the same number of fields as the left-hand-side expression of 'IN'."), /** * Constraint Violation (errorcode 03, sqlstate 23) http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java index dcf162f..1ac9d68 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java @@ -267,7 +267,6 @@ public class HashJoinPlan implements QueryPlan { public ExplainPlan getExplainPlan() throws SQLException { List<String> planSteps = Lists.newArrayList(plan.getExplainPlan().getPlanSteps()); int count = subPlans.length; - planSteps.add(" PARALLEL EQUI/SEMI/ANTI-JOIN " + count + " TABLES:"); for (int i = 0; i < count; i++) { planSteps.addAll(subPlans[i].getPreSteps(this)); } @@ -422,12 +421,29 @@ public class HashJoinPlan implements QueryPlan { if (keyRangeRhsExpression != null) { keyRangeRhsValues = Lists.<ImmutableBytesWritable>newArrayList(); } - ServerCache cache = parent.hashClient.addHashCache(ranges, plan.iterator(), - clientProjector, plan.getEstimatedSize(), hashExpressions, parent.plan.getTableRef(), keyRangeRhsExpression, keyRangeRhsValues); - long endTime = System.currentTimeMillis(); - boolean isSet = parent.firstJobEndTime.compareAndSet(0, endTime); - if (!isSet && (endTime - parent.firstJobEndTime.get()) > parent.maxServerCacheTimeToLive) { - LOG.warn(addCustomAnnotations("Hash plan [" + index + "] execution seems too slow. Earlier hash cache(s) might have expired on servers.", parent.plan.context.getConnection())); + ServerCache cache = null; + if (hashExpressions != null) { + cache = parent.hashClient.addHashCache(ranges, plan.iterator(), + clientProjector, plan.getEstimatedSize(), hashExpressions, parent.plan.getTableRef(), keyRangeRhsExpression, keyRangeRhsValues); + long endTime = System.currentTimeMillis(); + boolean isSet = parent.firstJobEndTime.compareAndSet(0, endTime); + if (!isSet && (endTime - parent.firstJobEndTime.get()) > parent.maxServerCacheTimeToLive) { + LOG.warn(addCustomAnnotations("Hash plan [" + index + "] execution seems too slow. Earlier hash cache(s) might have expired on servers.", parent.plan.context.getConnection())); + } + } else { + assert(keyRangeRhsExpression != null); + ResultIterator iterator = plan.iterator(); + for (Tuple result = iterator.next(); result != null; result = iterator.next()) { + if (clientProjector != null) { + result = clientProjector.projectResults(result); + } + // Evaluate key expressions for hash join key range optimization. + ImmutableBytesWritable value = new ImmutableBytesWritable(); + keyRangeRhsExpression.reset(); + if (keyRangeRhsExpression.evaluate(result, value)) { + keyRangeRhsValues.add(value); + } + } } if (keyRangeRhsValues != null) { parent.keyRangeExpressions.add(parent.createKeyRangeExpression(keyRangeLhsExpression, keyRangeRhsExpression, keyRangeRhsValues, plan.getContext().getTempPtr(), hasFilters)); @@ -439,8 +455,10 @@ public class HashJoinPlan implements QueryPlan { public void postProcess(Object result, HashJoinPlan parent) throws SQLException { ServerCache cache = (ServerCache) result; - parent.joinInfo.getJoinIds()[index].set(cache.getId()); - parent.dependencies.add(cache); + if (cache != null) { + parent.joinInfo.getJoinIds()[index].set(cache.getId()); + parent.dependencies.add(cache); + } } @Override @@ -448,7 +466,13 @@ public class HashJoinPlan implements QueryPlan { List<String> steps = Lists.newArrayList(); boolean earlyEvaluation = parent.joinInfo.earlyEvaluation()[index]; boolean skipMerge = parent.joinInfo.getSchemas()[index].getFieldCount() == 0; - steps.add(" BUILD HASH TABLE " + index + (earlyEvaluation ? "" : "(DELAYED EVALUATION)") + (skipMerge ? " (SKIP MERGE)" : "")); + if (hashExpressions != null) { + steps.add(" PARALLEL " + parent.joinInfo.getJoinTypes()[index].toString().toUpperCase() + + "-JOIN TABLE " + index + (earlyEvaluation ? "" : "(DELAYED EVALUATION)") + (skipMerge ? " (SKIP MERGE)" : "")); + } + else { + steps.add(" SKIP-SCAN-JOIN TABLE " + index); + } for (String step : plan.getExplainPlan().getPlanSteps()) { steps.add(" " + step); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java index bdde415..30376e7 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java @@ -45,6 +45,7 @@ import org.apache.phoenix.compile.DropSequenceCompiler; import org.apache.phoenix.compile.ExplainPlan; import org.apache.phoenix.compile.ExpressionProjector; import org.apache.phoenix.compile.FromCompiler; +import org.apache.phoenix.compile.SubqueryRewriter; import org.apache.phoenix.compile.GroupByCompiler.GroupBy; import org.apache.phoenix.compile.MutationPlan; import org.apache.phoenix.compile.OrderByCompiler.OrderBy; @@ -301,6 +302,11 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho SelectStatement select = SubselectRewriter.flatten(this, stmt.getConnection()); ColumnResolver resolver = FromCompiler.getResolverForQuery(select, stmt.getConnection()); select = StatementNormalizer.normalize(select, resolver); + SelectStatement transformedSelect = SubqueryRewriter.transform(select, resolver, stmt.getConnection()); + if (transformedSelect != select) { + resolver = FromCompiler.getResolverForQuery(transformedSelect, stmt.getConnection()); + select = StatementNormalizer.normalize(transformedSelect, resolver); + } QueryPlan plan = new QueryCompiler(stmt, select, resolver).compile(); plan.getContext().getSequenceManager().validateSequences(seqAction); return plan; http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/BooleanParseNodeVisitor.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/BooleanParseNodeVisitor.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/BooleanParseNodeVisitor.java index eb68211..0d6feda 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/BooleanParseNodeVisitor.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/BooleanParseNodeVisitor.java @@ -139,6 +139,16 @@ public abstract class BooleanParseNodeVisitor<T> extends BaseParseNodeVisitor<T> } @Override + public boolean visitEnter(ExistsParseNode node) throws SQLException { + return enterBooleanNode(node); + } + + @Override + public T visitLeave(ExistsParseNode node, List<T> l) throws SQLException { + return leaveBooleanNode(node, l); + } + + @Override public boolean visitEnter(InListParseNode node) throws SQLException { return enterBooleanNode(node); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/ExistsParseNode.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ExistsParseNode.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ExistsParseNode.java index 2c252e3..45ccdfe 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ExistsParseNode.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ExistsParseNode.java @@ -30,11 +30,11 @@ import java.util.List; * * @since 0.1 */ -public class ExistsParseNode extends BinaryParseNode { +public class ExistsParseNode extends UnaryParseNode { private final boolean negate; - ExistsParseNode(ParseNode l, ParseNode r, boolean negate) { - super(l, r); + ExistsParseNode(ParseNode child, boolean negate) { + super(child); this.negate = negate; } http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/JoinTableNode.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/JoinTableNode.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/JoinTableNode.java index cbd6bce..a51ca5c 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/JoinTableNode.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/JoinTableNode.java @@ -29,7 +29,15 @@ import java.sql.SQLException; * @since 0.1 */ public class JoinTableNode extends TableNode { - public enum JoinType {Inner, Left, Right, Full}; + public enum JoinType { + Inner, + Left, + Right, + Full, + // the following two types derive from sub-query rewriting + Semi, + Anti, + }; private final JoinType type; private final TableNode lhs; http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java index eb1fda5..2b9f914 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; import org.apache.hadoop.hbase.util.Pair; @@ -180,6 +181,12 @@ public class ParseNodeFactory { public ParseNodeFactory() { } + + private static AtomicInteger tempAliasCounter = new AtomicInteger(0); + + public static String createTempAlias() { + return "$" + tempAliasCounter.incrementAndGet(); + } public ExplainStatement explain(BindableStatement statement) { return new ExplainStatement(statement); @@ -395,8 +402,8 @@ public class ParseNodeFactory { return new InListParseNode(children, negate); } - public ExistsParseNode exists(ParseNode l, ParseNode r, boolean negate) { - return new ExistsParseNode(l, r, negate); + public ExistsParseNode exists(ParseNode child, boolean negate) { + return new ExistsParseNode(child, negate); } public InParseNode in(ParseNode l, ParseNode r, boolean negate) { @@ -555,7 +562,11 @@ public class ParseNodeFactory { return new NotEqualParseNode(lhs, rhs); } - public NotParseNode not(ParseNode child) { + public ParseNode not(ParseNode child) { + if (child instanceof ExistsParseNode) { + return exists(child.getChildren().get(0), !((ExistsParseNode) child).isNegate()); + } + return new NotParseNode(child); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java ---------------------------------------------------------------------- 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 be34efa..06ac1c6 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 @@ -204,17 +204,21 @@ public class ParseNodeRewriter extends TraverseAllParseNodeVisitor<ParseNode> { this.nodeCount = 0; } - private static interface CompoundNodeFactory { + protected static interface CompoundNodeFactory { ParseNode createNode(List<ParseNode> children); } - private ParseNode leaveCompoundNode(CompoundParseNode node, List<ParseNode> children, CompoundNodeFactory factory) { + protected ParseNode leaveCompoundNode(CompoundParseNode node, List<ParseNode> children, CompoundNodeFactory factory) { if (children.equals(node.getChildren())) { return node; } else { // Child nodes have been inverted (because a literal was found on LHS) return factory.createNode(children); } } + + @Override + protected void enterParseNode(ParseNode node) { + } @Override public ParseNode visitLeave(AndParseNode node, List<ParseNode> nodes) throws SQLException { @@ -327,6 +331,16 @@ public class ParseNodeRewriter extends TraverseAllParseNodeVisitor<ParseNode> { } @Override + public ParseNode visitLeave(final ExistsParseNode node, List<ParseNode> nodes) throws SQLException { + return leaveCompoundNode(node, nodes, new CompoundNodeFactory() { + @Override + public ParseNode createNode(List<ParseNode> children) { + return NODE_FACTORY.exists(children.get(0), node.isNegate()); + } + }); + } + + @Override public ParseNode visitLeave(final CastParseNode node, List<ParseNode> nodes) throws SQLException { return leaveCompoundNode(node, nodes, new CompoundNodeFactory() { @Override http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java index 01925ff..50edf91 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java @@ -78,6 +78,9 @@ public interface ParseNodeVisitor<E> { public boolean visitEnter(NotParseNode node) throws SQLException; public E visitLeave(NotParseNode node, List<E> l) throws SQLException; + public boolean visitEnter(ExistsParseNode node) throws SQLException; + public E visitLeave(ExistsParseNode node, List<E> l) throws SQLException; + public boolean visitEnter(InListParseNode node) throws SQLException; public E visitLeave(InListParseNode node, List<E> l) throws SQLException; http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java index 228e1be..e95b480 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java @@ -23,6 +23,10 @@ import java.util.List; public class StatelessTraverseAllParseNodeVisitor extends TraverseAllParseNodeVisitor<Void> { @Override + protected void enterParseNode(ParseNode node) { + } + + @Override public Void visitLeave(LikeParseNode node, List<Void> l) throws SQLException { return null; } @@ -81,6 +85,11 @@ public class StatelessTraverseAllParseNodeVisitor extends TraverseAllParseNodeVi public Void visitLeave(NotParseNode node, List<Void> l) throws SQLException { return null; } + + @Override + public Void visitLeave(ExistsParseNode node, List<Void> l) throws SQLException { + return null; + } @Override public Void visitLeave(CastParseNode node, List<Void> l) throws SQLException { http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java index ae24824..bbe58d0 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java @@ -18,7 +18,6 @@ package org.apache.phoenix.parse; import java.sql.SQLException; -import java.util.List; /** @@ -29,158 +28,197 @@ import java.util.List; * @since 0.1 */ public abstract class TraverseAllParseNodeVisitor<T> extends BaseParseNodeVisitor<T> { + protected abstract void enterParseNode(ParseNode node) throws SQLException; + @Override public boolean visitEnter(AndParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(OrParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(FunctionParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(CaseParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(ComparisonParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(LikeParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(NotParseNode node) throws SQLException { + enterParseNode(node); + return true; + } + + @Override + public boolean visitEnter(ExistsParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(CastParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(InListParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(InParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(IsNullParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(MultiplyParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(SubtractParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(AddParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(DivideParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(ModulusParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(BetweenParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public T visit(ColumnParseNode node) throws SQLException { + enterParseNode(node); return null; } @Override public T visit(LiteralParseNode node) throws SQLException { + enterParseNode(node); return null; } @Override public T visit(BindParseNode node) throws SQLException { + enterParseNode(node); return null; } @Override public T visit(WildcardParseNode node) throws SQLException { + enterParseNode(node); return null; } @Override public T visit(TableWildcardParseNode node) throws SQLException { + enterParseNode(node); return null; } @Override public T visit(FamilyWildcardParseNode node) throws SQLException { + enterParseNode(node); return null; } @Override public T visit(SubqueryParseNode node) throws SQLException { + enterParseNode(node); return null; } @Override public boolean visitEnter(StringConcatParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(RowValueConstructorParseNode node) throws SQLException { + enterParseNode(node); return true; } @Override public T visit(SequenceValueParseNode node) throws SQLException { + enterParseNode(node); return null; } @Override public boolean visitEnter(ArrayConstructorNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(ArrayAllComparisonNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(ArrayAnyComparisonNode node) throws SQLException { + enterParseNode(node); return true; } @Override public boolean visitEnter(ArrayElemRefNode node) throws SQLException { + enterParseNode(node); return true; } } http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java index 37be462..7a8732a 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java @@ -75,6 +75,11 @@ public abstract class TraverseNoParseNodeVisitor<T> extends BaseParseNodeVisitor } @Override + public boolean visitEnter(ExistsParseNode node) throws SQLException { + return false; + } + + @Override public boolean visitEnter(CastParseNode node) throws SQLException { return false; } @@ -85,6 +90,11 @@ public abstract class TraverseNoParseNodeVisitor<T> extends BaseParseNodeVisitor } @Override + public T visitLeave(ExistsParseNode node, List<T> l) throws SQLException { + return null; + } + + @Override public T visitLeave(CastParseNode node, List<T> l) throws SQLException { return null; } http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java index 43cb0c3..8e6a84e 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java @@ -76,6 +76,11 @@ abstract public class UnsupportedAllParseNodeVisitor<E> extends BaseParseNodeVis } @Override + public E visit(SubqueryParseNode node) throws SQLException { + throw new SQLFeatureNotSupportedException(node.toString()); + } + + @Override public boolean visitEnter(AndParseNode node) throws SQLException { throw new SQLFeatureNotSupportedException(node.toString()); } @@ -139,6 +144,16 @@ abstract public class UnsupportedAllParseNodeVisitor<E> extends BaseParseNodeVis public boolean visitEnter(NotParseNode node) throws SQLException { throw new SQLFeatureNotSupportedException(node.toString()); } + + @Override + public E visitLeave(ExistsParseNode node, List<E> l) throws SQLException { + throw new SQLFeatureNotSupportedException(node.toString()); + } + + @Override + public boolean visitEnter(ExistsParseNode node) throws SQLException { + throw new SQLFeatureNotSupportedException(node.toString()); + } @Override public E visitLeave(CastParseNode node, List<E> l) throws SQLException { @@ -154,6 +169,11 @@ abstract public class UnsupportedAllParseNodeVisitor<E> extends BaseParseNodeVis public E visitLeave(InListParseNode node, List<E> l) throws SQLException { throw new SQLFeatureNotSupportedException(node.toString()); } + + @Override + public E visitLeave(InParseNode node, List<E> l) throws SQLException { + throw new SQLFeatureNotSupportedException(node.toString()); + } @Override public E visitLeave(BetweenParseNode node, List<E> l) throws SQLException { @@ -164,6 +184,11 @@ abstract public class UnsupportedAllParseNodeVisitor<E> extends BaseParseNodeVis public boolean visitEnter(InListParseNode node) throws SQLException { throw new SQLFeatureNotSupportedException(node.toString()); } + + @Override + public boolean visitEnter(InParseNode node) throws SQLException { + throw new SQLFeatureNotSupportedException(node.toString()); + } @Override public boolean visitEnter(IsNullParseNode node) throws SQLException { http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/test/java/org/apache/phoenix/compile/JoinQueryCompilerTest.java ---------------------------------------------------------------------- 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 a08b0e3..ffaafd8 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 @@ -59,14 +59,12 @@ public class JoinQueryCompilerTest extends BaseConnectionlessQueryTest { assertEquals( "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_DISPLAY_NAME + "\n" + " SERVER FILTER BY FIRST KEY ONLY\n" + - " PARALLEL EQUI/SEMI/ANTI-JOIN 1 TABLES:\n" + - " BUILD HASH TABLE 0\n" + + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" + - " PARALLEL EQUI/SEMI/ANTI-JOIN 2 TABLES:\n" + - " BUILD HASH TABLE 0\n" + + " PARALLEL LEFT-JOIN TABLE 0\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_CUSTOMER_TABLE_DISPLAY_NAME + "\n" + " SERVER FILTER BY NAME LIKE 'C%'\n" + - " BUILD HASH TABLE 1\n" + + " PARALLEL LEFT-JOIN TABLE 1\n" + " CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_DISPLAY_NAME + "\n" + " AFTER-JOIN SERVER FILTER BY I.NAME LIKE 'T%'", QueryUtil.getExplainPlan(rs)); } @@ -133,6 +131,11 @@ public class JoinQueryCompilerTest extends BaseConnectionlessQueryTest { 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); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java index 11f394f..1844edb 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java @@ -45,6 +45,7 @@ import static org.apache.phoenix.util.TestUtil.HBASE_DYNAMIC_COLUMNS; import static org.apache.phoenix.util.TestUtil.HBASE_NATIVE; import static org.apache.phoenix.util.TestUtil.INDEX_DATA_SCHEMA; import static org.apache.phoenix.util.TestUtil.INDEX_DATA_TABLE; +import static org.apache.phoenix.util.TestUtil.JOIN_COITEM_TABLE_FULL_NAME; import static org.apache.phoenix.util.TestUtil.JOIN_CUSTOMER_TABLE_FULL_NAME; import static org.apache.phoenix.util.TestUtil.JOIN_ITEM_TABLE_FULL_NAME; import static org.apache.phoenix.util.TestUtil.JOIN_ORDER_TABLE_FULL_NAME; @@ -433,6 +434,13 @@ public abstract class BaseTest { " phone varchar(12), " + " address varchar, " + " loc_id varchar(5))"); + builder.put(JOIN_COITEM_TABLE_FULL_NAME, "create table " + JOIN_COITEM_TABLE_FULL_NAME + + " (item_id varchar(10) NOT NULL, " + + " item_name varchar NOT NULL, " + + " co_item_id varchar(10), " + + " co_item_name varchar " + + " CONSTRAINT pk PRIMARY KEY (item_id, item_name)) " + + " SALT_BUCKETS=4"); tableDDLMap = builder.build(); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/909d9759/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java ---------------------------------------------------------------------- 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 1aac3c5..4b25992 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 @@ -182,14 +182,17 @@ public class TestUtil { public static final String JOIN_CUSTOMER_TABLE = "CustomerTable"; public static final String JOIN_ITEM_TABLE = "ItemTable"; public static final String JOIN_SUPPLIER_TABLE = "SupplierTable"; + public static final String JOIN_COITEM_TABLE = "CoitemTable"; public static final String JOIN_ORDER_TABLE_FULL_NAME = '"' + JOIN_SCHEMA + "\".\"" + JOIN_ORDER_TABLE + '"'; public static final String JOIN_CUSTOMER_TABLE_FULL_NAME = '"' + JOIN_SCHEMA + "\".\"" + JOIN_CUSTOMER_TABLE + '"'; public static final String JOIN_ITEM_TABLE_FULL_NAME = '"' + JOIN_SCHEMA + "\".\"" + JOIN_ITEM_TABLE + '"'; public static final String JOIN_SUPPLIER_TABLE_FULL_NAME = '"' + JOIN_SCHEMA + "\".\"" + JOIN_SUPPLIER_TABLE + '"'; + public static final String JOIN_COITEM_TABLE_FULL_NAME = '"' + JOIN_SCHEMA + "\".\"" + JOIN_COITEM_TABLE + '"'; public static final String JOIN_ORDER_TABLE_DISPLAY_NAME = JOIN_SCHEMA + "." + JOIN_ORDER_TABLE; public static final String JOIN_CUSTOMER_TABLE_DISPLAY_NAME = JOIN_SCHEMA + "." + JOIN_CUSTOMER_TABLE; public static final String JOIN_ITEM_TABLE_DISPLAY_NAME = JOIN_SCHEMA + "." + JOIN_ITEM_TABLE; public static final String JOIN_SUPPLIER_TABLE_DISPLAY_NAME = JOIN_SCHEMA + "." + JOIN_SUPPLIER_TABLE; + public static final String JOIN_COITEM_TABLE_DISPLAY_NAME = JOIN_SCHEMA + "." + JOIN_COITEM_TABLE; /** * Read-only properties used by all tests