You are here

search_api_db.install in Search API Database Search 7

File

search_api_db.install
View source
<?php

/**
 * Implements hook_uninstall().
 */
function search_api_db_uninstall() {
  variable_del('search_api_db_autocomplete_max_occurrences');
}

/**
 * Implements hook_update_dependencies().
 */
function search_api_db_update_dependencies() {

  // This update should run after primary IDs have been changed to machine names
  // in the framework.
  $dependencies['search_api_db'][7101] = array(
    'search_api' => 7102,
  );
  return $dependencies;
}

/**
 * Replace numeric index IDs with machine names in the server options.
 */
function search_api_db_update_7101() {
  $query = db_select('search_api_server', 's');
  $query
    ->addField('s', 'machine_name');
  $query
    ->condition('class', 'search_api_db_service');
  $index_names = db_select('search_api_index', 'i')
    ->fields('i', array(
    'id',
    'machine_name',
  ))
    ->condition('server', clone $query, 'IN')
    ->execute()
    ->fetchAllKeyed();
  $query
    ->addField('s', 'options');
  $servers = $query
    ->execute();
  foreach ($servers
    ->fetchAllKeyed() as $name => $options) {
    $options = unserialize($options);
    if (empty($options['indexes'])) {
      continue;
    }
    $indexes = array();
    foreach ($options['indexes'] as $id => $info) {
      if (isset($index_names[$id])) {
        $indexes[$index_names[$id]] = $info;
      }
    }
    $options['indexes'] = $indexes;
    $options = serialize($options);
    db_update('search_api_server')
      ->fields(array(
      'options' => $options,
    ))
      ->condition('machine_name', $name)
      ->execute();
  }
}

/**
 * Solve index problems with non-ASCII characters on MySQL servers.
 */
function search_api_db_update_7102() {
  global $databases;
  $server_options = db_select('search_api_server', 's')
    ->fields('s', array(
    'options',
  ))
    ->condition('class', 'search_api_db_service')
    ->execute()
    ->fetchCol();
  foreach ($server_options as $options) {
    $options = unserialize($options);
    list($key, $target) = explode(':', $options['database'], 2);
    $db_driver = $databases[$key][$target]['driver'];
    if ($db_driver === 'mysql' && !empty($options['indexes'])) {
      $prev_db = db_set_active($key);
      foreach ($options['indexes'] as $fields) {
        foreach ($fields as $field) {
          db_query("ALTER TABLE {{$field['table']}} CONVERT TO CHARACTER SET 'utf8' COLLATE 'utf8_bin'", array(), array(
            'target' => $target,
          ));
        }
      }
      db_set_active($prev_db);
    }
  }
}

/**
 * Change date fields from int to big int.
 *
 * The purpose is to support historical dates.
 */
function search_api_db_update_7103() {
  $server_options = db_select('search_api_server', 's')
    ->fields('s', array(
    'options',
  ))
    ->condition('class', 'search_api_db_service')
    ->execute()
    ->fetchCol();
  $spec = array(
    'type' => 'int',
    'size' => 'big',
  );
  foreach ($server_options as $options) {
    $options = unserialize($options);
    if (!empty($options['indexes'])) {
      list($key, $target) = explode(':', $options['database'], 2);
      $connection = Database::getConnection($target, $key);
      foreach ($options['indexes'] as $fields) {
        foreach ($fields as $field) {
          if ($field['type'] == 'date') {
            $column = !empty($field['column']) ? $field['column'] : 'value';
            $connection
              ->schema()
              ->changeField($field['table'], $column, $column, $spec);
          }
        }
      }
    }
  }
}

/**
 * Use a single full text table per index.
 */
