package edu.brown.optimizer.optimizations;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.log4j.Logger;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.Table;
import org.voltdb.expressions.AbstractExpression;
import org.voltdb.expressions.TupleValueExpression;
import org.voltdb.planner.PlanColumn;
import org.voltdb.plannodes.AbstractJoinPlanNode;
import org.voltdb.plannodes.AbstractPlanNode;
import org.voltdb.plannodes.AbstractScanPlanNode;
import org.voltdb.plannodes.ProjectionPlanNode;
import org.voltdb.types.PlanNodeType;
import org.voltdb.utils.Pair;
import edu.brown.expressions.ExpressionUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.optimizer.PlanOptimizerState;
import edu.brown.plannodes.PlanNodeTreeWalker;
import edu.brown.plannodes.PlanNodeUtil;
import edu.brown.utils.CollectionUtil;
public class RemoveRedundantProjectionsOptimizations extends AbstractOptimization {
private static final Logger LOG = Logger.getLogger(RemoveRedundantProjectionsOptimizations.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private AbstractPlanNode new_root;
final AtomicBoolean modified = new AtomicBoolean(false);
public RemoveRedundantProjectionsOptimizations(PlanOptimizerState state) {
super(state);
}
@Override
public Pair<Boolean, AbstractPlanNode> optimize(final AbstractPlanNode rootNode) {
this.new_root = rootNode;
this.modified.set(false);
new PlanNodeTreeWalker(false) {
protected void callback(AbstractPlanNode element) {
// CASE #1
// If we are a ScanNode that has an inline projection with the
// same number of output columns as the scanned table in the
// same order that are straight TupleValueExpression, then we
// know that we're just dumping out the table, so therefore we don't
// need the projection at all!
if (element instanceof AbstractScanPlanNode) {
AbstractScanPlanNode scan_node = (AbstractScanPlanNode) element;
ProjectionPlanNode proj_node = element.getInlinePlanNode(PlanNodeType.PROJECTION);
if (proj_node == null) {
if (debug.val)
LOG.debug("SKIP - " + element + " does not an inline ProjectionPlanNode");
return;
}
String table_name = scan_node.getTargetTableName();
Table catalog_tbl = state.catalog_db.getTables().get(table_name);
assert (catalog_tbl != null) : "Unexpected table '" + table_name + "'";
if (catalog_tbl.getColumns().size() != proj_node.getOutputColumnGUIDCount()) {
if (debug.val)
LOG.debug("SKIP - Inline " + proj_node + " does not have the same number of output columns as " + catalog_tbl);
return;
}
for (int i = 0, cnt = catalog_tbl.getColumns().size(); i < cnt; i++) {
int col_guid = proj_node.getOutputColumnGUID(i);
PlanColumn plan_col = state.plannerContext.get(col_guid);
assert (plan_col != null);
AbstractExpression col_exp = plan_col.getExpression();
assert (col_exp != null);
if ((col_exp instanceof TupleValueExpression) == false) {
if (debug.val)
LOG.debug("SKIP - Inline " + proj_node + " does not have a TupleValueExpression for output column #" + i);
return;
}
Collection<Column> columns = ExpressionUtil.getReferencedColumns(state.catalog_db, col_exp);
assert (columns.size() == 1);
Column catalog_col = CollectionUtil.first(columns);
if (catalog_col.getIndex() != i) {
return;
}
} // FOR
// If we're here, the new know that we can remove the
// projection
scan_node.removeInlinePlanNode(PlanNodeType.PROJECTION);
modified.set(true);
if (debug.val)
LOG.debug(String.format("PLANOPT - Removed redundant %s from %s\n%s", proj_node, scan_node, PlanNodeUtil.debug(rootNode)));
}
// CASE #2
// We are ProjectionPlanNode that references the same columns as one
// further below. That means we are unnecessary and can be removed!
if (element instanceof ProjectionPlanNode) {
// Find the first ProjectionPlanNode below us
ProjectionPlanNode next_proj = getFirstProjection(element);
assert (next_proj == null || next_proj != element);
if (next_proj == null) {
if (debug.val)
LOG.debug("SKIP - No other Projection found below " + element);
return;
}
// They must at least have the same number of output columns
else if (element.getOutputColumnGUIDCount() != next_proj.getOutputColumnGUIDCount()) {
if (debug.val)
LOG.debug(String.format("SKIP - %s and %s do not have the same number of output columns", element, next_proj));
return;
}
// There can't be a JOIN PlanNode in between us, since that may mean we need
// to prune out the colums at the bottom scan node
Collection<AbstractJoinPlanNode> join_nodes = PlanNodeUtil.getPlanNodes(element, AbstractJoinPlanNode.class);
if (debug.val) LOG.debug(String.format("%s has %d join nodes below it: %s",
element, join_nodes.size(), join_nodes));
if (join_nodes.isEmpty() == false) {
// Check to see whether the joins appear *after* the projection node
int elementDepth = PlanNodeUtil.getDepth(rootNode, element);
int nextDepth = PlanNodeUtil.getDepth(rootNode, next_proj);
assert(elementDepth < nextDepth) :
String.format("%s %d < %s %d", element, elementDepth, next_proj, nextDepth);
for (AbstractJoinPlanNode join_node : join_nodes) {
int joinDepth = PlanNodeUtil.getDepth(rootNode, join_node);
assert(elementDepth < joinDepth);
assert(nextDepth != joinDepth);
if (joinDepth < nextDepth) {
if (debug.val)
LOG.debug(String.format("SKIP - %s has %s that comes after %s", element, join_node, next_proj));
return;
}
} // FOR
}
// Check whether we have the same output columns
List<PlanColumn> elementColumns = new ArrayList<PlanColumn>();
for (Integer guid : element.getOutputColumnGUIDs()) {
PlanColumn pc = state.plannerContext.get(guid);
assert(pc != null);
elementColumns.add(pc);
} // FOR
boolean match = true;
int idx = 0;
for (Integer guid : next_proj.getOutputColumnGUIDs()) {
PlanColumn pc = state.plannerContext.get(guid);
assert(pc != null);
if (elementColumns.get(idx).equals(pc, true, true) == false) {
match = false;
break;
}
idx++;
} // FOR
if (match == false) {
if (debug.val)
LOG.debug(String.format("SKIP - %s and %s do not have the same output columns", element, next_proj));
return;
}
// Ok so we need to remove it. But we need to check whether our child
// node will become the new root of the tree
if (element.getParentPlanNodeCount() == 0) {
assert (element.getChildPlanNodeCount() == 1) : "Projection element expected 1 child but has " + element.getChildPlanNodeCount() + " children";
new_root = element.getChild(0);
if (debug.val)
LOG.debug("PLANOPT - Promoted " + new_root + " as the new query plan root!");
} else {
AbstractPlanNode parent = element.getParent(0);
assert(parent != null);
for (AbstractPlanNode child : element.getChildren()) {
parent.addAndLinkChild(child);
} // FOR
}
// Off with it's head!
if (debug.val)
LOG.debug("PLANOPT - Removed redundant " + element + " from query plan!");
element.removeFromGraph();
modified.set(true);
}
}
}.traverse(rootNode);
assert (this.new_root != null);
return (Pair.of(modified.get(), this.new_root));
}
private ProjectionPlanNode getFirstProjection(final AbstractPlanNode root) {
final ProjectionPlanNode proj_node[] = { null };
new PlanNodeTreeWalker(true) {
@Override
protected void callback(AbstractPlanNode element) {
if (element != root && element instanceof ProjectionPlanNode) {
proj_node[0] = (ProjectionPlanNode) element;
this.stop();
}
}
}.traverse(root);
return (proj_node[0]);
}
}