Esempio n. 1
0
 public function add($item_id, $quantity)
 {
     $batches = array();
     $local_txn = $this->claimStart();
     $table = $this->table();
     $sql_add = "INSERT OR IGNORE INTO `{$table}` " . "(`user_id`, `item_id`, `serial`, `properties`, `soft_delete`) " . "VALUES (%i, %i, %i, %s, 0)";
     $sql_update = "UPDATE `{$table}` SET `properties` = %s, `soft_delete` = 0 " . "WHERE `user_id` = %i AND `item_id` = %i AND `serial` = %i";
     foreach ($quantity->all() as $serial => $properties) {
         $properties = json_encode($properties);
         $rs = $this->execute($sql_add, $this->user_id, $item_id, $serial, $properties);
         if ($rs->affected() < 1) {
             $rs = $this->execute($sql_update, $properties, $this->user_id, $item_id, $serial);
             if ($rs->affected() < 1) {
                 if ($local_txn) {
                     Transaction::rollback();
                 }
                 throw new Exception('database error', $this->dbInfo());
             }
         }
     }
     if ($local_txn) {
         if (!Transaction::commit()) {
             throw new Exception('database error', $this->dbInfo());
         }
     }
     return TRUE;
 }
Esempio n. 2
0
 public function subtract($item_id, $quantity)
 {
     $local_txn = $this->claimStart();
     $table = $this->table();
     $sql = "UPDATE `{$table}` SET `quantity` = `quantity` - %i " . "WHERE user_id = %i AND `item_id` = %i AND `quantity` >= %i";
     $rs = $this->execute($sql, $quantity, $this->user_id, $item_id, $quantity);
     if ($rs->affected() < 1) {
         if ($local_txn) {
             Transaction::rollback();
         }
         throw new Exception('not enough', $this->dbInfo());
     }
     $sql = "SELECT `quantity` FROM `{$table}`  WHERE `user_id` = %i AND `item_id` = %i";
     $rs = $this->execute($sql, $this->user_id, $item_id);
     $row = $rs->fetch();
     $rs->free();
     if (!$row) {
         if ($local_txn) {
             Transaction::rollback();
         }
         throw new Exception('database error', $this->dbInfo());
     }
     if ($local_txn) {
         if (!Transaction::commit()) {
             throw new Exception('database error', $this->dbInfo());
         }
     }
     return $row['quantity'];
 }
Esempio n. 3
0
 public function spawn($parent = NULL)
 {
     $parent_path = $parent !== NULL ? $this->pathById($parent) : '';
     $db = $this->db();
     $table = $this->table();
     Transaction::start();
     $rs = $db->execute("INSERT INTO `{$table}` VALUES ()");
     $id = $rs->insertId();
     $db->execute("UPDATE `{$table}` SET `path` = %s WHERE `id` = %i", $parent_path . Util::SEP . $id, $id);
     Transaction::commit();
     return $id;
 }
 /**
  * @see Stockpile_Interface::set();
  */
 public function set($item_id, $quantity, array $data = NULL)
 {
     Transaction::start();
     try {
         $current = $this->get($item_id);
         if (Base::quantify($current) > 0) {
             $this->subtract($item_id, $current, $data);
         }
         if (Base::quantify($quantity) == 0) {
             $result = $quantity;
         } else {
             $result = $this->add($item_id, $quantity, $data);
         }
         if (!Transaction::commit()) {
             throw new \Gaia\Exception('database error');
         }
         return $result;
     } catch (\Exception $e) {
         $this->handle($e);
         throw $e;
     }
 }
Esempio n. 5
0
 public function sort($pos, array $item_ids, $ignore_dupes = FALSE)
 {
     $batch = array();
     $ct = 0;
     $local_txn = $this->claimStart();
     $table = $this->table();
     $sql_insert = "INSERT OR IGNORE INTO `{$table}` (`user_id`, `item_id`, `pos`) VALUES (%i, %i, %i)";
     $sql_update = "UPDATE `{$table}` SET `pos` = %i WHERE `user_id` = %i AND `item_id` = %i";
     foreach ($item_ids as $item_id) {
         $pos = bcadd($pos, 1);
         $rs = $this->execute($sql_insert, $this->user_id, $item_id, $pos);
         $ct += $curr = $rs->affected();
         if (!$ignore_dupes && !$curr) {
             $rs = $this->execute($sql_update, $pos, $this->user_id, $item_id);
             $ct += $rs->affected();
         }
     }
     if ($local_txn) {
         if (!Transaction::commit()) {
             throw new Exception('database error', $this->dbInfo());
         }
     }
     return $ct;
 }
