Commit f0b1d39b authored by Andreas Hennings's avatar Andreas Hennings Committed by Andreas Hennings
Browse files

Issue #3162854 by donquixote, joelpittet, klausi: Move 'features_codecache' /...

Issue #3162854 by donquixote, joelpittet, klausi: Move 'features_codecache' / 'cache_featurestate' to a dedicated db table 'features_signatures'.
parent 46257ddb
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1453,7 +1453,7 @@ function features_admin_components_review(&$form, &$form_state) {
  $module = $form_state['values']['module'];
  $revert = array();
  foreach (array_filter($form_state['values']['revert']) as $component => $status) {
    features_set_signature($module, $component);
    features_set_signature($module, $component, NULL, 'review');
    drupal_set_message(t('All <strong>@component</strong> components for <strong>@module</strong> reviewed.', array('@component' => $component, '@module' => $module)));
  }
  $form_state['redirect'] = 'admin/structure/features/' . $module;
+143 −17
Original line number Diff line number Diff line
@@ -897,21 +897,52 @@ function features_get_storage($module_name) {
function features_get_signature($state = 'default', $module_name, $component, $reset = FALSE) {
  switch ($state) {
    case 'cache':
      // Load the last known stored signature from the database.
      switch (_features_get_signature_storage_type()) {
        case 'table':
          // The database is fully updated.
          // All signatures are stored in a dedicated database table.
          $qr = db_select('features_signature', 'fs')
            ->fields('fs', array('signature'))
            ->condition('module', $module_name)
            ->condition('component', $component)
            ->execute();
          return $qr ? $qr->fetchField() : FALSE;

        case 'cache':
          // The database is not fully updated, only to schema version 7201.
          // Signatures are stored in a cache table.
          $cache = cache_get('features_codecache', 'cache_featurestate');
      if (!empty($cache->data)) {
        $codecache = $cache->data;
          if (isset($cache->data[$module_name][$component])) {
            return $cache->data[$module_name][$component];
          }
          // No stored signature for this component.
          return FALSE;

        case 'variable':
        default:
          // The database is not fully updated, schema version before 7201.
          // Signatures are stored in a variable.
          $signaturess = variable_get('features_codecache', array());
          if (isset($signaturess[$module_name][$component])) {
            return $signaturess[$module_name][$component];
          }
          // No stored signature for this component.
          return FALSE;
      }
      return isset($codecache[$module_name][$component]) ? $codecache[$module_name][$component] : FALSE;

    case 'default':
      // Get the component data as currently in code.
      $objects = features_get_default($component, $module_name, TRUE, $reset);
      break;

    case 'normal':
      // Get the component data as currently in the database.
      $objects = features_get_normal($component, $module_name, $reset);
      break;
  }
  if (!empty($objects)) {
    // Build a signature hash from the component data.
    features_sanitize($objects, $component);
    return md5(_features_linetrim(features_var_export($objects)));
  }
@@ -919,23 +950,118 @@ function features_get_signature($state = 'default', $module_name, $component, $r
}

/**
 * Set the signature of a module/component pair in the codecache.
 * Updates a module/component signature in the database.
 *
 * The signature stored in the database reflects the last known state of the
 * component in code.
 *
 * @param string $module
 *   A module name.
 *   A feature module name.
 * @param string $component
 *   A component name, e.g. 'field_instance'.
 * @param string|null $signature
 *   An md5 signature, or NULL to auto-generate one.
 * @param string|null|false $signature
 *   An md5 signature, or NULL to generate one from the current state in code,
 *   or FALSE to delete the signature.
 * @param string|null $message
 *   (optional) Message to store along with the updated signature.
 */
function features_set_signature($module, $component, $signature = NULL) {
function features_set_signature($module, $component, $signature = NULL, $message = NULL) {

  if ($signature === NULL) {
    // Build signature from current state in code.
    $signature = features_get_signature('default', $module, $component, TRUE);
  }

  // Support un-updated databases.
  switch (_features_get_signature_storage_type()) {
    case 'table':
      // The database is fully updated.
      // All signatures are stored in a dedicated database table.
      if ($signature === FALSE) {
        // Delete the signature.
        db_delete('features_signature')
          ->condition('module', $module)
          ->condition('component', $component)
          ->execute();
      }
      else {
        // Insert or update the signature.
        db_merge('features_signature')
          ->key(array(
            'module' => $module,
            'component' => $component,
          ))
          ->fields(array(
            'signature' => $signature,
            'updated' => time(),
            'message' => $message,
          ))
          ->execute();
      }
      break;

    case 'cache':
      // The database is not fully updated, only to schema version 7201.
      // Signatures are stored in a cache table.
      $cache = cache_get('features_codecache', 'cache_featurestate');
      if (!empty($cache->data)) {
    $codecache = $cache->data;
        $signaturess = $cache->data;
      }
      $signaturess[$module][$component] = $signature;
      cache_set('features_codecache', $signaturess, 'cache_featurestate');
      break;

    case 'variable':
    default:
      // The database is not fully updated, schema version before 7201.
      // Signatures are stored in a variable.
      $signaturess = variable_get('features_codecache', array());
      $signaturess[$module][$component] = $signature;
      variable_set('features_codecache', $signaturess);
      break;
  }
}

/**
 * Gets the current storage type for features component signatures.
 *
 * This is needed to prevent breakage in a database that is not fully updated
 * yet, e.g. in deployment operations that run before the database update.
 *
 * The signatures used to be stored in a variable.
 * Since #1325288, it was stored in a cache table. This only applies to projects
 * that were using a -dev branch after 7.x-2.11.
 * Since #3162854, it is stored in a dedicated non-cache table.
 *
 * @param bool $reset
 *   TRUE, to reset the static cache.
 *
 * @return string
 *   One of 'table', 'cache' or 'type'.
 *   On a fully updated database, this value will be 'table'.
 *
 * @see \features_get_signature()
 * @see \features_set_signature()
 * @see \features_update_7202()
 */
function _features_get_signature_storage_type($reset = FALSE) {
  static $type;
  if ($reset) {
    $type = NULL;
  }
  if ($type !== NULL) {
    return $type;
  }
  if (db_table_exists('features_signature')) {
    $type = 'table';
  }
  elseif (db_table_exists('cache_featurestate')) {
    $type = 'cache';
  }
  else {
    $type = 'variable';
  }
  $signature = isset($signature) ? $signature : features_get_signature('default', $module, $component, TRUE);
  $codecache[$module][$component] = $signature;
  cache_set('features_codecache', $codecache, 'cache_featurestate');
  return $type;
}

/**
@@ -1256,7 +1382,7 @@ function features_get_component_states($features = array(), $rebuild_only = TRUE
            // Update code cache if it is stale, clear out semaphore if it is
            // stale.
            if ($default != $codecache) {
              features_set_signature($feature, $component, $default);
              features_set_signature($feature, $component, $default, __FUNCTION__ . '(): $normal === $default');
            }
          }
          // Component properly implements exportables.
+148 −6
Original line number Diff line number Diff line
@@ -11,8 +11,46 @@
function features_schema() {
  $schema['cache_features'] = drupal_get_schema_unprocessed('system', 'cache');
  $schema['cache_features']['description'] = 'Cache table for features to store module info.';
  $schema['cache_featurestate'] = drupal_get_schema_unprocessed('system', 'cache');
  $schema['cache_featurestate']['description'] = 'Table for features to store persistent state info.';
  $schema['features_signature'] = array(
    'description' => 'Stores hashes that reflect the last known state of a features component.',
    'fields' => array(
      'module' => array(
        'description' => 'Name of the feature module.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
      ),
      'component' => array(
        'description' => 'Name of the features component.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
      ),
      'signature' => array(
        'description' => 'Hash reflecting the last approved state of the component in code.',
        'type' => 'varchar',
        'length' => 128,
        'not null' => TRUE,
      ),
      'updated' => array(
        'description' => 'Timestamp when the signature was last updated.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'message' => array(
        'description' => 'Message to document why the component was updated.',
        'type' => 'varchar',
        'length' => 255,
        'not null' => FALSE,
      ),
    ),
    'primary key' => array('module', 'component'),
    'indexes' => array(
      'module' => array('module'),
      'component' => array('component'),
    ),
  );
  return $schema;
}

@@ -156,9 +194,113 @@ function features_update_7200() {
 * Add {cache_featurestate} table.
 */
function features_update_7201() {
  if (!db_table_exists('cache_featurestate')) {
  // This update hook is no longer active.
}

/**
 * Create a new table 'features_signature' to store signatures.
 */
function features_update_7202() {
  if (!db_table_exists('features_signature')) {
    // Create the new table for signatures.
    $schema = array(
      'description' => 'Stores hashes that reflect the last known state of a features component.',
      'fields' => array(
        'module' => array(
          'description' => 'Name of the feature module.',
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
        ),
        'component' => array(
          'description' => 'Name of the features component.',
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
        ),
        'signature' => array(
          'description' => 'Hash reflecting the last approved state of the component in code.',
          'type' => 'varchar',
          'length' => 128,
          'not null' => TRUE,
        ),
        'updated' => array(
          'description' => 'Timestamp when the signature was last updated.',
          'type' => 'int',
          'not null' => TRUE,
          'default' => 0,
        ),
        'message' => array(
          'description' => 'Message to document why the component was updated.',
          'type' => 'varchar',
          'length' => 255,
          'not null' => FALSE,
        ),
      ),
      'primary key' => array('module', 'component'),
      'indexes' => array(
        'module' => array('module'),
        'component' => array('component'),
      ),
    );
    db_create_table('features_signature', $schema);
  }


  // Load existing signatures.
  if (db_table_exists('cache_features')) {
    // The original version of the previous update has already run.
    $cache = cache_get('features_codecache', 'cache_featurestate');
    $signaturess = !empty($cache->data)
      ? $cache->data
      : array();
    $message = __FUNCTION__ . '() - from cache storage';
  }
  else {
    // The update is from an earlier version of features.
    $signaturess = variable_get('features_codecache', array());
    $message = __FUNCTION__ . '() - from variable storage';
  }

  // Prevent existing records from being inserted again.
  // This way we don't need a REPLACE query.
  // This only applies if the table was already created e.g. in a previous
  // failed attempt to run this update.
  $q = db_select('features_signature', 'fs')->fields('fs');
  if ($qr = $q->execute()) {
    foreach ($qr as $obj) {
      unset($signaturess[$obj->module][$obj->component]);
    }
  }

  // Get a timestamp to be stored in each record.
  $timestamp = time();

  // Build the insert query.
  $insert = db_insert('features_signature')
    ->fields(array('module', 'component', 'signature', 'udated', 'message'));

  foreach ($signaturess as $module => $signatures) {
    foreach ($signatures as $component => $signature) {
      $record = array(
        'module' => $module,
        'component' => $component,
        'signature' => $signature,
        'updated' => $timestamp,
        'message' => $message,
      );
      $insert->values($record);
    }
  }

  // Execute the insert query.
  // On failure, allow the exception to trickle up.
  $insert->execute();

  // Delete the old table and variable if the data migration was successful.
  variable_del('features_codecache');
    $schema = drupal_get_schema_unprocessed('system', 'cache');
    db_create_table('cache_featurestate', $schema);

  if (db_table_exists('cache_featurestate')) {
    db_drop_table('cache_featurestate');
  }
}
+1 −1
Original line number Diff line number Diff line
@@ -1236,7 +1236,7 @@ function _features_restore($op, $items = array()) {
        // If the script completes, remove the semaphore and set the code
        // signature.
        features_semaphore('del', $component);
        features_set_signature($module_name, $component);
        features_set_signature($module_name, $component, NULL, __FUNCTION__ . '(' . $log_action . ')');
        watchdog('features', '@action completed for @module_name / @component.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name));
      }