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();
- }
- }
-
- /**
- * 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.
return $i;
} else {
$i = DB_DataObject::factory($cls);
- if (empty($i)) {
+ if (empty($i) || PEAR::isError($i)) {
return false;
}
foreach ($kv as $k => $v) {
}
static function memcache() {
- return common_memcache();
+ return Cache::instance();
}
static function cacheKey($cls, $k, $v) {
- if (is_object($cls) || is_object($k) || is_object($v)) {
+ if (is_object($cls) || is_object($k) || (is_object($v) && !($v instanceof DB_DataObject_Cast))) {
$e = new Exception();
common_log(LOG_ERR, __METHOD__ . ' object in param: ' .
str_replace("\n", " ", $e->getTraceAsString()));
}
- return common_cache_key(strtolower($cls).':'.$k.':'.$v);
+ $vstr = self::valueString($v);
+ return Cache::key(strtolower($cls).':'.$k.':'.$vstr);
}
static function getcached($cls, $k, $v) {
str_replace("\n", " ", $e->getTraceAsString()));
return false;
} else {
- $keys = $this->_allCacheKeys();
+ $keys = $this->_allCacheKeys();
- foreach ($keys as $key) {
- $c->set($key, $this);
- }
+ foreach ($keys as $key) {
+ $c->set($key, $this);
+ }
}
}
if (empty($this->$key)) {
continue;
}
- $ckeys[] = $this->cacheKey($this->tableName(), $key, $this->$key);
+ $ckeys[] = $this->cacheKey($this->tableName(), $key, self::valueString($this->$key));
} else if ($type == 'K' || $type == 'N') {
$pkey[] = $key;
- $pval[] = $this->$key;
+ $pval[] = self::valueString($this->$key);
} else {
+ // Low level exception. No need for i18n as discussed with Brion.
throw new Exception("Unknown key type $key => $type for " . $this->tableName());
}
}
} 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);
}
} else {
$inst->query($qry);
return $inst;
}
- $key_part = common_keyize($cls).':'.md5($qry);
- $ckey = common_cache_key($key_part);
+ $key_part = Cache::keyize($cls).':'.md5($qry);
+ $ckey = Cache::key($key_part);
$stored = $c->get($ckey);
if ($stored !== false) {
*/
function _query($string)
{
+ if (common_config('db', 'annotate_queries')) {
+ $string = $this->annotateQuery($string);
+ }
+
$start = microtime(true);
- $result = parent::_query($string);
+ $fail = false;
+ $result = null;
+ if (Event::handle('StartDBQuery', array($this, $string, &$result))) {
+ common_perf_counter('query', $string);
+ try {
+ $result = parent::_query($string);
+ } catch (Exception $e) {
+ $fail = $e;
+ }
+ Event::handle('EndDBQuery', array($this, $string, &$result));
+ }
$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));
+ if ($fail) {
+ $msg = sprintf("FAILED DB query (%0.3fs): %s - %s", $delta, $fail->getMessage(), $clean);
+ } else {
+ $msg = sprintf("DB query (%0.3fs): %s", $delta, $clean);
+ }
+ common_log(LOG_DEBUG, $msg);
+ }
+
+ if ($fail) {
+ throw $fail;
}
return $result;
}
+ /**
+ * Find the first caller in the stack trace that's not a
+ * low-level database function and add a comment to the
+ * query string. This should then be visible in process lists
+ * and slow query logs, to help identify problem areas.
+ *
+ * Also marks whether this was a web GET/POST or which daemon
+ * was running it.
+ *
+ * @param string $string SQL query string
+ * @return string SQL query string, with a comment in it
+ */
+ function annotateQuery($string)
+ {
+ $ignore = array('annotateQuery',
+ '_query',
+ 'query',
+ 'get',
+ 'insert',
+ 'delete',
+ 'update',
+ 'find');
+ $ignoreStatic = array('staticGet',
+ 'pkeyGet',
+ 'cachedQuery');
+ $here = get_class($this); // if we get confused
+ $bt = debug_backtrace();
+
+ // Find the first caller that's not us?
+ foreach ($bt as $frame) {
+ $func = $frame['function'];
+ if (isset($frame['type']) && $frame['type'] == '::') {
+ if (in_array($func, $ignoreStatic)) {
+ continue;
+ }
+ $here = $frame['class'] . '::' . $func;
+ break;
+ } else if (isset($frame['type']) && $frame['type'] == '->') {
+ if ($frame['object'] === $this && in_array($func, $ignore)) {
+ continue;
+ }
+ if (in_array($func, $ignoreStatic)) {
+ continue; // @fixme this shouldn't be needed?
+ }
+ $here = get_class($frame['object']) . '->' . $func;
+ break;
+ }
+ $here = $func;
+ break;
+ }
+
+ if (php_sapi_name() == 'cli') {
+ $context = basename($_SERVER['PHP_SELF']);
+ } else {
+ $context = $_SERVER['REQUEST_METHOD'];
+ }
+
+ // Slip the comment in after the first command,
+ // or DB_DataObject gets confused about handling inserts and such.
+ $parts = explode(' ', $string, 2);
+ $parts[0] .= " /* $context $here */";
+ return implode(' ', $parts);
+ }
+
// Sanitize a query for logging
// @fixme don't trim spaces in string literals
function sanitizeQuery($string)
}
}
}
+ // Needed to make timestamp values usefully comparable.
+ if (common_config('db', 'type') == 'mysql') {
+ parent::_query("set time_zone='+0:00'");
+ }
}
return $result;
}
if (!$dsn) {
- throw new Exception("No database name / dsn found anywhere");
+ // TRANS: Exception thrown when database name or Data Source Name could not be found.
+ throw new Exception(_("No database name or DSN found anywhere."));
}
return $dsn;
$keyPart = vsprintf($format, $args);
- $cacheKey = common_cache_key($keyPart);
+ $cacheKey = Cache::key($keyPart);
return $c->delete($cacheKey);
}
function raiseError($message, $type = null, $behaviour = null)
{
- throw new ServerException("DB_DataObject error [$type]: $message");
+ $id = get_class($this);
+ if (!empty($this->id)) {
+ $id .= ':' . $this->id;
+ }
+ if ($message instanceof PEAR_Error) {
+ $message = $message->getMessage();
+ }
+ // Low level exception. No need for i18n as discussed with Brion.
+ throw new ServerException("[$id] DB_DataObject error [$type]: $message");
}
static function cacheGet($keyPart)
return false;
}
- $cacheKey = common_cache_key($keyPart);
+ $cacheKey = Cache::key($keyPart);
return $c->get($cacheKey);
}
- static function cacheSet($keyPart, $value)
+ static function cacheSet($keyPart, $value, $flag=null, $expiry=null)
{
$c = self::memcache();
return false;
}
- $cacheKey = common_cache_key($keyPart);
+ $cacheKey = Cache::key($keyPart);
+
+ return $c->set($cacheKey, $value, $flag, $expiry);
+ }
- return $c->set($cacheKey, $value);
+ static function valueString($v)
+ {
+ $vstr = null;
+ if (is_object($v) && $v instanceof DB_DataObject_Cast) {
+ switch ($v->type) {
+ case 'date':
+ $vstr = $v->year . '-' . $v->month . '-' . $v->day;
+ break;
+ case 'blob':
+ case 'string':
+ case 'sql':
+ case 'datetime':
+ case 'time':
+ // Low level exception. No need for i18n as discussed with Brion.
+ throw new ServerException("Unhandled DB_DataObject_Cast type passed as cacheKey value: '$v->type'");
+ break;
+ default:
+ // Low level exception. No need for i18n as discussed with Brion.
+ throw new ServerException("Unknown DB_DataObject_Cast type passed as cacheKey value: '$v->type'");
+ break;
+ }
+ } else {
+ $vstr = strval($v);
+ }
+ return $vstr;
}
}