// Clear this out so we don't accidentally break global
// state in *this* process.
$this->_DB_resultid = null;
+
// We don't have any local DBO refs, so clear these out.
$this->_link_loaded = false;
}
unset($i);
}
$i = Memcached_DataObject::getcached($cls, $k, $v);
- if ($i === false) { // false == cache miss
+ if ($i) {
+ return $i;
+ } else {
$i = DB_DataObject::factory($cls);
if (empty($i)) {
$i = false;
}
$result = $i->get($k, $v);
if ($result) {
- // Hit!
$i->encache();
+ return $i;
} else {
- // save the fact that no such row exists
- $c = self::memcache();
- if (!empty($c)) {
- $ck = self::cachekey($cls, $k, $v);
- $c->set($ck, null);
- }
$i = false;
+ return $i;
}
}
- return $i;
}
- /**
- * @fixme Should this return false on lookup fail to match staticGet?
- */
- function pkeyGet($cls, $kv)
+ function &pkeyGet($cls, $kv)
{
$i = Memcached_DataObject::multicache($cls, $kv);
- if ($i !== false) { // false == cache miss
+ if ($i) {
return $i;
} else {
- $i = DB_DataObject::factory($cls);
- if (empty($i)) {
- return false;
- }
+ $i = new $cls();
foreach ($kv as $k => $v) {
$i->$k = $v;
}
$i->encache();
} else {
$i = null;
- $c = self::memcache();
- if (!empty($c)) {
- $ck = self::multicacheKey($cls, $kv);
- $c->set($ck, null);
- }
}
return $i;
}
function insert()
{
$result = parent::insert();
- if ($result) {
- $this->encache(); // in case of cached negative lookups
- }
return $result;
}
if (!$c) {
return false;
} else {
- return $c->get(Memcached_DataObject::cacheKey($cls, $k, $v));
+ $obj = $c->get(Memcached_DataObject::cacheKey($cls, $k, $v));
+ if (0 == strcasecmp($cls, 'User')) {
+ // Special case for User
+ if (is_object($obj) && is_object($obj->id)) {
+ common_log(LOG_ERR, "User " . $obj->nickname . " was cached with User as ID; deleting");
+ $c->delete(Memcached_DataObject::cacheKey($cls, $k, $v));
+ return false;
+ }
+ }
+ return $obj;
}
}
function encache()
{
$c = $this->memcache();
-
if (!$c) {
return false;
- }
-
- $keys = $this->_allCacheKeys();
-
- foreach ($keys as $key) {
- $c->set($key, $this);
+ } else if ($this->tableName() == 'user' && is_object($this->id)) {
+ // Special case for User bug
+ $e = new Exception();
+ common_log(LOG_ERR, __METHOD__ . ' caching user with User object as ID ' .
+ str_replace("\n", " ", $e->getTraceAsString()));
+ return false;
+ } else {
+ $pkey = array();
+ $pval = array();
+ $types = $this->keyTypes();
+ ksort($types);
+ foreach ($types as $key => $type) {
+ if ($type == 'K') {
+ $pkey[] = $key;
+ $pval[] = $this->$key;
+ } else {
+ $c->set($this->cacheKey($this->tableName(), $key, $this->$key), $this);
+ }
+ }
+ # XXX: should work for both compound and scalar pkeys
+ $pvals = implode(',', $pval);
+ $pkeys = implode(',', $pkey);
+ $c->set($this->cacheKey($this->tableName(), $pkeys, $pvals), $this);
}
}
function decache()
{
$c = $this->memcache();
-
if (!$c) {
return false;
- }
-
- $keys = $this->_allCacheKeys();
-
- foreach ($keys as $key) {
- $c->delete($key, $this);
- }
- }
-
- function _allCacheKeys()
- {
- $ckeys = array();
-
- $types = $this->keyTypes();
- ksort($types);
-
- $pkey = array();
- $pval = array();
-
- foreach ($types as $key => $type) {
-
- assert(!empty($key));
-
- if ($type == 'U') {
- if (empty($this->$key)) {
- continue;
+ } else {
+ $pkey = array();
+ $pval = array();
+ $types = $this->keyTypes();
+ ksort($types);
+ foreach ($types as $key => $type) {
+ if ($type == 'K') {
+ $pkey[] = $key;
+ $pval[] = $this->$key;
+ } else {
+ $c->delete($this->cacheKey($this->tableName(), $key, $this->$key));
}
- $ckeys[] = $this->cacheKey($this->tableName(), $key, $this->$key);
- } else if ($type == 'K' || $type == 'N') {
- $pkey[] = $key;
- $pval[] = $this->$key;
- } else {
- throw new Exception("Unknown key type $key => $type for " . $this->tableName());
}
+ # should work for both compound and scalar pkeys
+ # XXX: comma works for now but may not be safe separator for future keys
+ $pvals = implode(',', $pval);
+ $pkeys = implode(',', $pkey);
+ $c->delete($this->cacheKey($this->tableName(), $pkeys, $pvals));
}
-
- assert(count($pkey) > 0);
-
- // XXX: should work for both compound and scalar pkeys
- $pvals = implode(',', $pval);
- $pkeys = implode(',', $pkey);
-
- $ckeys[] = $this->cacheKey($this->tableName(), $pkeys, $pvals);
-
- return $ckeys;
}
function multicache($cls, $kv)
{
ksort($kv);
- $c = self::memcache();
+ $c = Memcached_DataObject::memcache();
if (!$c) {
return false;
} else {
- return $c->get(self::multicacheKey($cls, $kv));
+ $pkeys = implode(',', array_keys($kv));
+ $pvals = implode(',', array_values($kv));
+ return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals));
}
}
- static function multicacheKey($cls, $kv)
- {
- ksort($kv);
- $pkeys = implode(',', array_keys($kv));
- $pvals = implode(',', array_values($kv));
- return self::cacheKey($cls, $pkeys, $pvals);
- }
-
function getSearchEngine($table)
{
require_once INSTALLDIR.'/lib/search_engines.php';
$key_part = common_keyize($cls).':'.md5($qry);
$ckey = common_cache_key($key_part);
$stored = $c->get($ckey);
-
- if ($stored !== false) {
+ if ($stored) {
return new ArrayWrapper($stored);
}
return new ArrayWrapper($cached);
}
+ /**
+ * sends query to database - this is the private one that must work
+ * - internal functions use this rather than $this->query()
+ *
+ * Overridden to do logging.
+ *
+ * @param string $string
+ * @access private
+ * @return mixed none or PEAR_Error
+ */
+ function _query($string)
+ {
+ $start = microtime(true);
+ $result = parent::_query($string);
+ $delta = microtime(true) - $start;
+
+ $limit = common_config('db', 'log_slow_queries');
+ if (($limit > 0 && $delta >= $limit) || common_config('db', 'log_queries')) {
+ $clean = $this->sanitizeQuery($string);
+ common_log(LOG_DEBUG, sprintf("DB query (%0.3fs): %s", $delta, $clean));
+ }
+ return $result;
+ }
+
+ // Sanitize a query for logging
+ // @fixme don't trim spaces in string literals
+ function sanitizeQuery($string)
+ {
+ $string = preg_replace('/\s+/', ' ', $string);
+ $string = trim($string);
+ return $string;
+ }
+
// We overload so that 'SET NAMES "utf8"' is called for
// each connection
$exists = false;
}
+ // @fixme horrible evil hack!
+ //
+ // In multisite configuration we don't want to keep around a separate
+ // connection for every database; we could end up with thousands of
+ // connections open per thread. In an ideal world we might keep
+ // a connection per server and select different databases, but that'd
+ // be reliant on having the same db username/pass as well.
+ //
+ // MySQL connections are cheap enough we're going to try just
+ // closing out the old connection and reopening when we encounter
+ // a new DSN.
+ //
+ // WARNING WARNING if we end up actually using multiple DBs at a time
+ // we'll need some fancier logic here.
+ if (!$exists && !empty($_DB_DATAOBJECT['CONNECTIONS'])) {
+ foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $index => $conn) {
+ if (!empty($conn)) {
+ $conn->disconnect();
+ }
+ unset($_DB_DATAOBJECT['CONNECTIONS'][$index]);
+ }
+ }
+
$result = parent::_connect();
if ($result && !$exists) {