public function SearchApiDbService::fieldsUpdated in Search API Database Search 7
Overrides SearchApiAbstractService::fieldsUpdated().
Internally, this is also used by addIndex().
Overrides SearchApiAbstractService::fieldsUpdated
2 calls to SearchApiDbService::fieldsUpdated()
- SearchApiDbService::addIndex in ./
service.inc - Implements SearchApiServiceInterface::__construct().
- SearchApiDbService::indexItem in ./
service.inc - Indexes a single item on the specified index.
File
- ./
service.inc, line 506 - Contains SearchApiDbService.
Class
- SearchApiDbService
- Indexes and searches items using the database.
Code
public function fieldsUpdated(SearchApiIndex $index) {
try {
$fields =& $this->options['indexes'][$index->machine_name];
$new_fields = $index
->getFields();
$reindex = FALSE;
$cleared = FALSE;
$change = FALSE;
$text_table = NULL;
$missing_text_tables = array();
foreach ($fields as $name => $field) {
if (!isset($text_table) && search_api_is_text_type($field['type'])) {
// Stash the shared text table name for the index, if it exists.
// Otherwise, there was some error previously and we have to remember
// to later come back and set the correct table here.
if ($this->connection
->schema()
->tableExists($field['table'])) {
$text_table = $field['table'];
}
else {
$missing_text_tables[$name] = $name;
}
}
if (!isset($new_fields[$name])) {
// The field is no longer in the index, drop the data.
$this
->removeFieldStorage($name, $field);
unset($fields[$name]);
$change = TRUE;
continue;
}
$old_type = $field['type'];
$new_type = $new_fields[$name]['type'];
$fields[$name]['type'] = $new_type;
$fields[$name]['boost'] = $new_fields[$name]['boost'];
$old_inner_type = search_api_extract_inner_type($old_type);
$new_inner_type = search_api_extract_inner_type($new_type);
if ($old_type != $new_type) {
$change = TRUE;
$list_old = (bool) search_api_list_nesting_level($old_type);
$list_new = (bool) search_api_list_nesting_level($new_type);
if ($old_inner_type == 'text' || $new_inner_type == 'text' || $list_old != $list_new) {
// A change in fulltext or list status necessitates completely
// clearing the index.
$reindex = TRUE;
if (!$cleared) {
$cleared = TRUE;
$this
->deleteItems('all', $index);
}
$this
->removeFieldStorage($name, $field);
// Keep the table in $new_fields to create the new storage.
continue;
}
elseif ($this
->sqlType($old_inner_type) != $this
->sqlType($new_inner_type)) {
// There is a change in SQL type. We don't have to clear the index,
// since types can be converted.
$column = isset($field['column']) ? $field['column'] : 'value';
$this->connection
->schema()
->changeField($field['table'], $column, $column, $this
->sqlType($new_type) + array(
'description' => "The field's value for this item.",
));
$reindex = TRUE;
}
elseif ($old_inner_type == 'date' || $new_inner_type == 'date') {
// Even though the SQL type stays the same, we have to reindex since
// conversion rules change.
$reindex = TRUE;
}
}
elseif ($text_table && $new_inner_type == 'text' && $field['boost'] != $new_fields[$name]['boost']) {
$change = TRUE;
if (!$reindex) {
// If there was a non-zero boost set previously, we can just update
// all scores with a single UPDATE query. Otherwise, no way around
// re-indexing.
if ($field['boost']) {
$multiplier = $new_fields[$name]['boost'] / $field['boost'];
$this->connection
->update($text_table)
->expression('score', 'score * :mult', array(
':mult' => $multiplier,
))
->condition('field_name', self::getTextFieldName($name))
->execute();
}
else {
$reindex = TRUE;
}
}
}
// Make sure the table and column now exist. (Especially important when
// we actually add the index for the first time.)
if (!search_api_is_text_type($field['type'])) {
$storageExists = $this->connection
->schema()
->tableExists($field['table']) && (!isset($field['column']) || $this->connection
->schema()
->fieldExists($field['table'], $field['column']));
if (!$storageExists) {
$this
->createFieldTable($index, $new_fields[$name], $field);
}
}
elseif ($text_table && $fields[$name]['table'] != $text_table) {
$fields[$name]['table'] = $text_table;
$change = TRUE;
}
unset($new_fields[$name]);
}
$prefix = 'search_api_db_' . $index->machine_name;
// These are new fields that were previously not indexed.
foreach ($new_fields as $name => $field) {
$reindex = TRUE;
if (search_api_is_text_type($field['type'])) {
if (!isset($text_table)) {
// If we have not encountered a text table, assign a name for it.
$text_table = $this
->findFreeTable($prefix . '_', 'text');
}
$fields[$name] = array(
'table' => $text_table,
);
}
else {
if ($this
->canDenormalize($field)) {
$fields[$name] = array(
'table' => $prefix,
'column' => $this
->findFreeColumn($prefix, $name),
);
}
else {
$fields[$name] = array(
'table' => $this
->findFreeTable($prefix . '_', $name),
);
}
$this
->createFieldTable($index, $field, $fields[$name]);
}
$fields[$name]['type'] = $field['type'];
$fields[$name]['boost'] = $field['boost'];
$change = TRUE;
}
// If there were fulltext fields without valid table set, set it now.
if ($missing_text_tables) {
if (!isset($text_table)) {
// If we have not encountered a text table, assign a name for it.
$text_table = $this
->findFreeTable($prefix . '_', 'text');
}
foreach ($missing_text_tables as $name) {
$fields[$name]['table'] = $text_table;
}
}
// If needed, make sure the text table exists.
if (isset($text_table) && !$this->connection
->schema()
->tableExists($text_table)) {
$table = array(
'name' => $text_table,
'module' => 'search_api_db',
'fields' => array(
'item_id' => array(
'description' => 'The primary identifier of the item.',
'not null' => TRUE,
),
'field_name' => 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,
),
'word' => array(
'description' => 'The text of the indexed token.',
'type' => 'varchar',
'length' => 50,
'not null' => TRUE,
),
'score' => array(
'description' => 'The score associated with this token.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
),
'indexes' => array(
'word_field' => array(
array(
'word',
20,
),
'field_name',
),
),
// Add a covering index since word is not repeated for each item.
'primary key' => array(
'item_id',
'field_name',
'word',
),
);
// The type of the item_id field depends on the ID field's type.
$id_field = $index
->datasource()
->getIdFieldInfo();
$table['fields']['item_id'] += $this
->sqlType($id_field['type'] == 'text' ? 'string' : $id_field['type']);
if (isset($table['fields']['item_id']['length'])) {
// A length of 255 is overkill for IDs. 50 should be more than enough.
$table['fields']['item_id']['length'] = 50;
}
$this->connection
->schema()
->createTable($text_table, $table);
// Some DBMSs will need a character encoding and collation set. Since
// this largely circumvents Drupal's database layer (but isn't integral
// enough to fail completely when it doesn't work), we wrap it in a
// try/catch, to be on the safe side.
try {
switch ($this->connection
->databaseType()) {
case 'mysql':
$this->connection
->query("ALTER TABLE {{$text_table}} CONVERT TO CHARACTER SET 'utf8' COLLATE 'utf8_bin'");
break;
case 'pgsql':
$this->connection
->query("ALTER TABLE {{$text_table}} ALTER COLUMN word SET DATA TYPE character varying(50) COLLATE \"C\"");
break;
// @todo Add fixes for other DBMSs.
case 'oracle':
case 'sqlite':
case 'sqlsrv':
break;
}
} catch (PDOException $e) {
$vars['%index'] = $index->name;
watchdog_exception('search_api_db', $e, '%type while trying to change collation for the fulltext table of index %index: !message in %function (line %line of %file).', $vars);
}
}
if ($change) {
$this->server
->save();
}
return $reindex;
} catch (Exception $e) {
throw new SearchApiException($e
->getMessage());
}
}