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
+1 -1
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"
-65
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
+1
View File
@@ -5,6 +5,7 @@
#include <memory>
#include <ranges>
#include "IteratorBase.hpp"
#include "Ref.hpp"
#include "Result.hpp"
#include "internal/batch_size.hpp"
+113
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
+11 -10
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);
}
};
+11 -13
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{};
+9 -11
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;
+7 -8
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_;
+7 -9
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_;
+7 -9
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;
+10 -11
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_;
};
+9 -12
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_;
};
+63
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
+21 -27
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
+22 -25
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);
}
}
+10 -6
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) {
+21 -27
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
+8 -10
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...)};
+11 -17
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_;
};