예제 #1
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'];
 }
예제 #2
0
 /**
  * takes a simple list of item ids and pushes those items to the top of the list
  * starting with the first item id. doesn't need to be every item in the inventory.
  * just the ones you want sorted to the top of the list.
  * makes sense from an api standpoint but may not be perfect from an app standpoint.
  */
 public function sort(array $item_ids)
 {
     if (count($item_ids) < 1) {
         return FALSE;
     }
     rsort($item_ids);
     $user_id = $this->user();
     $pos = $this->maxPos();
     $min = $this->minCustomPos();
     if (bccomp($pos, $min) < 0) {
         $pos = $min;
     }
     if ($this->cacher) {
         $timeout = $this->cacheTimeout();
         foreach ($item_ids as $item_id) {
             $pos = bcadd($pos, 1);
             $this->cacher->set($item_id, $pos, 0, $timeout);
             if ($this->inTran()) {
                 Transaction::onRollback(array($this->cacher, 'delete'), array($item_id));
             }
         }
     }
     try {
         $this->storage('sorter')->sort($pos, $item_ids);
     } catch (Exception $e) {
         throw $this->handle($e);
     }
     return TRUE;
 }
예제 #3
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;
 }
예제 #4
0
 protected function claimStart()
 {
     if (!Transaction::claimStart()) {
         return FALSE;
     }
     Transaction::add($this->db);
     return TRUE;
 }
예제 #5
0
 protected function execute($query)
 {
     if (!Transaction::atStart()) {
         Transaction::add($this->db);
     }
     $args = func_get_args();
     array_shift($args);
     $rs = $this->db->execute($qs = $this->db->prep_args($query, $args));
     if (!$rs) {
         throw new Exception('database error', array('db' => $this->db, 'query' => $qs, 'error' => $this->db->error()));
     }
     return $rs;
 }
 public function execute($parameters = NULL)
 {
     if ($this->connection->lock) {
         return FALSE;
     }
     $res = parent::execute($parameters);
     if ($res) {
         return $res;
     }
     if ($this->connection->txn) {
         Transaction::block();
     }
     return $res;
 }
 /**
  * @see Stockpile_Interface::add();
  * on insert, set the pos column of the sort table to match current time.
  * on dupe key violation, update the pos column to match current time
  */
 public function add($item_id, $quantity = 1, array $data = NULL)
 {
     $res = $this->core->add($item_id, $quantity, $data);
     try {
         $this->storage('sorter')->sort(Base::time(), array($item_id));
     } catch (Exception $e) {
         throw $this->handle($e);
     }
     if ($this->cacher) {
         $this->cacher->set($item_id, $now, $this->cacheTimeout());
         Transaction::onRollback(array($this->cacher, 'delete'), array($item_id));
     }
     return $res;
 }
예제 #8
0
 public function store($id, $name, $strict = FALSE)
 {
     Transaction::onRollback(array($this, 'clearCache'), array($id, $name));
     $namecheck = $this->cacher('id')->get($id);
     $idcheck = $this->cacher('name')->get($name);
     $res = parent::store($id, $name, $strict);
     $this->cacher('id')->set($id, $name, $this->ttl);
     $this->cacher('name')->set($name, $id, $this->ttl);
     if ($namecheck != $name && $namecheck !== null) {
         $this->cacher('name')->delete($namecheck);
     }
     if ($id != $idcheck && $idcheck !== null) {
         $this->cacher('id')->delete($idcheck);
     }
     return $res;
 }
 /**
  * @see Stockpile_Interface::add();
  * only bump up to the top the first time we add this item id to the inventory.
  * after that, just let it slide.
  */
 public function add($item_id, $quantity = 1, array $data = NULL)
 {
     $res = $this->core->add($item_id, $quantity, $data);
     $now = Base::time();
     try {
         $ct = $this->storage('sorter')->sort($now, array($item_id), $ignore = TRUE);
     } catch (\Exception $e) {
         throw $this->handle($e);
     }
     if ($ct < 1 || !$this->cacher) {
         return $res;
     }
     $this->cacher->set($item_id, $now, 0, $this->cacheTimeout());
     if ($this->inTran()) {
         Transaction::onRollback(array($cache, 'delete'), array($item_id));
     }
     return $res;
 }
