X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=classes%2FMemcached_DataObject.php;h=9cedcc6ff604c891a1d5628ac5eaacc3ebd7bcb5;hb=d6b28c64830f632bb2f4b6f3c9369b9e56ad217a;hp=59809dc8f772a060efa5200595e3cb0ded66310c;hpb=36b331d469b6dcd1101783f21265f7be624bc58f;p=quix0rs-gnu-social.git diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 59809dc8f7..b5057ba220 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('GNUSOCIAL')) { exit(1); } class Memcached_DataObject extends Safe_DataObject { @@ -30,23 +30,20 @@ class Memcached_DataObject extends Safe_DataObject * @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) + static function getClassKV($cls, $k, $v=null) { if (is_null($v)) { $v = $k; - // XXX: HACK! - $i = new $cls; - $keys = $i->keys(); + $keys = static::pkeyCols(); + if (count($keys) > 1) { + // FIXME: maybe call pkeyGetClass() ourselves? + throw new Exception('Use pkeyGetClass() for compound primary keys'); + } $k = $keys[0]; - unset($i); } - $i = Memcached_DataObject::getcached($cls, $k, $v); + $i = self::getcached($cls, $k, $v); if ($i === false) { // false == cache miss - $i = DB_DataObject::factory($cls); - if (empty($i)) { - $i = false; - return $i; - } + $i = new $cls; $result = $i->get($k, $v); if ($result) { // Hit! @@ -65,20 +62,315 @@ class Memcached_DataObject extends Safe_DataObject } /** - * @fixme Should this return false on lookup fail to match staticGet? + * Get multiple items from the database by key + * + * @param string $cls Class to fetch + * @param string $keyCol name of column for key + * @param array $keyVals key values to fetch + * + * @return array Array of objects, in order + */ + static function multiGetClass($cls, $keyCol, array $keyVals) + { + $obj = new $cls; + + // PHP compatible datatype for settype() below + $colType = $obj->columnType($keyCol); + + if (!in_array($colType, array('integer', 'int'))) { + // This is because I'm afraid to escape strings incorrectly + // in the way we use them below in FIND_IN_SET for MariaDB + throw new ServerException('Cannot do multiGet on anything but integer columns'); + } + + $obj->whereAddIn($keyCol, $keyVals, $colType); + + // Since we're inputting straight to a query: format and escape + foreach ($keyVals as $key=>$val) { + settype($val, $colType); + $keyVals[$key] = $obj->escape($val); + } + + // FIND_IN_SET will make sure we keep the desired order + $obj->orderBy(sprintf("FIND_IN_SET(%s, '%s')", $keyCol, implode(',', $keyVals))); + $obj->find(); + + return $obj; + } + + /** + * Get multiple items from the database by key + * + * @param string $cls Class to fetch + * @param string $keyCol name of column for key + * @param array $keyVals key values to fetch + * @param boolean $otherCols Other columns to hold fixed + * + * @return array Array mapping $keyVals to objects, or null if not found */ - function pkeyGet($cls, $kv) + static function pivotGetClass($cls, $keyCol, array $keyVals, array $otherCols = array()) { - $i = Memcached_DataObject::multicache($cls, $kv); + if (is_array($keyCol)) { + foreach ($keyVals as $keyVal) { + $result[implode(',', $keyVal)] = null; + } + } else { + $result = array_fill_keys($keyVals, null); + } + + $toFetch = array(); + + foreach ($keyVals as $keyVal) { + + if (is_array($keyCol)) { + $kv = array_combine($keyCol, $keyVal); + } else { + $kv = array($keyCol => $keyVal); + } + + $kv = array_merge($otherCols, $kv); + + $i = self::multicache($cls, $kv); + + if ($i !== false) { + if (is_array($keyCol)) { + $result[implode(',', $keyVal)] = $i; + } else { + $result[$keyVal] = $i; + } + } else if (!empty($keyVal)) { + $toFetch[] = $keyVal; + } + } + + if (count($toFetch) > 0) { + $i = new $cls; + foreach ($otherCols as $otherKeyCol => $otherKeyVal) { + $i->$otherKeyCol = $otherKeyVal; + } + if (is_array($keyCol)) { + $i->whereAdd(self::_inMultiKey($i, $keyCol, $toFetch)); + } else { + $i->whereAddIn($keyCol, $toFetch, $i->columnType($keyCol)); + } + if ($i->find()) { + while ($i->fetch()) { + $copy = clone($i); + $copy->encache(); + if (is_array($keyCol)) { + $vals = array(); + foreach ($keyCol as $k) { + $vals[] = $i->$k; + } + $result[implode(',', $vals)] = $copy; + } else { + $result[$i->$keyCol] = $copy; + } + } + } + + // Save state of DB misses + + foreach ($toFetch as $keyVal) { + $r = null; + if (is_array($keyCol)) { + $r = $result[implode(',', $keyVal)]; + } else { + $r = $result[$keyVal]; + } + if (empty($r)) { + if (is_array($keyCol)) { + $kv = array_combine($keyCol, $keyVal); + } else { + $kv = array($keyCol => $keyVal); + } + $kv = array_merge($otherCols, $kv); + // save the fact that no such row exists + $c = self::memcache(); + if (!empty($c)) { + $ck = self::multicacheKey($cls, $kv); + $c->set($ck, null); + } + } + } + } + + return $result; + } + + static function _inMultiKey($i, $cols, $values) + { + $types = array(); + + foreach ($cols as $col) { + $types[$col] = $i->columnType($col); + } + + $first = true; + + $query = ''; + + foreach ($values as $value) { + if ($first) { + $query .= '( '; + $first = false; + } else { + $query .= ' OR '; + } + $query .= '( '; + $i = 0; + $firstc = true; + foreach ($cols as $col) { + if (!$firstc) { + $query .= ' AND '; + } else { + $firstc = false; + } + switch ($types[$col]) { + case 'string': + case 'datetime': + $query .= sprintf("%s = %s", $col, $i->_quote($value[$i])); + break; + default: + $query .= sprintf("%s = %s", $col, $value[$i]); + break; + } + } + $query .= ') '; + } + + if (!$first) { + $query .= ' )'; + } + + return $query; + } + + static function pkeyColsClass($cls) + { + $i = new $cls; + $types = $i->keyTypes(); + ksort($types); + + $pkey = array(); + + foreach ($types as $key => $type) { + if ($type == 'K' || $type == 'N') { + $pkey[] = $key; + } + } + + return $pkey; + } + + static function listFindClass($cls, $keyCol, array $keyVals) + { + $i = new $cls; + $i->whereAddIn($keyCol, $keyVals, $i->columnType($keyCol)); + if (!$i->find()) { + throw new NoResultException($i); + } + + return $i; + } + + static function listGetClass($cls, $keyCol, array $keyVals) + { + $pkeyMap = array_fill_keys($keyVals, array()); + $result = array_fill_keys($keyVals, array()); + + $pkeyCols = static::pkeyCols(); + + $toFetch = array(); + $allPkeys = array(); + + // We only cache keys -- not objects! + + foreach ($keyVals as $keyVal) { + $l = self::cacheGet(sprintf("%s:list-ids:%s:%s", strtolower($cls), $keyCol, $keyVal)); + if ($l !== false) { + $pkeyMap[$keyVal] = $l; + foreach ($l as $pkey) { + $allPkeys[] = $pkey; + } + } else { + $toFetch[] = $keyVal; + } + } + + if (count($allPkeys) > 0) { + $keyResults = self::pivotGetClass($cls, $pkeyCols, $allPkeys); + + foreach ($pkeyMap as $keyVal => $pkeyList) { + foreach ($pkeyList as $pkeyVal) { + $i = $keyResults[implode(',',$pkeyVal)]; + if (!empty($i)) { + $result[$keyVal][] = $i; + } + } + } + } + + if (count($toFetch) > 0) { + try { + $i = self::listFindClass($cls, $keyCol, $toFetch); + + while ($i->fetch()) { + $copy = clone($i); + $copy->encache(); + $result[$i->$keyCol][] = $copy; + $pkeyVal = array(); + foreach ($pkeyCols as $pkeyCol) { + $pkeyVal[] = $i->$pkeyCol; + } + $pkeyMap[$i->$keyCol][] = $pkeyVal; + } + } catch (NoResultException $e) { + // no results found for our keyVals, so we leave them as empty arrays + } + foreach ($toFetch as $keyVal) { + self::cacheSet(sprintf("%s:list-ids:%s:%s", strtolower($cls), $keyCol, $keyVal), + $pkeyMap[$keyVal]); + } + } + + return $result; + } + + function columnType($columnName) + { + $keys = $this->table(); + if (!array_key_exists($columnName, $keys)) { + throw new Exception('Unknown key column ' . $columnName . ' in ' . join(',', array_keys($keys))); + } + + $def = $keys[$columnName]; + + if ($def & DB_DATAOBJECT_INT) { + return 'integer'; + } else { + return 'string'; + } + } + + /** + * @todo FIXME: Should this return false on lookup fail to match getKV? + */ + static function pkeyGetClass($cls, array $kv) + { + $i = self::multicache($cls, $kv); if ($i !== false) { // false == cache miss return $i; } else { - $i = DB_DataObject::factory($cls); - if (empty($i) || PEAR::isError($i)) { - return false; - } + $i = new $cls; foreach ($kv as $k => $v) { - $i->$k = $v; + if (is_null($v)) { + // XXX: possible SQL injection...? Don't + // pass keys from the browser, eh. + $i->whereAdd("$k is null"); + } else { + $i->$k = $v; + } } if ($i->find(true)) { $i->encache(); @@ -104,23 +396,23 @@ class Memcached_DataObject extends Safe_DataObject return $result; } - function update($orig=null) + function update($dataObject=false) { - if (is_object($orig) && $orig instanceof Memcached_DataObject) { - $orig->decache(); # might be different keys + if (is_object($dataObject) && $dataObject instanceof Memcached_DataObject) { + $dataObject->decache(); # might be different keys } - $result = parent::update($orig); - if ($result) { + $result = parent::update($dataObject); + if ($result !== false) { $this->fixupTimestamps(); $this->encache(); } return $result; } - function delete() + function delete($useWhere=false) { $this->decache(); # while we still have the values! - return parent::delete(); + return parent::delete($useWhere); } static function memcache() { @@ -138,16 +430,16 @@ class Memcached_DataObject extends Safe_DataObject } static function getcached($cls, $k, $v) { - $c = Memcached_DataObject::memcache(); + $c = self::memcache(); if (!$c) { return false; } else { - $obj = $c->get(Memcached_DataObject::cacheKey($cls, $k, $v)); + $obj = $c->get(self::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)); + $c->delete(self::cacheKey($cls, $k, $v)); return false; } } @@ -169,16 +461,16 @@ class Memcached_DataObject extends Safe_DataObject } global $_DB_DATAOBJECT; - if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) { + if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) { $this->databaseStructure(); } - return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]; + return $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]; } function encache() { - $c = $this->memcache(); + $c = self::memcache(); if (!$c) { return false; @@ -199,7 +491,7 @@ class Memcached_DataObject extends Safe_DataObject function decache() { - $c = $this->memcache(); + $c = self::memcache(); if (!$c) { return false; @@ -230,7 +522,7 @@ class Memcached_DataObject extends Safe_DataObject if (empty($this->$key)) { continue; } - $ckeys[] = $this->cacheKey($this->tableName(), $key, self::valueString($this->$key)); + $ckeys[] = self::cacheKey($this->tableName(), $key, self::valueString($this->$key)); } else if ($type == 'K' || $type == 'N') { $pkey[] = $key; $pval[] = self::valueString($this->$key); @@ -246,12 +538,12 @@ class Memcached_DataObject extends Safe_DataObject $pvals = implode(',', $pval); $pkeys = implode(',', $pkey); - $ckeys[] = $this->cacheKey($this->tableName(), $pkeys, $pvals); + $ckeys[] = self::cacheKey($this->tableName(), $pkeys, $pvals); return $ckeys; } - function multicache($cls, $kv) + static function multicache($cls, $kv) { ksort($kv); $c = self::memcache(); @@ -273,30 +565,29 @@ class Memcached_DataObject extends Safe_DataObject 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 { - // Low level exception. No need for i18n as discussed with Brion. - throw new ServerException('Unknown search type: ' . $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); + // Low level exception. No need for i18n as discussed with Brion. + 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(); + $c = self::memcache(); if (!$c) { $inst = new $cls(); $inst->query($qry); @@ -359,7 +650,7 @@ class Memcached_DataObject extends Safe_DataObject } else { $msg = sprintf("DB query (%0.3fs): %s", $delta, $clean); } - common_log(LOG_DEBUG, $msg); + common_debug($msg); } if ($fail) { @@ -390,8 +681,10 @@ class Memcached_DataObject extends Safe_DataObject 'delete', 'update', 'find'); - $ignoreStatic = array('staticGet', + $ignoreStatic = array('getKV', + 'getClassKV', 'pkeyGet', + 'pkeyGetClass', 'cachedQuery'); $here = get_class($this); // if we get confused $bt = debug_backtrace(); @@ -410,7 +703,7 @@ class Memcached_DataObject extends Safe_DataObject continue; } if (in_array($func, $ignoreStatic)) { - continue; // @fixme this shouldn't be needed? + continue; // @todo FIXME: This shouldn't be needed? } $here = get_class($frame['object']) . '->' . $func; break; @@ -441,17 +734,17 @@ class Memcached_DataObject extends Safe_DataObject return $string; } - // We overload so that 'SET NAMES "utf8"' is called for + // We overload so that 'SET NAMES "utf8mb4"' is called for // each connection function _connect() { - global $_DB_DATAOBJECT; + global $_DB_DATAOBJECT, $_PEAR; $sum = $this->_getDbDsnMD5(); if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$sum]) && - !PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$sum])) { + !$_PEAR->isError($_DB_DATAOBJECT['CONNECTIONS'][$sum])) { $exists = true; } else { $exists = false; @@ -473,7 +766,9 @@ class Memcached_DataObject extends Safe_DataObject // 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)) { + if ($_PEAR->isError($conn)) { + common_log(LOG_WARNING, __METHOD__ . " cannot disconnect failed DB connection: '".$conn->getMessage()."'."); + } elseif (!empty($conn)) { $conn->disconnect(); } unset($_DB_DATAOBJECT['CONNECTIONS'][$index]); @@ -488,10 +783,10 @@ class Memcached_DataObject extends Safe_DataObject 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); + if ($DB instanceof DB_mysqli || $DB instanceof MDB2_Driver_mysqli) { + mysqli_set_charset($conn, 'utf8mb4'); + } else if ($DB instanceof DB_mysql || $DB instanceof MDB2_Driver_mysql) { + mysql_set_charset('utf8mb4', $conn); } } } @@ -529,7 +824,7 @@ class Memcached_DataObject extends Safe_DataObject global $_DB_DATAOBJECT; if (empty($_DB_DATAOBJECT['CONFIG'])) { - DB_DataObject::_loadConfig(); + self::_loadConfig(); } $options = &$_DB_DATAOBJECT['CONFIG']; @@ -541,7 +836,7 @@ class Memcached_DataObject extends Safe_DataObject if (!$dsn) { if (!$this->_database) { - $this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null; + $this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null; } if ($this->_database && !empty($options["database_{$this->_database}"])) { @@ -553,7 +848,7 @@ class Memcached_DataObject extends Safe_DataObject if (!$dsn) { // TRANS: Exception thrown when database name or Data Source Name could not be found. - throw new Exception(_("No database name or DSN found anywhere.")); + throw new Exception(_('No database name or DSN found anywhere.')); } return $dsn;