/* 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 callcenter; import org.voltdb.SQLStmt; import org.voltdb.VoltTable; import org.voltdb.VoltTableRow; import org.voltdb.types.TimestampType; /** * <p>Step 1. Insert given tuple. * Step 2. If the table (at this partition) is larger than than maxTotalRows, * delete tuples from oldest to newest until it's the right size, or until * maxRowsToDeletePerProc tuples have been deleted.</p> * * <p>This procedure basically combines TIMEDATA.insert with DeleteOldestToTarget.</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 EndCall extends BeginOrEndCallBase { final SQLStmt findCompletedCall = new SQLStmt( "SELECT * FROM completedcalls WHERE call_id = ? AND agent_id = ? AND phone_no = ?;"); final SQLStmt findOpenCall = new SQLStmt( "SELECT * FROM opencalls WHERE call_id = ? AND agent_id = ? AND phone_no = ?;"); final SQLStmt upsertOpenCall = new SQLStmt( "UPSERT INTO opencalls " + " (call_id, agent_id, phone_no, end_ts) " + " VALUES ( ?, ?, ?, ?);"); final SQLStmt insertCompletedCall = new SQLStmt( "INSERT INTO completedcalls " + " (call_id, agent_id, phone_no, start_ts, end_ts, duration) " + " VALUES ( ?, ?, ?, ?, ?, ?);"); final SQLStmt deleteOpenCall = new SQLStmt( "DELETE FROM opencalls WHERE call_id = ? AND agent_id = ? AND phone_no = ?;"); /** * 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 maxTotalRows The desired number of rows per partition. * @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(int agent_id, String phone_no, long call_id, TimestampType end_ts) { voltQueueSQL(findOpenCall, EXPECT_ZERO_OR_ONE_ROW, call_id, agent_id, phone_no); voltQueueSQL(findCompletedCall, EXPECT_ZERO_OR_ONE_ROW, call_id, agent_id, phone_no); VoltTable[] results = voltExecuteSQL(); boolean completedCall = results[1].getRowCount() > 0; if (completedCall) { return -1; } VoltTable openRowTable = results[0]; if (openRowTable.getRowCount() > 0) { VoltTableRow existingCall = openRowTable.fetchRow(0); // check if this is the second begin we've seen for this open call existingCall.getTimestampAsTimestamp("end_ts"); if (existingCall.wasNull() == false) { return -1; } // check if this completes the call TimestampType start_ts = existingCall.getTimestampAsTimestamp("start_ts"); if (existingCall.wasNull() == false) { int durationms = (int) ((end_ts.getTime() - start_ts.getTime()) / 1000); // update per-day running stddev calculation computeRunningStdDev(agent_id, end_ts, durationms); // completes the call voltQueueSQL(deleteOpenCall, EXPECT_SCALAR_MATCH(1), call_id, agent_id, phone_no); voltQueueSQL(insertCompletedCall, EXPECT_SCALAR_MATCH(1), call_id, agent_id, phone_no, start_ts, end_ts, durationms); voltExecuteSQL(true); return 0; } } voltQueueSQL(upsertOpenCall, EXPECT_SCALAR_MATCH(1), call_id, agent_id, phone_no, end_ts); voltExecuteSQL(true); return 0; } }