Package com.foundationdb.sql.optimizer.rule

Source Code of com.foundationdb.sql.optimizer.rule.GroupJoinFinder$JoinedReferenceFinder

/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.foundationdb.sql.optimizer.rule;

import com.foundationdb.server.error.UnsupportedSQLException;

import com.foundationdb.sql.optimizer.plan.*;
import com.foundationdb.sql.optimizer.plan.JoinNode.JoinType;
import com.foundationdb.sql.optimizer.plan.TableGroupJoinTree.TableGroupJoinNode;

import com.foundationdb.server.types.texpressions.Comparison;

import com.foundationdb.ais.model.Column;
import com.foundationdb.ais.model.ForeignKey;
import com.foundationdb.ais.model.Group;
import com.foundationdb.ais.model.Join;
import com.foundationdb.ais.model.JoinColumn;
import com.foundationdb.ais.model.Table;

import com.foundationdb.util.ListUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/** Use join conditions to identify which tables are part of the same group.
*/
public class GroupJoinFinder extends BaseRule
{
    private static final Logger logger = LoggerFactory.getLogger(GroupJoinFinder.class);

    @Override
    protected Logger getLogger() {
        return logger;
    }

    @Override
    public void apply(PlanContext plan) {
        List<JoinIsland> islands = new JoinIslandFinder().find(plan.getPlan());
        // this sets outerTables on all subqueries
        new SubqueryBoundTablesTracker(plan) {}.run();
        moveAndNormalizeWhereConditions(islands);
        findGroupJoins(islands);
        reorderJoins(islands);
        isolateGroups(islands);
        moveJoinConditions(islands);
    }
   
    static class JoinIslandFinder implements PlanVisitor, ExpressionVisitor {
        private ColumnEquivalenceStack equivs = new ColumnEquivalenceStack();
        List<JoinIsland> result = new ArrayList<>();

        public List<JoinIsland> find(PlanNode root) {
            root.accept(this);
            return result;
        }

        @Override
        public boolean visitEnter(PlanNode n) {
            equivs.enterNode(n);
            return visit(n);
        }

        @Override
        public boolean visitLeave(PlanNode n) {
            equivs.leaveNode(n);
            return true;
        }

        @Override
        public boolean visit(PlanNode n) {
            if (n instanceof Joinable) {
                Joinable joinable = (Joinable)n;
                PlanWithInput output = joinable.getOutput();
                if (!(output instanceof Joinable)) {
                    result.add(new JoinIsland(joinable, output, equivs.get(), equivs.getFks()));
                }
            }
            return true;
        }

        @Override
        public boolean visitEnter(ExpressionNode n) {
            return visit(n);
        }

        @Override
        public boolean visitLeave(ExpressionNode n) {
            return true;
        }

        @Override
        public boolean visit(ExpressionNode n) {
            return true;
        }
    }

    // A subtree of joins.
    static class JoinIsland {
        Joinable root;
        PlanWithInput output;
        ConditionList whereConditions;
        List<TableGroupJoin> whereJoins;
        EquivalenceFinder<ColumnExpression> columnEquivs;
        EquivalenceFinder<ColumnExpression> fkEquivs;

        public JoinIsland(Joinable root, PlanWithInput output, EquivalenceFinder<ColumnExpression> columnEquivs,
                EquivalenceFinder<ColumnExpression> fkEquivs) {
            this.root = root;
            this.output = output;
            this.columnEquivs = columnEquivs;
            this.fkEquivs = fkEquivs;
            if (output instanceof Select)
                whereConditions = ((Select)output).getConditions();
        }

        public BaseQuery getQuery() {
            PlanWithInput output = root.getOutput();
            BaseQuery baseQuery = null;
            while (output != null) {
                if (output instanceof BaseQuery) {
                    baseQuery = (BaseQuery) output;
                }
                output = output.getOutput();
            }
            return baseQuery;
        }
    }

    // First pass: find all the WHERE conditions above inner joins
    // and put given join condition up there, since it's equivalent.
    // While there, normalize comparisons.
    protected void moveAndNormalizeWhereConditions(List<JoinIsland> islands) {
        for (JoinIsland island : islands) {
            if (island.whereConditions != null) {
                moveInnerJoinConditions(island.root, island.whereConditions);
                normalizeColumnComparisons(island.whereConditions);
            }
            normalizeColumnComparisons(island.root);
        }
    }

    // So long as there are INNER joins, move their conditions up to
    // the top-level join.
    protected void moveInnerJoinConditions(Joinable joinable,
                                           ConditionList whereConditions) {
        if (joinable.isJoin()) {
            JoinNode join = (JoinNode)joinable;
            if (joinable.isInnerJoin()) {
                ConditionList joinConditions = join.getJoinConditions();
                if (joinConditions != null) {
                    whereConditions.addAll(joinConditions);
                    joinConditions.clear();
                }
            }
            if (join.getJoinType() != JoinType.RIGHT) {
                moveInnerJoinConditions(join.getLeft(), whereConditions);
            }
            if (join.getJoinType() != JoinType.LEFT) {
                moveInnerJoinConditions(join.getRight(), whereConditions);
            }
        }
    }

