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