Better transaction management (#10)

This commit is contained in:
Dr. Patrick Urbanke (劉自成)
2025-05-24 12:21:50 +02:00
committed by GitHub
parent 87b911bba2
commit 622c44efbb
22 changed files with 352 additions and 359 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -1,65 +0,0 @@
#ifndef SQLGEN_CONNECTION_HPP_
#define SQLGEN_CONNECTION_HPP_
#include <optional>
#include <string>
#include <vector>
#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<Nothing> begin_transaction() = 0;
/// Commits a transaction.
virtual Result<Nothing> commit() = 0;
/// Executes a statement. Note that in order for the statement to take effect,
/// you must call .commit() afterwards.
virtual Result<Nothing> 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<Nothing> insert(
const dynamic::Insert& _stmt,
const std::vector<std::vector<std::optional<std::string>>>& _data) = 0;
/// Reads the results of a SelectFrom statement.
virtual Result<Ref<IteratorBase>> read(const dynamic::SelectFrom& _query) = 0;
/// Rolls a transaction back.
virtual Result<Nothing> 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<Nothing> start_write(const dynamic::Write& _stmt) = 0;
/// Ends the write operation and thus commits the results.
virtual Result<Nothing> 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<Nothing> write(
const std::vector<std::vector<std::optional<std::string>>>& _data) = 0;
};
} // namespace sqlgen
#endif

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <ranges>
#include "IteratorBase.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "internal/batch_size.hpp"

View File