Esempio n. 6
0
 /**
  * add a new entry to the skein. returns the id.
  */
 public function add($data, $shard = NULL)
 {
     $shard = strval($shard);
     if (!ctype_digit($shard)) {
         $shard = Util::currentShard();
     }
     $table = $this->table('index');
     $dbi = $this->db($table);
     DB\Transaction::start();
     $dbi->start();
     $sql = "INSERT INTO {$table} (thread,shard,sequence) VALUES (%i, %i, @SKEIN_SEQUENCE:=1) ON DUPLICATE KEY UPDATE `sequence` = @SKEIN_SEQUENCE:=( `sequence` + 1 )";
     $dbi->execute($sql, $this->thread, $shard);
     $rs = $dbi->execute('SELECT @SKEIN_SEQUENCE as sequence');
     $sequence = NULL;
     if ($row = $rs->fetch()) {
         $sequence = $row['sequence'];
     }
     $rs->free();
     $table = $this->table($shard);
     $dbs = $this->db($table);
     $dbs->start();
     $sql = "INSERT INTO {$table} (thread, sequence, data) VALUES (%i, %i, %s) ON DUPLICATE KEY UPDATE `data` = VALUES(`data`)";
     $dbs->execute($sql, $this->thread, $sequence, $this->serialize($data));
     $dbi->commit();
     $dbs->commit();
     DB\Transaction::commit();
     $id = Util::composeId($shard, $sequence);
     return $id;
 }
namespace Gaia\Stockpile;

use Gaia\DB\Transaction;
use Gaia\Test\Tap;
$user_id = uniqueUserID();
$item_id = uniqueNumber(1, 1000000);
// add several items to the account.
$items = array($item_id, uniqueNumber(1, 1000000), uniqueNumber(1, 1000000));
sort($items, SORT_NUMERIC);
Transaction::claimStart();
$stockpile = stockpile($app, $user_id);
foreach ($items as $id) {
    $stockpile->add($id);
}
Tap::ok(Transaction::commit(), 'add many items in a transaction');
// grab all the data at once
$all = $stockpile->all();
Tap::is(array_keys($all), $items, 'got back all the items we put in');
// test multi-get
$some = $stockpile->get($some_keys = array($items[0], $items[1]));
Tap::is(array_keys($some), $some_keys, 'multi-get only grabs the items we specified');
// test multi-get with an item that isn't in the list.
$stockpile = stockpile($app, $user_id);
do {
    $not_in_list = uniqueNumber(1, 100000);
} while (in_array($not_in_list, $items));
$res = $stockpile->get($not_in_list);
Tap::is(quantify($res), 0, 'get for an item that doesnt exist in user inventory returns zero');
$res = $stockpile->get(array($not_in_list));
Tap::is($res, array(), 'multi-get for an item that doesnt exist in user inventory returns empty array');
Esempio n. 8
0
 public function trans_complete($auth = NULL)
 {
     if ($auth != Transaction::SIGNATURE) {
         return Transaction::commit();
     }
     if (!$this->txn) {
         return $this->core->trans_complete();
     }
     if ($this->lock) {
         return FALSE;
     }
     $res = $this->core->trans_complete();
     if (!$res) {
         return $res;
     }
     $this->txn = FALSE;
     return $res;
 }
Esempio n. 9
0
 public function delete(array $identifiers)
 {
     $db = $this->db();
     $table = $this->table();
     $local_txn = DB\Transaction::claimStart();
     $db->execute("DELETE FROM `{$table}` WHERE `identifier` IN ( %s )", $identifiers);
     if ($local_txn && !DB\Transaction::commit()) {
         throw new Exception('database error: unable to commit transaction', $db);
     }
 }