    // Make comparisons involving a single column have
    // the form <col> <op> <expr>, with the child on the left in the
    // case of two columns, which is what we may then recognize as a
    // group join.
    protected void normalizeColumnComparisons(ConditionList conditions) {
        if (conditions == null) return;
        Collection<ConditionExpression> newExpressions = new ArrayList<>();
        for (ConditionExpression cond : conditions) {
            if (cond instanceof ComparisonCondition) {
                ComparisonCondition ccond = (ComparisonCondition)cond;
                ExpressionNode left = ccond.getLeft();
                ExpressionNode right = ccond.getRight();
                if (right.isColumn()) {
                    ColumnSource rightTable = ((ColumnExpression)right).getTable();
                    if (left.isColumn()) {
                        ColumnSource leftTable = ((ColumnExpression)left).getTable();
                        if (compareColumnSources(leftTable, rightTable) < 0) {
                            ccond.reverse();
                        }
                    }
                    else {
                        ccond.reverse();
                    }
                }
            }
        }
        conditions.addAll(newExpressions);
        ListUtils.removeDuplicates(conditions);
    }

    // Normalize join's conditions and any below it.
    protected void normalizeColumnComparisons(Joinable joinable) {
        if (joinable.isJoin()) {
            JoinNode join = (JoinNode)joinable;
            normalizeColumnComparisons(join.getJoinConditions());
            normalizeColumnComparisons(join.getLeft());
            normalizeColumnComparisons(join.getRight());
        }
    }

    // Third pass: put adjacent inner joined tables together in
    // left-deep ascending-ordinal order. E.g. (CO)I.
    protected void reorderJoins(List<JoinIsland> islands) {
        for (JoinIsland island : islands) {
            Joinable nroot = reorderJoins(island.root);           
            if (island.root != nroot) {
                island.output.replaceInput(island.root, nroot);
                island.root = nroot;
            }
        }
    }

    protected Joinable reorderJoins(Joinable joinable) {
        if (countInnerJoins(joinable) > 1) {
            List<Joinable> joins = new ArrayList<>();
            getInnerJoins(joinable, joins);
            for (int i = 0; i < joins.size(); i++) {
                joins.set(i, reorderJoins(joins.get(i)));
            }
            return orderInnerJoins(joins);
        }
        else if (joinable.isJoin()) {
            JoinNode join = (JoinNode)joinable;
            join.setLeft(reorderJoins(join.getLeft()));
            join.setRight(reorderJoins(join.getRight()));
            if (compareJoinables(join.getLeft(), join.getRight()) > 0)
                join.reverse();
        }
        return joinable;
    }

    // Make inner joins into a tree of group-tree / non-table.
    protected Joinable orderInnerJoins(List<Joinable> joinables) {
        Map<TableGroup,List<TableSource>> groups =
            new HashMap<>();
        List<Joinable> nonTables = new ArrayList<>();
        for (Joinable joinable : joinables) {
            if (joinable instanceof TableSource) {
                TableSource table = (TableSource)joinable;
                TableGroup group = table.getGroup();
                List<TableSource> entry = groups.get(group);
                if (entry == null) {
                    entry = new ArrayList<>();
                    groups.put(group, entry);
                }
                entry.add(table);
            }
            else
                nonTables.add(joinable);
        }
        joinables.clear();
        // Make order of groups predictable.
        List<TableGroup> keys = new ArrayList<>(groups.keySet());
        Collections.sort(keys, tableGroupComparator);
        for (TableGroup gkey : keys) {
            List<TableSource> group = groups.get(gkey);
            Collections.sort(group, tableSourceComparator);
            joinables.add(constructLeftInnerJoins(group));
        }
        joinables.addAll(nonTables);
        if (joinables.size() > 1)
            return constructRightInnerJoins(joinables);
        else
            return joinables.get(0);
    }

    // Group flattening is left-recursive.
    protected Joinable constructLeftInnerJoins(List<? extends Joinable> joinables) {
        Joinable result = joinables.get(0);
        for (int i = 1; i < joinables.size(); i++) {
            result = new JoinNode(result, joinables.get(i), JoinType.INNER);
        }
        return result;
    }

    // Nested loop joins are right-recursive.
    protected Joinable constructRightInnerJoins(List<? extends Joinable> joinables) {
        int size = joinables.size();
        Joinable result = joinables.get(--size);
        while (size > 0) {
            result = new JoinNode(joinables.get(--size), result, JoinType.INNER);
           
            // Reset the FK Join if we recreate the Join Tree
            JoinNode node = (JoinNode) result;
            if (node.getRight().isTable() &&
                    (((TableSource)node.getRight()).getParentFKJoin() != null)) {
                node.setFKJoin(((TableSource)node.getRight()).getParentFKJoin());
            }
            if (node.getFKJoin() == null && node.getLeft().isTable() &&
                    (((TableSource)node.getLeft()).getParentFKJoin() != null)) {
                node.setFKJoin(((TableSource)node.getLeft()).getParentFKJoin());
            }
        }
        return result;
    }

