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

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

There is a newer version: 5.1.0-HBase-2.0.0.2
Show 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.ArrayAllComparisonNode;
import org.apache.phoenix.parse.ArrayAnyComparisonNode;
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.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);
            }
        });
    }

    @Override
    public ParseNode visitLeave(InParseNode node, List l) throws SQLException {
        boolean isTopNode = topNode == node;
        if (isTopNode) {
            topNode = null;
        }
        
        SubqueryParseNode subqueryNode = (SubqueryParseNode) l.get(1);
        SelectStatement subquery = fixSubqueryStatement(subqueryNode.getSelectNode());
        String rhsTableAlias = ParseNodeFactory.createTempAlias();
        List selectNodes = fixAliasedNodes(subquery.getSelect(), true);
        subquery = NODE_FACTORY.select(subquery, !node.isSubqueryDistinct(), selectNodes);
        ParseNode onNode = getJoinConditionNode(l.get(0), selectNodes, rhsTableAlias);
        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(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 = Lists.newArrayListWithExpectedSize(additionalSelectNodes.size() + subquery.getGroupBy().size());
            for (AliasedNode aliasedNode : additionalSelectNodes) {
                groupbyNodes.add(aliasedNode.getNode());
            }
            groupbyNodes.addAll(subquery.getGroupBy());
            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 = fixAliasedNodes(aliasedNodes, false);
        }
        
        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());
    }
    
    private List fixAliasedNodes(List nodes, boolean addSelectOne) {
        List normNodes = Lists. newArrayListWithExpectedSize(nodes.size() + (addSelectOne ? 1 : 0));
        if (addSelectOne) {
            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 rhs, String rhsTableAlias) throws SQLException {
        List 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 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 {
        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 {
            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
        public List newElementList(int size) {
            return Lists. newArrayListWithExpectedSize(size);
        }

        @Override
        public void addElement(List 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 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 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 l) throws SQLException {
            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 - 2025 Weber Informatics LLC | Privacy Policy