Add drogon::orm::CoroMapper<T> template (#712)

Co-authored-by: marty1885 <marty188586@gmail.com>
This commit is contained in:
An Tao 2021-02-13 18:22:17 +08:00 committed by GitHub
parent cfb71cc619
commit 3b8b63d17d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 354 additions and 14 deletions

View File

@ -13,6 +13,8 @@
*/
#pragma once
#include <drogon/utils/optional.h>
#include <trantor/net/EventLoop.h>
#include <algorithm>
#include <coroutine>
#include <exception>
@ -21,7 +23,6 @@
#include <atomic>
#include <future>
#include <cassert>
#include <drogon/utils/optional.h>
namespace drogon
{
@ -80,7 +81,7 @@ template <typename T>
constexpr bool is_awaitable_v = is_awaitable<T>::value;
template <typename T>
struct final_awiter
struct final_awaiter
{
bool await_ready() noexcept
{
@ -140,7 +141,7 @@ struct Task
auto final_suspend() noexcept
{
return final_awiter<promise_type>{};
return final_awaiter<promise_type>{};
}
void unhandled_exception()
@ -247,7 +248,7 @@ struct Task<void>
}
auto final_suspend() noexcept
{
return final_awiter<promise_type>{};
return final_awaiter<promise_type>{};
}
void unhandled_exception()
{
@ -349,7 +350,7 @@ struct AsyncTask final
/// Helper class that provices the infrastructure for turning callback into
/// corourines
// The user is responsible to fill in `await_suspend()` and construtors.
template <typename T>
template <typename T = void>
struct CallbackAwaiter
{
bool await_ready() noexcept
@ -374,7 +375,7 @@ struct CallbackAwaiter
// entire struct to be constructed for awaiting. std::optional takes care of
// that.
optional<T> result_;
std::exception_ptr exception_ = nullptr;
std::exception_ptr exception_{nullptr};
protected:
void setException(const std::exception_ptr &e)
@ -391,6 +392,30 @@ struct CallbackAwaiter
}
};
template <>
struct CallbackAwaiter<void>
{
bool await_ready() noexcept
{
return false;
}
void await_resume() noexcept(false)
{
if (exception_)
std::rethrow_exception(exception_);
}
private:
std::exception_ptr exception_{nullptr};
protected:
void setException(const std::exception_ptr &e)
{
exception_ = e;
}
};
// An ok implementation of sync_await. This allows one to call
// coroutines and wait for the result from a function.
//
@ -479,5 +504,42 @@ inline auto co_future(Await await) noexcept
}(std::move(prom), std::move(await));
return fut;
}
namespace internal
{
struct TimerAwaiter : CallbackAwaiter<void>
{
TimerAwaiter(trantor::EventLoop *loop,
const std::chrono::duration<long double> &delay)
: loop_(loop), delay_(delay.count())
{
}
TimerAwaiter(trantor::EventLoop *loop, double delay)
: loop_(loop), delay_(delay)
{
}
void await_suspend(std::coroutine_handle<> handle)
{
loop_->runAfter(delay_, [handle]() { handle.resume(); });
}
private:
trantor::EventLoop *loop_;
double delay_;
};
} // namespace internal
inline Task<void> sleepCoro(
trantor::EventLoop *loop,
const std::chrono::duration<long double> &delay) noexcept
{
assert(loop);
co_return co_await internal::TimerAwaiter(loop, delay);
}
inline Task<void> sleepCoro(trantor::EventLoop *loop, double delay) noexcept
{
assert(loop);
co_return co_await internal::TimerAwaiter(loop, delay);
}
} // namespace drogon

View File

@ -0,0 +1,184 @@
/**
*
* @file CoroMapper.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#include <drogon/orm/Mapper.h>
namespace drogon
{
namespace orm
{
namespace internal
{
template <typename ReturnType>
struct MapperAwaiter : public CallbackAwaiter<ReturnType>
{
using MapperFunction =
std::function<void(std::function<void(ReturnType result)> &&,
std::function<void(const DrogonDbException &)> &&)>;
MapperAwaiter(MapperFunction &&function) : function_(std::move(function))
{
}
void await_suspend(std::coroutine_handle<> handle)
{
function_(
[handle, this](ReturnType result) {
this->setValue(std::move(result));
handle.resume();
},
[handle, this](const DrogonDbException &e) {
this->setException(std::make_exception_ptr(e));
handle.resume();
});
}
private:
MapperFunction function_;
};
} // namespace internal
/**
* @brief This template implements coroutine interfaces of ORM. All the methods
* of this template are coroutine versions of the synchronous interfaces of the
* orm::Mapper template.
*
* @tparam T The type of the model.
*/
template <typename T>
class CoroMapper : public Mapper<T>
{
public:
CoroMapper(const DbClientPtr &client) : Mapper<T>(client)
{
}
using TraitsPKType = typename Mapper<T>::TraitsPKType;
inline const Task<T> findByPrimaryKey(const TraitsPKType &key)
{
auto lb =
[this, key](
std::function<void(T)> &&callback,
std::function<void(const DrogonDbException &)> &&errCallback) {
Mapper<T>::findByPrimaryKey(key,
std::move(callback),
std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<T>(std::move(lb));
}
inline const Task<std::vector<T>> findAll()
{
auto lb =
[this](
std::function<void(std::vector<T>)> &&callback,
std::function<void(const DrogonDbException &)> &&errCallback) {
Mapper<T>::findAll(std::move(callback), std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<std::vector<T>>(
std::move(lb));
}
inline const Task<size_t> count(const Criteria &criteria = Criteria())
{
auto lb =
[this, criteria](
std::function<void(const size_t)> &&callback,
std::function<void(const DrogonDbException &)> &&errCallback) {
Mapper<T>::count(criteria,
std::move(callback),
std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<size_t>(std::move(lb));
}
inline const Task<T> findOne(const Criteria &criteria)
{
auto lb =
[this, criteria](
std::function<void(T)> &&callback,
std::function<void(const DrogonDbException &)> &&errCallback) {
Mapper<T>::findOne(criteria,
std::move(callback),
std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<T>(std::move(lb));
}
inline const Task<std::vector<T>> findBy(const Criteria &criteria)
{
auto lb =
[this, criteria](
std::function<void(std::vector<T>)> &&callback,
std::function<void(const DrogonDbException &)> &&errCallback) {
Mapper<T>::findBy(criteria,
std::move(callback),
std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<std::vector<T>>(
std::move(lb));
}
inline const Task<T> insert(const T &obj)
{
auto lb = [this, obj](std::function<void(T)> &&callback,
std::function<void(const DrogonDbException &)>
&&errCallback) {
Mapper<T>::insert(obj, std::move(callback), std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<T>(std::move(lb));
}
inline const Task<size_t> update(const T &obj)
{
auto lb = [this, obj](std::function<void(const size_t)> &&callback,
std::function<void(const DrogonDbException &)>
&&errCallback) {
Mapper<T>::update(obj, std::move(callback), std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<size_t>(std::move(lb));
}
inline const Task<size_t> deleteOne(const T &obj)
{
auto lb =
[this, obj](
std::function<void(const size_t)> &&callback,
std::function<void(const DrogonDbException &)> &&errCallback) {
Mapper<T>::deleteOne(obj,
std::move(callback),
std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<size_t>(std::move(lb));
}
inline const Task<size_t> deleteBy(const Criteria &criteria)
{
auto lb =
[this, criteria](
std::function<void(const size_t)> &&callback,
std::function<void(const DrogonDbException &)> &&errCallback) {
Mapper<T>::deleteBy(criteria,
std::move(callback),
std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<size_t>(std::move(lb));
}
inline const Task<size_t> deleteByPrimaryKey(const TraitsPKType &key)
{
auto lb =
[this, key](
std::function<void(const size_t)> &&callback,
std::function<void(const DrogonDbException &)> &&errCallback) {
Mapper<T>::deleteByPrimaryKey(key,
std::move(callback),
std::move(errCallback));
};
co_return co_await internal::MapperAwaiter<size_t>(std::move(lb));
}
};
} // namespace orm
} // namespace drogon
#endif

View File

@ -46,7 +46,7 @@ namespace internal
#ifdef __cpp_impl_coroutine
struct SqlAwaiter : public CallbackAwaiter<Result>
{
SqlAwaiter(internal::SqlBinder &&binder) : binder_(binder)
SqlAwaiter(internal::SqlBinder &&binder) : binder_(std::move(binder))
{
}

View File

@ -23,6 +23,7 @@
#include <drogon/utils/string_view.h>
#include <drogon/utils/optional.h>
#include <trantor/utils/Logger.h>
#include <trantor/utils/NonCopyable.h>
#include <functional>
#include <iostream>
#include <map>
@ -262,7 +263,7 @@ class CallbackHolder : public CallbackHolderBase
return field.as<ValueType>();
}
};
class SqlBinder
class SqlBinder : public trantor::NonCopyable
{
using self = SqlBinder;
@ -295,6 +296,29 @@ class SqlBinder
type_(type)
{
}
SqlBinder(SqlBinder &&that)
: sqlPtr_(std::move(that.sqlPtr_)),
sqlViewPtr_(that.sqlViewPtr_),
sqlViewLength_(that.sqlViewLength_),
client_(that.client_),
parametersNumber_(that.parametersNumber_),
parameters_(std::move(that.parameters_)),
lengths_(std::move(that.lengths_)),
formats_(std::move(that.formats_)),
objs_(std::move(that.objs_)),
mode_(that.mode_),
callbackHolder_(std::move(that.callbackHolder_)),
exceptionCallback_(std::move(that.exceptionCallback_)),
exceptionPtrCallback_(std::move(that.exceptionPtrCallback_)),
execed_(that.execed_),
destructed_(that.destructed_),
isExceptionPtr_(that.isExceptionPtr_),
type_(that.type_)
{
// set the execed_ to true to avoid the same sql being executed twice.
that.execed_ = true;
}
SqlBinder &operator=(SqlBinder &&that) = delete;
~SqlBinder();
template <typename CallbackType,
typename traits = FunctionTraits<CallbackType>>

View File

@ -15,6 +15,7 @@
#include <drogon/config.h>
#include <drogon/orm/DbClient.h>
#include <drogon/orm/DbTypes.h>
#include <drogon/orm/CoroMapper.h>
#include <trantor/utils/Logger.h>
#include <chrono>
#include <iostream>
@ -33,9 +34,9 @@ using namespace drogon::orm;
#define GREEN "\033[32m" /* Green */
#ifdef __cpp_impl_coroutine
constexpr int postgre_tests = 47;
constexpr int postgre_tests = 50;
constexpr int mysql_tests = 47;
constexpr int sqlite_tests = 49;
constexpr int sqlite_tests = 51;
#else
constexpr int postgre_tests = 44;
constexpr int mysql_tests = 45;
@ -761,20 +762,61 @@ void doPostgreTest(const drogon::orm::DbClientPtr &clientPtr)
std::cerr << e.base().what() << std::endl;
testOutput(false, "postgresql - DbClient coroutine interface(1)");
}
/// 7.3 Transactions
/// 7.3 CoroMapper
try
{
CoroMapper<Users> mapper(clientPtr);
auto user = co_await mapper.findByPrimaryKey(2);
testOutput(true, "postgresql - ORM mapper coroutine interface(0)");
}
catch (const DrogonDbException &e)
{
std::cerr << "error";
std::cerr << e.base().what() << std::endl;
testOutput(false,
"postgresql - ORM mapper coroutine interface(0)");
}
try
{
CoroMapper<Users> mapper(clientPtr);
auto user = co_await mapper.findByPrimaryKey(314);
testOutput(false, "postgresql - ORM mapper coroutine interface(1)");
}
catch (const DrogonDbException &e)
{
std::cerr << e.base().what() << std::endl;
testOutput(true, "postgresql - ORM mapper coroutine interface(1)");
}
try
{
CoroMapper<Users> mapper(clientPtr);
auto users = co_await mapper.findAll();
auto count = co_await mapper.count();
testOutput(users.size() == count,
"postgresql - ORM mapper coroutine interface(2)");
}
catch (const DrogonDbException &e)
{
std::cerr << e.base().what() << std::endl;
testOutput(true, "postgresql - ORM mapper coroutine interface(2)");
}
/// 7.4 Transactions
try
{
auto trans = co_await clientPtr->newTransactionCoro();
auto result =
co_await trans->execSqlCoro("select * from users where 1=$1;",
1);
testOutput(result.size() != 0,
"postgresql - DbClient coroutine interface(2)");
testOutput(
result.size() != 0,
"postgresql - DbClient coroutine transaction interface(0)");
}
catch (const DrogonDbException &e)
{
std::cerr << e.base().what() << std::endl;
testOutput(false, "postgresql - DbClient coroutine interface(2)");
testOutput(
false,
"postgresql - DbClient coroutine transaction interface(0)");
}
};
drogon::sync_wait(coro_test());
@ -2109,6 +2151,34 @@ void doSqliteTest(const drogon::orm::DbClientPtr &clientPtr)
std::cerr << e.base().what() << std::endl;
testOutput(false, "sqlite3 - DbClient coroutine interface(1)");
}
/// 7.3 ORM CoroMapper
try
{
auto mapper = CoroMapper<Users>(clientPtr);
auto user = co_await mapper.findOne(
Criteria(Users::Cols::_id, CompareOperator::EQ, 1));
testOutput(true, "sqlite3 - CoroMapper coroutine interface(0)");
}
catch (const DrogonDbException &e)
{
std::cerr << e.base().what() << std::endl;
testOutput(false, "sqlite3 - CoroMapper coroutine interface(0)");
}
try
{
auto mapper = CoroMapper<Users>(clientPtr);
auto users = co_await mapper.findBy(
Criteria(Users::Cols::_id, CompareOperator::EQ, 1));
testOutput(users.size() == 1,
"sqlite3 - CoroMapper coroutine interface(1)");
}
catch (const DrogonDbException &e)
{
std::cerr << e.base().what() << std::endl;
testOutput(false, "sqlite3 - CoroMapper coroutine interface(1)");
}
co_await drogon::sleepCoro(
trantor::EventLoop::getEventLoopOfCurrentThread(), 1.0s);
};
drogon::sync_wait(coro_test());
#endif