All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.phoenix.compile.SubqueryRewriter Maven / Gradle / Ivy

The newest version!
/*
 * 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.expression.function.DistinctValueAggregateFunction;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.parse.AliasedNode;
import org.apache.phoenix.parse.AndParseNode;
import org.apache.phoenix.parse.AndRewriterBooleanParseNodeVisitor;
import org.apache.phoenix.parse.ArrayAllComparisonNode;
import org.apache.phoenix.parse.ArrayAnyComparisonNode;
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.HintNode;
import org.apache.phoenix.parse.InParseNode;
import org.apache.phoenix.parse.JoinTableNode.JoinType;
import org.apache.phoenix.parse.LiteralParseNode;
import org.apache.phoenix.parse.OrderByNode;
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 org.apache.phoenix.schema.TableNotFoundException;

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(select, rewriter.tableNode, normWhere);
    }
    
    protected SubqueryRewriter(SelectStatement select, ColumnResolver resolver, PhoenixConnection connection) {
        this.resolver = resolver;
        this.connection = connection;
        this.tableNode = select.getFrom();
        this.topNode = null;
    }
    
    @Override
    protected void enterParseNode(ParseNode node) {
        if (topNode == null) {
            topNode = node;
        }
        super.enterParseNode(node);
    }
    
    @Override
    protected ParseNode leaveCompoundNode(CompoundParseNode node, List 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 l) throws SQLException {
        return leaveCompoundNode(node, l, new CompoundNodeFactory() {
            @Override
            public ParseNode createNode(List children) {
                if (children.isEmpty()) {
                    return null;
                }
                if (children.size() == 1) {
                    return children.get(0);
                }
                return NODE_FACTORY.and(children);
            }
        });
    }

    /**
     * 
     * Rewrite the In Subquery to semi/anti/left join for both NonCorrelated and Correlated subquery.
     *
     * 1.If the {@link InParseNode} is the only node in where clause or is the ANDed part of the where clause,
     *   then we would rewrite the In Subquery to semi/anti join:
     *   For  NonCorrelated subquery, an example is:
     *    SELECT item_id, name FROM item i WHERE i.item_id IN
     *    (SELECT item_id FROM order o  where o.price > 8)
     *
     *    The above sql would be rewritten as:
     *    SELECT ITEM_ID,NAME FROM item I  Semi JOIN
     *    (SELECT DISTINCT 1 $35,ITEM_ID $36 FROM order O  WHERE O.PRICE > 8) $34
     *     ON (I.ITEM_ID = $34.$36)
     *
     *   For Correlated subquery, an example is:
     *    SELECT item_id, name FROM item i WHERE i.item_id IN
     *    (SELECT item_id FROM order o  where o.price = i.price)
     *
     *    The above sql would be rewritten as:
     *    SELECT ITEM_ID,NAME FROM item I  Semi JOIN
     *    (SELECT DISTINCT 1 $3,ITEM_ID $4,O.PRICE $2 FROM order O ) $1
     *    ON ((I.ITEM_ID = $1.$4 AND $1.$2 = I.PRICE))
     *
     * 2.If the {@link InParseNode} is the ORed part of the where clause,then we would rewrite the In Subquery to
     *   Left Join.
     *
     *   For  NonCorrelated subquery, an example is:
     *    SELECT item_id, name FROM item i WHERE i.item_id IN
     *    (SELECT max(item_id) FROM order o  where o.price > 8 group by o.customer_id,o.item_id) or i.discount1 > 10
     *
     *    The above sql would be rewritten as:
     *    SELECT ITEM_ID,NAME FROM item I  Left JOIN
     *    (SELECT DISTINCT 1 $56, MAX(ITEM_ID) $57 FROM order O  WHERE O.PRICE > 8 GROUP BY O.CUSTOMER_ID,O.ITEM_ID) $55
     *    ON (I.ITEM_ID = $55.$57) WHERE ($55.$56 IS NOT NULL  OR I.DISCOUNT1 > 10)
     *
     *   For  Correlated subquery, an example is:
     *     SELECT item_id, name FROM item i WHERE i.item_id IN
     *     (SELECT max(item_id) FROM order o  where o.price = i.price group by o.customer_id) or i.discount1 > 10;
     *
     *     The above sql would be rewritten as:
     *     SELECT ITEM_ID,NAME FROM item I  Left JOIN
     *     (SELECT DISTINCT 1 $28, MAX(ITEM_ID) $29,O.PRICE $27 FROM order O  GROUP BY O.PRICE,O.CUSTOMER_ID) $26
     *     ON ((I.ITEM_ID = $26.$29 AND $26.$27 = I.PRICE)) WHERE ($26.$28 IS NOT NULL  OR I.DISCOUNT1 > 10)
     * 
*/ @Override public ParseNode visitLeave(InParseNode inParseNode, List childParseNodes) throws SQLException { boolean isTopNode = topNode == inParseNode; if (isTopNode) { topNode = null; } SubqueryParseNode subqueryParseNode = (SubqueryParseNode) childParseNodes.get(1); SelectStatement subquerySelectStatementToUse = fixSubqueryStatement(subqueryParseNode.getSelectNode()); String subqueryTableTempAlias = ParseNodeFactory.createTempAlias(); JoinConditionExtractor joinConditionExtractor = new JoinConditionExtractor( subquerySelectStatementToUse, resolver, connection, subqueryTableTempAlias); List newSubquerySelectAliasedNodes = null; ParseNode extractedJoinConditionParseNode = null; int extractedSelectAliasNodeCount = 0; List oldSubqueryAliasedNodes = subquerySelectStatementToUse.getSelect(); ParseNode whereParseNodeAfterExtract = subquerySelectStatementToUse.getWhere() == null ? null : subquerySelectStatementToUse.getWhere().accept(joinConditionExtractor); if (whereParseNodeAfterExtract == subquerySelectStatementToUse.getWhere()) { /** * It is an NonCorrelated subquery. */ newSubquerySelectAliasedNodes = Lists. newArrayListWithExpectedSize( oldSubqueryAliasedNodes.size() + 1); newSubquerySelectAliasedNodes.add( NODE_FACTORY.aliasedNode( ParseNodeFactory.createTempAlias(), LiteralParseNode.ONE)); this.addNewAliasedNodes(newSubquerySelectAliasedNodes, oldSubqueryAliasedNodes); subquerySelectStatementToUse = NODE_FACTORY.select( subquerySelectStatementToUse, !inParseNode.isSubqueryDistinct(), newSubquerySelectAliasedNodes, whereParseNodeAfterExtract); } else { /** * It is an Correlated subquery. */ List extractedAdditionalSelectAliasNodes = joinConditionExtractor.getAdditionalSelectNodes(); extractedSelectAliasNodeCount = extractedAdditionalSelectAliasNodes.size(); newSubquerySelectAliasedNodes = Lists. newArrayListWithExpectedSize( oldSubqueryAliasedNodes.size() + 1 + extractedAdditionalSelectAliasNodes.size()); newSubquerySelectAliasedNodes.add(NODE_FACTORY.aliasedNode( ParseNodeFactory.createTempAlias(), LiteralParseNode.ONE)); this.addNewAliasedNodes(newSubquerySelectAliasedNodes, oldSubqueryAliasedNodes); newSubquerySelectAliasedNodes.addAll(extractedAdditionalSelectAliasNodes); extractedJoinConditionParseNode = joinConditionExtractor.getJoinCondition(); boolean isAggregate = subquerySelectStatementToUse.isAggregate(); if(!isAggregate) { subquerySelectStatementToUse = NODE_FACTORY.select( subquerySelectStatementToUse, !inParseNode.isSubqueryDistinct(), newSubquerySelectAliasedNodes, whereParseNodeAfterExtract); } else { /** * If exists AggregateFunction,we must add the correlated join condition to both the * groupBy clause and select lists of the subquery. */ List newGroupByParseNodes = this.createNewGroupByParseNodes( extractedAdditionalSelectAliasNodes, subquerySelectStatementToUse); subquerySelectStatementToUse = NODE_FACTORY.select( subquerySelectStatementToUse, !inParseNode.isSubqueryDistinct(), newSubquerySelectAliasedNodes, whereParseNodeAfterExtract, newGroupByParseNodes, true); } } ParseNode joinOnConditionParseNode = getJoinConditionNodeForInSubquery( childParseNodes.get(0), newSubquerySelectAliasedNodes, subqueryTableTempAlias, extractedJoinConditionParseNode, extractedSelectAliasNodeCount); TableNode rhsTableNode = NODE_FACTORY.derivedTable( subqueryTableTempAlias, subquerySelectStatementToUse); JoinType joinType = isTopNode ? (inParseNode.isNegate() ? JoinType.Anti : JoinType.Semi) : JoinType.Left; ParseNode resultWhereParseNode = isTopNode ? null : NODE_FACTORY.isNull( NODE_FACTORY.column( NODE_FACTORY.table(null, subqueryTableTempAlias), newSubquerySelectAliasedNodes.get(0).getAlias(), null), !inParseNode.isNegate()); tableNode = NODE_FACTORY.join( joinType, tableNode, rhsTableNode, joinOnConditionParseNode, false); return resultWhereParseNode; } @Override public ParseNode visitLeave(ExistsParseNode node, List l) throws SQLException { boolean isTopNode = topNode == node; if (isTopNode) { topNode = null; } SubqueryParseNode subqueryNode = (SubqueryParseNode) l.get(0); SelectStatement subquery = fixSubqueryStatement(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. singletonList(subqueryNode)); } List additionalSelectNodes = conditionExtractor.getAdditionalSelectNodes(); List selectNodes = Lists.newArrayListWithExpectedSize(additionalSelectNodes.size() + 1); selectNodes.add(NODE_FACTORY.aliasedNode(ParseNodeFactory.createTempAlias(), LiteralParseNode.ONE)); selectNodes.addAll(additionalSelectNodes); subquery = NODE_FACTORY.select(subquery, true, selectNodes, where); ParseNode onNode = conditionExtractor.getJoinCondition(); TableNode rhsTable = NODE_FACTORY.derivedTable(rhsTableAlias, subquery); JoinType joinType = isTopNode ? (node.isNegate() ? JoinType.Anti : JoinType.Semi) : JoinType.Left; ParseNode ret = isTopNode ? 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, false); return ret; } @Override public ParseNode visitLeave(ComparisonParseNode node, List l) throws SQLException { boolean isTopNode = topNode == node; if (isTopNode) { topNode = null; } ParseNode secondChild = l.get(1); if (!(secondChild instanceof SubqueryParseNode)) { return super.visitLeave(node, l); } SubqueryParseNode subqueryNode = (SubqueryParseNode) secondChild; SelectStatement subquery = fixSubqueryStatement(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 comparison subquery, add LIMIT 2, expectSingleRow = true subquery = NODE_FACTORY.select(subquery, NODE_FACTORY.limit(NODE_FACTORY.literal(2))); subqueryNode = NODE_FACTORY.subquery(subquery, true); l = Lists.newArrayList(l.get(0), subqueryNode); node = NODE_FACTORY.comparison(node.getFilterOp(), l.get(0), l.get(1)); return super.visitLeave(node, l); } ParseNode rhsNode = null; boolean isGroupby = !subquery.getGroupBy().isEmpty(); boolean isAggregate = subquery.isAggregate(); List aliasedNodes = subquery.getSelect(); if (aliasedNodes.size() == 1) { rhsNode = aliasedNodes.get(0).getNode(); } else { List nodes = Lists. newArrayListWithExpectedSize(aliasedNodes.size()); for (AliasedNode aliasedNode : aliasedNodes) { nodes.add(aliasedNode.getNode()); } rhsNode = NODE_FACTORY.rowValueConstructor(nodes); } List additionalSelectNodes = conditionExtractor.getAdditionalSelectNodes(); List selectNodes = Lists.newArrayListWithExpectedSize(additionalSelectNodes.size() + 1); selectNodes.add(NODE_FACTORY.aliasedNode(ParseNodeFactory.createTempAlias(), rhsNode)); selectNodes.addAll(additionalSelectNodes); if (!isAggregate) { subquery = NODE_FACTORY.select(subquery, subquery.isDistinct(), selectNodes, where); } else { List groupbyNodes = this.createNewGroupByParseNodes( additionalSelectNodes, subquery); subquery = NODE_FACTORY.select(subquery, subquery.isDistinct(), selectNodes, where, groupbyNodes, true); } ParseNode onNode = conditionExtractor.getJoinCondition(); TableNode rhsTable = NODE_FACTORY.derivedTable(rhsTableAlias, subquery); JoinType joinType = isTopNode ? JoinType.Inner : JoinType.Left; ParseNode ret = NODE_FACTORY.comparison(node.getFilterOp(), l.get(0), NODE_FACTORY.column(NODE_FACTORY.table(null, rhsTableAlias), selectNodes.get(0).getAlias(), null)); tableNode = NODE_FACTORY.join(joinType, tableNode, rhsTable, onNode, !isAggregate || isGroupby); return ret; } @Override public ParseNode visitLeave(ArrayAnyComparisonNode node, List l) throws SQLException { List children = leaveArrayComparisonNode(node, l); if (children == l) return super.visitLeave(node, l); node = NODE_FACTORY.arrayAny(children.get(0), (ComparisonParseNode) children.get(1)); return node; } @Override public ParseNode visitLeave(ArrayAllComparisonNode node, List l) throws SQLException { List children = leaveArrayComparisonNode(node, l); if (children == l) return super.visitLeave(node, l); node = NODE_FACTORY.arrayAll(children.get(0), (ComparisonParseNode) children.get(1)); return node; } protected List leaveArrayComparisonNode(ParseNode node, List l) throws SQLException { boolean isTopNode = topNode == node; if (isTopNode) { topNode = null; } ParseNode firstChild = l.get(0); if (!(firstChild instanceof SubqueryParseNode)) { return l; } SubqueryParseNode subqueryNode = (SubqueryParseNode) firstChild; SelectStatement subquery = fixSubqueryStatement(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 any/all comparison subquery return l; } ParseNode rhsNode = null; boolean isNonGroupByAggregate = subquery.getGroupBy().isEmpty() && subquery.isAggregate(); List aliasedNodes = subquery.getSelect(); String derivedTableAlias = null; if (!subquery.getGroupBy().isEmpty()) { derivedTableAlias = ParseNodeFactory.createTempAlias(); aliasedNodes = createNewAliasedNodes(aliasedNodes); } if (aliasedNodes.size() == 1) { rhsNode = derivedTableAlias == null ? aliasedNodes.get(0).getNode() : NODE_FACTORY.column(NODE_FACTORY.table(null, derivedTableAlias), aliasedNodes.get(0).getAlias(), null); } else { List nodes = Lists. newArrayListWithExpectedSize(aliasedNodes.size()); for (AliasedNode aliasedNode : aliasedNodes) { nodes.add(derivedTableAlias == null ? aliasedNode.getNode() : NODE_FACTORY.column(NODE_FACTORY.table(null, derivedTableAlias), aliasedNode.getAlias(), null)); } rhsNode = NODE_FACTORY.rowValueConstructor(nodes); } if (!isNonGroupByAggregate) { rhsNode = NODE_FACTORY.function(DistinctValueAggregateFunction.NAME, Collections.singletonList(rhsNode)); } List additionalSelectNodes = conditionExtractor.getAdditionalSelectNodes(); List selectNodes = Lists.newArrayListWithExpectedSize(additionalSelectNodes.size() + 1); selectNodes.add(NODE_FACTORY.aliasedNode(ParseNodeFactory.createTempAlias(), rhsNode)); selectNodes.addAll(additionalSelectNodes); List groupbyNodes = Lists.newArrayListWithExpectedSize(additionalSelectNodes.size()); for (AliasedNode aliasedNode : additionalSelectNodes) { groupbyNodes.add(aliasedNode.getNode()); } if (derivedTableAlias == null) { subquery = NODE_FACTORY.select(subquery, false, selectNodes, where, groupbyNodes, true); } else { List derivedTableGroupBy = Lists.newArrayListWithExpectedSize(subquery.getGroupBy().size() + groupbyNodes.size()); derivedTableGroupBy.addAll(groupbyNodes); derivedTableGroupBy.addAll(subquery.getGroupBy()); List derivedTableSelect = Lists.newArrayListWithExpectedSize(aliasedNodes.size() + selectNodes.size() - 1); derivedTableSelect.addAll(aliasedNodes); for (int i = 1; i < selectNodes.size(); i++) { AliasedNode aliasedNode = selectNodes.get(i); String alias = ParseNodeFactory.createTempAlias(); derivedTableSelect.add(NODE_FACTORY.aliasedNode(alias, aliasedNode.getNode())); aliasedNode = NODE_FACTORY.aliasedNode(aliasedNode.getAlias(), NODE_FACTORY.column(NODE_FACTORY.table(null, derivedTableAlias), alias, null)); selectNodes.set(i, aliasedNode); groupbyNodes.set(i - 1, aliasedNode.getNode()); } SelectStatement derivedTableStmt = NODE_FACTORY.select(subquery, subquery.isDistinct(), derivedTableSelect, where, derivedTableGroupBy, true); subquery = NODE_FACTORY.select(NODE_FACTORY.derivedTable(derivedTableAlias, derivedTableStmt), subquery.getHint(), false, selectNodes, null, groupbyNodes, null, Collections. emptyList(), null, null, subquery.getBindCount(), true, false, Collections. emptyList(), subquery.getUdfParseNodes()); } ParseNode onNode = conditionExtractor.getJoinCondition(); TableNode rhsTable = NODE_FACTORY.derivedTable(rhsTableAlias, subquery); JoinType joinType = isTopNode ? JoinType.Inner : JoinType.Left; tableNode = NODE_FACTORY.join(joinType, tableNode, rhsTable, onNode, false); firstChild = NODE_FACTORY.column(NODE_FACTORY.table(null, rhsTableAlias), selectNodes.get(0).getAlias(), null); if (isNonGroupByAggregate) { firstChild = NODE_FACTORY.upsertStmtArrayNode(Collections.singletonList(firstChild)); } ComparisonParseNode secondChild = (ComparisonParseNode) l.get(1); secondChild = NODE_FACTORY.comparison(secondChild.getFilterOp(), secondChild.getLHS(), NODE_FACTORY.elementRef(Lists.newArrayList(firstChild, NODE_FACTORY.literal(1)))); return Lists.newArrayList(firstChild, secondChild); } private SelectStatement fixSubqueryStatement(SelectStatement select) { if (!select.isUnion()) return select; // Wrap as a derived table. return NODE_FACTORY.select(NODE_FACTORY.derivedTable(ParseNodeFactory.createTempAlias(), select), HintNode.EMPTY_HINT_NODE, false, select.getSelect(), null, null, null, null, null, null, select.getBindCount(), false, false, Collections. emptyList(), select.getUdfParseNodes()); } /** * Create new {@link AliasedNode}s by every {@link ParseNode} in subquerySelectAliasedNodes and generate new aliases * by {@link ParseNodeFactory#createTempAlias}. * and generate new Aliases for subquerySelectAliasedNodes, * @param subquerySelectAliasedNodes * @param addSelectOne * @return */ private List createNewAliasedNodes(List subquerySelectAliasedNodes) { List newAliasedNodes = Lists. newArrayListWithExpectedSize( subquerySelectAliasedNodes.size()); this.addNewAliasedNodes(newAliasedNodes, subquerySelectAliasedNodes); return newAliasedNodes; } /** * Add every {@link ParseNode} in oldSelectAliasedNodes to newSelectAliasedNodes and generate new aliases by * {@link ParseNodeFactory#createTempAlias}. * @param oldSelectAliasedNodes * @param addSelectOne * @return */ private void addNewAliasedNodes(List newSelectAliasedNodes, List oldSelectAliasedNodes) { for (int index = 0; index < oldSelectAliasedNodes.size(); index++) { AliasedNode oldSelectAliasedNode = oldSelectAliasedNodes.get(index); newSelectAliasedNodes.add(NODE_FACTORY.aliasedNode( ParseNodeFactory.createTempAlias(), oldSelectAliasedNode.getNode())); } } /** * Get the join conditions in order to rewrite InSubquery to Join. * @param lhsParseNode * @param rhsSubquerySelectAliasedNodes the first element is {@link LiteralParseNode#ONE}. * @param rhsSubqueryTableAlias * @param extractedJoinConditionParseNode For NonCorrelated subquery, it is null. * @param extractedSelectAliasNodeCount For NonCorrelated subquery, it is 0. * @throws SQLException */ private ParseNode getJoinConditionNodeForInSubquery( ParseNode lhsParseNode, List rhsSubquerySelectAliasedNodes, String rhsSubqueryTableAlias, ParseNode extractedJoinConditionParseNode, int extractedSelectAliasNodeCount) throws SQLException { List lhsParseNodes; if (lhsParseNode instanceof RowValueConstructorParseNode) { lhsParseNodes = ((RowValueConstructorParseNode) lhsParseNode).getChildren(); } else { lhsParseNodes = Collections.singletonList(lhsParseNode); } if (lhsParseNodes.size() != (rhsSubquerySelectAliasedNodes.size() - 1 - extractedSelectAliasNodeCount)) { throw new SQLExceptionInfo.Builder( SQLExceptionCode.SUBQUERY_RETURNS_DIFFERENT_NUMBER_OF_FIELDS) .build().buildException(); } int count = lhsParseNodes.size(); TableName rhsSubqueryTableName = NODE_FACTORY.table(null, rhsSubqueryTableAlias); List joinEqualParseNodes = Lists.newArrayListWithExpectedSize( count + (extractedJoinConditionParseNode == null ? 0: 1)); for (int index = 0; index < count; index++) { /** * The +1 is to skip the first {@link LiteralParseNode#ONE} */ ParseNode rhsNode = NODE_FACTORY.column( rhsSubqueryTableName, rhsSubquerySelectAliasedNodes.get(index + 1).getAlias(), null); joinEqualParseNodes.add(NODE_FACTORY.equal(lhsParseNodes.get(index), rhsNode)); } if(extractedJoinConditionParseNode != null) { joinEqualParseNodes.add(extractedJoinConditionParseNode); } return joinEqualParseNodes.size() == 1 ? joinEqualParseNodes.get(0) : NODE_FACTORY.and(joinEqualParseNodes); } /** * Combine every {@link ParseNode} in extractedAdditionalSelectAliasNodes and GroupBy clause of the * subquerySelectStatementToUse to get new GroupBy ParseNodes. * @param extractedAdditionalSelectAliasNodes * @param subquerySelectStatementToUse * @return */ private List createNewGroupByParseNodes( List extractedAdditionalSelectAliasNodes, SelectStatement subquerySelectStatementToUse) { List newGroupByParseNodes = Lists.newArrayListWithExpectedSize( extractedAdditionalSelectAliasNodes.size() + subquerySelectStatementToUse.getGroupBy().size()); for (AliasedNode aliasedNode : extractedAdditionalSelectAliasNodes) { newGroupByParseNodes.add(aliasedNode.getNode()); } newGroupByParseNodes.addAll(subquerySelectStatementToUse.getGroupBy()); return newGroupByParseNodes; } private static class JoinConditionExtractor extends AndRewriterBooleanParseNodeVisitor { private final TableName tableName; private ColumnResolveVisitor columnResolveVisitor; private List additionalSelectNodes; private List joinConditions; public JoinConditionExtractor(SelectStatement subquery, ColumnResolver outerResolver, PhoenixConnection connection, String tableAlias) throws SQLException { super(NODE_FACTORY); this.tableName = NODE_FACTORY.table(null, tableAlias); ColumnResolver localResolver = FromCompiler.getResolverForQuery(subquery, connection); this.columnResolveVisitor = new ColumnResolveVisitor(localResolver, outerResolver); this.additionalSelectNodes = Lists. newArrayList(); this.joinConditions = Lists. newArrayList(); } public List 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 protected ParseNode leaveBooleanNode(ParseNode node, List 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 public ParseNode visitLeave(ComparisonParseNode node, List 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 (TableNotFoundException e) { } 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; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy