diff --git a/lib/inc/drogon/utils/coroutine.h b/lib/inc/drogon/utils/coroutine.h index 26c96cb6..dc3cf776 100644 --- a/lib/inc/drogon/utils/coroutine.h +++ b/lib/inc/drogon/utils/coroutine.h @@ -13,6 +13,8 @@ */ #pragma once +#include +#include #include #include #include @@ -21,7 +23,6 @@ #include #include #include -#include namespace drogon { @@ -80,7 +81,7 @@ template constexpr bool is_awaitable_v = is_awaitable::value; template -struct final_awiter +struct final_awaiter { bool await_ready() noexcept { @@ -140,7 +141,7 @@ struct Task auto final_suspend() noexcept { - return final_awiter{}; + return final_awaiter{}; } void unhandled_exception() @@ -247,7 +248,7 @@ struct Task } auto final_suspend() noexcept { - return final_awiter{}; + return final_awaiter{}; } 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 +template 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 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 +{ + 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 +{ + TimerAwaiter(trantor::EventLoop *loop, + const std::chrono::duration &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 sleepCoro( + trantor::EventLoop *loop, + const std::chrono::duration &delay) noexcept +{ + assert(loop); + co_return co_await internal::TimerAwaiter(loop, delay); +} + +inline Task sleepCoro(trantor::EventLoop *loop, double delay) noexcept +{ + assert(loop); + co_return co_await internal::TimerAwaiter(loop, delay); +} } // namespace drogon diff --git a/orm_lib/inc/drogon/orm/CoroMapper.h b/orm_lib/inc/drogon/orm/CoroMapper.h new file mode 100644 index 00000000..c6a5d488 --- /dev/null +++ b/orm_lib/inc/drogon/orm/CoroMapper.h @@ -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 +#include +namespace drogon +{ +namespace orm +{ +namespace internal +{ +template +struct MapperAwaiter : public CallbackAwaiter +{ + using MapperFunction = + std::function &&, + std::function &&)>; + 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 +class CoroMapper : public Mapper +{ + public: + CoroMapper(const DbClientPtr &client) : Mapper(client) + { + } + using TraitsPKType = typename Mapper::TraitsPKType; + inline const Task findByPrimaryKey(const TraitsPKType &key) + { + auto lb = + [this, key]( + std::function &&callback, + std::function &&errCallback) { + Mapper::findByPrimaryKey(key, + std::move(callback), + std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter(std::move(lb)); + } + inline const Task> findAll() + { + auto lb = + [this]( + std::function)> &&callback, + std::function &&errCallback) { + Mapper::findAll(std::move(callback), std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter>( + std::move(lb)); + } + inline const Task count(const Criteria &criteria = Criteria()) + { + auto lb = + [this, criteria]( + std::function &&callback, + std::function &&errCallback) { + Mapper::count(criteria, + std::move(callback), + std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter(std::move(lb)); + } + inline const Task findOne(const Criteria &criteria) + { + auto lb = + [this, criteria]( + std::function &&callback, + std::function &&errCallback) { + Mapper::findOne(criteria, + std::move(callback), + std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter(std::move(lb)); + } + inline const Task> findBy(const Criteria &criteria) + { + auto lb = + [this, criteria]( + std::function)> &&callback, + std::function &&errCallback) { + Mapper::findBy(criteria, + std::move(callback), + std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter>( + std::move(lb)); + } + inline const Task insert(const T &obj) + { + auto lb = [this, obj](std::function &&callback, + std::function + &&errCallback) { + Mapper::insert(obj, std::move(callback), std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter(std::move(lb)); + } + inline const Task update(const T &obj) + { + auto lb = [this, obj](std::function &&callback, + std::function + &&errCallback) { + Mapper::update(obj, std::move(callback), std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter(std::move(lb)); + } + inline const Task deleteOne(const T &obj) + { + auto lb = + [this, obj]( + std::function &&callback, + std::function &&errCallback) { + Mapper::deleteOne(obj, + std::move(callback), + std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter(std::move(lb)); + } + inline const Task deleteBy(const Criteria &criteria) + { + auto lb = + [this, criteria]( + std::function &&callback, + std::function &&errCallback) { + Mapper::deleteBy(criteria, + std::move(callback), + std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter(std::move(lb)); + } + inline const Task deleteByPrimaryKey(const TraitsPKType &key) + { + auto lb = + [this, key]( + std::function &&callback, + std::function &&errCallback) { + Mapper::deleteByPrimaryKey(key, + std::move(callback), + std::move(errCallback)); + }; + co_return co_await internal::MapperAwaiter(std::move(lb)); + } +}; +} // namespace orm +} // namespace drogon +#endif \ No newline at end of file diff --git a/orm_lib/inc/drogon/orm/DbClient.h b/orm_lib/inc/drogon/orm/DbClient.h index 6646c956..7ea3789d 100644 --- a/orm_lib/inc/drogon/orm/DbClient.h +++ b/orm_lib/inc/drogon/orm/DbClient.h @@ -46,7 +46,7 @@ namespace internal #ifdef __cpp_impl_coroutine struct SqlAwaiter : public CallbackAwaiter { - SqlAwaiter(internal::SqlBinder &&binder) : binder_(binder) + SqlAwaiter(internal::SqlBinder &&binder) : binder_(std::move(binder)) { } diff --git a/orm_lib/inc/drogon/orm/SqlBinder.h b/orm_lib/inc/drogon/orm/SqlBinder.h index a94f7430..5c286032 100644 --- a/orm_lib/inc/drogon/orm/SqlBinder.h +++ b/orm_lib/inc/drogon/orm/SqlBinder.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -262,7 +263,7 @@ class CallbackHolder : public CallbackHolderBase return field.as(); } }; -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 > diff --git a/orm_lib/tests/db_test.cc b/orm_lib/tests/db_test.cc index 8ce12abb..6fe7bce0 100644 --- a/orm_lib/tests/db_test.cc +++ b/orm_lib/tests/db_test.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -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 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 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 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(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(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