protected function Database::fieldsUpdated in Search API 8
Updates the storage tables when the field configuration changes.
Parameters
\Drupal\search_api\IndexInterface $index: The search index whose fields (might) have changed.
Return value
bool TRUE if the data needs to be reindexed, FALSE otherwise.
Throws
\Drupal\search_api\SearchApiException Thrown if any exceptions occur internally – for example, in the database layer.
3 calls to Database::fieldsUpdated()
- Database::addIndex in modules/
search_api_db/ src/ Plugin/ search_api/ backend/ Database.php - Adds a new index to this server.
- Database::indexItem in modules/
search_api_db/ src/ Plugin/ search_api/ backend/ Database.php - Indexes a single item on the specified index.
- Database::updateIndex in modules/
search_api_db/ src/ Plugin/ search_api/ backend/ Database.php - Notifies the server that an index attached to it has been changed.
File
- modules/
search_api_db/ src/ Plugin/ search_api/ backend/ Database.php, line 933
Class
- Database
- Indexes and searches items using the database.
Namespace
Drupal\search_api_db\Plugin\search_api\backendCode
protected function fieldsUpdated(IndexInterface $index) {
try {
$db_info = $this
->getIndexDbInfo($index);
$fields =& $db_info['field_tables'];
$new_fields = $index
->getFields();
$new_fields += $this
->getSpecialFields($index);
$reindex = FALSE;
$cleared = FALSE;
$text_table = NULL;
$denormalized_table = $db_info['index_table'];
foreach ($fields as $field_id => $field) {
$was_text_type = $this
->getDataTypeHelper()
->isTextType($field['type']);
if (!isset($text_table) && $was_text_type) {
// Stash the shared text table name for the index.
$text_table = $field['table'];
}
if (!isset($new_fields[$field_id])) {
// The field is no longer in the index, drop the data.
$this
->removeFieldStorage($field_id, $field, $denormalized_table);
unset($fields[$field_id]);
continue;
}
$old_type = $field['type'];
$new_type = $new_fields[$field_id]
->getType();
$fields[$field_id]['type'] = $new_type;
$fields[$field_id]['boost'] = $new_fields[$field_id]
->getBoost();
if ($old_type != $new_type) {
$is_text_type = $this
->getDataTypeHelper()
->isTextType($new_type);
if ($was_text_type || $is_text_type) {
// A change in fulltext status necessitates completely clearing the
// index.
$reindex = TRUE;
if (!$cleared) {
$cleared = TRUE;
$this
->deleteAllIndexItems($index);
}
$this
->removeFieldStorage($field_id, $field, $denormalized_table);
// Keep the table in $new_fields to create the new storage.
continue;
}
elseif ($this
->sqlType($old_type) != $this
->sqlType($new_type)) {
// There is a change in SQL type. We don't have to clear the index,
// since types can be converted.
$sql_spec = $this
->sqlType($new_type);
$sql_spec += [
'description' => "The field's value for this item",
];
$this->database
->schema()
->changeField($denormalized_table, $field['column'], $field['column'], $sql_spec);
$sql_spec['not null'] = TRUE;
$this->database
->schema()
->changeField($field['table'], 'value', 'value', $sql_spec);
$reindex = TRUE;
}
elseif ($old_type == 'date' || $new_type == 'date') {
// Even though the SQL type stays the same, we have to reindex since
// conversion rules change.
$reindex = TRUE;
}
}
elseif ($was_text_type && $field['boost'] != $new_fields[$field_id]
->getBoost()) {
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[$field_id]
->getBoost() / $field['boost'];
// Postgres doesn't allow multiplying an integer column with a
// float literal, so we have to work around that.
$expression = 'score * :mult';
$args = [
':mult' => $multiplier,
];
if (is_float($multiplier) && ($pos = strpos("{$multiplier}", '.'))) {
$expression .= ' / :div';
$after_point_digits = strlen("{$multiplier}") - $pos - 1;
$args[':div'] = pow(10, min(3, $after_point_digits));
$args[':mult'] = (int) round($args[':mult'] * $args[':div']);
}
$this->database
->update($text_table)
->expression('score', $expression, $args)
->condition('field_name', static::getTextFieldName($field_id))
->execute();
}
else {
$reindex = TRUE;
}
}
}
// Make sure the table and column now exist. (Especially important when
// we actually add the index for the first time.)
$storage_exists = empty($field['table']) || $this->database
->schema()
->fieldExists($field['table'], 'value');
$denormalized_storage_exists = $this->database
->schema()
->fieldExists($denormalized_table, $field['column']);
if (!$was_text_type && !$storage_exists) {
$db = [
'table' => $field['table'],
];
$this
->createFieldTable($new_fields[$field_id], $db);
}
// Ensure that a column is created in the denormalized storage even for
// 'text' fields.
if (!$denormalized_storage_exists) {
$db = [
'table' => $denormalized_table,
'column' => $field['column'],
];
$this
->createFieldTable($new_fields[$field_id], $db, 'index');
}
unset($new_fields[$field_id]);
}
$prefix = 'search_api_db_' . $index
->id();
// These are new fields that were previously not indexed.
foreach ($new_fields as $field_id => $field) {
$reindex = TRUE;
$fields[$field_id] = [];
if ($this
->getDataTypeHelper()
->isTextType($field
->getType())) {
if (!isset($text_table)) {
// If we have not encountered a text table, assign a name for it.
$text_table = $this
->findFreeTable($prefix . '_', 'text');
}
$fields[$field_id]['table'] = $text_table;
}
else {
$fields[$field_id]['table'] = $this
->findFreeTable($prefix . '_', $field_id);
$this
->createFieldTable($field, $fields[$field_id]);
}
// Always add a column in the denormalized table.
$fields[$field_id]['column'] = $this
->findFreeColumn($denormalized_table, $field_id);
$this
->createFieldTable($field, [
'table' => $denormalized_table,
'column' => $fields[$field_id]['column'],
], 'index');
$fields[$field_id]['type'] = $field
->getType();
$fields[$field_id]['boost'] = $field
->getBoost();
}
// If needed, make sure the text table exists.
if (isset($text_table) && !$this->database
->schema()
->tableExists($text_table)) {
$table = [
'name' => $text_table,
'module' => 'search_api_db',
'fields' => [
'item_id' => [
'type' => 'varchar',
'length' => 150,
'description' => 'The primary identifier of the item',
'not null' => TRUE,
],
'field_name' => [
'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' => 191,
],
'word' => [
'description' => 'The text of the indexed token',
'type' => 'varchar',
'length' => 50,
'not null' => TRUE,
'binary' => TRUE,
],
'score' => [
'description' => 'The score associated with this token',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
],
],
'indexes' => [
'word_field' => [
[
'word',
20,
],
'field_name',
],
],
// Add a covering index since word is not repeated for each item.
'primary key' => [
'item_id',
'field_name',
'word',
],
];
$this->database
->schema()
->createTable($text_table, $table);
$this->dbmsCompatibility
->alterNewTable($text_table, 'text');
}
$this
->getKeyValueStore()
->set($index
->id(), $db_info);
return $reindex;
} catch (\Exception $e) {
throw new SearchApiException($e
->getMessage(), $e
->getCode(), $e);
}
}