예제 #10
0
 /**
  * pass in two accounts, always.
  */
 public function __construct(Iface $core, Iface $other)
 {
     parent::__construct($core);
     if (Transaction::atStart()) {
         throw $this->handle(new Exception('need transaction to transfer'));
     }
     if ($core->app() == $other->app() && $core->user() == $other->user()) {
         throw $this->handle(new Exception('need two different parties to trade'));
     }
     if ($core->coreType() == $this->coreType()) {
         throw $this->handle(new Exception('core must not be a transfer'));
     }
     if ($other->coreType() == $this->coreType()) {
         throw $this->handle(new Exception('other must not be a transfer'));
     }
     if ($core->coreType() != $other->coreType() && $core->coreType() != 'serial-tally' && $other->coreType() != 'serial-tally') {
         throw $this->handle(new Exception('both must be of same coretype in a transfer'));
     }
     $this->other = $other;
 }
예제 #11
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;
 }
예제 #12
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();
 }
Tap::is($e, 'buy only', 'when bidding on buy-only, fails');
DB\Transaction::reset();
DB\Connection::reset();
$listing = $souk->buy($listing->id);
Tap::is($listing->closed, 1, 'buy works fine, tho');
$souk = Souk($app, $seller_id);
$listing = $souk->auction(array('bid' => 0, 'item_id' => $item_id));
$souk = Souk($app, $buyer_id);
$e = NULL;
try {
    $listing = $souk->buy($listing->id);
} catch (Exception $e) {
    $e = $e->getMessage();
}
Tap::is($e, 'bid only', 'when buying bid-only, fails');
DB\Transaction::reset();
DB\Connection::reset();
$listing = $souk->bid($listing->id, 1);
Tap::is($listing->bid, 1, 'bid works fine, tho');
$listing = Souk($app, $seller_id)->auction(array('bid' => 0, 'reserve' => 5, 'item_id' => $item_id));
$listing = Souk($app, $buyer_id)->bid($listing->id, 5);
Tap::is($listing->bid, 5, 'without enable_proxy, bid is set, not stepped');
$listing = Souk($app)->close($listing->id);
Tap::is($listing->buyer, 0, 'when reserve isnt met, bidder doesnt win listing');
Tap::is($listing->closed, 1, 'even tho reserve wasnt met, closing still ends the bidding.');
unset($seller_id);
unset($buyer_id);
unset($item_id);
Time::offset(86400 * 30);
$id = 0;
$ct = 0;
예제 #14
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);
     }
 }
예제 #15
0
<?php

use Gaia\DB;
use Gaia\Test\Tap;
include __DIR__ . '/../lib/setup.php';
include __DIR__ . '/../../assert/pdo_installed.php';
include __DIR__ . '/../../assert/pdo_sqlite_installed.php';
DB\Connection::load(array('test' => function () {
    $db = new DB\Driver\PDO('sqlite:/tmp/souk.db');
    $cb = array('start' => function () {
        $i = \Gaia\DB\Transaction::internals();
        Tap::debug('TXN: start ' . $i['depth']);
    }, 'commit' => function () {
        $i = \Gaia\DB\Transaction::internals();
        Tap::debug('TXN: commit ' . $i['depth']);
    }, 'rollback' => function () {
        $i = \Gaia\DB\Transaction::internals();
        Tap::debug('TXN: rollback ' . $i['depth']);
    }, 'query' => function ($args) {
        $query = array_shift($args);
        $query = \Gaia\DB\Query::format($query, $args);
        Tap::debug('QUERY: ' . $query);
    });
    //$db = new DB\Observe( $db, $cb);
    return $db;
}));
예제 #16
0
 /**
  * run a query and return a Gaia\DB\Result object on success.
  * returns FALSE on failure.
  * pass in extra args to do automagic SQL preparation.
  */
 public function execute($query)
 {
     if ($this->lock) {
         return FALSE;
     }
     $args = func_get_args();
     array_shift($args);
     $sql = $this->prep_args($query, $args);
     $f = $this->_[__FUNCTION__];
     $res = $f($sql);
     if ($res) {
         return $res;
     }
     if ($this->txn) {
         Transaction::block();
         $this->lock = TRUE;
     }
     return $res;
 }
예제 #17
0
 public function handle(\Exception $e)
 {
     if (Transaction::inProgress()) {
         Transaction::rollback();
     }
     return $e;
 }
예제 #18
0
 /**
  * Utility function used mainly by other functions to derive values, but can be used by
  * the application if you know what you are doing.
  * Returns a count of how many entries are in each shard.
  */
 public function shardSequences()
 {
     $table = $this->table('index');
     $db = $this->db($table);
     if (DB\Transaction::inProgress()) {
         DB\Transaction::add($db);
     }
     $sql = "SELECT `shard`, `sequence` FROM {$table} WHERE `thread` = %s ORDER BY `shard` DESC";
     $rs = $db->execute($sql, $this->thread);
     $result = array();
     while ($row = $rs->fetch()) {
         $result[$row['shard']] = $row['sequence'];
     }
     $rs->free();
     return $result;
 }
