/* This file is part of VoltDB. * Copyright (C) 2008-2010 VoltDB L.L.C. * * VoltDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VoltDB 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.voltdb.VoltTable.ColumnInfo; import org.voltdb.catalog.Partition; import org.voltdb.catalog.Procedure; import org.voltdb.catalog.Site; import edu.brown.hstore.HStoreConstants; import edu.brown.hstore.Hstoreservice.WorkFragment; import edu.brown.hstore.PartitionExecutor; import edu.brown.hstore.txns.LocalTransaction; import edu.brown.logging.LoggerUtil; import edu.brown.logging.LoggerUtil.LoggerBoolean; import edu.brown.utils.CollectionUtil; /** * System procedures extend VoltSystemProcedure and use its utility methods to * create work in the system. This functionality is not available to standard * user procedures (which extend VoltProcedure). */ public abstract class VoltSystemProcedure extends VoltProcedure { private static final Logger LOG = Logger.getLogger(VoltSystemProcedure.class); private static final LoggerBoolean debug = new LoggerBoolean(); private static final LoggerBoolean trace = new LoggerBoolean(); static { LoggerUtil.attachObserver(LOG, debug, trace); } /** Standard column type for host/partition/site id columns */ protected static VoltType CTYPE_ID = VoltType.INTEGER; /** Standard column name for a host id column */ protected static String CNAME_HOST_ID = "HOST_ID"; /** Standard column name for a site id column */ protected static String CNAME_SITE_ID = "SITE_ID"; /** Standard column name for a partition id column */ protected static String CNAME_PARTITION_ID = "PARTITION_ID"; /** Standard schema for sysprocs returning a simple status table */ public static ColumnInfo STATUS_SCHEMA = new ColumnInfo("STATUS", VoltType.BIGINT); // public to fix javadoc linking warning /** Standard success return value for sysprocs returning STATUS_SCHEMA */ protected static long STATUS_OK = 0L; protected CatalogContext catalogContext; protected final List<WorkFragment.Builder> fragments = new ArrayList<WorkFragment.Builder>(); public abstract void initImpl(); @Override public final void init(PartitionExecutor executor, Procedure catalog_proc, BackendTarget eeType) { super.init(executor, catalog_proc, eeType); this.catalogContext = executor.getCatalogContext(); this.initImpl(); } protected final void registerPlanFragment(long fragId) { this.executor.registerPlanFragment(fragId, this); } // protected final AbstractTransaction getTransactionState(Long txnId) { // return (this.executor.getHStoreSite().getTransaction(txnId)); // } /** Bundles the data needed to describe a plan fragment. */ public static class SynthesizedPlanFragment { public int destPartitionId = -1; public int fragmentId = -1; public int inputDependencyIds[] = null; public int outputDependencyIds[] = null; public ParameterSet parameters = null; public boolean multipartition = false; /** true if distributes to all executable partitions */ public boolean nonExecSites = false; /** true if distributes once to each node */ public boolean last_task = false; } abstract public DependencySet executePlanFragment(Long txn_id, Map<Integer,List<VoltTable>> dependencies, int fragmentId, ParameterSet params, PartitionExecutor.SystemProcedureExecutionContext context); /** * Produce work units, possibly on all sites, for a list of plan fragments. * The final plan fragment must aggregate intermediate results and produce * a single output dependency. This aggregate output is returned as the result. * * @param pfs an array of synthesized plan fragments * @param aggregatorOutputDependencyId dependency id produced by the aggregation pf * The id of the table returned as the result of this procedure. * @return the resulting VoltTable as a length-one array. */ protected VoltTable[] executeSysProcPlanFragments(SynthesizedPlanFragment pfs[], int aggregatorOutputDependencyId) { // Block until we get all of our responses. // We can do this because our ExecutionSite is multi-threaded return (executeSysProcPlanFragmentsAsync(pfs)); } /** * Produce work units, possibly on all sites, for a list of plan fragments. * The final plan fragment must aggregate intermediate results and produce * a single output dependency. This aggregate output is returned as the result. * * @param pfs an array of synthesized plan fragments * @param aggregatorOutputDependencyId dependency id produced by the aggregation pf * The id of the table returned as the result of this procedure. */ protected final VoltTable[] executeSysProcPlanFragmentsAsync(SynthesizedPlanFragment pfs[]) { LocalTransaction ts = this.getTransactionState(); if (debug.val) LOG.debug(ts + " - Preparing to execute " + pfs.length + " sysproc fragments"); this.fragments.clear(); ParameterSet parameters[] = new ParameterSet[pfs.length]; for (int i = 0; i < pfs.length; i++) { SynthesizedPlanFragment pf = pfs[i]; // check mutually exclusive flags assert(!(pf.multipartition && pf.nonExecSites)); assert(pf.parameters != null); // We'll let the PartitionExecutor decide how to serialize our ParameterSets parameters[i] = pf.parameters; // If the multipartition flag is set to true and we don't have a destPartitionId, // then we'll just make it go to all partitions. This is so that we can support // old-school VoltDB's sysprocs int partitions[] = null; if (pf.destPartitionId < 0) { if (pf.multipartition) { partitions = CollectionUtil.toIntArray(catalogContext.getAllPartitionIds()); } // If it's not multipartitioned and they still don't have a destPartitionId, // then we'll make it just go to this PartitionExecutor's local partition else { partitions = new int[]{ this.executor.getPartitionId() }; } if (debug.val) LOG.debug(this.getClass() + " => " + Arrays.toString(partitions)); } else { partitions = new int[]{ pf.destPartitionId }; } // Create a WorkFragment for each target partition for (int destPartitionId : partitions) { if (debug.val) LOG.debug(String.format("%s - Creating %s for partition %s [fragmentId=%d]", ts, WorkFragment.class.getSimpleName(), destPartitionId, pf.fragmentId)); WorkFragment.Builder builder = WorkFragment.newBuilder() .setPartitionId(destPartitionId) .setReadOnly(false) .setLastFragment(pf.last_task) .addFragmentId(pf.fragmentId) .addStmtCounter(0) .addStmtIndex(0) .addStmtIgnore(false) .addParamIndex(i); ts.getTouchedPartitions().put(destPartitionId); boolean needs_input = false; for (int ii = 0; ii < pf.outputDependencyIds.length; ii++) { // Input Dependencies if (pf.inputDependencyIds != null && ii < pf.inputDependencyIds.length) { builder.addInputDepId(pf.inputDependencyIds[ii]); needs_input = needs_input || (pf.inputDependencyIds[ii] != HStoreConstants.NULL_DEPENDENCY_ID); } else { builder.addInputDepId(HStoreConstants.NULL_DEPENDENCY_ID); } // Output Dependencies builder.addOutputDepId(pf.outputDependencyIds[ii]); } // FOR builder.setNeedsInput(needs_input); this.fragments.add(builder); } // FOR } // FOR // For some reason we have problems if we're using the transaction profiler // with sysprocs, so we'll just always turn it off if (hstore_conf.site.txn_profiling && ts.profiler != null) ts.profiler.disableProfiling(); // Bombs away! return (this.executor.dispatchWorkFragments(ts, 1, parameters, this.fragments, false)); } /** * Helper method that will return true if the invoking partition * is the first partition at this HStoreSite * @return */ protected final boolean isFirstLocalPartition() { return (Collections.min(hstore_site.getLocalPartitionIds()) == this.partitionId); } /** * Helper method that will queue and execute the given SynthesizedPlanFragment id * at the local partition * @param fragId * @param params * @return */ protected final VoltTable[] executeLocal(final int fragId, final ParameterSet params) { final SynthesizedPlanFragment pfs[] = new SynthesizedPlanFragment[1]; int i = 0; pfs[i] = new SynthesizedPlanFragment(); pfs[i].fragmentId = fragId; pfs[i].inputDependencyIds = new int[] { }; pfs[i].outputDependencyIds = new int[] { fragId }; pfs[i].multipartition = true; pfs[i].nonExecSites = false; pfs[i].destPartitionId = this.partitionId; pfs[i].parameters = params; pfs[i].last_task = true; return (this.executeSysProcPlanFragments(pfs, fragId)); } protected final VoltTable[] executeOncePerSite(final int distributeId, final int aggregateId) { return this.executeOncePerSite(distributeId, aggregateId, new ParameterSet()); } /** * Helper method that will queue up the distributed SynthesizedPlanFragment (distributeId) * at the first partition at each site and then execute the aggregate * SynthesizedPlanFragment (aggregateId) at the local partition. Note that the same * ParameterSet will be given to both the distributed and aggregate operations * @param distributeId * @param aggregateId * @param params * @return */ protected final VoltTable[] executeOncePerSite(final int distributeId, final int aggregateId, final ParameterSet params) { final SynthesizedPlanFragment pfs[] = new SynthesizedPlanFragment[catalogContext.numberOfSites + 1]; int i = 0; for (Site catalog_site : catalogContext.sites.values()) { Partition catalog_part = null; int first_id = Integer.MAX_VALUE; for (Partition p : catalog_site.getPartitions().values()) { if (catalog_part == null || p.getId() < first_id) { catalog_part = p; first_id = p.getId(); } } // FOR assert(catalog_part != null) : "No partitions for " + catalog_site; if (debug.val) LOG.debug(String.format("Creating PlanFragment #%d for %s on %s", distributeId, catalog_part, catalog_site)); pfs[i] = new SynthesizedPlanFragment(); pfs[i].fragmentId = distributeId; pfs[i].inputDependencyIds = new int[] { }; pfs[i].outputDependencyIds = new int[] { distributeId }; pfs[i].multipartition = true; pfs[i].nonExecSites = false; pfs[i].destPartitionId = catalog_part.getId(); pfs[i].parameters = params; pfs[i].last_task = (catalog_site.getId() != hstore_site.getSiteId()); i += 1; } // FOR // a final plan fragment to aggregate the results pfs[i] = new SynthesizedPlanFragment(); pfs[i].fragmentId = aggregateId; pfs[i].inputDependencyIds = new int[] { distributeId }; pfs[i].outputDependencyIds = new int[] { aggregateId }; pfs[i].multipartition = false; pfs[i].nonExecSites = false; pfs[i].destPartitionId = this.partitionId; pfs[i].parameters = params; pfs[i].last_task = true; return (this.executeSysProcPlanFragments(pfs, aggregateId)); } /** * Helper method that will queue up the distributed SynthesizedPlanFragment (distributeId) * at all of the partitions in the cluster. Note that the same ParameterSet will be given * to both the distributed and aggregate operations * @param distributeId * @param aggregateId * @param params * @return */ protected final VoltTable[] executeOncePerPartition(final int distributeId, final int aggregateId, final ParameterSet params) { final SynthesizedPlanFragment pfs[] = new SynthesizedPlanFragment[catalogContext.numberOfPartitions + 1]; int i = 0; for (Partition catalog_part : catalogContext.getAllPartitions()) { pfs[i] = new SynthesizedPlanFragment(); pfs[i].fragmentId = distributeId; pfs[i].inputDependencyIds = new int[] { }; pfs[i].outputDependencyIds = new int[] { distributeId }; pfs[i].multipartition = true; pfs[i].nonExecSites = false; pfs[i].destPartitionId = catalog_part.getId(); pfs[i].parameters = params; pfs[i].last_task = false; // (catalog_part.getId() != this.partitionId); i += 1; } // FOR // a final plan fragment to aggregate the results pfs[i] = new SynthesizedPlanFragment(); pfs[i].fragmentId = aggregateId; pfs[i].inputDependencyIds = new int[] { distributeId }; pfs[i].outputDependencyIds = new int[] { aggregateId }; pfs[i].multipartition = false; pfs[i].nonExecSites = false; pfs[i].destPartitionId = this.partitionId; pfs[i].parameters = params; pfs[i].last_task = true; return (this.executeSysProcPlanFragments(pfs, aggregateId)); } /** * Returns the formatted procedure name to use to invoke the given sysproc class * This is what is passed into the client * @param procClass * @return */ public static final String procCallName(Class<? extends VoltSystemProcedure> procClass) { return "@" + procClass.getSimpleName(); } }