    // Second pass: find join conditions corresponding to group joins.
    protected void findGroupJoins(List<JoinIsland> islands) {
        for (JoinIsland island : islands) {
            List<TableGroupJoin> whereJoins = new ArrayList<>();
            findGroupJoins(island.root, new ArrayDeque<JoinNode>(),
                           island.whereConditions, whereJoins,
                           island.columnEquivs);
            island.whereJoins = whereJoins;
        }

        //Find FK Joins
        for (JoinIsland island : islands) {
            findFKGroups(island.root,
                    new ArrayDeque<JoinNode>(),
                    island.whereConditions,
                    island.columnEquivs,
                    island.fkEquivs);
        }

        // find single groups
        for (JoinIsland island : islands) {
            findSingleGroups(island.root);
        }
    }

    protected void findGroupJoins(Joinable joinable,
                                  Deque<JoinNode> outputJoins,
                                  ConditionList whereConditions,
                                  List<TableGroupJoin> whereJoins,
                                  EquivalenceFinder<ColumnExpression> columnEquivs) {
        if (joinable.isTable()) {
            TableSource table = (TableSource)joinable;
            for (JoinNode output : outputJoins) {
                ConditionList conditions = output.getJoinConditions();
                TableGroupJoin tableJoin = findParentJoin(table, conditions, columnEquivs);
                if (tableJoin != null) {
                    output.setGroupJoin(tableJoin);
                    return;
                }
            }
            TableGroupJoin tableJoin = findParentJoin(table, whereConditions, columnEquivs);
            if (tableJoin != null) {
                whereJoins.add(tableJoin); // Position after reordering.
                return;
            }
        }
        else if (joinable.isJoin()) {
            JoinNode join = (JoinNode)joinable;
            outputJoins.push(join);
            if (join.isInnerJoin()) {
                findGroupJoins(join.getLeft(), outputJoins, whereConditions, whereJoins, columnEquivs);
                findGroupJoins(join.getRight(), outputJoins, whereConditions, whereJoins, columnEquivs);
            }
            else {
                Deque<JoinNode> singleJoin = new ArrayDeque<>(1);
                singleJoin.push(join);
                // In a LEFT OUTER JOIN, the outer half is allowed to
                // take from higher conditions.
                if (join.getJoinType() == JoinType.LEFT)
                    findGroupJoins(join.getLeft(), outputJoins, whereConditions, whereJoins, columnEquivs);
                else
                    findGroupJoins(join.getLeft(), singleJoin, null, null, columnEquivs);
                if (join.getJoinType() == JoinType.RIGHT)
                    findGroupJoins(join.getRight(), outputJoins, whereConditions, whereJoins, columnEquivs);
                else
                    findGroupJoins(join.getRight(), singleJoin, null, null, columnEquivs);
            }
            outputJoins.pop();
        }
    }

    // Find a condition among the given conditions that matches the
    // parent join for the given table.
    protected TableGroupJoin findParentJoin(TableSource childTable,
                                            ConditionList conditions,
                                            EquivalenceFinder<ColumnExpression> columnEquivs) {
        if ((conditions == null) || conditions.isEmpty()) return null;
        TableNode childNode = childTable.getTable();
        Join groupJoin = childNode.getTable().getParentJoin();
        if (groupJoin == null) return null;
        TableNode parentNode = childNode.getTree().getNode(groupJoin.getParent());
        if (parentNode == null) return null;
        List<JoinColumn> joinColumns = groupJoin.getJoinColumns();
        int ncols = joinColumns.size();
        Map<TableSource,GroupJoinConditions> parentTables = new HashMap<>();

        for (int i = 0; i < ncols; ++i) {
            if (!findGroupCondition(joinColumns, i, childTable, conditions, true, parentTables, columnEquivs)) {
                if (!findGroupCondition(joinColumns, i, childTable, conditions, false, parentTables, columnEquivs)) {
                    return null; // join column had no direct or equivalent group joins, so we know the answer
                }
            }
        }
       
        TableSource parentTable = null;
        GroupJoinConditions groupJoinConditions = null;
        for (Map.Entry<TableSource,GroupJoinConditions> entry : parentTables.entrySet()) {
            boolean found = true;
            for (ComparisonCondition elem : entry.getValue().getConditions()) {
                if (elem == null) {
                    found = false;
                    break;
                }
            }
            if (found) {
                if (parentTable == null) {
                    parentTable = entry.getKey();
                    groupJoinConditions = entry.getValue();
                }
                else {
                    // TODO: What we need is something
                    // earlier to decide that the primary
                    // keys are equated and so share the
                    // references somehow.
                    ConditionExpression c1 = groupJoinConditions.getConditions().get(0);
                    ConditionExpression c2 = entry.getValue().getConditions().get(0);
                    if (conditions.indexOf(c1) > conditions.indexOf(c2)) {
                        // Make the order predictable for tests.
                        ConditionExpression temp = c1;
                        c1 = c2;
                        c2 = temp;
                    }
                    throw new UnsupportedSQLException("Found two possible parent joins",
                                                      c2.getSQLsource());
                }
            }
        }
        if (parentTable == null) return null;
        TableGroup group = parentTable.getGroup();
        if (group == null) {
            group = childTable.getGroup();
            if (group == null)
                group = new TableGroup(groupJoin.getGroup());
        }
        else if (childTable.getGroup() != null) {
            group.merge(childTable.getGroup());
        }
        if (!tableAllowedInGroup(group, childTable))
            return null;
        groupJoinConditions.installGeneratedConditionsTo(conditions);
        return new TableGroupJoin(group, parentTable, childTable, groupJoinConditions.getConditions(), groupJoin);
    }

