示例#1
0
 /**
  * Saves a link without clearing caches.
  *
  * @param array $link
  *   A definition, according to $definitionFields, for a
  *   \Drupal\Core\Menu\MenuLinkInterface plugin.
  *
  * @return array
  *   The menu names affected by the save operation. This will be one menu
  *   name if the link is saved to the sane menu, or two if it is saved to a
  *   new menu.
  *
  * @throws \Exception
  *   Thrown if the storage back-end does not exist and could not be created.
  * @throws \Drupal\Component\Plugin\Exception\PluginException
  *   Thrown if the definition is invalid, for example, if the specified parent
  *   would cause the links children to be moved to greater than the maximum
  *   depth.
  */
 protected function doSave(array $link)
 {
     $original = $this->loadFull($link['id']);
     // @todo Should we just return here if the link values match the original
     //   values completely?
     //   https://www.drupal.org/node/2302137
     $affected_menus = array();
     $transaction = $this->connection->startTransaction();
     try {
         if ($original) {
             $link['mlid'] = $original['mlid'];
             $link['has_children'] = $original['has_children'];
             $affected_menus[$original['menu_name']] = $original['menu_name'];
         } else {
             // Generate a new mlid.
             $options = array('return' => Database::RETURN_INSERT_ID) + $this->options;
             $link['mlid'] = $this->connection->insert($this->table, $options)->fields(array('id' => $link['id'], 'menu_name' => $link['menu_name']))->execute();
         }
         $fields = $this->preSave($link, $original);
         // We may be moving the link to a new menu.
         $affected_menus[$fields['menu_name']] = $fields['menu_name'];
         $query = $this->connection->update($this->table, $this->options);
         $query->condition('mlid', $link['mlid']);
         $query->fields($fields)->execute();
         if ($original) {
             $this->updateParentalStatus($original);
         }
         $this->updateParentalStatus($link);
     } catch (\Exception $e) {
         $transaction->rollback();
         throw $e;
     }
     return $affected_menus;
 }
 /**
  * {@inheritdoc}
  */
 public function setMultiple(array $items)
 {
     // Use a transaction so that the database can write the changes in a single
     // commit.
     $transaction = $this->connection->startTransaction();
     try {
         // Delete all items first so we can do one insert. Rather than multiple
         // merge queries.
         $this->deleteMultiple(array_keys($items));
         $query = $this->connection->insert($this->bin)->fields(array('cid', 'data', 'expire', 'created', 'serialized', 'tags', 'checksum'));
         foreach ($items as $cid => $item) {
             $item += array('expire' => CacheBackendInterface::CACHE_PERMANENT, 'tags' => array());
             Cache::validateTags($item['tags']);
             $item['tags'] = array_unique($item['tags']);
             // Sort the cache tags so that they are stored consistently in the DB.
             sort($item['tags']);
             $fields = array('cid' => $cid, 'expire' => $item['expire'], 'created' => round(microtime(TRUE), 3), 'tags' => implode(' ', $item['tags']), 'checksum' => $this->checksumProvider->getCurrentChecksum($item['tags']));
             if (!is_string($item['data'])) {
                 $fields['data'] = serialize($item['data']);
                 $fields['serialized'] = 1;
             } else {
                 $fields['data'] = $item['data'];
                 $fields['serialized'] = 0;
             }
             $query->values($fields);
         }
         $query->execute();
     } catch (\Exception $e) {
         $transaction->rollback();
         // @todo Log something here or just re throw?
         throw $e;
     }
 }
