Unverified Commit 9a28ddce authored by alexpott's avatar alexpott
Browse files

Issue #3185269 by mondrake, daffie, alexpott, andypost: Introduce...

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

(cherry picked from commit 87dc8f41)
parent ca7158d4
......@@ -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);
}
}
......
......@@ -143,6 +143,8 @@ public function write($name, array $data) {
* @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 @@ protected static function schemaDefinition() {
* @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 @@ public function delete($name) {
* @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 @@ public function listAll($prefix = '') {
*/
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')
......
......@@ -367,12 +367,12 @@ public function __destruct() {
* 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 @@ public function __destruct() {
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 @@ public function getFullQualifiedTableName($table) {
* @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 @@ protected function filterComment($comment = '') {
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 @@ public function query($query, array $args = [], $options = []) {
// 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 @@ public function insert($table, array $options = []) {
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.
*
......
......@@ -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;
......
......@@ -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;
......
......@@ -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 @@ public function execute() {
// 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) {
......
......@@ -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;
......
......@@ -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';
......
......@@ -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;
......
......@@ -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;
......
......@@ -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;
......
......@@ -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);
}
......
......@@ -932,6 +932,8 @@ protected function doSaveFieldItems(ContentEntityInterface $entity, array $names
}
}
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 @@ protected function saveRevision(ContentEntityInterface $entity) {
$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)
......
......@@ -314,6 +314,8 @@ protected function doSave(array $link) {
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']])
......
......@@ -343,7 +343,8 @@ public function mapConditionOperator($operator) {
}
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 @@ public function nextId($existing_id = 0) {
// 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;
......
<?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']);
}
}
......@@ -9,6 +9,16 @@
*/
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 @@ public function execute() {
$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 = [];
......
<?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']);
}
}
<?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']);
}
}
<?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']);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment