#pragma once
#include <drogon/orm/Criteria.h>
#include <drogon/orm/DbClient.h>
#include <drogon/utils/Utilities.h>
#include <string>
#include <type_traits>
#include <vector>
#ifdef _WIN32
using ssize_t = std::intptr_t;
#endif
namespace drogon
{
namespace orm
{
enum class SortOrder
{
    ASC,
    DESC
};
namespace internal
{
template <typename T, bool hasPrimaryKey = true>
struct Traits
{
    using type = typename T::PrimaryKeyType;
};
template <typename T>
struct Traits<T, false>
{
    using type = int;
};
template <typename T>
struct has_sqlForFindingByPrimaryKey
{
  private:
    using yes = std::true_type;
    using no = std::false_type;
    template <typename U>
    static auto test(int) -> decltype(U::sqlForFindingByPrimaryKey(), yes());
    template <typename>
    static no test(...);
  public:
    static constexpr bool value = std::is_same_v<decltype(test<T>(0)), yes>;
};
template <typename T>
struct has_sqlForDeletingByPrimaryKey
{
  private:
    using yes = std::true_type;
    using no = std::false_type;
    template <typename U>
    static auto test(int)
        -> decltype(U::sqlForDeletingByPrimaryKey().length(), yes());
    template <typename>
    static no test(...);
  public:
    static constexpr bool value = std::is_same_v<decltype(test<T>(0)), yes>;
};
}  
template <typename T>
class Mapper
{
  public:
    explicit Mapper(DbClientPtr client) : client_(std::move(client))
    {
    }
    Mapper<T> &limit(size_t limit);
    Mapper<T> &offset(size_t offset);
    Mapper<T> &orderBy(const std::string &colName,
                       const SortOrder &order = SortOrder::ASC);
    Mapper<T> &orderBy(size_t colIndex,
                       const SortOrder &order = SortOrder::ASC);
    Mapper<T> &paginate(size_t page, size_t perPage);
    Mapper<T> &forUpdate();
    using SingleRowCallback = std::function<void(T)>;
    using MultipleRowsCallback = std::function<void(std::vector<T>)>;
    using CountCallback = std::function<void(const size_t)>;
    using TraitsPKType = typename internal::
        Traits<T, !std::is_same_v<typename T::PrimaryKeyType, void>>::type;
    template <typename U = T>
    inline T findByPrimaryKey(const TraitsPKType &key) noexcept(false)
    {
        if constexpr (!std::is_same_v<typename U::PrimaryKeyType, void>)
        {
            static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                          "No primary key in the table!");
            static_assert(
                internal::has_sqlForFindingByPrimaryKey<T>::value,
                "No function member named sqlForFindingByPrimaryKey, please "
                "make sure that the model class is generated by the latest "
                "version of drogon_ctl");
            std::string sql = T::sqlForFindingByPrimaryKey();
            if (forUpdate_)
            {
                sql += " for update";
            }
            clear();
            Result r(nullptr);
            {
                auto binder = *client_ << std::move(sql);
                outputPrimaryKeyToBinder(key, binder);
                binder << Mode::Blocking;
                binder >> [&r](const Result &result) { r = result; };
                binder.exec();  
            }
            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);
        }
        else
        {
            LOG_FATAL << "The table must have a primary key";
            abort();
        }
    }
    template <typename U = T>
    inline void findByPrimaryKey(const TraitsPKType &key,
                                 const SingleRowCallback &rcb,
                                 const ExceptionCallback &ecb) noexcept
    {
        if constexpr (!std::is_same_v<typename U::PrimaryKeyType, void>)
        {
            static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                          "No primary key in the table!");
            static_assert(
                internal::has_sqlForFindingByPrimaryKey<T>::value,
                "No function member named sqlForFindingByPrimaryKey, please "
                "make sure that the model class is generated by the latest "
                "version of drogon_ctl");
            std::string sql = T::sqlForFindingByPrimaryKey();
            if (forUpdate_)
            {
                sql += " for update";
            }
            clear();
            auto binder = *client_ << std::move(sql);
            outputPrimaryKeyToBinder(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;
        }
        else
        {
            LOG_FATAL << "The table must have a primary key";
            abort();
        }
    }
    template <typename U = T>
    inline std::future<T> findFutureByPrimaryKey(
        const TraitsPKType &key) noexcept
    {
        if constexpr (!std::is_same_v<typename U::PrimaryKeyType, void>)
        {
            static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                          "No primary key in the table!");
            static_assert(
                internal::has_sqlForFindingByPrimaryKey<T>::value,
                "No function member named sqlForFindingByPrimaryKey, please "
                "make sure that the model class is generated by the latest "
                "version of drogon_ctl");
            std::string sql = T::sqlForFindingByPrimaryKey();
            if (forUpdate_)
            {
                sql += " for update";
            }
            clear();
            auto binder = *client_ << std::move(sql);
            outputPrimaryKeyToBinder(key, binder);
            std::shared_ptr<std::promise<T>> prom =
                std::make_shared<std::promise<T>>();
            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();
        }
        else
        {
            LOG_FATAL << "The table must have a primary key";
            abort();
        }
    }
    std::vector<T> findAll() noexcept(false);
    void findAll(const MultipleRowsCallback &rcb,
                 const ExceptionCallback &ecb) noexcept;
    std::future<std::vector<T>> findFutureAll() noexcept;
    size_t count(const Criteria &criteria = Criteria()) noexcept(false);
    void count(const Criteria &criteria,
               const CountCallback &rcb,
               const ExceptionCallback &ecb) noexcept;
    std::future<size_t> countFuture(
        const Criteria &criteria = Criteria()) noexcept;
    T findOne(const Criteria &criteria) noexcept(false);
    void findOne(const Criteria &criteria,
                 const SingleRowCallback &rcb,
                 const ExceptionCallback &ecb) noexcept;
    std::future<T> findFutureOne(const Criteria &criteria) noexcept;
    std::vector<T> findBy(const Criteria &criteria) noexcept(false);
    void findBy(const Criteria &criteria,
                const MultipleRowsCallback &rcb,
                const ExceptionCallback &ecb) noexcept;
    std::future<std::vector<T>> findFutureBy(const Criteria &criteria) noexcept;
    void insert(T &obj) noexcept(false);
    void insert(const T &obj,
                const SingleRowCallback &rcb,
                const ExceptionCallback &ecb) noexcept;
    std::future<T> insertFuture(const T &) noexcept;
    size_t update(const T &obj) noexcept(false);
    void update(const T &obj,
                const CountCallback &rcb,
                const ExceptionCallback &ecb) noexcept;
    std::future<size_t> updateFuture(const T &obj) noexcept;
    template <typename... Arguments>
    size_t updateBy(const std::vector<std::string> &colNames,
                    const Criteria &criteria,
                    Arguments &&...args) noexcept(false);
    template <typename... Arguments>
    void updateBy(const std::vector<std::string> &colNames,
                  const CountCallback &rcb,
                  const ExceptionCallback &ecb,
                  const Criteria &criteria,
                  Arguments &&...args) noexcept;
    template <typename... Arguments>
    std::future<size_t> updateFutureBy(const std::vector<std::string> &colNames,
                                       const Criteria &criteria,
                                       Arguments &&...args) noexcept;
    template <typename... Arguments>
    size_t increment(const std::vector<std::string> &colNames,
                     const Criteria &criteria,
                     Arguments... args) noexcept(false);
    template <typename... Arguments>
    void increment(const std::vector<std::string> &colNames,
                   const CountCallback &rcb,
                   const ExceptionCallback &ecb,
                   const Criteria &criteria,
                   Arguments... args) noexcept;
    template <typename... Arguments>
    std::future<size_t> incrementFuture(
        const std::vector<std::string> &colNames,
        const Criteria &criteria,
        Arguments... args) noexcept;
    size_t deleteOne(const T &obj) noexcept(false);
    void deleteOne(const T &obj,
                   const CountCallback &rcb,
                   const ExceptionCallback &ecb) noexcept;
    std::future<size_t> deleteFutureOne(const T &obj) noexcept;
    size_t deleteBy(const Criteria &criteria) noexcept(false);
    void deleteBy(const Criteria &criteria,
                  const CountCallback &rcb,
                  const ExceptionCallback &ecb) noexcept;
    std::future<size_t> deleteFutureBy(const Criteria &criteria) noexcept;
    size_t deleteByPrimaryKey(const TraitsPKType &key) noexcept(false);
    void deleteByPrimaryKey(const TraitsPKType &key,
                            const CountCallback &rcb,
                            const ExceptionCallback &ecb) noexcept;
    std::future<size_t> 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 PKType = decltype(T::primaryKeyName)>
    void makePrimaryKeyCriteria(std::string &sql)
    {
        if constexpr (std::is_same_v<const std::string, PKType>)
        {
            sql += " where ";
            sql += T::primaryKeyName;
            sql += " = $?";
        }
        else if constexpr (std::is_same_v<const std::vector<std::string>,
                                          PKType>)
        {
            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 PKType = decltype(T::primaryKeyName)>
    void outputPrimaryKeyToBinder(const TraitsPKType &pk,
                                  internal::SqlBinder &binder)
    {
        if constexpr (std::is_same_v<const std::string, PKType>)
        {
            binder << pk;
        }
        else if constexpr (std::is_same_v<const std::vector<std::string>,
                                          PKType>)
        {
            tupleToBinder<typename T::PrimaryKeyType>(pk, binder);
        }
    }
    template <typename TP, ssize_t N = std::tuple_size<TP>::value>
    void tupleToBinder(const TP &t, internal::SqlBinder &binder)
    {
        if constexpr (N > 1)
        {
            tupleToBinder<TP, N - 1>(t, binder);
            binder << std::get<N - 1>(t);
        }
        else if constexpr (N == 1)
        {
            binder << std::get<0>(t);
        }
    }
    std::string replaceSqlPlaceHolder(const std::string &sqlStr,
                                      const std::string &holderStr) const;
};
template <typename T>
inline T Mapper<T>::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();  
    }
    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 <typename T>
