/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * 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 windowing; import org.voltdb.SQLStmt; import org.voltdb.VoltProcedure; import org.voltdb.types.TimestampType; /** * <p>Step 1. Insert given tuple. * Step 2. Find tuples with timestamps older than or equivalent to newestToDiscard. * Delete up to maxRowsToDeletePerProc of them, in oldest to newest order.</p> * * <p>This procedure basically combines TIMEDATA.insert with DeleteAfterDate.</p> * * <p>One important thing to consider when developing procedures like this is * that they be deterministic. This procedure may be applied simultaniously on * many replica partitions, but since it will deterministically delete the same * tuples if the database contents are identical, then it will be fine. Note: * this is why VoltDB doesn't allow LIMIT in delete operators. All DML must * be deterministic.</p> * * <p>Note, there is a lot of redundant code/comments among the stored procedures * in this example app. That's intentional to make each stand alone and be easier * to follow. A production app might offer less choice or just reuse more code.</p> * */ public class InsertAndDeleteAfterDate extends VoltProcedure { final SQLStmt insert = new SQLStmt( "INSERT INTO timedata (uuid, val, update_ts) VALUES (?, ?, ?);"); final SQLStmt countMatchingRows = new SQLStmt( "SELECT COUNT(*) FROM timedata WHERE update_ts <= ?;"); final SQLStmt getNthOldestTimestamp = new SQLStmt( "SELECT update_ts FROM timedata ORDER BY update_ts ASC OFFSET ? LIMIT 1;"); final SQLStmt deleteOlderThanDate = new SQLStmt( "DELETE FROM timedata WHERE update_ts <= ?;"); /** * Procedure main logic. * * @param uuid Column value for tuple insertion and partitioning key for this procedure. * @param val Column value for tuple insertion. * @param update_ts Column value for tuple insertion. * @param newestToDiscard Try to remove any tuples as old or older than this value. * @param targetMaxRowsToDelete The upper limit on the number of rows to delete per transaction. * @return The number of deleted rows. * @throws VoltAbortException on bad input. */ public long run(String uuid, long val, TimestampType update_ts, TimestampType newestToDiscard, long targetMaxRowsToDelete) { if (newestToDiscard == null) { throw new VoltAbortException("newestToDiscard shouldn't be null."); // It might be Long.MIN_VALUE as a TimestampType though. } if (targetMaxRowsToDelete <= 0) { throw new VoltAbortException("maxRowsToDeletePerProc must be > 0."); } // This line inserts the row. voltQueueSQL(insert, EXPECT_SCALAR_MATCH(1), uuid, val, update_ts); // In the same round trip to the storage engine, count the rows. voltQueueSQL(countMatchingRows, EXPECT_SCALAR_LONG, newestToDiscard); // Can assume insert worked because of EXPECT_SCALAR_MATCH(1) // Note that the index into the set of results tables below is the second table. long agedOutCount = voltExecuteSQL()[1].asScalarLong(); if (agedOutCount > targetMaxRowsToDelete) { // Find the timestamp of the row at position N in the sorted order, where N is the chunk size voltQueueSQL(getNthOldestTimestamp, EXPECT_SCALAR, targetMaxRowsToDelete); newestToDiscard = voltExecuteSQL()[0].fetchRow(0).getTimestampAsTimestamp(0); } // Delete all rows >= the timestamp found in the previous statement. // This will delete AT LEAST N rows, but since timestamps may be non-unique, // it might delete more than N. In the worst case, it could delete all rows // if every row has an identical timestamp value. It is guaranteed to make // progress. If we used strictly less than, it might not make progress. // This is why the max rows to delete number is a target, not always a perfect max. voltQueueSQL(deleteOlderThanDate, EXPECT_SCALAR_LONG, newestToDiscard); long deletedCount = voltExecuteSQL(true)[0].asScalarLong(); return deletedCount; } }