package edu.brown.optimizer; import java.util.Collection; import java.util.Comparator; import org.apache.log4j.Logger; import org.voltdb.catalog.Column; import org.voltdb.catalog.Database; import org.voltdb.planner.PlannerContext; import org.voltdb.plannodes.AbstractPlanNode; import org.voltdb.types.PlanNodeType; import org.voltdb.utils.Pair; import edu.brown.logging.LoggerUtil.LoggerBoolean; import edu.brown.optimizer.optimizations.AbstractOptimization; import edu.brown.optimizer.optimizations.AggregatePushdownOptimization; import edu.brown.optimizer.optimizations.CombineOptimization; import edu.brown.optimizer.optimizations.LimitPushdownOptimization; import edu.brown.optimizer.optimizations.ProjectionPushdownOptimization; import edu.brown.optimizer.optimizations.RemoveDistributedReplicatedTableJoinOptimization; import edu.brown.optimizer.optimizations.RemoveRedundantProjectionsOptimizations; import edu.brown.plannodes.PlanNodeUtil; import edu.brown.utils.ClassUtil; import edu.brown.utils.StringBoxUtil; import edu.brown.utils.StringUtil; /** * @author pavlo * @author sw47 */ public class PlanOptimizer { private static final Logger LOG = Logger.getLogger(PlanOptimizer.class); private static final LoggerBoolean debug = new LoggerBoolean(); private static final LoggerBoolean trace = new LoggerBoolean(); // ---------------------------------------------------------------------------- // GLOBAL CONFIGURATION // ---------------------------------------------------------------------------- /** * The list of PlanNodeTypes that we do not want to try to optimize */ private static final PlanNodeType TO_IGNORE[] = { PlanNodeType.AGGREGATE, PlanNodeType.NESTLOOP, }; private static final String BROKEN_SQL[] = { // "FROM CUSTOMER, FLIGHT, RESERVATION", // Airline DeleteReservation.GetCustomerReservation // "SELECT imb_ib_id, ib_bid", // AuctionMark NewBid.getMaxBidId }; /** * */ protected static final Comparator<Column> COLUMN_COMPARATOR = new Comparator<Column>() { public int compare(Column c0, Column c1) { Integer i0 = c0.getIndex(); assert (i0 != null) : "Missing index for " + c0; Integer i1 = c1.getIndex(); assert (i1 != null) : "Missing index for " + c1; return (i0.compareTo(i1)); } }; /** * List of the Optimizations that we will want to apply */ @SuppressWarnings("unchecked") protected static final Class<? extends AbstractOptimization> OPTIMIZATONS[] = (Class<? extends AbstractOptimization>[]) new Class<?>[] { RemoveDistributedReplicatedTableJoinOptimization.class, AggregatePushdownOptimization.class, ProjectionPushdownOptimization.class, LimitPushdownOptimization.class, RemoveRedundantProjectionsOptimizations.class, CombineOptimization.class, }; // ---------------------------------------------------------------------------- // INSTANCE CONFIGURATION // ---------------------------------------------------------------------------- private final PlanOptimizerState state; // ---------------------------------------------------------------------------- // CONSTRUCTOR // ---------------------------------------------------------------------------- /** * @param context * Information about context * @param catalogDb * Catalog info about schema, metadata and procedures */ public PlanOptimizer(PlannerContext context, Database catalogDb) { this.state = new PlanOptimizerState(catalogDb, context); } // ---------------------------------------------------------------------------- // MAIN METHOD // ---------------------------------------------------------------------------- /** * Main entry point for the PlanOptimizer */ public AbstractPlanNode optimize(final String sql, final AbstractPlanNode root) { try { return _optimize(sql, root); } catch (Throwable ex) { ex.printStackTrace(); throw new RuntimeException(ex); } } public AbstractPlanNode _optimize(final String sql, final AbstractPlanNode root) { // HACK for (String broken : BROKEN_SQL) { if (sql.contains(broken)) { if (debug.val) LOG.debug("Given SQL contains broken fragment '" + broken + "'. Skipping...\n" + sql); return (null); } } // check to see if the join nodes have the wrong offsets. If so fix them // and propagate them. // XXX: Why is this here and not down with the rest of the stuff??? PlanOptimizerUtil.fixJoinColumnOffsets(state, root); // Check if our tree contains anything that we want to ignore Collection<PlanNodeType> types = PlanNodeUtil.getPlanNodeTypes(root); if (trace.val) LOG.trace(sql + " - PlanNodeTypes: " + types); for (PlanNodeType t : TO_IGNORE) { if (types.contains(t)) { if (trace.val) LOG.trace(String.format("Tree rooted at %s contains %s. Skipping optimization...", root, t)); return (null); } } // FOR // Skip single partition query plans // if (types.contains(PlanNodeType.RECEIVE) == false) return (null); AbstractPlanNode new_root = root; if (trace.val) LOG.trace("BEFORE: " + sql + "\n" + StringBoxUtil.box(PlanNodeUtil.debug(root))); // LOG.debug("LET 'ER RIP!"); // } // STEP #1: // Populate the PlanOptimizerState with the information that we will // need to figure out our various optimizations if (debug.val) LOG.debug(StringUtil.header("POPULATING OPTIMIZER STATE", "*")); PlanOptimizerUtil.populateTableNodeInfo(state, new_root); PlanOptimizerUtil.populateJoinTableInfo(state, new_root); // STEP #2 // Apply all the optimizations that we have // We will pass in the new_root each time to ensure that each // optimization // gets a full view of the quey plan tree if (debug.val) LOG.debug(StringUtil.header("APPLYING OPTIMIZATIONS", "*")); for (Class<? extends AbstractOptimization> optClass : OPTIMIZATONS) { if (debug.val) LOG.debug(StringUtil.header(optClass.getSimpleName())); // Always reset everything so that each optimization has a clean // slate to work with state.clearDirtyNodes(); state.updateColumnInfo(new_root); try { AbstractOptimization opt = ClassUtil.newInstance(optClass, new Object[] { state }, new Class<?>[] { PlanOptimizerState.class }); assert (opt != null); Pair<Boolean, AbstractPlanNode> p = opt.optimize(new_root); if (p.getFirst()) { if (debug.val) LOG.debug(String.format("%s modified query plan", optClass.getSimpleName())); new_root = p.getSecond(); } } catch (Throwable ex) { if (debug.val) LOG.debug("Last Query Plan:\n" + PlanNodeUtil.debug(new_root)); String msg = String.format("Failed to apply %s to query plan\n%s", optClass.getSimpleName(), sql); if (ex instanceof AssertionError) throw new RuntimeException(msg, ex); LOG.warn(msg, ex); return (null); } // STEP #3 // If any nodes were modified by this optimization, go through the tree // and make sure our output columns and other information is all in sync if (state.hasDirtyNodes()) PlanOptimizerUtil.updateAllColumns(state, new_root, false); } // FOR PlanOptimizerUtil.updateAllColumns(state, new_root, true); if (trace.val) LOG.trace("AFTER: " + sql + "\n" + StringBoxUtil.box(PlanNodeUtil.debug(new_root))); return (new_root); } public PlanOptimizerState getPlanOptimizerState() { return this.state; } }