function search_api_db_update_7104() {
  $servers_query = db_select('search_api_server', 's')
    ->condition('s.class', 'search_api_db_service');
  $servers_query
    ->innerJoin('search_api_index', 'i', 'i.server = s.machine_name');
  $servers_query
    ->fields('s', array(
    'options',
  ));
  $servers_query
    ->fields('i', array(
    'server',
    'machine_name',
    'item_type',
  ));
  $servers = $servers_query
    ->execute();
  $server_options = array();
  foreach ($servers as $server) {
    if (!isset($server_options[$server->server])) {
      $server_options[$server->server] = unserialize($server->options);
    }
    $options = $server_options[$server->server];
    list($key, $target) = explode(':', $options['database'], 2);
    if (!empty($options['indexes'][$server->machine_name])) {
      $connection = Database::getConnection($target, $key);
      $schema = $connection
        ->schema();
      $text_table = NULL;

      // Migrate the index's fulltext data.
      foreach ($options['indexes'][$server->machine_name] as $name => $field) {
        if (search_api_is_text_type($field['type'])) {
          $field_name = strlen($name) > 255 ? md5($name) : $name;
          if (!isset($text_table)) {

            // Find a free name for the new table.
            $text_table = _search_api_db_update_find_free_table('search_api_db_' . $server->machine_name . '_text', $connection);

            // To avoid having to look up the index's ID field type, we just
            // rename the first fulltext table we come across and add a
            // "field_name" column.
            $schema
              ->renameTable($field['table'], $text_table);

            // First, set a default for the field for the existing data, then
            // remove it for future inserts.
            $spec = array(
              'description' => "The name of the field in which the token appears, or an MD5 hash of the field.",
              'not null' => TRUE,
              'type' => 'varchar',
              'length' => 255,
              'default' => $field_name,
            );
            $schema
              ->addField($text_table, 'field_name', $spec);
            unset($spec['default']);
            $schema
              ->changeField($text_table, 'field_name', 'field_name', $spec);

            // Finally, replace the primary key and "word" index with new ones.
            $schema
              ->dropIndex($text_table, 'word');
            $schema
              ->dropPrimaryKey($text_table);
            $schema
              ->addIndex($text_table, 'word_field', array(
              array(
                'word',
                20,
              ),
              'field_name',
            ));
            $schema
              ->addPrimaryKey($text_table, array(
              'item_id',
              'field_name',
              'word',
            ));
          }
          else {

            // Move the fulltext data to the new combined fulltext table, then
            // drop the old table.
            $query = $connection
              ->select($field['table'], 't')
              ->fields('t', array(
              'item_id',
              'word',
              'score',
            ));
            $query
              ->addExpression(':field_name', 'field_name', array(
              ':field_name' => $field_name,
            ));
            $connection
              ->insert($text_table)
              ->from($query)
              ->execute();
            $schema
              ->dropTable($field['table']);
          }
          $server_options[$server->server]['indexes'][$server->machine_name][$name]['table'] = $text_table;
        }
      }
    }
  }

  // Save changes to fields.
  foreach ($server_options as $server => $options) {
    db_update('search_api_server')
      ->condition('machine_name', $server)
      ->fields(array(
      'options' => serialize($options),
    ))
      ->execute();
  }
}

/**
 * Add a (word, field_name) covering index to fulltext tables.
 */
function search_api_db_update_7105() {

  // Get a connection for each service using Search API DB.
  $servers_query = db_select('search_api_server', 's')
    ->condition('s.class', 'search_api_db_service');
  $servers_query
    ->innerJoin('search_api_index', 'i', 'i.server = s.machine_name');
  $servers_query
    ->fields('s', array(
    'options',
  ));
  $servers_query
    ->fields('i', array(
    'server',
    'machine_name',
    'item_type',
  ));
  $servers = $servers_query
    ->execute();
  $server_options = array();
  foreach ($servers as $server) {
    if (!isset($server_options[$server->server])) {
      $server_options[$server->server] = unserialize($server->options);
    }
    $options = $server_options[$server->server];
    list($key, $target) = explode(':', $options['database'], 2);
    if (!empty($options['indexes'][$server->machine_name])) {
      $connection = Database::getConnection($target, $key);

      // Find name of text table.
      foreach ($options['indexes'][$server->machine_name] as $field) {
        if (search_api_is_text_type($field['type'])) {

          // If the word index exists, replace it with word_field.
          if ($connection
            ->schema()
            ->indexExists($field['table'], 'word')) {
            $connection
              ->schema()
              ->dropIndex($field['table'], 'word');
            $connection
              ->schema()
              ->addIndex($field['table'], 'word_field', array(
              array(
                'word',
                20,
              ),
              'field_name',
            ));
          }
          break;
        }
      }
    }
  }
}

/**
 * Change full text score from float to int.
 */
function search_api_db_update_7106() {

  // Get a connection for each service using Search API DB.
  $servers_query = db_select('search_api_server', 's')
    ->condition('s.class', 'search_api_db_service');
  $servers_query
    ->innerJoin('search_api_index', 'i', 'i.server = s.machine_name');
  $servers_query
    ->fields('s', array(
    'options',
  ));
  $servers_query
    ->fields('i', array(
    'server',
    'machine_name',
    'item_type',
  ));
  $servers = $servers_query
    ->execute();
  $server_options = array();
  foreach ($servers as $server) {
    if (!isset($server_options[$server->server])) {
      $server_options[$server->server] = unserialize($server->options);
    }
    $options = $server_options[$server->server];
    list($key, $target) = explode(':', $options['database'], 2);
    if (!empty($options['indexes'][$server->machine_name])) {
      $connection = Database::getConnection($target, $key);

      // Find name of text table.
      foreach ($options['indexes'][$server->machine_name] as $field) {
        if (search_api_is_text_type($field['type'])) {
          $spec = array(
            'description' => 'The score associated with this token.',
            'type' => 'int',
            'unsigned' => TRUE,
            'not null' => TRUE,
            'default' => 0,
          );

          // Add new field and populate data.
          $connection
            ->schema()
            ->addField($field['table'], 'score_int', $spec);
          $connection
            ->update($field['table'])
            ->expression('score_int', 'score * 1000')
            ->execute();

          // Drop old column and move into place.
          $connection
            ->schema()
            ->dropField($field['table'], 'score');
          $connection
            ->schema()
            ->changeField($field['table'], 'score_int', 'score', $spec);
          break;
        }
      }
    }
  }
}