示例#3
0
 /**
  * Dumps a set of routes to the router table in the database.
  *
  * Available options:
  * - provider: The route grouping that is being dumped. All existing
  *   routes with this provider will be deleted on dump.
  * - base_class: The base class name.
  *
  * @param array $options
  *   An array of options.
  */
 public function dump(array $options = array())
 {
     // Convert all of the routes into database records.
     // Accumulate the menu masks on top of any we found before.
     $masks = array_flip($this->state->get('routing.menu_masks.' . $this->tableName, array()));
     // Delete any old records first, then insert the new ones. That avoids
     // stale data. The transaction makes it atomic to avoid unstable router
     // states due to random failures.
     $transaction = $this->connection->startTransaction();
     try {
         // We don't use truncate, because it is not guaranteed to be transaction
         // safe.
         try {
             $this->connection->delete($this->tableName)->execute();
         } catch (\Exception $e) {
             $this->ensureTableExists();
         }
         // Split the routes into chunks to avoid big INSERT queries.
         $route_chunks = array_chunk($this->routes->all(), 50, TRUE);
         foreach ($route_chunks as $routes) {
             $insert = $this->connection->insert($this->tableName)->fields(array('name', 'fit', 'path', 'pattern_outline', 'number_parts', 'route'));
             $names = array();
             foreach ($routes as $name => $route) {
                 /** @var \Symfony\Component\Routing\Route $route */
                 $route->setOption('compiler_class', '\\Drupal\\Core\\Routing\\RouteCompiler');
                 /** @var \Drupal\Core\Routing\CompiledRoute $compiled */
                 $compiled = $route->compile();
                 // The fit value is a binary number which has 1 at every fixed path
                 // position and 0 where there is a wildcard. We keep track of all such
                 // patterns that exist so that we can minimize the number of path
                 // patterns we need to check in the RouteProvider.
                 $masks[$compiled->getFit()] = 1;
                 $names[] = $name;
                 $values = array('name' => $name, 'fit' => $compiled->getFit(), 'path' => $route->getPath(), 'pattern_outline' => $compiled->getPatternOutline(), 'number_parts' => $compiled->getNumParts(), 'route' => serialize($route));
                 $insert->values($values);
             }
             // Insert all new routes.
             $insert->execute();
         }
     } catch (\Exception $e) {
         $transaction->rollback();
         watchdog_exception('Routing', $e);
         throw $e;
     }
     // Sort the masks so they are in order of descending fit.
     $masks = array_keys($masks);
     rsort($masks);
     $this->state->set('routing.menu_masks.' . $this->tableName, $masks);
     $this->routes = NULL;
 }
 /**
  * {@inheritdoc}
  */
 public function save(EntityInterface $entity)
 {
     $transaction = $this->database->startTransaction();
     try {
         $return = parent::save($entity);
         // Ignore replica server temporarily.
         db_ignore_replica();
         return $return;
     } catch (\Exception $e) {
         $transaction->rollback();
         watchdog_exception($this->entityTypeId, $e);
         throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
     }
 }
 /**
  * {@inheritdoc}
  */
 public function setMultiple(array $items)
 {
     $deleted_tags =& drupal_static('Drupal\\Core\\Cache\\DatabaseBackend::deletedTags', array());
     $invalidated_tags =& drupal_static('Drupal\\Core\\Cache\\DatabaseBackend::invalidatedTags', array());
     // Use a transaction so that the database can write the changes in a single
     // commit.
     $transaction = $this->connection->startTransaction();
     try {
         // Delete all items first so we can do one insert. Rather than multiple
         // merge queries.
         $this->deleteMultiple(array_keys($items));
         $query = $this->connection->insert($this->bin)->fields(array('cid', 'data', 'expire', 'created', 'serialized', 'tags', 'checksum_invalidations', 'checksum_deletions'));
         foreach ($items as $cid => $item) {
             $item += array('expire' => CacheBackendInterface::CACHE_PERMANENT, 'tags' => array());
             Cache::validateTags($item['tags']);
             $item['tags'] = array_unique($item['tags']);
             // Sort the cache tags so that they are stored consistently in the DB.
             sort($item['tags']);
             // Remove tags that were already deleted or invalidated during this
             // request from the static caches so that another deletion or
             // invalidation can occur.
             foreach ($item['tags'] as $tag) {
                 if (isset($deleted_tags[$tag])) {
                     unset($deleted_tags[$tag]);
                 }
                 if (isset($invalidated_tags[$tag])) {
                     unset($invalidated_tags[$tag]);
                 }
             }
             $checksum = $this->checksumTags($item['tags']);
             $fields = array('cid' => $cid, 'expire' => $item['expire'], 'created' => round(microtime(TRUE), 3), 'tags' => implode(' ', $item['tags']), 'checksum_invalidations' => $checksum['invalidations'], 'checksum_deletions' => $checksum['deletions']);
             if (!is_string($item['data'])) {
                 $fields['data'] = serialize($item['data']);
                 $fields['serialized'] = 1;
             } else {
                 $fields['data'] = $item['data'];
                 $fields['serialized'] = 0;
             }
             $query->values($fields);
         }
         $query->execute();
     } catch (\Exception $e) {
         $transaction->rollback();
         // @todo Log something here or just re throw?
         throw $e;
     }
 }
