You are here

abstract class DatabaseSchema in Drupal 7

Base class for database schema definitions.

Hierarchy

Expanded class hierarchy of DatabaseSchema

Related topics

1 string reference to 'DatabaseSchema'
DatabaseConnection::schema in includes/database/database.inc
Returns a DatabaseSchema object for manipulating the schema.

File

includes/database/schema.inc, line 170

View source
abstract class DatabaseSchema implements QueryPlaceholderInterface {

  /**
   * The database connection.
   *
   * @var DatabaseConnection
   */
  protected $connection;

  /**
   * The placeholder counter.
   */
  protected $placeholder = 0;

  /**
   * Definition of prefixInfo array structure.
   *
   * Rather than redefining DatabaseSchema::getPrefixInfo() for each driver,
   * by defining the defaultSchema variable only MySQL has to re-write the
   * method.
   *
   * @see DatabaseSchema::getPrefixInfo()
   */
  protected $defaultSchema = 'public';

  /**
   * A unique identifier for this query object.
   */
  protected $uniqueIdentifier;
  public function __construct($connection) {
    $this->uniqueIdentifier = uniqid('', TRUE);
    $this->connection = $connection;
  }

  /**
   * Implements the magic __clone function.
   */
  public function __clone() {
    $this->uniqueIdentifier = uniqid('', TRUE);
  }

  /**
   * Implements QueryPlaceHolderInterface::uniqueIdentifier().
   */
  public function uniqueIdentifier() {
    return $this->uniqueIdentifier;
  }

  /**
   * Implements QueryPlaceHolderInterface::nextPlaceholder().
   */
  public function nextPlaceholder() {
    return $this->placeholder++;
  }

  /**
   * Get information about the table name and schema from the prefix.
   *
   * @param
   *   Name of table to look prefix up for. Defaults to 'default' because thats
   *   default key for prefix.
   * @param $add_prefix
   *   Boolean that indicates whether the given table name should be prefixed.
   *
   * @return
   *   A keyed array with information about the schema, table name and prefix.
   */
  protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
    $info = array(
      'schema' => $this->defaultSchema,
      'prefix' => $this->connection
        ->tablePrefix($table),
    );
    if ($add_prefix) {
      $table = $info['prefix'] . $table;
    }

