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']; }
/** * 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; }
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; }
protected function claimStart() { if (!Transaction::claimStart()) { return FALSE; } Transaction::add($this->db); return TRUE; }
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; }
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; }
/** * 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; }
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; }
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;
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); } }
<?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; }));
/** * 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; }
public function handle(\Exception $e) { if (Transaction::inProgress()) { Transaction::rollback(); } return $e; }
/** * 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; }
/** * 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); }
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');
<?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();
/** * @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; } }
/** * @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!');
/** * 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; } }
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); }
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); } }
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; }
} 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;