示例#6
0
 /**
  * {@inheritdoc}
  */
 public function setMultiple(array $items)
 {
     $values = array();
     foreach ($items as $cid => $item) {
         $item += array('expire' => CacheBackendInterface::CACHE_PERMANENT, 'tags' => array());
         Cache::validateTags($item['tags']);
         $item['tags'] = array_unique($item['tags']);
         // Sort the cache tags so that they are stored consistently in the DB.
         sort($item['tags']);
         $fields = array('cid' => $cid, 'expire' => $item['expire'], 'created' => round(microtime(TRUE), 3), 'tags' => implode(' ', $item['tags']), 'checksum' => $this->checksumProvider->getCurrentChecksum($item['tags']));
         if (!is_string($item['data'])) {
             $fields['data'] = serialize($item['data']);
             $fields['serialized'] = 1;
         } else {
             $fields['data'] = $item['data'];
             $fields['serialized'] = 0;
         }
         $values[] = $fields;
     }
     // Use a transaction so that the database can write the changes in a single
     // commit. The transaction is started after calculating the tag checksums
     // since that can create a table and this causes an exception when using
     // PostgreSQL.
     $transaction = $this->connection->startTransaction();
     try {
         // Delete all items first so we can do one insert. Rather than multiple
         // merge queries.
         $this->deleteMultiple(array_keys($items));
         $query = $this->connection->insert($this->bin)->fields(array('cid', 'expire', 'created', 'tags', 'checksum', 'data', 'serialized'));
         foreach ($values as $fields) {
             // Only pass the values since the order of $fields matches the order of
             // the insert fields. This is a performance optimization to avoid
             // unnecessary loops within the method.
             $query->values(array_values($fields));
         }
         $query->execute();
     } catch (\Exception $e) {
         $transaction->rollback();
         // @todo Log something here or just re throw?
         throw $e;
     }
 }
 /**
  * Updates the schema for a field stored in a shared table.
  *
  * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  *   The storage definition of the field being updated.
  * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
  *   The original storage definition; i.e., the definition before the update.
  *
  * @throws \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException
  *   Thrown when the update to the field is forbidden.
  * @throws \Exception
  *   Rethrown exception if the table recreation fails.
  */
 protected function updateSharedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original)
 {
     if (!$this->storage->countFieldData($original, TRUE)) {
         if ($this->database->supportsTransactionalDDL()) {
             // If the database supports transactional DDL, we can go ahead and rely
             // on it. If not, we will have to rollback manually if something fails.
             $transaction = $this->database->startTransaction();
         }
         try {
             // Since there is no data we may be switching from a dedicated table
             // to a schema table schema, hence we should use the proper API.
             $this->performFieldSchemaOperation('delete', $original);
             $this->performFieldSchemaOperation('create', $storage_definition);
         } catch (\Exception $e) {
             if ($this->database->supportsTransactionalDDL()) {
                 $transaction->rollback();
             } else {
                 // Recreate original schema.
                 $this->createSharedTableSchema($original);
             }
             throw $e;
         }
     } else {
         if ($this->hasColumnChanges($storage_definition, $original)) {
             throw new FieldStorageDefinitionUpdateForbiddenException('The SQL storage cannot change the schema for an existing field (' . $storage_definition->getName() . ' in ' . $storage_definition->getTargetEntityTypeId() . ' entity) with data.');
         }
         $updated_field_name = $storage_definition->getName();
         $table_mapping = $this->storage->getTableMapping();
         $column_names = $table_mapping->getColumnNames($updated_field_name);
         $schema_handler = $this->database->schema();
         // Iterate over the mapped table to find the ones that host the deleted
         // field schema.
         $original_schema = $this->loadFieldSchemaData($original);
         $schema = array();
         foreach ($table_mapping->getTableNames() as $table_name) {
             foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
                 if ($field_name == $updated_field_name) {
                     $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
                     // Handle NOT NULL constraints.
                     foreach ($schema[$table_name]['fields'] as $column_name => $specifier) {
                         $not_null = !empty($specifier['not null']);
                         $original_not_null = !empty($original_schema[$table_name]['fields'][$column_name]['not null']);
                         if ($not_null !== $original_not_null) {
                             if ($not_null && $this->hasNullFieldPropertyData($table_name, $column_name)) {
                                 throw new EntityStorageException("The {$column_name} column cannot have NOT NULL constraints as it holds NULL values.");
                             }
                             $column_schema = $original_schema[$table_name]['fields'][$column_name];
                             $column_schema['not null'] = $not_null;
                             $schema_handler->changeField($table_name, $field_name, $field_name, $column_schema);
                         }
                     }
                     // Drop original indexes and unique keys.
                     if (!empty($original_schema[$table_name]['indexes'])) {
                         foreach ($original_schema[$table_name]['indexes'] as $name => $specifier) {
                             $schema_handler->dropIndex($table_name, $name);
                         }
                     }
                     if (!empty($original_schema[$table_name]['unique keys'])) {
                         foreach ($original_schema[$table_name]['unique keys'] as $name => $specifier) {
                             $schema_handler->dropUniqueKey($table_name, $name);
                         }
                     }
                     // Create new indexes and unique keys.
                     if (!empty($schema[$table_name]['indexes'])) {
                         foreach ($schema[$table_name]['indexes'] as $name => $specifier) {
                             // Check if the index exists because it might already have been
                             // created as part of the earlier entity type update event.
                             $this->addIndex($table_name, $name, $specifier, $schema[$table_name]);
                         }
                     }
                     if (!empty($schema[$table_name]['unique keys'])) {
                         foreach ($schema[$table_name]['unique keys'] as $name => $specifier) {
                             $schema_handler->addUniqueKey($table_name, $name, $specifier);
                         }
                     }
                     // After deleting the field schema skip to the next table.
                     break;
                 }
             }
         }
         $this->saveFieldSchemaData($storage_definition, $schema);
     }
 }
 /**
  * {@inheritdoc}
  */
 public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original)
 {
     if (!$storage_definition->hasData()) {
         // There is no data. Re-create the tables completely.
         if ($this->database->supportsTransactionalDDL()) {
             // If the database supports transactional DDL, we can go ahead and rely
             // on it. If not, we will have to rollback manually if something fails.
             $transaction = $this->database->startTransaction();
         }
         try {
             $original_schema = $this->_fieldSqlSchema($original);
             foreach ($original_schema as $name => $table) {
                 $this->database->schema()->dropTable($name, $table);
             }
             $schema = $this->_fieldSqlSchema($storage_definition);
             foreach ($schema as $name => $table) {
                 $this->database->schema()->createTable($name, $table);
             }
         } catch (\Exception $e) {
             if ($this->database->supportsTransactionalDDL()) {
                 $transaction->rollback();
             } else {
                 // Recreate tables.
                 $original_schema = $this->_fieldSqlSchema($original);
                 foreach ($original_schema as $name => $table) {
                     if (!$this->database->schema()->tableExists($name)) {
                         $this->database->schema()->createTable($name, $table);
                     }
                 }
             }
             throw $e;
         }
     } else {
         if ($storage_definition->getColumns() != $original->getColumns()) {
             throw new FieldStorageDefinitionUpdateForbiddenException("The SQL storage cannot change the schema for an existing field with data.");
         }
         // There is data, so there are no column changes. Drop all the prior
         // indexes and create all the new ones, except for all the priors that
         // exist unchanged.
         $table = static::_fieldTableName($original);
         $revision_table = static::_fieldRevisionTableName($original);
         $schema = $storage_definition->getSchema();
         $original_schema = $original->getSchema();
         foreach ($original_schema['indexes'] as $name => $columns) {
             if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) {
                 $real_name = static::_fieldIndexName($storage_definition, $name);
                 $this->database->schema()->dropIndex($table, $real_name);
                 $this->database->schema()->dropIndex($revision_table, $real_name);
             }
         }
         $table = static::_fieldTableName($storage_definition);
         $revision_table = static::_fieldRevisionTableName($storage_definition);
         foreach ($schema['indexes'] as $name => $columns) {
             if (!isset($original_schema['indexes'][$name]) || $columns != $original_schema['indexes'][$name]) {
                 $real_name = static::_fieldIndexName($storage_definition, $name);
                 $real_columns = array();
                 foreach ($columns as $column_name) {
                     // Indexes can be specified as either a column name or an array with
                     // column name and length. Allow for either case.
                     if (is_array($column_name)) {
                         $real_columns[] = array(static::_fieldColumnName($storage_definition, $column_name[0]), $column_name[1]);
                     } else {
                         $real_columns[] = static::_fieldColumnName($storage_definition, $column_name);
                     }
                 }
                 $this->database->schema()->addIndex($table, $real_name, $real_columns);
                 $this->database->schema()->addIndex($revision_table, $real_name, $real_columns);
             }
         }
     }
 }
 /**
  * {@inheritdoc}
  */
 public function save(EntityInterface $entity)
 {
     $transaction = $this->database->startTransaction();
     try {
         // Sync the changes made in the fields array to the internal values array.
         $entity->updateOriginalValues();
         $return = parent::save($entity);
         // Ignore replica server temporarily.
         db_ignore_replica();
         return $return;
     } catch (\Exception $e) {
         $transaction->rollback();
         watchdog_exception($this->entityTypeId, $e);
         throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
     }
 }
 /**
  * Updates the schema for a field stored in a shared table.
  *
  * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  *   The storage definition of the field being updated.
  * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $original
  *   The original storage definition; i.e., the definition before the update.
  *
  * @throws \Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException
  *   Thrown when the update to the field is forbidden.
  * @throws \Exception
  *   Rethrown exception if the table recreation fails.
  */
 protected function updateSharedTableSchema(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original)
 {
     if (!$this->storage->countFieldData($original, TRUE)) {
         if ($this->database->supportsTransactionalDDL()) {
             // If the database supports transactional DDL, we can go ahead and rely
             // on it. If not, we will have to rollback manually if something fails.
             $transaction = $this->database->startTransaction();
         }
         try {
             // Since there is no data we may be switching from a dedicated table
             // to a schema table schema, hence we should use the proper API.
             $this->performFieldSchemaOperation('delete', $original);
             $this->performFieldSchemaOperation('create', $storage_definition);
         } catch (\Exception $e) {
             if ($this->database->supportsTransactionalDDL()) {
                 $transaction->rollback();
             } else {
                 // Recreate original schema.
                 $this->createSharedTableSchema($original);
             }
             throw $e;
         }
     } else {
         if ($storage_definition->getColumns() != $original->getColumns()) {
             throw new FieldStorageDefinitionUpdateForbiddenException("The SQL storage cannot change the schema for an existing field with data.");
         }
         $updated_field_name = $storage_definition->getName();
         $table_mapping = $this->storage->getTableMapping();
         $column_names = $table_mapping->getColumnNames($updated_field_name);
         $schema_handler = $this->database->schema();
         // Iterate over the mapped table to find the ones that host the deleted
         // field schema.
         $original_schema = $this->loadFieldSchemaData($original);
         $schema = array();
         foreach ($table_mapping->getTableNames() as $table_name) {
             foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
                 if ($field_name == $updated_field_name) {
                     $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
                     // Drop original indexes and unique keys.
                     if (!empty($original_schema[$table_name]['indexes'])) {
                         foreach ($original_schema[$table_name]['indexes'] as $name => $specifier) {
                             $schema_handler->dropIndex($table_name, $name);
                         }
                     }
                     if (!empty($original_schema[$table_name]['unique keys'])) {
                         foreach ($original_schema[$table_name]['unique keys'] as $name => $specifier) {
                             $schema_handler->dropUniqueKey($table_name, $name);
                         }
                     }
                     // Create new indexes and unique keys.
                     if (!empty($schema[$table_name]['indexes'])) {
                         foreach ($schema[$table_name]['indexes'] as $name => $specifier) {
                             $schema_handler->addIndex($table_name, $name, $specifier);
                         }
                     }
                     if (!empty($schema[$table_name]['unique keys'])) {
                         foreach ($schema[$table_name]['unique keys'] as $name => $specifier) {
                             $schema_handler->addUniqueKey($table_name, $name, $specifier);
                         }
                     }
                     // After deleting the field schema skip to the next table.
                     break;
                 }
             }
         }
         $this->saveFieldSchemaData($storage_definition, $schema);
     }
 }
