#pragma once #include #include #include #include #include #include #include #include #define MODERN_SQLITE_VERSION 3002008 #ifdef __has_include #if __cplusplus > 201402 && __has_include() #define MODERN_SQLITE_STD_OPTIONAL_SUPPORT #elif __has_include() #define MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT #endif #endif #ifdef __has_include #if __cplusplus > 201402 && __has_include() #define MODERN_SQLITE_STD_VARIANT_SUPPORT #endif #endif #ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT #include #endif #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT #include #define MODERN_SQLITE_STD_OPTIONAL_SUPPORT #endif #ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT #include #endif #include #include "sqlite_modern_cpp/errors.h" #include "sqlite_modern_cpp/utility/function_traits.h" #include "sqlite_modern_cpp/utility/uncaught_exceptions.h" #include "sqlite_modern_cpp/utility/utf16_utf8.h" #ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT #include "sqlite_modern_cpp/utility/variant.h" #endif namespace sqlite { // std::optional support for NULL values #ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT template using optional = std::experimental::optional; #else template using optional = std::optional; #endif #endif class database; class database_binder; template class binder; typedef std::shared_ptr connection_type; template::value == Element)> struct tuple_iterate { static void iterate(Tuple& t, database_binder& db) { get_col_from_db(db, Element, std::get(t)); tuple_iterate::iterate(t, db); } }; template struct tuple_iterate { static void iterate(Tuple&, database_binder&) {} }; class database_binder { public: // database_binder is not copyable database_binder() = delete; database_binder(const database_binder& other) = delete; database_binder& operator=(const database_binder&) = delete; database_binder(database_binder&& other) : _db(std::move(other._db)), _stmt(std::move(other._stmt)), _inx(other._inx), execution_started(other.execution_started) { } void execute() { _start_execute(); int hresult; while((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) {} if(hresult != SQLITE_DONE) { errors::throw_sqlite_error(hresult, sql()); } } std::string sql() { #if SQLITE_VERSION_NUMBER >= 3014000 auto sqlite_deleter = [](void *ptr) {sqlite3_free(ptr);}; std::unique_ptr str(sqlite3_expanded_sql(_stmt.get()), sqlite_deleter); return str ? str.get() : original_sql(); #else return original_sql(); #endif } std::string original_sql() { return sqlite3_sql(_stmt.get()); } void used(bool state) { if(!state) { // We may have to reset first if we haven't done so already: _next_index(); --_inx; } execution_started = state; } bool used() const { return execution_started; } private: std::shared_ptr _db; std::unique_ptr _stmt; utility::UncaughtExceptionDetector _has_uncaught_exception; int _inx; bool execution_started = false; int _next_index() { if(execution_started && !_inx) { sqlite3_reset(_stmt.get()); sqlite3_clear_bindings(_stmt.get()); } return ++_inx; } void _start_execute() { _next_index(); _inx = 0; used(true); } void _extract(std::function call_back) { int hresult; _start_execute(); while((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { call_back(); } if(hresult != SQLITE_DONE) { errors::throw_sqlite_error(hresult, sql()); } } void _extract_single_value(std::function call_back) { int hresult; _start_execute(); if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { call_back(); } else if(hresult == SQLITE_DONE) { throw errors::no_rows("no rows to extract: exactly 1 row expected", sql(), SQLITE_DONE); } if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { throw errors::more_rows("not all rows extracted", sql(), SQLITE_ROW); } if(hresult != SQLITE_DONE) { errors::throw_sqlite_error(hresult, sql()); } } sqlite3_stmt* _prepare(const std::u16string& sql) { return _prepare(utility::utf16_to_utf8(sql)); } sqlite3_stmt* _prepare(const std::string& sql) { int hresult; sqlite3_stmt* tmp = nullptr; const char *remaining; hresult = sqlite3_prepare_v2(_db.get(), sql.data(), -1, &tmp, &remaining); if(hresult != SQLITE_OK) errors::throw_sqlite_error(hresult, sql); if(!std::all_of(remaining, sql.data() + sql.size(), [](char ch) {return std::isspace(ch);})) throw errors::more_statements("Multiple semicolon separated statements are unsupported", sql); return tmp; } template struct is_sqlite_value : public std::integral_constant< bool, std::is_floating_point::value || std::is_integral::value || std::is_same::value || std::is_same::value || std::is_same::value > { }; template struct is_sqlite_value< std::vector > : public std::integral_constant< bool, std::is_floating_point::value || std::is_integral::value || std::is_same::value > { }; #ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT template struct is_sqlite_value< std::variant > : public std::integral_constant< bool, true > { }; #endif /* for vector support */ template friend database_binder& operator <<(database_binder& db, const std::vector& val); template friend void get_col_from_db(database_binder& db, int inx, std::vector& val); /* for nullptr & unique_ptr support */ friend database_binder& operator <<(database_binder& db, std::nullptr_t); template friend database_binder& operator <<(database_binder& db, const std::unique_ptr& val); template friend void get_col_from_db(database_binder& db, int inx, std::unique_ptr& val); #ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT template friend database_binder& operator <<(database_binder& db, const std::variant& val); template friend void get_col_from_db(database_binder& db, int inx, std::variant& val); #endif template friend T operator++(database_binder& db, int); // Overload instead of specializing function templates (http://www.gotw.ca/publications/mill17.htm) friend database_binder& operator<<(database_binder& db, const int& val); friend void get_col_from_db(database_binder& db, int inx, int& val); friend database_binder& operator <<(database_binder& db, const sqlite_int64& val); friend void get_col_from_db(database_binder& db, int inx, sqlite3_int64& i); friend database_binder& operator <<(database_binder& db, const float& val); friend void get_col_from_db(database_binder& db, int inx, float& f); friend database_binder& operator <<(database_binder& db, const double& val); friend void get_col_from_db(database_binder& db, int inx, double& d); friend void get_col_from_db(database_binder& db, int inx, std::string & s); friend database_binder& operator <<(database_binder& db, const std::string& txt); friend void get_col_from_db(database_binder& db, int inx, std::u16string & w); friend database_binder& operator <<(database_binder& db, const std::u16string& txt); #ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT template friend database_binder& operator <<(database_binder& db, const optional& val); template friend void get_col_from_db(database_binder& db, int inx, optional& o); #endif #ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT template friend database_binder& operator <<(database_binder& db, const boost::optional& val); template friend void get_col_from_db(database_binder& db, int inx, boost::optional& o); #endif public: database_binder(std::shared_ptr db, std::u16string const & sql): _db(db), _stmt(_prepare(sql), sqlite3_finalize), _inx(0) { } database_binder(std::shared_ptr db, std::string const & sql): _db(db), _stmt(_prepare(sql), sqlite3_finalize), _inx(0) { } ~database_binder() noexcept(false) { /* Will be executed if no >>op is found, but not if an exception is in mid flight */ if(!used() && !_has_uncaught_exception && _stmt) { execute(); } } template typename std::enable_if::value, void>::type operator>>( Result& value) { this->_extract_single_value([&value, this] { get_col_from_db(*this, 0, value); }); } template void operator>>(std::tuple&& values) { this->_extract_single_value([&values, this] { tuple_iterate>::iterate(values, *this); }); } template typename std::enable_if::value, void>::type operator>>( Function&& func) { typedef utility::function_traits traits; this->_extract([&func, this]() { binder::run(*this, func); }); } }; namespace sql_function_binder { template< typename ContextType, std::size_t Count, typename Functions > inline void step( sqlite3_context* db, int count, sqlite3_value** vals ); template< std::size_t Count, typename Functions, typename... Values > inline typename std::enable_if<(sizeof...(Values) && sizeof...(Values) < Count), void>::type step( sqlite3_context* db, int count, sqlite3_value** vals, Values&&... values ); template< std::size_t Count, typename Functions, typename... Values > inline typename std::enable_if<(sizeof...(Values) == Count), void>::type step( sqlite3_context* db, int, sqlite3_value**, Values&&... values ); template< typename ContextType, typename Functions > inline void final(sqlite3_context* db); template< std::size_t Count, typename Function, typename... Values > inline typename std::enable_if<(sizeof...(Values) < Count), void>::type scalar( sqlite3_context* db, int count, sqlite3_value** vals, Values&&... values ); template< std::size_t Count, typename Function, typename... Values > inline typename std::enable_if<(sizeof...(Values) == Count), void>::type scalar( sqlite3_context* db, int, sqlite3_value**, Values&&... values ); } enum class OpenFlags { READONLY = SQLITE_OPEN_READONLY, READWRITE = SQLITE_OPEN_READWRITE, CREATE = SQLITE_OPEN_CREATE, NOMUTEX = SQLITE_OPEN_NOMUTEX, FULLMUTEX = SQLITE_OPEN_FULLMUTEX, SHAREDCACHE = SQLITE_OPEN_SHAREDCACHE, PRIVATECACH = SQLITE_OPEN_PRIVATECACHE, URI = SQLITE_OPEN_URI }; inline OpenFlags operator|(const OpenFlags& a, const OpenFlags& b) { return static_cast(static_cast(a) | static_cast(b)); } enum class Encoding { ANY = SQLITE_ANY, UTF8 = SQLITE_UTF8, UTF16 = SQLITE_UTF16 }; struct sqlite_config { OpenFlags flags = OpenFlags::READWRITE | OpenFlags::CREATE; const char *zVfs = nullptr; Encoding encoding = Encoding::ANY; }; class database { protected: std::shared_ptr _db; public: database(const std::string &db_name, const sqlite_config &config = {}): _db(nullptr) { sqlite3* tmp = nullptr; auto ret = sqlite3_open_v2(db_name.data(), &tmp, static_cast(config.flags), config.zVfs); _db = std::shared_ptr(tmp, [=](sqlite3* ptr) { sqlite3_close_v2(ptr); }); // this will close the connection eventually when no longer needed. if(ret != SQLITE_OK) errors::throw_sqlite_error(_db ? sqlite3_extended_errcode(_db.get()) : ret); sqlite3_extended_result_codes(_db.get(), true); if(config.encoding == Encoding::UTF16) *this << R"(PRAGMA encoding = "UTF-16";)"; } database(const std::u16string &db_name, const sqlite_config &config = {}): _db(nullptr) { auto db_name_utf8 = utility::utf16_to_utf8(db_name); sqlite3* tmp = nullptr; auto ret = sqlite3_open_v2(db_name_utf8.data(), &tmp, static_cast(config.flags), config.zVfs); _db = std::shared_ptr(tmp, [=](sqlite3* ptr) { sqlite3_close_v2(ptr); }); // this will close the connection eventually when no longer needed. if(ret != SQLITE_OK) errors::throw_sqlite_error(_db ? sqlite3_extended_errcode(_db.get()) : ret); sqlite3_extended_result_codes(_db.get(), true); if(config.encoding != Encoding::UTF8) *this << R"(PRAGMA encoding = "UTF-16";)"; } database(std::shared_ptr db): _db(db) {} database_binder operator<<(const std::string& sql) { return database_binder(_db, sql); } database_binder operator<<(const char* sql) { return *this << std::string(sql); } database_binder operator<<(const std::u16string& sql) { return database_binder(_db, sql); } database_binder operator<<(const char16_t* sql) { return *this << std::u16string(sql); } connection_type connection() const { return _db; } sqlite3_int64 last_insert_rowid() const { return sqlite3_last_insert_rowid(_db.get()); } template void define(const std::string &name, Function&& func) { typedef utility::function_traits traits; auto funcPtr = new auto(std::forward(func)); if(int result = sqlite3_create_function_v2( _db.get(), name.c_str(), traits::arity, SQLITE_UTF8, funcPtr, sql_function_binder::scalar::type>, nullptr, nullptr, [](void* ptr){ delete static_cast(ptr); })) errors::throw_sqlite_error(result); } template void define(const std::string &name, StepFunction&& step, FinalFunction&& final) { typedef utility::function_traits traits; using ContextType = typename std::remove_reference>::type; auto funcPtr = new auto(std::make_pair(std::forward(step), std::forward(final))); if(int result = sqlite3_create_function_v2( _db.get(), name.c_str(), traits::arity - 1, SQLITE_UTF8, funcPtr, nullptr, sql_function_binder::step::type>, sql_function_binder::final::type>, [](void* ptr){ delete static_cast(ptr); })) errors::throw_sqlite_error(result); } }; template class binder { private: template < typename Function, std::size_t Index > using nth_argument_type = typename utility::function_traits< Function >::template argument; public: // `Boundary` needs to be defaulted to `Count` so that the `run` function // template is not implicitly instantiated on class template instantiation. // Look up section 14.7.1 _Implicit instantiation_ of the ISO C++14 Standard // and the [dicussion](https://github.com/aminroosta/sqlite_modern_cpp/issues/8) // on Github. template< typename Function, typename... Values, std::size_t Boundary = Count > static typename std::enable_if<(sizeof...(Values) < Boundary), void>::type run( database_binder& db, Function&& function, Values&&... values ) { typename std::remove_cv>::type>::type value{}; get_col_from_db(db, sizeof...(Values), value); run(db, function, std::forward(values)..., std::move(value)); } template< typename Function, typename... Values, std::size_t Boundary = Count > static typename std::enable_if<(sizeof...(Values) == Boundary), void>::type run( database_binder&, Function&& function, Values&&... values ) { function(std::move(values)...); } }; // int inline database_binder& operator<<(database_binder& db, const int& val) { int hresult; if((hresult = sqlite3_bind_int(db._stmt.get(), db._next_index(), val)) != SQLITE_OK) { errors::throw_sqlite_error(hresult, db.sql()); } return db; } inline void store_result_in_db(sqlite3_context* db, const int& val) { sqlite3_result_int(db, val); } inline void get_col_from_db(database_binder& db, int inx, int& val) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { val = 0; } else { val = sqlite3_column_int(db._stmt.get(), inx); } } inline void get_val_from_db(sqlite3_value *value, int& val) { if(sqlite3_value_type(value) == SQLITE_NULL) { val = 0; } else { val = sqlite3_value_int(value); } } // sqlite_int64 inline database_binder& operator <<(database_binder& db, const sqlite_int64& val) { int hresult; if((hresult = sqlite3_bind_int64(db._stmt.get(), db._next_index(), val)) != SQLITE_OK) { errors::throw_sqlite_error(hresult, db.sql()); } return db; } inline void store_result_in_db(sqlite3_context* db, const sqlite_int64& val) { sqlite3_result_int64(db, val); } inline void get_col_from_db(database_binder& db, int inx, sqlite3_int64& i) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { i = 0; } else { i = sqlite3_column_int64(db._stmt.get(), inx); } } inline void get_val_from_db(sqlite3_value *value, sqlite3_int64& i) { if(sqlite3_value_type(value) == SQLITE_NULL) { i = 0; } else { i = sqlite3_value_int64(value); } } // float inline database_binder& operator <<(database_binder& db, const float& val) { int hresult; if((hresult = sqlite3_bind_double(db._stmt.get(), db._next_index(), double(val))) != SQLITE_OK) { errors::throw_sqlite_error(hresult, db.sql()); } return db; } inline void store_result_in_db(sqlite3_context* db, const float& val) { sqlite3_result_double(db, val); } inline void get_col_from_db(database_binder& db, int inx, float& f) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { f = 0; } else { f = float(sqlite3_column_double(db._stmt.get(), inx)); } } inline void get_val_from_db(sqlite3_value *value, float& f) { if(sqlite3_value_type(value) == SQLITE_NULL) { f = 0; } else { f = float(sqlite3_value_double(value)); } } // double inline database_binder& operator <<(database_binder& db, const double& val) { int hresult; if((hresult = sqlite3_bind_double(db._stmt.get(), db._next_index(), val)) != SQLITE_OK) { errors::throw_sqlite_error(hresult, db.sql()); } return db; } inline void store_result_in_db(sqlite3_context* db, const double& val) { sqlite3_result_double(db, val); } inline void get_col_from_db(database_binder& db, int inx, double& d) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { d = 0; } else { d = sqlite3_column_double(db._stmt.get(), inx); } } inline void get_val_from_db(sqlite3_value *value, double& d) { if(sqlite3_value_type(value) == SQLITE_NULL) { d = 0; } else { d = sqlite3_value_double(value); } } // vector template inline database_binder& operator<<(database_binder& db, const std::vector& vec) { void const* buf = reinterpret_cast(vec.data()); int bytes = vec.size() * sizeof(T); int hresult; if((hresult = sqlite3_bind_blob(db._stmt.get(), db._next_index(), buf, bytes, SQLITE_TRANSIENT)) != SQLITE_OK) { errors::throw_sqlite_error(hresult, db.sql()); } return db; } template inline void store_result_in_db(sqlite3_context* db, const std::vector& vec) { void const* buf = reinterpret_cast(vec.data()); int bytes = vec.size() * sizeof(T); sqlite3_result_blob(db, buf, bytes, SQLITE_TRANSIENT); } template inline void get_col_from_db(database_binder& db, int inx, std::vector& vec) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { vec.clear(); } else { int bytes = sqlite3_column_bytes(db._stmt.get(), inx); T const* buf = reinterpret_cast(sqlite3_column_blob(db._stmt.get(), inx)); vec = std::vector(buf, buf + bytes/sizeof(T)); } } template inline void get_val_from_db(sqlite3_value *value, std::vector& vec) { if(sqlite3_value_type(value) == SQLITE_NULL) { vec.clear(); } else { int bytes = sqlite3_value_bytes(value); T const* buf = reinterpret_cast(sqlite3_value_blob(value)); vec = std::vector(buf, buf + bytes/sizeof(T)); } } /* for nullptr support */ inline database_binder& operator <<(database_binder& db, std::nullptr_t) { int hresult; if((hresult = sqlite3_bind_null(db._stmt.get(), db._next_index())) != SQLITE_OK) { errors::throw_sqlite_error(hresult, db.sql()); } return db; } inline void store_result_in_db(sqlite3_context* db, std::nullptr_t) { sqlite3_result_null(db); } /* for nullptr support */ template inline database_binder& operator <<(database_binder& db, const std::unique_ptr& val) { if(val) db << *val; else db << nullptr; return db; } /* for unique_ptr support */ template inline void get_col_from_db(database_binder& db, int inx, std::unique_ptr& _ptr_) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { _ptr_ = nullptr; } else { auto underling_ptr = new T(); get_col_from_db(db, inx, *underling_ptr); _ptr_.reset(underling_ptr); } } template inline void get_val_from_db(sqlite3_value *value, std::unique_ptr& _ptr_) { if(sqlite3_value_type(value) == SQLITE_NULL) { _ptr_ = nullptr; } else { auto underling_ptr = new T(); get_val_from_db(value, *underling_ptr); _ptr_.reset(underling_ptr); } } // std::string inline void get_col_from_db(database_binder& db, int inx, std::string & s) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { s = std::string(); } else { sqlite3_column_bytes(db._stmt.get(), inx); s = std::string(reinterpret_cast(sqlite3_column_text(db._stmt.get(), inx))); } } inline void get_val_from_db(sqlite3_value *value, std::string & s) { if(sqlite3_value_type(value) == SQLITE_NULL) { s = std::string(); } else { sqlite3_value_bytes(value); s = std::string(reinterpret_cast(sqlite3_value_text(value))); } } // Convert char* to string to trigger op<<(..., const std::string ) template inline database_binder& operator <<(database_binder& db, const char(&STR)[N]) { return db << std::string(STR); } template inline database_binder& operator <<(database_binder& db, const char16_t(&STR)[N]) { return db << std::u16string(STR); } inline database_binder& operator <<(database_binder& db, const std::string& txt) { int hresult; if((hresult = sqlite3_bind_text(db._stmt.get(), db._next_index(), txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { errors::throw_sqlite_error(hresult, db.sql()); } return db; } inline void store_result_in_db(sqlite3_context* db, const std::string& val) { sqlite3_result_text(db, val.data(), -1, SQLITE_TRANSIENT); } // std::u16string inline void get_col_from_db(database_binder& db, int inx, std::u16string & w) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { w = std::u16string(); } else { sqlite3_column_bytes16(db._stmt.get(), inx); w = std::u16string(reinterpret_cast(sqlite3_column_text16(db._stmt.get(), inx))); } } inline void get_val_from_db(sqlite3_value *value, std::u16string & w) { if(sqlite3_value_type(value) == SQLITE_NULL) { w = std::u16string(); } else { sqlite3_value_bytes16(value); w = std::u16string(reinterpret_cast(sqlite3_value_text16(value))); } } inline database_binder& operator <<(database_binder& db, const std::u16string& txt) { int hresult; if((hresult = sqlite3_bind_text16(db._stmt.get(), db._next_index(), txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { errors::throw_sqlite_error(hresult, db.sql()); } return db; } inline void store_result_in_db(sqlite3_context* db, const std::u16string& val) { sqlite3_result_text16(db, val.data(), -1, SQLITE_TRANSIENT); } // Other integer types template::value>::type> inline database_binder& operator <<(database_binder& db, const Integral& val) { return db << static_cast(val); } template::type>> inline void store_result_in_db(sqlite3_context* db, const Integral& val) { store_result_in_db(db, static_cast(val)); } template::value>::type> inline void get_col_from_db(database_binder& db, int inx, Integral& val) { sqlite3_int64 i; get_col_from_db(db, inx, i); val = i; } template::value>::type> inline void get_val_from_db(sqlite3_value *value, Integral& val) { sqlite3_int64 i; get_val_from_db(value, i); val = i; } // std::optional support for NULL values #ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT template inline database_binder& operator <<(database_binder& db, const optional& val) { if(val) { return db << std::move(*val); } else { return db << nullptr; } } template inline void store_result_in_db(sqlite3_context* db, const optional& val) { if(val) { store_result_in_db(db, *val); } sqlite3_result_null(db); } template inline void get_col_from_db(database_binder& db, int inx, optional& o) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT o = std::experimental::nullopt; #else o.reset(); #endif } else { OptionalT v; get_col_from_db(db, inx, v); o = std::move(v); } } template inline void get_val_from_db(sqlite3_value *value, optional& o) { if(sqlite3_value_type(value) == SQLITE_NULL) { #ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT o = std::experimental::nullopt; #else o.reset(); #endif } else { OptionalT v; get_val_from_db(value, v); o = std::move(v); } } #endif // boost::optional support for NULL values #ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT template inline database_binder& operator <<(database_binder& db, const boost::optional& val) { if(val) { return db << std::move(*val); } else { return db << nullptr; } } template inline void store_result_in_db(sqlite3_context* db, const boost::optional& val) { if(val) { store_result_in_db(db, *val); } sqlite3_result_null(db); } template inline void get_col_from_db(database_binder& db, int inx, boost::optional& o) { if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { o.reset(); } else { BoostOptionalT v; get_col_from_db(db, inx, v); o = std::move(v); } } template inline void get_val_from_db(sqlite3_value *value, boost::optional& o) { if(sqlite3_value_type(value) == SQLITE_NULL) { o.reset(); } else { BoostOptionalT v; get_val_from_db(value, v); o = std::move(v); } } #endif #ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT template inline database_binder& operator <<(database_binder& db, const std::variant& val) { std::visit([&](auto &&opt) {db << std::forward(opt);}, val); return db; } template inline void store_result_in_db(sqlite3_context* db, const std::variant& val) { std::visit([&](auto &&opt) {store_result_in_db(db, std::forward(opt));}, val); } template inline void get_col_from_db(database_binder& db, int inx, std::variant& val) { utility::variant_select(sqlite3_column_type(db._stmt.get(), inx))([&](auto v) { get_col_from_db(db, inx, v); val = std::move(v); }); } template inline void get_val_from_db(sqlite3_value *value, std::variant& val) { utility::variant_select(sqlite3_value_type(value))([&](auto v) { get_val_from_db(value, v); val = std::move(v); }); } #endif // Some ppl are lazy so we have a operator for proper prep. statemant handling. void inline operator++(database_binder& db, int) { db.execute(); } // Convert the rValue binder to a reference and call first op<<, its needed for the call that creates the binder (be carefull of recursion here!) template database_binder&& operator << (database_binder&& db, const T& val) { db << val; return std::move(db); } namespace sql_function_binder { template struct AggregateCtxt { T obj; bool constructed = true; }; template< typename ContextType, std::size_t Count, typename Functions > inline void step( sqlite3_context* db, int count, sqlite3_value** vals ) { auto ctxt = static_cast*>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt))); if(!ctxt) return; try { if(!ctxt->constructed) new(ctxt) AggregateCtxt(); step(db, count, vals, ctxt->obj); return; } catch(sqlite_exception &e) { sqlite3_result_error_code(db, e.get_code()); sqlite3_result_error(db, e.what(), -1); } catch(std::exception &e) { sqlite3_result_error(db, e.what(), -1); } catch(...) { sqlite3_result_error(db, "Unknown error", -1); } if(ctxt && ctxt->constructed) ctxt->~AggregateCtxt(); } template< std::size_t Count, typename Functions, typename... Values > inline typename std::enable_if<(sizeof...(Values) && sizeof...(Values) < Count), void>::type step( sqlite3_context* db, int count, sqlite3_value** vals, Values&&... values ) { typename std::remove_cv< typename std::remove_reference< typename utility::function_traits< typename Functions::first_type >::template argument >::type >::type value{}; get_val_from_db(vals[sizeof...(Values) - 1], value); step(db, count, vals, std::forward(values)..., std::move(value)); } template< std::size_t Count, typename Functions, typename... Values > inline typename std::enable_if<(sizeof...(Values) == Count), void>::type step( sqlite3_context* db, int, sqlite3_value**, Values&&... values ) { static_cast(sqlite3_user_data(db))->first(std::forward(values)...); } template< typename ContextType, typename Functions > inline void final(sqlite3_context* db) { auto ctxt = static_cast*>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt))); try { if(!ctxt) return; if(!ctxt->constructed) new(ctxt) AggregateCtxt(); store_result_in_db(db, static_cast(sqlite3_user_data(db))->second(ctxt->obj)); } catch(sqlite_exception &e) { sqlite3_result_error_code(db, e.get_code()); sqlite3_result_error(db, e.what(), -1); } catch(std::exception &e) { sqlite3_result_error(db, e.what(), -1); } catch(...) { sqlite3_result_error(db, "Unknown error", -1); } if(ctxt && ctxt->constructed) ctxt->~AggregateCtxt(); } template< std::size_t Count, typename Function, typename... Values > inline typename std::enable_if<(sizeof...(Values) < Count), void>::type scalar( sqlite3_context* db, int count, sqlite3_value** vals, Values&&... values ) { typename std::remove_cv< typename std::remove_reference< typename utility::function_traits::template argument >::type >::type value{}; get_val_from_db(vals[sizeof...(Values)], value); scalar(db, count, vals, std::forward(values)..., std::move(value)); } template< std::size_t Count, typename Function, typename... Values > inline typename std::enable_if<(sizeof...(Values) == Count), void>::type scalar( sqlite3_context* db, int, sqlite3_value**, Values&&... values ) { try { store_result_in_db(db, (*static_cast(sqlite3_user_data(db)))(std::forward(values)...)); } catch(sqlite_exception &e) { sqlite3_result_error_code(db, e.get_code()); sqlite3_result_error(db, e.what(), -1); } catch(std::exception &e) { sqlite3_result_error(db, e.what(), -1); } catch(...) { sqlite3_result_error(db, "Unknown error", -1); } } } }