/* This file is part of VoltDB.
* Copyright (C) 2008-2010 VoltDB L.L.C.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb.planner;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.List;
import org.hsqldb.HSQLInterface;
import org.json.JSONException;
import org.json.JSONObject;
import org.voltdb.VoltType;
import org.voltdb.catalog.Catalog;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Statement;
import org.voltdb.catalog.StmtParameter;
import org.voltdb.compiler.DDLCompiler;
import org.voltdb.compiler.DatabaseEstimates;
import org.voltdb.compiler.VoltCompiler;
import org.voltdb.plannodes.AbstractPlanNode;
import org.voltdb.plannodes.PlanNodeList;
import org.voltdb.types.QueryType;
import org.voltdb.utils.BuildDirectoryUtils;
import org.voltdb.utils.Pair;
/**
* Some utility functions to compile SQL statements for plan generation tests.
*/
public class PlannerTestAideDeCamp {
private final Catalog catalog;
private final Procedure proc;
private final HSQLInterface hsql;
private final Database db;
int compileCounter = 0;
/**
* Loads the schema at ddlurl and setups a voltcompiler / hsql instance.
* @param ddlurl URL to the schema/ddl file.
* @param basename Unique string, JSON plans [basename]-stmt-#_json.txt on disk
* @throws Exception
*/
public PlannerTestAideDeCamp(URL ddlurl, String basename) throws Exception {
catalog = new Catalog();
catalog.execute("add / clusters cluster");
catalog.execute("add /clusters[cluster] databases database");
db = catalog.getClusters().get("cluster").getDatabases().get("database");
proc = db.getProcedures().add(basename);
String schemaPath = URLDecoder.decode(ddlurl.getPath(), "UTF-8");
VoltCompiler compiler = new VoltCompiler();
hsql = HSQLInterface.loadHsqldb();
//hsql.runDDLFile(schemaPath);
DDLCompiler ddl_compiler = new DDLCompiler(compiler, hsql);
ddl_compiler.loadSchema(schemaPath);
ddl_compiler.compileToCatalog(catalog, db);
}
/**
* Cleans up HSQL. Mandatory - call this when done!
*/
public void tearDown() {
hsql.close();
}
public Catalog getCatalog() {
return catalog;
}
/**
* Compile a statement and return the final plan graph.
* @param sql
* @param paramCount
*/
public AbstractPlanNode compile(String sql, int paramCount) {
Statement catalogStmt = proc.getStatements().add("stmt-" + String.valueOf(compileCounter++));
catalogStmt.setSqltext(sql);
catalogStmt.setSinglepartition(false);
catalogStmt.setBatched(false);
catalogStmt.setParamnum(paramCount);
// determine the type of the query
QueryType qtype = QueryType.SELECT;
catalogStmt.setReadonly(true);
if (sql.toLowerCase().startsWith("insert")) {
qtype = QueryType.INSERT;
catalogStmt.setReadonly(false);
}
if (sql.toLowerCase().startsWith("update")) {
qtype = QueryType.UPDATE;
catalogStmt.setReadonly(false);
}
if (sql.toLowerCase().startsWith("delete")) {
qtype = QueryType.DELETE;
catalogStmt.setReadonly(false);
}
catalogStmt.setQuerytype(qtype.getValue());
// name will look like "basename-stmt-#"
String name = catalogStmt.getParent().getTypeName() + "-" + catalogStmt.getTypeName();
DatabaseEstimates estimates = new DatabaseEstimates();
TrivialCostModel costModel = new TrivialCostModel();
QueryPlanner planner =
new QueryPlanner(catalog.getClusters().get("cluster"), db, hsql,
estimates, true, false);
CompiledPlan plan = null;
plan = planner.compilePlan(costModel, catalogStmt.getSqltext(), catalogStmt.getTypeName(),
catalogStmt.getParent().getTypeName(), catalogStmt.getSinglepartition(), null);
if (plan == null)
{
String msg = "planner.compilePlan returned null plan";
String plannerMsg = planner.getErrorMessage();
if (plannerMsg != null)
{
msg += " with error: \"" + plannerMsg + "\"";
}
throw new NullPointerException(msg);
}
// Input Parameters
// We will need to update the system catalogs with this new information
// If this is an adhoc query then there won't be any parameters
for (ParameterInfo param : plan.parameters) {
StmtParameter catalogParam = catalogStmt.getParameters().add(String.valueOf(param.index));
catalogParam.setJavatype(param.type.getValue());
catalogParam.setIndex(param.index);
}
// Output Columns
int index = 0;
for (Integer plancol : plan.columns) {
PlanColumn planColumn = planner.getPlannerContext().get(plancol);
Column column = catalogStmt.getOutput_columns().add(planColumn.getDisplayName()); // String.valueOf(index));
column.setNullable(false);
column.setIndex(index);
// column.setName(planColumn.displayName());
column.setType(planColumn.type().getValue());
column.setSize(planColumn.width());
index++;
}
CompiledPlan.Fragment fragment = plan.fragments.get(0);
PlanNodeList nodeList = new PlanNodeList(fragment.planGraph);
//Store the list of parameters types and indexes in the plan node list.
List<Pair<Integer, VoltType>> parameters = nodeList.getParameters();
for (ParameterInfo param : plan.parameters) {
Pair<Integer, VoltType> parameter = new Pair<Integer, VoltType>(param.index, param.type);
parameters.add(parameter);
}
// Now update our catalog information
// HACK: We're using the node_tree's hashCode() as it's name. It would be really
// nice if the Catalog code give us an guid without needing a name first...
String json = null;
try {
JSONObject jobj = new JSONObject(nodeList.toJSONString());
json = jobj.toString(4);
} catch (JSONException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
System.exit(-1);
}
//
// We then stick a serialized version of PlanNodeTree into a PlanFragment
//
try {
PrintStream plansJSONOut = BuildDirectoryUtils.getDebugOutputPrintStream(
"statement-plans", name + "_json.txt");
plansJSONOut.print(json);
plansJSONOut.close();
PrintStream plansDOTOut = BuildDirectoryUtils.getDebugOutputPrintStream(
"statement-plans", name + ".dot");
plansDOTOut.print(nodeList.toDOTString("name"));
plansDOTOut.close();
} catch (Exception e) {
e.printStackTrace();
}
return nodeList.getRootPlanNode();
}
}