/** * * @file Mapper.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 #include #include #include #include #include #include #ifdef _WIN32 using ssize_t = std::intptr_t; #endif namespace drogon { namespace orm { enum class SortOrder { ASC, DESC }; namespace internal { template struct Traits { using type = typename T::PrimaryKeyType; }; template struct Traits { using type = int; }; template struct has_sqlForFindingByPrimaryKey { private: using yes = std::true_type; using no = std::false_type; template static auto test(int) -> decltype(U::sqlForFindingByPrimaryKey(), yes()); template static no test(...); public: static constexpr bool value = std::is_same(0)), yes>::value; }; template struct has_sqlForDeletingByPrimaryKey { private: using yes = std::true_type; using no = std::false_type; template static auto test(int) -> decltype(U::sqlForDeletingByPrimaryKey().length(), yes()); template static no test(...); public: static constexpr bool value = std::is_same(0)), yes>::value; }; } // namespace internal /** * @brief The mapper template * * @tparam T The type of the model to be mapped. * * @details The mapping between the model object and the database table is * performed by the Mapper class template. The Mapper class template * encapsulates common operations such as adding, deleting, and changing, so * that the user can perform the above operations without writing a SQL * statement. * * The construction of the Mapper object is very simple. The template * parameter is the type of the model you want to access. The constructor has * only one parameter, which is the DbClient smart pointer mentioned earlier. As * mentioned earlier, the Transaction class is a subclass of DbClient, so you * can also construct a Mapper object with a smart pointer to a transaction, * which means that the Mapper mapping also supports transactions. * * Like DbClient, Mapper also provides asynchronous and synchronous interfaces. * The synchronous interface is blocked and may throw an exception. The returned * future object is blocked in the get() method and may throw an exception. The * normal asynchronous interface does not throw an exception, but returns the * result through two callbacks (result callback and exception callback). The * type of the exception callback is the same as that in the DbClient interface. * The result callback is also divided into several categories according to the * interface function. */ template class Mapper { public: /** * @brief Construct a new Mapper object * * @param client The smart pointer to the database client object. */ explicit Mapper(DbClientPtr client) : client_(std::move(client)) { } /** * @brief Add a limit to the query. * * @param limit The limit * @return Mapper& The Mapper itself. */ Mapper &limit(size_t limit); /** * @brief Add a offset to the query. * * @param offset The offset. * @return Mapper& The Mapper itself. */ Mapper &offset(size_t offset); /** * @brief Set the order of the results. * * @param colName the column name, the results are sorted by that column * @param order Ascending or descending order * @return Mapper& The Mapper itself. */ Mapper &orderBy(const std::string &colName, const SortOrder &order = SortOrder::ASC); /** * @brief Set the order of the results. * * @param colIndex the column index, the results are sorted by that column * @param order Ascending or descending order * @return Mapper& The Mapper itself. */ Mapper &orderBy(size_t colIndex, const SortOrder &order = SortOrder::ASC); /** * @brief Set limit and offset to achieve pagination. * This method will override limit() and offset(), and will be overriden by * them. * * @param page The page number * @param perPage The number of columns per page * @return Mapper& The Mapper itself. */ Mapper &paginate(size_t page, size_t perPage); /** * @brief Lock the result for updating. * * @return Mapper& The Mapper itself. */ Mapper &forUpdate(); using SingleRowCallback = std::function; using MultipleRowsCallback = std::function)>; using CountCallback = std::function; using TraitsPKType = typename internal:: Traits::value>::type; /** * @brief Find a record by the primary key. * * @param key The value of the primary key. * @return T The record of the primary key. * @note If no hit record exists, an UnexpectedRows exception is thrown. */ template inline typename std::enable_if< !std::is_same::value, T>::type findByPrimaryKey(const TraitsPKType &key) noexcept(false) { static_assert(!std::is_same::value, "No primary key in the table!"); static_assert( internal::has_sqlForFindingByPrimaryKey::value, "No function member named sqlForFindingByPrimaryKey, please " "make sure that the model class is generated by the latest " "version of drogon_ctl"); // return findOne(Criteria(T::primaryKeyName, key)); std::string sql = T::sqlForFindingByPrimaryKey(); if (forUpdate_) { sql += " for update"; } clear(); Result r(nullptr); { auto binder = *client_ << std::move(sql); outputPrimeryKeyToBinder(key, binder); binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // exec may be throw exception; } if (r.size() == 0) { throw UnexpectedRows("0 rows found"); } else if (r.size() > 1) { throw UnexpectedRows("Found more than one row"); } auto row = r[0]; return T(row); } template inline typename std::enable_if< std::is_same::value, T>::type findByPrimaryKey(const TraitsPKType &key) noexcept(false) { LOG_FATAL << "The table must have a primary key"; abort(); } /** * @brief Asynchronously find a record by the primary key. * * @param key The value of the primary key. * @param rcb Is called when a record is found. * @param ecb Is called when an error occurs or a record cannot be found. */ template inline typename std::enable_if< !std::is_same::value, void>::type findByPrimaryKey(const TraitsPKType &key, const SingleRowCallback &rcb, const ExceptionCallback &ecb) noexcept { static_assert(!std::is_same::value, "No primary key in the table!"); static_assert( internal::has_sqlForFindingByPrimaryKey::value, "No function member named sqlForFindingByPrimaryKey, please " "make sure that the model class is generated by the latest " "version of drogon_ctl"); // findOne(Criteria(T::primaryKeyName, key), rcb, ecb); std::string sql = T::sqlForFindingByPrimaryKey(); if (forUpdate_) { sql += " for update"; } clear(); auto binder = *client_ << std::move(sql); outputPrimeryKeyToBinder(key, binder); binder >> [ecb, rcb](const Result &r) { if (r.size() == 0) { ecb(UnexpectedRows("0 rows found")); } else if (r.size() > 1) { ecb(UnexpectedRows("Found more than one row")); } else { rcb(T(r[0])); } }; binder >> ecb; } template inline typename std::enable_if< std::is_same::value, void>::type findByPrimaryKey(const TraitsPKType &key, const SingleRowCallback &rcb, const ExceptionCallback &ecb) noexcept { LOG_FATAL << "The table must have a primary key"; abort(); } /** * @brief Asynchronously find a record by the primary key. * * @param key The value of the primary key. * @return std::future The future object with which user can get the * result. * @note If no hit record exists, an UnexpectedRows exception is thrown when * user calls the get() method of the future object. */ template inline typename std::enable_if< !std::is_same::value, std::future>::type findFutureByPrimaryKey(const TraitsPKType &key) noexcept { static_assert(!std::is_same::value, "No primary key in the table!"); static_assert( internal::has_sqlForFindingByPrimaryKey::value, "No function member named sqlForFindingByPrimaryKey, please " "make sure that the model class is generated by the latest " "version of drogon_ctl"); // return findFutureOne(Criteria(T::primaryKeyName, key)); std::string sql = T::sqlForFindingByPrimaryKey(); if (forUpdate_) { sql += " for update"; } clear(); auto binder = *client_ << std::move(sql); outputPrimeryKeyToBinder(key, binder); std::shared_ptr> prom = std::make_shared>(); binder >> [prom](const Result &r) { if (r.size() == 0) { prom->set_exception( std::make_exception_ptr(UnexpectedRows("0 rows found"))); } else if (r.size() > 1) { prom->set_exception(std::make_exception_ptr( UnexpectedRows("Found more than one row"))); } else { prom->set_value(T(r[0])); } }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } template inline typename std::enable_if< std::is_same::value, std::future>::type findFutureByPrimaryKey(const TraitsPKType &key) noexcept { LOG_FATAL << "The table must have a primary key"; abort(); } /** * @brief Find all the records in the table. * * @return std::vector The vector of all the records. */ std::vector findAll() noexcept(false); /** * @brief Asynchronously find all the records in the table. * * @param rcb is called with the result. * @param ecb is called when an error occurs. */ void findAll(const MultipleRowsCallback &rcb, const ExceptionCallback &ecb) noexcept; /** * @brief Asynchronously find all the records in the table. * * @return std::future> The future object with which user can * get the result. */ std::future> findFutureAll() noexcept; /** * @brief Get the count of rows that match the given criteria. * * @param criteria The criteria. * @return size_t The number of rows. */ size_t count(const Criteria &criteria = Criteria()) noexcept(false); /** * @brief Asynchronously get the number of rows that match the given * criteria. * * @param criteria The criteria. * @param rcb is clalled with the result. * @param ecb is called when an error occurs. */ void count(const Criteria &criteria, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept; /** * @brief Asynchronously get the number of rows that match the given * criteria. * * @param criteria The criteria. * @return std::future The future object with which user can get the * number of rows */ std::future countFuture( const Criteria &criteria = Criteria()) noexcept; /** * @brief Find one record that matches the given criteria. * * @param criteria The criteria. * @return T The result record. * @note if the number of rows is greater than one or equal to zero, an * UnexpectedRows exception is thrown. */ T findOne(const Criteria &criteria) noexcept(false); /** * @brief Asynchronously find one record that matches the given criteria. * * @param criteria The criteria. * @param rcb is called with the result. * @param ecb is called when an error occurs. */ void findOne(const Criteria &criteria, const SingleRowCallback &rcb, const ExceptionCallback &ecb) noexcept; /** * @brief Asynchronously find one record that matches the given criteria. * * @param criteria The criteria. * @return std::future The future object with which user can get the * result. * @note if the number of rows is greater than one or equal to zero, an * UnexpectedRows exception is thrown when the get() method of the future * object is called. */ std::future findFutureOne(const Criteria &criteria) noexcept; /** * @brief Select the rows that match the given criteria. * * @param criteria The criteria. * @return std::vector The vector of rows that match the given criteria. */ std::vector findBy(const Criteria &criteria) noexcept(false); /** * @brief Asynchronously select the rows that match the given criteria. * * @param criteria The criteria. * @param rcb is called with the result. * @param ecb is called when an error occurs. */ void findBy(const Criteria &criteria, const MultipleRowsCallback &rcb, const ExceptionCallback &ecb) noexcept; /** * @brief Asynchronously select the rows that match the given criteria. * * @param criteria The criteria. * @return std::future> The future object with which user can * get the result. */ std::future> findFutureBy(const Criteria &criteria) noexcept; /** * @brief Insert a row into the table. * * @param obj The object to be inserted. * @note The auto-increased primary key (if it exists) is set to the obj * argument after the method returns. */ void insert(T &obj) noexcept(false); /** * @brief Asynchronously insert a row into the table. * * @param obj The object to be inserted. * @param rcb is called with the result (with the auto-increased primary key * (if it exists)). * @param ecb is called when an error occurs. */ void insert(const T &obj, const SingleRowCallback &rcb, const ExceptionCallback &ecb) noexcept; /** * @brief Asynchronously insert a row into the table. * * @return std::future The future object with which user can get the * result (with the auto-increased primary key (if it exists)). */ std::future insertFuture(const T &) noexcept; /** * @brief Update a record. * * @param obj The record. * @return size_t The number of updated records. It only could be 0 or 1. * @note The table must have a primary key. */ size_t update(const T &obj) noexcept(false); /** * @brief Asynchronously update a record. * * @param obj The record. * @param rcb is called with the number of updated records. * @param ecb is called when an error occurs. * @note The table must have a primary key. */ void update(const T &obj, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept; /** * @brief Asynchronously update a record. * * @param obj The record. * @return std::future The future object with which user can get the * number of updated records. * @note The table must have a primary key. */ std::future updateFuture(const T &obj) noexcept; /** * @brief Update a record that match both the primary key and the given * criteria. * * @param colNames Columns to update. * @param criteria The criteria. * @param args New value of target columns. * @return size_t The number of updated records. It only could be 0 or 1. * @note The table must have a primary key. */ template size_t updateBy(const std::vector &colNames, const Criteria &criteria, Arguments &&...args) noexcept(false); /** * @brief Asynchronously select the rows that match both the primary key and * the given criteria. * * @param colNames Columns to update. * @param criteria The criteria. * @param rcb is called with the result. * @param ecb is called when an error occurs. * @param args New value of target columns. */ template void updateBy(const std::vector &colNames, const CountCallback &rcb, const ExceptionCallback &ecb, const Criteria &criteria, Arguments &&...args) noexcept; /** * @brief Asynchronously update a record that match both the primary key and * the given criteria. * * @param colNames Columns to update. * @param criteria The criteria. * @param args New value of target columns. * @return std::future The future object with which user can get the * number of updated records. * @note The table must have a primary key. */ template std::future updateFutureBy(const std::vector &colNames, const Criteria &criteria, Arguments &&...args) noexcept; /** * @brief Delete a record from the table. * * @param obj The record. * @return size_t The number of deleted records. * @note The table must have a primary key. */ size_t deleteOne(const T &obj) noexcept(false); /** * @brief Asynchronously delete a record from the table. * * @param obj The record. * @param rcb is called with the number of deleted records. * @param ecb is called when an error occurs. * @note The table must have a primary key. */ void deleteOne(const T &obj, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept; /** * @brief Asynchronously delete a record from the table. * * @param obj The record. * @return std::future The future object with which user can get the * number of deleted records. * @note The table must have a primary key. */ std::future deleteFutureOne(const T &obj) noexcept; /** * @brief Delete records that satisfy the given criteria. * * @param criteria The criteria. * @return size_t The number of deleted records. */ size_t deleteBy(const Criteria &criteria) noexcept(false); /** * @brief Delete records that match the given criteria asynchronously. * * @param criteria The criteria * @param rcb is called with the number of deleted records. * @param ecb is called when an error occurs. */ void deleteBy(const Criteria &criteria, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept; /** * @brief Delete records that match the given criteria asynchronously. * * @param criteria The criteria * @return std::future The future object with which user can get the * number of deleted records */ std::future deleteFutureBy(const Criteria &criteria) noexcept; /** * @brief Delete the record that matches the given primary key. * * @param key The primary key. * @return size_t The number of deleted records (1 or 0). */ size_t deleteByPrimaryKey(const TraitsPKType &key) noexcept(false); /** * @brief Asynchronously delete the record that matches the given primary * key. * * @param key The primary key. * @param rcb is called with the number of deleted records. * @param ecb is called when an error occurs. */ void deleteByPrimaryKey(const TraitsPKType &key, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept; /** * @brief Delete the record that matches the given primary key * asynchronously. * * @param key The primary key. * @return std::future The future object with which user can get the * number of deleted records */ std::future deleteFutureByPrimaryKey( const TraitsPKType &key) noexcept; protected: DbClientPtr client_; size_t limit_{0}; size_t offset_{0}; std::string orderByString_; bool forUpdate_{false}; void clear() { limit_ = 0; offset_ = 0; orderByString_.clear(); forUpdate_ = false; } template typename std::enable_if::value, void>::type makePrimaryKeyCriteria(std::string &sql) { sql += " where "; sql += T::primaryKeyName; sql += " = $?"; } template typename std::enable_if< std::is_same, PKType>::value, void>::type makePrimaryKeyCriteria(std::string &sql) { sql += " where "; for (size_t i = 0; i < T::primaryKeyName.size(); ++i) { sql += T::primaryKeyName[i]; sql += " = $?"; if (i < (T::primaryKeyName.size() - 1)) { sql += " and "; } } } template typename std::enable_if::value, void>::type outputPrimeryKeyToBinder(const TraitsPKType &pk, internal::SqlBinder &binder) { binder << pk; } template typename std::enable_if< std::is_same, PKType>::value, void>::type outputPrimeryKeyToBinder(const TraitsPKType &pk, internal::SqlBinder &binder) { tupleToBinder(pk, binder); } template ::value> typename std::enable_if<(N > 1), void>::type tupleToBinder( const TP &t, internal::SqlBinder &binder) { tupleToBinder(t, binder); binder << std::get(t); } template ::value> typename std::enable_if<(N == 1), void>::type tupleToBinder( const TP &t, internal::SqlBinder &binder) { binder << std::get<0>(t); } std::string replaceSqlPlaceHolder(const std::string &sqlStr, const std::string &holderStr) const; }; template inline T Mapper::findOne(const Criteria &criteria) noexcept(false) { std::string sql = "select * from "; sql += T::tableName; bool hasParameters = false; if (criteria) { sql += " where "; sql += criteria.criteriaString(); hasParameters = true; } sql.append(orderByString_); if (limit_ > 0) { hasParameters = true; sql.append(" limit $?"); } if (offset_ > 0) { hasParameters = true; sql.append(" offset $?"); } if (hasParameters) sql = replaceSqlPlaceHolder(sql, "$?"); if (forUpdate_) { sql += " for update"; } Result r(nullptr); { auto binder = *client_ << std::move(sql); if (criteria) criteria.outputArgs(binder); if (limit_ > 0) binder << limit_; if (offset_) binder << offset_; clear(); binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // exec may be throw exception; } if (r.size() == 0) { throw UnexpectedRows("0 rows found"); } else if (r.size() > 1) { throw UnexpectedRows("Found more than one row"); } auto row = r[0]; return T(row); } template inline void Mapper::findOne(const Criteria &criteria, const SingleRowCallback &rcb, const ExceptionCallback &ecb) noexcept { std::string sql = "select * from "; sql += T::tableName; bool hasParameters = false; if (criteria) { sql += " where "; sql += criteria.criteriaString(); hasParameters = true; } sql.append(orderByString_); if (limit_ > 0) { hasParameters = true; sql.append(" limit $?"); } if (offset_ > 0) { hasParameters = true; sql.append(" offset $?"); } if (hasParameters) sql = replaceSqlPlaceHolder(sql, "$?"); if (forUpdate_) { sql += " for update"; } auto binder = *client_ << std::move(sql); if (criteria) criteria.outputArgs(binder); if (limit_ > 0) binder << limit_; if (offset_) binder << offset_; clear(); binder >> [ecb, rcb](const Result &r) { if (r.size() == 0) { ecb(UnexpectedRows("0 rows found")); } else if (r.size() > 1) { ecb(UnexpectedRows("Found more than one row")); } else { rcb(T(r[0])); } }; binder >> ecb; } template inline std::future Mapper::findFutureOne( const Criteria &criteria) noexcept { std::string sql = "select * from "; sql += T::tableName; bool hasParameters = false; if (criteria) { sql += " where "; sql += criteria.criteriaString(); hasParameters = true; } sql.append(orderByString_); if (limit_ > 0) { hasParameters = true; sql.append(" limit $?"); } if (offset_ > 0) { hasParameters = true; sql.append(" offset $?"); } if (hasParameters) sql = replaceSqlPlaceHolder(sql, "$?"); if (forUpdate_) { sql += " for update"; } auto binder = *client_ << std::move(sql); if (criteria) criteria.outputArgs(binder); if (limit_ > 0) binder << limit_; if (offset_) binder << offset_; clear(); std::shared_ptr> prom = std::make_shared>(); binder >> [prom](const Result &r) { if (r.size() == 0) { prom->set_exception( std::make_exception_ptr(UnexpectedRows("0 rows found"))); } else if (r.size() > 1) { prom->set_exception(std::make_exception_ptr( UnexpectedRows("Found more than one row"))); } else { prom->set_value(T(r[0])); } }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } template inline std::vector Mapper::findBy(const Criteria &criteria) noexcept( false) { std::string sql = "select * from "; sql += T::tableName; bool hasParameters = false; if (criteria) { hasParameters = true; sql += " where "; sql += criteria.criteriaString(); } sql.append(orderByString_); if (limit_ > 0) { hasParameters = true; sql.append(" limit $?"); } if (offset_ > 0) { hasParameters = true; sql.append(" offset $?"); } if (hasParameters) sql = replaceSqlPlaceHolder(sql, "$?"); if (forUpdate_) { sql += " for update"; } Result r(nullptr); { auto binder = *client_ << std::move(sql); if (criteria) criteria.outputArgs(binder); if (limit_ > 0) binder << limit_; if (offset_) binder << offset_; clear(); binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // exec may be throw exception; } std::vector ret; for (auto const &row : r) { ret.push_back(T(row)); } return ret; } template inline void Mapper::findBy(const Criteria &criteria, const MultipleRowsCallback &rcb, const ExceptionCallback &ecb) noexcept { std::string sql = "select * from "; sql += T::tableName; bool hasParameters = false; if (criteria) { hasParameters = true; sql += " where "; sql += criteria.criteriaString(); } sql.append(orderByString_); if (limit_ > 0) { hasParameters = true; sql.append(" limit $?"); } if (offset_ > 0) { hasParameters = true; sql.append(" offset $?"); } if (hasParameters) sql = replaceSqlPlaceHolder(sql, "$?"); if (forUpdate_) { sql += " for update"; } auto binder = *client_ << std::move(sql); if (criteria) criteria.outputArgs(binder); if (limit_ > 0) binder << limit_; if (offset_) binder << offset_; clear(); binder >> [rcb](const Result &r) { std::vector ret; for (auto const &row : r) { ret.push_back(T(row)); } rcb(ret); }; binder >> ecb; } template inline std::future> Mapper::findFutureBy( const Criteria &criteria) noexcept { std::string sql = "select * from "; sql += T::tableName; bool hasParameters = false; if (criteria) { hasParameters = true; sql += " where "; sql += criteria.criteriaString(); } sql.append(orderByString_); if (limit_ > 0) { hasParameters = true; sql.append(" limit $?"); } if (offset_ > 0) { hasParameters = true; sql.append(" offset $?"); } if (hasParameters) sql = replaceSqlPlaceHolder(sql, "$?"); if (forUpdate_) { sql += " for update"; } auto binder = *client_ << std::move(sql); if (criteria) criteria.outputArgs(binder); if (limit_ > 0) binder << limit_; if (offset_) binder << offset_; clear(); std::shared_ptr>> prom = std::make_shared>>(); binder >> [prom](const Result &r) { std::vector ret; for (auto const &row : r) { ret.push_back(T(row)); } prom->set_value(ret); }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } template inline std::vector Mapper::findAll() noexcept(false) { return findBy(Criteria()); } template inline void Mapper::findAll(const MultipleRowsCallback &rcb, const ExceptionCallback &ecb) noexcept { findBy(Criteria(), rcb, ecb); } template inline std::future> Mapper::findFutureAll() noexcept { return findFutureBy(Criteria()); } template inline size_t Mapper::count(const Criteria &criteria) noexcept(false) { std::string sql = "select count(*) from "; sql += T::tableName; if (criteria) { sql += " where "; sql += criteria.criteriaString(); sql = replaceSqlPlaceHolder(sql, "$?"); } clear(); Result r(nullptr); { auto binder = *client_ << std::move(sql); if (criteria) criteria.outputArgs(binder); binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // exec may be throw exception; } assert(r.size() == 1); return r[0][(Row::SizeType)0].as(); } template inline void Mapper::count(const Criteria &criteria, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept { std::string sql = "select count(*) from "; sql += T::tableName; if (criteria) { sql += " where "; sql += criteria.criteriaString(); sql = replaceSqlPlaceHolder(sql, "$?"); } clear(); auto binder = *client_ << std::move(sql); if (criteria) criteria.outputArgs(binder); binder >> [rcb](const Result &r) { assert(r.size() == 1); rcb(r[0][(Row::SizeType)0].as()); }; binder >> ecb; } template inline std::future Mapper::countFuture( const Criteria &criteria) noexcept { std::string sql = "select count(*) from "; sql += T::tableName; if (criteria) { sql += " where "; sql += criteria.criteriaString(); sql = replaceSqlPlaceHolder(sql, "$?"); } clear(); auto binder = *client_ << std::move(sql); if (criteria) criteria.outputArgs(binder); std::shared_ptr> prom = std::make_shared>(); binder >> [prom](const Result &r) { assert(r.size() == 1); prom->set_value(r[0][(Row::SizeType)0].as()); }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } template inline void Mapper::insert(T &obj) noexcept(false) { clear(); Result r(nullptr); bool needSelection = false; { auto binder = *client_ << obj.sqlForInserting(needSelection); obj.outputArgs(binder); binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // Maybe throw exception; } assert(r.affectedRows() == 1); if (client_->type() == ClientType::PostgreSQL) { if (needSelection) { assert(r.size() == 1); obj = T(r[0]); } } else // Mysql or Sqlite3 { auto id = r.insertId(); obj.updateId(id); if (needSelection) { obj = findByPrimaryKey(obj.getPrimaryKey()); } } } template inline void Mapper::insert(const T &obj, const SingleRowCallback &rcb, const ExceptionCallback &ecb) noexcept { clear(); bool needSelection = false; auto binder = *client_ << obj.sqlForInserting(needSelection); obj.outputArgs(binder); auto client = client_; binder >> [client, rcb, obj, needSelection, ecb](const Result &r) { assert(r.affectedRows() == 1); if (client->type() == ClientType::PostgreSQL) { if (needSelection) { assert(r.size() == 1); rcb(T(r[0])); } else { rcb(obj); } } else // Mysql or Sqlite3 { auto id = r.insertId(); auto newObj = obj; newObj.updateId(id); if (needSelection) { auto tmp = Mapper(client); tmp.findByPrimaryKey(newObj.getPrimaryKey(), rcb, ecb); } else { rcb(newObj); } } }; binder >> ecb; } template inline std::future Mapper::insertFuture(const T &obj) noexcept { clear(); bool needSelection = false; auto binder = *client_ << obj.sqlForInserting(needSelection); obj.outputArgs(binder); std::shared_ptr> prom = std::make_shared>(); auto client = client_; binder >> [client, prom, obj, needSelection](const Result &r) { assert(r.affectedRows() == 1); if (client->type() == ClientType::PostgreSQL) { if (needSelection) { assert(r.size() == 1); prom->set_value(T(r[0])); } else { prom->set_value(obj); } } else // Mysql or Sqlite3 { auto id = r.insertId(); auto newObj = obj; newObj.updateId(id); if (needSelection) { auto tmp = Mapper(client); tmp.findByPrimaryKey( newObj.getPrimaryKey(), [prom](T selObj) { prom->set_value(selObj); }, [prom](const DrogonDbException &e) { prom->set_exception( std::make_exception_ptr(Failure(e.base().what()))); }); } else { prom->set_value(newObj); } } }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } template inline size_t Mapper::update(const T &obj) noexcept(false) { clear(); static_assert(!std::is_same::value, "No primary key in the table!"); std::string sql = "update "; sql += T::tableName; sql += " set "; for (auto const &colName : obj.updateColumns()) { sql += colName; sql += " = $?,"; } sql[sql.length() - 1] = ' '; // Replace the last ',' makePrimaryKeyCriteria(sql); sql = replaceSqlPlaceHolder(sql, "$?"); Result r(nullptr); { auto binder = *client_ << std::move(sql); obj.updateArgs(binder); outputPrimeryKeyToBinder(obj.getPrimaryKey(), binder); binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // Maybe throw exception; } return r.affectedRows(); } template template size_t Mapper::updateBy(const std::vector &colNames, const Criteria &criteria, Arguments &&...args) noexcept(false) { static_assert(sizeof...(args) > 0); assert(colNames.size() == sizeof...(args)); clear(); std::string sql = "update "; sql += T::tableName; sql += " set "; for (auto const &colName : colNames) { sql += colName; sql += " = $?,"; } sql[sql.length() - 1] = ' '; // Replace the last ',' if (criteria) { sql += " where "; sql += criteria.criteriaString(); } sql = replaceSqlPlaceHolder(sql, "$?"); Result r(nullptr); { auto binder = *client_ << std::move(sql); (void)std::initializer_list{ (binder << std::forward(args), 0)...}; if (criteria) criteria.outputArgs(binder); binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // Maybe throw exception; } return r.affectedRows(); } template inline void Mapper::update(const T &obj, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept { clear(); static_assert(!std::is_same::value, "No primary key in the table!"); std::string sql = "update "; sql += T::tableName; sql += " set "; for (auto const &colName : obj.updateColumns()) { sql += colName; sql += " = $?,"; } sql[sql.length() - 1] = ' '; // Replace the last ',' makePrimaryKeyCriteria(sql); sql = replaceSqlPlaceHolder(sql, "$?"); auto binder = *client_ << std::move(sql); obj.updateArgs(binder); outputPrimeryKeyToBinder(obj.getPrimaryKey(), binder); binder >> [rcb](const Result &r) { rcb(r.affectedRows()); }; binder >> ecb; } template template void Mapper::updateBy(const std::vector &colNames, const CountCallback &rcb, const ExceptionCallback &ecb, const Criteria &criteria, Arguments &&...args) noexcept { static_assert(sizeof...(args) > 0); assert(colNames.size() == sizeof...(args)); clear(); std::string sql = "update "; sql += T::tableName; sql += " set "; for (auto const &colName : colNames) { sql += colName; sql += " = $?,"; } sql[sql.length() - 1] = ' '; // Replace the last ',' if (criteria) { sql += " where "; sql += criteria.criteriaString(); } sql = replaceSqlPlaceHolder(sql, "$?"); auto binder = *client_ << std::move(sql); (void)std::initializer_list{ (binder << std::forward(args), 0)...}; if (criteria) criteria.outputArgs(binder); binder >> [rcb](const Result &r) { rcb(r.affectedRows()); }; binder >> ecb; } template inline std::future Mapper::updateFuture(const T &obj) noexcept { clear(); static_assert(!std::is_same::value, "No primary key in the table!"); std::string sql = "update "; sql += T::tableName; sql += " set "; for (auto const &colName : obj.updateColumns()) { sql += colName; sql += " = $?,"; } sql[sql.length() - 1] = ' '; // Replace the last ',' makePrimaryKeyCriteria(sql); sql = replaceSqlPlaceHolder(sql, "$?"); auto binder = *client_ << std::move(sql); obj.updateArgs(binder); outputPrimeryKeyToBinder(obj.getPrimaryKey(), binder); std::shared_ptr> prom = std::make_shared>(); binder >> [prom](const Result &r) { prom->set_value(r.affectedRows()); }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } template template inline std::future Mapper::updateFutureBy( const std::vector &colNames, const Criteria &criteria, Arguments &&...args) noexcept { static_assert(sizeof...(args) > 0); assert(colNames.size() == sizeof...(args)); clear(); std::string sql = "update "; sql += T::tableName; sql += " set "; for (auto const &colName : colNames) { sql += colName; sql += " = $?,"; } sql[sql.length() - 1] = ' '; // Replace the last ',' if (criteria) { sql += " where "; sql += criteria.criteriaString(); } sql = replaceSqlPlaceHolder(sql, "$?"); auto binder = *client_ << std::move(sql); (void)std::initializer_list{ (binder << std::forward(args), 0)...}; if (criteria) criteria.outputArgs(binder); std::shared_ptr> prom = std::make_shared>(); binder >> [prom](const Result &r) { prom->set_value(r.affectedRows()); }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } template inline size_t Mapper::deleteOne(const T &obj) noexcept(false) { clear(); static_assert(!std::is_same::value, "No primary key in the table!"); std::string sql = "delete from "; sql += T::tableName; sql += " "; // Replace the last ',' makePrimaryKeyCriteria(sql); sql = replaceSqlPlaceHolder(sql, "$?"); Result r(nullptr); { auto binder = *client_ << std::move(sql); outputPrimeryKeyToBinder(obj.getPrimaryKey(), binder); binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // Maybe throw exception; } return r.affectedRows(); } template inline void Mapper::deleteOne(const T &obj, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept { clear(); static_assert(!std::is_same::value, "No primary key in the table!"); std::string sql = "delete from "; sql += T::tableName; sql += " "; makePrimaryKeyCriteria(sql); sql = replaceSqlPlaceHolder(sql, "$?"); auto binder = *client_ << std::move(sql); outputPrimeryKeyToBinder(obj.getPrimaryKey(), binder); binder >> [rcb](const Result &r) { rcb(r.affectedRows()); }; binder >> ecb; } template inline std::future Mapper::deleteFutureOne(const T &obj) noexcept { clear(); static_assert(!std::is_same::value, "No primary key in the table!"); std::string sql = "delete from "; sql += T::tableName; sql += " "; makePrimaryKeyCriteria(sql); sql = replaceSqlPlaceHolder(sql, "$?"); auto binder = *client_ << std::move(sql); outputPrimeryKeyToBinder(obj.getPrimaryKey(), binder); std::shared_ptr> prom = std::make_shared>(); binder >> [prom](const Result &r) { prom->set_value(r.affectedRows()); }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } template inline size_t Mapper::deleteBy(const Criteria &criteria) noexcept(false) { clear(); static_assert(!std::is_same::value, "No primary key in the table!"); std::string sql = "delete from "; sql += T::tableName; if (criteria) { sql += " where "; sql += criteria.criteriaString(); sql = replaceSqlPlaceHolder(sql, "$?"); } Result r(nullptr); { auto binder = *client_ << std::move(sql); if (criteria) { criteria.outputArgs(binder); } binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // Maybe throw exception; } return r.affectedRows(); } template inline void Mapper::deleteBy(const Criteria &criteria, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept { clear(); static_assert(!std::is_same::value, "No primary key in the table!"); std::string sql = "delete from "; sql += T::tableName; if (criteria) { sql += " where "; sql += criteria.criteriaString(); sql = replaceSqlPlaceHolder(sql, "$?"); } auto binder = *client_ << std::move(sql); if (criteria) { criteria.outputArgs(binder); } binder >> [rcb](const Result &r) { rcb(r.affectedRows()); }; binder >> ecb; } template inline std::future Mapper::deleteFutureBy( const Criteria &criteria) noexcept { clear(); static_assert(!std::is_same::value, "No primary key in the table!"); std::string sql = "delete from "; sql += T::tableName; if (criteria) { sql += " where "; sql += criteria.criteriaString(); sql = replaceSqlPlaceHolder(sql, "$?"); } auto binder = *client_ << std::move(sql); if (criteria) { criteria.outputArgs(binder); } std::shared_ptr> prom = std::make_shared>(); binder >> [prom](const Result &r) { prom->set_value(r.affectedRows()); }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } template inline Mapper &Mapper::limit(size_t limit) { assert(limit > 0); limit_ = limit; return *this; } template inline Mapper &Mapper::offset(size_t offset) { offset_ = offset; return *this; } template inline Mapper &Mapper::orderBy(const std::string &colName, const SortOrder &order) { if (orderByString_.empty()) { orderByString_ = utils::formattedString(" order by %s", colName.c_str()); if (order == SortOrder::DESC) { orderByString_ += " desc"; } } else { orderByString_ += ","; orderByString_ += colName; if (order == SortOrder::DESC) { orderByString_ += " desc"; } } return *this; } template inline Mapper &Mapper::orderBy(size_t colIndex, const SortOrder &order) { std::string colName = T::getColumnName(colIndex); assert(!colName.empty()); return orderBy(colName, order); } template inline Mapper &Mapper::paginate(size_t page, size_t perPage) { assert(page > 0 && perPage > 0); return limit(perPage).offset((page - 1) * perPage); } template inline Mapper &Mapper::forUpdate() { forUpdate_ = true; return *this; } template inline std::string Mapper::replaceSqlPlaceHolder( const std::string &sqlStr, const std::string &holderStr) const { if (client_->type() == ClientType::PostgreSQL) { std::string::size_type startPos = 0; std::string::size_type pos; std::stringstream ret; size_t phCount = 1; do { pos = sqlStr.find(holderStr, startPos); if (pos == std::string::npos) { ret << sqlStr.substr(startPos); return ret.str(); } ret << sqlStr.substr(startPos, pos - startPos); ret << "$"; ret << phCount++; startPos = pos + holderStr.length(); } while (1); } else if (client_->type() == ClientType::Mysql || client_->type() == ClientType::Sqlite3) { std::string::size_type startPos = 0; std::string::size_type pos; std::stringstream ret; do { pos = sqlStr.find(holderStr, startPos); if (pos == std::string::npos) { ret << sqlStr.substr(startPos); return ret.str(); } ret << sqlStr.substr(startPos, pos - startPos); ret << "?"; startPos = pos + holderStr.length(); } while (1); } else { return sqlStr; } } template inline size_t Mapper::deleteByPrimaryKey( const typename Mapper::TraitsPKType &key) noexcept(false) { static_assert(!std::is_same::value, "No primary key in the table!"); static_assert(internal::has_sqlForDeletingByPrimaryKey::value, "No function member named sqlForDeletingByPrimaryKey, please " "make sure that the model class is generated by the latest " "version of drogon_ctl"); clear(); Result r(nullptr); { auto binder = *client_ << T::sqlForDeletingByPrimaryKey(); outputPrimeryKeyToBinder(key, binder); binder << Mode::Blocking; binder >> [&r](const Result &result) { r = result; }; binder.exec(); // exec may be throw exception; } return r.affectedRows(); } template inline void Mapper::deleteByPrimaryKey( const typename Mapper::TraitsPKType &key, const CountCallback &rcb, const ExceptionCallback &ecb) noexcept { static_assert(!std::is_same::value, "No primary key in the table!"); static_assert(internal::has_sqlForDeletingByPrimaryKey::value, "No function member named sqlForDeletingByPrimaryKey, please " "make sure that the model class is generated by the latest " "version of drogon_ctl"); clear(); auto binder = *client_ << T::sqlForDeletingByPrimaryKey(); outputPrimeryKeyToBinder(key, binder); binder >> [rcb = std::move(rcb)](const Result &r) { rcb(r.affectedRows()); }; binder >> ecb; } template inline std::future Mapper::deleteFutureByPrimaryKey( const typename Mapper::TraitsPKType &key) noexcept { static_assert(!std::is_same::value, "No primary key in the table!"); static_assert(internal::has_sqlForDeletingByPrimaryKey::value, "No function member named sqlForDeletingByPrimaryKey, please " "make sure that the model class is generated by the latest " "version of drogon_ctl"); clear(); auto binder = *client_ << T::sqlForDeletingByPrimaryKey(); outputPrimeryKeyToBinder(key, binder); std::shared_ptr> prom = std::make_shared>(); binder >> [prom](const Result &r) { prom->set_value(r.affectedRows()); }; binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); }; binder.exec(); return prom->get_future(); } /** * @brief Convert std::vector where Value can be JSON to JSON. * * @param container The container to be converted. * @return Json::Value The JSON value converted. */ template inline Json::Value toJson(const std::vector &container) { Json::Value values(Json::arrayValue); for (const Value &c : container) { values.append(c.toJson()); } return values; } } // namespace orm } // namespace drogon