/***************************************************************************
* Copyright (C) 2012 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* http://hstore.cs.brown.edu/ *
* *
* 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 edu.brown.benchmark.auctionmark.procedures;
import org.apache.log4j.Logger;
import org.voltdb.ProcInfo;
import org.voltdb.SQLStmt;
import org.voltdb.VoltProcedure;
import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.types.TimestampType;
import edu.brown.benchmark.auctionmark.AuctionMarkConstants.ItemStatus;
import edu.brown.benchmark.auctionmark.AuctionMarkProfile;
import edu.brown.benchmark.auctionmark.AuctionMarkConstants;
import edu.brown.benchmark.auctionmark.util.ItemId;
import edu.brown.benchmark.auctionmark.util.UserId;
/**
* NewBid
* @author pavlo
* @author visawee
*/
@ProcInfo (
partitionInfo = "USER.U_ID: 2",
singlePartition = true
)
public class NewBid extends VoltProcedure {
private static final Logger LOG = Logger.getLogger(NewBid.class);
// -----------------------------------------------------------------
// STATIC MEMBERS
// -----------------------------------------------------------------
private static final VoltTable.ColumnInfo[] RESULT_COLS = {
new VoltTable.ColumnInfo("i_id", VoltType.BIGINT),
new VoltTable.ColumnInfo("i_u_id", VoltType.BIGINT),
new VoltTable.ColumnInfo("i_num_bids", VoltType.BIGINT),
new VoltTable.ColumnInfo("i_current_price", VoltType.FLOAT),
new VoltTable.ColumnInfo("i_end_date", VoltType.TIMESTAMP),
new VoltTable.ColumnInfo("ib_id", VoltType.BIGINT),
new VoltTable.ColumnInfo("ib_buyer_id", VoltType.BIGINT),
};
// -----------------------------------------------------------------
// STATEMENTS
// -----------------------------------------------------------------
public final SQLStmt getItem = new SQLStmt(
"SELECT i_initial_price, i_current_price, i_num_bids, i_end_date, i_status " +
"FROM " + AuctionMarkConstants.TABLENAME_ITEM + " " +
"WHERE i_id = ? AND i_u_id = ? " //+
// " AND i_end_date > ? " +
// " AND i_status = " + ItemStatus.OPEN
);
public final SQLStmt getMaxBidId = new SQLStmt(
"SELECT MAX(ib_id) " +
" FROM " + AuctionMarkConstants.TABLENAME_ITEM_BID +
" WHERE ib_i_id = ? AND ib_u_id = ? "
);
public final SQLStmt getItemMaxBid = new SQLStmt(
"SELECT imb_ib_id, ib_bid, ib_max_bid, ib_buyer_id " +
" FROM " + AuctionMarkConstants.TABLENAME_ITEM_MAX_BID + ", " +
AuctionMarkConstants.TABLENAME_ITEM_BID +
" WHERE imb_i_id = ? AND imb_u_id = ? " +
" AND imb_ib_id = ib_id AND imb_ib_i_id = ib_i_id AND imb_ib_u_id = ib_u_id "
);
public final SQLStmt updateItem = new SQLStmt(
"UPDATE " + AuctionMarkConstants.TABLENAME_ITEM +
" SET i_num_bids = i_num_bids + 1, " +
" i_current_price = ?, " +
" i_updated = ? " +
" WHERE i_id = ? AND i_u_id = ? "
);
public final SQLStmt updateItemMaxBid = new SQLStmt(
"UPDATE " + AuctionMarkConstants.TABLENAME_ITEM_MAX_BID +
" SET imb_ib_id = ?, " +
" imb_ib_i_id = ?, " +
" imb_ib_u_id = ?, " +
" imb_updated = ? " +
" WHERE imb_i_id = ? " +
" AND imb_u_id = ?"
);
public final SQLStmt updateBid = new SQLStmt(
"UPDATE " + AuctionMarkConstants.TABLENAME_ITEM_BID +
" SET ib_bid = ?, " +
" ib_max_bid = ?, " +
" ib_updated = ? " +
" WHERE ib_id = ? " +
" AND ib_i_id = ? " +
" AND ib_u_id = ? "
);
public final SQLStmt insertItemBid = new SQLStmt(
"INSERT INTO " + AuctionMarkConstants.TABLENAME_ITEM_BID + "(" +
"ib_id, " +
"ib_i_id, " +
"ib_u_id, " +
"ib_buyer_id, " +
"ib_bid, " +
"ib_max_bid, " +
"ib_created, " +
"ib_updated " +
") VALUES (" +
"?, " + // ib_id
"?, " + // ib_i_id
"?, " + // ib_u_id
"?, " + // ib_buyer_id
"?, " + // ib_bid
"?, " + // ib_max_bid
"?, " + // ib_created
"? " + // ib_updated
")"
);
public final SQLStmt insertItemMaxBid = new SQLStmt(
"INSERT INTO " + AuctionMarkConstants.TABLENAME_ITEM_MAX_BID + "(" +
"imb_i_id, " +
"imb_u_id, " +
"imb_ib_id, " +
"imb_ib_i_id, " +
"imb_ib_u_id, " +
"imb_created, " +
"imb_updated " +
") VALUES (" +
"?, " + // imb_i_id
"?, " + // imb_u_id
"?, " + // imb_ib_id
"?, " + // imb_ib_i_id
"?, " + // imb_ib_u_id
"?, " + // imb_created
"? " + // imb_updated
")"
);
public VoltTable run(TimestampType benchmarkTimes[], long item_id, long seller_id, long buyer_id, double newBid, TimestampType estimatedEndDate) {
final TimestampType currentTime = AuctionMarkProfile.getScaledTimestamp(benchmarkTimes[0], benchmarkTimes[1], new TimestampType());
final boolean debug = LOG.isDebugEnabled();
if (debug) LOG.debug(String.format("Attempting to place new bid on Item %d [buyer=%d, bid=%.2f]", item_id, buyer_id, newBid));
// Check to make sure that we can even add a new bid to this item
// If we fail to get back an item, then we know that the auction is closed
voltQueueSQL(getItem, item_id, seller_id); // , currentTime);
VoltTable[] results = voltExecuteSQL();
assert (results.length == 1);
if (results[0].getRowCount() == 0) {
if (debug)
LOG.debug("The auction for item " + item_id + " has ended - " + currentTime);
throw new VoltAbortException("Unable to bid on item: Auction has ended");
}
boolean advRow = results[0].advanceRow();
assert (advRow);
double i_initial_price = results[0].getDouble(0);
double i_current_price = results[0].getDouble(1);
long i_num_bids = results[0].getLong(2);
TimestampType i_end_date = results[0].getTimestampAsTimestamp(3);
ItemStatus i_status = ItemStatus.get(results[0].getLong(4));
long newBidId = 0;
long newBidMaxBuyerId = buyer_id;
if (i_end_date.compareTo(currentTime) < 0 || i_status != ItemStatus.OPEN) {
if (debug)
LOG.debug(String.format("The auction for item %d has ended [status=%s]\nCurrentTime:\t%s\nActualEndDate:\t%s\nEstimatedEndDate:\t%s",
item_id, i_status, currentTime, i_end_date, estimatedEndDate));
throw new VoltAbortException("Unable to bid on item: Auction has ended");
}
// If we existing bids, then we need to figure out whether we are the new highest
// bidder or if the existing one just has their max_bid bumped up
if (i_num_bids > 0) {
if (debug) LOG.debug("Retrieving ITEM_MAX_BID information for " + ItemId.toString(item_id));
voltQueueSQL(getMaxBidId, item_id, seller_id);
voltQueueSQL(getItemMaxBid, item_id, seller_id);
results = voltExecuteSQL();
// Get the next ITEM_BID id for this item
boolean advanceRow = results[0].advanceRow();
assert (advanceRow);
newBidId = results[0].getLong(0) + 1;
assert(results[0].wasNull() == false);
// Get the current max bid record for this item
advanceRow = results[1].advanceRow();
assert (advanceRow);
long currentBidId = results[1].getLong(0);
double currentBidAmount = results[1].getDouble(1);
double currentBidMax = 0.0d;
try {
currentBidMax = results[1].getDouble(2);
} catch (IllegalArgumentException ex) {
SQLStmt last[] = voltLastQueriesExecuted();
for (int i = 0; i < last.length; i++) {
LOG.error(last[i].getText() + "\n" + results[i]);
} // FOR
throw ex;
}
long currentBuyerId = results[1].getLong(3);
assert((int)currentBidAmount == (int)i_current_price) : String.format("%.2f == %.2f", currentBidAmount, i_current_price);
// Check whether this bidder is already the max bidder
// This means we just need to increase their current max bid amount without
// changing the current auction price
if (buyer_id == currentBuyerId) {
if (newBid < currentBidMax) {
String msg = String.format("%s is already the highest bidder for Item %d but is trying to " +
"set a new max bid %.2f that is less than current max bid %.2f",
buyer_id, item_id, newBid, currentBidMax);
if (debug) LOG.debug(msg);
throw new VoltAbortException(msg);
}
voltQueueSQL(updateBid, i_current_price, newBid, currentTime, currentBidId, item_id, seller_id);
if (debug) LOG.debug(String.format("Increasing the max bid the highest bidder %s from %.2f to %.2f for Item %d",
buyer_id, currentBidMax, newBid, item_id));
}
// Otherwise check whether this new bidder's max bid is greater than the current max
else {
// The new maxBid trumps the existing guy, so our the buyer_id for this txn becomes the new
// winning bidder at this time. The new current price is one step above the previous
// max bid amount
if (newBid > currentBidMax) {
i_current_price = Math.min(newBid, currentBidMax + (i_initial_price * AuctionMarkConstants.ITEM_BID_PERCENT_STEP));
assert(i_current_price > currentBidMax);
voltQueueSQL(updateItemMaxBid, newBidId, item_id, seller_id, currentTime, item_id, seller_id);
if (debug) LOG.debug(String.format("Changing new highest bidder of Item %d to %s [newMaxBid=%.2f > currentMaxBid=%.2f]",
item_id, UserId.toString(buyer_id), newBid, currentBidMax));
}
// The current max bidder is still the current one
// We just need to bump up their bid amount to be at least the bidder's amount
// Make sure that we don't go over the the currentMaxBidMax, otherwise this would mean
// that we caused the user to bid more than they wanted.
else {
newBidMaxBuyerId = currentBuyerId;
i_current_price = Math.min(currentBidMax, newBid + (i_initial_price * AuctionMarkConstants.ITEM_BID_PERCENT_STEP));
assert(i_current_price >= newBid) : String.format("%.2f > %.2f", i_current_price, newBid);
voltQueueSQL(updateBid, i_current_price, i_current_price, currentTime, currentBidId, item_id, seller_id);
if (debug) LOG.debug(String.format("Keeping the existing highest bidder of Item %d as %s but updating current price from %.2f to %.2f",
item_id, buyer_id, currentBidAmount, i_current_price));
}
// Always insert an new ITEM_BID record even if BuyerId doesn't become
// the new highest bidder. We also want to insert a new record even if
// the BuyerId already has ITEM_BID record, because we want to maintain
// the history of all the bid attempts
voltQueueSQL(insertItemBid, newBidId, item_id, seller_id, buyer_id,
i_current_price, newBid, currentTime, currentTime);
voltQueueSQL(updateItem, i_current_price, currentTime, item_id, seller_id);
}
}
// There is no existing max bid record, therefore we can just insert ourselves
else {
voltQueueSQL(insertItemBid, newBidId, item_id, seller_id, buyer_id, i_initial_price, newBid, currentTime, currentTime);
voltQueueSQL(insertItemMaxBid, item_id, seller_id, newBidId, item_id, seller_id, currentTime, currentTime);
voltQueueSQL(updateItem, i_current_price, currentTime, item_id, seller_id);
if (debug) LOG.debug(String.format("Creating the first bid record for Item %d and setting %s as highest bidder at %.2f",
item_id, buyer_id, i_current_price));
}
// Fire off all of our queue
// We don't have to worry about checking whether the auction ended before we could update it
// because we use the timestamp of when the txn started, not when we updated it
results = voltExecuteSQL(true);
assert (results.length > 0);
// Return back information about the current state of the item auction
VoltTable ret = new VoltTable(RESULT_COLS);
ret.addRow(new Object[] {
// ITEM_ID
item_id,
// SELLER_ID
seller_id,
// NUM BIDS
i_num_bids + 1,
// CURRENT PRICE
i_current_price,
// END DATE
i_end_date,
// MAX BID ID
newBidId,
// MAX BID BUYER_ID
newBidMaxBuyerId,
});
return ret;
}
}