/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.sysprocs; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import org.voltcore.utils.Pair; import org.voltdb.DependencyPair; import org.voltdb.DeprecatedProcedureAPIAccess; import org.voltdb.ParameterSet; import org.voltdb.SQLStmt; import org.voltdb.SQLStmtAdHocHelper; import org.voltdb.StoredProcedureInvocation; import org.voltdb.SystemProcedureExecutionContext; import org.voltdb.VoltSystemProcedure; import org.voltdb.VoltTable; import org.voltdb.common.Constants; import org.voltdb.compiler.AdHocPlannedStatement; import org.voltdb.compiler.AdHocPlannedStmtBatch; import org.voltdb.planner.ActivePlanRepository; import com.google_voltpatches.common.base.Charsets; /** * Base class for @AdHoc... system procedures. * * Provides default implementation for VoltSystemProcedure call-backs. */ public abstract class AdHocBase extends VoltSystemProcedure { /* (non-Javadoc) * @see org.voltdb.VoltSystemProcedure#init() */ @Override public long[] getPlanFragmentIds() { return new long[]{}; } /* (non-Javadoc) * @see org.voltdb.VoltSystemProcedure#executePlanFragment(java.util.Map, long, org.voltdb.ParameterSet, org.voltdb.SystemProcedureExecutionContext) */ @Override public DependencyPair executePlanFragment( Map<Integer, List<VoltTable>> dependencies, long fragmentId, ParameterSet params, SystemProcedureExecutionContext context) { // This code should never be called. assert(false); return null; } /** * Get a string containing the SQL statements and any parameters for a given * batch passed to an ad-hoc query. Used for debugging and logging. */ public static String adHocSQLFromInvocationForDebug(StoredProcedureInvocation invocation) { assert(invocation.getProcName().startsWith("@AdHoc")); ParameterSet params = invocation.getParams(); assert(params.size() == 2 || params.size() == 3); // the final param is the byte array we need byte[] serializedBatchData = (byte[]) params.getParam(params.size() - 1); Pair<Object[], AdHocPlannedStatement[]> data = decodeSerializedBatchData(serializedBatchData); Object[] userparams = data.getFirst(); AdHocPlannedStatement[] statements = data.getSecond(); StringBuilder sb = new StringBuilder(); if (statements.length == 0) { sb.append("ADHOC INVOCATION HAS NO SQL"); } else if (statements.length == 1) { sb.append(adHocSQLStringFromPlannedStatement(statements[0], userparams)); } else { // > 1 sb.append("BEGIN ADHOC_SQL_BATCH {\n"); for (AdHocPlannedStatement stmt : statements) { sb.append(adHocSQLStringFromPlannedStatement(stmt, userparams)).append("\n"); } sb.append("} END ADHOC_SQL_BATCH"); } return sb.toString(); } /** * Get a string containing a SQL statement and any parameters for a given * AdHocPlannedStatement. Used for debugging and logging. */ public static String adHocSQLStringFromPlannedStatement(AdHocPlannedStatement statement, Object[] userparams) { final int MAX_PARAM_LINE_CHARS = 120; StringBuilder sb = new StringBuilder(); String sql = new String(statement.sql, Charsets.UTF_8); sb.append(sql); Object[] params = paramsForStatement(statement, userparams); // convert params to strings of a certain max length for (int i = 0; i < params.length; i++) { Object param = params[i]; String paramLineStr = String.format(" Param %d: %s", i, param.toString()); // trim param line if it's silly long if (paramLineStr.length() > MAX_PARAM_LINE_CHARS) { paramLineStr = paramLineStr.substring(0, MAX_PARAM_LINE_CHARS - 3); paramLineStr += "..."; } sb.append('\n').append(paramLineStr); } return sb.toString(); } /** * Decode binary data into structures needed to process adhoc queries. * This code was pulled out of runAdHoc so it could be shared there and with * adHocSQLStringFromPlannedStatement. */ public static Pair<Object[], AdHocPlannedStatement[]> decodeSerializedBatchData(byte[] serializedBatchData) { // Collections must be the same size since they all contain slices of the same data. assert(serializedBatchData != null); ByteBuffer buf = ByteBuffer.wrap(serializedBatchData); AdHocPlannedStatement[] statements = null; Object[] userparams = null; try { userparams = AdHocPlannedStmtBatch.userParamsFromBuffer(buf); statements = AdHocPlannedStmtBatch.planArrayFromBuffer(buf); } catch (IOException e) { throw new VoltAbortException(e); } return new Pair<Object[], AdHocPlannedStatement[]>(userparams, statements); } /** * Get the params for a specific SQL statement within a batch. * Note that there is usually a batch size of one. */ static Object[] paramsForStatement(AdHocPlannedStatement statement, Object[] userparams) { // When there are no user-provided parameters, statements may have parameterized constants. if (userparams.length > 0) { return userparams; } else { return statement.extractedParamArray(); } } /** * Call from derived class run() method for implementation. * * @param ctx * @param aggregatorFragments * @param collectorFragments * @param sqlStatements * @param replicatedTableDMLFlags * @return */ public VoltTable[] runAdHoc(SystemProcedureExecutionContext ctx, byte[] serializedBatchData) { Pair<Object[], AdHocPlannedStatement[]> data = decodeSerializedBatchData(serializedBatchData); Object[] userparams = data.getFirst(); AdHocPlannedStatement[] statements = data.getSecond(); if (statements.length == 0) { return new VoltTable[]{}; } for (AdHocPlannedStatement statement : statements) { if (!statement.core.wasPlannedAgainstHash(ctx.getCatalogHash())) { @SuppressWarnings("deprecation") String msg = String.format("AdHoc transaction %d wasn't planned " + "against the current catalog version. Statement: %s", DeprecatedProcedureAPIAccess.getVoltPrivateRealTransactionId(this), new String(statement.sql, Constants.UTF8ENCODING)); throw new VoltAbortException(msg); } // Don't cache the statement text, since ad hoc statements // that differ only by constants reuse the same plan, statement text may change. long aggFragId = ActivePlanRepository.loadOrAddRefPlanFragment( statement.core.aggregatorHash, statement.core.aggregatorFragment, null); long collectorFragId = 0; if (statement.core.collectorFragment != null) { collectorFragId = ActivePlanRepository.loadOrAddRefPlanFragment( statement.core.collectorHash, statement.core.collectorFragment, null); } SQLStmt stmt = SQLStmtAdHocHelper.createWithPlan( statement.sql, aggFragId, statement.core.aggregatorHash, true, collectorFragId, statement.core.collectorHash, true, statement.core.isReplicatedTableDML, statement.core.readOnly, statement.core.parameterTypes, m_site); Object[] params = paramsForStatement(statement, userparams); voltQueueSQL(stmt, params); } return voltExecuteSQL(true); } }