/** * This method behaves similarly to parse(), but allows the caller to pass in XML to avoid * re-parsing SQL text that has already gone through HSQL. * * @param xmlSql XML produced by previous invocation of HSQL */ public void parseFromXml(VoltXMLElement xmlSQL) { m_recentErrorMsg = null; m_xmlSQL = xmlSQL; if (m_xmlSQL.attributes.containsKey(UPSERT_TAG)) { m_isUpsert = true; } m_planSelector.outputCompiledStatement(m_xmlSQL); }
/** * Auto-parameterize all of the literals in the parsed SQL statement. * * @return An opaque token representing the parsed statement with (possibly) parameterization. */ public String parameterize() { m_paramzInfo = ParameterizationInfo.parameterize(m_xmlSQL); Set<Integer> paramIds = new HashSet<Integer>(); ParameterizationInfo.findUserParametersRecursively(m_xmlSQL, paramIds); m_adhocUserParamsCount = paramIds.size(); // skip plans with pre-existing parameters and plans that don't parameterize // assume a user knows how to cache/optimize these if (m_paramzInfo != null) { // if requested output the second version of the parsed plan m_planSelector.outputParameterizedCompiledStatement(m_paramzInfo.parameterizedXmlSQL); return m_paramzInfo.parameterizedXmlSQL.toMinString(); } // fallback when parameterization is return m_xmlSQL.toMinString(); }
/** * Parse a SQL literal statement into an unplanned, intermediate representation. This is normally * followed by a call to {@link this#plan(AbstractCostModel, String, String, String, String, int, * ScalarValueHints[]) }, but splitting these two affords an opportunity to check a cache for a * plan matching the auto-parameterized parsed statement. */ public void parse() throws PlanningErrorException { // reset any error message m_recentErrorMsg = null; // Reset plan node ids to start at 1 for this plan AbstractPlanNode.resetPlanNodeIds(); // determine the type of the query // // (Hmmm... seems like this pre-processing of the SQL text // and subsequent placement of UPSERT_TAG should be pushed down // into getXMLCompiledStatement) m_sql = m_sql.trim(); if (m_sql.length() > 6 && m_sql.substring(0, 6).toUpperCase().startsWith("UPSERT")) { // ENG-7395 m_isUpsert = true; m_sql = "INSERT" + m_sql.substring(6); } // use HSQLDB to get XML that describes the semantics of the statement // this is much easier to parse than SQL and is checked against the catalog try { m_xmlSQL = m_HSQL.getXMLCompiledStatement(m_sql); } catch (HSQLParseException e) { // XXXLOG probably want a real log message here throw new PlanningErrorException(e.getMessage()); } if (m_isUpsert) { assert (m_xmlSQL.name.equalsIgnoreCase("INSERT")); // for AdHoc cache distinguish purpose which is based on the XML m_xmlSQL.attributes.put(UPSERT_TAG, "true"); } m_planSelector.outputCompiledStatement(m_xmlSQL); }
private CompiledPlan compileFromXML(VoltXMLElement xmlSQL, String[] paramValues) { // Get a parsed statement from the xml // The callers of compilePlan are ready to catch any exceptions thrown here. AbstractParsedStmt parsedStmt = AbstractParsedStmt.parse(m_sql, xmlSQL, paramValues, m_db, m_joinOrder); if (parsedStmt == null) { m_recentErrorMsg = "Failed to parse SQL statement: " + getOriginalSql(); return null; } if (m_isUpsert) { // no insert/upsert with joins if (parsedStmt.m_tableList.size() != 1) { m_recentErrorMsg = "UPSERT is support only with one single table: " + getOriginalSql(); return null; } Table tb = parsedStmt.m_tableList.get(0); Constraint pkey = null; for (Constraint ct : tb.getConstraints()) { if (ct.getType() == ConstraintType.PRIMARY_KEY.getValue()) { pkey = ct; break; } } if (pkey == null) { m_recentErrorMsg = "Unsupported UPSERT table without primary key: " + getOriginalSql(); return null; } } m_planSelector.outputParsedStatement(parsedStmt); // Init Assembler. Each plan assembler requires a new instance of the PlanSelector // to keep track of the best plan PlanAssembler assembler = new PlanAssembler(m_cluster, m_db, m_partitioning, (PlanSelector) m_planSelector.clone()); // find the plan with minimal cost CompiledPlan bestPlan = assembler.getBestCostPlan(parsedStmt); // This processing of bestPlan outside/after getBestCostPlan // allows getBestCostPlan to be called both here and // in PlanAssembler.getNextUnion on each branch of a union. // make sure we got a winner if (bestPlan == null) { if (m_debuggingStaticModeToRetryOnError) { assembler.getBestCostPlan(parsedStmt); } m_recentErrorMsg = assembler.getErrorMessage(); if (m_recentErrorMsg == null) { m_recentErrorMsg = "Unable to plan for statement. Error unknown."; } return null; } if (bestPlan.isReadOnly()) { SendPlanNode sendNode = new SendPlanNode(); // connect the nodes to build the graph sendNode.addAndLinkChild(bestPlan.rootPlanGraph); // this plan is final, generate schema and resolve all the column index references bestPlan.rootPlanGraph = sendNode; } // Execute the generateOutputSchema and resolveColumnIndexes once for the best plan bestPlan.rootPlanGraph.generateOutputSchema(m_db); bestPlan.rootPlanGraph.resolveColumnIndexes(); if (parsedStmt instanceof ParsedSelectStmt) { List<SchemaColumn> columns = bestPlan.rootPlanGraph.getOutputSchema().getColumns(); ((ParsedSelectStmt) parsedStmt).checkPlanColumnMatch(columns); } // Output the best plan debug info assembler.finalizeBestCostPlan(); // reset all the plan node ids for a given plan // this makes the ids deterministic bestPlan.resetPlanNodeIds(1); // split up the plan everywhere we see send/recieve into multiple plan fragments List<AbstractPlanNode> receives = bestPlan.rootPlanGraph.findAllNodesOfClass(AbstractReceivePlanNode.class); if (receives.size() > 1) { // Have too many receive node for two fragment plan limit m_recentErrorMsg = "This join of multiple partitioned tables is too complex. " + "Consider simplifying its subqueries: " + getOriginalSql(); return null; } /*/ enable for debug ... if (receives.size() > 1) { System.out.println(plan.rootPlanGraph.toExplainPlanString()); } // ... enable for debug */ if (receives.size() == 1) { AbstractReceivePlanNode recvNode = (AbstractReceivePlanNode) receives.get(0); fragmentize(bestPlan, recvNode); } return bestPlan; }