From 9b49afa306cbb5294e01790532904c86f313f5a0 Mon Sep 17 00:00:00 2001 From: MeanSquaredError <35379301+MeanSquaredError@users.noreply.github.com> Date: Fri, 5 Jan 2024 09:59:42 +0200 Subject: [PATCH] Add is_transaction_active() to all connectors (#550) * Document the connector API method is_transaction_active() * Move mysql::connection_base::is_transaction_active() to the other transaction-handling methods. * Add more tests for mysql::connection::is_transaction_active() * Add postgresql::connection_base::is_transaction_active() * Add tests for postgresql::connection_base::is_transaction_active() * Change the type of the SQLite3 transaction status from transaction_status_type to a boolean flag. * Add sqlite3::connection_base::is_transaction_active() * Add tests for sqlite3::connection_base::is_transaction_active() * When closing a transaction do it in the following order: report (if any), execute SQL command, set transaction active flag to false. --- connector_api/connection.h | 4 +++- include/sqlpp11/mysql/connection.h | 15 +++++++------ include/sqlpp11/postgresql/connection.h | 12 ++++++---- include/sqlpp11/sqlite3/connection.h | 30 +++++++++++-------------- tests/mysql/usage/MoveConstructor.cpp | 2 ++ tests/postgresql/usage/Transaction.cpp | 5 ++++- tests/sqlite3/usage/Transaction.cpp | 4 ++++ 7 files changed, 42 insertions(+), 30 deletions(-) diff --git a/connector_api/connection.h b/connector_api/connection.h index 18fc8d03..a1ac6cd9 100644 --- a/connector_api/connection.h +++ b/connector_api/connection.h @@ -180,7 +180,6 @@ namespace sqlpp return t._prepare(*this); } - //! set the transaction isolation level for the current connection /// time of effect is connector-specific, for most is will only affect new transactions void set_default_isolation_level(sqlpp::isolation_level); @@ -204,6 +203,9 @@ namespace sqlpp //! report a rollback failure (will be called by transactions in case of a rollback failure in the destructor) void report_rollback_failure(const std::string message) noexcept; + //! check if transaction is active + bool is_transaction_active(); + protected: // Low-level connection handle _handle_ptr_t _handle; diff --git a/include/sqlpp11/mysql/connection.h b/include/sqlpp11/mysql/connection.h index 4abcbd8b..1c711c0e 100644 --- a/include/sqlpp11/mysql/connection.h +++ b/include/sqlpp11/mysql/connection.h @@ -291,11 +291,6 @@ namespace sqlpp return _handle->config; } - bool is_transaction_active() - { - return _transaction_active; - } - template char_result_t select(const Select& s) { @@ -462,8 +457,8 @@ namespace sqlpp { throw sqlpp::exception{"MySQL: Cannot commit a finished or failed transaction"}; } - _transaction_active = false; execute_statement(_handle, "COMMIT"); + _transaction_active = false; } //! rollback transaction (or throw if the transaction has been finished already) @@ -477,8 +472,8 @@ namespace sqlpp { std::cerr << "MySQL warning: Rolling back unfinished transaction" << std::endl; } - _transaction_active = false; execute_statement(_handle, "ROLLBACK"); + _transaction_active = false; } //! report a rollback failure (will be called by transactions in case of a rollback failure in the destructor) @@ -487,6 +482,12 @@ namespace sqlpp std::cerr << "MySQL message:" << message << std::endl; } + //! check if transaction is active + bool is_transaction_active() + { + return _transaction_active; + } + MYSQL* native_handle() { return _handle->native_handle(); diff --git a/include/sqlpp11/postgresql/connection.h b/include/sqlpp11/postgresql/connection.h index 4302e160..60faf6a4 100644 --- a/include/sqlpp11/postgresql/connection.h +++ b/include/sqlpp11/postgresql/connection.h @@ -542,9 +542,8 @@ namespace sqlpp { throw sqlpp::exception{"PostgreSQL error: transaction failed or finished."}; } - - _transaction_active = false; execute("COMMIT"); + _transaction_active = false; } //! rollback transaction @@ -554,12 +553,11 @@ namespace sqlpp { throw sqlpp::exception{"PostgreSQL error: transaction failed or finished."}; } - execute("ROLLBACK"); if (report) { std::cerr << "PostgreSQL warning: rolling back unfinished transaction" << std::endl; } - + execute("ROLLBACK"); _transaction_active = false; } @@ -569,6 +567,12 @@ namespace sqlpp std::cerr << "PostgreSQL error: " << message << std::endl; } + //! check if transaction is active + bool is_transaction_active() + { + return _transaction_active; + } + //! get the last inserted id for a certain table uint64_t last_insert_id(const std::string& table, const std::string& fieldname) { diff --git a/include/sqlpp11/sqlite3/connection.h b/include/sqlpp11/sqlite3/connection.h index 8aa23f8e..aa9cabd8 100644 --- a/include/sqlpp11/sqlite3/connection.h +++ b/include/sqlpp11/sqlite3/connection.h @@ -146,14 +146,7 @@ namespace sqlpp class SQLPP11_SQLITE3_EXPORT connection_base : public sqlpp::connection { private: - enum class transaction_status_type - { - none, - maybe, - active - }; - - transaction_status_type _transaction_status{transaction_status_type::none}; + bool _transaction_active{false}; // direct execution bind_result_t select_impl(const std::string& statement) @@ -470,35 +463,33 @@ namespace sqlpp //! start transaction void start_transaction() { - if (_transaction_status == transaction_status_type::active) + if (_transaction_active) { throw sqlpp::exception{"Sqlite3 error: Cannot have more than one open transaction per connection"}; } - _transaction_status = transaction_status_type::maybe; auto prepared = prepare_statement(_handle, "BEGIN"); execute_statement(_handle, prepared); - _transaction_status = transaction_status_type::active; + _transaction_active = true; } //! commit transaction (or throw if the transaction has been finished already) void commit_transaction() { - if (_transaction_status == transaction_status_type::none) + if (!_transaction_active) { throw sqlpp::exception{"Sqlite3 error: Cannot commit a finished or failed transaction"}; } - _transaction_status = transaction_status_type::maybe; auto prepared = prepare_statement(_handle, "COMMIT"); execute_statement(_handle, prepared); - _transaction_status = transaction_status_type::none; + _transaction_active = false; } //! rollback transaction with or without reporting the rollback (or throw if the transaction has been finished // already) void rollback_transaction(bool report) { - if (_transaction_status == transaction_status_type::none) + if (!_transaction_active) { throw sqlpp::exception{"Sqlite3 error: Cannot rollback a finished or failed transaction"}; } @@ -506,10 +497,9 @@ namespace sqlpp { std::cerr << "Sqlite3 warning: Rolling back unfinished transaction" << std::endl; } - _transaction_status = transaction_status_type::maybe; auto prepared = prepare_statement(_handle, "ROLLBACK"); execute_statement(_handle, prepared); - _transaction_status = transaction_status_type::none; + _transaction_active = false; } //! report a rollback failure (will be called by transactions in case of a rollback failure in the destructor) @@ -518,6 +508,12 @@ namespace sqlpp std::cerr << "Sqlite3 message:" << message << std::endl; } + //! check if transaction is active + bool is_transaction_active() + { + return _transaction_active; + } + //! get the last inserted id uint64_t last_insert_id() noexcept { diff --git a/tests/mysql/usage/MoveConstructor.cpp b/tests/mysql/usage/MoveConstructor.cpp index 08a69765..cc15505a 100644 --- a/tests/mysql/usage/MoveConstructor.cpp +++ b/tests/mysql/usage/MoveConstructor.cpp @@ -55,6 +55,7 @@ int MoveConstructor(int, char*[]) gamma bool DEFAULT NULL ))"); + assert(connections.at(0).is_transaction_active() == false); connections.at(0).start_transaction(); auto db = std::move(connections.at(0)); assert(db.is_transaction_active()); @@ -74,6 +75,7 @@ int MoveConstructor(int, char*[]) std::cerr << "row.alpha: " << row.alpha << ", row.beta: " << row.at("beta") << std::endl; }; db.commit_transaction(); + assert(db.is_transaction_active() == false); } catch (const std::exception& e) { diff --git a/tests/postgresql/usage/Transaction.cpp b/tests/postgresql/usage/Transaction.cpp index 8f856a89..22cf989e 100644 --- a/tests/postgresql/usage/Transaction.cpp +++ b/tests/postgresql/usage/Transaction.cpp @@ -94,6 +94,7 @@ int Transaction(int, char*[]) { { + require_equal(__LINE__, db.is_transaction_active(), false); auto current_level = db(custom_query(sqlpp::verbatim("show transaction_isolation;")) .with_result_type_of(select(sqlpp::value("").as(level)))) .front() @@ -102,14 +103,16 @@ int Transaction(int, char*[]) std::cerr << "isolation level outside transaction: " << current_level << "\n"; auto tx = start_transaction(db, sqlpp::isolation_level::serializable); - + require_equal(__LINE__, db.is_transaction_active(), true); current_level = db(custom_query(sqlpp::verbatim("show transaction_isolation;")) .with_result_type_of(select(sqlpp::value("").as(level)))) .front() .level; require_equal(__LINE__, current_level, "serializable"); std::cerr << "isolation level in transaction(serializable) : " << current_level << "\n"; + tx.commit(); + require_equal(__LINE__, db.is_transaction_active(), false); } require_equal(__LINE__, db.get_default_isolation_level(), sqlpp::isolation_level::read_committed); diff --git a/tests/sqlite3/usage/Transaction.cpp b/tests/sqlite3/usage/Transaction.cpp index 027f5816..bc6b42f5 100644 --- a/tests/sqlite3/usage/Transaction.cpp +++ b/tests/sqlite3/usage/Transaction.cpp @@ -53,6 +53,8 @@ int Transaction(int, char*[]) std::cerr << "--------------------------------------" << std::endl; + assert(db.is_transaction_active() == false); + auto current_level = db.get_default_isolation_level(); std::cout << "Expecting default isolation level = 1, is " << static_cast(current_level) << std::endl; assert(current_level == sqlpp::isolation_level::serializable); @@ -66,6 +68,7 @@ int Transaction(int, char*[]) std::cerr << "Expecting read_uncommitted = 0, is: " << pragmaValue << std::endl; db.set_default_isolation_level(sqlpp::isolation_level::read_uncommitted); auto tx = start_transaction(db); + assert(db.is_transaction_active()); pragmaValue = db(custom_query(sqlpp::verbatim("PRAGMA read_uncommitted")) .with_result_type_of(select(sqlpp::value(1).as(pragma)))) .front() @@ -78,6 +81,7 @@ int Transaction(int, char*[]) assert(current_level == sqlpp::isolation_level::read_uncommitted); tx.commit(); + assert(db.is_transaction_active() == false); std::cerr << "--------------------------------------" << std::endl; return 0;