inline void Mapper<T>::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 <typename T>
inline std::future<T> Mapper<T>::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<std::promise<T>> prom = std::make_shared<std::promise<T>>();
    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 <typename T>
inline std::vector<T> Mapper<T>::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();  
    }
    std::vector<T> ret;
    for (auto const &row : r)
    {
        ret.push_back(T(row));
    }
    return ret;
}
template <typename T>
inline void Mapper<T>::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<T> ret;
        for (auto const &row : r)
        {
            ret.emplace_back(row);
        }
        rcb(std::move(ret));
    };
    binder >> ecb;
}
template <typename T>
inline std::future<std::vector<T>> Mapper<T>::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<std::promise<std::vector<T>>> prom =
        std::make_shared<std::promise<std::vector<T>>>();
    binder >> [prom](const Result &r) {
        std::vector<T> 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 <typename T>
inline std::vector<T> Mapper<T>::findAll() noexcept(false)
{
    return findBy(Criteria());
}
template <typename T>
inline void Mapper<T>::findAll(const MultipleRowsCallback &rcb,
                               const ExceptionCallback &ecb) noexcept
{
    findBy(Criteria(), rcb, ecb);
}
template <typename T>
inline std::future<std::vector<T>> Mapper<T>::findFutureAll() noexcept
{
    return findFutureBy(Criteria());
}
template <typename T>
inline size_t Mapper<T>::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();  
    }
    assert(r.size() == 1);
    return r[0][(Row::SizeType)0].as<size_t>();
}
template <typename T>
inline void Mapper<T>::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<size_t>());
    };
    binder >> ecb;
}
template <typename T>
inline std::future<size_t> Mapper<T>::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<std::promise<size_t>> prom =
        std::make_shared<std::promise<size_t>>();
    binder >> [prom](const Result &r) {
        assert(r.size() == 1);
        prom->set_value(r[0][(Row::SizeType)0].as<size_t>());
    };
    binder >> [prom](const std::exception_ptr &e) { prom->set_exception(e); };
    binder.exec();
    return prom->get_future();
}
template <typename T>
inline void Mapper<T>::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();  
    }
    assert(r.affectedRows() == 1);
    if (client_->type() == ClientType::PostgreSQL)
    {
        if (needSelection)
        {
            assert(r.size() == 1);
            obj = T(r[0]);
        }
    }
    else  
    {
        auto id = r.insertId();
        obj.updateId(id);
        if (needSelection)
        {
            obj = findByPrimaryKey(obj.getPrimaryKey());
        }
    }
}
template <typename T>
inline void Mapper<T>::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  
        {
            auto id = r.insertId();
            auto newObj = obj;
            newObj.updateId(id);
            if (needSelection)
            {
                auto tmp = Mapper<T>(client);
                tmp.findByPrimaryKey(newObj.getPrimaryKey(), rcb, ecb);
            }
            else
            {
                rcb(newObj);
            }
        }
    };
    binder >> ecb;
}
template <typename T>
inline std::future<T> Mapper<T>::insertFuture(const T &obj) noexcept
{
    clear();
    bool needSelection = false;
    auto binder = *client_ << obj.sqlForInserting(needSelection);
    obj.outputArgs(binder);
    std::shared_ptr<std::promise<T>> prom = std::make_shared<std::promise<T>>();
    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  
        {
            auto id = r.insertId();
            auto newObj = obj;
            newObj.updateId(id);
            if (needSelection)
            {
                auto tmp = Mapper<T>(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 <typename T>
inline size_t Mapper<T>::update(const T &obj) noexcept(false)
{
    clear();
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "No primary key in the table!");
    std::vector<std::string> colNames = obj.updateColumns();
    if (colNames.empty())
    {
        return 0;
    }
    std::string sql = "update ";
    sql += T::tableName;
    sql += " set ";
    for (auto const &colName : colNames)
    {
        sql += colName;
        sql += " = $?,";
    }
    sql[sql.length() - 1] = ' ';  
    makePrimaryKeyCriteria(sql);
    sql = replaceSqlPlaceHolder(sql, "$?");
    Result r(nullptr);
    {
        auto binder = *client_ << std::move(sql);
        obj.updateArgs(binder);
        outputPrimaryKeyToBinder(obj.getPrimaryKey(), binder);
        binder << Mode::Blocking;
        binder >> [&r](const Result &result) { r = result; };
        binder.exec();  
    }
    return r.affectedRows();
}
template <typename T>
template <typename... Arguments>
size_t Mapper<T>::updateBy(const std::vector<std::string> &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] = ' ';  
    if (criteria)
    {
        sql += " where ";
        sql += criteria.criteriaString();
    }
    sql = replaceSqlPlaceHolder(sql, "$?");
    Result r(nullptr);
    {
        auto binder = *client_ << std::move(sql);
        (void)std::initializer_list<int>{
            (binder << std::forward<Arguments>(args), 0)...};
        if (criteria)
            criteria.outputArgs(binder);
        binder << Mode::Blocking;
        binder >> [&r](const Result &result) { r = result; };
        binder.exec();  
    }
    return r.affectedRows();
}
template <typename T>
inline void Mapper<T>::update(const T &obj,
                              const CountCallback &rcb,
                              const ExceptionCallback &ecb) noexcept
{
    clear();
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "No primary key in the table!");
    std::vector<std::string> colNames = obj.updateColumns();
    if (colNames.empty())
    {
        rcb(0);
        return;
    }
    std::string sql = "update ";
    sql += T::tableName;
    sql += " set ";
    for (auto const &colName : colNames)
    {
        sql += colName;
        sql += " = $?,";
    }
    sql[sql.length() - 1] = ' ';  
    makePrimaryKeyCriteria(sql);
    sql = replaceSqlPlaceHolder(sql, "$?");
    auto binder = *client_ << std::move(sql);
    obj.updateArgs(binder);
    outputPrimaryKeyToBinder(obj.getPrimaryKey(), binder);
    binder >> [rcb](const Result &r) { rcb(r.affectedRows()); };
    binder >> ecb;
}
template <typename T>
template <typename... Arguments>
void Mapper<T>::updateBy(const std::vector<std::string> &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] = ' ';  
    if (criteria)
    {
        sql += " where ";
        sql += criteria.criteriaString();
    }
    sql = replaceSqlPlaceHolder(sql, "$?");
    auto binder = *client_ << std::move(sql);
    (void)std::initializer_list<int>{
        (binder << std::forward<Arguments>(args), 0)...};
    if (criteria)
        criteria.outputArgs(binder);
    binder >> [rcb](const Result &r) { rcb(r.affectedRows()); };
    binder >> ecb;
}
template <typename T>
inline std::future<size_t> Mapper<T>::updateFuture(const T &obj) noexcept
{
    clear();
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "No primary key in the table!");
    std::vector<std::string> colNames = obj.updateColumns();
    if (colNames.empty())
    {
        std::shared_ptr<std::promise<size_t>> prom =
            std::make_shared<std::promise<size_t>>();
        prom->set_value(0);
        return prom->get_future();
    }
    std::string sql = "update ";
    sql += T::tableName;
    sql += " set ";
    for (auto const &colName : colNames)
    {
        sql += colName;
        sql += " = $?,";
    }
    sql[sql.length() - 1] = ' ';  
    makePrimaryKeyCriteria(sql);
    sql = replaceSqlPlaceHolder(sql, "$?");
    auto binder = *client_ << std::move(sql);
    obj.updateArgs(binder);
    outputPrimaryKeyToBinder(obj.getPrimaryKey(), binder);
    std::shared_ptr<std::promise<size_t>> prom =
        std::make_shared<std::promise<size_t>>();
    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 <typename T>
