Add a Transaction::rollback() method

This commit is contained in:
Sébastien Rombauts 2022-12-15 19:00:04 +01:00
parent fc92d9cf92
commit cc0044a603
3 changed files with 138 additions and 121 deletions

View File

@ -86,6 +86,11 @@ public:
*/ */
void commit(); void commit();
/**
* @brief Rollback the transaction
*/
void rollback();
private: private:
Database& mDatabase; ///< Reference to the SQLite Database Connection Database& mDatabase; ///< Reference to the SQLite Database Connection
bool mbCommited = false; ///< True when commit has been called bool mbCommited = false; ///< True when commit has been called

View File

@ -44,7 +44,7 @@ Transaction::Transaction(Database& aDatabase, TransactionBehavior behavior) :
Transaction::Transaction(Database &aDatabase) : Transaction::Transaction(Database &aDatabase) :
mDatabase(aDatabase) mDatabase(aDatabase)
{ {
mDatabase.exec("BEGIN"); mDatabase.exec("BEGIN TRANSACTION");
} }
// Safely rollback the transaction if it has not been committed. // Safely rollback the transaction if it has not been committed.
@ -54,7 +54,7 @@ Transaction::~Transaction()
{ {
try try
{ {
mDatabase.exec("ROLLBACK"); mDatabase.exec("ROLLBACK TRANSACTION");
} }
catch (SQLite::Exception&) catch (SQLite::Exception&)
{ {
@ -68,7 +68,7 @@ void Transaction::commit()
{ {
if (false == mbCommited) if (false == mbCommited)
{ {
mDatabase.exec("COMMIT"); mDatabase.exec("COMMIT TRANSACTION");
mbCommited = true; mbCommited = true;
} }
else 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 } // namespace SQLite

View File

@ -1,118 +1,118 @@
/** /**
* @file Transaction_test.cpp * @file Transaction_test.cpp
* @ingroup tests * @ingroup tests
* @brief Test of a SQLite Transaction. * @brief Test of a SQLite Transaction.
* *
* Copyright (c) 2012-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com) * Copyright (c) 2012-2020 Sebastien Rombauts (sebastien.rombauts@gmail.com)
* *
* Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
* or copy at http://opensource.org/licenses/MIT) * or copy at http://opensource.org/licenses/MIT)
*/ */
#include <SQLiteCpp/Transaction.h> #include <SQLiteCpp/Transaction.h>
#include <SQLiteCpp/Database.h> #include <SQLiteCpp/Database.h>
#include <SQLiteCpp/Statement.h> #include <SQLiteCpp/Statement.h>
#include <SQLiteCpp/Exception.h> #include <SQLiteCpp/Exception.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <cstdio> #include <cstdio>
TEST(Transaction, commitRollback) TEST(Transaction, commitRollback)
{ {
// Create a new database // Create a new database
SQLite::Database db(":memory:", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE); SQLite::Database db(":memory:", SQLite::OPEN_READWRITE|SQLite::OPEN_CREATE);
EXPECT_EQ(SQLite::OK, db.getErrorCode()); EXPECT_EQ(SQLite::OK, db.getErrorCode());
{ {
// Begin transaction // Begin transaction
SQLite::Transaction transaction(db); SQLite::Transaction transaction(db);
EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")); EXPECT_EQ(0, db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"));
EXPECT_EQ(SQLite::OK, db.getErrorCode()); EXPECT_EQ(SQLite::OK, db.getErrorCode());
// Insert a first value // Insert a first value
EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")")); EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"first\")"));
EXPECT_EQ(1, db.getLastInsertRowid()); EXPECT_EQ(1, db.getLastInsertRowid());
// Commit transaction // Commit transaction
transaction.commit(); transaction.commit();
// Commit again throw an exception // Commit again throw an exception
EXPECT_THROW(transaction.commit(), SQLite::Exception); EXPECT_THROW(transaction.commit(), SQLite::Exception);
} }
// ensure transactions with different types are well-formed // ensure transactions with different types are well-formed
{ {
for (auto behavior : { for (auto behavior : {
SQLite::TransactionBehavior::DEFERRED, SQLite::TransactionBehavior::DEFERRED,
SQLite::TransactionBehavior::IMMEDIATE, SQLite::TransactionBehavior::IMMEDIATE,
SQLite::TransactionBehavior::EXCLUSIVE }) SQLite::TransactionBehavior::EXCLUSIVE })
{ {
SQLite::Transaction transaction(db, behavior); SQLite::Transaction transaction(db, behavior);
transaction.commit(); transaction.commit();
} }
EXPECT_THROW(SQLite::Transaction(db, static_cast<SQLite::TransactionBehavior>(-1)), SQLite::Exception); EXPECT_THROW(SQLite::Transaction(db, static_cast<SQLite::TransactionBehavior>(-1)), SQLite::Exception);
} }
// Auto rollback if no commit() before the end of scope // Auto rollback if no commit() before the end of scope
{ {
// Begin transaction // Begin transaction
SQLite::Transaction transaction(db); SQLite::Transaction transaction(db);
// Insert a second value (that will be rollbacked) // Insert a second value (that will be rollbacked)
EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")")); EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")"));
EXPECT_EQ(2, db.getLastInsertRowid()); EXPECT_EQ(2, db.getLastInsertRowid());
// end of scope: automatic rollback // end of scope: automatic rollback
} }
// Auto rollback of a transaction on error/exception // Auto rollback of a transaction on error/exception
try try
{ {
// Begin transaction // Begin transaction
SQLite::Transaction transaction(db); SQLite::Transaction transaction(db);
// Insert a second value (that will be rollbacked) // Insert a second value (that will be rollbacked)
EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"second\")")); EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"second\")"));
EXPECT_EQ(2, db.getLastInsertRowid()); EXPECT_EQ(2, db.getLastInsertRowid());
// Execute with an error => exception with auto-rollback // Execute with an error => exception with auto-rollback
db.exec("DesiredSyntaxError to raise an exception to rollback the transaction"); db.exec("DesiredSyntaxError to raise an exception to rollback the transaction");
GTEST_FATAL_FAILURE_("we should never get there"); GTEST_FATAL_FAILURE_("we should never get there");
transaction.commit(); // We should never get there transaction.commit(); // We should never get there
} }
catch (std::exception& e) catch (std::exception& e)
{ {
std::cout << "SQLite exception: " << e.what() << std::endl; std::cout << "SQLite exception: " << e.what() << std::endl;
// expected error, see above // expected error, see above
} }
// Double rollback with a manual command before the end of scope // Double rollback with a manual command before the end of scope
{ {
// Begin transaction // Begin transaction
SQLite::Transaction transaction(db); SQLite::Transaction transaction(db);
// Insert a second value (that will be rollbacked) // Insert a second value (that will be rollbacked)
EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")")); EXPECT_EQ(1, db.exec("INSERT INTO test VALUES (NULL, \"third\")"));
EXPECT_EQ(2, db.getLastInsertRowid()); EXPECT_EQ(2, db.getLastInsertRowid());
// Execute a manual rollback (no real use case I can think of, so no rollback() method) // Execute a manual rollback
db.exec("ROLLBACK"); transaction.rollback();
// end of scope: the automatic rollback should not raise an error because it is harmless // 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) // Check the results (expect only one row of result, as all other one have been rollbacked)
SQLite::Statement query(db, "SELECT * FROM test"); SQLite::Statement query(db, "SELECT * FROM test");
int nbRows = 0; int nbRows = 0;
while (query.executeStep()) while (query.executeStep())
{ {
nbRows++; nbRows++;
EXPECT_EQ(1, query.getColumn(0).getInt()); EXPECT_EQ(1, query.getColumn(0).getInt());
EXPECT_STREQ("first", query.getColumn(1).getText()); EXPECT_STREQ("first", query.getColumn(1).getText());
} }
EXPECT_EQ(1, nbRows); EXPECT_EQ(1, nbRows);
} }