From cc0044a603d58d3f9f5fc87e305a8bc3e9f14fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Rombauts?= Date: Thu, 15 Dec 2022 19:00:04 +0100 Subject: [PATCH] Add a Transaction::rollback() method --- include/SQLiteCpp/Transaction.h | 5 + src/Transaction.cpp | 18 ++- tests/Transaction_test.cpp | 236 ++++++++++++++++---------------- 3 files changed, 138 insertions(+), 121 deletions(-) diff --git a/include/SQLiteCpp/Transaction.h b/include/SQLiteCpp/Transaction.h index e6d2618..af9de7e 100644 --- a/include/SQLiteCpp/Transaction.h +++ b/include/SQLiteCpp/Transaction.h @@ -86,6 +86,11 @@ public: */ void commit(); + /** + * @brief Rollback the transaction + */ + void rollback(); + private: Database& mDatabase; ///< Reference to the SQLite Database Connection bool mbCommited = false; ///< True when commit has been called diff --git a/src/Transaction.cpp b/src/Transaction.cpp index fb640cd..fb02531 100644 --- a/src/Transaction.cpp +++ b/src/Transaction.cpp @@ -44,7 +44,7 @@ Transaction::Transaction(Database& aDatabase, TransactionBehavior behavior) : Transaction::Transaction(Database &aDatabase) : mDatabase(aDatabase) { - mDatabase.exec("BEGIN"); + mDatabase.exec("BEGIN TRANSACTION"); } // Safely rollback the transaction if it has not been committed. @@ -54,7 +54,7 @@ Transaction::~Transaction() { try { - mDatabase.exec("ROLLBACK"); + mDatabase.exec("ROLLBACK TRANSACTION"); } catch (SQLite::Exception&) { @@ -68,7 +68,7 @@ void Transaction::commit() { if (false == mbCommited) { - mDatabase.exec("COMMIT"); + mDatabase.exec("COMMIT TRANSACTION"); mbCommited = true; } else @@ -77,5 +77,17 @@ void Transaction::commit() } } +// Rollback the transaction +void Transaction::rollback() +{ + if (false == mbCommited) + { + mDatabase.exec("ROLLBACK TRANSACTION"); + } + else + { + throw SQLite::Exception("Transaction already committed."); + } +} } // namespace SQLite diff --git a/tests/Transaction_test.cpp b/tests/Transaction_test.cpp index 9ef4531..4593769 100644 --- a/tests/Transaction_test.cpp +++ b/tests/Transaction_test.cpp @@ -1,118 +1,118 @@ -/** - * @file Transaction_test.cpp - * @ingroup tests - * @brief Test of a SQLite Transaction. - * - * Copyright (c) 2012-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ - -#include -#include -#include -#include - -#include - -#include - -TEST(Transaction, commitRollback) -{ - // Create a new database - SQLite::Database db(":memory:", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); - EXPECT_EQ(SQLite::OK, db.getErrorCode()); - - { - // Begin transaction - SQLite::Transaction transaction(db); - - EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); - EXPECT_EQ(SQLite::OK, db.getErrorCode()); - - // Insert a first value - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")")); - EXPECT_EQ(1, db.getLastInsertRowid()); - - // Commit transaction - transaction.commit(); - - // Commit again throw an exception - EXPECT_THROW(transaction.commit(), SQLite::Exception); - } - - // ensure transactions with different types are well-formed - { - for (auto behavior : { - SQLite::TransactionBehavior::DEFERRED, - SQLite::TransactionBehavior::IMMEDIATE, - SQLite::TransactionBehavior::EXCLUSIVE }) - { - SQLite::Transaction transaction(db, behavior); - transaction.commit(); - } - - EXPECT_THROW(SQLite::Transaction(db, static_cast(-1)), SQLite::Exception); - } - - // Auto rollback if no commit() before the end of scope - { - // Begin transaction - SQLite::Transaction transaction(db); - - // Insert a second value (that will be rollbacked) - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")")); - EXPECT_EQ(2, db.getLastInsertRowid()); - - // end of scope: automatic rollback - } - - // Auto rollback of a transaction on error/exception - try - { - // Begin transaction - SQLite::Transaction transaction(db); - - // Insert a second value (that will be rollbacked) - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"second\")")); - EXPECT_EQ(2, db.getLastInsertRowid()); - - // Execute with an error => exception with auto-rollback - db.exec("DesiredSyntaxError to raise an exception to rollback the transaction"); - - GTEST_FATAL_FAILURE_("we should never get there"); - transaction.commit(); // We should never get there - } - catch (std::exception& e) - { - std::cout << "SQLite exception: " << e.what() << std::endl; - // expected error, see above - } - - // Double rollback with a manual command before the end of scope - { - // Begin transaction - SQLite::Transaction transaction(db); - - // Insert a second value (that will be rollbacked) - EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")")); - EXPECT_EQ(2, db.getLastInsertRowid()); - - // Execute a manual rollback (no real use case I can think of, so no rollback() method) - db.exec("ROLLBACK"); - - // end of scope: the automatic rollback should not raise an error because it is harmless - } - - // Check the results (expect only one row of result, as all other one have been rollbacked) - SQLite::Statement query(db, "SELECT * FROM test"); - int nbRows = 0; - while (query.executeStep()) - { - nbRows++; - EXPECT_EQ(1, query.getColumn(0).getInt()); - EXPECT_STREQ("first", query.getColumn(1).getText()); - } - EXPECT_EQ(1, nbRows); -} +/** + * @file Transaction_test.cpp + * @ingroup tests + * @brief Test of a SQLite Transaction. + * + * Copyright (c) 2012-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ + +#include +#include +#include +#include + +#include + +#include + +TEST(Transaction, commitRollback) +{ + // Create a new database + SQLite::Database db(":memory:", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + + { + // Begin transaction + SQLite::Transaction transaction(db); + + EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); + EXPECT_EQ(SQLite::OK, db.getErrorCode()); + + // Insert a first value + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")")); + EXPECT_EQ(1, db.getLastInsertRowid()); + + // Commit transaction + transaction.commit(); + + // Commit again throw an exception + EXPECT_THROW(transaction.commit(), SQLite::Exception); + } + + // ensure transactions with different types are well-formed + { + for (auto behavior : { + SQLite::TransactionBehavior::DEFERRED, + SQLite::TransactionBehavior::IMMEDIATE, + SQLite::TransactionBehavior::EXCLUSIVE }) + { + SQLite::Transaction transaction(db, behavior); + transaction.commit(); + } + + EXPECT_THROW(SQLite::Transaction(db, static_cast(-1)), SQLite::Exception); + } + + // Auto rollback if no commit() before the end of scope + { + // Begin transaction + SQLite::Transaction transaction(db); + + // Insert a second value (that will be rollbacked) + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")")); + EXPECT_EQ(2, db.getLastInsertRowid()); + + // end of scope: automatic rollback + } + + // Auto rollback of a transaction on error/exception + try + { + // Begin transaction + SQLite::Transaction transaction(db); + + // Insert a second value (that will be rollbacked) + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"second\")")); + EXPECT_EQ(2, db.getLastInsertRowid()); + + // Execute with an error => exception with auto-rollback + db.exec("DesiredSyntaxError to raise an exception to rollback the transaction"); + + GTEST_FATAL_FAILURE_("we should never get there"); + transaction.commit(); // We should never get there + } + catch (std::exception& e) + { + std::cout << "SQLite exception: " << e.what() << std::endl; + // expected error, see above + } + + // Double rollback with a manual command before the end of scope + { + // Begin transaction + SQLite::Transaction transaction(db); + + // Insert a second value (that will be rollbacked) + EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")")); + EXPECT_EQ(2, db.getLastInsertRowid()); + + // Execute a manual rollback + transaction.rollback(); + + // end of scope: the automatic rollback should not raise an error because it is harmless + } + + // Check the results (expect only one row of result, as all other one have been rollbacked) + SQLite::Statement query(db, "SELECT * FROM test"); + int nbRows = 0; + while (query.executeStep()) + { + nbRows++; + EXPECT_EQ(1, query.getColumn(0).getInt()); + EXPECT_STREQ("first", query.getColumn(1).getText()); + } + EXPECT_EQ(1, nbRows); +}