Initial Drupal 11 with DDEV setup
This commit is contained in:
370
web/core/modules/mysql/src/Driver/Database/mysql/Connection.php
Normal file
370
web/core/modules/mysql/src/Driver/Database/mysql/Connection.php
Normal file
@ -0,0 +1,370 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\Connection as DatabaseConnection;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\DatabaseAccessDeniedException;
|
||||
use Drupal\Core\Database\DatabaseConnectionRefusedException;
|
||||
use Drupal\Core\Database\DatabaseNotFoundException;
|
||||
use Drupal\Core\Database\StatementWrapperIterator;
|
||||
use Drupal\Core\Database\SupportsTemporaryTablesInterface;
|
||||
use Drupal\Core\Database\Transaction\TransactionManagerInterface;
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* MySQL implementation of \Drupal\Core\Database\Connection.
|
||||
*/
|
||||
class Connection extends DatabaseConnection implements SupportsTemporaryTablesInterface {
|
||||
|
||||
/**
|
||||
* Error code for "Unknown database" error.
|
||||
*/
|
||||
const DATABASE_NOT_FOUND = 1049;
|
||||
|
||||
/**
|
||||
* Error code for "Access denied" error.
|
||||
*/
|
||||
const ACCESS_DENIED = 1045;
|
||||
|
||||
/**
|
||||
* Error code for "Connection refused".
|
||||
*/
|
||||
const CONNECTION_REFUSED = 2002;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $statementWrapperClass = StatementWrapperIterator::class;
|
||||
|
||||
/**
|
||||
* Stores the server version after it has been retrieved from the database.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see \Drupal\mysql\Driver\Database\mysql\Connection::version
|
||||
*/
|
||||
private $serverVersion;
|
||||
|
||||
/**
|
||||
* The minimal possible value for the max_allowed_packet setting of MySQL.
|
||||
*
|
||||
* @link https://mariadb.com/kb/en/mariadb/server-system-variables/#max_allowed_packet
|
||||
* @link https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_allowed_packet
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const MIN_MAX_ALLOWED_PACKET = 1024;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $identifierQuotes = ['"', '"'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(\PDO $connection, array $connection_options) {
|
||||
// If the SQL mode doesn't include 'ANSI_QUOTES' (explicitly or via a
|
||||
// combination mode), then MySQL doesn't interpret a double quote as an
|
||||
// identifier quote, in which case use the non-ANSI-standard backtick.
|
||||
//
|
||||
// @see https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_ansi_quotes
|
||||
$ansi_quotes_modes = ['ANSI_QUOTES', 'ANSI'];
|
||||
$is_ansi_quotes_mode = FALSE;
|
||||
if (isset($connection_options['init_commands']['sql_mode'])) {
|
||||
foreach ($ansi_quotes_modes as $mode) {
|
||||
// None of the modes in $ansi_quotes_modes are substrings of other modes
|
||||
// that are not in $ansi_quotes_modes, so a simple stripos() does not
|
||||
// return false positives.
|
||||
if (stripos($connection_options['init_commands']['sql_mode'], $mode) !== FALSE) {
|
||||
$is_ansi_quotes_mode = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->identifierQuotes === ['"', '"'] && !$is_ansi_quotes_mode) {
|
||||
$this->identifierQuotes = ['`', '`'];
|
||||
}
|
||||
parent::__construct($connection, $connection_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function open(array &$connection_options = []) {
|
||||
// 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'];
|
||||
}
|
||||
else {
|
||||
// Default to TCP connection on port 3306.
|
||||
$dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
|
||||
}
|
||||
// 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';
|
||||
if (!empty($connection_options['database'])) {
|
||||
$dsn .= ';dbname=' . $connection_options['database'];
|
||||
}
|
||||
// Allow PDO options to be overridden.
|
||||
$connection_options += [
|
||||
'pdo' => [],
|
||||
];
|
||||
$connection_options['pdo'] += [
|
||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||||
// So we don't have to mess around with cursors and unbuffered queries by
|
||||
// default.
|
||||
\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
|
||||
// Make sure MySQL returns all matched rows on update queries including
|
||||
// rows that actually didn't have to be updated because the values didn't
|
||||
// change. This matches common behavior among other database systems.
|
||||
\PDO::MYSQL_ATTR_FOUND_ROWS => TRUE,
|
||||
// Because MySQL's prepared statements skip the query cache, because it's
|
||||
// dumb.
|
||||
\PDO::ATTR_EMULATE_PREPARES => TRUE,
|
||||
// Limit SQL to a single statement like mysqli.
|
||||
\PDO::MYSQL_ATTR_MULTI_STATEMENTS => FALSE,
|
||||
// Convert numeric values to strings when fetching. In PHP 8.1,
|
||||
// \PDO::ATTR_EMULATE_PREPARES now behaves the same way as non emulated
|
||||
// prepares and returns integers. See https://externals.io/message/113294
|
||||
// for further discussion.
|
||||
\PDO::ATTR_STRINGIFY_FETCHES => TRUE,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new \PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
|
||||
}
|
||||
catch (\PDOException $e) {
|
||||
switch ($e->getCode()) {
|
||||
case static::CONNECTION_REFUSED:
|
||||
if (isset($connection_options['unix_socket'])) {
|
||||
// Show message for socket connection via 'unix_socket' option.
|
||||
$message = 'Drupal is configured to connect to the database server via a socket, but the socket file could not be found.';
|
||||
$message .= ' This message normally means that there is no MySQL server running on the system or that you are using an incorrect Unix socket file name when trying to connect to the server.';
|
||||
throw new DatabaseConnectionRefusedException($e->getMessage() . ' [Tip: ' . $message . '] ', $e->getCode(), $e);
|
||||
}
|
||||
if (isset($connection_options['host']) && in_array(strtolower($connection_options['host']), ['', 'localhost'], TRUE)) {
|
||||
// Show message for socket connection via 'host' option.
|
||||
$message = 'Drupal was attempting to connect to the database server via a socket, but the socket file could not be found.';
|
||||
$message .= ' A Unix socket file is used if you do not specify a host name or if you specify the special host name localhost.';
|
||||
$message .= ' To connect via TCP/IP use an IP address (127.0.0.1 for IPv4) instead of "localhost".';
|
||||
$message .= ' This message normally means that there is no MySQL server running on the system or that you are using an incorrect Unix socket file name when trying to connect to the server.';
|
||||
throw new DatabaseConnectionRefusedException($e->getMessage() . ' [Tip: ' . $message . '] ', $e->getCode(), $e);
|
||||
}
|
||||
// Show message for TCP/IP connection.
|
||||
$message = 'This message normally means that there is no MySQL server running on the system or that you are using an incorrect host name or port number when trying to connect to the server.';
|
||||
$message .= ' You should also check that the TCP/IP port you are using has not been blocked by a firewall or port blocking service.';
|
||||
throw new DatabaseConnectionRefusedException($e->getMessage() . ' [Tip: ' . $message . '] ', $e->getCode(), $e);
|
||||
|
||||
case static::DATABASE_NOT_FOUND:
|
||||
throw new DatabaseNotFoundException($e->getMessage(), $e->getCode(), $e);
|
||||
|
||||
case static::ACCESS_DENIED:
|
||||
throw new DatabaseAccessDeniedException($e->getMessage(), $e->getCode(), $e);
|
||||
|
||||
default:
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// Force MySQL to use the UTF-8 character set. Also set the collation, if a
|
||||
// certain one has been set; otherwise, MySQL defaults to
|
||||
// 'utf8mb4_general_ci' (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) for
|
||||
// utf8mb4.
|
||||
if (!empty($connection_options['collation'])) {
|
||||
$pdo->exec('SET NAMES utf8mb4 COLLATE ' . $connection_options['collation']);
|
||||
}
|
||||
else {
|
||||
$pdo->exec('SET NAMES utf8mb4');
|
||||
}
|
||||
|
||||
// Set MySQL init_commands if not already defined. Default Drupal's MySQL
|
||||
// behavior to conform more closely to SQL standards. This allows Drupal
|
||||
// to run almost seamlessly on many different kinds of database systems.
|
||||
// These settings force MySQL to behave the same as postgresql, or sqlite
|
||||
// in regards to syntax interpretation and invalid data handling. See
|
||||
// https://www.drupal.org/node/344575 for further discussion. Also, as MySQL
|
||||
// 5.5 changed the meaning of TRADITIONAL we need to spell out the modes one
|
||||
// by one.
|
||||
$connection_options += [
|
||||
'init_commands' => [],
|
||||
];
|
||||
|
||||
$connection_options['init_commands'] += [
|
||||
'sql_mode' => "SET sql_mode = 'ANSI,TRADITIONAL'",
|
||||
];
|
||||
if (!empty($connection_options['isolation_level'])) {
|
||||
$connection_options['init_commands'] += [
|
||||
'isolation_level' => 'SET SESSION TRANSACTION ISOLATION LEVEL ' . strtoupper($connection_options['isolation_level']),
|
||||
];
|
||||
}
|
||||
|
||||
// Execute initial commands.
|
||||
foreach ($connection_options['init_commands'] as $sql) {
|
||||
$pdo->exec($sql);
|
||||
}
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function queryRange($query, $from, $count, array $args = [], array $options = []) {
|
||||
return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function queryTemporary($query, array $args = [], array $options = []) {
|
||||
$tablename = 'db_temporary_' . uniqid();
|
||||
$this->query('CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY ' . $query, $args, $options);
|
||||
return $tablename;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function driver() {
|
||||
return 'mysql';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function version() {
|
||||
if ($this->isMariaDb()) {
|
||||
return $this->getMariaDbVersionMatch();
|
||||
}
|
||||
|
||||
return $this->getServerVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the MySQL distribution is MariaDB or not.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the distribution is MariaDB, or FALSE if not.
|
||||
*/
|
||||
public function isMariaDb(): bool {
|
||||
return (bool) $this->getMariaDbVersionMatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the MariaDB portion of the server version.
|
||||
*
|
||||
* @return string
|
||||
* The MariaDB portion of the server version if present, or NULL if not.
|
||||
*/
|
||||
protected function getMariaDbVersionMatch(): ?string {
|
||||
// MariaDB may prefix its version string with '5.5.5-', which should be
|
||||
// ignored.
|
||||
// @see https://github.com/MariaDB/server/blob/f6633bf058802ad7da8196d01fd19d75c53f7274/include/mysql_com.h#L42.
|
||||
$regex = '/^(?:5\.5\.5-)?(\d+\.\d+\.\d+.*-mariadb.*)/i';
|
||||
|
||||
preg_match($regex, $this->getServerVersion(), $matches);
|
||||
return (empty($matches[1])) ? NULL : $matches[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server version.
|
||||
*
|
||||
* @return string
|
||||
* The PDO server version.
|
||||
*/
|
||||
protected function getServerVersion(): string {
|
||||
if (!$this->serverVersion) {
|
||||
$this->serverVersion = $this->query('SELECT VERSION()')->fetchField();
|
||||
}
|
||||
return $this->serverVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function databaseType() {
|
||||
return 'mysql';
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Database\Connection::createDatabase().
|
||||
*
|
||||
* @param string $database
|
||||
* The name of the database to create.
|
||||
*
|
||||
* @throws \Drupal\Core\Database\DatabaseNotFoundException
|
||||
*/
|
||||
public function createDatabase($database) {
|
||||
// Escape the database name.
|
||||
$database = Database::getConnection()->escapeDatabase($database);
|
||||
|
||||
try {
|
||||
// Create the database and set it as active.
|
||||
$this->connection->exec("CREATE DATABASE $database");
|
||||
$this->connection->exec("USE $database");
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
throw new DatabaseNotFoundException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mapConditionOperator($operator) {
|
||||
// We don't want to override any of the defaults.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exceptionHandler() {
|
||||
return new ExceptionHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function insert($table, array $options = []) {
|
||||
return new Insert($this, $table, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function upsert($table, array $options = []) {
|
||||
return new Upsert($this, $table, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function schema() {
|
||||
if (empty($this->schema)) {
|
||||
$this->schema = new Schema($this);
|
||||
}
|
||||
return $this->schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function driverTransactionManager(): TransactionManagerInterface {
|
||||
return new TransactionManager($this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
13
web/core/modules/mysql/src/Driver/Database/mysql/Delete.php
Normal file
13
web/core/modules/mysql/src/Driver/Database/mysql/Delete.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\Query\Delete as QueryDelete;
|
||||
|
||||
@trigger_error('Extending from \Drupal\mysql\Driver\Database\mysql\Delete is deprecated in drupal:11.0.0 and is removed from drupal:12.0.0. Extend from the base class instead. See https://www.drupal.org/node/3256524', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* MySQL implementation of \Drupal\Core\Database\Query\Delete.
|
||||
*/
|
||||
class Delete extends QueryDelete {
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Database\DatabaseExceptionWrapper;
|
||||
use Drupal\Core\Database\ExceptionHandler as BaseExceptionHandler;
|
||||
use Drupal\Core\Database\Exception\SchemaTableColumnSizeTooLargeException;
|
||||
use Drupal\Core\Database\Exception\SchemaTableKeyTooLargeException;
|
||||
use Drupal\Core\Database\IntegrityConstraintViolationException;
|
||||
use Drupal\Core\Database\StatementInterface;
|
||||
|
||||
/**
|
||||
* MySql database exception handler class.
|
||||
*/
|
||||
class ExceptionHandler extends BaseExceptionHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleExecutionException(\Exception $exception, StatementInterface $statement, array $arguments = [], array $options = []): void {
|
||||
if ($exception instanceof \PDOException) {
|
||||
// Wrap the exception in another exception, because PHP does not allow
|
||||
// overriding Exception::getMessage(). Its message is the extra database
|
||||
// debug information.
|
||||
$code = is_int($exception->getCode()) ? $exception->getCode() : 0;
|
||||
|
||||
// If a max_allowed_packet error occurs the message length is truncated.
|
||||
// This should prevent the error from recurring if the exception is logged
|
||||
// to the database using dblog or the like.
|
||||
if (($exception->errorInfo[1] ?? NULL) === 1153) {
|
||||
$message = Unicode::truncateBytes($exception->getMessage(), Connection::MIN_MAX_ALLOWED_PACKET);
|
||||
throw new DatabaseExceptionWrapper($message, $code, $exception);
|
||||
}
|
||||
|
||||
$message = $exception->getMessage() . ": " . $statement->getQueryString() . "; " . print_r($arguments, TRUE);
|
||||
|
||||
// SQLSTATE 23xxx errors indicate an integrity constraint violation. Also,
|
||||
// in case of attempted INSERT of a record with an undefined column and no
|
||||
// default value indicated in schema, MySql returns a 1364 error code.
|
||||
if (
|
||||
substr($exception->getCode(), -6, -3) == '23' ||
|
||||
($exception->errorInfo[1] ?? NULL) === 1364
|
||||
) {
|
||||
throw new IntegrityConstraintViolationException($message, $code, $exception);
|
||||
}
|
||||
|
||||
if ($exception->getCode() === '42000') {
|
||||
match ($exception->errorInfo[1]) {
|
||||
1071 => throw new SchemaTableKeyTooLargeException($message, $code, $exception),
|
||||
1074 => throw new SchemaTableColumnSizeTooLargeException($message, $code, $exception),
|
||||
default => throw new DatabaseExceptionWrapper($message, 0, $exception),
|
||||
};
|
||||
}
|
||||
|
||||
throw new DatabaseExceptionWrapper($message, 0, $exception);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
}
|
||||
78
web/core/modules/mysql/src/Driver/Database/mysql/Insert.php
Normal file
78
web/core/modules/mysql/src/Driver/Database/mysql/Insert.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\Query\Insert as QueryInsert;
|
||||
|
||||
/**
|
||||
* MySQL implementation of \Drupal\Core\Database\Query\Insert.
|
||||
*/
|
||||
class Insert extends QueryInsert {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute() {
|
||||
if (!$this->preExecute()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If we're selecting from a SelectQuery, finish building the query and
|
||||
// pass it back, as any remaining options are irrelevant.
|
||||
if (empty($this->fromQuery)) {
|
||||
$max_placeholder = 0;
|
||||
$values = [];
|
||||
foreach ($this->insertValues as $insert_values) {
|
||||
foreach ($insert_values as $value) {
|
||||
$values[':db_insert_placeholder_' . $max_placeholder++] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$values = $this->fromQuery->getArguments();
|
||||
}
|
||||
|
||||
$stmt = $this->connection->prepareStatement((string) $this, $this->queryOptions);
|
||||
try {
|
||||
$stmt->execute($values, $this->queryOptions);
|
||||
$last_insert_id = $this->connection->lastInsertId();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->connection->exceptionHandler()->handleExecutionException($e, $stmt, $values, $this->queryOptions);
|
||||
}
|
||||
|
||||
// Re-initialize the values array so that we can re-use this query.
|
||||
$this->insertValues = [];
|
||||
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 ($field) {
|
||||
return $this->connection->escapeField($field);
|
||||
}, $insert_fields);
|
||||
|
||||
// If we're selecting from a SelectQuery, finish building the query and
|
||||
// pass it back, as any remaining options are irrelevant.
|
||||
if (!empty($this->fromQuery)) {
|
||||
$insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
|
||||
return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
|
||||
}
|
||||
|
||||
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
|
||||
|
||||
$values = $this->getInsertPlaceholderFragment($this->insertValues, $this->defaultFields);
|
||||
$query .= implode(', ', $values);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql\Install;
|
||||
|
||||
use Drupal\Core\Database\ConnectionNotDefinedException;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\Install\Tasks as InstallTasks;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\mysql\Driver\Database\mysql\Connection;
|
||||
use Drupal\Core\Database\DatabaseNotFoundException;
|
||||
|
||||
/**
|
||||
* Specifies installation tasks for MySQL and equivalent databases.
|
||||
*/
|
||||
class Tasks extends InstallTasks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Minimum required MySQL version.
|
||||
*/
|
||||
const MYSQL_MINIMUM_VERSION = '8.0';
|
||||
|
||||
/**
|
||||
* Minimum required MariaDB version.
|
||||
*/
|
||||
const MARIADB_MINIMUM_VERSION = '10.6';
|
||||
|
||||
/**
|
||||
* The PDO driver name for MySQL and equivalent databases.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pdoDriver = 'mysql';
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\mysql\Driver\Database\mysql\Install\Tasks object.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->tasks[] = [
|
||||
'arguments' => [],
|
||||
'function' => 'ensureInnoDbAvailable',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function name() {
|
||||
try {
|
||||
if (!$this->isConnectionActive() || !$this->getConnection() instanceof Connection) {
|
||||
throw new ConnectionNotDefinedException('The database connection is not active or not a MySql connection');
|
||||
}
|
||||
if ($this->getConnection()->isMariaDb()) {
|
||||
return $this->t('MariaDB');
|
||||
}
|
||||
return $this->t('MySQL, Percona Server, or equivalent');
|
||||
}
|
||||
catch (ConnectionNotDefinedException) {
|
||||
return $this->t('MySQL, MariaDB, Percona Server, or equivalent');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function minimumVersion() {
|
||||
if ($this->getConnection()->isMariaDb()) {
|
||||
return static::MARIADB_MINIMUM_VERSION;
|
||||
}
|
||||
return static::MYSQL_MINIMUM_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function connect() {
|
||||
try {
|
||||
// This doesn't actually test the connection.
|
||||
Database::setActiveConnection();
|
||||
// Now actually do a check.
|
||||
Database::getConnection();
|
||||
$this->pass('Drupal can CONNECT to the database ok.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Attempt to create the database if it is not found.
|
||||
if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
|
||||
// Remove the database string from connection info.
|
||||
$connection_info = Database::getConnectionInfo();
|
||||
$database = $connection_info['default']['database'];
|
||||
unset($connection_info['default']['database']);
|
||||
|
||||
// In order to change the Database::$databaseInfo array, need to remove
|
||||
// the active connection, then re-add it with the new info.
|
||||
Database::removeConnection('default');
|
||||
Database::addConnectionInfo('default', 'default', $connection_info['default']);
|
||||
|
||||
try {
|
||||
// Now, attempt the connection again; if it's successful, attempt to
|
||||
// create the database.
|
||||
Database::getConnection()->createDatabase($database);
|
||||
Database::closeConnection();
|
||||
|
||||
// Now, restore the database config.
|
||||
Database::removeConnection('default');
|
||||
$connection_info['default']['database'] = $database;
|
||||
Database::addConnectionInfo('default', 'default', $connection_info['default']);
|
||||
|
||||
// Check the database connection.
|
||||
Database::getConnection();
|
||||
$this->pass('Drupal can CONNECT to the database ok.');
|
||||
}
|
||||
catch (DatabaseNotFoundException $e) {
|
||||
// Still no dice; probably a permission issue. Raise the error to the
|
||||
// installer.
|
||||
$this->fail($this->t('Database %database not found. The server reports the following message when attempting to create the database: %error.', ['%database' => $database, '%error' => $e->getMessage()]));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Database connection failed for some other reason than a non-existent
|
||||
// database.
|
||||
$this->fail($this->t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist or does the database user have sufficient privileges to create the database?</li><li>Have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname and port number?</li></ul>', ['%error' => $e->getMessage()]));
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormOptions(array $database) {
|
||||
$form = parent::getFormOptions($database);
|
||||
if (empty($form['advanced_options']['port']['#default_value'])) {
|
||||
$form['advanced_options']['port']['#default_value'] = '3306';
|
||||
}
|
||||
$form['advanced_options']['isolation_level'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Transaction isolation level'),
|
||||
'#options' => [
|
||||
'READ COMMITTED' => $this->t('READ COMMITTED'),
|
||||
'REPEATABLE READ' => $this->t('REPEATABLE READ'),
|
||||
'' => $this->t('Use database default'),
|
||||
],
|
||||
'#default_value' => $database['isolation_level'] ?? 'READ COMMITTED',
|
||||
'#description' => $this->t('The recommended database transaction level for Drupal is "READ COMMITTED". For more information, see the <a href=":performance_doc">setting MySQL transaction isolation level</a> page.', [
|
||||
':performance_doc' => 'https://www.drupal.org/docs/system-requirements/setting-the-mysql-transaction-isolation-level',
|
||||
]),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that InnoDB is available.
|
||||
*/
|
||||
public function ensureInnoDbAvailable() {
|
||||
$engines = Database::getConnection()->query('SHOW ENGINES')->fetchAllKeyed();
|
||||
if (isset($engines['MyISAM']) && $engines['MyISAM'] == 'DEFAULT' && !isset($engines['InnoDB'])) {
|
||||
$this->fail($this->t('The MyISAM storage engine is not supported.'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
13
web/core/modules/mysql/src/Driver/Database/mysql/Merge.php
Normal file
13
web/core/modules/mysql/src/Driver/Database/mysql/Merge.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\Query\Merge as QueryMerge;
|
||||
|
||||
@trigger_error('Extending from \Drupal\mysql\Driver\Database\mysql\Merge is deprecated in drupal:11.0.0 and is removed from drupal:12.0.0. Extend from the base class instead. See https://www.drupal.org/node/3256524', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* MySQL implementation of \Drupal\Core\Database\Query\Merge.
|
||||
*/
|
||||
class Merge extends QueryMerge {
|
||||
}
|
||||
695
web/core/modules/mysql/src/Driver/Database/mysql/Schema.php
Normal file
695
web/core/modules/mysql/src/Driver/Database/mysql/Schema.php
Normal file
@ -0,0 +1,695 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\DatabaseExceptionWrapper;
|
||||
use Drupal\Core\Database\SchemaException;
|
||||
use Drupal\Core\Database\SchemaObjectExistsException;
|
||||
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
|
||||
use Drupal\Core\Database\Schema as DatabaseSchema;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
// cspell:ignore gipk
|
||||
|
||||
/**
|
||||
* @addtogroup schemaapi
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* MySQL implementation of \Drupal\Core\Database\Schema.
|
||||
*/
|
||||
class Schema extends DatabaseSchema {
|
||||
|
||||
/**
|
||||
* Maximum length of a table comment in MySQL.
|
||||
*/
|
||||
const COMMENT_MAX_TABLE = 60;
|
||||
|
||||
/**
|
||||
* Maximum length of a column comment in MySQL.
|
||||
*/
|
||||
const COMMENT_MAX_COLUMN = 255;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* List of MySQL string types.
|
||||
*/
|
||||
protected $mysqlStringTypes = [
|
||||
'VARCHAR',
|
||||
'CHAR',
|
||||
'TINYTEXT',
|
||||
'MEDIUMTEXT',
|
||||
'LONGTEXT',
|
||||
'TEXT',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get information about the table and database name from the prefix.
|
||||
*
|
||||
* @return array
|
||||
* A keyed array with information about the database, table name and prefix.
|
||||
*/
|
||||
protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
|
||||
$info = ['prefix' => $this->connection->getPrefix()];
|
||||
if ($add_prefix) {
|
||||
$table = $info['prefix'] . $table;
|
||||
}
|
||||
if (($pos = strpos($table, '.')) !== FALSE) {
|
||||
$info['database'] = substr($table, 0, $pos);
|
||||
$info['table'] = substr($table, ++$pos);
|
||||
}
|
||||
else {
|
||||
$info['database'] = $this->connection->getConnectionOptions()['database'];
|
||||
$info['table'] = $table;
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a condition to match a table name with the information schema.
|
||||
*
|
||||
* MySQL uses databases like schemas rather than catalogs so when we build a
|
||||
* condition to query the information_schema.tables, we set the default
|
||||
* database as the schema unless specified otherwise, and exclude
|
||||
* table_catalog from the condition criteria.
|
||||
*/
|
||||
protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
|
||||
$table_info = $this->getPrefixInfo($table_name, $add_prefix);
|
||||
|
||||
$condition = $this->connection->condition('AND');
|
||||
$condition->condition('table_schema', $table_info['database']);
|
||||
$condition->condition('table_name', $table_info['table'], $operator);
|
||||
return $condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createTableSql($name, $table) {
|
||||
$info = $this->connection->getConnectionOptions();
|
||||
|
||||
// Provide defaults if needed.
|
||||
$table += [
|
||||
'mysql_engine' => 'InnoDB',
|
||||
'mysql_character_set' => 'utf8mb4',
|
||||
];
|
||||
|
||||
$sql = "CREATE TABLE {" . $name . "} (\n";
|
||||
|
||||
// Add the SQL statement for each field.
|
||||
foreach ($table['fields'] as $field_name => $field) {
|
||||
$sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n";
|
||||
}
|
||||
|
||||
// Process keys & indexes.
|
||||
if (!empty($table['primary key']) && is_array($table['primary key'])) {
|
||||
$this->ensureNotNullPrimaryKey($table['primary key'], $table['fields']);
|
||||
}
|
||||
$keys = $this->createKeysSql($table);
|
||||
if (count($keys)) {
|
||||
$sql .= implode(", \n", $keys) . ", \n";
|
||||
}
|
||||
|
||||
// Remove the last comma and space.
|
||||
$sql = substr($sql, 0, -3) . "\n) ";
|
||||
|
||||
$sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set'];
|
||||
// By default, MySQL uses the default collation for new tables, which is
|
||||
// 'utf8mb4_general_ci' (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) for
|
||||
// utf8mb4. If an alternate collation has been set, it needs to be
|
||||
// explicitly specified.
|
||||
// @see \Drupal\mysql\Driver\Database\mysql\Schema
|
||||
if (!empty($info['collation'])) {
|
||||
$sql .= ' COLLATE ' . $info['collation'];
|
||||
}
|
||||
|
||||
// Add table comment.
|
||||
if (!empty($table['description'])) {
|
||||
$sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);
|
||||
}
|
||||
|
||||
return [$sql];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SQL string for a field used in table creation or alteration.
|
||||
*
|
||||
* @param string $name
|
||||
* Name of the field.
|
||||
* @param array $spec
|
||||
* The field specification, as per the schema data structure format.
|
||||
*/
|
||||
protected function createFieldSql($name, $spec) {
|
||||
$sql = "[" . $name . "] " . $spec['mysql_type'];
|
||||
|
||||
if (in_array($spec['mysql_type'], $this->mysqlStringTypes)) {
|
||||
if (isset($spec['length'])) {
|
||||
$sql .= '(' . $spec['length'] . ')';
|
||||
}
|
||||
if (isset($spec['type']) && $spec['type'] == 'varchar_ascii') {
|
||||
$sql .= ' CHARACTER SET ascii';
|
||||
}
|
||||
if (!empty($spec['binary'])) {
|
||||
$sql .= ' BINARY';
|
||||
}
|
||||
// Note we check for the "type" key here. "mysql_type" is VARCHAR:
|
||||
elseif (isset($spec['type']) && $spec['type'] == 'varchar_ascii') {
|
||||
$sql .= ' COLLATE ascii_general_ci';
|
||||
}
|
||||
}
|
||||
elseif (isset($spec['precision']) && isset($spec['scale'])) {
|
||||
$sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
|
||||
}
|
||||
|
||||
if (!empty($spec['unsigned'])) {
|
||||
$sql .= ' unsigned';
|
||||
}
|
||||
|
||||
if (isset($spec['not null'])) {
|
||||
if ($spec['not null']) {
|
||||
$sql .= ' NOT NULL';
|
||||
}
|
||||
else {
|
||||
$sql .= ' NULL';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($spec['auto_increment'])) {
|
||||
$sql .= ' auto_increment';
|
||||
}
|
||||
|
||||
// $spec['default'] can be NULL, so we explicitly check for the key here.
|
||||
if (array_key_exists('default', $spec)) {
|
||||
$sql .= ' DEFAULT ' . $this->escapeDefaultValue($spec['default']);
|
||||
}
|
||||
|
||||
if (empty($spec['not null']) && !isset($spec['default'])) {
|
||||
$sql .= ' DEFAULT NULL';
|
||||
}
|
||||
|
||||
// Add column comment.
|
||||
if (!empty($spec['description'])) {
|
||||
$sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database-engine specific properties for a field.
|
||||
*
|
||||
* @param array $field
|
||||
* A field description array, as specified in the schema documentation.
|
||||
*/
|
||||
protected function processField($field) {
|
||||
|
||||
if (!isset($field['size'])) {
|
||||
$field['size'] = 'normal';
|
||||
}
|
||||
|
||||
// Set the correct database-engine specific datatype.
|
||||
// In case one is already provided, force it to uppercase.
|
||||
if (isset($field['mysql_type'])) {
|
||||
$field['mysql_type'] = mb_strtoupper($field['mysql_type']);
|
||||
}
|
||||
else {
|
||||
$map = $this->getFieldTypeMap();
|
||||
$field['mysql_type'] = $map[$field['type'] . ':' . $field['size']];
|
||||
}
|
||||
|
||||
if (isset($field['type']) && $field['type'] == 'serial') {
|
||||
$field['auto_increment'] = TRUE;
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldTypeMap() {
|
||||
// Put :normal last so it gets preserved by array_flip. This makes
|
||||
// it much easier for modules (such as schema.module) to map
|
||||
// database types back into schema types.
|
||||
// $map does not use drupal_static as its value never changes.
|
||||
static $map = [
|
||||
'varchar_ascii:normal' => 'VARCHAR',
|
||||
|
||||
'varchar:normal' => 'VARCHAR',
|
||||
'char:normal' => 'CHAR',
|
||||
|
||||
'text:tiny' => 'TINYTEXT',
|
||||
'text:small' => 'TINYTEXT',
|
||||
'text:medium' => 'MEDIUMTEXT',
|
||||
'text:big' => 'LONGTEXT',
|
||||
'text:normal' => 'TEXT',
|
||||
|
||||
'serial:tiny' => 'TINYINT',
|
||||
'serial:small' => 'SMALLINT',
|
||||
'serial:medium' => 'MEDIUMINT',
|
||||
'serial:big' => 'BIGINT',
|
||||
'serial:normal' => 'INT',
|
||||
|
||||
'int:tiny' => 'TINYINT',
|
||||
'int:small' => 'SMALLINT',
|
||||
'int:medium' => 'MEDIUMINT',
|
||||
'int:big' => 'BIGINT',
|
||||
'int:normal' => 'INT',
|
||||
|
||||
'float:tiny' => 'FLOAT',
|
||||
'float:small' => 'FLOAT',
|
||||
'float:medium' => 'FLOAT',
|
||||
'float:big' => 'DOUBLE',
|
||||
'float:normal' => 'FLOAT',
|
||||
|
||||
'numeric:normal' => 'DECIMAL',
|
||||
|
||||
'blob:big' => 'LONGBLOB',
|
||||
'blob:normal' => 'BLOB',
|
||||
];
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the keys for an SQL table.
|
||||
*/
|
||||
protected function createKeysSql($spec) {
|
||||
$keys = [];
|
||||
|
||||
if (!empty($spec['primary key'])) {
|
||||
$keys[] = 'PRIMARY KEY (' . $this->createKeySql($spec['primary key']) . ')';
|
||||
}
|
||||
if (!empty($spec['unique keys'])) {
|
||||
foreach ($spec['unique keys'] as $key => $fields) {
|
||||
$keys[] = 'UNIQUE KEY [' . $key . '] (' . $this->createKeySql($fields) . ')';
|
||||
}
|
||||
}
|
||||
if (!empty($spec['indexes'])) {
|
||||
$indexes = $this->getNormalizedIndexes($spec);
|
||||
foreach ($indexes as $index => $fields) {
|
||||
$keys[] = 'INDEX [' . $index . '] (' . $this->createKeySql($fields) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets normalized indexes from a table specification.
|
||||
*
|
||||
* 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 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(array $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.
|
||||
$field_name = is_array($index_field) ? $index_field[0] : $index_field;
|
||||
// Check whether the field is defined in the table specification.
|
||||
if (isset($spec['fields'][$field_name])) {
|
||||
// Get the MySQL type from the processed field.
|
||||
$mysql_field = $this->processField($spec['fields'][$field_name]);
|
||||
if (in_array($mysql_field['mysql_type'], $this->mysqlStringTypes)) {
|
||||
// Check whether we need to shorten the index.
|
||||
if ((!isset($mysql_field['type']) || $mysql_field['type'] != 'varchar_ascii') && (!isset($mysql_field['length']) || $mysql_field['length'] > 191)) {
|
||||
// Limit the index length to 191 characters.
|
||||
$this->shortenIndex($indexes[$index_name][$index_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new SchemaException("MySQL needs the '$field_name' field specification in order to normalize the '$index_name' index");
|
||||
}
|
||||
}
|
||||
}
|
||||
return $indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for normalizeIndexes().
|
||||
*
|
||||
* Shortens an index to 191 characters.
|
||||
*
|
||||
* @param array $index
|
||||
* The index array to be used in createKeySql.
|
||||
*
|
||||
* @see Drupal\mysql\Driver\Database\mysql\Schema::createKeySql()
|
||||
* @see Drupal\mysql\Driver\Database\mysql\Schema::normalizeIndexes()
|
||||
*/
|
||||
protected function shortenIndex(&$index) {
|
||||
if (is_array($index)) {
|
||||
if ($index[1] > 191) {
|
||||
$index[1] = 191;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$index = [$index, 191];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SQL key for the given fields.
|
||||
*/
|
||||
protected function createKeySql($fields) {
|
||||
$return = [];
|
||||
foreach ($fields as $field) {
|
||||
if (is_array($field)) {
|
||||
$return[] = '[' . $field[0] . '] (' . $field[1] . ')';
|
||||
}
|
||||
else {
|
||||
$return[] = '[' . $field . ']';
|
||||
}
|
||||
}
|
||||
return implode(', ', $return);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function renameTable($table, $new_name) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new SchemaObjectDoesNotExistException("Cannot rename '$table' to '$new_name': table '$table' doesn't exist.");
|
||||
}
|
||||
if ($this->tableExists($new_name)) {
|
||||
throw new SchemaObjectExistsException("Cannot rename '$table' to '$new_name': table '$new_name' already exists.");
|
||||
}
|
||||
|
||||
$info = $this->getPrefixInfo($new_name);
|
||||
$this->executeDdlStatement('ALTER TABLE {' . $table . '} RENAME TO [' . $info['table'] . ']');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dropTable($table) {
|
||||
if (!$this->tableExists($table)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->executeDdlStatement('DROP TABLE {' . $table . '}');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addField($table, $field, $spec, $keys_new = []) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new SchemaObjectDoesNotExistException("Cannot add field '$table.$field': table doesn't exist.");
|
||||
}
|
||||
if ($this->fieldExists($table, $field)) {
|
||||
throw new SchemaObjectExistsException("Cannot add field '$table.$field': field already exists.");
|
||||
}
|
||||
|
||||
// Fields that are part of a PRIMARY KEY must be added as NOT NULL.
|
||||
$is_primary_key = isset($keys_new['primary key']) && in_array($field, $keys_new['primary key'], TRUE);
|
||||
if ($is_primary_key) {
|
||||
$this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field => $spec]);
|
||||
}
|
||||
|
||||
$fix_null = FALSE;
|
||||
if (!empty($spec['not null']) && !isset($spec['default']) && !$is_primary_key) {
|
||||
$fix_null = TRUE;
|
||||
$spec['not null'] = FALSE;
|
||||
}
|
||||
$query = 'ALTER TABLE {' . $table . '} ADD ';
|
||||
$query .= $this->createFieldSql($field, $this->processField($spec));
|
||||
if ($keys_sql = $this->createKeysSql($keys_new)) {
|
||||
// Make sure to drop the existing primary key before adding a new one.
|
||||
// This is only needed when adding a field because this method, unlike
|
||||
// changeField(), is supposed to handle primary keys automatically.
|
||||
if (isset($keys_new['primary key']) && $this->indexExists($table, 'PRIMARY')) {
|
||||
$query .= ', DROP PRIMARY KEY';
|
||||
}
|
||||
|
||||
$query .= ', ADD ' . implode(', ADD ', $keys_sql);
|
||||
}
|
||||
try {
|
||||
$this->executeDdlStatement($query);
|
||||
}
|
||||
catch (DatabaseExceptionWrapper $e) {
|
||||
// MySQL error number 4111 (ER_DROP_PK_COLUMN_TO_DROP_GIPK) indicates that
|
||||
// when dropping and adding a primary key, the generated invisible primary
|
||||
// key (GIPK) column must also be dropped.
|
||||
if (isset($e->getPrevious()->errorInfo[1]) && $e->getPrevious()->errorInfo[1] === 4111 && isset($keys_new['primary key']) && $this->indexExists($table, 'PRIMARY') && $this->findPrimaryKeyColumns($table) === ['my_row_id']) {
|
||||
$this->executeDdlStatement($query . ', DROP COLUMN [my_row_id]');
|
||||
}
|
||||
else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($spec['initial_from_field'])) {
|
||||
if (isset($spec['initial'])) {
|
||||
$expression = 'COALESCE(' . $spec['initial_from_field'] . ', :default_initial_value)';
|
||||
$arguments = [':default_initial_value' => $spec['initial']];
|
||||
}
|
||||
else {
|
||||
$expression = $spec['initial_from_field'];
|
||||
$arguments = [];
|
||||
}
|
||||
$this->connection->update($table)
|
||||
->expression($field, $expression, $arguments)
|
||||
->execute();
|
||||
}
|
||||
elseif (isset($spec['initial'])) {
|
||||
$this->connection->update($table)
|
||||
->fields([$field => $spec['initial']])
|
||||
->execute();
|
||||
}
|
||||
if ($fix_null) {
|
||||
$spec['not null'] = TRUE;
|
||||
$this->changeField($table, $field, $field, $spec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dropField($table, $field) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// When dropping a field that is part of a composite primary key MySQL
|
||||
// automatically removes the field from the primary key, which can leave the
|
||||
// table in an invalid state. MariaDB 10.2.8 requires explicitly dropping
|
||||
// the primary key first for this reason. We perform this deletion
|
||||
// explicitly which also makes the behavior on both MySQL and MariaDB
|
||||
// consistent with PostgreSQL.
|
||||
// @see https://mariadb.com/kb/en/library/alter-table
|
||||
$primary_key = $this->findPrimaryKeyColumns($table);
|
||||
if ((count($primary_key) > 1) && in_array($field, $primary_key, TRUE)) {
|
||||
$this->dropPrimaryKey($table);
|
||||
}
|
||||
|
||||
$this->executeDdlStatement('ALTER TABLE {' . $table . '} DROP [' . $field . ']');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function indexExists($table, $name) {
|
||||
// Returns one row for each column in the index. Result is string or FALSE.
|
||||
// Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html
|
||||
$row = $this->connection->query('SHOW INDEX FROM {' . $table . '} WHERE key_name = ' . $this->connection->quote($name))->fetchAssoc();
|
||||
return isset($row['Key_name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addPrimaryKey($table, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new SchemaObjectDoesNotExistException("Cannot add primary key to table '$table': table doesn't exist.");
|
||||
}
|
||||
if ($this->indexExists($table, 'PRIMARY')) {
|
||||
throw new SchemaObjectExistsException("Cannot add primary key to table '$table': primary key already exists.");
|
||||
}
|
||||
|
||||
$this->executeDdlStatement('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dropPrimaryKey($table) {
|
||||
if (!$this->indexExists($table, 'PRIMARY')) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->executeDdlStatement('ALTER TABLE {' . $table . '} DROP PRIMARY KEY');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function findPrimaryKeyColumns($table) {
|
||||
if (!$this->tableExists($table)) {
|
||||
return FALSE;
|
||||
}
|
||||
$result = $this->connection->query("SHOW KEYS FROM {" . $table . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
|
||||
return array_keys($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addUniqueKey($table, $name, $fields) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new SchemaObjectDoesNotExistException("Cannot add unique key '$name' to table '$table': table doesn't exist.");
|
||||
}
|
||||
if ($this->indexExists($table, $name)) {
|
||||
throw new SchemaObjectExistsException("Cannot add unique key '$name' to table '$table': unique key already exists.");
|
||||
}
|
||||
|
||||
$this->executeDdlStatement('ALTER TABLE {' . $table . '} ADD UNIQUE KEY [' . $name . '] (' . $this->createKeySql($fields) . ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dropUniqueKey($table, $name) {
|
||||
if (!$this->indexExists($table, $name)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->executeDdlStatement('ALTER TABLE {' . $table . '} DROP KEY [' . $name . ']');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addIndex($table, $name, $fields, array $spec) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new SchemaObjectDoesNotExistException("Cannot add index '$name' to table '$table': table doesn't exist.");
|
||||
}
|
||||
if ($this->indexExists($table, $name)) {
|
||||
throw new SchemaObjectExistsException("Cannot add index '$name' to table '$table': index already exists.");
|
||||
}
|
||||
|
||||
$spec['indexes'][$name] = $fields;
|
||||
$indexes = $this->getNormalizedIndexes($spec);
|
||||
|
||||
$this->executeDdlStatement('ALTER TABLE {' . $table . '} ADD INDEX [' . $name . '] (' . $this->createKeySql($indexes[$name]) . ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dropIndex($table, $name) {
|
||||
if (!$this->indexExists($table, $name)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->executeDdlStatement('ALTER TABLE {' . $table . '} DROP INDEX [' . $name . ']');
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function introspectIndexSchema($table) {
|
||||
if (!$this->tableExists($table)) {
|
||||
throw new SchemaObjectDoesNotExistException("The table $table doesn't exist.");
|
||||
}
|
||||
|
||||
$index_schema = [
|
||||
'primary key' => [],
|
||||
'unique keys' => [],
|
||||
'indexes' => [],
|
||||
];
|
||||
|
||||
$result = $this->connection->query('SHOW INDEX FROM {' . $table . '}')->fetchAll();
|
||||
foreach ($result as $row) {
|
||||
if ($row->Key_name === 'PRIMARY') {
|
||||
$index_schema['primary key'][] = $row->Column_name;
|
||||
}
|
||||
elseif ($row->Non_unique == 0) {
|
||||
$index_schema['unique keys'][$row->Key_name][] = $row->Column_name;
|
||||
}
|
||||
else {
|
||||
$index_schema['indexes'][$row->Key_name][] = $row->Column_name;
|
||||
}
|
||||
}
|
||||
|
||||
return $index_schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function changeField($table, $field, $field_new, $spec, $keys_new = []) {
|
||||
if (!$this->fieldExists($table, $field)) {
|
||||
throw new SchemaObjectDoesNotExistException("Cannot change the definition of field '$table.$field': field doesn't exist.");
|
||||
}
|
||||
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
|
||||
throw new SchemaObjectExistsException("Cannot rename field '$table.$field' to '$field_new': target field already exists.");
|
||||
}
|
||||
if (isset($keys_new['primary key']) && in_array($field_new, $keys_new['primary key'], TRUE)) {
|
||||
$this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field_new => $spec]);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE {' . $table . '} CHANGE [' . $field . '] ' . $this->createFieldSql($field_new, $this->processField($spec));
|
||||
if ($keys_sql = $this->createKeysSql($keys_new)) {
|
||||
$sql .= ', ADD ' . implode(', ADD ', $keys_sql);
|
||||
}
|
||||
$this->executeDdlStatement($sql);
|
||||
|
||||
if ($spec['type'] === 'serial') {
|
||||
$max = $this->connection->query('SELECT MAX(`' . $field_new . '`) FROM {' . $table . '}')->fetchField();
|
||||
$this->executeDdlStatement("ALTER TABLE {" . $table . "} AUTO_INCREMENT = " . ($max + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareComment($comment, $length = NULL) {
|
||||
// Truncate comment to maximum comment length.
|
||||
if (isset($length)) {
|
||||
// Add table prefix before truncating.
|
||||
$comment = Unicode::truncate($this->connection->prefixTables($comment), $length, TRUE, TRUE);
|
||||
}
|
||||
// Remove semicolons to avoid triggering multi-statement check.
|
||||
$comment = strtr($comment, [';' => '.']);
|
||||
return $this->connection->quote($comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a table or column comment.
|
||||
*/
|
||||
public function getComment($table, $column = NULL) {
|
||||
$condition = $this->buildTableNameCondition($table);
|
||||
if (isset($column)) {
|
||||
$condition->condition('column_name', $column);
|
||||
$condition->compile($this->connection, $this);
|
||||
// Don't use {} around information_schema.columns table.
|
||||
return $this->connection->query("SELECT column_comment AS column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
|
||||
}
|
||||
$condition->compile($this->connection, $this);
|
||||
// Don't use {} around information_schema.tables table.
|
||||
$comment = $this->connection->query("SELECT table_comment AS table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
|
||||
// Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
|
||||
return preg_replace('/; InnoDB free:.*$/', '', $comment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup schemaapi".
|
||||
*/
|
||||
13
web/core/modules/mysql/src/Driver/Database/mysql/Select.php
Normal file
13
web/core/modules/mysql/src/Driver/Database/mysql/Select.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\Query\Select as QuerySelect;
|
||||
|
||||
@trigger_error('Extending from \Drupal\mysql\Driver\Database\mysql\Select is deprecated in drupal:11.0.0 and is removed from drupal:12.0.0. Extend from the base class instead. See https://www.drupal.org/node/3256524', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* MySQL implementation of \Drupal\Core\Database\Query\Select.
|
||||
*/
|
||||
class Select extends QuerySelect {
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\Transaction\ClientConnectionTransactionState;
|
||||
use Drupal\Core\Database\Transaction\TransactionManagerBase;
|
||||
|
||||
/**
|
||||
* MySql implementation of TransactionManagerInterface.
|
||||
*
|
||||
* MySQL will automatically commit transactions when tables are altered or
|
||||
* created (DDL transactions are not supported). However, pdo_mysql tracks
|
||||
* whether a client connection is still active and we can prevent triggering
|
||||
* exceptions.
|
||||
*/
|
||||
class TransactionManager extends TransactionManagerBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function beginClientTransaction(): bool {
|
||||
return $this->connection->getClientConnection()->beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function processRootCommit(): void {
|
||||
if (!$this->connection->getClientConnection()->inTransaction()) {
|
||||
$this->voidClientTransaction();
|
||||
return;
|
||||
}
|
||||
parent::processRootCommit();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function rollbackClientSavepoint(string $name): bool {
|
||||
if (!$this->connection->getClientConnection()->inTransaction()) {
|
||||
$this->voidClientTransaction();
|
||||
return TRUE;
|
||||
}
|
||||
return parent::rollbackClientSavepoint($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function releaseClientSavepoint(string $name): bool {
|
||||
if (!$this->connection->getClientConnection()->inTransaction()) {
|
||||
$this->voidClientTransaction();
|
||||
return TRUE;
|
||||
}
|
||||
return parent::releaseClientSavepoint($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function commitClientTransaction(): bool {
|
||||
$clientCommit = $this->connection->getClientConnection()->commit();
|
||||
$this->setConnectionTransactionState($clientCommit ?
|
||||
ClientConnectionTransactionState::Committed :
|
||||
ClientConnectionTransactionState::CommitFailed
|
||||
);
|
||||
return $clientCommit;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function rollbackClientTransaction(): bool {
|
||||
if (!$this->connection->getClientConnection()->inTransaction()) {
|
||||
$this->voidClientTransaction();
|
||||
return FALSE;
|
||||
}
|
||||
$clientRollback = $this->connection->getClientConnection()->rollBack();
|
||||
$this->setConnectionTransactionState($clientRollback ?
|
||||
ClientConnectionTransactionState::RolledBack :
|
||||
ClientConnectionTransactionState::RollbackFailed
|
||||
);
|
||||
return $clientRollback;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\Query\Truncate as QueryTruncate;
|
||||
|
||||
@trigger_error('Extending from \Drupal\mysql\Driver\Database\mysql\Truncate is deprecated in drupal:11.0.0 and is removed from drupal:12.0.0. Extend from the base class instead. See https://www.drupal.org/node/3256524', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* MySQL implementation of \Drupal\Core\Database\Query\Truncate.
|
||||
*/
|
||||
class Truncate extends QueryTruncate {
|
||||
}
|
||||
13
web/core/modules/mysql/src/Driver/Database/mysql/Update.php
Normal file
13
web/core/modules/mysql/src/Driver/Database/mysql/Update.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\Query\Update as QueryUpdate;
|
||||
|
||||
@trigger_error('Extending from \Drupal\mysql\Driver\Database\mysql\Update is deprecated in drupal:11.0.0 and is removed from drupal:12.0.0. Extend from the base class instead. See https://www.drupal.org/node/3256524', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* MySQL implementation of \Drupal\Core\Database\Query\Update.
|
||||
*/
|
||||
class Update extends QueryUpdate {
|
||||
}
|
||||
43
web/core/modules/mysql/src/Driver/Database/mysql/Upsert.php
Normal file
43
web/core/modules/mysql/src/Driver/Database/mysql/Upsert.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Driver\Database\mysql;
|
||||
|
||||
use Drupal\Core\Database\Query\Upsert as QueryUpsert;
|
||||
|
||||
/**
|
||||
* MySQL implementation of \Drupal\Core\Database\Query\Upsert.
|
||||
*/
|
||||
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);
|
||||
$insert_fields = array_map(function ($field) {
|
||||
return $this->connection->escapeField($field);
|
||||
}, $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 = VALUES($field)";
|
||||
}
|
||||
|
||||
$query .= ' ON DUPLICATE KEY UPDATE ' . implode(', ', $update);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
33
web/core/modules/mysql/src/Hook/MysqlHooks.php
Normal file
33
web/core/modules/mysql/src/Hook/MysqlHooks.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Hook;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Hook implementations for mysql.
|
||||
*/
|
||||
class MysqlHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
#[Hook('help')]
|
||||
public function help($route_name, RouteMatchInterface $route_match): ?string {
|
||||
switch ($route_name) {
|
||||
case 'help.page.mysql':
|
||||
$output = '';
|
||||
$output .= '<h2>' . $this->t('About') . '</h2>';
|
||||
$output .= '<p>' . $this->t('The MySQL module provides the connection between Drupal and a MySQL, MariaDB or equivalent database. For more information, see the <a href=":mysql">online documentation for the MySQL module</a>.', [
|
||||
':mysql' => 'https://www.drupal.org/docs/core-modules-and-themes/core-modules/mysql-module',
|
||||
]) . '</p>';
|
||||
return $output;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
86
web/core/modules/mysql/src/Hook/MysqlRequirements.php
Normal file
86
web/core/modules/mysql/src/Hook/MysqlRequirements.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\mysql\Hook;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Extension\Requirement\RequirementSeverity;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\Render\Markup;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Requirements for the MySQL module.
|
||||
*/
|
||||
class MysqlRequirements {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_runtime_requirements().
|
||||
*/
|
||||
#[Hook('runtime_requirements')]
|
||||
public function runtime(): array {
|
||||
$requirements = [];
|
||||
// Test with MySql databases.
|
||||
if (Database::isActiveConnection()) {
|
||||
$connection = Database::getConnection();
|
||||
// Only show requirements when MySQL is the default database connection.
|
||||
if (!($connection->driver() === 'mysql' && $connection->getProvider() === 'mysql')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = $connection->isMariaDb() ? 'SELECT @@SESSION.tx_isolation' : 'SELECT @@SESSION.transaction_isolation';
|
||||
|
||||
$isolation_level = $connection->query($query)->fetchField();
|
||||
|
||||
$tables_missing_primary_key = [];
|
||||
$tables = $connection->schema()->findTables('%');
|
||||
foreach ($tables as $table) {
|
||||
$primary_key_column = Database::getConnection()->query("SHOW KEYS FROM {" . $table . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
|
||||
if (empty($primary_key_column)) {
|
||||
$tables_missing_primary_key[] = $table;
|
||||
}
|
||||
}
|
||||
|
||||
$description = [];
|
||||
if ($isolation_level == 'READ-COMMITTED') {
|
||||
if (empty($tables_missing_primary_key)) {
|
||||
$severity_level = RequirementSeverity::OK;
|
||||
}
|
||||
else {
|
||||
$severity_level = RequirementSeverity::Error;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($isolation_level == 'REPEATABLE-READ') {
|
||||
$severity_level = RequirementSeverity::Warning;
|
||||
}
|
||||
else {
|
||||
$severity_level = RequirementSeverity::Error;
|
||||
$description[] = $this->t('This is not supported by Drupal.');
|
||||
}
|
||||
$description[] = $this->t('The recommended level for Drupal is "READ COMMITTED".');
|
||||
}
|
||||
|
||||
if (!empty($tables_missing_primary_key)) {
|
||||
$description[] = $this->t('For this to work correctly, all tables must have a primary key. The following table(s) do not have a primary key: @tables.', ['@tables' => implode(', ', $tables_missing_primary_key)]);
|
||||
}
|
||||
|
||||
$description[] = $this->t('See the <a href=":performance_doc">setting MySQL transaction isolation level</a> page for more information.', [
|
||||
':performance_doc' => 'https://www.drupal.org/docs/system-requirements/setting-the-mysql-transaction-isolation-level',
|
||||
]);
|
||||
|
||||
$requirements['mysql_transaction_level'] = [
|
||||
'title' => $this->t('Transaction isolation level'),
|
||||
'severity' => $severity_level,
|
||||
'value' => $isolation_level,
|
||||
'description' => Markup::create(implode(' ', $description)),
|
||||
];
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
}
|
||||
25
web/core/modules/mysql/src/MysqlServiceProvider.php
Normal file
25
web/core/modules/mysql/src/MysqlServiceProvider.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\DependencyInjection\ServiceProviderBase;
|
||||
use Drupal\mysql\Plugin\views\query\MysqlCastSql;
|
||||
|
||||
/**
|
||||
* Registers the 'mysql.views.cast_sql' service when views is installed.
|
||||
*/
|
||||
class MysqlServiceProvider extends ServiceProviderBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container): void {
|
||||
if (isset($container->getParameter('container.modules')['views'])) {
|
||||
$container
|
||||
->register('mysql.views.cast_sql', MysqlCastSql::class)
|
||||
->setPublic(FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\mysql\Plugin\views\query;
|
||||
|
||||
use Drupal\views\Plugin\views\query\CastSqlInterface;
|
||||
|
||||
/**
|
||||
* MySQL specific cast handling.
|
||||
*/
|
||||
class MysqlCastSql implements CastSqlInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldAsInt(string $field): string {
|
||||
return "CAST($field AS UNSIGNED)";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user