예제 #19
0
 /**
  * Write the item_id => total quantity pairing into the cache.
  * @param int        item id
  * @param int        total count, after db write
  * if the total is less than zero, write it in as undefined in the cache so it doesn't hit the 
  * db again. With tally, the total is a number, but the serial total will be a quantity object.
  * no matter. we can get it's total value easily enough by just getting strval of it.
  * if there is a transaction attached, set up a callback to delete this key if the transaction doesn't work.
  * can't delete the cache key for history since variations are endless, but we can bust the key
  * using last touch. if we aren't in a transaction, just bust last touch right away.
  * check the id index so we can update it in the cache.
  * if nothing left of a given item, remove it from the index.
  * if the item id is in the index, no more work needed.
  * make sure the index is sorted the way it is supposed to be.
  * always sorted in numeric order of item id.
  */
 protected function writeToCache($item_id, $total)
 {
     $cache = $this->cacher;
     $timeout = $this->cacheTimeout();
     $cache->set($item_id, Base::quantify($total) > 0 ? $total : Store\Callback::UNDEF, $timeout);
     if ($this->inTran()) {
         Transaction::onRollback(array($cache, 'delete'), array($item_id));
         Transaction::onRollback(array($this, 'lastTouch'), array(TRUE));
         Transaction::onCommit(array($this, 'lastTouch'), array(TRUE));
     } else {
         $this->lastTouch(TRUE);
     }
     $index = $cache->get(self::INDEX_CACHEKEY);
     if (!is_array($index)) {
         return;
     }
     if (Base::quantify($total) > 0) {
         if (in_array($item_id, $index)) {
             return;
         }
         $index[] = $item_id;
         sort($index, SORT_NUMERIC);
     } else {
         $found = array_keys($index, $item_id);
         if (count($found) < 1) {
             return;
         }
         foreach ($found as $k) {
             unset($index[$k]);
         }
     }
     $cache->set(self::INDEX_CACHEKEY, $index, $timeout);
 }
예제 #20
0
 public function query(array $params = array())
 {
     $search = NULL;
     $min = NULL;
     $max = NULL;
     $sort = 'ASC';
     $limit = NULL;
     $result = array();
     if (isset($params['search'])) {
         $search = $params['search'];
     }
     if (isset($params['min'])) {
         $min = $params['min'];
     }
     if (isset($params['max'])) {
         $max = $params['max'];
     }
     if (isset($params['sort'])) {
         $sort = $params['sort'];
     }
     if (isset($params['limit'])) {
         $limit = $params['limit'];
     }
     if ($limit !== NULL) {
         $limit = str_replace(' ', '', $limit);
     }
     $sort = strtoupper($sort);
     if ($sort != 'DESC') {
         $sort = 'ASC';
     }
     $db = $this->db();
     $table = $this->table();
     if (DB\Transaction::inProgress()) {
         DB\Transaction::add($db);
     }
     $where = array();
     if ($search !== NULL) {
         $where[] = $db->prep_args("`stratum` IN( %s )", array($search));
     }
     if ($min !== NULL) {
         $where[] = $db->prep_args("`stratum` >= %i", array($min));
     }
     if ($max !== NULL) {
         $where[] = $db->prep_args("`stratum` <= %i", array($max));
     }
     $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
     $sql = "SELECT `constraint`, `stratum` FROM `{$table}` {$where} ORDER BY `stratum` {$sort}";
     if ($limit !== NULL && preg_match("#^([0-9]+)((,[0-9]+)?)\$#", $limit)) {
         $sql .= " LIMIT " . $limit;
     }
     //print "\n$sql\n";
     $rs = $db->execute($sql);
     while ($row = $rs->fetch()) {
         $result[$row['constraint']] = $row['stratum'];
     }
     $rs->free();
     return $result;
 }
