/* 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; import java.util.Date; import java.util.Random; import org.voltdb.Expectation.Type; /** * Wraps the stored procedure object created by the user * with metadata available at runtime. This is used to call * the procedure. * * VoltProcedure is extended by all running stored procedures. * Consider this when specifying access privileges. * */ public abstract class VoltProcedure { final static Double DOUBLE_NULL = new Double(-1.7976931348623157E+308); /** * Expect an empty result set (0 rows) */ public static final Expectation EXPECT_EMPTY = new Expectation(Type.EXPECT_EMPTY); /** * Expect a result set with exactly one row */ public static final Expectation EXPECT_ONE_ROW = new Expectation(Type.EXPECT_ONE_ROW); /** * Expect a result set with one or no rows */ public static final Expectation EXPECT_ZERO_OR_ONE_ROW = new Expectation(Type.EXPECT_ZERO_OR_ONE_ROW); /** * Expect a result set with one or more rows */ public static final Expectation EXPECT_NON_EMPTY = new Expectation(Type.EXPECT_NON_EMPTY); /** * Expect a result set with a single row and a single column (scalar value) */ public static final Expectation EXPECT_SCALAR = new Expectation(Type.EXPECT_SCALAR); /** * Expect a result with a single row and a single BIGINT column */ public static final Expectation EXPECT_SCALAR_LONG = new Expectation(Type.EXPECT_SCALAR_LONG); /** * Expect a result with a single row and a single BIGINT column containing * the specified value. This factory method constructs an Expectation for the specified * value. * @param scalar The expected value the single row/column should contain * @return An Expectation that will cause an exception to be thrown if the value or schema doesn't match */ public static final Expectation EXPECT_SCALAR_MATCH(long scalar) { return new Expectation(Type.EXPECT_SCALAR_MATCH, scalar); } ProcedureRunner m_runner; private boolean m_initialized; /** * <p>Allow VoltProcedures access to a unique ID generated for each transaction.</p> * * <p>Note that calling it repeatedly within a single transaction will return the same * number.</p> * * <p>The id consists of a time based component in the most significant bits followed * by a counter, and then a partition id to allow parallel unique number generation.</p> * * <p>Currently, if the host clock moves backwards this call may block while the clock * catches up to its previous max. Running NTP with the -x option will prevent this in * most cases. It may not be sufficient to 100% avoid this, especially in managed and/or * virtualized environments (like AWS). The value will always be unique, but clocks moving * backwards may affect system throughput.</p> * * <p>Since this number is initially based on the wall clock time (UTC), it is not * guaranteed to be unique across different clusters of VoltDB. If you snapshot, then * move time back by 1HR, then load that snapshot into a new VoltDB cluster, you might * see duplicate ids for up to 1HR. Since the wall clocks VoltDB uses are UTC, and it * takes some amount of time to snapshot and restore, this is a pretty easy corner case * to avoid. Note that failure and recovery from disk using command logs will preserve * unique id continuity even if the clock shifts.</p> * * <p>In many cases it may make sense to generate unique ids on the client-side and pass * them to the server. Please reach out to VoltDB if you have questions about when using * this method is appropriate.</p> * * @return An ID that is unique to this transaction */ public long getUniqueId() { return m_runner.getUniqueId(); } /** * Get the ID of cluster that the client connects to. * @return An ID that identifies the VoltDB cluster */ public int getClusterId() { return m_runner.getClusterId(); } /** * End users should not instantiate VoltProcedure instances. * Constructor does nothing. All actual initialization is done in the * {@link VoltProcedure init} method. */ public VoltProcedure() {} /** * End users should not call this method. * Used by the VoltDB runtime to initialize stored procedures for execution. */ void init(ProcedureRunner procRunner) { if (m_initialized) { throw new IllegalStateException("VoltProcedure has already been initialized"); } m_initialized = true; m_runner = procRunner; } /** * Thrown from a stored procedure to indicate to VoltDB * that the procedure should be aborted and rolled back. */ public static class VoltAbortException extends RuntimeException { private static final long serialVersionUID = -1L; private String message = "No message specified."; /** * Constructs a new AbortException */ public VoltAbortException() {} /** * Constructs a new AbortException from an existing <code>Throwable</code>. * * @param t Throwable to embed. */ public VoltAbortException(Throwable t) { super(t); if (t.getMessage() != null) { message = t.getMessage(); } else if (t.getCause() != null) { message = t.getCause().getMessage(); } } /** * Constructs a new AbortException with the specified detail message. * * @param msg Exception specific message. */ public VoltAbortException(String msg) { message = msg; } /** * Returns the detail message string of this <tt>AbortException</tt> * * @return The detail message. */ @Override public String getMessage() { return message; } } /** * Get a Java RNG seeded with the current transaction id. This will ensure that * two procedures for the same transaction, but running on different replicas, * can generate an identical stream of random numbers. This is required to endure * procedures have deterministic behavior. The RNG is memoized so you can invoke this * multiple times within a single procedure. * * @return A deterministically-seeded java.util.Random instance. */ public Random getSeededRandomNumberGenerator() { return m_runner.getSeededRandomNumberGenerator(); } /** * YOU MUST BE RUNNING NTP AND START NTP WITH THE -x OPTION * TO GET GOOD BEHAVIOR FROM THIS METHOD - e.g. time always goes forward * * Get the time that this procedure was accepted into the VoltDB cluster. This is the * effective, but not always actual, moment in time this procedure executes. Use this * method to get the current time instead of non-deterministic methods. Note that the * value will not be unique across transactions as it is only millisecond granularity. * * @return A java.util.Date instance with deterministic time for all replicas using * UTC (Universal Coordinated Time is like GMT). */ public Date getTransactionTime() { return m_runner.getTransactionTime(); } /* * Commented this out and nothing broke? It's cluttering up the javadoc AW 9/2/11 */ // public void checkExpectation(Expectation expectation, VoltTable table) { // Expectation.check(m_procedureName, "NO STMT", 0, expectation, table); // } /** * <p>Queue the adhoc SQL statement for execution. The adhoc SQL statement will have * to be planned which is orders of magnitude slower then using a precompiled SQL statements.</p> * * <p>If the query is parameterized it is possible to pass in the parameters.</p> * * @deprecated This method is experimental and not intended for production use yet. * @param sql An ad-hoc SQL string to be run transactionally in this procedure. * @param args Parameter values for the SQL string. */ @Deprecated public void voltQueueSQLExperimental(String sql, Object... args) { m_runner.voltQueueSQL(sql, args); } /** * <p>Queue the SQL {@link org.voltdb.SQLStmt statement} for execution with the specified argument list, * and an Expectation describing the expected results. If the Expectation is not met then VoltAbortException * will be thrown with a description of the expectation that was not met. This exception must not be * caught from within the procedure.</p> * * @param stmt {@link org.voltdb.SQLStmt Statement} to queue for execution. * @param expectation Expectation describing the expected result of executing this SQL statement. * @param args List of arguments to be bound as parameters for the {@link org.voltdb.SQLStmt statement} * @see <a href="#allowable_params">List of allowable parameter types</a> */ public void voltQueueSQL(final SQLStmt stmt, Expectation expectation, Object... args) { m_runner.voltQueueSQL(stmt, expectation, args); } /** * Queue the SQL {@link org.voltdb.SQLStmt statement} for execution with the specified argument list. * * @param stmt {@link org.voltdb.SQLStmt Statement} to queue for execution. * @param args List of arguments to be bound as parameters for the {@link org.voltdb.SQLStmt statement} * @see <a href="#allowable_params">List of allowable parameter types</a> */ public void voltQueueSQL(final SQLStmt stmt, Object... args) { m_runner.voltQueueSQL(stmt, (Expectation) null, args); } /** * Execute the currently queued SQL {@link org.voltdb.SQLStmt statements} and return * the result tables. * * @return Result {@link org.voltdb.VoltTable tables} generated by executing the queued * query {@link org.voltdb.SQLStmt statements} */ public VoltTable[] voltExecuteSQL() { return voltExecuteSQL(false); } /** * Execute the currently queued SQL {@link org.voltdb.SQLStmt statements} and return * the result tables. Boolean option allows caller to indicate if this is the final * batch for a procedure. If it's final, then additional optimizations can be enabled. * Any call to voltExecuteSQL() after calling this with the argument set to true * will cause the entire procedure to roll back. * * @param isFinalSQL Is this the final batch for a procedure? * @return Result {@link org.voltdb.VoltTable tables} generated by executing the queued * query {@link org.voltdb.SQLStmt statements} */ public VoltTable[] voltExecuteSQL(boolean isFinalSQL) { return m_runner.voltExecuteSQL(isFinalSQL); } /** * Set the status code that will be returned to the client. This is not the same as the status * code returned by the server. If a procedure sets the status code and then rolls back or causes an error * the status code will still be propagated back to the client so it is always necessary to check * the server status code first. * * @param statusCode Byte-long application-specific status code. */ public void setAppStatusCode(byte statusCode) { m_runner.setAppStatusCode(statusCode); } /** * Set the string that will be turned to the client. This is not the same as the status string * returned by the server. If a procedure sets the status string and then rolls back or causes an error * the status string will still be propagated back to the client so it is always necessary to check * the server status code first. * * @param statusString Application specific status string. */ public void setAppStatusString(String statusString) { m_runner.setAppStatusString(statusString); } }