    // If the prefix contains a period in it, then that means the prefix also
    // contains a schema reference in which case we will change the schema key
    // to the value before the period in the prefix. Everything after the dot
    // will be prefixed onto the front of the table.
    if (($pos = strpos($table, '.')) !== FALSE) {

      // Grab everything before the period.
      $info['schema'] = substr($table, 0, $pos);

      // Grab everything after the dot.
      $info['table'] = substr($table, ++$pos);
    }
    else {
      $info['table'] = $table;
    }
    return $info;
  }

  /**
   * Create names for indexes, primary keys and constraints.
   *
   * This prevents using {} around non-table names like indexes and keys.
   */
  function prefixNonTable($table) {
    $args = func_get_args();
    $info = $this
      ->getPrefixInfo($table);
    $args[0] = $info['table'];
    return implode('_', $args);
  }

  /**
   * Build a condition to match a table name against a standard information_schema.
   *
   * The information_schema is a SQL standard that provides information about the
   * database server and the databases, schemas, tables, columns and users within
   * it. This makes information_schema a useful tool to use across the drupal
   * database drivers and is used by a few different functions. The function below
   * describes the conditions to be meet when querying information_schema.tables
   * for drupal tables or information associated with drupal tables. Even though
   * this is the standard method, not all databases follow standards and so this
   * method should be overwritten by a database driver if the database provider
   * uses alternate methods. Because information_schema.tables is used in a few
   * different functions, a database driver will only need to override this function
   * to make all the others work. For example see includes/databases/mysql/schema.inc.
   *
   * @param $table_name
   *   The name of the table in question.
   * @param $operator
   *   The operator to apply on the 'table' part of the condition.
   * @param $add_prefix
   *   Boolean to indicate whether the table name needs to be prefixed.
   *
   * @return QueryConditionInterface
   *   A DatabaseCondition object.
   */
  protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
    $info = $this->connection
      ->getConnectionOptions();

    // Retrieve the table name and schema
    $table_info = $this
      ->getPrefixInfo($table_name, $add_prefix);
    $condition = new DatabaseCondition('AND');
    $condition
      ->condition('table_catalog', $info['database']);
    $condition
      ->condition('table_schema', $table_info['schema']);
    $condition
      ->condition('table_name', $table_info['table'], $operator);
    return $condition;
  }

  /**
   * Check if a table exists.
   *
   * @param $table
   *   The name of the table in drupal (no prefixing).
   *
   * @return
   *   TRUE if the given table exists, otherwise FALSE.
   */
  public function tableExists($table) {
    $condition = $this
      ->buildTableNameCondition($table);
    $condition
      ->compile($this->connection, $this);

    // Normally, we would heartily discourage the use of string
    // concatenation for conditionals like this however, we
    // couldn't use db_select() here because it would prefix
    // information_schema.tables and the query would fail.
    // Don't use {} around information_schema.tables table.
    return (bool) $this->connection
      ->query("SELECT 1 FROM information_schema.tables WHERE " . (string) $condition, $condition
      ->arguments())
      ->fetchField();
  }

  /**
   * Find all tables that are like the specified base table name.
   *
   * @param $table_expression
   *   An SQL expression, for example "simpletest%" (without the quotes).
   *   BEWARE: this is not prefixed, the caller should take care of that.
   *
   * @return
   *   Array, both the keys and the values are the matching tables.
   */
  public function findTables($table_expression) {
    $condition = $this
      ->buildTableNameCondition($table_expression, 'LIKE', FALSE);
    $condition
      ->compile($this->connection, $this);

    // Normally, we would heartily discourage the use of string
    // concatenation for conditionals like this however, we
    // couldn't use db_select() here because it would prefix
    // information_schema.tables and the query would fail.
    // Don't use {} around information_schema.tables table.
    return $this->connection
      ->query("SELECT table_name AS table_name FROM information_schema.tables WHERE " . (string) $condition, $condition
      ->arguments())
      ->fetchAllKeyed(0, 0);
  }

  /**
   * Finds all tables that are like the specified base table name. This is a
   * backport of the change made to findTables in Drupal 8 to work with virtual,
   * un-prefixed table names. The original function is retained for Backwards
   * Compatibility.
   * @see https://www.drupal.org/node/2552435
   *
   * @param string $table_expression
   *   An SQL expression, for example "cache_%" (without the quotes).
   *
   * @return array
   *   Both the keys and the values are the matching tables.
   */
  public function findTablesD8($table_expression) {

    // Load all the tables up front in order to take into account per-table
    // prefixes. The actual matching is done at the bottom of the method.
    $condition = $this
      ->buildTableNameCondition('%', 'LIKE');
    $condition
      ->compile($this->connection, $this);
    $individually_prefixed_tables = $this->connection
      ->getUnprefixedTablesMap();
    $default_prefix = $this->connection
      ->tablePrefix();
    $default_prefix_length = strlen($default_prefix);
    $tables = array();

    // Normally, we would heartily discourage the use of string
    // concatenation for conditionals like this however, we
    // couldn't use db_select() here because it would prefix
    // information_schema.tables and the query would fail.
    // Don't use {} around information_schema.tables table.
    $results = $this->connection
      ->query("SELECT table_name AS table_name FROM information_schema.tables WHERE " . (string) $condition, $condition
      ->arguments());
    foreach ($results as $table) {

      // Take into account tables that have an individual prefix.
      if (isset($individually_prefixed_tables[$table->table_name])) {
        $prefix_length = strlen($this->connection
          ->tablePrefix($individually_prefixed_tables[$table->table_name]));
      }
      elseif ($default_prefix && substr($table->table_name, 0, $default_prefix_length) !== $default_prefix) {

        // This table name does not start the default prefix, which means that
        // it is not managed by Drupal so it should be excluded from the result.
        continue;
      }
      else {
        $prefix_length = $default_prefix_length;
      }

      // Remove the prefix from the returned tables.
      $unprefixed_table_name = substr($table->table_name, $prefix_length);

      // The pattern can match a table which is the same as the prefix. That
      // will become an empty string when we remove the prefix, which will
      // probably surprise the caller, besides not being a prefixed table. So
      // remove it.
      if (!empty($unprefixed_table_name)) {
        $tables[$unprefixed_table_name] = $unprefixed_table_name;
      }
    }

    // Convert the table expression from its SQL LIKE syntax to a regular
    // expression and escape the delimiter that will be used for matching.
    $table_expression = str_replace(array(
      '%',
      '_',
    ), array(
      '.*?',
      '.',
    ), preg_quote($table_expression, '/'));
    $tables = preg_grep('/^' . $table_expression . '$/i', $tables);
    return $tables;
  }

  /**
   * Check if a column exists in the given table.
   *
   * @param $table
   *   The name of the table in drupal (no prefixing).
   * @param $name
   *   The name of the column.
   *
   * @return
   *   TRUE if the given column exists, otherwise FALSE.
   */
  public function fieldExists($table, $column) {
    $condition = $this
      ->buildTableNameCondition($table);
    $condition
      ->condition('column_name', $column);
    $condition
      ->compile($this->connection, $this);

    // Normally, we would heartily discourage the use of string
    // concatenation for conditionals like this however, we
    // couldn't use db_select() here because it would prefix
    // information_schema.tables and the query would fail.
    // Don't use {} around information_schema.columns table.
    return (bool) $this->connection
      ->query("SELECT 1 FROM information_schema.columns WHERE " . (string) $condition, $condition
      ->arguments())
      ->fetchField();
  }

  /**
   * Returns a mapping of Drupal schema field names to DB-native field types.
   *
   * Because different field types do not map 1:1 between databases, Drupal has
   * its own normalized field type names. This function returns a driver-specific
   * mapping table from Drupal names to the native names for each database.
   *
   * @return array
   *   An array of Schema API field types to driver-specific field types.
   */
  public abstract function getFieldTypeMap();

  /**
   * Rename a table.
   *
   * @param $table
   *   The table to be renamed.
   * @param $new_name
   *   The new name for the table.
   *
   * @throws DatabaseSchemaObjectDoesNotExistException
   *   If the specified table doesn't exist.
   * @throws DatabaseSchemaObjectExistsException
   *   If a table with the specified new name already exists.
   */
  public abstract function renameTable($table, $new_name);

  /**
   * Drop a table.
   *
   * @param $table
   *   The table to be dropped.
   *
   * @return
   *   TRUE if the table was successfully dropped, FALSE if there was no table
   *   by that name to begin with.
   */
  public abstract function dropTable($table);

  /**
   * Add a new field to a table.
   *
   * @param $table
   *   Name of the table to be altered.
   * @param $field
   *   Name of the field to be added.
   * @param $spec
   *   The field specification array, as taken from a schema definition.
   *   The specification may also contain the key 'initial', the newly
   *   created field will be set to the value of the key in all rows.
   *   This is most useful for creating NOT NULL columns with no default
   *   value in existing tables.
   * @param $keys_new
   *   (optional) Keys and indexes specification to be created on the
   *   table along with adding the field. The format is the same as a
   *   table specification but without the 'fields' element. If you are
   *   adding a type 'serial' field, you MUST specify at least one key
   *   or index including it in this array. See db_change_field() for more
   *   explanation why.
   *
   * @throws DatabaseSchemaObjectDoesNotExistException
   *   If the specified table doesn't exist.
   * @throws DatabaseSchemaObjectExistsException
   *   If the specified table already has a field by that name.
   */
  public abstract function addField($table, $field, $spec, $keys_new = array());

  /**
   * Drop a field.
   *
   * @param $table
   *   The table to be altered.
   * @param $field
   *   The field to be dropped.
   *
   * @return
   *   TRUE if the field was successfully dropped, FALSE if there was no field
   *   by that name to begin with.
   */
  public abstract function dropField($table, $field);

  /**
   * Set the default value for a field.
   *
   * @param $table
   *   The table to be altered.
   * @param $field
   *   The field to be altered.
   * @param $default
   *   Default value to be set. NULL for 'default NULL'.
   *
   * @throws DatabaseSchemaObjectDoesNotExistException
   *   If the specified table or field doesn't exist.
   */
  public abstract function fieldSetDefault($table, $field, $default);

  /**
   * Set a field to have no default value.
   *
   * @param $table
   *   The table to be altered.
   * @param $field
   *   The field to be altered.
   *
   * @throws DatabaseSchemaObjectDoesNotExistException
   *   If the specified table or field doesn't exist.
   */
  public abstract function fieldSetNoDefault($table, $field);

  /**
   * Checks if an index exists in the given table.
   *
   * @param $table
   *   The name of the table in drupal (no prefixing).
   * @param $name
   *   The name of the index in drupal (no prefixing).
   *
   * @return
   *   TRUE if the given index exists, otherwise FALSE.
   */
  public abstract function indexExists($table, $name);

  /**
   * Add a primary key.
   *
   * @param $table
   *   The table to be altered.
   * @param $fields
   *   Fields for the primary key.
   *
   * @throws DatabaseSchemaObjectDoesNotExistException
   *   If the specified table doesn't exist.
   * @throws DatabaseSchemaObjectExistsException
   *   If the specified table already has a primary key.
   */
  public abstract function addPrimaryKey($table, $fields);

  /**
   * Drop the primary key.
   *
   * @param $table
   *   The table to be altered.
   *
   * @return
   *   TRUE if the primary key was successfully dropped, FALSE if there was no
   *   primary key on this table to begin with.
   */
  public abstract function dropPrimaryKey($table);

  /**
   * Add a unique key.
   *
   * @param $table
   *   The table to be altered.
   * @param $name
   *   The name of the key.
   * @param $fields
   *   An array of field names.
   *
   * @throws DatabaseSchemaObjectDoesNotExistException
   *   If the specified table doesn't exist.
   * @throws DatabaseSchemaObjectExistsException
   *   If the specified table already has a key by that name.
   */
  public abstract function addUniqueKey($table, $name, $fields);

  /**
   * Drop a unique key.
   *
   * @param $table
   *   The table to be altered.
   * @param $name
   *   The name of the key.
   *
   * @return
   *   TRUE if the key was successfully dropped, FALSE if there was no key by
   *   that name to begin with.
   */
  public abstract function dropUniqueKey($table, $name);

  /**
   * Add an index.
   *
   * @param $table
   *   The table to be altered.
   * @param $name
   *   The name of the index.
   * @param $fields
   *   An array of field names.
   *
   * @throws DatabaseSchemaObjectDoesNotExistException
   *   If the specified table doesn't exist.
   * @throws DatabaseSchemaObjectExistsException
   *   If the specified table already has an index by that name.
   */
  public abstract function addIndex($table, $name, $fields);

  /**
   * Drop an index.
   *
   * @param $table
   *   The table to be altered.
   * @param $name
   *   The name of the index.
   *
   * @return
   *   TRUE if the index was successfully dropped, FALSE if there was no index
   *   by that name to begin with.
   */
  public abstract function dropIndex($table, $name);

  /**
   * Change a field definition.
   *
   * IMPORTANT NOTE: To maintain database portability, you have to explicitly
   * recreate all indices and primary keys that are using the changed field.
   *
   * That means that you have to drop all affected keys and indexes with
   * db_drop_{primary_key,unique_key,index}() before calling db_change_field().
   * To recreate the keys and indices, pass the key definitions as the
   * optional $keys_new argument directly to db_change_field().
   *
   * For example, suppose you have:
   * @code
   * $schema['foo'] = array(
   *   'fields' => array(
   *     'bar' => array('type' => 'int', 'not null' => TRUE)
   *   ),
   *   'primary key' => array('bar')
   * );
   * @endcode
   * and you want to change foo.bar to be type serial, leaving it as the
   * primary key. The correct sequence is:
   * @code
   * db_drop_primary_key('foo');
   * db_change_field('foo', 'bar', 'bar',
   *   array('type' => 'serial', 'not null' => TRUE),
   *   array('primary key' => array('bar')));
   * @endcode
   *
   * The reasons for this are due to the different database engines:
   *
   * On PostgreSQL, changing a field definition involves adding a new field
   * and dropping an old one which* causes any indices, primary keys and
   * sequences (from serial-type fields) that use the changed field to be dropped.
   *
   * On MySQL, all type 'serial' fields must be part of at least one key
   * or index as soon as they are created. You cannot use
   * db_add_{primary_key,unique_key,index}() for this purpose because
   * the ALTER TABLE command will fail to add the column without a key
   * or index specification. The solution is to use the optional
   * $keys_new argument to create the key or index at the same time as
   * field.
   *
   * You could use db_add_{primary_key,unique_key,index}() in all cases
   * unless you are converting a field to be type serial. You can use
   * the $keys_new argument in all cases.
   *
   * @param $table
   *   Name of the table.
   * @param $field
   *   Name of the field to change.
   * @param $field_new
   *   New name for the field (set to the same as $field if you don't want to change the name).
   * @param $spec
   *   The field specification for the new field.
   * @param $keys_new
   *   (optional) Keys and indexes specification to be created on the
   *   table along with changing the field. The format is the same as a
   *   table specification but without the 'fields' element.
   *
   * @throws DatabaseSchemaObjectDoesNotExistException
   *   If the specified table or source field doesn't exist.
   * @throws DatabaseSchemaObjectExistsException
   *   If the specified destination field already exists.
   */
  public abstract function changeField($table, $field, $field_new, $spec, $keys_new = array());

  /**
   * Create a new table from a Drupal table definition.
   *
   * @param $name
   *   The name of the table to create.
   * @param $table
   *   A Schema API table definition array.
   *
   * @throws DatabaseSchemaObjectExistsException
   *   If the specified table already exists.
   */
  public function createTable($name, $table) {
    if ($this
      ->tableExists($name)) {
      throw new DatabaseSchemaObjectExistsException(t('Table @name already exists.', array(
        '@name' => $name,
      )));
    }
    $statements = $this
      ->createTableSql($name, $table);
    foreach ($statements as $statement) {
      $this->connection
        ->query($statement);
    }
  }

  /**
   * Return an array of field names from an array of key/index column specifiers.
   *
   * This is usually an identity function but if a key/index uses a column prefix
   * specification, this function extracts just the name.
   *
   * @param $fields
   *   An array of key/index column specifiers.
   *
   * @return
   *   An array of field names.
   */
  public function fieldNames($fields) {
    $return = array();
    foreach ($fields as $field) {
      if (is_array($field)) {
        $return[] = $field[0];
      }
      else {
        $return[] = $field;
      }
    }
    return $return;
  }

  /**
   * Prepare a table or column comment for database query.
   *
   * @param $comment
   *   The comment string to prepare.
   * @param $length
   *   Optional upper limit on the returned string length.
   *
   * @return
   *   The prepared comment.
   */
  public function prepareComment($comment, $length = NULL) {
    return $this->connection
      ->quote($comment);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DatabaseSchema::$connection protected property The database connection.
DatabaseSchema::$defaultSchema protected property Definition of prefixInfo array structure. 1
DatabaseSchema::$placeholder protected property The placeholder counter.
DatabaseSchema::$uniqueIdentifier protected property A unique identifier for this query object.
DatabaseSchema::addField abstract public function Add a new field to a table. 3
DatabaseSchema::addIndex abstract public function Add an index. 3
DatabaseSchema::addPrimaryKey abstract public function Add a primary key. 3
DatabaseSchema::addUniqueKey abstract public function Add a unique key. 3
DatabaseSchema::buildTableNameCondition protected function Build a condition to match a table name against a standard information_schema. 1
DatabaseSchema::changeField abstract public function Change a field definition. 3
DatabaseSchema::createTable public function Create a new table from a Drupal table definition.
DatabaseSchema::dropField abstract public function Drop a field. 3
DatabaseSchema::dropIndex abstract public function Drop an index. 3
DatabaseSchema::dropPrimaryKey abstract public function Drop the primary key. 3
DatabaseSchema::dropTable abstract public function Drop a table. 3
DatabaseSchema::dropUniqueKey abstract public function Drop a unique key. 3
DatabaseSchema::fieldExists public function Check if a column exists in the given table. 2
DatabaseSchema::fieldNames public function Return an array of field names from an array of key/index column specifiers.
DatabaseSchema::fieldSetDefault abstract public function Set the default value for a field. 3
DatabaseSchema::fieldSetNoDefault abstract public function Set a field to have no default value. 3
DatabaseSchema::findTables public function Find all tables that are like the specified base table name. 1
DatabaseSchema::findTablesD8 public function Finds all tables that are like the specified base table name. This is a backport of the change made to findTables in Drupal 8 to work with virtual, un-prefixed table names. The original function is retained for Backwards Compatibility. 1
DatabaseSchema::getFieldTypeMap abstract public function Returns a mapping of Drupal schema field names to DB-native field types. 3
DatabaseSchema::getPrefixInfo protected function Get information about the table name and schema from the prefix. 1
DatabaseSchema::indexExists abstract public function Checks if an index exists in the given table. 3
DatabaseSchema::nextPlaceholder public function Implements QueryPlaceHolderInterface::nextPlaceholder(). Overrides QueryPlaceholderInterface::nextPlaceholder
DatabaseSchema::prefixNonTable function Create names for indexes, primary keys and constraints.
DatabaseSchema::prepareComment public function Prepare a table or column comment for database query. 1
DatabaseSchema::renameTable abstract public function Rename a table. 3
DatabaseSchema::tableExists public function Check if a table exists. 2
DatabaseSchema::uniqueIdentifier public function Implements QueryPlaceHolderInterface::uniqueIdentifier(). Overrides QueryPlaceholderInterface::uniqueIdentifier
DatabaseSchema::__clone public function Implements the magic __clone function.
DatabaseSchema::__construct public function