@@ -0,0 +1,113 @@
#ifndef SQLGEN_TRANSACTION_HPP_
#define SQLGEN_TRANSACTION_HPP_
#include "Ref.hpp"
#include "is_connection.hpp"
namespace sqlgen {
template <class _ConnType>
requires is_connection<_ConnType>
class Transaction {
public:
using ConnType = _ConnType;
Transaction(const Ref<ConnType>& _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<Nothing> 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<Nothing> 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<ConnType>& conn() const noexcept { return conn_; }
Result<Nothing> execute(const std::string& _sql) {
return conn_->execute(_sql);
}
Result<Nothing> insert(
const dynamic::Insert& _stmt,
const std::vector<std::vector<std::optional<std::string>>>& _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<Ref<IteratorBase>> read(const dynamic::SelectFrom& _query) {
return conn_->read(_query);
}
Result<Nothing> 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<Nothing> start_write(const dynamic::Write& _stmt) {
return conn_->to_sql(_stmt);
}
Result<Nothing> end_write() { return conn_->end_write(); }
Result<Nothing> write(
const std::vector<std::vector<std::optional<std::string>>>& _data) {
return conn_->write(_data);
}
private:
Ref<ConnType> conn_;
bool transaction_ended_;
};
} // namespace sqlgen
#endif

View File

@@ -3,31 +3,32 @@
#include <type_traits>
#include "Connection.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "Transaction.hpp"
#include "is_connection.hpp"
namespace sqlgen {
inline Result<Ref<Connection>> begin_transaction_impl(
template <class Connection>
requires is_connection<Connection>
Result<Ref<Transaction<Connection>>> begin_transaction_impl(
const Ref<Connection>& _conn) {
return _conn->begin_transaction().transform(
[&](const auto&) { return _conn; });
[&](const auto&) { return Ref<Transaction<Connection>>::make(_conn); });
}
inline Result<Ref<Connection>> begin_transaction_impl(
template <class Connection>
requires is_connection<Connection>
Result<Ref<Transaction<Connection>>> begin_transaction_impl(
const Result<Ref<Connection>>& _res) {
return _res.and_then(
[&](const auto& _conn) { return begin_transaction_impl(_conn); });
}
struct BeginTransaction {
Result<Ref<Connection>> 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);
}
};

View File

@@ -3,29 +3,27 @@
#include <type_traits>
#include "Connection.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "Transaction.hpp"
namespace sqlgen {
inline Result<Ref<Connection>> commit_impl(const Ref<Connection>& _conn) {
return _conn->commit().transform([&](const auto&) { return _conn; });
template <class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> commit_impl(const Ref<Transaction<Connection>>& _t) {
return _t->commit().transform([&](const auto&) { return _t->conn(); });
}
inline Result<Ref<Connection>> commit_impl(
const Result<Ref<Connection>>& _res) {
return _res.and_then([&](const auto& _conn) { return commit_impl(_conn); });
template <class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> commit_impl(
const Result<Transaction<Connection>>& _res) {
return _res.and_then([&](const auto& _t) { return commit_impl(_t); });
}
struct Commit {
Result<Ref<Connection>> 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{};

View File

@@ -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 <class ValueType, class ColumnsType, class WhereType>
template <class ValueType, class ColumnsType, class WhereType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> create_index_impl(const Ref<Connection>& _conn,
const std::string& _name,
const bool _unique,
@@ -24,7 +26,8 @@ Result<Ref<Connection>> create_index_impl(const Ref<Connection>& _conn,
});
}
template <class ValueType, class ColumnsType, class WhereType>
template <class ValueType, class ColumnsType, class WhereType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> create_index_impl(const Result<Ref<Connection>>& _res,
const std::string& _name,
const bool _unique,
@@ -39,15 +42,10 @@ Result<Ref<Connection>> create_index_impl(const Result<Ref<Connection>>& _res,
template <rfl::internal::StringLiteral _name, class ValueType, class WhereType,
class... ColTypes>
struct CreateIndex {
Result<Ref<Connection>> operator()(const auto& _conn) const noexcept {
try {
return create_index_impl<ValueType,
transpilation::columns_t<ValueType, ColTypes...>,
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<ValueType, ColTypes...>, WhereType>(
_conn, _name.str(), unique_, if_not_exists_, where_);
}
bool unique_ = false;

View File

@@ -3,11 +3,13 @@
#include <rfl.hpp>
#include "is_connection.hpp"
#include "transpilation/to_create_table.hpp"
namespace sqlgen {
template <class ValueType>
template <class ValueType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> create_table_impl(const Ref<Connection>& _conn,
const bool _if_not_exists) {
const auto query = transpilation::to_create_table<ValueType>(_if_not_exists);
@@ -16,7 +18,8 @@ Result<Ref<Connection>> create_table_impl(const Ref<Connection>& _conn,
});
}
template <class ValueType>
template <class ValueType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> create_table_impl(const Result<Ref<Connection>>& _res,
const bool _if_not_exists) {
return _res.and_then([&](const auto& _conn) {
@@ -26,12 +29,8 @@ Result<Ref<Connection>> create_table_impl(const Result<Ref<Connection>>& _res,
template <class ValueType>
struct CreateTable {
Result<Ref<Connection>> operator()(const auto& _conn) const noexcept {
try {
return create_table_impl<ValueType>(_conn, if_not_exists_);
} catch (std::exception& e) {
return error(e.what());
}
auto operator()(const auto& _conn) const {
return create_table_impl<ValueType>(_conn, if_not_exists_);
}
bool if_not_exists_;

View File

@@ -3,14 +3,15 @@
#include <type_traits>
#include "Connection.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "is_connection.hpp"
#include "transpilation/to_delete_from.hpp"
namespace sqlgen {
template <class ValueType, class WhereType>
template <class ValueType, class WhereType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> delete_from_impl(const Ref<Connection>& _conn,
const WhereType& _where) {
const auto query =
@@ -20,7 +21,8 @@ Result<Ref<Connection>> delete_from_impl(const Ref<Connection>& _conn,
});
}
template <class ValueType, class WhereType>
template <class ValueType, class WhereType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> delete_from_impl(const Result<Ref<Connection>>& _res,
const WhereType& _where) {
return _res.and_then([&](const auto& _conn) {
@@ -30,12 +32,8 @@ Result<Ref<Connection>> delete_from_impl(const Result<Ref<Connection>>& _res,
template <class ValueType, class WhereType = Nothing>
struct DeleteFrom {
Result<Ref<Connection>> operator()(const auto& _conn) const noexcept {
try {
return delete_from_impl<ValueType, WhereType>(_conn, where_);
} catch (std::exception& e) {
return error(e.what());
}
auto operator()(const auto& _conn) const {
return delete_from_impl<ValueType, WhereType>(_conn, where_);
}
WhereType where_;

View File

@@ -3,14 +3,15 @@
#include <type_traits>
#include "Connection.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "is_connection.hpp"
#include "transpilation/to_drop.hpp"
namespace sqlgen {
template <class ValueType>
template <class ValueType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> drop_impl(const Ref<Connection>& _conn,
const bool _if_exists) {
const auto query = transpilation::to_drop<ValueType>(_if_exists);
@@ -19,7 +20,8 @@ Result<Ref<Connection>> drop_impl(const Ref<Connection>& _conn,
});
}
template <class ValueType>
template <class ValueType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> drop_impl(const Result<Ref<Connection>>& _res,
const bool _if_exists) {
return _res.and_then([&](const auto& _conn) {
@@ -29,12 +31,8 @@ Result<Ref<Connection>> drop_impl(const Result<Ref<Connection>>& _res,
template <class ValueType>
struct Drop {
Result<Ref<Connection>> operator()(const auto& _conn) const noexcept {
try {
return drop_impl<ValueType>(_conn, if_exists_);
} catch (std::exception& e) {
return error(e.what());
}
auto operator()(const auto& _conn) const {
return drop_impl<ValueType>(_conn, if_exists_);
}
bool if_exists_ = false;

View File

@@ -5,27 +5,26 @@
#include "Ref.hpp"
#include "Result.hpp"
#include "is_connection.hpp"
namespace sqlgen {
inline Result<Ref<Connection>> exec(const Ref<Connection>& _conn,
const std::string& _sql) {
template <class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> exec(const Ref<Connection>& _conn,
const std::string& _sql) {
return _conn->execute(_sql).transform([&](const auto&) { return _conn; });
}
inline Result<Ref<Connection>> exec(const Result<Ref<Connection>>& _res,
const std::string& _sql) {
template <class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> exec(const Result<Ref<Connection>>& _res,
const std::string& _sql) {
return _res.and_then([&](const auto& _conn) { return exec(_conn, _sql); });
}
struct Exec {
Result<Ref<Connection>> 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_;
};

View File

@@ -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 <class ItBegin, class ItEnd>
template <class ItBegin, class ItEnd, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> insert(const Ref<Connection>& _conn, ItBegin _begin,
ItEnd _end) {
using T =
@@ -47,7 +49,8 @@ Result<Ref<Connection>> insert(const Ref<Connection>& _conn, ItBegin _begin,
return _conn;
}
template <class ItBegin, class ItEnd>
template <class ItBegin, class ItEnd, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> insert(const Result<Ref<Connection>>& _res,
ItBegin _begin, ItEnd _end) {
return _res.and_then(
@@ -55,7 +58,7 @@ Result<Ref<Connection>> insert(const Result<Ref<Connection>>& _res,
}
template <class ContainerType>
Result<Ref<Connection>> insert(const auto& _conn, const ContainerType& _data) {
auto insert(const auto& _conn, const ContainerType& _data) {
if constexpr (std::ranges::input_range<std::remove_cvref_t<ContainerType>>) {
return insert(_conn, _data.begin(), _data.end());
} else {
@@ -64,20 +67,14 @@ Result<Ref<Connection>> insert(const auto& _conn, const ContainerType& _data) {
}
template <class ContainerType>
Result<Ref<Connection>> insert(
const auto& _conn, const std::reference_wrapper<ContainerType>& _data) {
auto insert(const auto& _conn,
const std::reference_wrapper<ContainerType>& _data) {
return insert(_conn, _data.get());
}
template <class ContainerType>
struct Insert {
Result<Ref<Connection>> 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_;
};

View File

@@ -0,0 +1,63 @@
#ifndef SQLGEN_ISCONNECTION_HPP_
#define SQLGEN_ISCONNECTION_HPP_
#include <concepts>
#include <optional>
#include <string>
#include <vector>
#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 <class ConnType>
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<std::vector<std::optional<std::string>>> _data) {
/// Begins a transaction.
{ c.begin_transaction() } -> std::same_as<Result<Nothing>>;
/// Commits a transaction.
{ c.commit() } -> std::same_as<Result<Nothing>>;
/// Executes a statement.
{ c.execute(_sql) } -> std::same_as<Result<Nothing>>;
/// 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<Result<Nothing>>;
/// Reads the results of a SelectFrom statement.
{ c.read(_select_from) } -> std::same_as<Result<Ref<IteratorBase>>>;
/// Commits a transaction.
{ c.rollback() } -> std::same_as<Result<Nothing>>;
/// Transpiles a statement to a particular SQL dialect.
{ c.to_sql(_stmt) } -> std::same_as<std::string>;
/// Starts the write operation.
{ c.start_write(_write) } -> std::same_as<Result<Nothing>>;
/// Ends the write operation and thus commits the results.
{ c.end_write() } -> std::same_as<Result<Nothing>>;
/// 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<Result<Nothing>>;
};
} // namespace sqlgen
#endif

View File

@@ -8,70 +8,61 @@
#include <stdexcept>
#include <string>
#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<PGconn>;
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<Ref<sqlgen::Connection>> make(
static rfl::Result<Ref<Connection>> make(
const Credentials& _credentials) noexcept;
Connection(const Connection& _other) = delete;
~Connection() = default;
Connection(Connection&& _other) noexcept;
Result<Nothing> begin_transaction() noexcept;
~Connection();
Result<Nothing> commit() noexcept;
Result<Nothing> begin_transaction() noexcept final;
Result<Nothing> commit() noexcept final;
Result<Nothing> execute(const std::string& _sql) noexcept final {
Result<Nothing> execute(const std::string& _sql) noexcept {
return exec(conn_, _sql).transform([](auto&&) { return Nothing{}; });
}
Result<Nothing> insert(
const dynamic::Insert& _stmt,
const std::vector<std::vector<std::optional<std::string>>>&
_data) noexcept final;
_data) noexcept;
Connection& operator=(const Connection& _other) = delete;
Result<Ref<IteratorBase>> read(const dynamic::SelectFrom& _query);
Connection& operator=(Connection&& _other) noexcept;
Result<Nothing> rollback() noexcept;
Result<Ref<IteratorBase>> read(const dynamic::SelectFrom& _query) final;
Result<Nothing> 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<Nothing> start_write(const dynamic::Write& _stmt) final {
Result<Nothing> start_write(const dynamic::Write& _stmt) {
return execute(postgres::to_sql_impl(_stmt));
}
Result<Nothing> end_write() final;
Result<Nothing> end_write();
Result<Nothing> write(
const std::vector<std::vector<std::optional<std::string>>>& _data) final;
const std::vector<std::vector<std::optional<std::string>>>& _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<Connection>,
"Must fulfill the is_connection concept.");
static_assert(is_connection<Transaction<Connection>>,
"Must fulfill the is_connection concept.");
} // namespace sqlgen::postgres
#endif

View File

@@ -4,18 +4,19 @@
#include <ranges>
#include <type_traits>
#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 ContainerType, class WhereType, class OrderByType,
class LimitType>
class LimitType, class Connection>
requires is_connection<Connection>
Result<ContainerType> read_impl(const Ref<Connection>& _conn,
const WhereType& _where,
const LimitType& _limit) {
@@ -47,7 +48,8 @@ Result<ContainerType> read_impl(const Ref<Connection>& _conn,
}
template <class ContainerType, class WhereType, class OrderByType,
class LimitType>
class LimitType, class Connection>
requires is_connection<Connection>
Result<ContainerType> read_impl(const Result<Ref<Connection>>& _res,
const WhereType& _where,
const LimitType& _limit) {
@@ -60,30 +62,25 @@ Result<ContainerType> read_impl(const Result<Ref<Connection>>& _res,
template <class Type, class WhereType = Nothing, class OrderByType = Nothing,
class LimitType = Nothing>
struct Read {
Result<Type> operator()(const auto& _conn) const noexcept {
try {
if constexpr (std::ranges::input_range<std::remove_cvref_t<Type>>) {
return read_impl<Type, WhereType, OrderByType, LimitType>(_conn, where_,
limit_);
Result<Type> operator()(const auto& _conn) const {
if constexpr (std::ranges::input_range<std::remove_cvref_t<Type>>) {
return read_impl<Type, WhereType, OrderByType, LimitType>(_conn, where_,
limit_);
} else {
const auto extract_result = [](auto&& _vec) -> Result<Type> {
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<Type> {
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<std::vector<Type>, WhereType, OrderByType, LimitType>(
_conn, where_, limit_)
.and_then(extract_result);
}
} catch (std::exception& e) {
return error(e.what());
return read_impl<std::vector<Type>, WhereType, OrderByType, LimitType>(
_conn, where_, limit_)
.and_then(extract_result);
}
}

View File

@@ -3,23 +3,27 @@
#include <type_traits>
#include "Connection.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "is_connection.hpp"
namespace sqlgen {
inline Result<Ref<Connection>> rollback_impl(const Ref<Connection>& _conn) {
return _conn->rollback().transform([&](const auto&) { return _conn; });
template <class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> rollback_impl(const Ref<Transaction<Connection>>& _t) {
return _t->rollback().transform([&](const auto&) { return _t->conn(); });
}
inline Result<Ref<Connection>> rollback_impl(
const Result<Ref<Connection>>& _res) {
template <class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> rollback_impl(
const Result<Ref<Transaction<Connection>>>& _res) {
return _res.and_then([&](const auto& _conn) { return rollback_impl(_conn); });
}
struct Rollback {
Result<Ref<Connection>> operator()(const auto& _conn) const noexcept {
auto operator()(const auto& _conn) const noexcept {
try {
return rollback_impl(_conn);
} catch (std::exception& e) {

View File

@@ -9,61 +9,53 @@
#include <stdexcept>
#include <string>
#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<sqlite3>;
using StmtPtr = std::shared_ptr<sqlite3_stmt>;
public:
Connection(const std::string& _fname)
: stmt_(nullptr), conn_(make_conn(_fname)), transaction_started_(false) {}
: stmt_(nullptr), conn_(make_conn(_fname)) {}
static rfl::Result<Ref<sqlgen::Connection>> make(
const std::string& _fname) noexcept;
static rfl::Result<Ref<Connection>> make(const std::string& _fname) noexcept;
Connection(const Connection& _other) = delete;
~Connection() = default;
Connection(Connection&& _other) noexcept;
Result<Nothing> begin_transaction() noexcept;
~Connection();
Result<Nothing> commit() noexcept;
Result<Nothing> begin_transaction() noexcept final;
Result<Nothing> commit() noexcept final;
Result<Nothing> execute(const std::string& _sql) noexcept final;
Result<Nothing> execute(const std::string& _sql) noexcept;
Result<Nothing> insert(
const dynamic::Insert& _stmt,
const std::vector<std::vector<std::optional<std::string>>>&
_data) noexcept final;
_data) noexcept;
Connection& operator=(const Connection& _other) = delete;
Result<Ref<IteratorBase>> read(const dynamic::SelectFrom& _query);
Connection& operator=(Connection&& _other) noexcept;
Result<Nothing> rollback() noexcept;
Result<Ref<IteratorBase>> read(const dynamic::SelectFrom& _query) final;
Result<Nothing> 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<Nothing> start_write(const dynamic::Write& _stmt) final;
Result<Nothing> start_write(const dynamic::Write& _stmt);
Result<Nothing> end_write() final;
Result<Nothing> end_write();
Result<Nothing> write(
const std::vector<std::vector<std::optional<std::string>>>& _data) final;
const std::vector<std::vector<std::optional<std::string>>>& _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<Connection>,
"Must fulfill the is_connection concept.");
static_assert(is_connection<Transaction<Connection>>,
"Must fulfill the is_connection concept.");
} // namespace sqlgen::sqlite
#endif

View File

@@ -4,14 +4,15 @@
#include <rfl.hpp>
#include <type_traits>
#include "Connection.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "is_connection.hpp"
#include "transpilation/to_update.hpp"
namespace sqlgen {
template <class ValueType, class SetsType, class WhereType>
template <class ValueType, class SetsType, class WhereType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> update_impl(const Ref<Connection>& _conn,
const SetsType& _sets,
const WhereType& _where) {
@@ -22,7 +23,8 @@ Result<Ref<Connection>> update_impl(const Ref<Connection>& _conn,
});
}
template <class ValueType, class SetsType, class WhereType>
template <class ValueType, class SetsType, class WhereType, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> update_impl(const Result<Ref<Connection>>& _res,
const SetsType& _sets,
const WhereType& _where) {
@@ -33,12 +35,8 @@ Result<Ref<Connection>> update_impl(const Result<Ref<Connection>>& _res,
template <class ValueType, class SetsType, class WhereType = Nothing>
struct Update {
Result<Ref<Connection>> operator()(const auto& _conn) const noexcept {
try {
return update_impl<ValueType, SetsType, WhereType>(_conn, sets_, where_);
} catch (std::exception& e) {
return error(e.what());
}
auto operator()(const auto& _conn) const {
return update_impl<ValueType, SetsType, WhereType>(_conn, sets_, where_);
}
SetsType sets_;
@@ -47,7 +45,7 @@ struct Update {
};
template <class ValueType, class... SetsType>
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<std::remove_cvref_t<SetsType>...>;
return Update<ValueType, TupleType>{.sets_ = TupleType(_sets...)};

View File

@@ -10,18 +10,19 @@
#include <type_traits>
#include <vector>
#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 <class ItBegin, class ItEnd>
template <class ItBegin, class ItEnd, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> write(const Ref<Connection>& _conn, ItBegin _begin,
ItEnd _end) noexcept {
using T =
@@ -69,16 +70,16 @@ Result<Ref<Connection>> write(const Ref<Connection>& _conn, ItBegin _begin,
.transform([&](const auto&) { return _conn; });
}
template <class ItBegin, class ItEnd>
template <class ItBegin, class ItEnd, class Connection>
requires is_connection<Connection>
Result<Ref<Connection>> write(const Result<Ref<Connection>>& _res,
ItBegin _begin, ItEnd _end) noexcept {
return _res.and_then(
[&](const auto& _conn) { return write(_conn, _begin, _end); });
}
template <class ConnectionType, class ContainerType>
Result<Ref<Connection>> write(const ConnectionType& _conn,
const ContainerType& _container) noexcept {
template <class ContainerType>
auto write(const auto& _conn, const ContainerType& _container) noexcept {
if constexpr (std::ranges::input_range<std::remove_cvref_t<ContainerType>>) {
return write(_conn, _container.begin(), _container.end());
} else {
@@ -86,22 +87,15 @@ Result<Ref<Connection>> write(const ConnectionType& _conn,
}
}
template <class ConnectionType, class ContainerType>
Result<Ref<Connection>> write(
const ConnectionType& _conn,
const std::reference_wrapper<ContainerType>& _data) {
template <class ContainerType>
auto write(const auto& _conn,
const std::reference_wrapper<ContainerType>& _data) {
return write(_conn, _data.get());
}
template <class ContainerType>
struct Write {
Result<Ref<Connection>> 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_;
};

View File

@@ -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<Nothing> 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<Nothing> Connection::commit() noexcept {
if (!transaction_started_) {
return error("Cannot COMMIT - no transaction has been started.");
}
transaction_started_ = false;
return execute("COMMIT;");
}
Result<Nothing> Connection::commit() noexcept { return execute("COMMIT;"); }
Result<Nothing> Connection::end_write() {
if (PQputCopyEnd(conn_.get(), NULL) == -1) {
@@ -108,10 +84,10 @@ Result<Nothing> Connection::insert(
return execute("DEALLOCATE sqlgen_insert_into_table;");
}
rfl::Result<Ref<sqlgen::Connection>> Connection::make(
rfl::Result<Ref<Connection>> Connection::make(
const Credentials& _credentials) noexcept {
try {
return Ref<sqlgen::Connection>(Ref<Connection>::make(_credentials));
return Ref<Connection>::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<PGconn>(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<Ref<IteratorBase>> Connection::read(const dynamic::SelectFrom& _query) {
const auto sql = postgres::to_sql_impl(_query);
try {
@@ -154,13 +116,7 @@ Result<Ref<IteratorBase>> Connection::read(const dynamic::SelectFrom& _query) {
}
}
Result<Nothing> Connection::rollback() noexcept {
if (!transaction_started_) {
return error("Cannot ROLLBACK - no transaction has been started.");
}
transaction_started_ = false;
return execute("ROLLBACK;");
}
Result<Nothing> Connection::rollback() noexcept { return execute("ROLLBACK;"); }
std::string Connection::to_buffer(
const std::vector<std::optional<std::string>>& _line) const noexcept {

View File

@@ -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<Nothing> Connection::actual_insert(
const std::vector<std::vector<std::optional<std::string>>>& _data,
sqlite3_stmt* _stmt) const noexcept {
@@ -66,26 +53,15 @@ Result<Nothing> Connection::actual_insert(
}
Result<Nothing> 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<Nothing> Connection::commit() noexcept {
if (!transaction_started_) {
return error("Cannot COMMIT - no transaction has been started.");
}
transaction_started_ = false;
return execute("COMMIT;");
}
Result<Nothing> Connection::commit() noexcept { return execute("COMMIT;"); }
rfl::Result<Ref<sqlgen::Connection>> Connection::make(
rfl::Result<Ref<Connection>> Connection::make(
const std::string& _fname) noexcept {
try {
return Ref<sqlgen::Connection>(Ref<Connection>::make(_fname));
return Ref<Connection>::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<sqlite3>(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<Ref<IteratorBase>> Connection::read(const dynamic::SelectFrom& _query) {
const auto sql = to_sql_impl(_query);
@@ -176,13 +138,7 @@ Result<Connection::StmtPtr> Connection::prepare_statement(
return StmtPtr(p_stmt, &sqlite3_finalize);
}
Result<Nothing> Connection::rollback() noexcept {
if (!transaction_started_) {
return error("Cannot ROLLBACK - no transaction has been started.");
}
transaction_started_ = false;
return execute("ROLLBACK;");
}
Result<Nothing> Connection::rollback() noexcept { return execute("ROLLBACK;"); }
Result<Nothing> Connection::start_write(const dynamic::Write& _stmt) {
if (stmt_) {