View source
<?php
define('CACHE_GARBAGE_COLLECTION_FREQUENCY', 3600);
define('CACHE_NO_TRUNCATE', FALSE);
define('CACHE_BOOTSTRAP_PREFETCH', TRUE);
define('APDQC_CALL_HOOK_ON_CLEAR', FALSE);
define('APDQC_USE_UNION_QUERY', FALSE);
define('APDQC_CACHE_DEFAULT_COMPRESS', 0);
define('APDQC_CACHE_DEFAULT_COMPRESSION_LEVEL', 9);
function apdqc_run_prefetch_array(array $table_keys_cache_prefetch, $reap_on_next_get = TRUE, $log = TRUE) {
static $use_async_data;
if (!isset($use_async_data)) {
$do_not_run_prefetch_array =& drupal_static('apdqc_run_prefetch_array', array());
}
foreach ($table_keys_cache_prefetch as $table => $cids) {
if (is_array($do_not_run_prefetch_array) && array_key_exists($table, $do_not_run_prefetch_array) && !empty($do_not_run_prefetch_array[$table])) {
return;
}
}
if (defined('APDQC_PREFETCH')) {
if (!variable_get('apdqc_prefetch', APDQC_PREFETCH)) {
return;
}
}
$queries = array();
foreach ($table_keys_cache_prefetch as $bin => $cids) {
if (apdqc_get_bin_class_name($bin) !== 'APDQCache') {
continue;
}
$cids = array_filter(array_unique($cids));
$query = '';
if (isset($cids['*'])) {
$query .= "SELECT '{$bin}' AS bin, cid, data, created, expire, serialized FROM " . apdqc_fast_prefix_tables('{' . apdqc_fast_escape_table($bin) . '}') . "";
if (!empty($cids['*']) && is_array($cids['*'])) {
$cids_string = "'" . implode("', '", $cids['*']) . "'";
$query .= " WHERE cid NOT IN ({$cids_string})";
}
}
else {
$like_ids = array();
foreach ($cids as $key => $cid) {
if (strpos($cid, ':%') !== FALSE) {
unset($cids[$key]);
$like_ids[] = $cid;
}
}
$query .= "SELECT '{$bin}' AS bin, cid, data, created, expire, serialized FROM " . apdqc_fast_prefix_tables('{' . apdqc_fast_escape_table($bin) . '}');
if (!empty($cids)) {
$cids_string = "'" . implode("', '", $cids) . "'";
$query .= " WHERE cid IN ({$cids_string}) ";
}
if (!empty($like_ids)) {
$cid = array_shift($like_ids);
if (empty($cids)) {
$query .= " WHERE ";
}
else {
$query .= "OR ";
}
$query .= "cid LIKE '{$cid}' ";
foreach ($like_ids as $cid) {
$query .= "OR cid LIKE '{$cid}' ";
}
}
}
$queries[] = array(
$query,
$bin,
$cids,
);
}
if (variable_get('apdqc_use_union_query', APDQC_USE_UNION_QUERY)) {
$query = '';
$all_cids = array();
$tables = array();
foreach ($queries as $values) {
$tables[] = $values[1];
$all_cids = array_merge($all_cids, $values[2]);
if (!empty($query)) {
$query .= "\nUNION ALL ";
}
$query .= $values[0];
}
apdqc_query($tables, $all_cids, $query, array(
'async' => TRUE,
'log' => $log,
));
}
else {
foreach ($queries as $values) {
apdqc_query(array(
$values[1],
), $values[2], $values[0], array(
'async' => TRUE,
'log' => $log,
));
}
}
}
function apdqc_fast_prefix_tables($sql) {
$db_prefix = isset($GLOBALS['databases']['default']['default']['prefix']) ? $GLOBALS['databases']['default']['default']['prefix'] : '';
if (is_array($db_prefix)) {
if (array_key_exists('default', $db_prefix)) {
$tmp = $db_prefix;
unset($tmp['default']);
foreach ($tmp as $key => $val) {
$sql = strtr($sql, array(
"{{$key}}" => "`{$val}{$key}`",
));
}
return strtr($sql, array(
'{' => "`{$db_prefix['default']}",
'}' => '`',
));
}
else {
foreach ($db_prefix as $key => $val) {
$sql = strtr($sql, array(
"{{$key}}" => "`{$val}{$key}`",
));
}
return strtr($sql, array(
'{' => '`',
'}' => '`',
));
}
}
else {
return strtr($sql, array(
'{' => "`{$db_prefix}",
'}' => '`',
));
}
}
function apdqc_fast_get_db_type() {
return $GLOBALS['databases']['default']['default']['driver'];
}
function apdqc_fast_escape_table($string) {
return preg_replace('/[^A-Za-z0-9_]+/', '', $string);
}
function apdqc_get_bin_class_name($bin) {
static $cache_objects;
if (!isset($cache_objects[$bin])) {
$class = variable_get('cache_class_' . $bin);
if (!isset($class)) {
$class = variable_get('cache_default_class', 'DrupalDatabaseCache');
}
$cache_objects[$bin] = $class;
}
return $cache_objects[$bin];
}
function apdqc_inflate_unserialize(&$cache) {
$converted = FALSE;
if (is_array($cache)) {
$cache = (object) $cache;
$converted = TRUE;
}
if ($cache->serialized > 1) {
$inflate = @gzinflate($cache->data);
if ($inflate !== FALSE && $cache->data !== FALSE) {
$cache->data = $inflate;
}
$cache->serialized -= 2;
}
if ($cache->serialized) {
$data = @unserialize($cache->data);
if ($cache->data === 'b:0;' || $data !== FALSE) {
$cache->data = $data;
}
}
if ($converted) {
$cache = (array) $cache;
}
}
class APDQCache extends DrupalDatabaseCache {
protected $compressed;
protected $compression_level;
public function __construct($bin) {
$db_type = apdqc_fast_get_db_type();
if ($db_type === 'mysql' && defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 50300) {
require_once 'apdqc.mysql.inc';
}
$this->compressed = $this
->apdqcCacheCompressBin($bin);
if ($this->compressed) {
$this->compression_level = $this
->apdqcCacheCompressionLevelBin($bin);
}
parent::__construct($bin);
}
protected function apdqcCacheCompressBin($bin) {
$compress = variable_get('apdqc_cache_compress_' . $bin);
if (!isset($compress)) {
$compress = variable_get('apdqc_cache_default_compress', APDQC_CACHE_DEFAULT_COMPRESS);
}
return $compress;
}
protected function apdqcCacheCompressionLevelBin($bin) {
$compress = variable_get('apdqc_cache_compression_level_' . $bin);
if (!isset($compress)) {
$compress = variable_get('apdqc_cache_default_compression_level', APDQC_CACHE_DEFAULT_COMPRESSION_LEVEL);
}
return $compress;
}
public function getMultiple(&$cids) {
try {
$gc_frequency = variable_get('cache_garbage_collection_frequency', CACHE_GARBAGE_COLLECTION_FREQUENCY);
if (empty($gc_frequency)) {
$this
->garbageCollection($this->bin);
}
$query_cids = $cids;
$prefetch = array();
$result = array();
if (variable_get('cache_bootstrap_prefetch', CACHE_BOOTSTRAP_PREFETCH) || defined('APDQC_PREFETCH') && variable_get('apdqc_prefetch', APDQC_PREFETCH)) {
$prefetch = apdqc_async_data(FALSE, $this->bin, $cids);
$query_cids = array_diff($cids, array_keys($prefetch));
$prefetch = array_values($prefetch);
}
$result = array();
if (!empty($query_cids)) {
$query = Database::getConnection()
->prefixTables("SELECT cid, data, created, expire, serialized FROM {" . db_escape_table($this->bin) . "}");
if ($this->bin === 'cache_bootstrap' && $query_cids[0] === 'variables' && count($query_cids) == 1) {
$query_cids[] = 'bootstrap_modules';
$query_cids[] = 'lookup_cache';
$query_cids[] = 'system_list';
$query_cids[] = 'module_implements';
$bootstrap = TRUE;
}
foreach ($query_cids as $cid) {
$escaped_cids[] = apdqc_escape_string($cid);
}
$escaped_cids = "'" . implode("', '", $escaped_cids) . "'";
$query .= " WHERE cid IN ({$escaped_cids})";
$result = apdqc_query(array(
$this->bin,
), $query_cids, $query, array(
'fetch_all' => TRUE,
));
}
if (is_string($result) && $result === 'NO DB') {
$result = db_query('SELECT cid, data, created, expire, serialized FROM {' . db_escape_table($this->bin) . '} WHERE cid IN (:cids)', array(
':cids' => $cids,
));
}
else {
$result = array_merge($result, $prefetch);
}
$cache = array();
if (!empty($result)) {
foreach ($result as $item) {
if (empty($item)) {
continue;
}
if (!empty($bootstrap) && is_array($item) && ($item['cid'] === 'bootstrap_modules' || $item['cid'] === 'lookup_cache' || $item['cid'] === 'system_list' || $item['cid'] === 'module_implements')) {
$local_storage =& drupal_static('apdqc_async_data');
$item['bin'] = $this->bin;
$local_storage[$this->bin][$item['cid']] = $item;
continue;
}
if (is_array($item)) {
$item = (object) $item;
}
$item = $this
->prepareItem($item);
if ($item) {
$cache[$item->cid] = $item;
}
}
}
$cids = array_diff($cids, array_keys($cache));
return $cache;
} catch (Exception $e) {
return array();
}
}
protected function prepareItem($cache) {
if (!isset($cache->data)) {
return FALSE;
}
if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && isset($_SESSION['cache_expiration'][$this->bin]) && $_SESSION['cache_expiration'][$this->bin] > $cache->created) {
return FALSE;
}
apdqc_inflate_unserialize($cache);
return $cache;
}
protected function garbageCollection() {
$cache_lifetime = variable_get('cache_lifetime', 0);
$gc_frequency = variable_get('cache_garbage_collection_frequency', CACHE_GARBAGE_COLLECTION_FREQUENCY);
if (empty($gc_frequency)) {
$cache_lifetime = variable_get('cache_lifetime', 0);
if (isset($_SESSION['cache_expiration'])) {
$expire = REQUEST_TIME - $cache_lifetime;
foreach ($_SESSION['cache_expiration'] as $bin => $timestamp) {
if ($timestamp < $expire) {
unset($_SESSION['cache_expiration'][$bin]);
}
}
if (!$_SESSION['cache_expiration']) {
unset($_SESSION['cache_expiration']);
}
}
if (!$cache_lifetime) {
return;
}
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
if ($cache_flush && $cache_flush + $cache_lifetime <= REQUEST_TIME) {
variable_set('cache_flush_' . $this->bin, 0);
$query = Database::getConnection()
->prefixTables("DELETE FROM {" . db_escape_table($this->bin) . "} ");
$query .= "WHERE (expire <> " . CACHE_PERMANENT . " AND expire <= " . $cache_flush . ")";
$result = apdqc_query(array(
$this->bin,
), array(
'*',
), $query, array(
'async' => TRUE,
));
if (is_string($result) && $result === 'NO DB') {
return parent::garbageCollection();
}
}
}
else {
$query = Database::getConnection()
->prefixTables("DELETE FROM {" . db_escape_table($this->bin) . "} ");
if (!empty($cache_lifetime)) {
$query .= "WHERE (expire > " . CACHE_PERMANENT . " AND expire < " . REQUEST_TIME . ")";
$query .= "OR (expire = " . CACHE_TEMPORARY . " AND created < " . apdqc_escape_string(REQUEST_TIME - $cache_lifetime) . ")";
}
else {
$query .= "WHERE (expire <> " . CACHE_PERMANENT . " AND expire < " . REQUEST_TIME . ")";
}
$result = apdqc_query(array(
$this->bin,
), array(
'*',
), $query, array(
'async' => TRUE,
));
if (is_string($result) && $result === 'NO DB') {
if (!empty($cache_lifetime)) {
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '>')
->condition('expire', REQUEST_TIME, '<')
->execute();
db_delete($this->bin)
->condition('expire', CACHE_TEMPORARY)
->condition('created', REQUEST_TIME - $cache_lifetime, '<')
->execute();
}
else {
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
}
}
}
}
protected function cleanSession() {
$cache_lifetime = variable_get('cache_lifetime', 0);
if (isset($_SESSION['cache_expiration'])) {
$expire = REQUEST_TIME - $cache_lifetime;
foreach ($_SESSION['cache_expiration'] as $bin => $timestamp) {
if ($timestamp < $expire) {
unset($_SESSION['cache_expiration'][$bin]);
}
}
if (!$_SESSION['cache_expiration']) {
unset($_SESSION['cache_expiration']);
}
}
}
public function set($cid, $data, $expire = CACHE_PERMANENT) {
$gc_frequency = variable_get('cache_garbage_collection_frequency', CACHE_GARBAGE_COLLECTION_FREQUENCY);
if (!empty($gc_frequency)) {
$this
->cleanSession();
}
$fields = array(
'serialized' => 0,
'created' => REQUEST_TIME,
'expire' => $expire,
);
if (!is_string($data)) {
$fields['data'] = serialize($data);
$fields['serialized'] = 1;
}
else {
$fields['data'] = $data;
$fields['serialized'] = 0;
}
if ($this->compressed > 1 || $this->compressed && $fields['serialized']) {
$deflate = gzdeflate($fields['data'], $this->compression_level);
if (strlen($deflate) < strlen($fields['data'])) {
$fields['data'] = $deflate;
$fields['serialized'] += 2;
}
}
try {
$escaped_cid = apdqc_escape_string($cid);
$escaped_data = apdqc_escape_string($fields['data']);
$query = Database::getConnection()
->prefixTables("INSERT INTO {" . db_escape_table($this->bin) . "}");
$query .= " (cid, serialized, created, expire, data) VALUES ('{$escaped_cid}', '{$fields['serialized']}', '{$fields['created']}', '{$fields['expire']}', '{$escaped_data}')";
$query .= " ON DUPLICATE KEY UPDATE serialized = '{$fields['serialized']}', created = '{$fields['created']}', expire = '{$fields['expire']}', data = '{$escaped_data}'";
$result = apdqc_query(array(
$this->bin,
), array(
$cid,
), $query, array(
'async' => TRUE,
));
if (is_string($result) && $result === 'NO DB') {
return parent::set($cid, $data, $expire);
}
} catch (Exception $e) {
}
}
public function clear($cid = NULL, $wildcard = FALSE) {
if (variable_get('apdqc_call_hook_on_clear', APDQC_CALL_HOOK_ON_CLEAR)) {
$apdqc_cache_clear_alter = module_implements('apdqc_cache_clear_alter');
if (!empty($apdqc_cache_clear_alter)) {
$caller = $this
->getCaller();
$bin = $this->bin;
drupal_alter('apdqc_cache_clear', $cid, $wildcard, $bin, $caller);
}
}
if (Database::getConnection()
->inTransaction()) {
$apdqc_async_data =& drupal_static('apdqc_async_data');
$do_not_use_async_data =& drupal_static('apdqc_async_data_do_not_use_async');
$do_not_run_prefetch_array =& drupal_static('apdqc_run_prefetch_array');
$apdqc_async_data[$this->bin] = array();
$do_not_use_async_data[$this->bin] = TRUE;
$do_not_run_prefetch_array[$this->bin] = TRUE;
$output = parent::clear($cid, $wildcard);
$this
->callCacheClearHooks($cid, $wildcard);
return $output;
}
$gc_frequency = variable_get('cache_garbage_collection_frequency', CACHE_GARBAGE_COLLECTION_FREQUENCY);
$cids = array(
$cid,
);
if ($wildcard || is_null($cid)) {
$cids = array(
'*',
);
}
if (!empty($gc_frequency) && empty($cid)) {
$backtrace = debug_backtrace();
if ($backtrace[2]['function'] == 'system_cron') {
$cache_lifetime = variable_get('cache_lifetime', 0);
$name = 'cache_garbage_collect_' . $this->bin;
$window = max($cache_lifetime, $gc_frequency);
if (flood_is_allowed($name, 1, $window, 'cron')) {
$this
->garbageCollection();
flood_register_event($name, $window, 'cron');
}
}
else {
$this
->garbageCollection();
}
$this
->callCacheClearHooks($cid, $wildcard);
return;
}
if (is_null($cid)) {
$query = Database::getConnection()
->prefixTables("DELETE FROM {" . db_escape_table($this->bin) . "} ");
if (variable_get('cache_lifetime', 0)) {
$_SESSION['cache_expiration'][$this->bin] = REQUEST_TIME;
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
if ($cache_flush == 0) {
variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
}
elseif (REQUEST_TIME > $cache_flush + variable_get('cache_lifetime', 0)) {
$query .= "WHERE (expire <> " . CACHE_PERMANENT . " AND expire < " . REQUEST_TIME . ")";
$result = apdqc_query(array(
$this->bin,
), $cids, $query, array(
'async' => TRUE,
));
if (is_string($result) && $result === 'NO DB') {
$output = parent::clear($cid, $wildcard);
$this
->callCacheClearHooks($cid, $wildcard);
return $output;
}
variable_set('cache_flush_' . $this->bin, 0);
}
}
else {
$query .= "WHERE (expire <> " . CACHE_PERMANENT . " AND expire < " . REQUEST_TIME . ")";
$result = apdqc_query(array(
$this->bin,
), $cids, $query, array(
'async' => TRUE,
));
if (is_string($result) && $result === 'NO DB') {
$output = parent::clear($cid, $wildcard);
$this
->callCacheClearHooks($cid, $wildcard);
return $output;
}
}
}
else {
if ($wildcard) {
if ($cid == '*') {
if ($this
->isValidBin()) {
apdqc_truncate_table($this->bin);
}
else {
throw new Exception(t('Invalid or missing cache bin specified: %bin', array(
'%bin' => $this->bin,
)));
}
}
else {
$query = Database::getConnection()
->prefixTables("DELETE FROM {" . db_escape_table($this->bin) . "} ");
db_delete($this->bin)
->condition('cid', db_like($cid) . '%', 'LIKE')
->execute();
$escaped_cid = apdqc_escape_string($cid);
$query .= " WHERE cid LIKE '{$escaped_cid}%'";
$result = apdqc_query(array(
$this->bin,
), $cids, $query, array(
'async' => TRUE,
));
if (is_string($result) && $result === 'NO DB') {
$output = parent::clear($cid, $wildcard);
$this
->callCacheClearHooks($cid, $wildcard);
return $output;
}
}
}
elseif (is_array($cid)) {
$chunks = array_chunk($cid, 2000);
$last = array_pop($chunks);
$query = Database::getConnection()
->prefixTables("DELETE FROM {" . db_escape_table($this->bin) . "} ");
foreach ($chunks as $cids) {
$escaped_cids = array();
foreach ($cids as $id) {
$escaped_cids[] = apdqc_escape_string($id);
}
$escaped_cids = "'" . implode("', '", $escaped_cids) . "'";
$result = apdqc_query(array(
$this->bin,
), $cids, $query . " WHERE cid IN ({$escaped_cids})");
if (is_string($result) && $result === 'NO DB') {
$output = parent::clear($cid, $wildcard);
$this
->callCacheClearHooks($cid, $wildcard);
return $output;
}
}
$escaped_cids = array();
foreach ($last as $id) {
$escaped_cids[] = apdqc_escape_string($id);
}
$escaped_cids = "'" . implode("', '", $escaped_cids) . "'";
$result = apdqc_query(array(
$this->bin,
), $last, $query . " WHERE cid IN ({$escaped_cids})", array(
'async' => TRUE,
));
if (is_string($result) && $result === 'NO DB') {
$output = parent::clear($cid, $wildcard);
$this
->callCacheClearHooks($cid, $wildcard);
return $output;
}
}
else {
$query = Database::getConnection()
->prefixTables("DELETE FROM {" . db_escape_table($this->bin) . "} ");
$result = apdqc_query(array(
$this->bin,
), $cids, $query . " WHERE cid = '" . apdqc_escape_string($cid) . "'", array(
'async' => TRUE,
));
if (is_string($result) && $result === 'NO DB') {
$output = parent::clear($cid, $wildcard);
$this
->callCacheClearHooks($cid, $wildcard);
return $output;
}
}
}
$this
->callCacheClearHooks($cid, $wildcard);
}
public function isEmpty() {
$this
->garbageCollection();
$real_table_name = Database::getConnection()
->prefixTables("{" . db_escape_table($this->bin) . "}");
$result = apdqc_query(array(
$this->bin,
), array(), "SELECT TRUE FROM {$real_table_name} LIMIT 1");
if (is_string($result) && $result === 'NO DB') {
return parent::isEmpty();
}
if (!empty($result) && $result instanceof mysqli_result) {
$empty_table = $result
->fetch_row();
}
if (empty($result) || empty($empty_table)) {
return TRUE;
}
return FALSE;
}
public function isValidBin() {
return parent::isValidBin();
}
public function callCacheClearHooks($cid, $wildcard) {
if (!variable_get('apdqc_call_hook_on_clear', APDQC_CALL_HOOK_ON_CLEAR)) {
return;
}
$apdqc_cache_clear = module_implements('apdqc_cache_clear');
if (empty($apdqc_cache_clear)) {
return;
}
$caller = $this
->getCaller();
module_invoke_all('apdqc_cache_clear', $cid, $wildcard, $this->bin, $caller);
}
public function getCaller() {
$bt = debug_backtrace();
$caller = array();
foreach ($bt as $key => $call) {
if (!empty($call['class']) && $call['class'] == 'APDQCache') {
continue;
}
if (empty($call['function']) || $call['function'] == 'cache_clear_all') {
continue;
}
$caller[$key] = $call;
if (isset($bt[$key + 1])) {
$caller[$key + 1] = $bt[$key + 1];
if (isset($bt[$key + 2])) {
$caller[$key + 2] = $bt[$key + 2];
}
}
break;
}
return $caller;
}
}