//var_dump( $conn1 );
Transaction::reset();
Tap::ok(Transaction::start(), 'started a transaction at the global level');
$conn1 = $newconn();
$conn2 = $newconn();
Tap::ok($conn1->start(), 'started a transaction on conn1');
Tap::ok($conn2->start(), 'started a transaction on conn2');
$rs = $conn1->execute("insert into {$table} values (3)");
Tap::ok($rs, 'inserted a row into test table from conn1');
//if( ! $rs ) Tap::debug( $conn1 );
$rs = $conn2->execute("insert into {$table} values(4)");
Tap::ok($rs, 'inserted a row into test table from conn2');
//if( ! $rs ) Tap::debug( $conn2 );
Tap::ok($conn1->commit(), 'committed inserted row on conn1');
Tap::ok($conn2->commit(), 'committed inserted row on conn2');
Tap::ok(Transaction::rollback(), 'rolled back the transaction at the global level');
Tap::ok($rs = $dbmain->execute("select id from {$table}"), 'selected all rows from the table');
$ct = $rs->affected();
Tap::is($ct, 2, '2 rows in the table, new rows rolled back');
$rs = $conn1->execute("select id from {$table}");
Tap::is($rs, FALSE, 'after rolling back, new queries fail on rolled back db object');
$dbmain->execute("drop table {$table}");
$db = $newconn();
$raw = file_get_contents(__DIR__ . '/../sample/i_can_eat_glass.txt');
$lines = explode("\n", $raw);
$lines = array_slice($lines, 0, 10) + array_slice($lines, 100, 10) + array_slice($lines, 200, 10) + array_slice($lines, 200, 10);
$raw = implode("\n", $lines);
$sql = "CREATE TEMPORARY TABLE t1utf8 (`i` INT UNSIGNED NOT NULL PRIMARY KEY, `line` VARCHAR(5000) ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8";
$db->execute($sql);
foreach ($lines as $i => $line) {
    //$lines[ $i ] = $line = mb_convert_encoding($line, 'UTF-8', 'auto');
예제 #22
0
<?php

// test transaction in/out of txn reads.
namespace Gaia\Stockpile;

use Gaia\DB\Transaction;
use Gaia\Test\Tap;
$user_id = uniqueUserID();
$item_id = uniqueNumber(1, 1000000);
Transaction::reset();
Transaction::claimStart();
stockpile($app, $user_id)->add($item_id, 10);
// read the value outside of the transaction ... shouldn't be able to see it yet.
$total = stockpile($app, $user_id)->get($item_id);
Tap::is(quantify($total), 10, 'get outside of txn sees the value we added - it is in the cache optimistically');
// now it should go away
Transaction::rollback();
$total = stockpile($app, $user_id)->get($item_id);
Tap::is(quantify($total), 0, 'after it is rolled back the value disappears from the cache');
Transaction::claimStart();
stockpile($app, $user_id)->add($item_id, 10);
$stockpike = stockpile($app, $user_id);
$stockpile->forceRefresh(TRUE);
$total = $stockpile->get($item_id);
Tap::is(quantify($total), 0, 'when force-refreshing the cache, item disappears that we added but didnt commit');
Transaction::rollback();
예제 #23
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;
     }
 }
예제 #24
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;
     }
 }
<?php

namespace Gaia\Stockpile;

use Gaia\DB\Transaction;
use Gaia\Test\Tap;
$user_id = uniqueUserID();
$item_id = uniqueNumber(1, 1000000);
// test transaction support.
Transaction::claimStart();
$total = stockpile($app, $user_id)->add($item_id);
Tap::is(quantify($total), 4, 'add inside a transaction');
// revert the transaction
Transaction::rollback();
$total = stockpile($app, $user_id)->get($item_id);
Tap::is(quantify($total), 3, 'after txn rollback, the value we added isnt there');
// add inside a transaction and commit it.
Transaction::claimStart();
$total = stockpile($app, $user_id)->add($item_id);
Tranaction::commit();
$total = stockpile($app, $user_id)->get($item_id);
Tap::is(quantify($total), 4, 'add inside of a transaction and commit it. now we can see it!');
예제 #26
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;
     }
 }
예제 #27
0
 public function __construct($dsn)
 {
     $args = func_get_args();
     $args = func_get_args();
     for ($i = 0; $i < 4; $i++) {
         if (!isset($args[$i])) {
             $args[$i] = NULL;
         }
     }
     if ($args[0] instanceof \PDO) {
         $db = $args[0];
     } else {
         $db = new \PDO($args[0], $args[1], $args[2], $args[3]);
     }
     $callbacks = array();
     $wrapper = $this;
     $db->setAttribute(\PDO::ATTR_STATEMENT_CLASS, array('Gaia\\DB\\Driver\\PDOStatement', array($wrapper)));
     $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
     $callbacks['__tostring'] = function () use($db) {
         list($errstate, $errcode, $errmsg) = $db->errorInfo();
         @($res = '(Gaia\\DB\\PDO object - ' . "\n" . '  [driver] => ' . $db->getAttribute(\PDO::ATTR_DRIVER_NAME) . "\n" . '  [connection] => ' . $db->getAttribute(\PDO::ATTR_CONNECTION_STATUS) . "\n" . '  [version] => ' . $db->getAttribute(\PDO::ATTR_SERVER_VERSION) . "\n" . '  [info] => ' . $db->getAttribute(\PDO::ATTR_SERVER_INFO) . "\n" . '  [error] => ' . $errmsg . "\n" . '  [insert_id] => ' . $db->lastInsertId() . "\n" . ')');
         return $res;
     };
     $callbacks['prep_query_args'] = $prep_args = function ($query, array $args) use($db) {
         if (!$args || count($args) < 1) {
             return $query;
         }
         return \Gaia\DB\Query::prepare($query, $args, function ($v) use($db) {
             return $db->quote($v);
         });
     };
     $callbacks['isa'] = function ($name) use($wrapper, $db) {
         if ($wrapper instanceof $name) {
             return TRUE;
         }
         if ($db instanceof $name) {
             return TRUE;
         }
         $name = strtolower($name);
         if ($name == 'pdo') {
             return TRUE;
         }
         return FALSE;
     };
     $callbacks['execute'] = function ($query) use($db, $prep_args) {
         $args = func_get_args();
         array_shift($args);
         $rs = $db->query($sql = $prep_args($query, $args));
         return $rs;
     };
     $callbacks['query'] = function ($query) use($db, $wrapper) {
         if ($wrapper->lock) {
             return FALSE;
         }
         $args = func_get_args();
         $res = call_user_func_array(array($db, 'query'), $args);
         if ($res) {
             return $res;
         }
         if ($wrapper->txn) {
             Transaction::block();
             $wrapper->lock = TRUE;
         }
         return $res;
     };
     $callbacks['exec'] = function ($query) use($db, $wrapper) {
         if ($wrapper->lock) {
             return FALSE;
         }
         $res = $db->exec($query);
         if ($res) {
             return $res;
         }
         if ($wrapper->txn) {
             Transaction::block();
             $wrapper->lock = TRUE;
         }
         return $res;
     };
     $callbacks['start'] = function () use($db) {
         return $db->beginTransaction();
     };
     $callbacks['beginTransaction'] = function () use($wrapper) {
         return $wrapper->start();
     };
     $callbacks['commit'] = function () use($db) {
         return $db->commit();
     };
     $callbacks['rollback'] = function () use($db) {
         return $db->rollback();
     };
     $callbacks['hash'] = function () use($db) {
         return spl_object_hash($db);
     };
     $callbacks['__get'] = function ($k) use($db) {
         return $db->{$k};
     };
     $callbacks['__call'] = function ($method, array $args) use($db) {
         return call_user_func_array(array($db, $method), $args);
     };
     parent::__construct($callbacks);
 }
예제 #28
0
 public function close($listing)
 {
     // grab the shard and row id.
     list($shard, $row_id) = Souk\Util::parseId($listing->id);
     // update the listing.
     $table = $this->table($shard);
     if (\Gaia\Souk\Storage::isAutoSchemaEnabled()) {
         $this->create($table);
     }
     if (!Transaction::atStart()) {
         Transaction::add($this->db);
     }
     $sql = "UPDATE {$table} SET buyer = %i, touch = %i, closed = 1, pricesort = NULL WHERE row_id = %i";
     $rs = $this->execute($sql, $listing->buyer, $listing->touch, $row_id);
     // should have affected a row. if it didn't toss an exception.
     if ($rs->affected() < 1) {
         throw new Exception('failed', $this->db);
     }
 }
예제 #29
0
 protected function db()
 {
     if ($this->db instanceof \Closure) {
         $mapper = $this->db;
         $db = $mapper($this->table());
     } elseif (is_scalar($this->db)) {
         $db = DB\Connection::instance($this->db);
     } else {
         $db = $this->db;
     }
     if (!$db instanceof DB\Iface) {
         throw new Exception('invalid db');
     }
     if (!$db->isa('mysql')) {
         throw new Exception('invalid db');
     }
     if (!$db->isa('gaia\\db\\extendediface')) {
         throw new Exception('invalid db');
     }
     if (!$db->isa('Gaia\\DB\\Except')) {
         $db = new DB\Except($db);
     }
     if (!Transaction::atStart()) {
         Transaction::add($db);
     }
     return $db;
 }
예제 #30
0
}
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;