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'])) { 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) { db_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_change_field($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); }