. */ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; 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(); } } /** * 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)) { $v = $k; # XXX: HACK! $i = new $cls; $keys = $i->keys(); $k = $keys[0]; unset($i); } $i = Memcached_DataObject::getcached($cls, $k, $v); if ($i) { return $i; } else { $i = DB_DataObject::factory($cls); if (empty($i)) { return null; } $result = $i->get($k, $v); if ($result) { $i->encache(); return $i; } else { return null; } } } function &pkeyGet($cls, $kv) { $i = Memcached_DataObject::multicache($cls, $kv); if ($i) { return $i; } else { $i = new $cls(); foreach ($kv as $k => $v) { $i->$k = $v; } if ($i->find(true)) { $i->encache(); } else { $i = null; } return $i; } } function insert() { $result = parent::insert(); return $result; } function update($orig=null) { if (is_object($orig) && $orig instanceof Memcached_DataObject) { $orig->decache(); # might be different keys } $result = parent::update($orig); if ($result) { $this->encache(); } return $result; } function delete() { $this->decache(); # while we still have the values! return parent::delete(); } static function memcache() { return common_memcache(); } static function cacheKey($cls, $k, $v) { if (is_object($cls) || is_object($j) || 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); } static function getcached($cls, $k, $v) { $c = Memcached_DataObject::memcache(); if (!$c) { return false; } else { return $c->get(Memcached_DataObject::cacheKey($cls, $k, $v)); } } function keyTypes() { global $_DB_DATAOBJECT; if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) { $this->databaseStructure(); } return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]; } function encache() { $c = $this->memcache(); if (!$c) { 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; } 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)); } } # 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)); } } function multicache($cls, $kv) { ksort($kv); $c = Memcached_DataObject::memcache(); if (!$c) { return false; } else { $pkeys = implode(',', array_keys($kv)); $pvals = implode(',', array_values($kv)); return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals)); } } function getSearchEngine($table) { require_once INSTALLDIR.'/lib/search_engines.php'; static $search_engine; if (!isset($search_engine)) { 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 { throw new ServerException('Unknown search type: ' . $type); } } else { $search_engine = new PGSearch($this, $table); } } } return $search_engine; } static function cachedQuery($cls, $qry, $expiry=3600) { $c = Memcached_DataObject::memcache(); if (!$c) { $inst = new $cls(); $inst->query($qry); return $inst; } $key_part = common_keyize($cls).':'.md5($qry); $ckey = common_cache_key($key_part); $stored = $c->get($ckey); if ($stored) { return new ArrayWrapper($stored); } $inst = new $cls(); $inst->query($qry); $cached = array(); while ($inst->fetch()) { $cached[] = clone($inst); } $inst->free(); $c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry); return new ArrayWrapper($cached); } function cleanup() { global $_DB_DATAOBJECT; if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) { unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]); } if (isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) { unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]); } } // 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; } $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; } }