Esempio n. 10
0
 /**
  * bid on an item
  * only works with those listings that set an opening bid (even if that amount is zero).
  * We use the proxy-bid system here, as used by ebay:
  * @see http://en.wikipedia.org/wiki/Proxy_bid
  * the winning bidder pays the price of the second-highest bid plus the step
  */
 public function bid($id, $bid, array $data = NULL)
 {
     // normalize the data.
     $data = new Store\KVP($data);
     // create an internal transaction if no transaction has been passed in.
     Transaction::start();
     try {
         // we assume the current user is always the bidder
         $bidder = $this->user();
         // if no bidder was passed into the constructor, blow up.
         if (!Souk\Util::validatePositiveInteger($bidder)) {
             throw new Exception('invalid bidder', $bidder);
         }
         // get a row lock on the listing.
         $listing = $this->get($id, TRUE);
         if (!$listing || !$listing->id) {
             throw new Exception('not found', $id);
         }
         // need the current time to do some comparisons.
         $ts = Souk\util::now();
         // don't go anywhere if the bidding is already closed.
         if ($listing->closed) {
             throw new Exception('closed', $listing);
         }
         // can't let the seller bid on the listing.
         if ($listing->seller == $bidder) {
             throw new Exception('invalid bidder', $listing);
         }
         // step is set when it is a biddable item. if it isn't there, don't allow bidding.
         if ($listing->step < 1) {
             throw new Exception('buy only', $listing);
         }
         // has time expired on this listing?
         if ($listing->expires <= $ts) {
             throw new Exception('expired', $listing);
         }
         // make sure we bid enough to challenge the current bid level.
         // if proxy bidding is enabled we still might not win the bid,
         // but at least we pushed it up a bit.
         if ($listing->bid + $listing->step > $bid) {
             throw new Exception('too low', $listing);
         }
         // keep a pristine copy of the listing internally so other wrapper classes can compare
         // afterward and see what changes were made.
         // The Souk\stockpile adapter especially needs this so it can return escrowed bids
         // to the previous bidder.
         $listing->setPriorState($listing);
         // if proxy bidding is enabled, this gets a little more complicated.
         // proxy bidding is where you bid the max you are willing to pay, but only pay
         // one step above the previous bidder's level.
         // This is how ebay runs its auction site.
         // this means when you bid, we track your max amount you are willing to spend, but only
         // bid the minimum. When the next bid comes in, we automatically up your bid for you
         // until you go over your max amount and someone else takes the lead.
         // this approach makes the escrow system more efficient as well since it can excrow your
         // maximum amount all at once, and then refund when you get outbid or refund the difference
         // if you get it for a lower bid.
         if ($data->enable_proxy) {
             // looks like the previous bidder got outbid.
             // track their maximum amount, and set the bid based on one step above the previous bid.
             if ($bid >= $listing->proxybid + $listing->step) {
                 $listing->bid = $listing->proxybid + $listing->step;
                 $listing->proxybid = $bid;
                 $listing->bidder = $bidder;
                 $listing->bidcount = $listing->bidcount + 1;
                 //  the other bidder is still the winner of the bid. our bid didn't go over their
                 // max bid amount. Bump up their bid amount to what we bid, and increment the
                 // bid count by 2, since we bid on it, and they bid back.
             } else {
                 $listing->bid = $bid;
                 $listing->bidcount = $listing->bidcount + 2;
             }
             // in this case, not a proxy bid system, just a straight up english auction.
             // don't worry about previous bidder. we know we bid more than the previous bidder,
             // so pump up the bid to whatever we passed in.
         } else {
             $listing->bid = $bid;
             $listing->bidder = $bidder;
             $listing->bidcount = $listing->bidcount + 1;
         }
         $listing->touch = $ts;
         $this->storage()->bid($listing);
         Transaction::commit();
         // done.
         return $listing;
         // something went wrong ...
     } catch (Exception $e) {
         // revert the transaction ...
         // if it was created internally, remove it.
         Transaction::rollback();
         // toss the exception again.
         throw $e;
     }
 }
Esempio n. 11
0
 /**
  * commit a transaction.
  * connected to the Transaction singleton to support multi-database transactions.
  */
 public function commit()
 {
     $args = func_get_args();
     $auth = isset($args[0]) ? $args[0] : NULL;
     if ($this->core instanceof Iface) {
         return $this->core->commit($auth);
     }
     if ($auth != Transaction::SIGNATURE) {
         return Transaction::commit();
     }
     if (!$this->txn) {
         return FALSE;
     }
     if ($this->lock) {
         return FALSE;
     }
     $f = $this->_[__FUNCTION__];
     $res = (bool) $f($auth);
     if (!$res) {
         return $res;
     }
     $this->txn = FALSE;
     return $res;
 }