示例#11
0
文件: Database.php 项目: jkyto/agolf
  /**
   * Indexes a single item on the specified index.
   *
   * Used as a helper method in indexItems().
   *
   * @param \Drupal\search_api\IndexInterface $index
   *   The index for which the item is being indexed.
   * @param string $id
   *   The item's ID.
   * @param \Drupal\search_api\Item\ItemInterface $item
   *   The item to index.
   *
   * @throws \Exception
   *   Any encountered database (or other) exceptions are passed on, out of this
   *   method.
   */
  protected function indexItem(IndexInterface $index, $id, ItemInterface $item) {
    $fields = $this->getFieldInfo($index);
    $fields_updated = FALSE;
    $field_errors = array();
    $db_info = $this->getIndexDbInfo($index);

    $denormalized_table = $db_info['index_table'];
    $txn = $this->database->startTransaction('search_api_indexing');
    $text_table = $denormalized_table . '_text';

    try {
      $inserts = array();
      $text_inserts = array();
      foreach ($item->getFields() as $name => $field) {
        $denormalized_value = NULL;
        // Sometimes index changes are not triggering the update hooks
        // correctly. Therefore, to avoid DB errors, we re-check the tables
        // here before indexing.
        if (empty($fields[$name]['table']) && !$fields_updated) {
          unset($db_info['field_tables'][$name]);
          $this->fieldsUpdated($index);
          $fields_updated = TRUE;
          $fields = $db_info['field_tables'];
        }
        if (empty($fields[$name]['table']) && empty($field_errors[$name])) {
          // Log an error, but only once per field. Since a superfluous field is
          // not too serious, we just index the rest of the item normally.
          $field_errors[$name] = TRUE;
          $this->getLogger()->warning("Unknown field @field: please check (and re-save) the index's fields settings.", array('@field' => $name));
          continue;
        }
        $table = $fields[$name]['table'];

        $boost = $fields[$name]['boost'];
        $this->database->delete($table)
          ->condition('item_id', $id)
          ->execute();
        $this->database->delete($denormalized_table)
          ->condition('item_id', $id)
          ->execute();

        $type = $field->getType();
        $value = array();
        foreach ($field->getValues() as $field_value) {
          $converted_value = $this->convert($field_value, $type, $field->getOriginalType(), $index);

          // Don't add NULL values to the return array. Also, adding an empty
          // array is, of course, a waste of time.
          if (isset($converted_value) && $converted_value !== array()) {
            $value = array_merge($value, is_array($converted_value) ? $converted_value : array($converted_value));
          }
        }

        if (Utility::isTextType($type, array('text', 'tokenized_text'))) {
          $words = array();
          // Store the first 30 characters of the string as the denormalized
          // value.
          $field_value = $value;
          $denormalized_value = '';

          do {
            $denormalized_value .= array_shift($field_value)['value'] . ' ';
          } while (strlen($denormalized_value) < 30);
          $denormalized_value = Unicode::truncateBytes(trim($denormalized_value), 30);

          foreach ($value as $token) {
            // Taken from core search to reflect less importance of words later
            // in the text.
            // Focus is a decaying value in terms of the amount of unique words
            // up to this point. From 100 words and more, it decays, to e.g. 0.5
            // at 500 words and 0.3 at 1000 words.
            $focus = min(1, .01 + 3.5 / (2 + count($words) * .015));

            $value = $token['value'];
            if (is_numeric($value)) {
              $value = ltrim($value, '-0');
            }
            elseif (Unicode::strlen($value) < $this->configuration['min_chars']) {
              continue;
            }
            $value = Unicode::strtolower($value);
            $token['score'] = $token['score'] * $focus;
            if (!isset($words[$value])) {
              $words[$value] = $token;
            }
            else {
              $words[$value]['score'] += $token['score'];
            }
            $token['value'] = $value;
          }
          if ($words) {
            $field_name = self::getTextFieldName($name);
            foreach ($words as $word) {
              $text_inserts[$text_table][] = array(
                'item_id' => $id,
                'field_name' => $field_name,
                'word' => $word['value'],
                'score' => (int) round($word['score'] * $boost * self::SCORE_MULTIPLIER),
              );
            }
          }
        }
        else {
          $values = array();
          if (is_array($value)) {
            foreach ($value as $v) {
              if (isset($v)) {
                $values["$v"] = TRUE;
              }
            }
            $values = array_keys($values);
          }
          elseif (isset($value)) {
            $values[] = $value;
          }
          if ($values) {
            $denormalized_value = reset($values);
            $insert = $this->database->insert($table)
              ->fields(array('item_id', 'value'));
            foreach ($values as $v) {
              $insert->values(array(
                'item_id' => $id,
                'value' => $v,
              ));
            }
            $insert->execute();
          }
        }

        // Insert a value in the denormalized table for all fields.
        if (isset($denormalized_value)) {
          $inserts[$denormalized_table][$fields[$name]['column']] = trim($denormalized_value);
        }
      }

      foreach ($inserts as $table => $data) {
        $this->database->insert($table)
          ->fields(array_merge($data, array('item_id' => $id)))
          ->execute();
      }
      foreach ($text_inserts as $table => $data) {
        $query = $this->database->insert($table)
          ->fields(array('item_id', 'field_name', 'word', 'score'));
        foreach ($data as $row) {
          $query->values($row);
        }
        $query->execute();
      }
    }
    catch (\Exception $e) {
      $txn->rollback();
      throw $e;
    }
  }