template <typename... Arguments>
inline std::future<size_t> Mapper<T>::updateFutureBy(
    const std::vector<std::string> &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] = ' ';  
    if (criteria)
    {
        sql += " where ";
        sql += criteria.criteriaString();
    }
    sql = replaceSqlPlaceHolder(sql, "$?");
    auto binder = *client_ << std::move(sql);
    (void)std::initializer_list<int>{
        (binder << std::forward<Arguments>(args), 0)...};
    if (criteria)
        criteria.outputArgs(binder);
    std::shared_ptr<std::promise<size_t>> prom =
        std::make_shared<std::promise<size_t>>();
    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 <typename T>
template <typename... Arguments>
inline size_t Mapper<T>::increment(const std::vector<std::string> &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 ";
    std::vector<const char *> temps;
    (void)std::initializer_list<int>{(
        [&args, &temps] {
            args = (args < 0) ? (temps.push_back(" - $?,"), -args)
                              : (temps.push_back(" + $?,"), args);
        }(),
        0)...};
    for (int i = 0; i < sizeof...(args); ++i)
    {
        const auto &colName = colNames[i];
        sql += colName;
        sql += " = ";
        sql += colName;
        sql += temps[i];
    }
    sql[sql.length() - 1] = ' ';  
    if (criteria)
    {
        sql += " where ";
        sql += criteria.criteriaString();
    }
    sql = replaceSqlPlaceHolder(sql, "$?");
    Result r(nullptr);
    {
        auto binder = *client_ << std::move(sql);
        (void)std::initializer_list<int>{
            (binder << std::forward<Arguments>(args), 0)...};
        if (criteria)
            criteria.outputArgs(binder);
        binder << Mode::Blocking;
        binder >> [&r](const Result &result) { r = result; };
        binder.exec();  
    }
    return r.affectedRows();
}
template <typename T>
template <typename... Arguments>
inline void Mapper<T>::increment(const std::vector<std::string> &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 ";
    std::vector<const char *> temps;
    (void)std::initializer_list<int>{(
        [&args, &temps] {
            args = args < 0 ? (temps.push_back(" - $?,"), -args)
                            : (temps.push_back(" + $?,"), args);
        }(),
        0)...};
    for (int i = 0; i < sizeof...(args); ++i)
    {
        const auto &colName = colNames[i];
        sql += colName;
        sql += " = ";
        sql += colName;
        sql += temps[i];
    }
    sql[sql.length() - 1] = ' ';  
    if (criteria)
    {
        sql += " where ";
        sql += criteria.criteriaString();
    }
    sql = replaceSqlPlaceHolder(sql, "$?");
    auto binder = *client_ << std::move(sql);
    (void)std::initializer_list<int>{
        (binder << std::forward<Arguments>(args), 0)...};
    if (criteria)
        criteria.outputArgs(binder);
    binder >> [rcb](const Result &r) { rcb(r.affectedRows()); };
    binder >> ecb;
}
template <typename T>
template <typename... Arguments>
inline std::future<size_t> Mapper<T>::incrementFuture(
    const std::vector<std::string> &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 ";
    std::vector<const char *> temps;
    (void)std::initializer_list<int>{(
        [&args, &temps] {
            args = args < 0 ? (temps.push_back(" - $?,"), -args)
                            : (temps.push_back(" + $?,"), args);
        }(),
        0)...};
    for (int i = 0; i < sizeof...(args); ++i)
    {
        const auto &colName = colNames[i];
        sql += colName;
        sql += " = ";
        sql += colName;
        sql += temps[i];
    }
    sql[sql.length() - 1] = ' ';  
    if (criteria)
    {
        sql += " where ";
        sql += criteria.criteriaString();
    }
    sql = replaceSqlPlaceHolder(sql, "$?");
    auto binder = *client_ << std::move(sql);
    (void)std::initializer_list<int>{
        (binder << std::forward<Arguments>(args), 0)...};
    if (criteria)
        criteria.outputArgs(binder);
    std::shared_ptr<std::promise<size_t>> prom =
        std::make_shared<std::promise<size_t>>();
    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 <typename T>
inline size_t Mapper<T>::deleteOne(const T &obj) noexcept(false)
{
    clear();
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "No primary key in the table!");
    std::string sql = "delete from ";
    sql += T::tableName;
    sql += " ";  
    makePrimaryKeyCriteria(sql);
    sql = replaceSqlPlaceHolder(sql, "$?");
    Result r(nullptr);
    {
        auto binder = *client_ << std::move(sql);
        outputPrimaryKeyToBinder(obj.getPrimaryKey(), binder);
        binder << Mode::Blocking;
        binder >> [&r](const Result &result) { r = result; };
        binder.exec();  
    }
    return r.affectedRows();
}
template <typename T>
inline void Mapper<T>::deleteOne(const T &obj,
                                 const CountCallback &rcb,
                                 const ExceptionCallback &ecb) noexcept
{
    clear();
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "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);
    outputPrimaryKeyToBinder(obj.getPrimaryKey(), binder);
    binder >> [rcb](const Result &r) { rcb(r.affectedRows()); };
    binder >> ecb;
}
template <typename T>
inline std::future<size_t> Mapper<T>::deleteFutureOne(const T &obj) noexcept
{
    clear();
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "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);
    outputPrimaryKeyToBinder(obj.getPrimaryKey(), binder);
    std::shared_ptr<std::promise<size_t>> prom =
        std::make_shared<std::promise<size_t>>();
    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 <typename T>
inline size_t Mapper<T>::deleteBy(const Criteria &criteria) noexcept(false)
{
    clear();
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "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();  
    }
    return r.affectedRows();
}
template <typename T>
inline void Mapper<T>::deleteBy(const Criteria &criteria,
                                const CountCallback &rcb,
                                const ExceptionCallback &ecb) noexcept
{
    clear();
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "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 <typename T>
inline std::future<size_t> Mapper<T>::deleteFutureBy(
    const Criteria &criteria) noexcept
{
    clear();
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "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<std::promise<size_t>> prom =
        std::make_shared<std::promise<size_t>>();
    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 <typename T>
inline Mapper<T> &Mapper<T>::limit(size_t limit)
{
    assert(limit > 0);
    limit_ = limit;
    return *this;
}
template <typename T>
inline Mapper<T> &Mapper<T>::offset(size_t offset)
{
    offset_ = offset;
    return *this;
}
template <typename T>
inline Mapper<T> &Mapper<T>::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 <typename T>
inline Mapper<T> &Mapper<T>::orderBy(size_t colIndex, const SortOrder &order)
{
    std::string colName = T::getColumnName(colIndex);
    assert(!colName.empty());
    return orderBy(colName, order);
}
template <typename T>
inline Mapper<T> &Mapper<T>::paginate(size_t page, size_t perPage)
{
    assert(page > 0 && perPage > 0);
    return limit(perPage).offset((page - 1) * perPage);
}
template <typename T>
inline Mapper<T> &Mapper<T>::forUpdate()
{
    forUpdate_ = true;
    return *this;
}
template <typename T>
inline std::string Mapper<T>::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 <typename T>
inline size_t Mapper<T>::deleteByPrimaryKey(
    const typename Mapper<T>::TraitsPKType &key) noexcept(false)
{
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "No primary key in the table!");
    static_assert(internal::has_sqlForDeletingByPrimaryKey<T>::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();
        outputPrimaryKeyToBinder(key, binder);
        binder << Mode::Blocking;
        binder >> [&r](const Result &result) { r = result; };
        binder.exec();  
    }
    return r.affectedRows();
}
template <typename T>
inline void Mapper<T>::deleteByPrimaryKey(
    const typename Mapper<T>::TraitsPKType &key,
    const CountCallback &rcb,
    const ExceptionCallback &ecb) noexcept
{
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "No primary key in the table!");
    static_assert(internal::has_sqlForDeletingByPrimaryKey<T>::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();
    outputPrimaryKeyToBinder(key, binder);
    binder >>
        [rcb = std::move(rcb)](const Result &r) { rcb(r.affectedRows()); };
    binder >> ecb;
}
template <typename T>
inline std::future<size_t> Mapper<T>::deleteFutureByPrimaryKey(
    const typename Mapper<T>::TraitsPKType &key) noexcept
{
    static_assert(!std::is_same_v<typename T::PrimaryKeyType, void>,
                  "No primary key in the table!");
    static_assert(internal::has_sqlForDeletingByPrimaryKey<T>::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();
    outputPrimaryKeyToBinder(key, binder);
    std::shared_ptr<std::promise<size_t>> prom =
        std::make_shared<std::promise<size_t>>();
    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 <typename Value>
inline Json::Value toJson(const std::vector<Value> &container)
{
    Json::Value values(Json::arrayValue);
    for (const Value &c : container)
    {
        values.append(c.toJson());
    }
    return values;
}
}  
}  