From 622c44efbb644c91f38313a8e33e43d6a902724f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dr=2E=20Patrick=20Urbanke=20=28=E5=8A=89=E8=87=AA=E6=88=90?= =?UTF-8?q?=29?= Date: Sat, 24 May 2025 12:21:50 +0200 Subject: [PATCH] Better transaction management (#10) --- docs/transactions.md | 4 +- include/sqlgen.hpp | 2 +- include/sqlgen/Connection.hpp | 65 -------------- include/sqlgen/Iterator.hpp | 1 + include/sqlgen/Transaction.hpp | 113 +++++++++++++++++++++++++ include/sqlgen/begin_transaction.hpp | 21 ++--- include/sqlgen/commit.hpp | 24 +++--- include/sqlgen/create_index.hpp | 20 ++--- include/sqlgen/create_table.hpp | 15 ++-- include/sqlgen/delete_from.hpp | 16 ++-- include/sqlgen/drop.hpp | 16 ++-- include/sqlgen/exec.hpp | 21 +++-- include/sqlgen/insert.hpp | 21 ++--- include/sqlgen/is_connection.hpp | 63 ++++++++++++++ include/sqlgen/postgres/Connection.hpp | 48 +++++------ include/sqlgen/read.hpp | 47 +++++----- include/sqlgen/rollback.hpp | 16 ++-- include/sqlgen/sqlite/Connection.hpp | 48 +++++------ include/sqlgen/update.hpp | 18 ++-- include/sqlgen/write.hpp | 28 +++--- src/sqlgen/postgres/Connection.cpp | 52 +----------- src/sqlgen/sqlite/Connection.cpp | 52 +----------- 22 files changed, 352 insertions(+), 359 deletions(-) delete mode 100644 include/sqlgen/Connection.hpp create mode 100644 include/sqlgen/Transaction.hpp create mode 100644 include/sqlgen/is_connection.hpp diff --git a/docs/transactions.md b/docs/transactions.md index b4ddd03..cf1ace4 100644 --- a/docs/transactions.md +++ b/docs/transactions.md @@ -8,7 +8,7 @@ Transactions in sqlgen are managed through three main functions: - `sqlgen::begin_transaction` - Starts a new transaction - `sqlgen::commit` - Commits the current transaction -- `sqlgen::rollback` - Rolls back the current transaction +- `sqlgen::rollback` - Rolls back the current transaction Here's a basic example of how to use transactions: @@ -27,7 +27,7 @@ auto conn = sqlite::connect("database.db") ## Automatic Rollback -sqlgen provides automatic rollback protection through RAII (Resource Acquisition Is Initialization). If a transaction is not explicitly committed, it will be automatically rolled back when the connection object goes out of scope. This helps ensure database consistency. +sqlgen provides automatic rollback protection through RAII (Resource Acquisition Is Initialization). `begin_transaction` creates a `sqlgen::Transaction` object. If a transaction is not explicitly committed, it will be automatically rolled back when the transaction object goes out of scope. This helps ensure database consistency. ## Error Handling diff --git a/include/sqlgen.hpp b/include/sqlgen.hpp index 7979467..201303e 100644 --- a/include/sqlgen.hpp +++ b/include/sqlgen.hpp @@ -1,7 +1,6 @@ #ifndef SQLGEN_HPP_ #define SQLGEN_HPP_ -#include "sqlgen/Connection.hpp" #include "sqlgen/Flatten.hpp" #include "sqlgen/Iterator.hpp" #include "sqlgen/IteratorBase.hpp" @@ -24,6 +23,7 @@ #include "sqlgen/if_exists.hpp" #include "sqlgen/if_not_exists.hpp" #include "sqlgen/insert.hpp" +#include "sqlgen/is_connection.hpp" #include "sqlgen/limit.hpp" #include "sqlgen/order_by.hpp" #include "sqlgen/patterns.hpp" diff --git a/include/sqlgen/Connection.hpp b/include/sqlgen/Connection.hpp deleted file mode 100644 index 146da30..0000000 --- a/include/sqlgen/Connection.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef SQLGEN_CONNECTION_HPP_ -#define SQLGEN_CONNECTION_HPP_ - -#include -#include -#include - -#include "IteratorBase.hpp" -#include "Ref.hpp" -#include "Result.hpp" -#include "dynamic/SelectFrom.hpp" -#include "dynamic/Statement.hpp" -#include "dynamic/Write.hpp" - -namespace sqlgen { - -/// Abstract base class to be implemented by the different -/// database connections. -struct Connection { - virtual ~Connection() = default; - - /// Begins a transaction. - virtual Result begin_transaction() = 0; - - /// Commits a transaction. - virtual Result commit() = 0; - - /// Executes a statement. Note that in order for the statement to take effect, - /// you must call .commit() afterwards. - virtual Result execute(const std::string& _sql) = 0; - - /// Inserts data into the database using the INSERT statement. - /// More minimal approach than write, but can be used inside transactions. - virtual Result insert( - const dynamic::Insert& _stmt, - const std::vector>>& _data) = 0; - - /// Reads the results of a SelectFrom statement. - virtual Result> read(const dynamic::SelectFrom& _query) = 0; - - /// Rolls a transaction back. - virtual Result rollback() = 0; - - /// Transpiles a statement to a particular SQL dialect. - virtual std::string to_sql(const dynamic::Statement& _stmt) = 0; - - /// Starts the write operation. - virtual Result start_write(const dynamic::Write& _stmt) = 0; - - /// Ends the write operation and thus commits the results. - virtual Result end_write() = 0; - - /// Writes data into a table. Each vector in data MUST have the same length as - /// _stmt.columns. - /// You MUST call .start_write(...) first and call .end_write() after all - /// the data has been written. - /// You CAN write the data in chunks, meaning you can call .write(...) more - /// than once between .start_write(...) and .end_write(). - virtual Result write( - const std::vector>>& _data) = 0; -}; - -} // namespace sqlgen - -#endif diff --git a/include/sqlgen/Iterator.hpp b/include/sqlgen/Iterator.hpp index 0a0bb27..ba2e585 100644 --- a/include/sqlgen/Iterator.hpp +++ b/include/sqlgen/Iterator.hpp @@ -5,6 +5,7 @@ #include #include +#include "IteratorBase.hpp" #include "Ref.hpp" #include "Result.hpp" #include "internal/batch_size.hpp" diff --git a/include/sqlgen/Transaction.hpp b/include/sqlgen/Transaction.hpp new file mode 100644 index 0000000..886eb2b --- /dev/null +++ b/include/sqlgen/Transaction.hpp @@ -0,0 +1,113 @@ +#ifndef SQLGEN_TRANSACTION_HPP_ +#define SQLGEN_TRANSACTION_HPP_ + +#include "Ref.hpp" +#include "is_connection.hpp" + +namespace sqlgen { + +template + requires is_connection<_ConnType> +class Transaction { + public: + using ConnType = _ConnType; + + Transaction(const Ref& _conn) + : conn_(_conn), transaction_ended_(false) {} + + Transaction(const Transaction& _other) = delete; + + Transaction(Transaction&& _other) noexcept + : conn_(std::move(_other.conn_)), + transaction_ended_(_other.transaction_ended_) { + _other.transaction_ended_ = true; + } + + ~Transaction() { + if (!transaction_ended_) { + rollback(); + } + } + + Result begin_transaction() { + if (!transaction_ended_) { + return error("Transaction has already begun, cannot begin another."); + } + return conn_->begin_transaction().transform([&](const auto& _nothing) { + transaction_ended_ = false; + return _nothing; + }); + } + + Result commit() { + if (transaction_ended_) { + return error("Transaction has already ended, cannot commit."); + } + return conn_->commit().transform([&](const auto& _nothing) { + transaction_ended_ = true; + return _nothing; + }); + } + + const Ref& conn() const noexcept { return conn_; } + + Result execute(const std::string& _sql) { + return conn_->execute(_sql); + } + + Result insert( + const dynamic::Insert& _stmt, + const std::vector>>& _data) { + return conn_->insert(_stmt, _data); + } + + Transaction& operator=(const Transaction& _other) = delete; + + Transaction& operator=(Transaction&& _other) noexcept { + if (this == &_other) { + return *this; + } + conn_ = _other.conn; + transaction_ended_ = _other.transaction_ended_; + _other.transaction_ended_ = true; + return *this; + } + + Result> read(const dynamic::SelectFrom& _query) { + return conn_->read(_query); + } + + Result rollback() noexcept { + if (transaction_ended_) { + return error("Transaction has already ended, cannot roll back."); + } + return conn_->rollback().transform([&](const auto& _nothing) { + transaction_ended_ = true; + return _nothing; + }); + } + + std::string to_sql(const dynamic::Statement& _stmt) noexcept { + return conn_->to_sql(_stmt); + } + + Result start_write(const dynamic::Write& _stmt) { + return conn_->to_sql(_stmt); + } + + Result end_write() { return conn_->end_write(); } + + Result write( + const std::vector>>& _data) { + return conn_->write(_data); + } + + private: + Ref conn_; + + bool transaction_ended_; +}; + +} // namespace sqlgen + +#endif diff --git a/include/sqlgen/begin_transaction.hpp b/include/sqlgen/begin_transaction.hpp index 46a37e4..311e079 100644 --- a/include/sqlgen/begin_transaction.hpp +++ b/include/sqlgen/begin_transaction.hpp @@ -3,31 +3,32 @@ #include -#include "Connection.hpp" #include "Ref.hpp" #include "Result.hpp" +#include "Transaction.hpp" +#include "is_connection.hpp" namespace sqlgen { -inline Result> begin_transaction_impl( +template + requires is_connection +Result>> begin_transaction_impl( const Ref& _conn) { return _conn->begin_transaction().transform( - [&](const auto&) { return _conn; }); + [&](const auto&) { return Ref>::make(_conn); }); } -inline Result> begin_transaction_impl( +template + requires is_connection +Result>> begin_transaction_impl( const Result>& _res) { return _res.and_then( [&](const auto& _conn) { return begin_transaction_impl(_conn); }); } struct BeginTransaction { - Result> operator()(const auto& _conn) const noexcept { - try { - return begin_transaction_impl(_conn); - } catch (std::exception& e) { - return error(e.what()); - } + auto operator()(const auto& _conn) const { + return begin_transaction_impl(_conn); } }; diff --git a/include/sqlgen/commit.hpp b/include/sqlgen/commit.hpp index e9db029..562210a 100644 --- a/include/sqlgen/commit.hpp +++ b/include/sqlgen/commit.hpp @@ -3,29 +3,27 @@ #include -#include "Connection.hpp" #include "Ref.hpp" #include "Result.hpp" +#include "Transaction.hpp" namespace sqlgen { -inline Result> commit_impl(const Ref& _conn) { - return _conn->commit().transform([&](const auto&) { return _conn; }); +template + requires is_connection +Result> commit_impl(const Ref>& _t) { + return _t->commit().transform([&](const auto&) { return _t->conn(); }); } -inline Result> commit_impl( - const Result>& _res) { - return _res.and_then([&](const auto& _conn) { return commit_impl(_conn); }); +template + requires is_connection +Result> commit_impl( + const Result>& _res) { + return _res.and_then([&](const auto& _t) { return commit_impl(_t); }); } struct Commit { - Result> operator()(const auto& _conn) const noexcept { - try { - return commit_impl(_conn); - } catch (std::exception& e) { - return error(e.what()); - } - } + auto operator()(const auto& _conn) const { return commit_impl(_conn); } }; inline const auto commit = Commit{}; diff --git a/include/sqlgen/create_index.hpp b/include/sqlgen/create_index.hpp index df11670..f203015 100644 --- a/include/sqlgen/create_index.hpp +++ b/include/sqlgen/create_index.hpp @@ -5,12 +5,14 @@ #include "Ref.hpp" #include "Result.hpp" +#include "is_connection.hpp" #include "transpilation/columns_t.hpp" #include "transpilation/to_create_index.hpp" namespace sqlgen { -template +template + requires is_connection Result> create_index_impl(const Ref& _conn, const std::string& _name, const bool _unique, @@ -24,7 +26,8 @@ Result> create_index_impl(const Ref& _conn, }); } -template +template + requires is_connection Result> create_index_impl(const Result>& _res, const std::string& _name, const bool _unique, @@ -39,15 +42,10 @@ Result> create_index_impl(const Result>& _res, template struct CreateIndex { - Result> operator()(const auto& _conn) const noexcept { - try { - return create_index_impl, - WhereType>(_conn, _name.str(), unique_, - if_not_exists_, where_); - } catch (std::exception& e) { - return error(e.what()); - } + auto operator()(const auto& _conn) const { + return create_index_impl< + ValueType, transpilation::columns_t, WhereType>( + _conn, _name.str(), unique_, if_not_exists_, where_); } bool unique_ = false; diff --git a/include/sqlgen/create_table.hpp b/include/sqlgen/create_table.hpp index 950f9ef..29dbadd 100644 --- a/include/sqlgen/create_table.hpp +++ b/include/sqlgen/create_table.hpp @@ -3,11 +3,13 @@ #include +#include "is_connection.hpp" #include "transpilation/to_create_table.hpp" namespace sqlgen { -template +template + requires is_connection Result> create_table_impl(const Ref& _conn, const bool _if_not_exists) { const auto query = transpilation::to_create_table(_if_not_exists); @@ -16,7 +18,8 @@ Result> create_table_impl(const Ref& _conn, }); } -template +template + requires is_connection Result> create_table_impl(const Result>& _res, const bool _if_not_exists) { return _res.and_then([&](const auto& _conn) { @@ -26,12 +29,8 @@ Result> create_table_impl(const Result>& _res, template struct CreateTable { - Result> operator()(const auto& _conn) const noexcept { - try { - return create_table_impl(_conn, if_not_exists_); - } catch (std::exception& e) { - return error(e.what()); - } + auto operator()(const auto& _conn) const { + return create_table_impl(_conn, if_not_exists_); } bool if_not_exists_; diff --git a/include/sqlgen/delete_from.hpp b/include/sqlgen/delete_from.hpp index 8e30958..08b7107 100644 --- a/include/sqlgen/delete_from.hpp +++ b/include/sqlgen/delete_from.hpp @@ -3,14 +3,15 @@ #include -#include "Connection.hpp" #include "Ref.hpp" #include "Result.hpp" +#include "is_connection.hpp" #include "transpilation/to_delete_from.hpp" namespace sqlgen { -template +template + requires is_connection Result> delete_from_impl(const Ref& _conn, const WhereType& _where) { const auto query = @@ -20,7 +21,8 @@ Result> delete_from_impl(const Ref& _conn, }); } -template +template + requires is_connection Result> delete_from_impl(const Result>& _res, const WhereType& _where) { return _res.and_then([&](const auto& _conn) { @@ -30,12 +32,8 @@ Result> delete_from_impl(const Result>& _res, template struct DeleteFrom { - Result> operator()(const auto& _conn) const noexcept { - try { - return delete_from_impl(_conn, where_); - } catch (std::exception& e) { - return error(e.what()); - } + auto operator()(const auto& _conn) const { + return delete_from_impl(_conn, where_); } WhereType where_; diff --git a/include/sqlgen/drop.hpp b/include/sqlgen/drop.hpp index 653eabd..6b49907 100644 --- a/include/sqlgen/drop.hpp +++ b/include/sqlgen/drop.hpp @@ -3,14 +3,15 @@ #include -#include "Connection.hpp" #include "Ref.hpp" #include "Result.hpp" +#include "is_connection.hpp" #include "transpilation/to_drop.hpp" namespace sqlgen { -template +template + requires is_connection Result> drop_impl(const Ref& _conn, const bool _if_exists) { const auto query = transpilation::to_drop(_if_exists); @@ -19,7 +20,8 @@ Result> drop_impl(const Ref& _conn, }); } -template +template + requires is_connection Result> drop_impl(const Result>& _res, const bool _if_exists) { return _res.and_then([&](const auto& _conn) { @@ -29,12 +31,8 @@ Result> drop_impl(const Result>& _res, template struct Drop { - Result> operator()(const auto& _conn) const noexcept { - try { - return drop_impl(_conn, if_exists_); - } catch (std::exception& e) { - return error(e.what()); - } + auto operator()(const auto& _conn) const { + return drop_impl(_conn, if_exists_); } bool if_exists_ = false; diff --git a/include/sqlgen/exec.hpp b/include/sqlgen/exec.hpp index e193710..977c650 100644 --- a/include/sqlgen/exec.hpp +++ b/include/sqlgen/exec.hpp @@ -5,27 +5,26 @@ #include "Ref.hpp" #include "Result.hpp" +#include "is_connection.hpp" namespace sqlgen { -inline Result> exec(const Ref& _conn, - const std::string& _sql) { +template + requires is_connection +Result> exec(const Ref& _conn, + const std::string& _sql) { return _conn->execute(_sql).transform([&](const auto&) { return _conn; }); } -inline Result> exec(const Result>& _res, - const std::string& _sql) { +template + requires is_connection +Result> exec(const Result>& _res, + const std::string& _sql) { return _res.and_then([&](const auto& _conn) { return exec(_conn, _sql); }); } struct Exec { - Result> operator()(const auto& _conn) const noexcept { - try { - return exec(_conn, sql_); - } catch (std::exception& e) { - return error(e.what()); - } - } + auto operator()(const auto& _conn) const { return exec(_conn, sql_); } std::string sql_; }; diff --git a/include/sqlgen/insert.hpp b/include/sqlgen/insert.hpp index e6ae67d..27c2ddf 100644 --- a/include/sqlgen/insert.hpp +++ b/include/sqlgen/insert.hpp @@ -11,11 +11,13 @@ #include "internal/batch_size.hpp" #include "internal/to_str_vec.hpp" +#include "is_connection.hpp" #include "transpilation/to_insert_or_write.hpp" namespace sqlgen { -template +template + requires is_connection Result> insert(const Ref& _conn, ItBegin _begin, ItEnd _end) { using T = @@ -47,7 +49,8 @@ Result> insert(const Ref& _conn, ItBegin _begin, return _conn; } -template +template + requires is_connection Result> insert(const Result>& _res, ItBegin _begin, ItEnd _end) { return _res.and_then( @@ -55,7 +58,7 @@ Result> insert(const Result>& _res, } template -Result> insert(const auto& _conn, const ContainerType& _data) { +auto insert(const auto& _conn, const ContainerType& _data) { if constexpr (std::ranges::input_range>) { return insert(_conn, _data.begin(), _data.end()); } else { @@ -64,20 +67,14 @@ Result> insert(const auto& _conn, const ContainerType& _data) { } template -Result> insert( - const auto& _conn, const std::reference_wrapper& _data) { +auto insert(const auto& _conn, + const std::reference_wrapper& _data) { return insert(_conn, _data.get()); } template struct Insert { - Result> operator()(const auto& _conn) const noexcept { - try { - return insert(_conn, data_); - } catch (std::exception& e) { - return error(e.what()); - } - } + auto operator()(const auto& _conn) const { return insert(_conn, data_); } ContainerType data_; }; diff --git a/include/sqlgen/is_connection.hpp b/include/sqlgen/is_connection.hpp new file mode 100644 index 0000000..a3cc0b3 --- /dev/null +++ b/include/sqlgen/is_connection.hpp @@ -0,0 +1,63 @@ +#ifndef SQLGEN_ISCONNECTION_HPP_ +#define SQLGEN_ISCONNECTION_HPP_ + +#include +#include +#include +#include + +#include "IteratorBase.hpp" +#include "Ref.hpp" +#include "Result.hpp" +#include "dynamic/SelectFrom.hpp" +#include "dynamic/Statement.hpp" +#include "dynamic/Write.hpp" + +namespace sqlgen { + +/// The concept any connection needs to implements. +template +concept is_connection = + requires(ConnType c, std::string _sql, dynamic::Statement _stmt, + dynamic::SelectFrom _select_from, dynamic::Insert _insert, + const dynamic::Write& _write, + std::vector>> _data) { + /// Begins a transaction. + { c.begin_transaction() } -> std::same_as>; + + /// Commits a transaction. + { c.commit() } -> std::same_as>; + + /// Executes a statement. + { c.execute(_sql) } -> std::same_as>; + + /// Inserts data into the database using the INSERT statement. + /// More minimal approach than write, but can be used inside transactions. + { c.insert(_insert, _data) } -> std::same_as>; + + /// Reads the results of a SelectFrom statement. + { c.read(_select_from) } -> std::same_as>>; + + /// Commits a transaction. + { c.rollback() } -> std::same_as>; + + /// Transpiles a statement to a particular SQL dialect. + { c.to_sql(_stmt) } -> std::same_as; + + /// Starts the write operation. + { c.start_write(_write) } -> std::same_as>; + + /// Ends the write operation and thus commits the results. + { c.end_write() } -> std::same_as>; + + /// Writes data into a table. Each vector in data MUST have the same + /// length as _stmt.columns. You MUST call .start_write(...) first and + /// call .end_write() after all the data has been written. You CAN write + /// the data in chunks, meaning you can call .write(...) more than once + /// between .start_write(...) and .end_write(). + { c.write(_data) } -> std::same_as>; + }; + +} // namespace sqlgen + +#endif diff --git a/include/sqlgen/postgres/Connection.hpp b/include/sqlgen/postgres/Connection.hpp index b9b73ab..72ece45 100644 --- a/include/sqlgen/postgres/Connection.hpp +++ b/include/sqlgen/postgres/Connection.hpp @@ -8,70 +8,61 @@ #include #include -#include "../Connection.hpp" #include "../IteratorBase.hpp" #include "../Ref.hpp" #include "../Result.hpp" +#include "../Transaction.hpp" #include "../dynamic/Column.hpp" #include "../dynamic/Statement.hpp" #include "../dynamic/Write.hpp" +#include "../is_connection.hpp" #include "Credentials.hpp" #include "exec.hpp" #include "to_sql.hpp" namespace sqlgen::postgres { -class Connection : public sqlgen::Connection { +class Connection { using ConnPtr = Ref; public: Connection(const Credentials& _credentials) - : conn_(make_conn(_credentials.to_str())), - credentials_(_credentials), - transaction_started_(false) {} + : conn_(make_conn(_credentials.to_str())), credentials_(_credentials) {} - static rfl::Result> make( + static rfl::Result> make( const Credentials& _credentials) noexcept; - Connection(const Connection& _other) = delete; + ~Connection() = default; - Connection(Connection&& _other) noexcept; + Result begin_transaction() noexcept; - ~Connection(); + Result commit() noexcept; - Result begin_transaction() noexcept final; - - Result commit() noexcept final; - - Result execute(const std::string& _sql) noexcept final { + Result execute(const std::string& _sql) noexcept { return exec(conn_, _sql).transform([](auto&&) { return Nothing{}; }); } Result insert( const dynamic::Insert& _stmt, const std::vector>>& - _data) noexcept final; + _data) noexcept; - Connection& operator=(const Connection& _other) = delete; + Result> read(const dynamic::SelectFrom& _query); - Connection& operator=(Connection&& _other) noexcept; + Result rollback() noexcept; - Result> read(const dynamic::SelectFrom& _query) final; - - Result rollback() noexcept final; - - std::string to_sql(const dynamic::Statement& _stmt) noexcept final { + std::string to_sql(const dynamic::Statement& _stmt) noexcept { return postgres::to_sql_impl(_stmt); } - Result start_write(const dynamic::Write& _stmt) final { + Result start_write(const dynamic::Write& _stmt) { return execute(postgres::to_sql_impl(_stmt)); } - Result end_write() final; + Result end_write(); Result write( - const std::vector>>& _data) final; + const std::vector>>& _data); private: static ConnPtr make_conn(const std::string& _conn_str); @@ -83,10 +74,13 @@ class Connection : public sqlgen::Connection { ConnPtr conn_; Credentials credentials_; - - bool transaction_started_; }; +static_assert(is_connection, + "Must fulfill the is_connection concept."); +static_assert(is_connection>, + "Must fulfill the is_connection concept."); + } // namespace sqlgen::postgres #endif diff --git a/include/sqlgen/read.hpp b/include/sqlgen/read.hpp index 4bb6746..6781496 100644 --- a/include/sqlgen/read.hpp +++ b/include/sqlgen/read.hpp @@ -4,18 +4,19 @@ #include #include -#include "Connection.hpp" #include "Range.hpp" #include "Ref.hpp" #include "Result.hpp" #include "internal/is_range.hpp" +#include "is_connection.hpp" #include "transpilation/to_select_from.hpp" #include "transpilation/value_t.hpp" namespace sqlgen { template + class LimitType, class Connection> + requires is_connection Result read_impl(const Ref& _conn, const WhereType& _where, const LimitType& _limit) { @@ -47,7 +48,8 @@ Result read_impl(const Ref& _conn, } template + class LimitType, class Connection> + requires is_connection Result read_impl(const Result>& _res, const WhereType& _where, const LimitType& _limit) { @@ -60,30 +62,25 @@ Result read_impl(const Result>& _res, template struct Read { - Result operator()(const auto& _conn) const noexcept { - try { - if constexpr (std::ranges::input_range>) { - return read_impl(_conn, where_, - limit_); + Result operator()(const auto& _conn) const { + if constexpr (std::ranges::input_range>) { + return read_impl(_conn, where_, + limit_); - } else { - const auto extract_result = [](auto&& _vec) -> Result { - if (_vec.size() != 1) { - return error( - "Because the provided type was not a container, the query " - "needs to return exactly one result, but it did return " + - std::to_string(_vec.size()) + " results."); - } - return std::move(_vec[0]); - }; + } else { + const auto extract_result = [](auto&& _vec) -> Result { + if (_vec.size() != 1) { + return error( + "Because the provided type was not a container, the query " + "needs to return exactly one result, but it did return " + + std::to_string(_vec.size()) + " results."); + } + return std::move(_vec[0]); + }; - return read_impl, WhereType, OrderByType, LimitType>( - _conn, where_, limit_) - .and_then(extract_result); - } - - } catch (std::exception& e) { - return error(e.what()); + return read_impl, WhereType, OrderByType, LimitType>( + _conn, where_, limit_) + .and_then(extract_result); } } diff --git a/include/sqlgen/rollback.hpp b/include/sqlgen/rollback.hpp index f37f143..a505255 100644 --- a/include/sqlgen/rollback.hpp +++ b/include/sqlgen/rollback.hpp @@ -3,23 +3,27 @@ #include -#include "Connection.hpp" #include "Ref.hpp" #include "Result.hpp" +#include "is_connection.hpp" namespace sqlgen { -inline Result> rollback_impl(const Ref& _conn) { - return _conn->rollback().transform([&](const auto&) { return _conn; }); +template + requires is_connection +Result> rollback_impl(const Ref>& _t) { + return _t->rollback().transform([&](const auto&) { return _t->conn(); }); } -inline Result> rollback_impl( - const Result>& _res) { +template + requires is_connection +Result> rollback_impl( + const Result>>& _res) { return _res.and_then([&](const auto& _conn) { return rollback_impl(_conn); }); } struct Rollback { - Result> operator()(const auto& _conn) const noexcept { + auto operator()(const auto& _conn) const noexcept { try { return rollback_impl(_conn); } catch (std::exception& e) { diff --git a/include/sqlgen/sqlite/Connection.hpp b/include/sqlgen/sqlite/Connection.hpp index 2ba6bb2..42a73ad 100644 --- a/include/sqlgen/sqlite/Connection.hpp +++ b/include/sqlgen/sqlite/Connection.hpp @@ -9,61 +9,53 @@ #include #include -#include "../Connection.hpp" #include "../IteratorBase.hpp" #include "../Ref.hpp" #include "../Result.hpp" +#include "../Transaction.hpp" #include "../dynamic/Write.hpp" +#include "../is_connection.hpp" #include "to_sql.hpp" namespace sqlgen::sqlite { -class Connection : public sqlgen::Connection { +class Connection { using ConnPtr = Ref; using StmtPtr = std::shared_ptr; public: Connection(const std::string& _fname) - : stmt_(nullptr), conn_(make_conn(_fname)), transaction_started_(false) {} + : stmt_(nullptr), conn_(make_conn(_fname)) {} - static rfl::Result> make( - const std::string& _fname) noexcept; + static rfl::Result> make(const std::string& _fname) noexcept; - Connection(const Connection& _other) = delete; + ~Connection() = default; - Connection(Connection&& _other) noexcept; + Result begin_transaction() noexcept; - ~Connection(); + Result commit() noexcept; - Result begin_transaction() noexcept final; - - Result commit() noexcept final; - - Result execute(const std::string& _sql) noexcept final; + Result execute(const std::string& _sql) noexcept; Result insert( const dynamic::Insert& _stmt, const std::vector>>& - _data) noexcept final; + _data) noexcept; - Connection& operator=(const Connection& _other) = delete; + Result> read(const dynamic::SelectFrom& _query); - Connection& operator=(Connection&& _other) noexcept; + Result rollback() noexcept; - Result> read(const dynamic::SelectFrom& _query) final; - - Result rollback() noexcept final; - - std::string to_sql(const dynamic::Statement& _stmt) noexcept final { + std::string to_sql(const dynamic::Statement& _stmt) noexcept { return sqlite::to_sql_impl(_stmt); } - Result start_write(const dynamic::Write& _stmt) final; + Result start_write(const dynamic::Write& _stmt); - Result end_write() final; + Result end_write(); Result write( - const std::vector>>& _data) final; + const std::vector>>& _data); private: /// Generates the underlying connection. @@ -85,11 +77,13 @@ class Connection : public sqlgen::Connection { /// The underlying sqlite3 connection. ConnPtr conn_; - - /// Whether a transaction has been started. - bool transaction_started_; }; +static_assert(is_connection, + "Must fulfill the is_connection concept."); +static_assert(is_connection>, + "Must fulfill the is_connection concept."); + } // namespace sqlgen::sqlite #endif diff --git a/include/sqlgen/update.hpp b/include/sqlgen/update.hpp index a90083d..5764547 100644 --- a/include/sqlgen/update.hpp +++ b/include/sqlgen/update.hpp @@ -4,14 +4,15 @@ #include #include -#include "Connection.hpp" #include "Ref.hpp" #include "Result.hpp" +#include "is_connection.hpp" #include "transpilation/to_update.hpp" namespace sqlgen { -template +template + requires is_connection Result> update_impl(const Ref& _conn, const SetsType& _sets, const WhereType& _where) { @@ -22,7 +23,8 @@ Result> update_impl(const Ref& _conn, }); } -template +template + requires is_connection Result> update_impl(const Result>& _res, const SetsType& _sets, const WhereType& _where) { @@ -33,12 +35,8 @@ Result> update_impl(const Result>& _res, template struct Update { - Result> operator()(const auto& _conn) const noexcept { - try { - return update_impl(_conn, sets_, where_); - } catch (std::exception& e) { - return error(e.what()); - } + auto operator()(const auto& _conn) const { + return update_impl(_conn, sets_, where_); } SetsType sets_; @@ -47,7 +45,7 @@ struct Update { }; template -inline auto update(const SetsType&... _sets) { +auto update(const SetsType&... _sets) { static_assert(sizeof...(_sets) > 0, "You must update at least one column."); using TupleType = rfl::Tuple...>; return Update{.sets_ = TupleType(_sets...)}; diff --git a/include/sqlgen/write.hpp b/include/sqlgen/write.hpp index 5c78dcb..bd142e3 100644 --- a/include/sqlgen/write.hpp +++ b/include/sqlgen/write.hpp @@ -10,18 +10,19 @@ #include #include -#include "Connection.hpp" #include "Ref.hpp" #include "Result.hpp" #include "dynamic/Write.hpp" #include "internal/batch_size.hpp" #include "internal/to_str_vec.hpp" +#include "is_connection.hpp" #include "transpilation/to_create_table.hpp" #include "transpilation/to_insert_or_write.hpp" namespace sqlgen { -template +template + requires is_connection Result> write(const Ref& _conn, ItBegin _begin, ItEnd _end) noexcept { using T = @@ -69,16 +70,16 @@ Result> write(const Ref& _conn, ItBegin _begin, .transform([&](const auto&) { return _conn; }); } -template +template + requires is_connection Result> write(const Result>& _res, ItBegin _begin, ItEnd _end) noexcept { return _res.and_then( [&](const auto& _conn) { return write(_conn, _begin, _end); }); } -template -Result> write(const ConnectionType& _conn, - const ContainerType& _container) noexcept { +template +auto write(const auto& _conn, const ContainerType& _container) noexcept { if constexpr (std::ranges::input_range>) { return write(_conn, _container.begin(), _container.end()); } else { @@ -86,22 +87,15 @@ Result> write(const ConnectionType& _conn, } } -template -Result> write( - const ConnectionType& _conn, - const std::reference_wrapper& _data) { +template +auto write(const auto& _conn, + const std::reference_wrapper& _data) { return write(_conn, _data.get()); } template struct Write { - Result> operator()(const auto& _conn) const noexcept { - try { - return write(_conn, data_); - } catch (std::exception& e) { - return error(e.what()); - } - } + auto operator()(const auto& _conn) const { return write(_conn, data_); } ContainerType data_; }; diff --git a/src/sqlgen/postgres/Connection.cpp b/src/sqlgen/postgres/Connection.cpp index 4428d0c..a983565 100644 --- a/src/sqlgen/postgres/Connection.cpp +++ b/src/sqlgen/postgres/Connection.cpp @@ -11,35 +11,11 @@ namespace sqlgen::postgres { -Connection::Connection(Connection&& _other) noexcept - : conn_(std::move(_other.conn_)), - credentials_(std::move(_other.credentials_)), - transaction_started_(_other.transaction_started_) { - _other.transaction_started_ = false; -} - -Connection::~Connection() { - if (transaction_started_) { - rollback(); - } -} - Result Connection::begin_transaction() noexcept { - if (transaction_started_) { - return error( - "Cannot BEGIN TRANSACTION - another transaction has been started."); - } - transaction_started_ = true; return execute("BEGIN TRANSACTION;"); } -Result Connection::commit() noexcept { - if (!transaction_started_) { - return error("Cannot COMMIT - no transaction has been started."); - } - transaction_started_ = false; - return execute("COMMIT;"); -} +Result Connection::commit() noexcept { return execute("COMMIT;"); } Result Connection::end_write() { if (PQputCopyEnd(conn_.get(), NULL) == -1) { @@ -108,10 +84,10 @@ Result Connection::insert( return execute("DEALLOCATE sqlgen_insert_into_table;"); } -rfl::Result> Connection::make( +rfl::Result> Connection::make( const Credentials& _credentials) noexcept { try { - return Ref(Ref::make(_credentials)); + return Ref::make(_credentials); } catch (std::exception& e) { return error(e.what()); } @@ -131,20 +107,6 @@ typename Connection::ConnPtr Connection::make_conn( return ConnPtr::make(std::shared_ptr(raw_ptr, &PQfinish)).value(); } -Connection& Connection::operator=(Connection&& _other) noexcept { - if (this == &_other) { - return *this; - } - if (transaction_started_) { - rollback(); - } - conn_ = std::move(_other.conn_); - credentials_ = std::move(_other.credentials_); - transaction_started_ = _other.transaction_started_; - _other.transaction_started_ = false; - return *this; -} - Result> Connection::read(const dynamic::SelectFrom& _query) { const auto sql = postgres::to_sql_impl(_query); try { @@ -154,13 +116,7 @@ Result> Connection::read(const dynamic::SelectFrom& _query) { } } -Result Connection::rollback() noexcept { - if (!transaction_started_) { - return error("Cannot ROLLBACK - no transaction has been started."); - } - transaction_started_ = false; - return execute("ROLLBACK;"); -} +Result Connection::rollback() noexcept { return execute("ROLLBACK;"); } std::string Connection::to_buffer( const std::vector>& _line) const noexcept { diff --git a/src/sqlgen/sqlite/Connection.cpp b/src/sqlgen/sqlite/Connection.cpp index cc08d68..cea1c6c 100644 --- a/src/sqlgen/sqlite/Connection.cpp +++ b/src/sqlgen/sqlite/Connection.cpp @@ -11,19 +11,6 @@ namespace sqlgen::sqlite { -Connection::Connection(Connection&& _other) noexcept - : stmt_(std::move(_other.stmt_)), - conn_(std::move(_other.conn_)), - transaction_started_(_other.transaction_started_) { - _other.transaction_started_ = false; -} - -Connection::~Connection() { - if (transaction_started_) { - rollback(); - } -} - Result Connection::actual_insert( const std::vector>>& _data, sqlite3_stmt* _stmt) const noexcept { @@ -66,26 +53,15 @@ Result Connection::actual_insert( } Result Connection::begin_transaction() noexcept { - if (transaction_started_) { - return error( - "Cannot BEGIN TRANSACTION - another transaction has been started."); - } - transaction_started_ = true; return execute("BEGIN TRANSACTION;"); } -Result Connection::commit() noexcept { - if (!transaction_started_) { - return error("Cannot COMMIT - no transaction has been started."); - } - transaction_started_ = false; - return execute("COMMIT;"); -} +Result Connection::commit() noexcept { return execute("COMMIT;"); } -rfl::Result> Connection::make( +rfl::Result> Connection::make( const std::string& _fname) noexcept { try { - return Ref(Ref::make(_fname)); + return Ref::make(_fname); } catch (std::exception& e) { return error(e.what()); } @@ -121,20 +97,6 @@ typename Connection::ConnPtr Connection::make_conn(const std::string& _fname) { return ConnPtr::make(std::shared_ptr(conn, &sqlite3_close)).value(); } -Connection& Connection::operator=(Connection&& _other) noexcept { - if (this == &_other) { - return *this; - } - if (transaction_started_) { - rollback(); - } - stmt_ = std::move(_other.stmt_); - conn_ = std::move(_other.conn_); - transaction_started_ = _other.transaction_started_; - _other.transaction_started_ = false; - return *this; -} - Result> Connection::read(const dynamic::SelectFrom& _query) { const auto sql = to_sql_impl(_query); @@ -176,13 +138,7 @@ Result Connection::prepare_statement( return StmtPtr(p_stmt, &sqlite3_finalize); } -Result Connection::rollback() noexcept { - if (!transaction_started_) { - return error("Cannot ROLLBACK - no transaction has been started."); - } - transaction_started_ = false; - return execute("ROLLBACK;"); -} +Result Connection::rollback() noexcept { return execute("ROLLBACK;"); } Result Connection::start_write(const dynamic::Write& _stmt) { if (stmt_) {