You are here

public function APDQCache::clear in Asynchronous Prefetch Database Query Cache 7

Implements DrupalCacheInterface::clear().

Here, we ensure that cron only runs cache garbage collection at a configurable frequency, defaulting to 24 hours.

Overrides DrupalDatabaseCache::clear

File

./apdqc.cache.inc, line 623
Extends Drupal's default database cache so async queries happen.

Class

APDQCache
A pretty darn quick cache implementation of Drupal's default cache backend.

Code

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();

      // The bin variable is not alterable; use a copy.
      $bin = $this->bin;

      // Call hook_apdqc_cache_clear_alter().
      drupal_alter('apdqc_cache_clear', $cid, $wildcard, $bin, $caller);
    }
  }

  // Use default core logic for this cache clear if inside of a transaction.
  if (Database::getConnection()
    ->inTransaction()) {

    // Do not use any prefectched data.
    $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)) {

    // Use backtrace to check that the clear came from system_cron.
    $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)) {

    // Build query.
    $query = Database::getConnection()
      ->prefixTables("DELETE FROM {" . db_escape_table($this->bin) . "} ");
    if (variable_get('cache_lifetime', 0)) {

      // We store the time in the current user's session. We then simulate
      // that the cache was flushed for this user by not returning cached
      // data that was cached before the timestamp.
      $_SESSION['cache_expiration'][$this->bin] = REQUEST_TIME;
      $cache_flush = variable_get('cache_flush_' . $this->bin, 0);
      if ($cache_flush == 0) {

        // This is the first request to clear the cache, start a timer.
        variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
      }
      elseif (REQUEST_TIME > $cache_flush + variable_get('cache_lifetime', 0)) {

        // Clear the cache for everyone, cache_lifetime seconds have
        // passed since the first request to clear the cache.
        $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') {

          // Use core connection if the additional connection to mysql fails.
          $output = parent::clear($cid, $wildcard);
          $this
            ->callCacheClearHooks($cid, $wildcard);
          return $output;
        }
        variable_set('cache_flush_' . $this->bin, 0);
      }
    }
    else {

      // No minimum cache lifetime, flush all temporary cache entries now.
      $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') {

        // Use core connection if an additional connection to mysql fails.
        $output = parent::clear($cid, $wildcard);
        $this
          ->callCacheClearHooks($cid, $wildcard);
        return $output;
      }
    }
  }
  else {
    if ($wildcard) {
      if ($cid == '*') {

        // Check if $this->bin is a cache table before truncating. Other
        // cache_clear_all() operations throw a PDO error in this situation,
        // so we don't need to verify them first. This ensures that non-cache
        // tables cannot be truncated accidentally.
        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 {

        // Build query.
        $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}%'";

        // Run an async query.
        $result = apdqc_query(array(
          $this->bin,
        ), $cids, $query, array(
          'async' => TRUE,
        ));
        if (is_string($result) && $result === 'NO DB') {

          // Use core connection if an additional connection to mysql fails.
          $output = parent::clear($cid, $wildcard);
          $this
            ->callCacheClearHooks($cid, $wildcard);
          return $output;
        }
      }
    }
    elseif (is_array($cid)) {

      // Delete in chunks when a large array is passed.
      $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) . "'";

        // Run query.
        $result = apdqc_query(array(
          $this->bin,
        ), $cids, $query . " WHERE cid IN ({$escaped_cids})");
        if (is_string($result) && $result === 'NO DB') {

          // Use core connection if an additional connection to mysql fails.
          $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) . "'";

      // Run last one as an async query.
      $result = apdqc_query(array(
        $this->bin,
      ), $last, $query . " WHERE cid IN ({$escaped_cids})", array(
        'async' => TRUE,
      ));
      if (is_string($result) && $result === 'NO DB') {

        // Use core connection if an additional connection to mysql fails.
        $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') {

        // Use core connection if an additional connection to mysql fails.
        $output = parent::clear($cid, $wildcard);
        $this
          ->callCacheClearHooks($cid, $wildcard);
        return $output;
      }
    }
  }
  $this
    ->callCacheClearHooks($cid, $wildcard);
}