From 75b106599dc54875dc8172bed6f1eb48fd02838a Mon Sep 17 00:00:00 2001 From: Ken Matsui <26405363+ken-matsui@users.noreply.github.com> Date: Sun, 21 May 2023 18:52:19 -0700 Subject: [PATCH] Implement QueryBuilder (#1331) --- drogon_ctl/templates/model_h.csp | 5 + orm_lib/inc/drogon/orm/BaseBuilder.h | 290 ++++++++++++++ orm_lib/inc/drogon/orm/FilterBuilder.h | 162 ++++++++ orm_lib/inc/drogon/orm/QueryBuilder.h | 78 ++++ orm_lib/inc/drogon/orm/Row.h | 4 +- orm_lib/inc/drogon/orm/TransformBuilder.h | 127 +++++++ orm_lib/tests/db_test.cc | 441 ++++++++++++++++++++++ orm_lib/tests/mysql/Users.h | 5 + orm_lib/tests/postgresql/Users.h | 5 + orm_lib/tests/sqlite3/Users.h | 5 + 10 files changed, 1121 insertions(+), 1 deletion(-) create mode 100644 orm_lib/inc/drogon/orm/BaseBuilder.h create mode 100644 orm_lib/inc/drogon/orm/FilterBuilder.h create mode 100644 orm_lib/inc/drogon/orm/QueryBuilder.h create mode 100644 orm_lib/inc/drogon/orm/TransformBuilder.h diff --git a/drogon_ctl/templates/model_h.csp b/drogon_ctl/templates/model_h.csp index 23fb9fb6..d02f9eae 100644 --- a/drogon_ctl/templates/model_h.csp +++ b/drogon_ctl/templates/model_h.csp @@ -14,6 +14,7 @@ using namespace drogon_ctl; #include #include #include +#include #ifdef __cpp_impl_coroutine #include #endif @@ -303,6 +304,10 @@ auto cols=@@.get>("columns"); %> private: friend drogon::orm::Mapper<[[className]]>; + friend drogon::orm::BaseBuilder<[[className]], true, true>; + friend drogon::orm::BaseBuilder<[[className]], true, false>; + friend drogon::orm::BaseBuilder<[[className]], false, true>; + friend drogon::orm::BaseBuilder<[[className]], false, false>; #ifdef __cpp_impl_coroutine friend drogon::orm::CoroMapper<[[className]]>; #endif diff --git a/orm_lib/inc/drogon/orm/BaseBuilder.h b/orm_lib/inc/drogon/orm/BaseBuilder.h new file mode 100644 index 00000000..66052a2b --- /dev/null +++ b/orm_lib/inc/drogon/orm/BaseBuilder.h @@ -0,0 +1,290 @@ +/** + * + * @file BaseBuilder.h + * @author Ken Matsui + * + * Copyright 2022, Ken Matsui. All rights reserved. + * https://github.com/drogonframework/drogon + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Drogon + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define unimplemented() assert(false && "unimplemented") + +namespace drogon +{ +namespace orm +{ +inline std::string to_string(CompareOperator op) +{ + switch (op) + { + case CompareOperator::EQ: + return "="; + case CompareOperator::NE: + return "!="; + case CompareOperator::GT: + return ">"; + case CompareOperator::GE: + return ">="; + case CompareOperator::LT: + return "<"; + case CompareOperator::LE: + return "<="; + case CompareOperator::Like: + return "like"; + case CompareOperator::NotLike: + case CompareOperator::In: + case CompareOperator::NotIn: + case CompareOperator::IsNull: + case CompareOperator::IsNotNull: + default: + unimplemented(); + return ""; + } +} + +struct Filter +{ + std::string column; + CompareOperator op; + std::string value; +}; + +// Forward declaration to be a friend +template +class TransformBuilder; + +template +class BaseBuilder +{ + using ResultType = + std::conditional_t>, + std::conditional_t>; + + // Make the constructor of `TransformBuilder` through + // `TransformBuilder::single()` be able to read these protected members. + friend class TransformBuilder; + + protected: + std::string from_; + std::string columns_; + std::vector filters_; + optional limit_; + optional offset_; + // The order is important; use vector instead of unordered_map and + // map. + std::vector> orders_; + + inline void assert_column(const std::string& colName) const + { + for (const typename T::MetaData& m : T::metaData_) + { + if (m.colName_ == colName) + { + return; + } + } + throw UsageError("The column `" + colName + + "` is not in the specified table."); + } + + private: + /** + * @brief Generate SQL query in string. + * + * @return std::string The string generated SQL query. + */ + inline std::string gen_sql(ClientType type) const noexcept + { + int pCount = 0; + const auto placeholder = [type, &pCount]() { + ++pCount; + return type == ClientType::PostgreSQL ? "$" + std::to_string(pCount) + : "?"; + }; + + std::string sql = "select " + columns_ + " from " + from_; + if (!filters_.empty()) + { + sql += " where " + filters_[0].column + " " + + to_string(filters_[0].op) + " " + placeholder() + ""; + for (int i = 1; i < filters_.size(); ++i) + { + sql += " and " + filters_[i].column + " " + + to_string(filters_[i].op) + " " + placeholder() + ""; + } + } + if (!orders_.empty()) + { + sql += " order by " + orders_[0].first + " " + + std::string(orders_[0].second ? "asc" : "desc"); + for (int i = 1; i < orders_.size(); ++i) + { + sql += ", " + orders_[i].first + " " + + std::string(orders_[i].second ? "asc" : "desc"); + } + } + if (limit_.has_value()) + { + sql += " limit " + std::to_string(limit_.value()); + } + if (offset_.has_value()) + { + sql += " offset " + std::to_string(offset_.value()); + } + return sql; + } + + inline std::vector gen_args() const noexcept + { + std::vector args; + if (!filters_.empty()) + { + for (const Filter& f : filters_) + { + args.emplace_back(f.value); + } + } + return args; + } + + public: +#ifdef __cpp_if_constexpr + static ResultType convert_result(const Result& r) + { + if constexpr (SelectAll) + { + if constexpr (Single) + { + return T(r[0]); + } + else + { + std::vector ret; + for (const Row& row : r) + { + ret.emplace_back(T(row)); + } + return ret; + } + } + else + { + if constexpr (Single) + { + return r[0]; + } + else + { + return r; + } + } + } +#else + template = nullptr, + std::enable_if_t = nullptr> + static inline T convert_result(const Result& r) + { + return T(r[0]); + } + template = nullptr, + std::enable_if_t = nullptr> + static inline std::vector convert_result(const Result& r) + { + std::vector ret; + for (const Row& row : r) + { + ret.template emplace_back(T(row)); + } + return ret; + } + template = nullptr, + std::enable_if_t = nullptr> + static inline Row convert_result(const Result& r) + { + return r[0]; + } + template = nullptr, + std::enable_if_t = nullptr> + static inline Result convert_result(const Result& r) + { + return r; + } +#endif + + inline ResultType execSync(const DbClientPtr& client) + { + Result r(nullptr); + { + auto binder = *client << gen_sql(client->type()); + for (const std::string& a : gen_args()) + { + binder << a; + } + binder << Mode::Blocking; + binder >> [&r](const Result& result) { r = result; }; + binder.exec(); // exec may throw exception + } + return convert_result(r); + } + + template + void execAsync(const DbClientPtr& client, + TFn&& rCallback, + EFn&& exceptCallback) noexcept + { + auto binder = *client << gen_sql(client->type()); + for (const std::string& a : gen_args()) + { + binder << a; + } + binder >> std::forward(rCallback); + binder >> std::forward(exceptCallback); + } + + inline std::future execAsyncFuture( + const DbClientPtr& client) noexcept + { + auto binder = *client << gen_sql(client->type()); + for (const std::string& a : gen_args()) + { + binder << a; + } + std::shared_ptr> prom = + std::make_shared>(); + binder >> + [prom](const Result& r) { prom->set_value(convert_result(r)); }; + binder >> + [prom](const std::exception_ptr& e) { prom->set_exception(e); }; + binder.exec(); + return prom->get_future(); + } +}; +} // namespace orm +} // namespace drogon diff --git a/orm_lib/inc/drogon/orm/FilterBuilder.h b/orm_lib/inc/drogon/orm/FilterBuilder.h new file mode 100644 index 00000000..cfaba061 --- /dev/null +++ b/orm_lib/inc/drogon/orm/FilterBuilder.h @@ -0,0 +1,162 @@ +/** + * + * @file FilterBuilder.h + * @author Ken Matsui + * + * Copyright 2022, Ken Matsui. All rights reserved. + * https://github.com/drogonframework/drogon + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Drogon + * + */ + +#pragma once + +#include +#include + +namespace drogon +{ +namespace orm +{ +template +class FilterBuilder : public TransformBuilder +{ + public: + /** + * @brief A default constructor for derived classes. + * + * @return FilterBuilder The FilterBuilder itself. + */ + FilterBuilder() = default; + + /** + * @brief A copy constructor to be called by QueryBuilder. + * + * @param from The table. + * @param columns The columns. + * + * @return FilterBuilder The FilterBuilder itself. + */ + FilterBuilder(const std::string& from, const std::string& columns) + { + this->from_ = from; + this->columns_ = columns; + } + + /** + * @brief Filter rows whose value is the same as `value`. + * + * @param column The column to be filtered. + * @param value The value to filter rows. + * + * @return FilterBuilder& The FilterBuilder itself. + */ + inline FilterBuilder& eq(const std::string& column, + const std::string& value) + { + this->assert_column(column); + this->filters_.push_back({column, CompareOperator::EQ, value}); + return *this; + } + + /** + * @brief Filter rows whose value is NOT the same as `value`. + * + * @param column The column to be filtered. + * @param value The value to filter rows. + * + * @return FilterBuilder& The FilterBuilder itself. + */ + inline FilterBuilder& neq(const std::string& column, + const std::string& value) + { + this->assert_column(column); + this->filters_.push_back({column, CompareOperator::NE, value}); + return *this; + } + + /** + * @brief Filter rows whose value is greater than `value`. + * + * @param column The column to be filtered. + * @param value The value to filter rows. + * + * @return FilterBuilder& The FilterBuilder itself. + */ + inline FilterBuilder& gt(const std::string& column, + const std::string& value) + { + this->assert_column(column); + this->filters_.push_back({column, CompareOperator::GT, value}); + return *this; + } + + /** + * @brief Filter rows whose value is greater than or equal to `value`. + * + * @param column The column to be filtered. + * @param value The value to filter rows. + * + * @return FilterBuilder& The FilterBuilder itself. + */ + inline FilterBuilder& gte(const std::string& column, + const std::string& value) + { + this->assert_column(column); + this->filters_.push_back({column, CompareOperator::GE, value}); + return *this; + } + + /** + * @brief Filter rows whose value is less than `value`. + * + * @param column The column to be filtered. + * @param value The value to filter rows. + * + * @return FilterBuilder& The FilterBuilder itself. + */ + inline FilterBuilder& lt(const std::string& column, + const std::string& value) + { + this->assert_column(column); + this->filters_.push_back({column, CompareOperator::LT, value}); + return *this; + } + + /** + * @brief Filter rows whose value is less than or equal to `value`. + * + * @param column The column to be filtered. + * @param value The value to filter rows. + * + * @return FilterBuilder& The FilterBuilder itself. + */ + inline FilterBuilder& lte(const std::string& column, + const std::string& value) + { + this->assert_column(column); + this->filters_.push_back({column, CompareOperator::LE, value}); + return *this; + } + + /** + * @brief Filter rows whose value matches the `pattern`. + * + * @param column The column to be filtered. + * @param pattern The pattern to filter rows. + * + * @return FilterBuilder& The FilterBuilder itself. + */ + inline FilterBuilder& like(const std::string& column, + const std::string& pattern) + { + this->assert_column(column); + this->filters_.push_back({column, CompareOperator::Like, pattern}); + return *this; + } +}; +} // namespace orm +} // namespace drogon diff --git a/orm_lib/inc/drogon/orm/QueryBuilder.h b/orm_lib/inc/drogon/orm/QueryBuilder.h new file mode 100644 index 00000000..90311051 --- /dev/null +++ b/orm_lib/inc/drogon/orm/QueryBuilder.h @@ -0,0 +1,78 @@ +/** + * + * @file QueryBuilder.h + * @author Ken Matsui + * + * Copyright 2022, Ken Matsui. All rights reserved. + * https://github.com/drogonframework/drogon + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Drogon + * + */ + +#pragma once + +#include +#include + +namespace drogon +{ +namespace orm +{ +template +class QueryBuilder : public FilterBuilder +{ + /** + * @brief When a user does not set the table name explicitly, then retrieve + * it from model `T`. + * + * @return std::string The table name + */ + inline const std::string& getTableName() const + { + return this->from_.empty() ? T::tableName : this->from_; + } + + public: + /** + * @brief Set from which table to return. + * + * @param table The table. + * + * @return QueryBuilder& The QueryBuilder itself. + */ + inline QueryBuilder& from(const std::string& table) + { + this->from_ = table; + return *this; + } + + /** + * @brief Select specific columns. + * + * @param columns The columns. + * + * @return FilterBuilder A new FilterBuilder. + * + * @note If you would return all rows, please use the `selectAll` method. + * The method can return rows as model type `T`. + */ + inline FilterBuilder select(const std::string& columns) const + { + return {getTableName(), columns}; + } + + /** + * @brief Select all columns. + * + * @return FilterBuilder A new FilterBuilder. + */ + inline FilterBuilder selectAll() const + { + return {getTableName(), "*"}; + } +}; +} // namespace orm +} // namespace drogon diff --git a/orm_lib/inc/drogon/orm/Row.h b/orm_lib/inc/drogon/orm/Row.h index b7f69140..a75c6e00 100644 --- a/orm_lib/inc/drogon/orm/Row.h +++ b/orm_lib/inc/drogon/orm/Row.h @@ -77,11 +77,13 @@ class DROGON_EXPORT Row ConstReverseIterator rend() const; ConstReverseIterator crend() const; + Row() noexcept = default; Row(const Row &r) noexcept = default; Row(Row &&) noexcept = default; + Row &operator=(const Row &) = default; private: - const Result result_; + Result result_; protected: friend class Field; diff --git a/orm_lib/inc/drogon/orm/TransformBuilder.h b/orm_lib/inc/drogon/orm/TransformBuilder.h new file mode 100644 index 00000000..5fbd25eb --- /dev/null +++ b/orm_lib/inc/drogon/orm/TransformBuilder.h @@ -0,0 +1,127 @@ +/** + * + * @file TransformBuilder.h + * @author Ken Matsui + * + * Copyright 2022, Ken Matsui. All rights reserved. + * https://github.com/drogonframework/drogon + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Drogon + * + */ + +#pragma once + +#include +#include +#include + +namespace drogon +{ +namespace orm +{ +template +class TransformBuilder : public BaseBuilder +{ + public: + /** + * @brief A default constructor for derived classes. + * + * @return TransformBuilder The TransformBuilder itself. + */ + TransformBuilder() = default; + + /** + * @brief A copy constructor from a non `Single` builder to `Single` + * builder, used by the `single` method. + * + * @return TransformBuilder The TransformBuilder itself. + * + * @note This function is enabled only when `Single` is true. + */ + template = nullptr> + TransformBuilder(const TransformBuilder& tb) + { + this->from_ = tb.from_; + this->columns_ = tb.columns_; + this->filters_ = tb.filters_; + this->limit_ = tb.limit_; + this->offset_ = tb.offset_; + this->orders_ = tb.orders_; + } + + /** + * @brief Limit the result to `count`. + * + * @param count The number of rows to be limited. + * + * @return TransformBuilder& The TransformBuilder itself. + */ + inline TransformBuilder& limit(std::uint64_t count) + { + this->limit_ = count; + return *this; + } + + /** + * @brief Add a offset to the query. + * + * @param offset The offset. + * + * @return TransformBuilder& The TransformBuilder itself. + */ + inline TransformBuilder& offset(std::uint64_t count) + { + this->offset_ = count; + return *this; + } + + /** + * @brief Limit the result to an inclusive range. + * + * @param from The first index to limit the result. + * @param to The last index to limit the result. + * + * @return TransformBuilder& The TransformBuilder itself. + */ + inline TransformBuilder& range(std::uint64_t from, std::uint64_t to) + { + this->offset_ = from; + this->limit_ = to - from + 1; // inclusive + return *this; + } + + /** + * @brief Order the result. + * + * @param column The column to order by. + * @param asc If `true`, ascending order. If `false`, descending order. + * + * @return TransformBuilder& The TransformBuilder itself. + */ + inline TransformBuilder& order(const std::string& column, bool asc = true) + { + this->assert_column(column); + this->orders_.emplace_back(column, asc); + return *this; + } + + /** + * @brief Ensure returning only one row. + * + * @return TransformBuilder The TransformBuilder where + * Single is true and all else is the same. + * + * @note This function can be called only once throughout an instance of a + * builder. + */ + template = nullptr> + inline TransformBuilder single() const + { + return {*this}; + } +}; +} // namespace orm +} // namespace drogon diff --git a/orm_lib/tests/db_test.cc b/orm_lib/tests/db_test.cc index ba7700c0..44847373 100644 --- a/orm_lib/tests/db_test.cc +++ b/orm_lib/tests/db_test.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -682,6 +683,152 @@ DROGON_TEST(PostgreTest) "salt of pg"); } + /// Test ORM QueryBuilder + /// execSync + try + { + const std::vector users = + QueryBuilder{}.from("users").selectAll().execSync(clientPtr); + MANDATE(users.size() == 3); + } + catch (const DrogonDbException &e) + { + FAULT("postgresql - ORM QueryBuilder synchronous interface(0) what():", + e.base().what()); + } + try + { + const Result users = + QueryBuilder{}.from("users").select("id").execSync( + clientPtr); + MANDATE(users.size() == 3); + for (const Row &u : users) + { + MANDATE(!u["id"].isNull()); + } + } + catch (const DrogonDbException &e) + { + FAULT("postgresql - ORM QueryBuilder synchronous interface(1) what():", + e.base().what()); + } + try + { + const Users user = QueryBuilder{} + .from("users") + .selectAll() + .eq("id", "3") + .limit(1) + .single() + .order("id", false) + .execSync(clientPtr); + MANDATE(user.getPrimaryKey() == 3); + } + catch (const DrogonDbException &e) + { + FAULT("postgresql - ORM QueryBuilder synchronous interface(2) what():", + e.base().what()); + } + try + { + const Row user = QueryBuilder{} + .from("users") + .select("id") + .limit(1) + .single() + .order("id", false) + .execSync(clientPtr); + MANDATE(user["id"].as() == 3); + } + catch (const DrogonDbException &e) + { + FAULT("postgresql - ORM QueryBuilder synchronous interface(3) what():", + e.base().what()); + } + + /// execAsyncFuture + { + std::future> users = + QueryBuilder{}.from("users").selectAll().execAsyncFuture( + clientPtr); + try + { + const std::vector r = users.get(); + MANDATE(r.size() == 3); + } + catch (const DrogonDbException &e) + { + FAULT( + "postgresql - ORM QueryBuilder asynchronous interface(0) " + "what():", + e.base().what()); + } + } + { + std::future users = + QueryBuilder{}.from("users").select("id").execAsyncFuture( + clientPtr); + try + { + const Result r = users.get(); + MANDATE(r.size() == 3); + for (const Row &u : r) + { + MANDATE(!u["id"].isNull()); + } + } + catch (const DrogonDbException &e) + { + FAULT( + "postgresql - ORM QueryBuilder asynchronous interface(1) " + "what():", + e.base().what()); + } + } + { + std::future user = QueryBuilder{} + .from("users") + .selectAll() + .eq("id", "3") + .limit(1) + .single() + .order("id", false) + .execAsyncFuture(clientPtr); + try + { + const Users r = user.get(); + MANDATE(r.getPrimaryKey() == 3); + } + catch (const DrogonDbException &e) + { + FAULT( + "postgresql - ORM QueryBuilder asynchronous interface(2) " + "what():", + e.base().what()); + } + } + { + std::future users = QueryBuilder{} + .from("users") + .select("id") + .limit(1) + .single() + .order("id", false) + .execAsyncFuture(clientPtr); + try + { + const Row r = users.get(); + MANDATE(r["id"].as() == 3); + } + catch (const DrogonDbException &e) + { + FAULT( + "postgresql - ORM QueryBuilder asynchronous interface(3) " + "what():", + e.base().what()); + } + } + #ifdef __cpp_impl_coroutine auto coro_test = [clientPtr, TEST_CTX]() -> drogon::Task<> { /// 7 Test coroutines. @@ -1422,6 +1569,153 @@ DROGON_TEST(MySQLTest) FAULT("mysql - ORM mapper synchronous interface(1) what():", e.base().what()); } + + /// Test ORM QueryBuilder + /// execSync + try + { + const std::vector users = + QueryBuilder{}.from("users").selectAll().execSync(clientPtr); + MANDATE(users.size() == 2); + } + catch (const DrogonDbException &e) + { + FAULT("mysql - ORM QueryBuilder synchronous interface(0) what():", + e.base().what()); + } + try + { + const Result users = + QueryBuilder{}.from("users").select("id").execSync( + clientPtr); + MANDATE(users.size() == 2); + for (const Row &u : users) + { + MANDATE(!u["id"].isNull()); + } + } + catch (const DrogonDbException &e) + { + FAULT("mysql - ORM QueryBuilder synchronous interface(1) what():", + e.base().what()); + } + try + { + const Users user = QueryBuilder{} + .from("users") + .selectAll() + .eq("id", "2") + .limit(1) + .single() + .order("id", false) + .execSync(clientPtr); + MANDATE(user.getPrimaryKey() == 2); + } + catch (const DrogonDbException &e) + { + FAULT("mysql - ORM QueryBuilder synchronous interface(2) what():", + e.base().what()); + } + try + { + const Row user = QueryBuilder{} + .from("users") + .select("id") + .limit(1) + .single() + .order("id", false) + .execSync(clientPtr); + MANDATE(user["id"].as() == 2); + } + catch (const DrogonDbException &e) + { + FAULT("mysql - ORM QueryBuilder synchronous interface(3) what():", + e.base().what()); + } + + /// execAsyncFuture + { + std::future> users = + QueryBuilder{}.from("users").selectAll().execAsyncFuture( + clientPtr); + try + { + const std::vector r = users.get(); + MANDATE(r.size() == 2); + } + catch (const DrogonDbException &e) + { + FAULT( + "mysql - ORM QueryBuilder asynchronous interface(0) " + "what():", + e.base().what()); + } + } + { + std::future users = + QueryBuilder{}.from("users").select("id").execAsyncFuture( + clientPtr); + try + { + const Result r = users.get(); + MANDATE(r.size() == 2); + for (const Row &u : r) + { + MANDATE(!u["id"].isNull()); + } + } + catch (const DrogonDbException &e) + { + FAULT( + "mysql - ORM QueryBuilder asynchronous interface(1) " + "what():", + e.base().what()); + } + } + { + std::future user = QueryBuilder{} + .from("users") + .selectAll() + .eq("id", "2") + .limit(1) + .single() + .order("id", false) + .execAsyncFuture(clientPtr); + try + { + const Users r = user.get(); + MANDATE(r.getPrimaryKey() == 2); + } + catch (const DrogonDbException &e) + { + FAULT( + "mysql - ORM QueryBuilder asynchronous interface(2) " + "what():", + e.base().what()); + } + } + { + std::future users = QueryBuilder{} + .from("users") + .select("id") + .limit(1) + .single() + .order("id", false) + .execAsyncFuture(clientPtr); + try + { + const Row r = users.get(); + MANDATE(r["id"].as() == 2); + } + catch (const DrogonDbException &e) + { + FAULT( + "mysql - ORM QueryBuilder asynchronous interface(3) " + "what():", + e.base().what()); + } + } + #ifdef __cpp_impl_coroutine auto coro_test = [clientPtr, TEST_CTX]() -> drogon::Task<> { /// 7 Test coroutines. @@ -2076,6 +2370,153 @@ DROGON_TEST(SQLite3Test) FAULT("sqlite3 - ORM mapper synchronous interface(0) what():", e.base().what()); } + + /// Test ORM QueryBuilder + /// execSync + try + { + const std::vector users = + QueryBuilder{}.from("users").selectAll().execSync(clientPtr); + MANDATE(users.size() == 2); + } + catch (const DrogonDbException &e) + { + FAULT("sqlite3 - ORM QueryBuilder synchronous interface(0) what():", + e.base().what()); + } + try + { + const Result users = + QueryBuilder{}.from("users").select("id").execSync( + clientPtr); + MANDATE(users.size() == 2); + for (const Row &u : users) + { + MANDATE(!u["id"].isNull()); + } + } + catch (const DrogonDbException &e) + { + FAULT("sqlite3 - ORM QueryBuilder synchronous interface(1) what():", + e.base().what()); + } + try + { + const Users user = QueryBuilder{} + .from("users") + .selectAll() + .eq("id", "2") + .limit(1) + .single() + .order("id", false) + .execSync(clientPtr); + MANDATE(user.getPrimaryKey() == 2); + } + catch (const DrogonDbException &e) + { + FAULT("sqlite3 - ORM QueryBuilder synchronous interface(2) what():", + e.base().what()); + } + try + { + const Row user = QueryBuilder{} + .from("users") + .select("id") + .limit(1) + .single() + .order("id", false) + .execSync(clientPtr); + MANDATE(user["id"].as() == 2); + } + catch (const DrogonDbException &e) + { + FAULT("sqlite3 - ORM QueryBuilder synchronous interface(3) what():", + e.base().what()); + } + + /// execAsyncFuture + { + std::future> users = + QueryBuilder{}.from("users").selectAll().execAsyncFuture( + clientPtr); + try + { + const std::vector r = users.get(); + MANDATE(r.size() == 2); + } + catch (const DrogonDbException &e) + { + FAULT( + "sqlite3 - ORM QueryBuilder asynchronous interface(0) " + "what():", + e.base().what()); + } + } + { + std::future users = + QueryBuilder{}.from("users").select("id").execAsyncFuture( + clientPtr); + try + { + const Result r = users.get(); + MANDATE(r.size() == 2); + for (const Row &u : r) + { + MANDATE(!u["id"].isNull()); + } + } + catch (const DrogonDbException &e) + { + FAULT( + "sqlite3 - ORM QueryBuilder asynchronous interface(1) " + "what():", + e.base().what()); + } + } + { + std::future user = QueryBuilder{} + .from("users") + .selectAll() + .eq("id", "2") + .limit(1) + .single() + .order("id", false) + .execAsyncFuture(clientPtr); + try + { + const Users r = user.get(); + MANDATE(r.getPrimaryKey() == 2); + } + catch (const DrogonDbException &e) + { + FAULT( + "sqlite3 - ORM QueryBuilder asynchronous interface(2) " + "what():", + e.base().what()); + } + } + { + std::future users = QueryBuilder{} + .from("users") + .select("id") + .limit(1) + .single() + .order("id", false) + .execAsyncFuture(clientPtr); + try + { + const Row r = users.get(); + MANDATE(r["id"].as() == 2); + } + catch (const DrogonDbException &e) + { + FAULT( + "sqlite3 - ORM QueryBuilder asynchronous interface(3) " + "what():", + e.base().what()); + } + } + #ifdef __cpp_impl_coroutine auto coro_test = [clientPtr, TEST_CTX]() -> drogon::Task<> { /// 7 Test coroutines. diff --git a/orm_lib/tests/mysql/Users.h b/orm_lib/tests/mysql/Users.h index 455750ce..28160a43 100644 --- a/orm_lib/tests/mysql/Users.h +++ b/orm_lib/tests/mysql/Users.h @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef __cpp_impl_coroutine #include #endif @@ -234,6 +235,10 @@ class Users /// Relationship interfaces private: friend Mapper; + friend BaseBuilder; + friend BaseBuilder; + friend BaseBuilder; + friend BaseBuilder; #ifdef __cpp_impl_coroutine friend CoroMapper; #endif diff --git a/orm_lib/tests/postgresql/Users.h b/orm_lib/tests/postgresql/Users.h index 6c50ff3a..27392289 100644 --- a/orm_lib/tests/postgresql/Users.h +++ b/orm_lib/tests/postgresql/Users.h @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef __cpp_impl_coroutine #include #endif @@ -234,6 +235,10 @@ class Users /// Relationship interfaces private: friend Mapper; + friend BaseBuilder; + friend BaseBuilder; + friend BaseBuilder; + friend BaseBuilder; #ifdef __cpp_impl_coroutine friend CoroMapper; #endif diff --git a/orm_lib/tests/sqlite3/Users.h b/orm_lib/tests/sqlite3/Users.h index 983608d6..d1570c21 100644 --- a/orm_lib/tests/sqlite3/Users.h +++ b/orm_lib/tests/sqlite3/Users.h @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef __cpp_impl_coroutine #include #endif @@ -239,6 +240,10 @@ class Users /// Relationship interfaces private: friend drogon::orm::Mapper; + friend drogon::orm::BaseBuilder; + friend drogon::orm::BaseBuilder; + friend drogon::orm::BaseBuilder; + friend drogon::orm::BaseBuilder; #ifdef __cpp_impl_coroutine friend drogon::orm::CoroMapper; #endif