    private boolean findGroupCondition(List<JoinColumn> joinColumns, int i, TableSource childTable,
                                       ConditionList conditions, boolean requireExact,
                                       Map<TableSource, GroupJoinConditions> parentTables,
                                       EquivalenceFinder<ColumnExpression> columnEquivs)
    {
        int ncols = joinColumns.size();
        boolean found = false;
        for (ConditionExpression condition : conditions) {
            List<ColumnExpression> colExprs = findColumnExpressions(condition);
            if (!colExprs.isEmpty()) {
                ComparisonCondition ccond = (ComparisonCondition)condition;
                ComparisonCondition normalized = normalizedCond(
                        joinColumns.get(i).getChild(), joinColumns.get(i).getParent(),
                        childTable,
                        colExprs.get(0),
                        colExprs.get(1),
                        ccond,
                        requireExact,
                        columnEquivs
                );
                if (normalized != null) {
                    found = true;
                    ColumnExpression rnorm = (ColumnExpression) normalized.getRight();
                    TableSource parentSource = (TableSource) rnorm.getTable();
                    GroupJoinConditions entry = parentTables.get(parentSource);
                    if (entry == null) {
                        entry = new GroupJoinConditions(ncols);
                        parentTables.put(parentSource, entry);
                    }
                    entry.set(i, normalized, normalized != ccond);
                }
               
            }
        }
        return found;
    }
   
    private static class GroupJoinConditions {
        private List<ComparisonCondition> conditions;
        private List<ComparisonCondition> generatedConditions;

        public GroupJoinConditions(int ncols) {
            this.conditions = new ArrayList<>(Collections.<ComparisonCondition>nCopies(ncols, null));
        }
       
        public void set(int i, ComparisonCondition condition, boolean wasGenerated) {
            conditions.set(i, condition);
            if (wasGenerated) {
                if (generatedConditions == null)
                    generatedConditions = new ArrayList<>(conditions.size());
                generatedConditions.add(condition);
            }
        }
       
        public List<ComparisonCondition> getConditions() {
            return conditions;
        }

        public void installGeneratedConditionsTo(ConditionList conditionList) {
            if (generatedConditions != null)
                conditionList.addAll(generatedConditions);
        }
    }

    private ComparisonCondition normalizedCond(Column childColumn, Column parentColumn,
                                               TableSource childSource,
                                               ColumnExpression lcol, ColumnExpression rcol,
                                               ComparisonCondition originalCond, boolean requireExact,
                                               EquivalenceFinder<ColumnExpression> columnEquivs)
    {
        // look for child
        ColumnExpression childEquiv = null;
        if (lcol.getTable() == childSource && lcol.getColumn() == childColumn) {
            childEquiv = lcol;
        }
        else {
            for (ColumnExpression equivalent : columnEquivs.findEquivalents(lcol)) {
                if (equivalent.getTable() == childSource && equivalent.getColumn() == childColumn) {
                    childEquiv = equivalent;
                    break;
                }
            }
        }
        if (childEquiv == null)
            return null;
       
        // look for parent
        ColumnExpression parentEquiv = null;
        if (rcol.getColumn() == parentColumn)  {
            parentEquiv = rcol;
        }
        else {
            for (ColumnExpression equivalent : columnEquivs.findEquivalents(rcol)) {
                if (equivalent.getColumn() == parentColumn) {
                    parentEquiv = equivalent;
                    break;
                }
            }
        }
        if (parentEquiv == null)
            return null;
       
        boolean isExact = childEquiv == lcol && parentEquiv == rcol;
        if (requireExact) {
            return isExact ? originalCond : null;
        }
        else {
            assert ! isExact : "exact match found; should have been discovered by previous invocation at call site";
            return new ComparisonCondition(
                    Comparison.EQ,
                    childEquiv,
                    parentEquiv,
                    originalCond.getSQLtype(),
                    originalCond.getSQLsource(),
                    originalCond.getType()
            );
        }
    }

    protected boolean tableAllowedInGroup(TableGroup group, TableSource childTable) {
        return true;
    }

    protected void findFKGroups(Joinable joinable,
            Deque<JoinNode> outputJoins,
            ConditionList whereConditions,
            EquivalenceFinder<ColumnExpression> columnEquivs,
            EquivalenceFinder<ColumnExpression> fkEquivs) {
        if (joinable.isTable()) {
            TableSource table = (TableSource)joinable;

            for (JoinNode output : outputJoins) {
                ConditionList conditions = output.getJoinConditions();
                if (table.getGroup() == null) {
                    findParentFKJoin (table, conditions, columnEquivs,fkEquivs);
                } else {
                    break;
                }
            }
            if (table.getGroup() == null) {
                findParentFKJoin (table, whereConditions, columnEquivs, fkEquivs);
            }
        } else if (joinable.isJoin()) {
            JoinNode join = (JoinNode)joinable;
            outputJoins.push(join);
            findFKGroups (join.getLeft(), outputJoins, whereConditions, columnEquivs, fkEquivs);
            findFKGroups (join.getRight(), outputJoins, whereConditions, columnEquivs, fkEquivs);
            outputJoins.pop();
           
            if (join.getRight().isTable() &&
                    (((TableSource)join.getRight()).getParentFKJoin() != null)) {
                join.setFKJoin(((TableSource)join.getRight()).getParentFKJoin());
            }
        }
    }

    // Find a condition among the given conditions that matches the
    // parent FK join for the given table.
    protected void findParentFKJoin(TableSource childTable,
                                            ConditionList conditions,
                                            EquivalenceFinder<ColumnExpression> columnEquivs,
                                            EquivalenceFinder<ColumnExpression> fkEquivs) {
        if ((conditions == null) || conditions.isEmpty()) return;
        TableNode childNode = childTable.getTable();
        if (childNode.getTable().getForeignKeys() == null || childNode.getTable().getForeignKeys().isEmpty()) return;
       
        for (ForeignKey key : childNode.getTable().getReferencingForeignKeys()) {
            TableFKJoin join = findFKConditions(childTable, conditions, key, columnEquivs, fkEquivs);
            if (join != null) {
                if (childTable.getGroup() == null) {
                    childTable.setGroup(new TableGroup(childTable.getTable().getTable().getGroup()));
                    childTable.setParentFKJoin(join);
                } else {
                    // TODO: If we find a child with 2 parents, don't mark this child as a
                    // FK Join. The JoinAndIndexPicker for the FK selection isn't
                    // (currently) smart enough to reorder the two joins correctly.
                    childTable.setParentFKJoin(null);
                }
            }
        }
    }
   
    protected TableFKJoin findFKConditions (TableSource childSource,
                ConditionList conditions,
                ForeignKey key,
                EquivalenceFinder<ColumnExpression> columnEquivs,
                EquivalenceFinder<ColumnExpression> fkEquivs) {
        List<ComparisonCondition> elements = new ArrayList<ComparisonCondition>(key.getReferencedColumns().size());
        TableFKJoin join = null;
        ColumnExpression parentEquiv = null;
        List<JoinColumn> joinColumns = key.getJoinColumns();
       
        for (int i = 0; i < joinColumns.size(); i++) {
            for (ConditionExpression condition : conditions) {
                List<ColumnExpression> columns = findColumnExpressions (condition);
                if (!columns.isEmpty()) {
                    ComparisonCondition ccond = (ComparisonCondition)condition;
                    ComparisonCondition normalized = normalizedCond(
                            joinColumns.get(i).getChild(), joinColumns.get(i).getParent(),
                            childSource,
                            columns.get(0),
                            columns.get(1),
                            ccond,
                            true,
                            fkEquivs
                    );
                   
                    if (normalized == null) {
                        normalized = normalizedCond(
                                joinColumns.get(i).getChild(), joinColumns.get(i).getParent(),
                                childSource,
                                columns.get(0),
                                columns.get(1),
                                ccond,
                                false,
                                fkEquivs
                        );                       
                    }
                   
                    if (normalized != null) {
                        elements.add(normalized);
                        parentEquiv = (ColumnExpression)normalized.getRight();
                        break;
                    }
                }
            }
        }
        if (elements.size() == key.getReferencedColumns().size()) {
            join = new TableFKJoin ((TableSource)parentEquiv.getTable(), childSource, elements, key);
        }
        return join;
    }
   
    protected List<ColumnExpression> findColumnExpressions(ConditionExpression condition) {
        ArrayList<ColumnExpression> columns = new ArrayList<> (2);
        if (condition instanceof ComparisonCondition) {
            ComparisonCondition ccond = (ComparisonCondition)condition;
            if (ccond.getOperation() == Comparison.EQ) {
                ExpressionNode left = ccond.getLeft();
                ExpressionNode right = ccond.getRight();
                if (left.isColumn() && right.isColumn()) {
                    ColumnExpression lcol = (ColumnExpression)left;
                    ColumnExpression rcol = (ColumnExpression)right;
                    if ((lcol.getTable() instanceof TableSource) && (rcol.getTable() instanceof TableSource)) {
                        columns.add(lcol);
                        columns.add(rcol);
                    }
                }
            }
        }
        return columns;
    }
   
    protected void findSingleGroups(Joinable joinable) {
        if (joinable.isTable()) {
            TableSource table = (TableSource)joinable;
            if (table.getGroup() == null) {
                table.setGroup(new TableGroup(table.getTable().getTable().getGroup()));
            }
        }
        else if (joinable.isJoin()) {
            JoinNode join = (JoinNode)joinable;
            findSingleGroups(join.getLeft());
            findSingleGroups(join.getRight());
        }
    }

    // Fourth pass: wrap contiguous group joins in separate joinable.
    // We have done out best with the inner joins to make this possible,
    // but some outer joins may require that a TableGroup be broken up into
    // multiple TableJoins.
    protected void isolateGroups(List<JoinIsland> islands) {
        for (JoinIsland island : islands) {
            TableGroupJoinNode tree = isolateGroupJoins(island.root);
            if (tree != null) {
                Joinable nroot = groupJoinTree(tree, island.root);
                island.output.replaceInput(island.root, nroot);
                island.root = nroot;
            }
        }
    }