示例#12
0
 /**
  * Indexes a single item on the specified index.
  *
  * Used as a helper method in indexItems().
  *
  * @param \Drupal\search_api\IndexInterface $index
  *   The index for which the item is being indexed.
  * @param \Drupal\search_api\Item\ItemInterface $item
  *   The item to index.
  *
  * @throws \Exception
  *   Any encountered database (or other) exceptions are passed on, out of this
  *   method.
  */
 protected function indexItem(IndexInterface $index, ItemInterface $item)
 {
     $fields = $this->getFieldInfo($index);
     $fields_updated = FALSE;
     $field_errors = array();
     $db_info = $this->getIndexDbInfo($index);
     $denormalized_table = $db_info['index_table'];
     $item_id = $item->getId();
     $transaction = $this->database->startTransaction('search_api_db_indexing');
     try {
         // Remove the item from the denormalized table.
         $this->database->delete($denormalized_table)->condition('item_id', $item_id)->execute();
         $denormalized_values = array();
         $text_inserts = array();
         foreach ($item->getFields() as $field_id => $field) {
             // Sometimes index changes are not triggering the update hooks
             // correctly. Therefore, to avoid DB errors, we re-check the tables
             // here before indexing.
             if (empty($fields[$field_id]['table']) && !$fields_updated) {
                 unset($db_info['field_tables'][$field_id]);
                 $this->fieldsUpdated($index);
                 $fields_updated = TRUE;
                 $fields = $db_info['field_tables'];
             }
             if (empty($fields[$field_id]['table']) && empty($field_errors[$field_id])) {
                 // Log an error, but only once per field. Since a superfluous field is
                 // not too serious, we just index the rest of the item normally.
                 $field_errors[$field_id] = TRUE;
                 $this->getLogger()->warning("Unknown field @field: please check (and re-save) the index's fields settings.", array('@field' => $field_id));
                 continue;
             }
             $field_info = $fields[$field_id];
             $table = $field_info['table'];
             $column = $field_info['column'];
             $this->database->delete($table)->condition('item_id', $item_id)->execute();
             $type = $field->getType();
             $values = array();
             foreach ($field->getValues() as $field_value) {
                 $converted_value = $this->convert($field_value, $type, $field->getOriginalType(), $index);
                 // Don't add NULL values to the return array. Also, adding an empty
                 // array is, of course, a waste of time.
                 if (isset($converted_value) && $converted_value !== array()) {
                     $values = array_merge($values, is_array($converted_value) ? $converted_value : array($converted_value));
                 }
             }
             if (!$values) {
                 // SQLite sometimes has problems letting columns not present in an
                 // INSERT statement default to NULL, so we set NULL values for the
                 // denormalized table explicitly.
                 $denormalized_values[$column] = NULL;
                 continue;
             }
             // If the field contains more than one value, we remember that the field
             // can be multi-valued.
             if (count($values) > 1) {
                 $db_info['field_tables'][$field_id]['multi-valued'] = TRUE;
             }
             if (Utility::isTextType($type, array('text', 'tokenized_text'))) {
                 // Remember the text table the first time we encounter it.
                 if (!isset($text_table)) {
                     $text_table = $table;
                 }
                 $unique_tokens = array();
                 $denormalized_value = '';
                 foreach ($values as $token) {
                     $word = $token['value'];
                     $score = $token['score'];
                     // Store the first 30 characters of the string as the denormalized
                     // value.
                     if (strlen($denormalized_value) < 30) {
                         $denormalized_value .= $word . ' ';
                     }
                     // Skip words that are too short, except for numbers.
                     if (is_numeric($word)) {
                         $word = ltrim($word, '-0');
                     } elseif (Unicode::strlen($word) < $this->configuration['min_chars']) {
                         continue;
                     }
                     // Taken from core search to reflect less importance of words later
                     // in the text.
                     // Focus is a decaying value in terms of the amount of unique words
                     // up to this point. From 100 words and more, it decays, to e.g. 0.5
                     // at 500 words and 0.3 at 1000 words.
                     $score *= min(1, 0.01 + 3.5 / (2 + count($unique_tokens) * 0.015));
                     // Only insert each canonical base form of a word once.
                     $word_base_form = $this->dbmsCompatibility->preprocessIndexValue($word);
                     if (!isset($unique_tokens[$word_base_form])) {
                         $unique_tokens[$word_base_form] = array('value' => $word, 'score' => $score);
                     } else {
                         $unique_tokens[$word_base_form]['score'] += $score;
                     }
                 }
                 $denormalized_values[$column] = Unicode::truncateBytes(trim($denormalized_value), 30);
                 if ($unique_tokens) {
                     $field_name = self::getTextFieldName($field_id);
                     $boost = $field_info['boost'];
                     foreach ($unique_tokens as $token) {
                         $text_inserts[] = array('item_id' => $item_id, 'field_name' => $field_name, 'word' => $token['value'], 'score' => (int) round($token['score'] * $boost * self::SCORE_MULTIPLIER));
                     }
                 }
             } else {
                 $denormalized_values[$column] = reset($values);
                 // Make sure no duplicate values are inserted (which would lead to a
                 // database exception).
                 // Use the canonical base form of the value for the comparison to
                 // avoid not catching different values that are duplicates under the
                 // database table's collation.
                 $case_insensitive_unique_values = array();
                 foreach ($values as $value) {
                     $value_base_form = $this->dbmsCompatibility->preprocessIndexValue("{$value}", 'field');
                     // We still insert the value in its original case.
                     $case_insensitive_unique_values[$value_base_form] = $value;
                 }
                 $values = array_values($case_insensitive_unique_values);
                 $insert = $this->database->insert($table)->fields(array('item_id', 'value'));
                 foreach ($values as $value) {
                     $insert->values(array('item_id' => $item_id, 'value' => $value));
                 }
                 $insert->execute();
             }
         }
         $this->database->insert($denormalized_table)->fields(array_merge($denormalized_values, array('item_id' => $item_id)))->execute();
         if ($text_inserts && isset($text_table)) {
             $query = $this->database->insert($text_table)->fields(array('item_id', 'field_name', 'word', 'score'));
             foreach ($text_inserts as $row) {
                 $query->values($row);
             }
             $query->execute();
         }
         // In case any new fields were detected as multi-valued, we re-save the
         // index's DB info.
         $this->getKeyValueStore()->set($index->id(), $db_info);
     } catch (\Exception $e) {
         $transaction->rollback();
         throw $e;
     }
 }