From cf44d3882fabebd9ad4bb4d9aa324a8437eded13 Mon Sep 17 00:00:00 2001 From: webchick <drupal@webchick.net> Date: Wed, 1 Oct 2014 01:16:23 -0700 Subject: [PATCH] Issue #2181291 by lokapujya, ricardoamaro, mradcliffe, fvideon, Damien Tournoud, Crell, bzrudi71, steinmb, grom358, jaredsmith: Fixed Prevent a query from aborting the entire transaction in pgsql. --- .../Core/Database/Driver/pgsql/Connection.php | 51 ++++++++++++++++++- .../Core/Database/Driver/pgsql/Delete.php | 21 +++++++- .../Core/Database/Driver/pgsql/Schema.php | 41 +++++++++++---- .../Core/Database/Driver/pgsql/Select.php | 17 +++++++ .../Core/Database/Driver/pgsql/Truncate.php | 20 +++++++- .../Core/Database/Driver/pgsql/Update.php | 12 ++++- 6 files changed, 148 insertions(+), 14 deletions(-) diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php index 7cf2659a82f4..98b7a6b050d8 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php @@ -12,6 +12,7 @@ use Drupal\Core\Database\DatabaseNotFoundException; use Drupal\Core\Database\StatementInterface; use Drupal\Core\Database\IntegrityConstraintViolationException; +use Drupal\Core\Database\DatabaseExceptionWrapper; /** * @addtogroup database @@ -101,7 +102,6 @@ public static function open(array &$connection_options = array()) { return $pdo; } - public function query($query, array $args = array(), $options = array()) { $options += $this->defaultOptions(); @@ -148,6 +148,9 @@ public function query($query, array $args = array(), $options = array()) { if (substr($e->getCode(), -6, -3) == '23') { $e = new IntegrityConstraintViolationException($e->getMessage(), $e->getCode(), $e); } + else { + $e = new DatabaseExceptionWrapper($e->getMessage(), 0, $e); + } // Add additional debug information. if ($query instanceof StatementInterface) { $e->query_string = $stmt->getQueryString(); @@ -274,6 +277,52 @@ public function nextId($existing = 0) { return $id; } + + /** + * Add a new savepoint with an unique name. + * + * The main use for this method is to mimic InnoDB functionality, which + * provides an inherent savepoint before any query in a transaction. + * + * @param $savepoint_name + * A string representing the savepoint name. By default, + * "mimic_implicit_commit" is used. + * + * @see Drupal\Core\Database\Connection::pushTransaction(). + */ + public function addSavepoint($savepoint_name = 'mimic_implicit_commit') { + if ($this->inTransaction()) { + $this->pushTransaction($savepoint_name); + } + } + + /** + * Release a savepoint by name. + * + * @param $savepoint_name + * A string representing the savepoint name. By default, + * "mimic_implicit_commit" is used. + * + * @see Drupal\Core\Database\Connection::popTransaction(). + */ + public function releaseSavepoint($savepoint_name = 'mimic_implicit_commit') { + if (isset($this->transactionLayers[$savepoint_name])) { + $this->popTransaction($savepoint_name); + } + } + + /** + * Rollback a savepoint by name if it exists. + * + * @param $savepoint_name + * A string representing the savepoint name. By default, + * "mimic_implicit_commit" is used. + */ + public function rollbackSavepoint($savepoint_name = 'mimic_implicit_commit') { + if (isset($this->transactionLayers[$savepoint_name])) { + $this->rollback($savepoint_name); + } + } } /** diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Delete.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Delete.php index e9914ca97d9b..85c5003f2381 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Delete.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Delete.php @@ -9,4 +9,23 @@ use Drupal\Core\Database\Query\Delete as QueryDelete; -class Delete extends QueryDelete { } +class Delete extends QueryDelete { + + /** + * {@inheritdoc} + */ + public function execute() { + $this->connection->addSavepoint(); + try { + $result = parent::execute(); + } + catch (\Exception $e) { + $this->connection->rollbackSavepoint(); + throw $e; + } + $this->connection->releaseSavepoint(); + + return $result; + } + +} diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php index 63349ade37dc..219384f5d16e 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php @@ -60,11 +60,21 @@ public function queryTableInformation($table) { 'sequences' => array(), ); // Don't use {} around information_schema.columns table. - $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array( - ':schema' => $schema, - ':table' => $table_name, - ':default' => '%nextval%', - )); + $this->connection->addSavepoint(); + + try { + $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array( + ':schema' => $schema, + ':table' => $table_name, + ':default' => '%nextval%', + )); + } + catch (\Exception $e) { + $this->connection->rollbackSavepoint(); + throw $e; + } + $this->connection->releaseSavepoint(); + foreach ($result as $column) { if ($column->data_type == 'bytea') { $table_information->blob_fields[$column->column_name] = TRUE; @@ -102,11 +112,22 @@ public function queryFieldInformation($table, $field) { $schema = $prefixInfo['schema']; $table_name = $prefixInfo['table']; - $checks = $this->connection->query("SELECT conname FROM pg_class cl INNER JOIN pg_constraint co ON co.conrelid = cl.oid INNER JOIN pg_attribute attr ON attr.attrelid = cl.oid AND attr.attnum = ANY (co.conkey) INNER JOIN pg_namespace ns ON cl.relnamespace = ns.oid WHERE co.contype = 'c' AND ns.nspname = :schema AND cl.relname = :table AND attr.attname = :column", array( - ':schema' => $schema, - ':table' => $table_name, - ':column' => $field, - )); + $this->connection->addSavepoint(); + + try { + $checks = $this->connection->query("SELECT conname FROM pg_class cl INNER JOIN pg_constraint co ON co.conrelid = cl.oid INNER JOIN pg_attribute attr ON attr.attrelid = cl.oid AND attr.attnum = ANY (co.conkey) INNER JOIN pg_namespace ns ON cl.relnamespace = ns.oid WHERE co.contype = 'c' AND ns.nspname = :schema AND cl.relname = :table AND attr.attname = :column", array( + ':schema' => $schema, + ':table' => $table_name, + ':column' => $field, + )); + } + catch (\Exception $e) { + $this->connection->rollbackSavepoint(); + throw $e; + } + + $this->connection->releaseSavepoint(); + $field_information = $checks->fetchCol(); return $field_information; diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php index 4065964cd243..de9409f03cf4 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php @@ -108,6 +108,23 @@ public function orderBy($field, $direction = 'ASC') { $this->addField(NULL, $field); return $return; } + + /** + * {@inheritdoc} + */ + public function execute() { + $this->connection->addSavepoint(); + try { + $result = parent::execute(); + } + catch (\Exception $e) { + $this->connection->rollbackSavepoint(); + throw $e; + } + $this->connection->releaseSavepoint(); + + return $result; + } } /** diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Truncate.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Truncate.php index e2fa83dac7c4..5357e6b31bce 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Truncate.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Truncate.php @@ -9,4 +9,22 @@ use Drupal\Core\Database\Query\Truncate as QueryTruncate; -class Truncate extends QueryTruncate { } +class Truncate extends QueryTruncate { + + /** + * {@inheritdoc} + */ + public function execute() { + $this->connection->addSavepoint(); + try { + $result = parent::execute(); + } + catch (\Exception $e) { + $this->connection->rollbackSavepoint(); + throw $e; + } + $this->connection->releaseSavepoint(); + + return $result; + } +} diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Update.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Update.php index f424dfe0ee3b..09efb6ce1965 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Update.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Update.php @@ -66,7 +66,17 @@ public function execute() { $options = $this->queryOptions; $options['already_prepared'] = TRUE; $options['return'] = Database::RETURN_AFFECTED; - return $this->connection->query($stmt, array(), $options); + + $this->connection->addSavepoint(); + try { + $result = $this->connection->query($stmt, array(), $options); + $this->connection->releaseSavepoint(); + return $result; + } + catch (\Exception $e) { + $this->connection->rollbackSavepoint(); + throw $e; + } } } -- GitLab