    protected TableGroupJoinNode isolateGroupJoins(Joinable joinable) {
        if (joinable.isTable()) {
            TableSource table = (TableSource)joinable;
            assert (table.getGroup() != null);
            return new TableGroupJoinNode(table);
        }
        if (!joinable.isJoin())
            return null;
        JoinNode join = (JoinNode)joinable;
        Joinable left = join.getLeft();
        Joinable right = join.getRight();
        TableGroupJoinNode leftTree = isolateGroupJoins(left);
        TableGroupJoinNode rightTree = isolateGroupJoins(right);
        if ((leftTree != null) && (rightTree != null) &&
            // All tables below the two sides must be from the same group.
            (leftTree.getTable().getGroup() == rightTree.getTable().getGroup())) {
            // An outer join condition must be one that can be
            // done before flattening because after that it's too
            // late to get back the outer side if the test never
            // succeeds.
            boolean joinOK;
            switch (join.getJoinType()) {
            case INNER:
                joinOK = true;
                break;
            case LEFT:
                joinOK = checkJoinConditions(join.getJoinConditions(), leftTree, rightTree);
                break;
            case RIGHT:
                // Cannot allow any non-group conditions, since even
                // one only on parent would kill the child because
                // that's how Select_HKeyOrdered works.
                joinOK = checkJoinConditions(join.getJoinConditions(), null, leftTree);
                break;
            default:
                joinOK = false;
            }
            if (joinOK) {
                // Still need to be able to splice them together.
                TableGroupJoinNode tree;
                int leftDepth = leftTree.getTable().getTable().getDepth();
                int rightDepth = rightTree.getTable().getTable().getDepth();
                if (leftDepth < rightDepth)
                    tree = spliceGroupJoins(leftTree, rightTree, join.getJoinType());
                else if (rightDepth < leftDepth)
                    tree = spliceGroupJoins(rightTree, leftTree, join.getJoinType());
                else
                    tree = null;
                if (tree != null) {
                    return tree;
                }
            }
        }
        // Did not manage to coalesce. Put in any intermediate trees.
        if (leftTree != null)
            join.setLeft(groupJoinTree(leftTree, left));
        if (rightTree != null)
            join.setRight(groupJoinTree(rightTree, right));
        // Make arbitrary joins LEFT not RIGHT.
        if (join.getJoinType() == JoinType.RIGHT)
            join.reverse();
        return null;
    }

    protected TableGroupJoinTree groupJoinTree(TableGroupJoinNode root, Joinable joins) {
        TableGroupJoinTree tree = new TableGroupJoinTree(root);
        Set<TableSource> required = new HashSet<>();
        getRequiredTables(joins, required);
        tree.setRequired(required);
        for (TableGroupJoinNode node : root) {
            node.getTable().setOutput(tree); // Instead of join that is going away.
        }
        return tree;
    }

    // Get all the tables reachable via inner joins from here.
    protected void getRequiredTables(Joinable joinable, Set<TableSource> required) {
        if (joinable instanceof TableSource) {
            required.add((TableSource)joinable);
        }
        else if (joinable instanceof JoinNode) {
            JoinNode join = (JoinNode)joinable;
            if (join.getJoinType() != JoinType.RIGHT)
                getRequiredTables(join.getLeft(), required);
            if (join.getJoinType() != JoinType.LEFT)
                getRequiredTables(join.getRight(), required);
        }
    }

    // Combine trees at the proper branch point.
    protected TableGroupJoinNode spliceGroupJoins(TableGroupJoinNode parent,
                                                  TableGroupJoinNode child,
                                                  JoinType joinType) {
        TableGroupJoinNode branch = parent.findTable(child.getTable().getParentTable());
        if (branch == null)
            return null;
        child.setParent(branch);
        child.setParentJoinType(joinType);
        TableGroupJoinNode prev = null;
        while (true) {
            TableGroupJoinNode next = (prev == null) ? branch.getFirstChild() : prev.getNextSibling();
            if ((next == null) ||
                (next.getTable().getTable().getOrdinal() > child.getTable().getTable().getOrdinal())) {
                child.setNextSibling(next);
                if (prev == null)
                    branch.setFirstChild(child);
                else
                    prev.setNextSibling(child);
                break;
            }
            prev = next;
        }
        return parent;
    }

    protected boolean checkJoinConditions(ConditionList joinConditions,
                                          TableGroupJoinNode outer,
                                          TableGroupJoinNode inner) {
        if (hasIllegalReferences(joinConditions, outer))
            return false;
        inner.setJoinConditions(joinConditions);
        return true;
    }

    // See whether any expression in the join condition other than the
    // grouping join references a table under the given tree.
    protected boolean hasIllegalReferences(ConditionList joinConditions,
                                           TableGroupJoinNode fromTree) {
        JoinedReferenceFinder finder = null;
        if (joinConditions != null) {
            for (ConditionExpression condition : joinConditions) {
                if (condition.getImplementation() == ConditionExpression.Implementation.GROUP_JOIN)
                    continue;   // Group condition okay.
                if (fromTree == null)
                    return true; // All non-group disallowed.
                if (finder == null)
                    finder = new JoinedReferenceFinder(fromTree);
                if (finder.find(condition))
                    return true; // Has references to other side.
            }
        }
        return false;
    }

