refactored result that an overload for void doesn't need to exist

This commit is contained in:
sascha 2026-07-30 12:33:24 +02:00
parent 78b30f9742
commit c880728093
7 changed files with 127 additions and 95 deletions

View File

@ -19,7 +19,7 @@ public:
void visit(const value_expression& node) override;
void visit(const placeholder_expression& node) override;
const std::string& result() const;
[[nodiscard]] const std::string& result() const;
private:
const sql::dialect &dialect_;

View File

@ -171,7 +171,8 @@ template<class Type>
utils::result<query_result<Type>, utils::error> statement::fetch() {
std::cout << statement_proxy_->sql() << std::endl;
statement_proxy_->statement_->query_.result_type = typeid(Type);
return statement_proxy_->fetch(*bindings_).and_then([this](std::unique_ptr<query_result_impl> &&value) {
return statement_proxy_->fetch(*bindings_)
.and_then([this](std::unique_ptr<query_result_impl> &&value) -> utils::result<query_result<Type>, utils::error> {
auto resolver = statement_proxy_->statement_->query_.resolver->object_resolver<Type>();
const auto prototype = value->prototype();
return utils::ok(query_result<Type>(std::forward<decltype(value)>(value), resolver, [prototype] {

View File

@ -2,7 +2,6 @@
#define QUERY_RESULT_HPP
#include <variant>
#include <optional>
#include <functional>
#include <type_traits>
@ -21,6 +20,7 @@ template < typename ValueType >
class ok {
public:
using value_type = ValueType;
constexpr ok() = default;
explicit constexpr ok(const ValueType &value) : value_(value) {}
explicit constexpr ok(ValueType &&value) : value_(std::move(value)) {}
@ -55,15 +55,43 @@ private:
ErrorType error_;
};
namespace detail {
template <typename ValueType, typename Func, bool IsVoidValue = std::is_void_v<ValueType>>
struct map_result_value_type;
template <typename ValueType, typename Func>
struct map_result_value_type<ValueType, Func, false> {
using type = std::invoke_result_t<Func, ValueType&&>;
};
template <typename ValueType, typename Func>
struct map_result_value_type<ValueType, Func, true> {
using type = std::invoke_result_t<Func>;
};
template <typename ValueType, typename Func, bool IsVoidValue = std::is_void_v<ValueType>>
struct and_then_result_type;
template <typename ValueType, typename Func>
struct and_then_result_type<ValueType, Func, false> {
using type = std::invoke_result_t<Func, ValueType&&>;
};
template <typename ValueType, typename Func>
struct and_then_result_type<ValueType, Func, true> {
using type = std::invoke_result_t<Func>;
};
}
template < typename ValueType, typename ErrorType >
class result {
public:
using value_type = ValueType;
using error_type = ErrorType;
result() : result_(ValueType{}) {}
result(ok<value_type> value) : result_(std::move(value.release())) {} // NOLINT(*-explicit-constructor)
result(failure<error_type> error) : result_(std::move(error.release())) {} // NOLINT(*-explicit-constructor)
result() : result_(ok<value_type>{}) {}
result(ok<value_type> value) : result_(std::move(value)) {} // NOLINT(*-explicit-constructor)
result(failure<error_type> error) : result_(std::move(error)) {} // NOLINT(*-explicit-constructor)
result(const result &x) = default;
result& operator=(const result &x) = default;
result(result &&x) = default;
@ -71,31 +99,76 @@ public:
operator bool() const { return is_ok(); } // NOLINT(*-explicit-constructor)
[[nodiscard]] bool is_ok() const { return std::holds_alternative<value_type>(result_); }
[[nodiscard]] bool is_error() const { return std::holds_alternative<error_type>(result_); }
[[nodiscard]] bool is_ok() const {
return std::holds_alternative<ok<value_type>>(result_);
}
[[nodiscard]] bool is_error() const {
return std::holds_alternative<failure<error_type>>(result_);
}
ValueType&& release() { return std::move(std::get<value_type>(result_)); }
ErrorType&& release_error() { return std::move(std::get<error_type>(result_)); }
template <typename T = ValueType>
std::enable_if_t<!std::is_void_v<T>, T&&> release() {
return std::move(std::get<ok<value_type>>(result_).release());
}
ErrorType&& release_error() {
return std::move(std::get<failure<error_type>>(result_).release());
}
const ValueType& value() const { return std::get<value_type>(result_); }
ValueType& value() { return std::get<value_type>(result_); }
const ErrorType& err() const { return std::get<error_type>(result_); }
ErrorType& err() { return std::get<error_type>(result_); }
template <typename T = ValueType>
std::enable_if_t<!std::is_void_v<T>, const T&> value() const {
return std::get<ok<value_type>>(result_).value();
}
template <typename T = ValueType>
std::enable_if_t<!std::is_void_v<T>, T&> value() {
return std::get<ok<value_type>>(result_).value();
}
const ErrorType& err() const {
return std::get<failure<error_type>>(result_).value();
}
ErrorType& err() {
return std::get<failure<error_type>>(result_).value();
}
constexpr const ValueType* operator->() const { return &value(); }
constexpr ValueType* operator->() { return &std::get<value_type>(result_); }
template <typename T = ValueType>
constexpr std::enable_if_t<!std::is_void_v<T>, const T*> operator->() const {
return &value();
}
template <typename T = ValueType>
constexpr std::enable_if_t<!std::is_void_v<T>, T*> operator->() {
return &value();
}
constexpr const ValueType& operator*() const& noexcept { return value(); }
constexpr ValueType& operator*() & noexcept { return value(); }
template <typename T = ValueType>
constexpr std::enable_if_t<!std::is_void_v<T>, const T&> operator*() const& noexcept {
return value();
}
template <typename T = ValueType>
constexpr std::enable_if_t<!std::is_void_v<T>, T&> operator*() & noexcept {
return value();
}
template<typename Func,
typename SecondValueType = std::invoke_result_t<Func, ValueType >>
typename SecondValueType = typename detail::map_result_value_type<ValueType, Func>::type>
result<SecondValueType, ErrorType> map(Func &&f) {
if (is_ok()) {
return result<SecondValueType, ErrorType>(ok(std::invoke(std::forward<Func>(f), release())));
if (is_error()) {
return failure<ErrorType>(release_error());
}
return result<SecondValueType, ErrorType>(failure(release_error()));
if constexpr (std::is_void_v<ValueType>) {
if constexpr (std::is_void_v<SecondValueType>) {
std::invoke(std::forward<Func>(f));
return ok<void>{};
} else {
return ok<SecondValueType>(std::invoke(std::forward<Func>(f)));
}
} else {
if constexpr (std::is_void_v<SecondValueType>) {
std::invoke(std::forward<Func>(f), release());
return ok<void>{};
} else {
return ok<SecondValueType>(std::invoke(std::forward<Func>(f), release()));
}
}
}
template<typename Func,
@ -105,23 +178,32 @@ public:
return failure<SecondErrorType>{std::invoke(std::forward<Func>(f), release_error())};
}
return ok<ValueType>(release());
if constexpr (std::is_void_v<ValueType>) {
return ok<void>{};
} else {
return ok<ValueType>(release());
}
}
template<typename Func,
typename SecondValueType = typename std::invoke_result_t<Func, ValueType>::value_type>
result<SecondValueType, ErrorType> and_then(Func &&f) {
using ReturnResult = std::invoke_result_t<Func, ValueType>;
template <typename Func,
typename ReturnResult = typename detail::and_then_result_type<ValueType, Func>::type>
ReturnResult and_then(Func &&f) {
static_assert(is_result<ReturnResult>::value, "and_then() callback must return matador::utils::result");
if (is_ok()) {
return std::invoke(std::forward<Func>(f), release());
if constexpr (std::is_void_v<ValueType>) {
return std::invoke(std::forward<Func>(f));
} else {
return std::invoke(std::forward<Func>(f), release());
}
}
return ReturnResult(failure(release_error()));
return ReturnResult(failure<ErrorType>(release_error()));
}
template<typename Func,
typename FailureType = std::invoke_result_t<Func, ErrorType&&>,
typename SecondErrorType = typename FailureType::value_type>
template <typename Func,
typename FailureType = std::invoke_result_t<Func, ErrorType&&>,
typename SecondErrorType = typename FailureType::value_type>
result<ValueType, SecondErrorType> or_else(Func &&f) {
if (is_error()) {
return result<ValueType, SecondErrorType>(
@ -129,69 +211,16 @@ public:
);
}
return result<ValueType, SecondErrorType>(ok<ValueType>(release()));
if constexpr (std::is_void_v<ValueType>) {
return ok<void>{};
} else {
return ok<ValueType>(release());
}
}
private:
std::variant<value_type, error_type> result_;
std::variant<ok<value_type>, failure<error_type>> result_;
};
template < typename ErrorType >
class result<void, ErrorType>
{
public:
using value_type = void;
using error_type = ErrorType;
result() = default;
result(ok<void> /*value*/) {}
result(failure<error_type> error) : result_(std::move(error.release())) {} // NOLINT(*-explicit-constructor)
result(const result &x) = default;
result& operator=(const result &x) = default;
result(result &&x) = default;
result& operator=(result &&x) = default;
operator bool() const { return is_ok(); } // NOLINT(*-explicit-constructor)
[[nodiscard]] bool is_ok() const { return !result_.has_value(); }
[[nodiscard]] bool is_error() const { return result_.has_value(); }
ErrorType&& release_error() { return std::move(*result_); }
const ErrorType& err() const { return result_.value(); }
ErrorType err() { return result_.value(); }
template<typename Func, typename SecondValueType = std::invoke_result_t<Func>>
result<SecondValueType, ErrorType> map(Func &&f) {
if (is_ok()) {
return result<SecondValueType, ErrorType>(ok(f()));
}
return result<SecondValueType, ErrorType>(failure(release_error()));
}
template<typename Func>
result and_then(Func &&f) {
if (is_ok()) {
return f();
}
return result(failure(release_error()));
}
template<typename Func, typename SecondErrorType = typename std::invoke_result_t<Func, ErrorType&& >::value_type>
result<void, SecondErrorType> or_else(Func &&f) {
if (is_error()) {
return f(release_error());
}
return result<void, SecondErrorType>(ok<void>());
}
private:
std::optional<error_type> result_;
};
}
#endif //QUERY_RESULT_HPP

View File

@ -56,7 +56,7 @@ public:
if (!res.is_ok()) {
return std::nullopt;
}
return *res;
return res.value();
}
template<class Type>

View File

@ -30,7 +30,7 @@ utils::result<sql::query_result<sql::record>, utils::error> fetchable_query::fet
auto ctx = compiler.build(*context_, exec.dialect(), std::nullopt);
ctx.resolver = exec.resolver();
return exec.fetch(ctx)
.and_then([](auto &&res) {
.and_then([](auto &&res) -> utils::result<sql::query_result<sql::record>, utils::error> {
const auto prototype = res->prototype();
return utils::ok(sql::query_result<sql::record>(std::forward<decltype(res)>(res), prototype));
});

View File

@ -37,7 +37,8 @@ void QueryFixture::check_table_not_exists(const std::string &table_name) const {
}
void QueryFixture::drop_table_if_exists(const std::string &table_name) const {
const auto result = db.exists(table_name).and_then([&table_name, this](const bool exists) {
const auto result = db.exists(table_name)
.and_then([&table_name, this](const bool exists) -> utils::result<bool, utils::error> {
if (exists) {
auto res = query::drop()
.table(table_name)

View File

@ -35,7 +35,8 @@ void SequenceFixture::check_sequence_not_exists(const std::string& sequence_name
}
void SequenceFixture::drop_sequence_if_exists(const std::string& sequence_name) const {
const auto result = db.sequence_exists(sequence_name).and_then([&sequence_name, this](const bool exists) {
const auto result = db.sequence_exists(sequence_name).
and_then([&sequence_name, this](const bool exists) -> utils::result<bool, utils::error> {
if (exists) {
auto res = query::drop()
.sequence(sequence_name)