예제 #1
0
 /**
  * Construct the database dump command.
  *
  * @param \Drupal\Core\Database\Connection $connection
  *   The database connection to use.
  * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  *   The module handler to use.
  */
 function __construct(Connection $connection, ModuleHandlerInterface $module_handler)
 {
     // Check this is MySQL.
     if ($connection->databaseType() !== 'mysql') {
         throw new \RuntimeException('This script can only be used with MySQL database backends.');
     }
     $this->connection = $connection;
     $this->moduleHandler = $module_handler;
     parent::__construct();
 }
예제 #2
0
 /**
  * Returns a schema array for a given table.
  *
  * @param \Drupal\Core\Database\Connection $connection
  *   The database connection to use.
  * @param string $table
  *   The table name.
  *
  * @return array
  *   A schema array (as defined by hook_schema()).
  *
  * @todo This implementation is hard-coded for MySQL.
  */
 protected function getTableSchema(Connection $connection, $table)
 {
     // Check this is MySQL.
     if ($connection->databaseType() !== 'mysql') {
         throw new \RuntimeException('This script can only be used with MySQL database backends.');
     }
     $query = $connection->query("SHOW FULL COLUMNS FROM {" . $table . "}");
     $definition = [];
     while (($row = $query->fetchAssoc()) !== FALSE) {
         $name = $row['Field'];
         // Parse out the field type and meta information.
         preg_match('@([a-z]+)(?:\\((\\d+)(?:,(\\d+))?\\))?\\s*(unsigned)?@', $row['Type'], $matches);
         $type = $this->fieldTypeMap($connection, $matches[1]);
         if ($row['Extra'] === 'auto_increment') {
             // If this is an auto increment, then the type is 'serial'.
             $type = 'serial';
         }
         $definition['fields'][$name] = ['type' => $type, 'not null' => $row['Null'] === 'NO'];
         if ($size = $this->fieldSizeMap($connection, $matches[1])) {
             $definition['fields'][$name]['size'] = $size;
         }
         if (isset($matches[2]) && $type === 'numeric') {
             // Add precision and scale.
             $definition['fields'][$name]['precision'] = $matches[2];
             $definition['fields'][$name]['scale'] = $matches[3];
         } elseif ($type === 'time' || $type === 'datetime') {
             // @todo Core doesn't support these, but copied from `migrate-db.sh` for now.
             // Convert to varchar.
             $definition['fields'][$name]['type'] = 'varchar';
             $definition['fields'][$name]['length'] = '100';
         } elseif (!isset($definition['fields'][$name]['size'])) {
             // Try use the provided length, if it doesn't exist default to 100. It's
             // not great but good enough for our dumps at this point.
             $definition['fields'][$name]['length'] = isset($matches[2]) ? $matches[2] : 100;
         }
         if (isset($row['Default'])) {
             $definition['fields'][$name]['default'] = $row['Default'];
         }
         if (isset($matches[4])) {
             $definition['fields'][$name]['unsigned'] = TRUE;
         }
         // Check for the 'varchar_ascii' type that should be 'binary'.
         if (isset($row['Collation']) && $row['Collation'] == 'ascii_bin') {
             $definition['fields'][$name]['type'] = 'varchar_ascii';
             $definition['fields'][$name]['binary'] = TRUE;
         }
         // Check for the non-binary 'varchar_ascii'.
         if (isset($row['Collation']) && $row['Collation'] == 'ascii_general_ci') {
             $definition['fields'][$name]['type'] = 'varchar_ascii';
         }
         // Check for the 'utf8_bin' collation.
         if (isset($row['Collation']) && $row['Collation'] == 'utf8_bin') {
             $definition['fields'][$name]['binary'] = TRUE;
         }
     }
     // Set primary key, unique keys, and indexes.
     $this->getTableIndexes($connection, $table, $definition);
     // Set table collation.
     $this->getTableCollation($connection, $table, $definition);
     return $definition;
 }
예제 #3
0
파일: Database.php 프로젝트: jkyto/agolf
  /**
   * Updates the storage tables when the field configuration changes.
   *
   * @param \Drupal\search_api\IndexInterface $index
   *   The search index whose fields (might) have changed.
   *
   * @return bool
   *   TRUE if the data needs to be reindexed, FALSE otherwise.
   *
   * @throws \Drupal\search_api\SearchApiException
   *   Thrown if any exceptions occur internally, e.g., in the database
   *   layer.
   */
  protected function fieldsUpdated(IndexInterface $index) {
    try {
      $db_info = $this->getIndexDbInfo($index);
      $fields = &$db_info['field_tables'];
      $new_fields = $index->getFields();

      $reindex = FALSE;
      $cleared = FALSE;
      $text_table = NULL;
      $denormalized_table = $db_info['index_table'];

      foreach ($fields as $field_id => $field) {
        if (!isset($text_table) && Utility::isTextType($field['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) {
          if ($old_type == 'text' || $new_type == 'text') {
            // 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.
            $this->database->schema()->changeField($field['table'], 'value', 'value', $this->sqlType($new_type) + array('description' => "The field's value for this item"));
            $this->database->schema()->changeField($denormalized_table, $field['column'], $field['column'], $this->sqlType($new_type) + array('description' => "The field's value for this item"));
            $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 ($new_type == 'text' && $field['boost'] != $new_fields[$field_id]->getBoost()) {
          if (!$reindex) {
            $multiplier = $new_fields[$field_id]->getBoost() / $field['boost'];
            $this->database->update($text_table)
              ->expression('score', 'score * :mult', array(':mult' => $multiplier))
              ->condition('field_name', self::getTextFieldName($field_id))
              ->execute();
          }
        }

        // Make sure the table and column now exist. (Especially important when
        // we actually add the index for the first time.)
        $storage_exists = $this->database->schema()
          ->fieldExists($field['table'], 'value');
        $denormalized_storage_exists = $this->database->schema()
          ->fieldExists($denormalized_table, $field['column']);
        if (!Utility::isTextType($field['type']) && !$storage_exists) {
          $db = array(
            '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 = array(
            'table' => $denormalized_table,
            'column' => $field['column'],
          );
          $this->createFieldTable($new_fields[$field_id], $db);
        }
        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;
        if (Utility::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, array('table' => $denormalized_table, 'column' => $fields[$field_id]['column']));

        $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 = array(
          'name' => $text_table,
          'module' => 'search_api_db',
          'fields' => array(
            'item_id' => array(
              'type' => 'varchar',
              'length' => 50,
              '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 an MD5 hash of the field",
              'not null' => TRUE,
              'type' => 'varchar',
              'length' => 191,
            ),
            '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'),
        );
        $this->database->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->database->databaseType()) {
            case 'mysql':
              $this->database->query("ALTER TABLE {{$text_table}} CONVERT TO CHARACTER SET 'utf8' COLLATE 'utf8_bin'");
              break;

            case 'pgsql':
              $this->database->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->label();
          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);
        }
      }

      $this->getKeyValueStore()->set($index->id(), $db_info);

      return $reindex;
    }
      // The database operations might throw PDO or other exceptions, so we catch
      // them all and re-wrap them appropriately.
    catch (\Exception $e) {
      throw new SearchApiException($e->getMessage(), $e->getCode(), $e);
    }
  }