    static class JoinedReferenceFinder implements PlanVisitor, ExpressionVisitor {
        private TableGroupJoinNode fromTree;
        private boolean found;

        public JoinedReferenceFinder(TableGroupJoinNode fromTree) {
            this.fromTree = fromTree;
        }

        public boolean find(ExpressionNode expression) {
            found = false;
            expression.accept(this);
            return found;
        }

        @Override
        public boolean visitEnter(PlanNode n) {
            return visit(n);
        }

        @Override
        public boolean visitLeave(PlanNode n) {
            return true;
        }

        @Override
        public boolean visit(PlanNode n) {
            return true;
        }

        @Override
        public boolean visitEnter(ExpressionNode n) {
            return visit(n);
        }

        @Override
        public boolean visitLeave(ExpressionNode n) {
            return true;
        }

        @Override
        public boolean visit(ExpressionNode n) {
            if (n instanceof ColumnExpression) {
                ColumnSource table = ((ColumnExpression)n).getTable();
                if (table instanceof TableSource) {
                    if (fromTree.findTable((TableSource)table) != null) {
                        found = true;
                    }
                }
            }
            return true;
        }
    }

    // Fifth pass: move the WHERE conditions back to their actual
    // joins, which may be different from the ones they were on in the
    // original query and reject any group joins that now cross TableJoins.
    protected void moveJoinConditions(List<JoinIsland> islands) {
        for (JoinIsland island : islands) {
            moveJoinConditions(island.root, island.whereConditions, island.whereJoins);
            if (island.whereConditions != null) {
                Iterator<ConditionExpression> iterator = island.whereConditions.iterator();
                while (iterator.hasNext()) {
                    ConditionExpression condition = iterator.next();
                    Set<ColumnSource> columnSources = new ConditionColumnSourcesFinder().find(condition);
                    columnSources.removeAll(island.getQuery().getOuterTables());
                    if (moveWhereCondition(columnSources, condition, island.root)) {
                        iterator.remove();
                    }
                }
            }

        }
    }

    protected void moveJoinConditions(Joinable joinable,
                                      ConditionList whereConditions, List<TableGroupJoin> whereJoins) {
        if (joinable instanceof TableGroupJoinTree) {
            for (TableGroupJoinNode table : (TableGroupJoinTree)joinable) {
                TableGroupJoin tableJoin = table.getTable().getParentJoin();
                if (tableJoin != null) {
                    if (table.getParent() == null) {
                        tableJoin.reject(); // Did not make it into the group.
                    }
                    else if (whereJoins.contains(tableJoin)) {
                        List<ComparisonCondition> joinConditions = tableJoin.getConditions();
                        // Move down from WHERE conditions to join conditions.
                        if (table.getJoinConditions() == null)
                            table.setJoinConditions(new ConditionList());
                        table.getJoinConditions().addAll(joinConditions);
                        whereConditions.removeAll(joinConditions);
                    }
                }
            }
        }
        else if (joinable instanceof JoinNode) {
            JoinNode join = (JoinNode)joinable;
            join.setGroupJoin(null);
            moveJoinConditions(join.getLeft(), whereConditions, whereJoins);
            moveJoinConditions(join.getRight(), whereConditions, whereJoins);
        }
    }
    /**
     * Moves the given condition as far down as possible, so long as the tableSources are visible
     * @param tableSources the tableSources referenced by the condition. All sources that are declared in a nested
     *                     join will be removed from the tableSources
     * @return true if the condition was added to a joinConditions
     */
    private boolean moveWhereCondition(Set<ColumnSource> tableSources, ConditionExpression condition, Joinable joinable) {
        // If we move Any/Exists Conditions down, the InConditionReverser won't be able to find them,
        // so they get to stay in the where clause
        if (condition instanceof AnyCondition || condition instanceof ExistsCondition) {
            return false;
        }
        if (joinable instanceof TableGroupJoinTree) {
            for (TableGroupJoinNode table : (TableGroupJoinTree)joinable) {
                tableSources.remove(table.getTable());
            }
        } else if (joinable instanceof JoinNode)
        {
            JoinNode join = (JoinNode)joinable;
            if (join.isInnerJoin()) {
                Set<ColumnSource> forLeft = new HashSet<>(tableSources);
                Set<ColumnSource> forRight = new HashSet<>(tableSources);
                if (moveWhereCondition(forLeft, condition, join.getLeft())) {
                    return true;
                }
                if (forLeft.isEmpty()) {
                    tableSources.clear();
                } else {
                    if (moveWhereCondition(forRight, condition, join.getRight())) {
                        return true;
                    }
                    tableSources.retainAll(forLeft);
                    tableSources.retainAll(forRight);
                }
                if (tableSources.isEmpty()) {
                    if (join.getJoinConditions() == null) {
                        join.setJoinConditions(new ConditionList());
                    }
                    join.getJoinConditions().add(condition);
                    return true;
                }
            }
            return false;
        } else if (joinable instanceof TableSource)
        {
            tableSources.remove(joinable);
            return false;
        }
        return false;
    }

