Update to Drupal 8.0.0-beta15. For more information, see: https://www.drupal.org/node/2563023

This commit is contained in:
Pantheon Automation 2015-09-04 13:20:09 -07:00 committed by Greg Anderson
parent 2720a9ec4b
commit f3791f1da3
1898 changed files with 54300 additions and 11481 deletions

View file

@ -138,6 +138,13 @@ abstract class Connection {
*/
protected $prefixReplace = array();
/**
* List of un-prefixed table names, keyed by prefixed table names.
*
* @var array
*/
protected $unprefixedTablesMap = [];
/**
* Constructs a Connection object.
*
@ -185,7 +192,9 @@ abstract class Connection {
// Destroy all references to this connection by setting them to NULL.
// The Statement class attribute only accepts a new value that presents a
// proper callable, so we reset it to PDOStatement.
$this->connection->setAttribute(\PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array()));
if (!empty($this->statementClass)) {
$this->connection->setAttribute(\PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array()));
}
$this->schema = NULL;
}
@ -289,6 +298,13 @@ abstract class Connection {
$this->prefixReplace[] = $this->prefixes['default'];
$this->prefixSearch[] = '}';
$this->prefixReplace[] = '';
// Set up a map of prefixed => un-prefixed tables.
foreach ($this->prefixes as $table_name => $prefix) {
if ($table_name !== 'default') {
$this->unprefixedTablesMap[$prefix . $table_name] = $table_name;
}
}
}
/**
@ -327,6 +343,17 @@ abstract class Connection {
}
}
/**
* Gets a list of individually prefixed table names.
*
* @return array
* An array of un-prefixed table names, keyed by their fully qualified table
* names (i.e. prefix + table_name).
*/
public function getUnprefixedTablesMap() {
return $this->unprefixedTablesMap;
}
/**
* Get a fully qualified table name.
*
@ -502,7 +529,7 @@ abstract class Connection {
* A sanitized version of the query comment string.
*/
protected function filterComment($comment = '') {
return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment);
return strtr($comment, ['*' => ' * ']);
}
/**
@ -786,6 +813,23 @@ abstract class Connection {
return new $class($this, $table, $options);
}
/**
* Prepares and returns an UPSERT query object.
*
* @param string $table
* The table to use for the upsert query.
* @param array $options
* (optional) An array of options on the query.
*
* @return \Drupal\Core\Database\Query\Upsert
* A new Upsert query object.
*
* @see \Drupal\Core\Database\Query\Upsert
*/
public function upsert($table, array $options = array()) {
$class = $this->getDriverClass('Upsert');
return new $class($this, $table, $options);
}
/**
* Prepares and returns an UPDATE query object.
@ -1216,6 +1260,13 @@ abstract class Connection {
return $this->connection->getAttribute(\PDO::ATTR_SERVER_VERSION);
}
/**
* Returns the version of the database client.
*/
public function clientVersion() {
return $this->connection->getAttribute(\PDO::ATTR_CLIENT_VERSION);
}
/**
* Determines if this driver supports transactions.
*

View file

@ -28,6 +28,11 @@ class Connection extends DatabaseConnection {
*/
const DATABASE_NOT_FOUND = 1049;
/**
* Error code for "Can't initialize character set" error.
*/
const UNSUPPORTED_CHARSET = 2019;
/**
* Flag to indicate if the cleanup function in __destruct() should run.
*
@ -82,6 +87,13 @@ class Connection extends DatabaseConnection {
* {@inheritdoc}
*/
public static function open(array &$connection_options = array()) {
if (isset($connection_options['_dsn_utf8_fallback']) && $connection_options['_dsn_utf8_fallback'] === TRUE) {
// Only used during the installer version check, as a fallback from utf8mb4.
$charset = 'utf8';
}
else {
$charset = 'utf8mb4';
}
// The DSN should use either a socket or a host/port.
if (isset($connection_options['unix_socket'])) {
$dsn = 'mysql:unix_socket=' . $connection_options['unix_socket'];
@ -93,7 +105,7 @@ class Connection extends DatabaseConnection {
// Character set is added to dsn to ensure PDO uses the proper character
// set when escaping. This has security implications. See
// https://www.drupal.org/node/1201452 for further discussion.
$dsn .= ';charset=utf8mb4';
$dsn .= ';charset=' . $charset;
if (!empty($connection_options['database'])) {
$dsn .= ';dbname=' . $connection_options['database'];
}
@ -124,10 +136,10 @@ class Connection extends DatabaseConnection {
// certain one has been set; otherwise, MySQL defaults to
// 'utf8mb4_general_ci' for utf8mb4.
if (!empty($connection_options['collation'])) {
$pdo->exec('SET NAMES utf8mb4 COLLATE ' . $connection_options['collation']);
$pdo->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']);
}
else {
$pdo->exec('SET NAMES utf8mb4');
$pdo->exec('SET NAMES ' . $charset);
}
// Set MySQL init_commands if not already defined. Default Drupal's MySQL

View file

@ -55,30 +55,7 @@ class Insert extends QueryInsert {
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$max_placeholder = 0;
$values = array();
if (count($this->insertValues)) {
foreach ($this->insertValues as $insert_values) {
$placeholders = array();
// Default fields aren't really placeholders, but this is the most convenient
// way to handle them.
$placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
$new_placeholder = $max_placeholder + count($insert_values);
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
$placeholders[] = ':db_insert_placeholder_' . $i;
}
$max_placeholder = $new_placeholder;
$values[] = '(' . implode(', ', $placeholders) . ')';
}
}
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($this->defaultFields), 'default');
$values[] = '(' . implode(', ', $placeholders) . ')';
}
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
return $query;

View file

@ -16,6 +16,17 @@ use Drupal\Core\Database\DatabaseNotFoundException;
* Specifies installation tasks for MySQL and equivalent databases.
*/
class Tasks extends InstallTasks {
/**
* Minimum required MySQLnd version.
*/
const MYSQLND_MINIMUM_VERSION = '5.0.9';
/**
* Minimum required libmysqlclient version.
*/
const LIBMYSQLCLIENT_MINIMUM_VERSION = '5.5.3';
/**
* The PDO driver name for MySQL and equivalent databases.
*
@ -27,13 +38,6 @@ class Tasks extends InstallTasks {
* Constructs a \Drupal\Core\Database\Driver\mysql\Install\Tasks object.
*/
public function __construct() {
$this->tasks[] = array(
'arguments' => array(
'SET NAMES utf8mb4',
'The %name database server supports utf8mb4 character encoding.',
'The %name database server must support utf8mb4 character encoding to work with Drupal. Make sure to use a database server that supports utf8mb4 character encoding, such as MySQL/MariaDB/Percona versions 5.5.3 and up.',
),
);
$this->tasks[] = array(
'arguments' => array(),
'function' => 'ensureInnoDbAvailable',
@ -62,7 +66,34 @@ class Tasks extends InstallTasks {
// This doesn't actually test the connection.
db_set_active();
// Now actually do a check.
Database::getConnection();
try {
Database::getConnection();
}
catch (\Exception $e) {
// Detect utf8mb4 incompability.
if ($e->getCode() == Connection::UNSUPPORTED_CHARSET) {
$this->fail(t('Your MySQL server and PHP MySQL driver must support utf8mb4 character encoding. Make sure to use a database system that supports this (such as MySQL/MariaDB/Percona 5.5.3 and up), and that the utf8mb4 character set is compiled in. See the <a href="@documentation" target="_blank">MySQL documentation</a> for more information.', array('@documentation' => 'https://dev.mysql.com/doc/refman/5.0/en/cannot-initialize-character-set.html')));
$info = Database::getConnectionInfo();
$info_copy = $info;
// Set a flag to fall back to utf8. Note: this flag should only be
// used here and is for internal use only.
$info_copy['default']['_dsn_utf8_fallback'] = TRUE;
// In order to change the Database::$databaseInfo array, we need to
// remove the active connection, then re-add it with the new info.
Database::removeConnection('default');
Database::addConnectionInfo('default', 'default', $info_copy['default']);
// Connect with the new database info, using the utf8 character set so
// that we can run the checkEngineVersion test.
Database::getConnection();
// Revert to the old settings.
Database::removeConnection('default');
Database::addConnectionInfo('default', 'default', $info['default']);
}
else {
// Rethrow the exception.
throw $e;
}
}
$this->pass('Drupal can CONNECT to the database ok.');
}
catch (\Exception $e) {
@ -121,4 +152,27 @@ class Tasks extends InstallTasks {
}
}
/**
* {@inheritdoc}
*/
protected function checkEngineVersion() {
parent::checkEngineVersion();
// Ensure that the MySQL driver supports utf8mb4 encoding.
$version = Database::getConnection()->clientVersion();
if (FALSE !== strpos($version, 'mysqlnd')) {
// The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
$version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
if (version_compare($version, self::MYSQLND_MINIMUM_VERSION, '<')) {
$this->fail(t("The MySQLnd driver version %version is less than the minimum required version. Upgrade to MySQLnd version %mysqlnd_minimum_version or up, or alternatively switch mysql drivers to libmysqlclient version %libmysqlclient_minimum_version or up.", array('%version' => Database::getConnection()->version(), '%mysqlnd_minimum_version' => self::MYSQLND_MINIMUM_VERSION, '%libmysqlclient_minimum_version' => self::LIBMYSQLCLIENT_MINIMUM_VERSION)));
}
}
else {
// The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
if (version_compare($version, self::LIBMYSQLCLIENT_MINIMUM_VERSION, '<')) {
$this->fail(t("The libmysqlclient driver version %version is less than the minimum required version. Upgrade to libmysqlclient version %libmysqlclient_minimum_version or up, or alternatively switch mysql drivers to MySQLnd version %mysqlnd_minimum_version or up.", array('%version' => Database::getConnection()->version(), '%libmysqlclient_minimum_version' => self::LIBMYSQLCLIENT_MINIMUM_VERSION, '%mysqlnd_minimum_version' => self::MYSQLND_MINIMUM_VERSION)));
}
}
}
}

View file

@ -9,6 +9,7 @@ namespace Drupal\Core\Database\Driver\mysql;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Database\SchemaException;
use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
use Drupal\Core\Database\Schema as DatabaseSchema;
@ -60,8 +61,7 @@ class Schema extends DatabaseSchema {
$info['table'] = substr($table, ++$pos);
}
else {
$db_info = Database::getConnectionInfo();
$info['database'] = $db_info[$this->connection->getTarget()]['database'];
$info['database'] = $this->connection->getConnectionOptions()['database'];
$info['table'] = $table;
}
return $info;
@ -299,14 +299,17 @@ class Schema extends DatabaseSchema {
* Shortens indexes to 191 characters if they apply to utf8mb4-encoded
* fields, in order to comply with the InnoDB index limitation of 756 bytes.
*
* @param $spec
* @param array $spec
* The table specification.
*
* @return array
* List of shortened indexes.
*
* @throws \Drupal\Core\Database\SchemaException
* Thrown if field specification is missing.
*/
protected function getNormalizedIndexes($spec) {
$indexes = $spec['indexes'];
protected function getNormalizedIndexes(array $spec) {
$indexes = isset($spec['indexes']) ? $spec['indexes'] : [];
foreach ($indexes as $index_name => $index_fields) {
foreach ($index_fields as $index_key => $index_field) {
// Get the name of the field from the index specification.
@ -323,6 +326,9 @@ class Schema extends DatabaseSchema {
}
}
}
else {
throw new SchemaException("MySQL needs the '$field_name' field specification in order to normalize the '$index_name' index");
}
}
}
return $indexes;
@ -486,7 +492,10 @@ class Schema extends DatabaseSchema {
return TRUE;
}
public function addIndex($table, $name, $fields) {
/**
* {@inheritdoc}
*/
public function addIndex($table, $name, $fields, array $spec) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
}
@ -494,7 +503,10 @@ class Schema extends DatabaseSchema {
throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
}
$this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')');
$spec['indexes'][$name] = $fields;
$indexes = $this->getNormalizedIndexes($spec);
$this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($indexes[$name]) . ')');
}
public function dropIndex($table, $name) {

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Driver\mysql\Upsert.
*/
namespace Drupal\Core\Database\Driver\mysql;
use Drupal\Core\Database\Query\Upsert as QueryUpsert;
/**
* Implements the Upsert query for the MySQL database driver.
*/
class Upsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
// Updating the unique / primary key is not necessary.
unset($insert_fields[$this->key]);
$update = [];
foreach ($insert_fields as $field) {
$update[] = "$field = VALUES($field)";
}
$query .= ' ON DUPLICATE KEY UPDATE ' . implode(', ', $update);
return $query;
}
}

View file

@ -383,6 +383,22 @@ class Connection extends DatabaseConnection {
$this->rollback($savepoint_name);
}
}
/**
* {@inheritdoc}
*/
public function upsert($table, array $options = array()) {
// Use the (faster) native Upsert implementation for PostgreSQL >= 9.5.
if (version_compare($this->version(), '9.5', '>=')) {
$class = $this->getDriverClass('NativeUpsert');
}
else {
$class = $this->getDriverClass('Upsert');
}
return new $class($this, $table, $options);
}
}
/**

View file

@ -128,30 +128,7 @@ class Insert extends QueryInsert {
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$max_placeholder = 0;
$values = array();
if (count($this->insertValues)) {
foreach ($this->insertValues as $insert_values) {
$placeholders = array();
// Default fields aren't really placeholders, but this is the most convenient
// way to handle them.
$placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
$new_placeholder = $max_placeholder + count($insert_values);
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
$placeholders[] = ':db_insert_placeholder_' . $i;
}
$max_placeholder = $new_placeholder;
$values[] = '(' . implode(', ', $placeholders) . ')';
}
}
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($this->defaultFields), 'default');
$values[] = '(' . implode(', ', $placeholders) . ')';
}
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
return $query;

View file

@ -180,8 +180,8 @@ class Tasks extends InstallTasks {
* Verify that a binary data roundtrip returns the original string.
*/
protected function checkBinaryOutputSuccess() {
$bytea_output = db_query("SELECT 'encoding'::bytea AS output")->fetchField();
return ($bytea_output == 'encoding');
$bytea_output = db_query("SHOW bytea_output")->fetchField();
return ($bytea_output == 'escape');
}
/**
@ -192,17 +192,9 @@ class Tasks extends InstallTasks {
// like we do with table names. This is so that we don't double up if more
// than one instance of Drupal is running on a single database. We therefore
// avoid trying to create them again in that case.
// At the same time checking for the existence of the function fixes
// concurrency issues, when both try to update at the same time.
try {
// Create functions.
db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS
\'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\'
LANGUAGE \'sql\''
);
db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS
\'SELECT greatest($1, greatest($2, $3));\'
LANGUAGE \'sql\''
);
// Don't use {} around pg_proc table.
if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
@ -211,37 +203,17 @@ class Tasks extends InstallTasks {
);
}
db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
\'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
LANGUAGE \'sql\''
);
// Using || to concatenate in Drupal is not recommended because there are
// database drivers for Drupal that do not support the syntax, however
// they do support CONCAT(item1, item2) which we can replicate in
// PostgreSQL. PostgreSQL requires the function to be defined for each
// different argument variation the function can handle.
db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, anynonarray) RETURNS text AS
\'SELECT CAST($1 AS text) || CAST($2 AS text);\'
LANGUAGE \'sql\'
');
db_query('CREATE OR REPLACE FUNCTION "concat"(text, anynonarray) RETURNS text AS
\'SELECT $1 || CAST($2 AS text);\'
LANGUAGE \'sql\'
');
db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, text) RETURNS text AS
\'SELECT CAST($1 AS text) || $2;\'
LANGUAGE \'sql\'
');
db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS
\'SELECT $1 || $2;\'
LANGUAGE \'sql\'
');
if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'substring_index'")->fetchField()) {
db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
\'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
LANGUAGE \'sql\''
);
}
$this->pass(t('PostgreSQL has initialized itself.'));
}
catch (\Exception $e) {
$this->fail(t('Drupal could not be correctly setup with the existing database. Revise any errors.'));
$this->fail(t('Drupal could not be correctly setup with the existing database due to the following error: @error.', ['@error' => $e->getMessage()]));
}
}

View file

@ -0,0 +1,116 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Driver\pgsql\NativeUpsert.
*/
namespace Drupal\Core\Database\Driver\pgsql;
use Drupal\Core\Database\Query\Upsert as QueryUpsert;
/**
* Implements the native Upsert query for the PostgreSQL database driver.
*
* @see http://www.postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT
*/
class NativeUpsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
$stmt = $this->connection->prepareQuery((string) $this);
// Fetch the list of blobs and sequences used on that table.
$table_information = $this->connection->schema()->queryTableInformation($this->table);
$max_placeholder = 0;
$blobs = [];
$blob_count = 0;
foreach ($this->insertValues as $insert_values) {
foreach ($this->insertFields as $idx => $field) {
if (isset($table_information->blob_fields[$field])) {
$blobs[$blob_count] = fopen('php://memory', 'a');
fwrite($blobs[$blob_count], $insert_values[$idx]);
rewind($blobs[$blob_count]);
$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], \PDO::PARAM_LOB);
// Pre-increment is faster in PHP than increment.
++$blob_count;
}
else {
$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $insert_values[$idx]);
}
}
// Check if values for a serial field has been passed.
if (!empty($table_information->serial_fields)) {
foreach ($table_information->serial_fields as $index => $serial_field) {
$serial_key = array_search($serial_field, $this->insertFields);
if ($serial_key !== FALSE) {
$serial_value = $insert_values[$serial_key];
// Sequences must be greater than or equal to 1.
if ($serial_value === NULL || !$serial_value) {
$serial_value = 1;
}
// Set the sequence to the bigger value of either the passed
// value or the max value of the column. It can happen that another
// thread calls nextval() which could lead to a serial number being
// used twice. However, trying to insert a value into a serial
// column should only be done in very rare cases and is not thread
// safe by definition.
$this->connection->query("SELECT setval('" . $table_information->sequences[$index] . "', GREATEST(MAX(" . $serial_field . "), :serial_value)) FROM {" . $this->table . "}", array(':serial_value' => (int)$serial_value));
}
}
}
}
$options = $this->queryOptions;
if (!empty($table_information->sequences)) {
$options['sequence_name'] = $table_information->sequences[0];
}
$this->connection->query($stmt, [], $options);
// Re-initialize the values array so that we can re-use this query.
$this->insertValues = [];
return TRUE;
}
/**
* {@inheritdoc}
*/
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$insert_fields = array_map(function($f) { return $this->connection->escapeField($f); }, $insert_fields);
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
// Updating the unique / primary key is not necessary.
unset($insert_fields[$this->key]);
$update = [];
foreach ($insert_fields as $field) {
$update[] = "$field = EXCLUDED.$field";
}
$query .= ' ON CONFLICT (' . $this->connection->escapeField($this->key) . ') DO UPDATE SET ' . implode(', ', $update);
return $query;
}
}

