/* 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(); } }