X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=classes%2FMemcached_DataObject.php;h=33645a3e8b6eeaf75246d69e87f70db41ab209a7;hb=ef3b849db05ff6ad4b9e97b38a82242a710519d1;hp=3ec7613891d14f172ffc2ddc0d4c32fa24f64376;hpb=dde1c00ce727b311c399271d02cb077f6725edb5;p=quix0rs-gnu-social.git diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 3ec7613891..33645a3e8b 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -1,7 +1,7 @@ . */ -if (!defined('LACONICA')) { exit(1); } - -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } class Memcached_DataObject extends DB_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(); + } + } + + /** + * Magic function called at serialize() time. + * + * We use this to drop a couple process-specific references + * from DB_DataObject which can cause trouble in future + * processes. + * + * @return array of variable names to include in serialization. + */ + function __sleep() + { + $vars = array_keys(get_object_vars($this)); + $skip = array('_DB_resultid', '_link_loaded'); + return array_diff($vars, $skip); + } + + /** + * Magic function called at unserialize() time. + * + * Clean out some process-specific variables which might + * be floating around from a previous process's cached + * objects. + * + * Old cached objects may still have them. + */ + function __wakeup() + { + // Refers to global state info from a previous process. + // 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; + } + + /** + * Wrapper for DB_DataObject's static lookup using memcached + * as backing instead of an in-process cache array. + * + * @param string $cls classname of object type to load + * @param mixed $k key field name, or value for primary key + * @param mixed $v key field value, or leave out for primary key lookup + * @return mixed Memcached_DataObject subtype or false + */ function &staticGet($cls, $k, $v=null) { if (is_null($v)) { @@ -37,11 +94,19 @@ class Memcached_DataObject extends DB_DataObject if ($i) { return $i; } else { - $i = DB_DataObject::staticGet($cls, $k, $v); - if ($i) { + $i = DB_DataObject::factory($cls); + if (empty($i)) { + $i = false; + return $i; + } + $result = $i->get($k, $v); + if ($result) { $i->encache(); + return $i; + } else { + $i = false; + return $i; } - return $i; } } @@ -93,6 +158,11 @@ class Memcached_DataObject extends DB_DataObject } static function cacheKey($cls, $k, $v) { + if (is_object($cls) || is_object($k) || is_object($v)) { + $e = new Exception(); + common_log(LOG_ERR, __METHOD__ . ' object in param: ' . + str_replace("\n", " ", $e->getTraceAsString())); + } return common_cache_key(strtolower($cls).':'.$k.':'.$v); } @@ -101,7 +171,16 @@ class Memcached_DataObject extends DB_DataObject 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; } } @@ -120,6 +199,12 @@ class Memcached_DataObject extends DB_DataObject $c = $this->memcache(); if (!$c) { return false; + } 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(); @@ -184,20 +269,20 @@ class Memcached_DataObject extends DB_DataObject require_once INSTALLDIR.'/lib/search_engines.php'; static $search_engine; if (!isset($search_engine)) { - $connected = false; - if (common_config('sphinx', 'enabled')) { - $search_engine = new SphinxSearch($this, $table); - $connected = $search_engine->is_connected(); - } - - // unable to connect to sphinx' search daemon - if (!$connected) { - if ('mysql' === common_config('db', 'type')) { + if (Event::handle('GetSearchEngine', array($this, $table, &$search_engine))) { + if ('mysql' === common_config('db', 'type')) { + $type = common_config('search', 'type'); + if ($type == 'like') { + $search_engine = new MySQLLikeSearch($this, $table); + } else if ($type == 'fulltext') { $search_engine = new MySQLSearch($this, $table); } else { - $search_engine = new PGSearch($this, $table); + throw new ServerException('Unknown search type: ' . $type); } + } else { + $search_engine = new PGSearch($this, $table); } + } } return $search_engine; } @@ -218,16 +303,159 @@ class Memcached_DataObject extends DB_DataObject } $inst = new $cls(); - $result = $inst->query($qry); - if (!$result) { - return $inst; - } + $inst->query($qry); $cached = array(); while ($inst->fetch()) { $cached[] = clone($inst); } $inst->free(); $c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry); - return ArrayWrapper($cached); + 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 + + function _connect() + { + global $_DB_DATAOBJECT; + + $sum = $this->_getDbDsnMD5(); + + if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$sum]) && + !PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$sum])) { + $exists = true; + } else { + $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) { + $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + if (common_config('db', 'type') == 'mysql' && + common_config('db', 'utf8')) { + $conn = $DB->connection; + if (!empty($conn)) { + if ($DB instanceof DB_mysqli) { + mysqli_set_charset($conn, 'utf8'); + } else if ($DB instanceof DB_mysql) { + mysql_set_charset('utf8', $conn); + } + } + } + } + + return $result; + } + + // XXX: largely cadged from DB_DataObject + + function _getDbDsnMD5() + { + if ($this->_database_dsn_md5) { + return $this->_database_dsn_md5; + } + + $dsn = $this->_getDbDsn(); + + if (is_string($dsn)) { + $sum = md5($dsn); + } else { + /// support array based dsn's + $sum = md5(serialize($dsn)); + } + + return $sum; + } + + function _getDbDsn() + { + global $_DB_DATAOBJECT; + + if (empty($_DB_DATAOBJECT['CONFIG'])) { + DB_DataObject::_loadConfig(); + } + + $options = &$_DB_DATAOBJECT['CONFIG']; + + // if the databse dsn dis defined in the object.. + + $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null; + + if (!$dsn) { + + if (!$this->_database) { + $this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null; + } + + if ($this->_database && !empty($options["database_{$this->_database}"])) { + $dsn = $options["database_{$this->_database}"]; + } else if (!empty($options['database'])) { + $dsn = $options['database']; + } + } + + if (!$dsn) { + throw new Exception("No database name / dsn found anywhere"); + } + + return $dsn; } }