View file

@ -93,9 +93,17 @@ class Schema extends DatabaseSchema {
public function queryTableInformation($table) {
// Generate a key to reference this table's information on.
$key = $this->connection->prefixTables('{' . $table . '}');
if (strpos($key, '.') === FALSE) {
// Take into account that temporary tables are stored in a different schema.
// \Drupal\Core\Database\Connection::generateTemporaryTableName() sets the
// 'db_temporary_' prefix to all temporary tables.
if (strpos($key, '.') === FALSE && strpos($table, 'db_temporary_') === FALSE) {
$key = 'public.' . $key;
}
else {
$schema = $this->connection->query('SELECT nspname FROM pg_namespace WHERE oid = pg_my_temp_schema()')->fetchField();
$key = $schema . '.' . $key;
}
if (!isset($this->tableInformation[$key])) {
// Split the key into schema and table for querying.
@ -580,13 +588,28 @@ class Schema extends DatabaseSchema {
/**
* Helper function: check if a constraint (PK, FK, UK) exists.
*
* @param $table
* @param string $table
* The name of the table.
* @param $name
* The name of the constraint (typically 'pkey' or '[constraint]_key').
* @param string $name
* The name of the constraint (typically 'pkey' or '[constraint]__key').
*
* @return bool
* TRUE if the constraint exists, FALSE otherwise.
*/
public function constraintExists($table, $name) {
$constraint_name = $this->ensureIdentifiersLength($table, $name);
// ::ensureIdentifiersLength() expects three parameters, although not
// explicitly stated in its signature, thus we split our constraint name in
// a proper name and a suffix.
if ($name == 'pkey') {
$suffix = $name;
$name = '';
}
else {
$pos = strrpos($name, '__');
$suffix = substr($name, $pos + 2);
$name = substr($name, 0, $pos);
}
$constraint_name = $this->ensureIdentifiersLength($table, $name, $suffix);
// Remove leading and trailing quotes because the index name is in a WHERE
// clause and not used as an identifier.
$constraint_name = str_replace('"', '', $constraint_name);
@ -637,7 +660,10 @@ class Schema extends DatabaseSchema {
return TRUE;
}
public function addIndex($table, $name, $fields) {
/**
* {@inheritdoc}
*/
public function addIndex($table, $name, $fields, array $spec) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
}
@ -779,7 +805,10 @@ class Schema extends DatabaseSchema {
}
if (isset($new_keys['indexes'])) {
foreach ($new_keys['indexes'] as $name => $fields) {
$this->addIndex($table, $name, $fields);
// Even though $new_keys is not a full schema it still has 'indexes' and
// so is a partial schema. Technically addIndex() doesn't do anything
// with it so passing an empty array would work as well.
$this->addIndex($table, $name, $fields, $new_keys);
}
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Driver\pgsql\Upsert.
*/
namespace Drupal\Core\Database\Driver\pgsql;
use Drupal\Core\Database\Query\Upsert as QueryUpsert;
/**
* Implements the Upsert query for the PostgreSQL database driver.
*/
class Upsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
// Default options for upsert queries.
$this->queryOptions += array(
'throw_exception' => TRUE,
);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$table = $this->connection->escapeTable($this->table);
// We have to execute multiple queries, therefore we wrap everything in a
// transaction so that it is atomic where possible.
$transaction = $this->connection->startTransaction();
try {
// First, lock the table we're upserting into.
$this->connection->query('LOCK TABLE {' . $table . '} IN SHARE ROW EXCLUSIVE MODE', [], $this->queryOptions);
// Second, delete all items first so we can do one insert.
$unique_key_position = array_search($this->key, $insert_fields);
$delete_ids = [];
foreach ($this->insertValues as $insert_values) {
$delete_ids[] = $insert_values[$unique_key_position];
}
// Delete in chunks when a large array is passed.
foreach (array_chunk($delete_ids, 1000) as $delete_ids_chunk) {
$this->connection->delete($this->table, $this->queryOptions)
->condition($this->key, $delete_ids_chunk, 'IN')
->execute();
}
// Third, insert all the values.
$insert = $this->connection->insert($this->table, $this->queryOptions)
->fields($insert_fields);
foreach ($this->insertValues as $insert_values) {
$insert->values($insert_values);
}
$insert->execute();
}
catch (\Exception $e) {
// One of the queries failed, rollback the whole batch.
$transaction->rollback();
// Rethrow the exception for the calling code.
throw $e;
}
// Re-initialize the values array so that we can re-use this query.
$this->insertValues = array();
// Transaction commits here where $transaction looses scope.
return TRUE;
}
/**
* {@inheritdoc}
*/
public function __toString() {
// Nothing to do.
}
}

View file

@ -154,9 +154,9 @@ class Connection extends DatabaseConnection {
// We can prune the database file if it doesn't have any tables.
if ($count == 0) {
// Detach the database.
$this->query('DETACH DATABASE :schema', array(':schema' => $prefix));
// Destroy the database file.
// Detaching the database fails at this point, but no other queries
// are executed after the connection is destructed so we can simply
// remove the database file.
unlink($this->connectionOptions['database'] . '-' . $prefix);
}
}
@ -168,6 +168,18 @@ class Connection extends DatabaseConnection {
}
}
/**
* Gets all the attached databases.
*
* @return array
* An array of attached database names.
*
* @see \Drupal\Core\Database\Driver\sqlite\Connection::__construct()
*/
public function getAttachedDatabases() {
return $this->attachedDatabases;
}
/**
* SQLite compatibility implementation for the IF() SQL function.
*/

View file

@ -582,7 +582,10 @@ class Schema extends DatabaseSchema {
return $key_definition;
}
public function addIndex($table, $name, $fields) {
/**
* {@inheritdoc}
*/
public function addIndex($table, $name, $fields, array $spec) {
if (!$this->tableExists($table)) {
throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
}
@ -693,16 +696,31 @@ class Schema extends DatabaseSchema {
$this->alterTable($table, $old_schema, $new_schema);
}
/**
* {@inheritdoc}
*/
public function findTables($table_expression) {
// Don't add the prefix, $table_expression already includes the prefix.
$info = $this->getPrefixInfo($table_expression, FALSE);
$tables = [];
// Can't use query placeholders for the schema because the query would have
// to be :prefixsqlite_master, which does not work.
$result = db_query("SELECT name FROM " . $info['schema'] . ".sqlite_master WHERE type = :type AND name LIKE :table_name", array(
':type' => 'table',
':table_name' => $info['table'],
));
return $result->fetchAllKeyed(0, 0);
// The SQLite implementation doesn't need to use the same filtering strategy
// as the parent one because individually prefixed tables live in their own
// schema (database), which means that neither the main database nor any
// attached one will contain a prefixed table name, so we just need to loop
// over all known schemas and filter by the user-supplied table expression.
$attached_dbs = $this->connection->getAttachedDatabases();
foreach ($attached_dbs as $schema) {
// Can't use query placeholders for the schema because the query would
// have to be :prefixsqlite_master, which does not work. We also need to
// ignore the internal SQLite tables.
$result = db_query("SELECT name FROM " . $schema . ".sqlite_master WHERE type = :type AND name LIKE :table_name AND name NOT LIKE :pattern", array(
':type' => 'table',
':table_name' => $table_expression,
':pattern' => 'sqlite_%',
));
$tables += $result->fetchAllKeyed(0, 0);
}
return $tables;
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Driver\sqlite\Upsert.
*/
namespace Drupal\Core\Database\Driver\sqlite;
use Drupal\Core\Database\Query\Upsert as QueryUpsert;
/**
* Implements the Upsert query for the SQLite database driver.
*/
class Upsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
$query = $comments . 'INSERT OR REPLACE INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
$query .= implode(', ', $values);
return $query;
}
}

View file

@ -7,7 +7,6 @@
namespace Drupal\Core\Database\Install;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Database\Database;
/**
@ -80,7 +79,10 @@ abstract class Tasks {
*
* @var array
*/
protected $results = array();
protected $results = array(
'fail' => array(),
'pass' => array(),
);
/**
* Ensure the PDO driver is supported by the version of PHP in use.
@ -93,14 +95,14 @@ abstract class Tasks {
* Assert test as failed.
*/
protected function fail($message) {
$this->results[$message] = FALSE;
$this->results['fail'][] = $message;
}
/**
* Assert test as a pass.
*/
protected function pass($message) {
$this->results[$message] = TRUE;
$this->results['pass'][] = $message;
}
/**
@ -128,6 +130,9 @@ abstract class Tasks {
/**
* Run database tasks and tests to see if Drupal can run on the database.
*
* @return array
* A list of error messages.
*/
public function runTasks() {
// We need to establish a connection before we can run tests.
@ -143,21 +148,11 @@ abstract class Tasks {
}
}
else {
throw new TaskException(t("Failed to run all tasks against the database server. The task %task wasn't found.", array('%task' => $task['function'])));
$this->fail(t("Failed to run all tasks against the database server. The task %task wasn't found.", array('%task' => $task['function'])));
}
}
}
// Check for failed results and compile message
$message = '';
foreach ($this->results as $result => $success) {
if (!$success) {
$message = SafeMarkup::isSafe($result) ? $result : SafeMarkup::checkPlain($result);
}
}
if (!empty($message)) {
$message = SafeMarkup::set('Resolve all issues below to continue the installation. For help configuring your database server, see the <a href="https://www.drupal.org/getting-started/install">installation handbook</a>, or contact your hosting provider.' . $message);
throw new TaskException($message);
}
return $this->results['fail'];
}
/**
@ -196,8 +191,9 @@ abstract class Tasks {
* Check the engine version.
*/
protected function checkEngineVersion() {
// Ensure that the database server has the right version.
if ($this->minimumVersion() && version_compare(Database::getConnection()->version(), $this->minimumVersion(), '<')) {
$this->fail(t("The database version %version is less than the minimum required version %minimum_version.", array('%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion())));
$this->fail(t("The database server version %version is less than the minimum required version %minimum_version.", array('%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion())));
}
}

View file

@ -16,43 +16,7 @@ use Drupal\Core\Database\Database;
*/
class Insert extends Query {
/**
* The table on which to insert.
*
* @var string
*/
protected $table;
/**
* An array of fields on which to insert.
*
* @var array
*/
protected $insertFields = array();
/**
* An array of fields that should be set to their database-defined defaults.
*
* @var array
*/
protected $defaultFields = array();
/**
* A nested array of values to insert.
*
* $insertValues is an array of arrays. Each sub-array is either an
* associative array whose keys are field names and whose values are field
* values to insert, or a non-associative array of values in the same order
* as $insertFields.
*
* Whether multiple insert sets will be run in a single query or multiple
* queries is left to individual drivers to implement in whatever manner is
* most appropriate. The order of values in each sub-array must match the
* order of fields in $insertFields.
*
* @var array
*/
protected $insertValues = array();
use InsertTrait;
/**
* A SelectQuery object to fetch the rows that should be inserted.
@ -79,96 +43,6 @@ class Insert extends Query {
$this->table = $table;
}
/**
* Adds a set of field->value pairs to be inserted.
*
* This method may only be called once. Calling it a second time will be
* ignored. To queue up multiple sets of values to be inserted at once,
* use the values() method.
*
* @param $fields
* An array of fields on which to insert. This array may be indexed or
* associative. If indexed, the array is taken to be the list of fields.
* If associative, the keys of the array are taken to be the fields and
* the values are taken to be corresponding values to insert. If a
* $values argument is provided, $fields must be indexed.
* @param $values
* An array of fields to insert into the database. The values must be
* specified in the same order as the $fields array.
*
* @return \Drupal\Core\Database\Query\Insert
* The called object.
*/
public function fields(array $fields, array $values = array()) {
if (empty($this->insertFields)) {
if (empty($values)) {
if (!is_numeric(key($fields))) {
$values = array_values($fields);
$fields = array_keys($fields);
}
}
$this->insertFields = $fields;
if (!empty($values)) {
$this->insertValues[] = $values;
}
}
return $this;
}
/**
* Adds another set of values to the query to be inserted.
*
* If $values is a numeric-keyed array, it will be assumed to be in the same
* order as the original fields() call. If it is associative, it may be
* in any order as long as the keys of the array match the names of the
* fields.
*
* @param $values
* An array of values to add to the query.
*
* @return \Drupal\Core\Database\Query\Insert
* The called object.
*/
public function values(array $values) {
if (is_numeric(key($values))) {
$this->insertValues[] = $values;
}
else {
// Reorder the submitted values to match the fields array.
foreach ($this->insertFields as $key) {
$insert_values[$key] = $values[$key];
}
// For consistency, the values array is always numerically indexed.
$this->insertValues[] = array_values($insert_values);
}
return $this;
}
/**
* Specifies fields for which the database defaults should be used.
*
* If you want to force a given field to use the database-defined default,
* not NULL or undefined, use this method to instruct the database to use
* default values explicitly. In most cases this will not be necessary
* unless you are inserting a row that is all default values, as you cannot
* specify no values in an INSERT query.
*
* Specifying a field both in fields() and in useDefaults() is an error
* and will not execute.
*
* @param $fields
* An array of values for which to use the default values
* specified in the table definition.
*
* @return \Drupal\Core\Database\Query\Insert
* The called object.
*/
public function useDefaults(array $fields) {
$this->defaultFields = $fields;
return $this;
}
/**
* Sets the fromQuery on this InsertQuery object.
*
@ -265,13 +139,13 @@ class Insert extends Query {
/**
* Preprocesses and validates the query.
*
* @return
* @return bool
* TRUE if the validation was successful, FALSE if not.
*
* @throws \Drupal\Core\Database\Query\FieldsOverlapException
* @throws \Drupal\Core\Database\Query\NoFieldsException
*/
public function preExecute() {
protected function preExecute() {
// Confirm that the user did not try to specify an identical
// field and default field.
if (array_intersect($this->insertFields, $this->defaultFields)) {

View file

@ -0,0 +1,184 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Query\InsertTrait.
*/
namespace Drupal\Core\Database\Query;
/**
* Provides common functionality for INSERT and UPSERT queries.
*
* @ingroup database
*/
trait InsertTrait {
/**
* The table on which to insert.
*
* @var string
*/
protected $table;
/**
* An array of fields on which to insert.
*
* @var array
*/
protected $insertFields = array();
/**
* An array of fields that should be set to their database-defined defaults.
*
* @var array
*/
protected $defaultFields = array();
/**
* A nested array of values to insert.
*
* $insertValues is an array of arrays. Each sub-array is either an
* associative array whose keys are field names and whose values are field
* values to insert, or a non-associative array of values in the same order
* as $insertFields.
*
* Whether multiple insert sets will be run in a single query or multiple
* queries is left to individual drivers to implement in whatever manner is
* most appropriate. The order of values in each sub-array must match the
* order of fields in $insertFields.
*
* @var array
*/
protected $insertValues = array();
/**
* Adds a set of field->value pairs to be inserted.
*
* This method may only be called once. Calling it a second time will be
* ignored. To queue up multiple sets of values to be inserted at once,
* use the values() method.
*
* @param array $fields
* An array of fields on which to insert. This array may be indexed or
* associative. If indexed, the array is taken to be the list of fields.
* If associative, the keys of the array are taken to be the fields and
* the values are taken to be corresponding values to insert. If a
* $values argument is provided, $fields must be indexed.
* @param array $values
* (optional) An array of fields to insert into the database. The values
* must be specified in the same order as the $fields array.
*
* @return $this
* The called object.
*/
public function fields(array $fields, array $values = array()) {
if (empty($this->insertFields)) {
if (empty($values)) {
if (!is_numeric(key($fields))) {
$values = array_values($fields);
$fields = array_keys($fields);
}
}
$this->insertFields = $fields;
if (!empty($values)) {
$this->insertValues[] = $values;
}
}
return $this;
}
/**
* Adds another set of values to the query to be inserted.
*
* If $values is a numeric-keyed array, it will be assumed to be in the same
* order as the original fields() call. If it is associative, it may be
* in any order as long as the keys of the array match the names of the
* fields.
*
* @param array $values
* An array of values to add to the query.
*
* @return $this
* The called object.
*/
public function values(array $values) {
if (is_numeric(key($values))) {
$this->insertValues[] = $values;
}
elseif ($this->insertFields) {
// Reorder the submitted values to match the fields array.
foreach ($this->insertFields as $key) {
$insert_values[$key] = $values[$key];
}
// For consistency, the values array is always numerically indexed.
$this->insertValues[] = array_values($insert_values);
}
return $this;
}
/**
* Specifies fields for which the database defaults should be used.
*
* If you want to force a given field to use the database-defined default,
* not NULL or undefined, use this method to instruct the database to use
* default values explicitly. In most cases this will not be necessary
* unless you are inserting a row that is all default values, as you cannot
* specify no values in an INSERT query.
*
* Specifying a field both in fields() and in useDefaults() is an error
* and will not execute.
*
* @param array $fields
* An array of values for which to use the default values
* specified in the table definition.
*
* @return $this
* The called object.
*/
public function useDefaults(array $fields) {
$this->defaultFields = $fields;
return $this;
}
/**
* Returns the query placeholders for values that will be inserted.
*
* @param array $nested_insert_values
* A nested array of values to insert.
* @param array $default_fields
* An array of fields that should be set to their database-defined defaults.
*
* @return array
* An array of insert placeholders.
*/
protected function getInsertPlaceholderFragment(array $nested_insert_values, array $default_fields) {
$max_placeholder = 0;
$values = array();
if ($nested_insert_values) {
foreach ($nested_insert_values as $insert_values) {
$placeholders = array();
// Default fields aren't really placeholders, but this is the most convenient
// way to handle them.
$placeholders = array_pad($placeholders, count($default_fields), 'default');
$new_placeholder = $max_placeholder + count($insert_values);
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
$placeholders[] = ':db_insert_placeholder_' . $i;
}
$max_placeholder = $new_placeholder;
$values[] = '(' . implode(', ', $placeholders) . ')';
}
}
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($default_fields), 'default');
$values[] = '(' . implode(', ', $placeholders) . ')';
}
return $values;
}
}

View file

@ -0,0 +1,15 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Query\NoUniqueFieldException.
*/
namespace Drupal\Core\Database\Query;
use Drupal\Core\Database\DatabaseException;
/**
* Exception thrown if an upsert query doesn't specify a unique field.
*/
class NoUniqueFieldException extends \InvalidArgumentException implements DatabaseException {}

View file

@ -147,6 +147,9 @@ interface SelectInterface extends ConditionInterface, AlterableInterface, Extend
* For some database drivers, it may also wrap the field name in
* database-specific escape characters.
*
* @param string $string
* An unsanitized field name.
*
* @return
* The sanitized field name string.
*/

View file

@ -0,0 +1,119 @@
<?php
/**
* @file
* Contains \Drupal\Core\Database\Query\Upsert.
*/
namespace Drupal\Core\Database\Query;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
/**
* General class for an abstracted "Upsert" (UPDATE or INSERT) query operation.
*
* This class can only be used with a table with a single unique index.
* Often, this will be the primary key. On such a table this class works like
* Insert except the rows will be set to the desired values even if the key
* existed before.
*/
abstract class Upsert extends Query {
use InsertTrait;
/**
* The unique or primary key of the table.
*
* @var string
*/
protected $key;
/**
* Constructs an Upsert object.
*
* @param \Drupal\Core\Database\Connection $connection
* A Connection object.
* @param string $table
* Name of the table to associate with this query.
* @param array $options
* (optional) An array of database options.
*/
public function __construct(Connection $connection, $table, array $options = []) {
$options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options);
$this->table = $table;
}
/**
* Sets the unique / primary key field to be used as condition for this query.
*
* @param string $field
* The name of the field to set.
*
* @return $this
*/
public function key($field) {
$this->key = $field;
return $this;
}
/**
* Preprocesses and validates the query.
*
* @return bool
* TRUE if the validation was successful, FALSE otherwise.
*
* @throws \Drupal\Core\Database\Query\NoUniqueFieldException
* @throws \Drupal\Core\Database\Query\FieldsOverlapException
* @throws \Drupal\Core\Database\Query\NoFieldsException
*/
protected function preExecute() {
// Confirm that the user set the unique/primary key of the table.
if (!$this->key) {
throw new NoUniqueFieldException('There is no unique field specified.');
}
// Confirm that the user did not try to specify an identical
// field and default field.
if (array_intersect($this->insertFields, $this->defaultFields)) {
throw new FieldsOverlapException('You may not specify the same field to have a value and a schema-default value.');
}
// Don't execute query without fields.
if (count($this->insertFields) + count($this->defaultFields) == 0) {
throw new NoFieldsException('There are no fields available to insert with.');
}
// If no values have been added, silently ignore this query. This can happen
// if values are added conditionally, so we don't want to throw an
// exception.
return isset($this->insertValues[0]) || $this->insertFields;
}
/**
* {@inheritdoc}
*/
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
$max_placeholder = 0;
$values = array();
foreach ($this->insertValues as $insert_values) {
foreach ($insert_values as $value) {
$values[':db_insert_placeholder_' . $max_placeholder++] = $value;
}
}
$last_insert_id = $this->connection->query((string) $this, $values, $this->queryOptions);
// Re-initialize the values array so that we can re-use this query.
$this->insertValues = array();
return $last_insert_id;
}
}

View file

@ -16,6 +16,11 @@ use Drupal\Core\Database\Query\PlaceholderInterface;
*/
abstract class Schema implements PlaceholderInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
@ -173,25 +178,62 @@ abstract class Schema implements PlaceholderInterface {
}
/**
* Find all tables that are like the specified base table name.
* Finds 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.
* @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.
* @return array
* Both the keys and the values are the matching tables.
*/
public function findTables($table_expression) {
$condition = $this->buildTableNameCondition($table_expression, 'LIKE', FALSE);
// 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 = [];
// 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 FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchAllKeyed(0, 0);
$results = $this->connection->query("SELECT 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;
}
/**
@ -413,13 +455,51 @@ abstract class Schema implements PlaceholderInterface {
* @code
* $fields = ['foo', ['bar', 4]];
* @endcode
* @param array $spec
* The table specification for the table to be altered. This is used in
* order to be able to ensure that the index length is not too long.
* This schema definition can usually be obtained through hook_schema(), or
* in case the table was created by the Entity API, through the schema
* handler listed in the entity class definition. For reference, see
* SqlContentEntityStorageSchema::getDedicatedTableSchema() and
* SqlContentEntityStorageSchema::getSharedTableFieldSchema().
*
* In order to prevent human error, it is recommended to pass in the
* complete table specification. However, in the edge case of the complete
* table specification not being available, we can pass in a partial table
* definition containing only the fields that apply to the index:
* @code
* $spec = [
* // Example partial specification for a table:
* 'fields' => [
* 'example_field' => [
* 'description' => 'An example field',
* 'type' => 'varchar',
* 'length' => 32,
* 'not null' => TRUE,
* 'default' => '',
* ],
* ],
* 'indexes' => [
* 'table_example_field' => ['example_field'],
* ],
* ];
* @endcode
* Note that the above is a partial table definition and that we would
* usually pass a complete table definition as obtained through
* hook_schema() instead.
*
* @see schemaapi
* @see hook_schema()
*
* @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
* If the specified table doesn't exist.
* @throws \Drupal\Core\Database\SchemaObjectExistsException
* If the specified table already has an index by that name.
*
* @todo remove the $spec argument whenever schema introspection is added.
*/
abstract public function addIndex($table, $name, $fields);
abstract public function addIndex($table, $name, $fields, array $spec);
/**
* Drop an index.