/**
* 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.sql.optimizer.plan.*;
import java.util.*;
/** Track which tables a conditional expression depends on that are
* not bound from an outer context.
* Used to determine exactly when the condition can be tested.
*/
public class ConditionDependencyAnalyzer implements PlanVisitor, ExpressionVisitor {
private enum State { GATHER, ANALYZE };
private State state;
private Set<ColumnSource> upstreamTables;
private Set<ColumnSource> referencedTables;
private Set<ColumnExpression> referencedColumns;
/** Make an analyzer using the upstream tables that input to the
* given node. */
public ConditionDependencyAnalyzer(PlanNode node) {
upstreamTables = new HashSet<>();
state = State.GATHER;
node.accept(this);
}
/** Make an analyzer that recognizes tables from either of two sources. */
public ConditionDependencyAnalyzer(ConditionDependencyAnalyzer a1,
ConditionDependencyAnalyzer a2) {
upstreamTables = new HashSet<>(a1.upstreamTables);
upstreamTables.addAll(a2.upstreamTables);
}
/** Get the tables that are not bound by some outer contour and so
* must be available at the point of execution. */
public Set<ColumnSource> getUpstreamTables() {
return upstreamTables;
}
/** Analyze the given condition and if it acccesses just a single
* table, return that. Otherwise, return <code>null</code>.
*/
public ColumnSource analyze(ConditionExpression cond) {
referencedTables = new HashSet<>();
referencedColumns = new HashSet<>();
state = State.ANALYZE;
cond.accept(this);
if (referencedTables.size() == 1)
return referencedTables.iterator().next();
else
return null;
}
/** Get the tables that were referenced by the condition. */
public Set<ColumnSource> getReferencedTables() {
return referencedTables;
}
/** Get the columns that were referenced by the condition. */
public Set<ColumnExpression> getReferencedColumns() {
return referencedColumns;
}
@Override
public boolean visitEnter(PlanNode n) {
return visit(n);
}
@Override
public boolean visitLeave(PlanNode n) {
return true;
}
@Override
public boolean visit(PlanNode n) {
if (state == State.GATHER) {
if (n instanceof ColumnSource)
upstreamTables.add((ColumnSource)n);
else if (n instanceof IndexScan)
upstreamTables.addAll(((IndexScan)n).getTables());
}
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 (state == State.ANALYZE) {
if (n instanceof ColumnExpression) {
ColumnExpression column = (ColumnExpression)n;
ColumnSource table = column.getTable();
if (upstreamTables.contains(table)) {
referencedTables.add(table);
referencedColumns.add(column);
}
}
}
return true;
}
}