From 47a6ebb4e380256d72ace0931161f5efa427fa72 Mon Sep 17 00:00:00 2001
From: Daniel Hensby <dhensby@silverstripe.com>
Date: Thu, 8 Feb 2018 20:00:49 +0000
Subject: [PATCH] FIX Allow nested transactions

---
 code/PostgreSQLDatabase.php | 37 ++++++++++++++++++++++++++++---------
 1 file changed, 28 insertions(+), 9 deletions(-)

diff --git a/code/PostgreSQLDatabase.php b/code/PostgreSQLDatabase.php
index 3d488d7..a0d1048 100644
--- a/code/PostgreSQLDatabase.php
+++ b/code/PostgreSQLDatabase.php
@@ -36,6 +36,11 @@ class PostgreSQLDatabase extends Database
      */
     protected $schema;
 
+    /**
+     * @var bool
+     */
+    protected $transactionNesting = 0;
+
     /**
      * Toggle if transactions are supported. Defaults to true.
      *
@@ -519,15 +524,20 @@ public function supportsExtensions($extensions = array('partitions', 'tablespace
 
     public function transactionStart($transaction_mode = false, $session_characteristics = false)
     {
-        $this->query('BEGIN;');
+        if ($this->transactionNesting > 0) {
+            $this->transactionSavepoint('NESTEDTRANSACTION' . $this->transactionNesting);
+        } else {
+            $this->query('BEGIN;');
 
-        if ($transaction_mode) {
-            $this->query("SET TRANSACTION {$transaction_mode};");
-        }
+            if ($transaction_mode) {
+                $this->query("SET TRANSACTION {$transaction_mode};");
+            }
 
-        if ($session_characteristics) {
-            $this->query("SET SESSION CHARACTERISTICS AS TRANSACTION {$session_characteristics};");
+            if ($session_characteristics) {
+                $this->query("SET SESSION CHARACTERISTICS AS TRANSACTION {$session_characteristics};");
+            }
         }
+        ++$this->transactionNesting;
     }
 
     public function transactionSavepoint($savepoint)
@@ -538,15 +548,24 @@ public function transactionSavepoint($savepoint)
     public function transactionRollback($savepoint = false)
     {
         if ($savepoint) {
-            $this->query("ROLLBACK TO {$savepoint};");
+            $this->query('ROLLBACK TO ' . $savepoint);
         } else {
-            $this->query('ROLLBACK;');
+            --$this->transactionNesting;
+            if ($this->transactionNesting > 0) {
+                $this->transactionRollback('NESTEDTRANSACTION' . $this->transactionNesting);
+            } else {
+                $this->query('ROLLBACK');
+            }
         }
     }
 
     public function transactionEnd($chain = false)
     {
-        $this->query('COMMIT;');
+        --$this->transactionNesting;
+        if ($this->transactionNesting <= 0) {
+            $this->transactionNesting = 0;
+            $this->query('COMMIT;');
+        }
     }
 
     public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null, $parameterised = false)