/* 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.compiler;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.log4j.Logger;
import org.voltdb.BackendTarget;
import org.voltdb.ProcInfoData;
import org.voltdb.VoltProcedure;
import org.voltdb.catalog.Catalog;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.ProcParameter;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Statement;
import org.voltdb.catalog.StmtParameter;
import org.voltdb.catalog.Table;
import org.voltdb.utils.Pair;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import edu.brown.catalog.CatalogUtil;
import edu.brown.catalog.ClusterConfiguration;
import edu.brown.catalog.special.MultiColumn;
import edu.brown.catalog.special.VerticalPartitionColumn;
import edu.brown.interfaces.Deferrable;
import edu.brown.interfaces.Prefetchable;
import edu.brown.mappings.ParameterMapping;
import edu.brown.mappings.ParameterMappingsSet;
import edu.brown.mappings.ParametersUtil;
import edu.brown.utils.FileUtil;
import edu.brown.utils.StringUtil;
/**
* Alternate (programmatic) interface to VoltCompiler. Give the class all of
* the information a user would put in a VoltDB project file and it will go
* and build the project file and run the compiler on it.
*
*/
public class VoltProjectBuilder {
private static final Logger LOG = Logger.getLogger(VoltProjectBuilder.class);
final LinkedHashSet<String> m_schemas = new LinkedHashSet<String>();
protected final String project_name;
public static final class ProcedureInfo {
private final String users[];
private final String groups[];
private final Class<?> cls;
private final String name;
private final String sql;
private final String partitionInfo;
public ProcedureInfo(final String users[], final String groups[], final Class<?> cls) {
this.users = users;
this.groups = groups;
this.cls = cls;
this.name = cls.getSimpleName();
this.sql = null;
this.partitionInfo = null;
assert(this.name != null);
}
public ProcedureInfo(final String users[], final String groups[], final String name, final String sql, final String partitionInfo) {
assert(name != null);
this.users = users;
this.groups = groups;
this.cls = null;
this.name = name;
this.sql = sql;
this.partitionInfo = partitionInfo;
assert(this.name != null);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(final Object o) {
if (o instanceof ProcedureInfo) {
final ProcedureInfo oInfo = (ProcedureInfo)o;
return name.equals(oInfo.name);
}
return false;
}
}
public static final class UserInfo {
private final String name;
private final boolean adhoc;
private final boolean sysproc;
private final String password;
private final String groups[];
public UserInfo (final String name, final boolean adhoc, final boolean sysproc, final String password, final String groups[]){
this.name = name;
this.adhoc = adhoc;
this.sysproc = sysproc;
this.password = password;
this.groups = groups;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(final Object o) {
if (o instanceof UserInfo) {
final UserInfo oInfo = (UserInfo)o;
return name.equals(oInfo.name);
}
return false;
}
}
public static final class GroupInfo {
private final String name;
private final boolean adhoc;
private final boolean sysproc;
public GroupInfo(final String name, final boolean adhoc, final boolean sysproc){
this.name = name;
this.adhoc = adhoc;
this.sysproc = sysproc;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(final Object o) {
if (o instanceof GroupInfo) {
final GroupInfo oInfo = (GroupInfo)o;
return name.equals(oInfo.name);
}
return false;
}
}
/** An export/tables/table entry */
public static final class ELTTableInfo {
final public String m_tablename;
final public boolean m_export_only;
ELTTableInfo(String tablename, boolean append) {
m_tablename = tablename;
m_export_only = append;
}
}
final ArrayList<ELTTableInfo> m_eltTables = new ArrayList<ELTTableInfo>();
final LinkedHashSet<UserInfo> m_users = new LinkedHashSet<UserInfo>();
final LinkedHashSet<GroupInfo> m_groups = new LinkedHashSet<GroupInfo>();
final LinkedHashSet<ProcedureInfo> m_procedures = new LinkedHashSet<ProcedureInfo>();
final LinkedHashSet<Class<?>> m_supplementals = new LinkedHashSet<Class<?>>();
final LinkedHashMap<String, String> m_partitionInfos = new LinkedHashMap<String, String>();
/**
* Replicated SecondaryIndex Info
* TableName -> Pair<CreateIndex, ColumnNames>
*/
private final LinkedHashMap<String, Pair<Boolean, Collection<String>>> m_replicatedSecondaryIndexes = new LinkedHashMap<String, Pair<Boolean, Collection<String>>>();
private boolean m_replicatedSecondaryIndexesEnabled = true;
/**
* Evictable Tables
*/
private final HashSet<String> m_evictableTables = new HashSet<String>();
private final HashSet<String> m_batchEvictableTables = new HashSet<String>();
/**
* Prefetchable Queries
* ProcedureName -> StatementName
* @see Prefetchable
*/
private final HashMap<String, Set<String>> m_prefetchQueries = new HashMap<String, Set<String>>();
/**
* Deferrable Queries
* ProcedureName -> StatementName
* @see Deferrable
*/
private final HashMap<String, Set<String>> m_deferQueries = new HashMap<String, Set<String>>();
/**
* File containing ParameterMappingsSet
*/
private File m_paramMappingsFile;
/**
* Values for a ParameterMappingsSet that we will construct
* after we have compile the catalog
*/
final LinkedHashMap<String, Map<Integer, Pair<String, Integer>>> m_paramMappings = new LinkedHashMap<String, Map<Integer,Pair<String,Integer>>>();
String m_elloader = null; // loader package.Classname
private boolean m_elenabled; // true if enabled; false if disabled
List<String> m_elAuthUsers; // authorized users
List<String> m_elAuthGroups; // authorized groups
BackendTarget m_target = BackendTarget.NATIVE_EE_JNI;
PrintStream m_compilerDebugPrintStream = null;
boolean m_securityEnabled = false;
final Map<String, ProcInfoData> m_procInfoOverrides = new HashMap<String, ProcInfoData>();
final ClusterConfiguration cluster_config = new ClusterConfiguration();
private String m_snapshotPath = null;
private int m_snapshotRetain = 0;
private String m_snapshotPrefix = null;
private String m_snapshotFrequency = null;
public VoltProjectBuilder(String project_name) {
this.project_name = project_name;
}
public String getProjectName() {
return project_name;
}
public void addAllDefaults() {
// does nothing in the base class
}
public void addUsers(final UserInfo users[]) {
for (final UserInfo info : users) {
final boolean added = m_users.add(info);
if (!added) {
assert(added);
}
}
}
public void addGroups(final GroupInfo groups[]) {
for (final GroupInfo info : groups) {
final boolean added = m_groups.add(info);
if (!added) {
assert(added);
}
}
}
// -------------------------------------------------------------------
// DATABASE PARTITIONS
// -------------------------------------------------------------------
public void clearPartitions() {
this.cluster_config.clear();
}
public void addPartition(String hostname, int site_id, int partition_id) {
this.cluster_config.addPartition(hostname, site_id, partition_id);
}
// -------------------------------------------------------------------
// SCHEMA
// -------------------------------------------------------------------
public void addSchema(final URL schemaURL) {
assert(schemaURL != null) :
"Invalid null schema file for " + this.project_name;
addSchema(schemaURL.getPath());
}
public void addSchema(final File schemaFile) {
assert(schemaFile != null);
addSchema(schemaFile.getAbsolutePath());
}
public void addSchema(String schemaPath) {
try {
schemaPath = URLDecoder.decode(schemaPath, "UTF-8");
} catch (final UnsupportedEncodingException e) {
e.printStackTrace();
System.exit(-1);
}
assert(m_schemas.contains(schemaPath) == false);
final File schemaFile = new File(schemaPath);
assert(schemaFile != null);
assert(schemaFile.isDirectory() == false);
// this check below fails in some valid cases (like when the file is in a jar)
//assert schemaFile.canRead()
// : "can't read file: " + schemaPath;
m_schemas.add(schemaPath);
}
// -------------------------------------------------------------------
// PROCEDURES
// -------------------------------------------------------------------
protected String getStmtProcedureSQL(String name) {
for (ProcedureInfo pi : m_procedures) {
if (pi.name.equals(name)) {
return (pi.sql);
}
}
return (null);
}
public void clearProcedures() {
m_procedures.clear();
m_procInfoOverrides.clear();
m_prefetchQueries.clear();
}
/**
* Remove all of the Procedures whose name matches the given Pattern
* @param procNameRegex
*/
public void removeProcedures(Pattern procNameRegex) {
Set<ProcedureInfo> toRemove = new HashSet<ProcedureInfo>();
for (ProcedureInfo procInfo : m_procedures) {
Matcher m = procNameRegex.matcher(procInfo.name);
if (m.matches()) {
toRemove.add(procInfo);
}
} // FOR
for (ProcedureInfo procInfo : toRemove)
this.removeProcedure(procInfo);
}
/**
* Remove a Procedure based on its name
* @param procName
*/
public void removeProcedure(String procName) {
for (ProcedureInfo procInfo : m_procedures) {
if (procInfo.name.equalsIgnoreCase(procName)) {
this.removeProcedure(procInfo);
break;
}
} // FOR
}
/**
* Removes the given ProcedureInfo
* @param procInfo
*/
protected void removeProcedure(ProcedureInfo procInfo) {
m_procedures.remove(procInfo);
m_procInfoOverrides.remove(procInfo.name);
m_prefetchQueries.remove(procInfo.name);
m_paramMappings.remove(procInfo.name);
if (LOG.isDebugEnabled())
LOG.debug("Removed Procedure " + procInfo.name + " from project " + this.project_name.toUpperCase());
}
/**
* Provide the path to the ParameterMappingsSet file to use to
* populate the Catalog after it has been created.
* @param mappingsFile
*/
public void addParameterMappings(File mappingsFile) {
assert(mappingsFile != null) :
"Invalid ParameterMappingsSet file";
assert(mappingsFile.exists()) :
"The ParameterMappingsSet file '" + mappingsFile + "' does not exist";
m_paramMappingsFile = mappingsFile;
}
/**
* Mark a ProcParameter to be mapped to a StmtParameter
* @param procedureClass
* @param procParamIdx
* @param statementName
* @param stmtParamIdx
*/
public void mapParameters(Class<? extends VoltProcedure> procedureClass, int procParamIdx, String statementName, int stmtParamIdx) {
this.mapParameters(procedureClass.getSimpleName(), procParamIdx, statementName, stmtParamIdx);
}
/**
* Mark a ProcParameter to be mapped to a StmtParameter
* @param procedureName
* @param procParamIdx
* @param statementName
* @param stmtParamIdx
*/
public void mapParameters(String procedureName, int procParamIdx, String statementName, int stmtParamIdx) {
Map<Integer, Pair<String, Integer>> m = m_paramMappings.get(procedureName);
if (m == null) {
m = new LinkedHashMap<Integer, Pair<String,Integer>>();
m_paramMappings.put(procedureName, m);
}
Pair<String, Integer> stmtPair = Pair.of(statementName, stmtParamIdx);
m.put(procParamIdx, stmtPair);
}
// -------------------------------------------------------------------
// PREFETCHABLE
// -------------------------------------------------------------------
/**
* Mark a Statement as prefetchable
* @param procedureName
* @param statementName
*/
public void markStatementPrefetchable(Class<? extends VoltProcedure> procedureClass, String statementName) {
this.markStatementPrefetchable(procedureClass.getSimpleName(), statementName);
}
/**
* Mark a Statement as prefetchable
* @param procedureName
* @param statementName
*/
public void markStatementPrefetchable(String procedureName, String statementName) {
Set<String> stmtNames = m_prefetchQueries.get(procedureName);
if (stmtNames == null) {
stmtNames = new HashSet<String>();
m_prefetchQueries.put(procedureName, stmtNames);
}
stmtNames.add(statementName);
}
// -------------------------------------------------------------------
// EVICTABLE TABLES
// -------------------------------------------------------------------
/**
* Mark a table as evictable. When using the anti-caching feature, this means
* that portions of this table can be moved out to blocks on disk
* @param tableName
*/
public void markTableEvictable(String tableName) {
m_evictableTables.add(tableName);
}
/**
* Mark a table as evictable. When using the anti-caching feature, this means
* that portions of this table can be moved out to blocks on disk
* @param tableName
*/
public void markTableBatchEvictable(String tableName) {
m_batchEvictableTables.add(tableName);
}
// -------------------------------------------------------------------
// DEFERRABLE STATEMENTS
// -------------------------------------------------------------------
/**
* Mark a Statement as deferrable
* @param procedureName
* @param statementName
*/
public void markStatementDeferrable(Class<? extends VoltProcedure> procedureClass, String statementName) {
this.markStatementDeferrable(procedureClass.getSimpleName(), statementName);
}
/**
* Mark a Statement as deferrable
* @param procedureName
* @param statementName
*/
public void markStatementDeferrable(String procedureName, String statementName) {
Set<String> stmtNames = m_deferQueries.get(procedureName);
if (stmtNames == null) {
stmtNames = new HashSet<String>();
m_deferQueries.put(procedureName, stmtNames);
}
stmtNames.add(statementName);
}
// -------------------------------------------------------------------
// SINGLE-STATEMENT PROCEDURES
// -------------------------------------------------------------------
/**
* Create a single statement procedure that only has one query
* The input parameters to the SQL statement will be automatically passed
* from the input parameters to the procedure.
* @param procedureName
* @param sql
*/
public void addStmtProcedure(String procedureName, String sql) {
addStmtProcedure(procedureName, sql, null);
}
public void addStmtProcedure(String name, String sql, String partitionInfo) {
addProcedures(new ProcedureInfo(new String[0], new String[0], name, sql, partitionInfo));
}
public void addProcedure(final Class<?> procedure) {
final ArrayList<ProcedureInfo> procArray = new ArrayList<ProcedureInfo>();
procArray.add(new ProcedureInfo(new String[0], new String[0], procedure));
addProcedures(procArray);
}
public void addProcedures(final Class<?>... procedures) {
if (procedures != null && procedures.length > 0) {
final ArrayList<ProcedureInfo> procArray = new ArrayList<ProcedureInfo>();
for (final Class<?> procedure : procedures)
procArray.add(new ProcedureInfo(new String[0], new String[0], procedure));
addProcedures(procArray);
}
}
/*
* List of users and groups permitted to invoke the procedure
*/
public void addProcedures(final ProcedureInfo... procedures) {
final ArrayList<ProcedureInfo> procArray = new ArrayList<ProcedureInfo>();
for (final ProcedureInfo procedure : procedures)
procArray.add(procedure);
addProcedures(procArray);
}
public void addProcedures(final Iterable<ProcedureInfo> procedures) {
// check for duplicates and existings
final Set<ProcedureInfo> newProcs = new HashSet<ProcedureInfo>();
for (final ProcedureInfo procInfo : procedures) {
assert(newProcs.contains(procInfo) == false);
if (m_procedures.contains(procInfo)) {
LOG.warn(String.format("Skipping duplicate procedure '%s' for %s",
procInfo.name, this.project_name));
}
newProcs.add(procInfo);
} // FOR
// add the procs
for (final ProcedureInfo procedure : newProcs) {
m_procedures.add(procedure);
}
}
public void addPartitionInfo(final String tableName, final String partitionColumnName) {
assert (m_partitionInfos.containsKey(tableName) == false);
m_partitionInfos.put(tableName, partitionColumnName);
}
public void addSupplementalClasses(final Class<?>... supplementals) {
final ArrayList<Class<?>> suppArray = new ArrayList<Class<?>>();
for (final Class<?> supplemental : supplementals)
suppArray.add(supplemental);
addSupplementalClasses(suppArray);
}
public void addSupplementalClasses(final Iterable<Class<?>> supplementals) {
// check for duplicates and existings
final HashSet<Class<?>> newSupps = new HashSet<Class<?>>();
for (final Class<?> supplemental : supplementals) {
assert(newSupps.contains(supplemental) == false);
assert(m_supplementals.contains(supplemental) == false);
newSupps.add(supplemental);
}
// add the supplemental classes
for (final Class<?> supplemental : supplementals)
m_supplementals.add(supplemental);
}
// -------------------------------------------------------------------
// TABLE PARTITIONS
// -------------------------------------------------------------------
public void addTablePartitionInfo(Table catalog_tbl, Column catalog_col) {
assert(catalog_col != null) : "Unexpected null partition column for " + catalog_tbl;
// TODO: Support special columns
if (catalog_col instanceof VerticalPartitionColumn) {
catalog_col = ((VerticalPartitionColumn)catalog_col).getHorizontalColumn();
}
if (catalog_col instanceof MultiColumn) {
catalog_col = ((MultiColumn)catalog_col).get(0);
}
this.addTablePartitionInfo(catalog_tbl.getName(), catalog_col.getName());
}
public void addTablePartitionInfo(final String tableName, final String partitionColumnName) {
assert(m_partitionInfos.containsKey(tableName) == false) :
String.format("Already contains table partitioning info for '%s': %s",
tableName, m_partitionInfos.get(tableName));
m_partitionInfos.put(tableName, partitionColumnName);
}
// -------------------------------------------------------------------
// REPLICATED SECONDARY INDEXES
// -------------------------------------------------------------------
public void removeReplicatedSecondaryIndexes() {
m_replicatedSecondaryIndexes.clear();
}
public void addReplicatedSecondaryIndex(final String tableName, final String...partitionColumnNames) {
this.addReplicatedSecondaryIndexInfo(tableName, true, partitionColumnNames);
}
public void addReplicatedSecondaryIndexInfo(final String tableName, final boolean createIndex, final String...partitionColumnNames) {
ArrayList<String> columns = new ArrayList<String>();
for (String col : partitionColumnNames) {
columns.add(col);
}
this.addReplicatedSecondaryIndexInfo(tableName, createIndex, columns);
}
public void addReplicatedSecondaryIndexInfo(final String tableName, final boolean createIndex, final Collection<String> partitionColumnNames) {
assert(m_replicatedSecondaryIndexes.containsKey(tableName) == false);
m_replicatedSecondaryIndexes.put(tableName, Pair.of(createIndex, partitionColumnNames));
}
public void enableReplicatedSecondaryIndexes(boolean val) {
m_replicatedSecondaryIndexesEnabled = val;
}
public void setSecurityEnabled(final boolean enabled) {
m_securityEnabled = enabled;
}
public void setSnapshotSettings(
String frequency,
int retain,
String path,
String prefix) {
assert(frequency != null);
assert(path != null);
assert(prefix != null);
m_snapshotFrequency = frequency;
m_snapshotRetain = retain;
m_snapshotPath = path;
m_snapshotPrefix = prefix;
}
public void addELT(final String loader, boolean enabled,
List<String> users, List<String> groups) {
m_elloader = loader;
m_elenabled = enabled;
m_elAuthUsers = users;
m_elAuthGroups = groups;
}
public void addELTTable(String name, boolean exportonly) {
ELTTableInfo info = new ELTTableInfo(name, exportonly);
m_eltTables.add(info);
}
public void setCompilerDebugPrintStream(final PrintStream out) {
m_compilerDebugPrintStream = out;
}
/**
* Override the procedure annotation with the specified values for a
* specified procedure.
*
* @param procName The name of the procedure to override the annotation.
* @param info The values to use instead of the annotation.
*/
public void overrideProcInfoForProcedure(final String procName, final ProcInfoData info) {
assert(procName != null);
assert(info != null);
m_procInfoOverrides.put(procName, info);
}
public boolean compile(final String jarPath) {
return compile(jarPath, 1, 1, 0, "localhost");
}
public boolean compile(final File jarPath, final int sitesPerHost, final int replication) {
return compile(jarPath.getAbsolutePath(), sitesPerHost, 1, replication, "localhost");
}
public boolean compile(final String jarPath, final int sitesPerHost, final int replication) {
return compile(jarPath, sitesPerHost, 1, replication, "localhost");
}
public boolean compile(final String jarPath, final int sitesPerHost, final int hostCount,
final int replication, final String leaderAddress)
{
VoltCompiler compiler = new VoltCompiler();
if (m_replicatedSecondaryIndexesEnabled) {
compiler.enableVerticalPartitionOptimizations();
}
return compile(compiler, jarPath, sitesPerHost, hostCount, replication,
leaderAddress);
}
public boolean compile(final VoltCompiler compiler, final String jarPath,
final int sitesPerHost, final int hostCount,
final int replication, final String leaderAddress)
{
assert(jarPath != null);
assert(sitesPerHost >= 1);
assert(hostCount >= 1);
assert(leaderAddress != null);
// this stuff could all be converted to org.voltdb.compiler.projectfile.*
// jaxb objects and (WE ARE!) marshaled to XML. Just needs some elbow grease.
DocumentBuilderFactory docFactory;
DocumentBuilder docBuilder;
Document doc;
try {
docFactory = DocumentBuilderFactory.newInstance();
docBuilder = docFactory.newDocumentBuilder();
doc = docBuilder.newDocument();
}
catch (final ParserConfigurationException e) {
e.printStackTrace();
return false;
}
// <project>
final Element project = doc.createElement("project");
doc.appendChild(project);
// <security>
final Element security = doc.createElement("security");
security.setAttribute("enabled", Boolean.valueOf(m_securityEnabled).toString());
project.appendChild(security);
// <database>
final Element database = doc.createElement("database");
database.setAttribute("name", "database");
database.setAttribute("project", this.project_name);
project.appendChild(database);
buildDatabaseElement(doc, database);
// boilerplate to write this DOM object to file.
StreamResult result;
try {
final Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
result = new StreamResult(new StringWriter());
final DOMSource domSource = new DOMSource(doc);
transformer.transform(domSource, result);
}
catch (final TransformerConfigurationException e) {
e.printStackTrace();
return false;
}
catch (final TransformerFactoryConfigurationError e) {
e.printStackTrace();
return false;
}
catch (final TransformerException e) {
e.printStackTrace();
return false;
}
// String xml = result.getWriter().toString();
// System.out.println(xml);
final File projectFile = writeStringToTempFile(result.getWriter().toString());
final String projectPath = projectFile.getPath();
LOG.debug("PROJECT XML: " + projectPath);
ClusterConfig cc = (this.cluster_config.isEmpty() ?
new ClusterConfig(hostCount, sitesPerHost, replication, leaderAddress) :
this.cluster_config);
final boolean success = compiler.compile(projectPath,
cc,
jarPath,
m_compilerDebugPrintStream,
m_procInfoOverrides);
// HACK: If we have a ParameterMappingsSet that we need to apply
// either from a file or a fixed mappings, then we have
// to load the catalog into this JVM, apply the mappings, and then
// update the jar file with the new catalog
if (m_paramMappingsFile != null || m_paramMappings.isEmpty() == false) {
File jarFile = new File(jarPath);
Catalog catalog = CatalogUtil.loadCatalogFromJar(jarFile);
assert(catalog != null);
Database catalog_db = CatalogUtil.getDatabase(catalog);
this.applyParameterMappings(catalog_db);
// Construct a List of prefetchable Statements
this.applyPrefetchableFlags(catalog_db);
// Write it out!
try {
CatalogUtil.updateCatalogInJar(jarFile, catalog, m_paramMappingsFile);
} catch (Exception ex) {
String msg = "Failed to updated Catalog in jar file '" + jarPath + "'";
throw new RuntimeException(msg, ex);
}
}
return success;
}
private void applyParameterMappings(Database catalog_db) {
ParameterMappingsSet mappings = new ParameterMappingsSet();
// Load ParameterMappingSet from file
if (m_paramMappingsFile != null) {
try {
mappings.load(m_paramMappingsFile, catalog_db);
} catch (IOException ex) {
String msg = "Failed to load ParameterMappingsSet file '" + m_paramMappingsFile + "'";
throw new RuntimeException(msg, ex);
}
}
// Build ParameterMappingSet from user-provided inputs
else {
for (String procName : m_paramMappings.keySet()) {
Procedure catalog_proc = catalog_db.getProcedures().getIgnoreCase(procName);
assert(catalog_proc != null) :
"Invalid Procedure name for ParameterMappings '" + procName + "'";
for (Integer procParamIdx : m_paramMappings.get(procName).keySet()) {
ProcParameter catalog_procParam = catalog_proc.getParameters().get(procParamIdx.intValue());
assert(catalog_procParam != null) :
"Invalid ProcParameter for '" + procName + "' at offset " + procParamIdx;
Pair<String, Integer> stmtPair = m_paramMappings.get(procName).get(procParamIdx);
assert(stmtPair != null);
Statement catalog_stmt = catalog_proc.getStatements().getIgnoreCase(stmtPair.getFirst());
assert(catalog_stmt != null) :
"Invalid Statement name '" + stmtPair.getFirst() + "' for ParameterMappings " +
"for Procedure '" + procName + "'";
StmtParameter catalog_stmtParam = catalog_stmt.getParameters().get(stmtPair.getSecond().intValue());
assert(catalog_stmtParam != null) :
"Invalid StmtParameter for '" + catalog_stmt.fullName() + "' at offset " + stmtPair.getSecond();
// HACK: This assumes that the ProcParameter is not an array
// and that we want to map the first invocation of the Statement
// directly to the ProcParameter.
ParameterMapping pm = new ParameterMapping(catalog_stmt,
0,
catalog_stmtParam,
catalog_procParam,
0,
1.0);
mappings.add(pm);
} // FOR (ProcParameter)
} // FOR (Procedure)
}
// Apply it!
ParametersUtil.applyParameterMappings(catalog_db, mappings);
}
private void applyPrefetchableFlags(Database catalog_db) {
for (Procedure catalog_proc : catalog_db.getProcedures()) {
boolean proc_prefetchable = false;
for (Statement statement : catalog_proc.getStatements()) {
boolean stmt_prefetchable = true;
for (StmtParameter stmtParam : statement.getParameters()) {
if (stmtParam.getProcparameter() == null) {
stmt_prefetchable = false;
break;
}
} // FOR (StmtParameter)
if (stmt_prefetchable) {
statement.setPrefetchable(true);
proc_prefetchable = true;
}
} // FOR (Statement)
if (proc_prefetchable) {
catalog_proc.setPrefetchable(true);
}
} // FOR (Procedure)
}
private void buildDatabaseElement(Document doc, final Element database) {
// /project/database/users
final Element users = doc.createElement("users");
database.appendChild(users);
// users/user
if (m_users.isEmpty()) {
final Element user = doc.createElement("user");
user.setAttribute("name", "default");
user.setAttribute("groups", "default");
user.setAttribute("password", "");
user.setAttribute("sysproc", "true");
user.setAttribute("adhoc", "true");
users.appendChild(user);
}
else {
for (final UserInfo info : m_users) {
final Element user = doc.createElement("user");
user.setAttribute("name", info.name);
user.setAttribute("password", info.password);
user.setAttribute("sysproc", info.sysproc ? "true" : "false");
user.setAttribute("adhoc", info.adhoc ? "true" : "false");
// build up user/@groups. This attribute must be redesigned
if (info.groups.length > 0) {
final StringBuilder groups = new StringBuilder();
for (final String group : info.groups) {
if (groups.length() > 0)
groups.append(",");
groups.append(group);
}
user.setAttribute("groups", groups.toString());
}
users.appendChild(user);
}
}
// /project/database/groups
final Element groups = doc.createElement("groups");
database.appendChild(groups);
// groups/group
if (m_groups.isEmpty()) {
final Element group = doc.createElement("group");
group.setAttribute("name", "default");
group.setAttribute("sysproc", "true");
group.setAttribute("adhoc", "true");
groups.appendChild(group);
}
else {
for (final GroupInfo info : m_groups) {
final Element group = doc.createElement("group");
group.setAttribute("name", info.name);
group.setAttribute("sysproc", info.sysproc ? "true" : "false");
group.setAttribute("adhoc", info.adhoc ? "true" : "false");
groups.appendChild(group);
}
}
// /project/database/schemas
final Element schemas = doc.createElement("schemas");
database.appendChild(schemas);
// schemas/schema
for (final String schemaPath : m_schemas) {
final Element schema = doc.createElement("schema");
schema.setAttribute("path", schemaPath);
schemas.appendChild(schema);
}
// /project/database/procedures
final Element procedures = doc.createElement("procedures");
database.appendChild(procedures);
// procedures/procedure
for (final ProcedureInfo procedure : m_procedures) {
if (procedure.cls == null)
continue;
assert(procedure.sql == null);
final Element proc = doc.createElement("procedure");
proc.setAttribute("class", procedure.cls.getName());
// build up @users. This attribute should be redesigned
if (procedure.users.length > 0) {
final StringBuilder userattr = new StringBuilder();
for (final String user : procedure.users) {
if (userattr.length() > 0)
userattr.append(",");
userattr.append(user);
}
proc.setAttribute("users", userattr.toString());
}
// build up @groups. This attribute should be redesigned
if (procedure.groups.length > 0) {
final StringBuilder groupattr = new StringBuilder();
for (final String group : procedure.groups) {
if (groupattr.length() > 0)
groupattr.append(",");
groupattr.append(group);
}
proc.setAttribute("groups", groupattr.toString());
}
// HACK: Prefetchable Statements
if (m_prefetchQueries.containsKey(procedure.cls.getSimpleName())) {
Collection<String> stmtNames = m_prefetchQueries.get(procedure.cls.getSimpleName());
proc.setAttribute("prefetchable", StringUtil.join(",", stmtNames));
}
// HACK: Deferrable Statements
if (m_deferQueries.containsKey(procedure.cls.getSimpleName())) {
Collection<String> stmtNames = m_deferQueries.get(procedure.cls.getSimpleName());
proc.setAttribute("deferrable", StringUtil.join(",", stmtNames));
}
procedures.appendChild(proc);
}
// procedures/procedures (that are stmtprocedures)
for (final ProcedureInfo procedure : m_procedures) {
if (procedure.sql == null)
continue;
assert(procedure.cls == null);
final Element proc = doc.createElement("procedure");
proc.setAttribute("class", procedure.name);
if (procedure.partitionInfo != null);
proc.setAttribute("partitioninfo", procedure.partitionInfo);
// build up @users. This attribute should be redesigned
if (procedure.users.length > 0) {
final StringBuilder userattr = new StringBuilder();
for (final String user : procedure.users) {
if (userattr.length() > 0)
userattr.append(",");
userattr.append(user);
}
proc.setAttribute("users", userattr.toString());
}
// build up @groups. This attribute should be redesigned
if (procedure.groups.length > 0) {
final StringBuilder groupattr = new StringBuilder();
for (final String group : procedure.groups) {
if (groupattr.length() > 0)
groupattr.append(",");
groupattr.append(group);
}
proc.setAttribute("groups", groupattr.toString());
}
final Element sql = doc.createElement("sql");
proc.appendChild(sql);
final Text sqltext = doc.createTextNode(procedure.sql);
sql.appendChild(sqltext);
procedures.appendChild(proc);
}
if (m_partitionInfos.size() > 0) {
// /project/database/partitions
final Element partitions = doc.createElement("partitions");
database.appendChild(partitions);
// partitions/table
for (final Entry<String, String> partitionInfo : m_partitionInfos.entrySet()) {
final Element table = doc.createElement("partition");
table.setAttribute("table", partitionInfo.getKey());
table.setAttribute("column", partitionInfo.getValue());
partitions.appendChild(table);
}
}
// Evictable Tables
if (m_evictableTables.isEmpty() == false) {
final Element evictables = doc.createElement("evictables");
database.appendChild(evictables);
// Table entries
for (String tableName : m_evictableTables) {
final Element table = doc.createElement("evictable");
table.setAttribute("table", tableName);
evictables.appendChild(table);
}
}
// BatchEvictable Tables
if (m_batchEvictableTables.isEmpty() == false) {
final Element batchevictables = doc.createElement("batchevictables");
database.appendChild(batchevictables);
// Table entries
for (String tableName : m_batchEvictableTables) {
final Element table = doc.createElement("evictable");
table.setAttribute("table", tableName);
batchevictables.appendChild(table);
}
}
// Vertical Partitions
if (m_replicatedSecondaryIndexes.size() > 0) {
// /project/database/partitions
final Element verticalpartitions = doc.createElement("verticalpartitions");
database.appendChild(verticalpartitions);
// partitions/table
for (String tableName : m_replicatedSecondaryIndexes.keySet()) {
Pair<Boolean, Collection<String>> p = m_replicatedSecondaryIndexes.get(tableName);
Boolean createIndex = p.getFirst();
Collection<String> columnNames = p.getSecond();
final Element vp = doc.createElement("verticalpartition");
vp.setAttribute("table", tableName);
vp.setAttribute("indexed", createIndex.toString());
for (final String columnName : columnNames) {
final Element column = doc.createElement("column");
column.setTextContent(columnName);
vp.appendChild(column);
} // FOR (cols)
verticalpartitions.appendChild(vp);
} // FOR (tables)
}
// /project/database/classdependencies
final Element classdeps = doc.createElement("classdependencies");
database.appendChild(classdeps);
// classdependency
for (final Class<?> supplemental : m_supplementals) {
final Element supp= doc.createElement("classdependency");
supp.setAttribute("class", supplemental.getName());
classdeps.appendChild(supp);
}
// project/database/exports
if (m_elloader != null) {
final Element exports = doc.createElement("exports");
database.appendChild(exports);
final Element conn = doc.createElement("connector");
conn.setAttribute("class", m_elloader);
conn.setAttribute("enabled", m_elenabled ? "true" : "false");
// turn list into stupid comma separated attribute list
String usersattr = "";
if (m_elAuthUsers != null) {
for (String s : m_elAuthUsers) {
if (usersattr.isEmpty()) {
usersattr += s;
}
else {
usersattr += "," + s;
}
}
conn.setAttribute("users", usersattr);
}
// turn list into stupid comma separated attribute list
String groupsattr = "";
if (m_elAuthGroups != null) {
for (String s : m_elAuthGroups) {
if (groupsattr.isEmpty()) {
groupsattr += s;
}
else {
groupsattr += "," + s;
}
}
conn.setAttribute("groups", groupsattr);
}
exports.appendChild(conn);
if (m_eltTables.size() > 0) {
final Element tables = doc.createElement("tables");
conn.appendChild(tables);
for (ELTTableInfo info : m_eltTables) {
final Element table = doc.createElement("table");
table.setAttribute("name", info.m_tablename);
table.setAttribute("exportonly", info.m_export_only ? "true" : "false");
tables.appendChild(table);
}
}
}
if (m_snapshotPath != null) {
final Element snapshot = doc.createElement("snapshot");
snapshot.setAttribute("frequency", m_snapshotFrequency);
snapshot.setAttribute("path", m_snapshotPath);
snapshot.setAttribute("prefix", m_snapshotPrefix);
snapshot.setAttribute("retain", Integer.toString(m_snapshotRetain));
database.appendChild(snapshot);
}
}
/**
* Utility method to take a string and put it in a file. This is used by
* this class to write the project file to a temp file, but it also used
* by some other tests.
*
* @param content The content of the file to create.
* @return A reference to the file created or null on failure.
*/
public static File writeStringToTempFile(final String content) {
return FileUtil.writeStringToTempFile(content, "project", true);
}
}