    static final Comparator<TableGroup> tableGroupComparator = new Comparator<TableGroup>() {
        @Override
        public int compare(TableGroup tg1, TableGroup tg2) {
            Group g1 = tg1.getGroup();
            Group g2 = tg2.getGroup();
            if (g1 != g2)
                return g1.getName().compareTo(g2.getName());
            int o1 = tg1.getMinOrdinal();
            int o2 = tg2.getMinOrdinal();
            if (o1 == o2) {
                TableSource ts1 = tg1.findByOrdinal(o1);
                TableSource ts2 = tg2.findByOrdinal(o2);
                return ts1.getName().compareTo(ts2.getName());
            }
            return o1 - o2;
        }
    };

    static final Comparator<TableSource> tableSourceComparator = new Comparator<TableSource>() {
        public int compare(TableSource t1, TableSource t2) {
            return compareTableSources(t1, t2);
        }
    };

    protected static int compareColumnSources(ColumnSource c1, ColumnSource c2) {
        if (c1 instanceof TableSource) {
            if (!(c2 instanceof TableSource))
                return +1;
            return compareTableSources((TableSource)c1, (TableSource)c2);
        }
        else if (c2 instanceof TableSource)
            return -1;
        else
            return 0;
    }
   
    protected static int compareTableSources(TableSource ts1, TableSource ts2) {
        TableNode t1 = ts1.getTable();
        Table ut1 = t1.getTable();
        Group g1 = ut1.getGroup();
        TableGroup tg1 = ts1.getGroup();
        TableNode t2 = ts2.getTable();
        Table ut2 = t2.getTable();
        Group g2 = ut2.getGroup();
        TableGroup tg2 = ts2.getGroup();
        if (g1 != g2)
            return g1.getName().compareTo(g2.getName());
        if (tg1 == tg2) {       // Including null because not yet computed.
            if (ut1 == ut2)
                return ts1.getName().compareTo(ts2.getName());
            return t1.getOrdinal() - t2.getOrdinal();
        }
        return tg1.getMinOrdinal() - tg2.getMinOrdinal();
    }

    // Return size of directly-reachable subtree of all simple inner joins.
    protected static int countInnerJoins(Joinable joinable) {
        if (!isSimpleInnerJoin(joinable))
            return 0;
        return 1 +
            countInnerJoins(((JoinNode)joinable).getLeft()) +
            countInnerJoins(((JoinNode)joinable).getRight());
    }


    // Accumulate operands of directly-reachable subtree of simple inner joins.
    protected static void getInnerJoins(Joinable joinable, Collection<Joinable> into) {
        if (!isSimpleInnerJoin(joinable))
            into.add(joinable);
        else {
            getInnerJoins(((JoinNode)joinable).getLeft(), into);
            getInnerJoins(((JoinNode)joinable).getRight(), into);
        }
    }

    // Can this inner join be reorderd?
    // TODO: If there are inner joins with conditions that didn't get
    // moved by the first pass, leave them alone. That will miss
    // opportunities.  Need to have a way to accumulate those
    // conditions and put them into the join tree.
    protected static boolean isSimpleInnerJoin(Joinable joinable) {
        return (joinable.isInnerJoin() && !((JoinNode)joinable).hasJoinConditions());
    }

    protected static int compareJoinables(Joinable j1, Joinable j2) {
        if (j1.isTable() && j2.isTable())
            return compareTableSources((TableSource)j1, (TableSource)j2);
        Group g1 = singleGroup(j1);
        Group g2 = singleGroup(j2);
        if (g1 == null) {
            if (g2 != null)
                return -1;
            else
                return 0;
        }
        else if (g2 == null)
            return +1;
        if (g1 != g2)
            return g1.getName().compareTo(g2.getName());
        int[] range1 = ordinalRange(j1);
        int[] range2 = ordinalRange(j2);
        if (range1[1] < range2[0])
            return -1;
        else if (range1[0] > range2[1])
            return +1;
        else
            return 0;
    }

    protected static Group singleGroup(Joinable j) {
        if (j.isTable())
            return ((TableSource)j).getTable().getGroup();
        else if (j.isJoin()) {
            JoinNode join = (JoinNode)j;
            Group gl = singleGroup(join.getLeft());
            Group gr = singleGroup(join.getRight());
            if (gl == gr)
                return gl;
            else
                return null;
        }
        else
            return null;
    }

    protected static int[] ordinalRange(Joinable j) {
        if (j.isTable()) {
            int ord = ((TableSource)j).getTable().getOrdinal();
            return new int[] { ord, ord };
        }
        else if (j.isJoin()) {
            JoinNode join = (JoinNode)j;
            int[] ol = ordinalRange(join.getLeft());
            int[] or = ordinalRange(join.getRight());
            if (ol[0] > or[0])
                ol[0] = or[0];
            if (ol[1] < or[1])
                ol[1] = or[1];
            return ol;
        }
        else
            return new int[] { -1, -1 };
    }

}
TOP

Related Classes of com.foundationdb.sql.optimizer.rule.GroupJoinFinder$JoinedReferenceFinder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.