From d5ede257ecba3281272e32232c4306641b2c3629 Mon Sep 17 00:00:00 2001 From: Daniel Alm Date: Mon, 9 May 2016 16:43:57 +0200 Subject: [PATCH] Fix transactions not being rolled back when the individual statements succeed, but the committing the transaction fails. --- SQLite/Core/Connection.swift | 2 +- SQLiteTests/ConnectionTests.swift | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/SQLite/Core/Connection.swift b/SQLite/Core/Connection.swift index 04fdd901..62a2b812 100644 --- a/SQLite/Core/Connection.swift +++ b/SQLite/Core/Connection.swift @@ -333,11 +333,11 @@ public final class Connection { try self.run(begin) do { try block() + try self.run(commit) } catch { try self.run(rollback) throw error } - try self.run(commit) } } diff --git a/SQLiteTests/ConnectionTests.swift b/SQLiteTests/ConnectionTests.swift index 3d7dd3eb..eda4677a 100644 --- a/SQLiteTests/ConnectionTests.swift +++ b/SQLiteTests/ConnectionTests.swift @@ -125,6 +125,33 @@ class ConnectionTests : SQLiteTestCase { AssertSQL("ROLLBACK TRANSACTION", 0) } + func test_transaction_rollsBackTransactionsIfCommitsFail() { + // This test case needs to emulate an environment where the individual statements succeed, but committing the + // transactuin fails. Using deferred foreign keys is one option to achieve this. + try! db.execute("PRAGMA foreign_keys = ON;") + try! db.execute("PRAGMA defer_foreign_keys = ON;") + let stmt = try! db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)", "alice@example.com", 100) + + do { + try db.transaction { + try stmt.run() + } + } catch { + } + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO users (email, manager_id) VALUES ('alice@example.com', 100)") + AssertSQL("COMMIT TRANSACTION") + AssertSQL("ROLLBACK TRANSACTION") + + // Run another transaction to ensure that a subsequent transaction does not fail with an "cannot start a + // transaction within a transaction" error. + let stmt2 = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + try! db.transaction { + try stmt2.run() + } + } + func test_transaction_beginsAndRollsTransactionsBack() { let stmt = try! db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com")