/**
 * Eliminates the use of low-standard hashes.
 */
function search_api_db_update_7107() {
  $spec = array(
    'description' => "The name of the field in which the token appears, or a base-64 encoded sha-256 hash of the field.",
    'not null' => TRUE,
    'type' => 'varchar',
    'length' => 255,
  );
  $server_options = db_select('search_api_server', 's')
    ->fields('s', array(
    'id',
    'options',
  ))
    ->condition('class', 'search_api_db_service')
    ->execute()
    ->fetchAllKeyed();
  foreach ($server_options as $id => $options) {
    $options = unserialize($options);
    if (!empty($options['indexes'])) {
      list($key, $target) = explode(':', $options['database'], 2);
      $connection = Database::getConnection($target, $key);
      foreach ($options['indexes'] as $index_id => $fields) {
        $text_table = NULL;
        foreach ($fields as $field_id => $field) {
          if (search_api_is_text_type($field['type'])) {
            $text_table = $field['table'];
            if (strlen($field_id) > 255) {
              $connection
                ->update($text_table)
                ->fields(array(
                'field_name' => drupal_hash_base64($field_id),
              ))
                ->condition('field_name', md5($field_id))
                ->execute();
            }
          }
        }

        // If there is a text table for this index, update its description.
        if ($text_table && db_table_exists($text_table)) {
          $connection
            ->schema()
            ->changeField($text_table, 'field_name', 'field_name', $spec);
        }
      }
    }
  }
}

/**
 * Finds a free table name within the given database.
 *
 * Slightly modified copy of SearchApiDbService::findFreeTable().
 *
 * @param string $table
 *   The base table name to use, if available, or to modify otherwise.
 * @param DatabaseConnection $connection
 *   The database in which the table will be created.
 *
 * @return string
 *   A free table in the given database.
 */
function _search_api_db_update_find_free_table($table, DatabaseConnection $connection) {

  // A DB prefix might further reduce the maximum length of the table name.
  $maxbytes = 62;
  if ($db_prefix = $connection
    ->tablePrefix()) {

    // Use strlen instead of drupal_strlen since we want to measure bytes
    // instead of characters.
    $maxbytes -= strlen($db_prefix);
  }
  $base = $table = _search_api_db_mb_strcut($table, 0, $maxbytes);
  $i = 0;
  while ($connection
    ->schema()
    ->tableExists($table)) {
    $suffix = '_' . ++$i;
    $table = _search_api_db_mb_strcut($base, 0, $maxbytes - strlen($suffix)) . $suffix;
  }
  return $table;
}

/**
 * Emulates mb_strcut() if that is not available.
 *
 * Though the Mbstring PHP extension is recommended for running Drupal, it is
 * not required. Therefore, we have to wrap calls to its functions.
 *
 * @param string $str
 *   The string being cut.
 * @param int $start
 *   Starting position in bytes.
 * @param int|null $length
 *   (optional) Length in bytes. If NULL is passed, extract all bytes to the
 *   end of the string.
 *
 * @return string
 *   The portion of $str specified by the $start and $length parameters.
 */
function _search_api_db_mb_strcut($str, $start, $length = NULL) {
  if (function_exists('mb_strcut')) {
    return mb_strcut($str, $start, $length);
  }
  return substr($str, $start, $length);
}

Functions

Namesort descending Description
search_api_db_uninstall Implements hook_uninstall().
search_api_db_update_7101 Replace numeric index IDs with machine names in the server options.
search_api_db_update_7102 Solve index problems with non-ASCII characters on MySQL servers.
search_api_db_update_7103 Change date fields from int to big int.
search_api_db_update_7104 Use a single full text table per index.
search_api_db_update_7105 Add a (word, field_name) covering index to fulltext tables.
search_api_db_update_7106 Change full text score from float to int.
search_api_db_update_7107 Eliminates the use of low-standard hashes.
search_api_db_update_dependencies Implements hook_update_dependencies().
_search_api_db_mb_strcut Emulates mb_strcut() if that is not available.
_search_api_db_update_find_free_table Finds a free table name within the given database.