Issue #3185269 by mondrake, daffie, alexpott, andypost: Introduce Connection::lastInsertId and deprecate the 'return' query option and Database::RETURN_* constants

merge-requests/1602/head
Alex Pott 2022-01-01 16:04:03 +00:00
parent 5296e875c6
commit 87dc8f4192
No known key found for this signature in database
GPG Key ID: BDA67E7EE836E5CE
51 changed files with 607 additions and 34 deletions

View File

@ -626,10 +626,15 @@ function drupal_install_system($install_state) {
// When the database driver is provided by a module, then install that module.
// This module must be installed before any other module, as it must be able
// to override any call to hook_schema() or any "backend_overridable" service.
// In edge cases, a driver module may extend from another driver module (for
// instance, a module to provide backward compatibility with a database
// version no longer supported by core). In order for the extended classes to
// be autoloadable, the extending module should list the extended module in
// its dependencies, and here the dependencies will be installed as well.
if ($provider !== 'core') {
$autoload = $connection->getConnectionOptions()['autoload'] ?? '';
if (($pos = strpos($autoload, 'src/Driver/Database/')) !== FALSE) {
$kernel->getContainer()->get('module_installer')->install([$provider], FALSE);
$kernel->getContainer()->get('module_installer')->install([$provider], TRUE);
}
}

View File

@ -143,6 +143,8 @@ class DatabaseStorage implements StorageInterface {
* @return bool
*/
protected function doWrite($name, $data) {
// @todo Remove the 'return' option in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options = ['return' => Database::RETURN_AFFECTED] + $this->options;
return (bool) $this->connection->merge($this->table, $options)
->keys(['collection', 'name'], [$this->collection, $name])
@ -218,6 +220,8 @@ class DatabaseStorage implements StorageInterface {
* @todo Ignore replica targets for data manipulation operations.
*/
public function delete($name) {
// @todo Remove the 'return' option in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options = ['return' => Database::RETURN_AFFECTED] + $this->options;
return (bool) $this->connection->delete($this->table, $options)
->condition('collection', $this->collection)
@ -231,6 +235,8 @@ class DatabaseStorage implements StorageInterface {
* @throws PDOException
*/
public function rename($name, $new_name) {
// @todo Remove the 'return' option in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options = ['return' => Database::RETURN_AFFECTED] + $this->options;
return (bool) $this->connection->update($this->table, $options)
->fields(['name' => $new_name])
@ -280,6 +286,8 @@ class DatabaseStorage implements StorageInterface {
*/
public function deleteAll($prefix = '') {
try {
// @todo Remove the 'return' option in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options = ['return' => Database::RETURN_AFFECTED] + $this->options;
return (bool) $this->connection->delete($this->table, $options)
->condition('name', $prefix . '%', 'LIKE')

View File

@ -367,12 +367,12 @@ abstract class Connection {
* class. If a string is specified, each record will be fetched into a new
* object of that class. The behavior of all other values is defined by PDO.
* See http://php.net/manual/pdostatement.fetch.php
* - return: Depending on the type of query, different return values may be
* meaningful. This directive instructs the system which type of return
* value is desired. The system will generally set the correct value
* automatically, so it is extremely rare that a module developer will ever
* need to specify this value. Setting it incorrectly will likely lead to
* unpredictable results or fatal errors. Legal values include:
* - return: (deprecated) Depending on the type of query, different return
* values may be meaningful. This directive instructs the system which type
* of return value is desired. The system will generally set the correct
* value automatically, so it is extremely rare that a module developer will
* ever need to specify this value. Setting it incorrectly will likely lead
* to unpredictable results or fatal errors. Legal values include:
* - Database::RETURN_STATEMENT: Return the prepared statement object for
* the query. This is usually only meaningful for SELECT queries, where
* the statement object is how one accesses the result set returned by the
@ -414,7 +414,6 @@ abstract class Connection {
protected function defaultOptions() {
return [
'fetch' => \PDO::FETCH_OBJ,
'return' => Database::RETURN_STATEMENT,
'allow_delimiter_in_query' => FALSE,
'allow_square_brackets' => FALSE,
'pdo' => [],
@ -616,6 +615,10 @@ abstract class Connection {
* @throws \Drupal\Core\Database\DatabaseExceptionWrapper
*/
public function prepareStatement(string $query, array $options, bool $allow_row_count = FALSE): StatementInterface {
if (isset($options['return'])) {
@trigger_error('Passing "return" option to ' . __METHOD__ . '() is deprecated in drupal:9.4.0 and is removed in drupal:11.0.0. For data manipulation operations, use dynamic queries instead. See https://www.drupal.org/node/3185520', E_USER_DEPRECATED);
}
try {
$query = $this->preprocessStatement($query, $options);
@ -914,6 +917,11 @@ abstract class Connection {
public function query($query, array $args = [], $options = []) {
// Use default values if not already set.
$options += $this->defaultOptions();
if (isset($options['return'])) {
@trigger_error('Passing "return" option to ' . __METHOD__ . '() is deprecated in drupal:9.4.0 and is removed in drupal:11.0.0. For data manipulation operations, use dynamic queries instead. See https://www.drupal.org/node/3185520', E_USER_DEPRECATED);
}
assert(!isset($options['target']), 'Passing "target" option to query() has no effect. See https://www.drupal.org/node/2993033');
// We allow either a pre-bound statement object (deprecated) or a literal
@ -946,7 +954,7 @@ abstract class Connection {
// Depending on the type of query we may need to return a different value.
// See DatabaseConnection::defaultOptions() for a description of each
// value.
switch ($options['return']) {
switch ($options['return'] ?? Database::RETURN_STATEMENT) {
case Database::RETURN_STATEMENT:
return $stmt;
@ -1234,6 +1242,40 @@ abstract class Connection {
return new $class($this, $table, $options);
}
/**
* Returns the ID of the last inserted row or sequence value.
*
* This method should normally be used only within database driver code.
*
* This is a proxy to invoke lastInsertId() from the wrapped connection.
* If a sequence name is not specified for the name parameter, this returns a
* string representing the row ID of the last row that was inserted into the
* database.
* If a sequence name is specified for the name parameter, this returns a
* string representing the last value retrieved from the specified sequence
* object.
*
* @param string|null $name
* (Optional) Name of the sequence object from which the ID should be
* returned.
*
* @return string
* The value returned by the wrapped connection.
*
* @throws \Drupal\Core\Database\DatabaseExceptionWrapper
* In case of failure.
*
* @see \PDO::lastInsertId
*
* @internal
*/
public function lastInsertId(?string $name = NULL): string {
if (($last_insert_id = $this->connection->lastInsertId($name)) === FALSE) {
throw new DatabaseExceptionWrapper("Could not determine last insert id" . $name === NULL ? '' : " for sequence $name");
}
return $last_insert_id;
}
/**
* Prepares and returns a MERGE query object.
*

View File

@ -19,21 +19,41 @@ abstract class Database {
*
* This is used for queries that have no reasonable return value anyway, such
* as INSERT statements to a table without a serial primary key.
*
* @deprecated in drupal:9.4.0 and is removed from drupal:11.0.0. There is no
* replacement.
*
* @see https://www.drupal.org/node/3185520
*/
const RETURN_NULL = 0;
/**
* Flag to indicate a query call should return the prepared statement.
*
* @deprecated in drupal:9.4.0 and is removed from drupal:11.0.0. There is no
* replacement.
*
* @see https://www.drupal.org/node/3185520
*/
const RETURN_STATEMENT = 1;
/**
* Flag to indicate a query call should return the number of affected rows.
*
* @deprecated in drupal:9.4.0 and is removed from drupal:11.0.0. There is no
* replacement.
*
* @see https://www.drupal.org/node/3185520
*/
const RETURN_AFFECTED = 2;
/**
* Flag to indicate a query call should return the "last insert id".
*
* @deprecated in drupal:9.4.0 and is removed from drupal:11.0.0. There is no
* replacement.
*
* @see https://www.drupal.org/node/3185520
*/
const RETURN_INSERT_ID = 3;

View File

@ -32,6 +32,8 @@ class Delete extends Query implements ConditionInterface {
* Array of database options.
*/
public function __construct(Connection $connection, $table, array $options = []) {
// @todo Remove $options['return'] in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options);
$this->table = $table;

View File

@ -31,6 +31,8 @@ class Insert extends Query implements \Countable {
* Array of database options.
*/
public function __construct($connection, $table, array $options = []) {
// @todo Remove $options['return'] in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
if (!isset($options['return'])) {
$options['return'] = Database::RETURN_INSERT_ID;
}
@ -82,11 +84,12 @@ class Insert extends Query implements \Countable {
// we wrap it in a transaction so that it is atomic where possible. On many
// databases, such as SQLite, this is also a notable performance boost.
$transaction = $this->connection->startTransaction();
$stmt = $this->connection->prepareStatement((string) $this, $this->queryOptions);
try {
$sql = (string) $this;
foreach ($this->insertValues as $insert_values) {
$last_insert_id = $this->connection->query($sql, $insert_values, $this->queryOptions);
$stmt->execute($insert_values, $this->queryOptions);
$last_insert_id = $this->connection->lastInsertId();
}
}
catch (\Exception $e) {

View File

@ -134,6 +134,8 @@ class Merge extends Query implements ConditionInterface {
* Array of database options.
*/
public function __construct(Connection $connection, $table, array $options = []) {
// @todo Remove $options['return'] in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options);
$this->table = $table;

View File

@ -131,6 +131,8 @@ class Select extends Query implements SelectInterface {
* Array of query options.
*/
public function __construct(Connection $connection, $table, $alias = NULL, $options = []) {
// @todo Remove $options['return'] in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options['return'] = Database::RETURN_STATEMENT;
parent::__construct($connection, $options);
$conjunction = $options['conjunction'] ?? 'AND';

View File

@ -28,6 +28,8 @@ class Truncate extends Query {
* Array of database options.
*/
public function __construct(Connection $connection, $table, array $options = []) {
// @todo Remove $options['return'] in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options);
$this->table = $table;

View File

@ -61,6 +61,8 @@ class Update extends Query implements ConditionInterface {
* Array of database options.
*/
public function __construct(Connection $connection, $table, array $options = []) {
// @todo Remove $options['return'] in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options);
$this->table = $table;

View File

@ -35,6 +35,8 @@ abstract class Upsert extends Query implements \Countable {
* (optional) An array of database options.
*/
public function __construct(Connection $connection, $table, array $options = []) {
// @todo Remove $options['return'] in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options);
$this->table = $table;

View File

@ -9,7 +9,7 @@ class RowCountException extends \RuntimeException implements DatabaseException {
public function __construct($message = '', $code = 0, \Exception $previous = NULL) {
if (empty($message)) {
$message = "rowCount() is supported for DELETE, INSERT, or UPDATE statements performed with structured query builders only, since they would not be portable across database engines otherwise. If the query builders are not sufficient, set the 'return' option to Database::RETURN_AFFECTED to get the number of affected rows.";
$message = "rowCount() is supported for DELETE, INSERT, or UPDATE statements performed with structured query builders only, since they would not be portable across database engines otherwise. If the query builders are not sufficient, use a prepareStatement() with an \$allow_row_count argument set to TRUE, execute() the Statement and get the number of affected rows via rowCount().";
}
parent::__construct($message, $code, $previous);
}

View File

@ -932,6 +932,8 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
}
}
else {
// @todo Remove the 'return' option in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$insert_id = $this->database
->insert($this->baseTable, ['return' => Database::RETURN_INSERT_ID])
->fields((array) $record)
@ -1135,6 +1137,8 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt
$entity->preSaveRevision($this, $record);
if ($entity->isNewRevision()) {
// @todo Remove the 'return' option in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$insert_id = $this->database
->insert($this->revisionTable, ['return' => Database::RETURN_INSERT_ID])
->fields((array) $record)

View File

@ -314,6 +314,8 @@ class MenuTreeStorage implements MenuTreeStorageInterface {
try {
if (!$original) {
// Generate a new mlid.
// @todo Remove the 'return' option in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
$options = ['return' => Database::RETURN_INSERT_ID] + $this->options;
$link['mlid'] = $this->connection->insert($this->table, $options)
->fields(['id' => $link['id'], 'menu_name' => $link['menu_name']])

View File

@ -343,7 +343,8 @@ class Connection extends DatabaseConnection {
}
public function nextId($existing_id = 0) {
$new_id = $this->query('INSERT INTO {sequences} () VALUES ()', [], ['return' => Database::RETURN_INSERT_ID]);
$this->query('INSERT INTO {sequences} () VALUES ()');
$new_id = $this->lastInsertId();
// This should only happen after an import or similar event.
if ($existing_id >= $new_id) {
// If we INSERT a value manually into the sequences table, on the next
@ -354,7 +355,8 @@ class Connection extends DatabaseConnection {
// UPDATE in such a way that the UPDATE does not do anything. This way,
// duplicate keys do not generate errors but everything else does.
$this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', [':value' => $existing_id]);
$new_id = $this->query('INSERT INTO {sequences} () VALUES ()', [], ['return' => Database::RETURN_INSERT_ID]);
$this->query('INSERT INTO {sequences} () VALUES ()');
$new_id = $this->lastInsertId();
}
$this->needsCleanup = TRUE;
return $new_id;

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\mysql\Driver\Database\mysql;
use Drupal\Core\Database\Query\Delete as QueryDelete;
/**
* MySQL implementation of \Drupal\Core\Database\Query\Delete.
*/
class Delete extends QueryDelete {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
}

View File

@ -9,6 +9,16 @@ use Drupal\Core\Database\Query\Insert as QueryInsert;
*/
class Insert extends QueryInsert {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
public function execute() {
if (!$this->preExecute()) {
return NULL;
@ -29,7 +39,14 @@ class Insert extends QueryInsert {
$values = $this->fromQuery->getArguments();
}
$last_insert_id = $this->connection->query((string) $this, $values, $this->queryOptions);
$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 = [];

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\mysql\Driver\Database\mysql;
use Drupal\Core\Database\Query\Merge as QueryMerge;
/**
* MySQL implementation of \Drupal\Core\Database\Query\Merge.
*/
class Merge extends QueryMerge {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\mysql\Driver\Database\mysql;
use Drupal\Core\Database\Query\Select as QuerySelect;
/**
* MySQL implementation of \Drupal\Core\Database\Query\Select.
*/
class Select extends QuerySelect {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, $table, $alias = NULL, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $alias, $options);
unset($this->queryOptions['return']);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\mysql\Driver\Database\mysql;
use Drupal\Core\Database\Query\Truncate as QueryTruncate;
/**
* MySQL implementation of \Drupal\Core\Database\Query\Truncate.
*/
class Truncate extends QueryTruncate {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\mysql\Driver\Database\mysql;
use Drupal\Core\Database\Query\Update as QueryUpdate;
/**
* MySQL implementation of \Drupal\Core\Database\Query\Update.
*/
class Update extends QueryUpdate {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
}

View File

@ -9,6 +9,16 @@ use Drupal\Core\Database\Query\Upsert as QueryUpsert;
*/
class Upsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
/**
* {@inheritdoc}
*/

View File

@ -9,6 +9,16 @@ use Drupal\Core\Database\Query\Delete as QueryDelete;
*/
class Delete extends QueryDelete {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
/**
* {@inheritdoc}
*/

View File

@ -3,7 +3,6 @@
namespace Drupal\pgsql\Driver\Database\pgsql;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Database\IntegrityConstraintViolationException;
use Drupal\Core\Database\Query\Insert as QueryInsert;
// cSpell:ignore nextval setval
@ -18,6 +17,16 @@ use Drupal\Core\Database\Query\Insert as QueryInsert;
*/
class Insert extends QueryInsert {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
public function execute() {
if (!$this->preExecute()) {
return NULL;
@ -92,20 +101,9 @@ class Insert extends QueryInsert {
}
$this->connection->releaseSavepoint();
}
catch (\PDOException $e) {
$this->connection->rollbackSavepoint();
$message = $e->getMessage() . ": " . $stmt->getQueryString();
// Match all SQLSTATE 23xxx errors.
if (substr($e->getCode(), -6, -3) == '23') {
throw new IntegrityConstraintViolationException($message, $e->getCode(), $e);
}
else {
throw new DatabaseExceptionWrapper($message, 0, $e->getCode());
}
}
catch (\Exception $e) {
$this->connection->rollbackSavepoint();
throw $e;
$this->connection->exceptionHandler()->handleExecutionException($e, $stmt, [], $this->queryOptions);
}
// Re-initialize the values array so that we can re-use this query.

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\pgsql\Driver\Database\pgsql;
use Drupal\Core\Database\Query\Merge as QueryMerge;
/**
* PostgreSQL implementation of \Drupal\Core\Database\Query\Merge.
*/
class Merge extends QueryMerge {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
}

View File

@ -14,6 +14,16 @@ use Drupal\Core\Database\Query\Select as QuerySelect;
*/
class Select extends QuerySelect {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, $table, $alias = NULL, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $alias, $options);
unset($this->queryOptions['return']);
}
public function orderRandom() {
$alias = $this->addExpression('RANDOM()', 'random_field');
$this->orderBy($alias);

View File

@ -9,6 +9,16 @@ use Drupal\Core\Database\Query\Truncate as QueryTruncate;
*/
class Truncate extends QueryTruncate {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
/**
* {@inheritdoc}
*/

View File

@ -10,6 +10,16 @@ use Drupal\Core\Database\Query\SelectInterface;
*/
class Update extends QueryUpdate {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
public function execute() {
$max_placeholder = 0;
$blobs = [];

View File

@ -11,6 +11,16 @@ use Drupal\Core\Database\Query\Upsert as QueryUpsert;
*/
class Upsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
/**
* {@inheritdoc}
*/

View File

@ -435,6 +435,10 @@ class Connection extends DatabaseConnection {
* {@inheritdoc}
*/
public function prepareStatement(string $query, array $options, bool $allow_row_count = FALSE): StatementInterface {
if (isset($options['return'])) {
@trigger_error('Passing "return" option to ' . __METHOD__ . '() is deprecated in drupal:9.4.0 and is removed in drupal:11.0.0. For data manipulation operations, use dynamic queries instead. See https://www.drupal.org/node/3185520', E_USER_DEPRECATED);
}
try {
$query = $this->preprocessStatement($query, $options);
$statement = new Statement($this->connection, $this, $query, $options['pdo'] ?? [], $allow_row_count);

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\sqlite\Driver\Database\sqlite;
use Drupal\Core\Database\Query\Delete as QueryDelete;
/**
* SQLite implementation of \Drupal\Core\Database\Query\Delete.
*/
class Delete extends QueryDelete {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
}

View File

@ -13,16 +13,61 @@ use Drupal\Core\Database\Query\Insert as QueryInsert;
*/
class Insert extends QueryInsert {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
if (count($this->insertFields) || !empty($this->fromQuery)) {
return parent::execute();
// 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)) {
// The SelectQuery may contain arguments, load and pass them through.
return $this->connection->query((string) $this, $this->fromQuery->getArguments(), $this->queryOptions);
}
// We wrap the insert in a transaction so that it is atomic where possible.
// In SQLite, this is also a notable performance boost.
$transaction = $this->connection->startTransaction();
if (count($this->insertFields)) {
// Each insert happens in its own query.
$stmt = $this->connection->prepareStatement((string) $this, $this->queryOptions);
foreach ($this->insertValues as $insert_values) {
try {
$stmt->execute($insert_values, $this->queryOptions);
}
catch (\Exception $e) {
// One of the INSERTs failed, rollback the whole batch.
$transaction->rollBack();
$this->connection->exceptionHandler()->handleExecutionException($e, $stmt, $insert_values, $this->queryOptions);
}
}
// Re-initialize the values array so that we can re-use this query.
$this->insertValues = [];
}
else {
return $this->connection->query('INSERT INTO {' . $this->table . '} DEFAULT VALUES', [], $this->queryOptions);
$stmt = $this->connection->prepareStatement("INSERT INTO {{$this->table}} DEFAULT VALUES", $this->queryOptions);
try {
$stmt->execute(NULL, $this->queryOptions);
}
catch (\Exception $e) {
$transaction->rollBack();
$this->connection->exceptionHandler()->handleExecutionException($e, $stmt, [], $this->queryOptions);
}
}
// Transaction commits here when $transaction looses scope.
return $this->connection->lastInsertId();
}
public function __toString() {

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\sqlite\Driver\Database\sqlite;
use Drupal\Core\Database\Query\Merge as QueryMerge;
/**
* SQLite implementation of \Drupal\Core\Database\Query\Merge.
*/
class Merge extends QueryMerge {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
}

View File

@ -9,6 +9,16 @@ use Drupal\Core\Database\Query\Select as QuerySelect;
*/
class Select extends QuerySelect {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, $table, $alias = NULL, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $alias, $options);
unset($this->queryOptions['return']);
}
public function forUpdate($set = TRUE) {
// SQLite does not support FOR UPDATE so nothing to do.
return $this;

View File

@ -12,6 +12,16 @@ use Drupal\Core\Database\Query\Truncate as QueryTruncate;
*/
class Truncate extends QueryTruncate {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);

View File

@ -0,0 +1,22 @@
<?php
namespace Drupal\sqlite\Driver\Database\sqlite;
use Drupal\Core\Database\Query\Update as QueryUpdate;
/**
* SQLite implementation of \Drupal\Core\Database\Query\Update.
*/
class Update extends QueryUpdate {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
}

View File

@ -11,6 +11,16 @@ use Drupal\Core\Database\Query\Upsert as QueryUpsert;
*/
class Upsert extends QueryUpsert {
/**
* {@inheritdoc}
*/
public function __construct(Connection $connection, string $table, array $options = []) {
// @todo Remove the __construct in Drupal 11.
// @see https://www.drupal.org/project/drupal/issues/3256524
parent::__construct($connection, $table, $options);
unset($this->queryOptions['return']);
}
/**
* {@inheritdoc}
*/

View File

@ -3,3 +3,6 @@ type: module
description: 'Support database contrib driver testing.'
package: Testing
version: VERSION
dependencies:
- drupal:mysql
- drupal:pgsql

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\mysql\Driver\Database\mysql\Delete as CoreDelete;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Delete.
*/
class Delete extends CoreDelete {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\mysql\Driver\Database\mysql\Merge as CoreMerge;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Merge.
*/
class Merge extends CoreMerge {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\mysql\Driver\Database\mysql\Select as CoreSelect;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Select.
*/
class Select extends CoreSelect {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\mysql\Driver\Database\mysql\Truncate as CoreTruncate;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Truncate.
*/
class Truncate extends CoreTruncate {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysql;
use Drupal\mysql\Driver\Database\mysql\Update as CoreUpdate;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Update.
*/
class Update extends CoreUpdate {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion;
use Drupal\mysql\Driver\Database\mysql\Delete as CoreDelete;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Delete.
*/
class Delete extends CoreDelete {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion;
use Drupal\mysql\Driver\Database\mysql\Merge as CoreMerge;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Merge.
*/
class Merge extends CoreMerge {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion;
use Drupal\mysql\Driver\Database\mysql\Select as CoreSelect;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Select.
*/
class Select extends CoreSelect {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion;
use Drupal\mysql\Driver\Database\mysql\Truncate as CoreTruncate;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Truncate.
*/
class Truncate extends CoreTruncate {}

View File

@ -0,0 +1,10 @@
<?php
namespace Drupal\driver_test\Driver\Database\DrivertestMysqlDeprecatedVersion;
use Drupal\mysql\Driver\Database\mysql\Update as CoreUpdate;
/**
* MySQL test implementation of \Drupal\Core\Database\Query\Update.
*/
class Update extends CoreUpdate {}

View File

@ -2,6 +2,10 @@
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
// cSpell:ignore aquery aprepare
/**
* Tests Drupal's extended prepared statement syntax..
*
@ -160,4 +164,22 @@ class QueryTest extends DatabaseTestBase {
$this->assertEquals('Update value 1', $result->update);
}
/**
* Tests deprecation of the 'return' query option.
*
* @covers ::query
* @covers ::prepareStatement
*
* @group legacy
*/
public function testReturnOptionDeprecation() {
$this->expectDeprecation('Passing "return" option to %Aquery() is deprecated in drupal:9.4.0 and is removed in drupal:11.0.0. For data manipulation operations, use dynamic queries instead. See https://www.drupal.org/node/3185520');
$this->expectDeprecation('Passing "return" option to %AprepareStatement() is deprecated in drupal:9.4.0 and is removed in drupal:11.0.0. For data manipulation operations, use dynamic queries instead. See https://www.drupal.org/node/3185520');
$this->assertIsInt((int) $this->connection->query('INSERT INTO {test} ([name], [age], [job]) VALUES (:name, :age, :job)', [
':name' => 'Magoo',
':age' => 56,
':job' => 'Driver',
], ['return' => Database::RETURN_INSERT_ID]));
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\KernelTests\Core\Database;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\StatementInterface;
/**
@ -24,7 +23,6 @@ class StatementTest extends DatabaseTestBase {
':age' => '30',
];
$options = [
'return' => Database::RETURN_STATEMENT,
'allow_square_brackets' => FALSE,
];

View File

@ -15,7 +15,7 @@ class RowCountExceptionTest extends UnitTestCase {
/**
* The default exception message.
*/
private const DEFAULT_EXCEPTION_MESSAGE = "rowCount() is supported for DELETE, INSERT, or UPDATE statements performed with structured query builders only, since they would not be portable across database engines otherwise. If the query builders are not sufficient, set the 'return' option to Database::RETURN_AFFECTED to get the number of affected rows.";
private const DEFAULT_EXCEPTION_MESSAGE = "rowCount() is supported for DELETE, INSERT, or UPDATE statements performed with structured query builders only, since they would not be portable across database engines otherwise. If the query builders are not sufficient, use a prepareStatement() with an \$allow_row_count argument set to TRUE, execute() the Statement and get the number of affected rows via rowCount().";
/**
* Data provider for ::testExceptionMessage()