package edu.brown.catalog.special; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.log4j.Logger; import org.voltdb.catalog.Column; import org.voltdb.catalog.Database; import org.voltdb.catalog.MaterializedViewInfo; import org.voltdb.catalog.Procedure; import org.voltdb.catalog.Statement; import org.voltdb.catalog.Table; import org.voltdb.compiler.VoltCompiler; import org.voltdb.planner.VerticalPartitionPlanner; import edu.brown.catalog.CatalogUtil; import edu.brown.logging.LoggerUtil; import edu.brown.logging.LoggerUtil.LoggerBoolean; import edu.brown.utils.StringUtil; public class VerticalPartitionColumn extends MultiColumn { private static final Logger LOG = Logger.getLogger(VerticalPartitionColumn.class); private static final LoggerBoolean debug = new LoggerBoolean(); private static final LoggerBoolean trace = new LoggerBoolean(); static { LoggerUtil.attachObserver(LOG, debug, trace); } public static final String PREFIX = "*VerticalPartitionColumn*"; private transient final Map<Statement, Statement> optimized = new HashMap<Statement, Statement>(); private transient final Map<Statement, Statement> backups = new HashMap<Statement, Statement>(); private transient boolean applied = false; private transient MaterializedViewInfo catalog_view; /** * THIS SHOULD NOT BE CALLED DIRECTLY Use VerticalPartitionColumn.get() * * @param attributes */ public VerticalPartitionColumn(Collection<Column> attributes) { super((Collection<? extends Column>) attributes); // There should only be two elements // The first one is the horizontal partitioning parameter(s) // The second one is the vertical partitioning parameter(s) assert (attributes.size() == 2) : "Total # of Attributes = " + this.getSize() + ": " + StringUtil.join(",", this); } public static VerticalPartitionColumn get(Column hp_cols, MultiColumn vp_cols) { assert (vp_cols.size() > 0) : "Empty vertical partitioning columns"; return InnerMultiAttributeCatalogType.get(VerticalPartitionColumn.class, hp_cols, (Column) vp_cols); } // -------------------------------------------------------------------------------------------- // UTILITY METHODS // -------------------------------------------------------------------------------------------- @Override public String toString() { Map<String, Object> m = new LinkedHashMap<String, Object>(); m.put("Horizontal", CatalogUtil.debug(this.getHorizontalColumn())); m.put("Vertical", CatalogUtil.debug(this.getVerticalPartitionColumns())); Map<String, String> inner = new HashMap<String, String>(); for (Entry<Statement, Statement> e : this.optimized.entrySet()) { inner.put(e.getKey().fullName(), e.getValue().fullName()); } // FOR m.put("Optimized Queries", inner); return StringUtil.formatMaps(m); } @Override public String getPrefix() { return (PREFIX); } @Override public void clear() { this.catalog_view = null; this.applied = false; this.optimized.clear(); this.backups.clear(); } public Collection<Statement> getOptimizedQueries() { return (this.optimized.keySet()); } public Statement getOptimizedQuery(Statement catalog_stmt) { return (this.optimized.get(catalog_stmt)); } public void addOptimizedQueries(Map<Statement, Statement> queries) { this.optimized.putAll(queries); } public boolean hasOptimizedQueries() { return (this.optimized.size() > 0); } /** * Return the Horizontal Partitioning Column * * @return */ public Column getHorizontalColumn() { return (this.get(0)); } /** * Return the Vertical Partitioning Columns * * @return */ public Collection<Column> getVerticalPartitionColumns() { return ((MultiColumn) this.get(1)).getAttributes(); } public MultiColumn getVerticalMultiColumn() { return ((MultiColumn) this.get(1)); } public List<String> getVerticalPartitionColumnNames() { List<String> columnNames = new ArrayList<String>(); for (Column catalog_col : this.getVerticalPartitionColumns()) { columnNames.add(catalog_col.getName()); } // FOR return (columnNames); } /** * Returns true if we have vertical partition columns defined * * @return */ public boolean hasVerticalPartitionColumns() { Collection<Column> cols = this.getVerticalPartitionColumns(); return (cols != null && cols.size() > 0); } public MaterializedViewInfo getViewCatalog() { return (this.catalog_view); } // -------------------------------------------------------------------------------------------- // CATALOG UPDATING METHODS // -------------------------------------------------------------------------------------------- /** * Returns true if updateCatalog() has been called and the Statements now * have the optimized query plans applied */ public boolean isUpdateApplied() { return (this.applied); } public synchronized MaterializedViewInfo createMaterializedView() { Table catalog_tbl = this.getParent(); MaterializedViewInfo catalog_view = CatalogUtil.getVerticalPartition(catalog_tbl); if (catalog_view == null || catalog_view.getDest() == null) { Collection<Column> cols = this.getVerticalPartitionColumns(); assert (cols.size() > 0) : "No Vertical Partition columns for " + this; if (trace.val) LOG.trace("Creating VerticalPartition in catalog for " + catalog_tbl + ": " + cols); try { catalog_view = VoltCompiler.addVerticalPartition(catalog_tbl, cols, true); assert (catalog_view != null); } catch (Throwable ex) { throw new RuntimeException("Failed to create vertical partition for " + this, ex); } if (debug.val) LOG.debug(String.format("Created vertical partition %s.%s: %s", catalog_tbl.getName(), catalog_view.getName(), CatalogUtil.debug(catalog_view.getDest().getColumns()))); } else if (debug.val) { LOG.debug(String.format("Using existing vertical partition %s.%s: %s", catalog_tbl.getName(), catalog_view.getName(), CatalogUtil.debug(catalog_view.getDest().getColumns()))); } validate(catalog_view, catalog_tbl); return (catalog_view); } /** * Create the MaterializedView catalog object for this vertical partition * candidate */ public MaterializedViewInfo applyUpdate() { assert (this.applied == false) : "Trying to apply " + this + " more than once"; Table catalog_tbl = this.getParent(); assert (catalog_tbl != null); if (this.catalog_view == null) { this.catalog_view = this.createMaterializedView(); } else { if (debug.val) LOG.debug("Reusing existing vertical partition " + this.catalog_view + " for " + catalog_tbl); if (catalog_tbl.getViews().contains(catalog_view) == false) catalog_tbl.getViews().add(this.catalog_view, false); } assert (this.catalog_view != null); // Make sure that the view's destination table is in the catalog Database catalog_db = CatalogUtil.getDatabase(catalog_view); if (catalog_db.getTables().contains(catalog_view.getDest()) == false) { if (debug.val) LOG.debug("Adding back " + catalog_view.getDest() + " to catalog"); catalog_db.getTables().add(catalog_view.getDest(), false); } else if (debug.val) { LOG.debug(String.format("%s already exists in catalog %s", catalog_view.getDest(), catalog_db.getTables())); } // Apply the new Statement query plans if (debug.val && this.optimized.isEmpty()) { LOG.warn("There are no optimized query plans for " + this.fullName()); } for (Entry<Statement, Statement> e : this.optimized.entrySet()) { // Make a back-up for each original Statement Statement backup = this.backups.get(e.getKey()); if (backup == null) { Procedure catalog_proc = e.getKey().getParent(); backup = catalog_proc.getStatements().add("BACKUP__" + e.getKey().getName()); if (debug.val) LOG.debug(String.format("Created temporary catalog object %s to back-up %s's query plans", backup.getName(), e.getKey().fullName())); this.backups.put(e.getKey(), backup); boolean ret = catalog_proc.getStatements().remove(backup); assert (ret); assert (catalog_proc.getStatements().contains(backup) == false); } VerticalPartitionPlanner.applyOptimization(e.getKey(), backup); // Then copy the optimized query plans if (debug.val) LOG.debug(String.format("Copying optimized query plans from %s to %s", e.getValue().fullName(), e.getKey().fullName())); CatalogUtil.copyQueryPlans(e.getValue(), e.getKey()); } // FOR this.applied = true; validate(catalog_view, catalog_tbl); if (debug.val) LOG.debug("Added " + catalog_view.getDest() + " for " + catalog_tbl + " and updated " + this.optimized.size() + " query plans"); return (this.catalog_view); } /** * */ public void revertUpdate() { assert (this.catalog_view != null); assert (this.applied) : "Trying to undo " + this + " before applying"; Table catalog_tbl = this.getParent(); assert (catalog_tbl != null); if (debug.val) LOG.debug(String.format("Reverting catalog update on %s for %s", catalog_tbl, this.catalog_view)); assert (catalog_tbl.getViews().contains(this.catalog_view)); catalog_tbl.getViews().remove(this.catalog_view); // Restore the original query plans from the backups for (Statement catalog_stmt : this.optimized.keySet()) { Statement backup = this.backups.get(catalog_stmt); assert (backup != null) : "Missing backup for " + catalog_stmt.fullName(); if (debug.val) LOG.debug(String.format("Restoring %s's original query plans from %s", catalog_stmt.fullName(), backup.getName())); CatalogUtil.copyQueryPlans(backup, catalog_stmt); } // FOR this.applied = false; validate(catalog_view, catalog_tbl); } private static void validate(MaterializedViewInfo catalog_view, Table catalog_tbl) { Database catalog_db = CatalogUtil.getDatabase(catalog_tbl); assert (catalog_view.getVerticalpartition()); assert (catalog_view.getDest() != null) : String.format("MaterializedViewInfo %s for %s is missing destination table!", catalog_view.fullName(), catalog_tbl); assert (catalog_db.getTables().contains(catalog_view.getDest())) : String.format("MaterializedViewInfo %s for %s is missing destination table! %s", catalog_view.fullName(), catalog_tbl, catalog_db.getTables()); assert (catalog_view.getGroupbycols().isEmpty() == false) : String.format("MaterializedViewInfo %s for %s is missing groupby columns!", catalog_view.fullName(), catalog_tbl); assert (catalog_view.getDest().getColumns().isEmpty() == false) : String.format("MaterializedViewInfo %s for %s is missing virtual columns!", catalog_view.getDest(), catalog_tbl); assert (catalog_view.getParent().equals(catalog_tbl)) : String.format("MaterializedViewInfo %s has parent %s, but it should be %s!", catalog_view.fullName(), catalog_view.getParent(), catalog_tbl); } }