/** * 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.*; /** Annotate subqueries with their outer table references. */ public abstract class SubqueryBoundTablesTracker implements PlanVisitor, ExpressionVisitor { protected PlanContext planContext; protected BaseQuery rootQuery; protected Deque<SubqueryState> subqueries = new ArrayDeque<>(); boolean trackingTables = true; protected SubqueryBoundTablesTracker(PlanContext planContext) { this.planContext = planContext; } protected void run() { rootQuery = (BaseQuery)planContext.getPlan(); rootQuery.accept(this); } @Override public boolean visitEnter(PlanNode n) { if(n instanceof HashTableLookup) trackingTables = false; if (n instanceof Subquery) { subqueries.push(new SubqueryState((Subquery)n)); return true; } return visit(n); } @Override public boolean visitLeave(PlanNode n) { if (n instanceof Subquery) { SubqueryState s = subqueries.pop(); Set<ColumnSource> outerTables = s.getTablesReferencedButNotDefined(); s.subquery.setOuterTables(outerTables); if (!subqueries.isEmpty()) subqueries.peek().tablesReferenced.addAll(outerTables); } if(n instanceof HashTableLookup) trackingTables = true; return true; } @Override public boolean visit(PlanNode n) { if (trackingTables && !subqueries.isEmpty() && (n instanceof ColumnSource)) { boolean added = subqueries.peek().tablesDefined.add((ColumnSource)n); assert added : "Table defined more than once"; } 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 (!subqueries.isEmpty() && (n instanceof ColumnExpression)) { subqueries.peek().tablesReferenced.add(((ColumnExpression)n).getTable()); } return true; } protected BaseQuery currentQuery() { if (subqueries.isEmpty()) { return rootQuery; } else { return subqueries.peek().subquery; } } static class SubqueryState { Subquery subquery; Set<ColumnSource> tablesReferenced = new HashSet<>(); Set<ColumnSource> tablesDefined = new HashSet<>(); public SubqueryState(Subquery subquery) { this.subquery = subquery; } public Set<ColumnSource> getTablesReferencedButNotDefined() { tablesReferenced.removeAll(tablesDefined); return tablesReferenced; } } }