Esempio n. 12
0
 /**
  * @see Souk::close()
  * close the bid and transfer currency from escrow into seller, and items to buyer.
  */
 function close($id, array $data = NULL)
 {
     // wrap in try catch so we can manage transactions.
     try {
         // kick off a transaction if not attached already.
         Transaction::start();
         // do the core logic of closing the listing.
         $listing = $this->prepListing($this->core->close($id));
         // did someone successfully buy this listing?
         if ($listing->buyer) {
             // settle up!
             // start by transferring funds from the buyer's escrow to the seller's currency account.
             $buyer = $this->transfer($this->currencyEscrow($listing->buyer), $this->currencyAccount($listing->seller));
             // subtract moves money from escrow into seller's currency.
             $buyer->subtract($this->currencyId(), $listing->bid, $this->prepData($data, $listing, 'pay_seller'));
             // set up a transfer between the buyer's item account and the seller's escrow
             $buyer = $this->transfer($this->itemAccount($listing->buyer), $this->itemEscrow($listing->seller));
             // now, move the item from escrow into the buyer's item account.
             $buyer->add($listing->item_id, $listing->quantity, $this->prepData($data, $listing, 'winbid'));
             // the buyer only pays the bid amount, not the max they were willing to pay,
             // since this is a proxy bid system.
             // that means if we escrowed extra money, we return it now.
             if ($listing->proxybid > $listing->bid) {
                 // figure out how much extra was escrowed.
                 $diff = $listing->proxybid - $listing->bid;
                 // set up a transfer between currency escrow and the buyer's currency account.
                 $buyer = $this->transfer($this->currencyAccount($listing->buyer), $this->currencyEscrow($listing->buyer));
                 // return the funds.
                 $buyer->add($this->currencyId(), $diff);
             }
             // no one won the bid? WTF? Return the item to the owner.
         } else {
             // set up a transfer between the seller and their escrow account.
             $seller = $this->transfer($this->itemAccount($listing->seller), $this->itemEscrow($listing->seller));
             // return the item from the listing.
             $seller->add($listing->item_id, $listing->quantity, $this->prepData($data, $listing, 'no_sale'));
             // if anyone bid on the listing, return their escrowed bid ... this happens if the reserve isn't met.
             $this->cancelBid($listing, $data);
         }
         // commit the transaction if we started one internally.
         Transaction::commit();
         // all done.
         return $listing;
     } catch (Exception $e) {
         // what happened? roll back the transaction
         Transaction::rollback();
         // exception, get your freak on! Fly! be free!
         throw $e;
     }
 }
}
if (!isset($buyer_id)) {
    $buyer_id = uniqueUserId();
}
if (!isset($item_id)) {
    $item_id = uniqueNumber(1, 100000);
}
Transaction::start();
$souk = souk($app, $seller_id);
$listings = array();
for ($i = 1; $i <= 16; $i++) {
    $listing = $souk->auction(array($i % 2 == 0 ? 'bid' : 'price' => ($i * 10 + 1) % 9, 'item_id' => $item_id));
    $listings[$listing->id] = $listing;
    Time::offset(3600 * 12 + 91);
}
Transaction::commit();
$ids = souk($app)->search(array('sort' => 'expires_soon', 'item_id' => $item_id, 'seller' => $seller_id));
Tap::cmp_ok(count($ids), '>', 12, 'search expires_soon found results');
$found = TRUE;
foreach ($ids as $id) {
    if (isset($listings[$id])) {
        continue;
    }
    $found = FALSE;
    break;
}
Tap::ok($found, 'returned only rows we created');
$owned = TRUE;
foreach ($ids as $id) {
    if ($listings[$id]->seller == $seller_id) {
        continue;
Esempio n. 14
0
 /**
  * add a new entry to the skein. returns the id.
  */
 public function add($data, $shard = NULL)
 {
     $shard = strval($shard);
     if (!ctype_digit($shard)) {
         $shard = Util::currentShard();
     }
     $table = $this->table('index');
     $dbi = $this->db($table);
     DB\Transaction::start();
     $dbi->start();
     $sql = "INSERT OR IGNORE INTO {$table} (thread,shard,sequence) VALUES (%i, %i, 1)";
     $rs = $dbi->execute($sql, $this->thread, $shard);
     if (!$rs->affected()) {
         $sql = "UPDATE {$table} SET `sequence` = `sequence` + 1 WHERE `thread` = %i AND `shard` = %i";
         $dbi->execute($sql, $this->thread, $shard);
     }
     $sql = "SELECT `sequence` FROM {$table} WHERE `thread` = %i AND `shard` = %i";
     $rs = $dbi->execute($sql, $this->thread, $shard);
     $sequence = NULL;
     if ($row = $rs->fetch()) {
         $sequence = $row['sequence'];
     }
     $rs->free();
     $table = $this->table($shard);
     $dbs = $this->db($table);
     $dbs->start();
     $sql = "INSERT OR IGNORE INTO {$table} (thread, sequence, data) VALUES (%i, %i, %s)";
     $data = $this->serialize($data);
     $dbs->execute($sql, $this->thread, $sequence, $data);
     if (!$rs->affected()) {
         $sql = "UPDATE {$table} SET `data` = %s WHERE `thread` = %i AND `sequence` = %i";
         $dbs->execute($sql, $data, $this->thread, $sequence);
     }
     $dbi->commit();
     $dbs->commit();
     DB\Transaction::commit();
     $id = Util::composeId($shard, $sequence);
     return $id;
 }
Esempio n. 15
0
 /**
  * @see Stockpile_Interface::subtract();
  */
 public function subtract($item_id, $quantity = 1, array $data = NULL)
 {
     Transaction::start();
     try {
         if (!$quantity instanceof Stockpile_HybridQuantity) {
             $quantity = $this->get($item_id)->grab($quantity);
         }
         if ($quantity->value() < 1) {
             throw $this->handle(new Stockpile_Exception('cannot subtract: invalid quantity', $quantity));
         }
         if ($quantity->tally() > 0) {
             $tally = $this->core->subtract($item_id, $quantity->tally(), $data);
         } else {
             $tally = $this->core->get($item_id, $with_lock = TRUE);
         }
         if (count($quantity->all()) > 0) {
             $serial = $this->serial->subtract($item_id, $quantity->all(), $data);
         } else {
             $serial = $this->serial->get($item_id);
         }
         if (!Transaction::commit()) {
             throw new Exception('database error');
         }
         return $this->quantity(array('tally' => $tally, 'serial' => $serial));
     } catch (\Exception $e) {
         if (Transaction::inProgress()) {
             Transaction::rollback();
         }
         $e = new Exception('cannot subtract: ' . $e->getMessage(), $e->__toString());
         throw $e;
     }
 }
Esempio n. 16
0
 public function commit($auth = NULL)
 {
     if ($auth != Transaction::SIGNATURE) {
         return Transaction::commit();
     }
     if (!$this->txn) {
         return parent::commit();
     }
     if ($this->lock) {
         return FALSE;
     }
     return parent::commit();
 }
    new Transfer($trade, $other);
} catch (\Exception $e) {
}
Tap::ok($e instanceof \Exception && preg_match('/transfer/i', $e->getMessage()), 'no nesting of transfers');
$e = NULL;
Transaction::claimStart();
try {
    new Transfer($core, $core);
} catch (\Exception $e) {
}
Tap::ok($e instanceof \Exception && preg_match('/need\\stwo/i', $e->getMessage()), 'enforce two different parties to trade');
$e = NULL;
Transaction::reset();
try {
    new Transfer(stockpile($app, $other_id), stockpile($app, $other_id));
} catch (\Exception $e) {
}
Tap::ok($e instanceof \Exception && preg_match('/transaction/i', $e->getMessage()), 'blow up when no transaction');
$e = NULL;
try {
    new Transfer(stockpile($app, $other_id), stockpile($app, $other_id));
} catch (\Exception $e) {
}
Tap::ok($e instanceof \Exception && preg_match('/transaction/i', $e->getMessage()), 'blow up when neither has a transaction');
Transaction::reset();
Transaction::claimStart();
$trade = new Transfer(stockpile($app, $user_id), stockpile('test2', $user_id));
Tap::is(quantify($trade->subtract($item_id)), 2, 'move items for same user to different app (escrow example with transaction)');
Tap::is(quantify($trade->add($item_id)), 3, 'add it back from other app to main');
Tap::ok(Transaction::commit(), 'transaction commits successfully');
Esempio n. 18
0
 public function commit($auth = NULL)
 {
     if ($auth != Transaction::SIGNATURE) {
         return Transaction::commit();
     }
     if (!$this->txn) {
         return parent::query('COMMIT');
     }
     if ($this->lock) {
         return FALSE;
     }
     $res = parent::query('COMMIT');
     if (!$res) {
         return $res;
     }
     $this->txn = FALSE;
     return $res;
 }