if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
-class Memcached_DataObject extends DB_DataObject
+class Memcached_DataObject extends Safe_DataObject
{
- /**
- * Destructor to free global memory resources associated with
- * this data object when it's unset or goes out of scope.
- * DB_DataObject doesn't do this yet by itself.
- */
-
- function __destruct()
- {
- $this->free();
- if (method_exists('DB_DataObject', '__destruct')) {
- parent::__destruct();
- }
- }
-
/**
* Wrapper for DB_DataObject's static lookup using memcached
* as backing instead of an in-process cache array.
unset($i);
}
$i = Memcached_DataObject::getcached($cls, $k, $v);
- if ($i !== false) { // false == cache miss
- return $i;
- } else {
+ if ($i === false) { // false == cache miss
$i = DB_DataObject::factory($cls);
if (empty($i)) {
- return false;
+ $i = false;
+ return $i;
}
$result = $i->get($k, $v);
if ($result) {
+ // Hit!
$i->encache();
- return $i;
} else {
// save the fact that no such row exists
$c = self::memcache();
$ck = self::cachekey($cls, $k, $v);
$c->set($ck, null);
}
- return false;
+ $i = false;
}
}
+ return $i;
}
- function &pkeyGet($cls, $kv)
+ /**
+ * @fixme Should this return false on lookup fail to match staticGet?
+ */
+ function pkeyGet($cls, $kv)
{
$i = Memcached_DataObject::multicache($cls, $kv);
if ($i !== false) { // false == cache miss
function insert()
{
$result = parent::insert();
+ if ($result) {
+ $this->fixupTimestamps();
+ $this->encache(); // in case of cached negative lookups
+ }
return $result;
}
}
$result = parent::update($orig);
if ($result) {
+ $this->fixupTimestamps();
$this->encache();
}
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 keyTypes()
{
+ // ini-based classes return number-indexed arrays. handbuilt
+ // classes return column => keytype. Make this uniform.
+
+ $keys = $this->keys();
+
+ $keyskeys = array_keys($keys);
+
+ if (is_string($keyskeys[0])) {
+ return $keys;
+ }
+
global $_DB_DATAOBJECT;
if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
$this->databaseStructure();
if (!$c) {
return false;
- }
-
- $keys = $this->_allCacheKeys();
+ } 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 {
+ $keys = $this->_allCacheKeys();
- foreach ($keys as $key) {
- $c->set($key, $this);
+ foreach ($keys as $key) {
+ $c->set($key, $this);
+ }
}
}
$cached[] = clone($inst);
}
$inst->free();
- $c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry);
+ $c->set($ckey, $cached, Cache::COMPRESSED, $expiry);
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']) && php_sapi_name() == 'cli') {
+ foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $index => $conn) {
+ if (!empty($conn)) {
+ $conn->disconnect();
+ }
+ unset($_DB_DATAOBJECT['CONNECTIONS'][$index]);
+ }
+ }
+
$result = parent::_connect();
if ($result && !$exists) {
return $dsn;
}
+
+ static function blow()
+ {
+ $c = self::memcache();
+
+ if (empty($c)) {
+ return false;
+ }
+
+ $args = func_get_args();
+
+ $format = array_shift($args);
+
+ $keyPart = vsprintf($format, $args);
+
+ $cacheKey = common_cache_key($keyPart);
+
+ return $c->delete($cacheKey);
+ }
+
+ function fixupTimestamps()
+ {
+ // Fake up timestamp columns
+ $columns = $this->table();
+ foreach ($columns as $name => $type) {
+ if ($type & DB_DATAOBJECT_MYSQLTIMESTAMP) {
+ $this->$name = common_sql_now();
+ }
+ }
+ }
+
+ function debugDump()
+ {
+ common_debug("debugDump: " . common_log_objstring($this));
+ }
+
+ function raiseError($message, $type = null, $behaviour = null)
+ {
+ throw new ServerException("DB_DataObject error [$type]: $message");
+ }
+
+ static function cacheGet($keyPart)
+ {
+ $c = self::memcache();
+
+ if (empty($c)) {
+ return false;
+ }
+
+ $cacheKey = common_cache_key($keyPart);
+
+ return $c->get($cacheKey);
+ }
+
+ static function cacheSet($keyPart, $value)
+ {
+ $c = self::memcache();
+
+ if (empty($c)) {
+ return false;
+ }
+
+ $cacheKey = common_cache_key($keyPart);
+
+ return $c->set($cacheKey, $value);
+ }
}
+