1161 sor
32 KiB
C++
1161 sor
32 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <string>
|
|
#include <functional>
|
|
#include <ctime>
|
|
#include <tuple>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#define MODERN_SQLITE_VERSION 3002008
|
|
|
|
#ifdef __has_include
|
|
#if __cplusplus > 201402 && __has_include(<optional>)
|
|
#define MODERN_SQLITE_STD_OPTIONAL_SUPPORT
|
|
#elif __has_include(<experimental/optional>)
|
|
#define MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef __has_include
|
|
#if __cplusplus > 201402 && __has_include(<variant>)
|
|
#define MODERN_SQLITE_STD_VARIANT_SUPPORT
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
|
|
#include <optional>
|
|
#endif
|
|
|
|
#ifdef MODERN_SQLITE_EXPERIMENTAL_OPTIONAL_SUPPORT
|
|
#include <experimental/optional>
|
|
#define MODERN_SQLITE_STD_OPTIONAL_SUPPORT
|
|
#endif
|
|
|
|
#ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT
|
|
#include <boost/optional.hpp>
|
|
#endif
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#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 <class T> using optional = std::experimental::optional<T>;
|
|
#else
|
|
template <class T> using optional = std::optional<T>;
|
|
#endif
|
|
#endif
|
|
|
|
class database;
|
|
class database_binder;
|
|
|
|
template <std::size_t> class binder;
|
|
|
|
typedef std::shared_ptr<sqlite3> connection_type;
|
|
|
|
template <typename Tuple, int Element = 0, bool Last = (std::tuple_size<Tuple>::value == Element)> struct tuple_iterate
|
|
{
|
|
static void iterate(Tuple &t, database_binder &db)
|
|
{
|
|
get_col_from_db(db, Element, std::get<Element>(t));
|
|
tuple_iterate<Tuple, Element + 1>::iterate(t, db);
|
|
}
|
|
};
|
|
|
|
template <typename Tuple, int Element> struct tuple_iterate<Tuple, Element, true>
|
|
{
|
|
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<char, decltype(sqlite_deleter)> 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<sqlite3> _db;
|
|
std::unique_ptr<sqlite3_stmt, decltype(&sqlite3_finalize)> _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<void(void)> 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<void(void)> 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 <typename Type>
|
|
struct is_sqlite_value
|
|
: public std::integral_constant<bool, std::is_floating_point<Type>::value || std::is_integral<Type>::value ||
|
|
std::is_same<std::string, Type>::value ||
|
|
std::is_same<std::u16string, Type>::value ||
|
|
std::is_same<sqlite_int64, Type>::value>
|
|
{
|
|
};
|
|
template <typename Type, typename Allocator>
|
|
struct is_sqlite_value<std::vector<Type, Allocator>>
|
|
: public std::integral_constant<bool, std::is_floating_point<Type>::value || std::is_integral<Type>::value ||
|
|
std::is_same<sqlite_int64, Type>::value>
|
|
{
|
|
};
|
|
#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
|
|
template <typename... Args>
|
|
struct is_sqlite_value<std::variant<Args...>> : public std::integral_constant<bool, true>
|
|
{
|
|
};
|
|
#endif
|
|
|
|
/* for vector<T, A> support */
|
|
template <typename T, typename A>
|
|
friend database_binder &operator<<(database_binder &db, const std::vector<T, A> &val);
|
|
template <typename T, typename A> friend void get_col_from_db(database_binder &db, int inx, std::vector<T, A> &val);
|
|
/* for nullptr & unique_ptr support */
|
|
friend database_binder &operator<<(database_binder &db, std::nullptr_t);
|
|
template <typename T> friend database_binder &operator<<(database_binder &db, const std::unique_ptr<T> &val);
|
|
template <typename T> friend void get_col_from_db(database_binder &db, int inx, std::unique_ptr<T> &val);
|
|
#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
|
|
template <typename... Args>
|
|
friend database_binder &operator<<(database_binder &db, const std::variant<Args...> &val);
|
|
template <typename... Args> friend void get_col_from_db(database_binder &db, int inx, std::variant<Args...> &val);
|
|
#endif
|
|
template <typename T> 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 <typename OptionalT>
|
|
friend database_binder &operator<<(database_binder &db, const optional<OptionalT> &val);
|
|
template <typename OptionalT> friend void get_col_from_db(database_binder &db, int inx, optional<OptionalT> &o);
|
|
#endif
|
|
|
|
#ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT
|
|
template <typename BoostOptionalT>
|
|
friend database_binder &operator<<(database_binder &db, const boost::optional<BoostOptionalT> &val);
|
|
template <typename BoostOptionalT>
|
|
friend void get_col_from_db(database_binder &db, int inx, boost::optional<BoostOptionalT> &o);
|
|
#endif
|
|
|
|
public:
|
|
database_binder(std::shared_ptr<sqlite3> db, std::u16string const &sql)
|
|
: _db(db), _stmt(_prepare(sql), sqlite3_finalize), _inx(0)
|
|
{
|
|
}
|
|
|
|
database_binder(std::shared_ptr<sqlite3> 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 Result>
|
|
typename std::enable_if<is_sqlite_value<Result>::value, void>::type operator>>(Result &value)
|
|
{
|
|
this->_extract_single_value([&value, this] { get_col_from_db(*this, 0, value); });
|
|
}
|
|
|
|
template <typename... Types> void operator>>(std::tuple<Types...> &&values)
|
|
{
|
|
this->_extract_single_value([&values, this] { tuple_iterate<std::tuple<Types...>>::iterate(values, *this); });
|
|
}
|
|
|
|
template <typename Function>
|
|
typename std::enable_if<!is_sqlite_value<Function>::value, void>::type operator>>(Function &&func)
|
|
{
|
|
typedef utility::function_traits<Function> traits;
|
|
|
|
this->_extract([&func, this]() { binder<traits::arity>::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);
|
|
} // namespace sql_function_binder
|
|
|
|
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<OpenFlags>(static_cast<int>(a) | static_cast<int>(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<sqlite3> _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<int>(config.flags), config.zVfs);
|
|
_db = std::shared_ptr<sqlite3>(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<int>(config.flags), config.zVfs);
|
|
_db = std::shared_ptr<sqlite3>(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<sqlite3> 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 <typename Function> void define(const std::string &name, Function &&func)
|
|
{
|
|
typedef utility::function_traits<Function> traits;
|
|
|
|
auto funcPtr = new auto(std::forward<Function>(func));
|
|
if(int result = sqlite3_create_function_v2(
|
|
_db.get(), name.c_str(), traits::arity, SQLITE_UTF8, funcPtr,
|
|
sql_function_binder::scalar<traits::arity, typename std::remove_reference<Function>::type>, nullptr,
|
|
nullptr, [](void *ptr) { delete static_cast<decltype(funcPtr)>(ptr); }))
|
|
errors::throw_sqlite_error(result);
|
|
}
|
|
|
|
template <typename StepFunction, typename FinalFunction>
|
|
void define(const std::string &name, StepFunction &&step, FinalFunction &&final)
|
|
{
|
|
typedef utility::function_traits<StepFunction> traits;
|
|
using ContextType = typename std::remove_reference<typename traits::template argument<0>>::type;
|
|
|
|
auto funcPtr = new auto(std::make_pair(std::forward<StepFunction>(step), std::forward<FinalFunction>(final)));
|
|
if(int result = sqlite3_create_function_v2(
|
|
_db.get(), name.c_str(), traits::arity - 1, SQLITE_UTF8, funcPtr, nullptr,
|
|
sql_function_binder::step<ContextType, traits::arity,
|
|
typename std::remove_reference<decltype(*funcPtr)>::type>,
|
|
sql_function_binder::final<ContextType, typename std::remove_reference<decltype(*funcPtr)>::type>,
|
|
[](void *ptr) { delete static_cast<decltype(funcPtr)>(ptr); }))
|
|
errors::throw_sqlite_error(result);
|
|
}
|
|
};
|
|
|
|
template <std::size_t Count> class binder
|
|
{
|
|
private:
|
|
template <typename Function, std::size_t Index>
|
|
using nth_argument_type = typename utility::function_traits<Function>::template argument<Index>;
|
|
|
|
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<
|
|
typename std::remove_reference<nth_argument_type<Function, sizeof...(Values)>>::type>::type value{};
|
|
get_col_from_db(db, sizeof...(Values), value);
|
|
|
|
run<Function>(db, function, std::forward<Values>(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<T, A>
|
|
template <typename T, typename A> inline database_binder &operator<<(database_binder &db, const std::vector<T, A> &vec)
|
|
{
|
|
void const *buf = reinterpret_cast<void const *>(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 <typename T, typename A> inline void store_result_in_db(sqlite3_context *db, const std::vector<T, A> &vec)
|
|
{
|
|
void const *buf = reinterpret_cast<void const *>(vec.data());
|
|
int bytes = vec.size() * sizeof(T);
|
|
sqlite3_result_blob(db, buf, bytes, SQLITE_TRANSIENT);
|
|
}
|
|
template <typename T, typename A> inline void get_col_from_db(database_binder &db, int inx, std::vector<T, A> &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<T const *>(sqlite3_column_blob(db._stmt.get(), inx));
|
|
vec = std::vector<T, A>(buf, buf + bytes / sizeof(T));
|
|
}
|
|
}
|
|
template <typename T, typename A> inline void get_val_from_db(sqlite3_value *value, std::vector<T, A> &vec)
|
|
{
|
|
if(sqlite3_value_type(value) == SQLITE_NULL)
|
|
{
|
|
vec.clear();
|
|
}
|
|
else
|
|
{
|
|
int bytes = sqlite3_value_bytes(value);
|
|
T const *buf = reinterpret_cast<T const *>(sqlite3_value_blob(value));
|
|
vec = std::vector<T, A>(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 <typename T> inline database_binder &operator<<(database_binder &db, const std::unique_ptr<T> &val)
|
|
{
|
|
if(val)
|
|
db << *val;
|
|
else
|
|
db << nullptr;
|
|
return db;
|
|
}
|
|
|
|
/* for unique_ptr<T> support */
|
|
template <typename T> inline void get_col_from_db(database_binder &db, int inx, std::unique_ptr<T> &_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 <typename T> inline void get_val_from_db(sqlite3_value *value, std::unique_ptr<T> &_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<char const *>(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<char const *>(sqlite3_value_text(value)));
|
|
}
|
|
}
|
|
|
|
// Convert char* to string to trigger op<<(..., const std::string )
|
|
template <std::size_t N> inline database_binder &operator<<(database_binder &db, const char (&STR)[N])
|
|
{
|
|
return db << std::string(STR);
|
|
}
|
|
template <std::size_t N> 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<char16_t const *>(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<char16_t const *>(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 <class Integral, class = typename std::enable_if<std::is_integral<Integral>::value>::type>
|
|
inline database_binder &operator<<(database_binder &db, const Integral &val)
|
|
{
|
|
return db << static_cast<sqlite3_int64>(val);
|
|
}
|
|
template <class Integral, class = std::enable_if<std::is_integral<Integral>::type>>
|
|
inline void store_result_in_db(sqlite3_context *db, const Integral &val)
|
|
{
|
|
store_result_in_db(db, static_cast<sqlite3_int64>(val));
|
|
}
|
|
template <class Integral, class = typename std::enable_if<std::is_integral<Integral>::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 <class Integral, class = typename std::enable_if<std::is_integral<Integral>::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 <typename OptionalT> inline database_binder &operator<<(database_binder &db, const optional<OptionalT> &val)
|
|
{
|
|
if(val)
|
|
{
|
|
return db << std::move(*val);
|
|
}
|
|
else
|
|
{
|
|
return db << nullptr;
|
|
}
|
|
}
|
|
template <typename OptionalT> inline void store_result_in_db(sqlite3_context *db, const optional<OptionalT> &val)
|
|
{
|
|
if(val)
|
|
{
|
|
store_result_in_db(db, *val);
|
|
}
|
|
sqlite3_result_null(db);
|
|
}
|
|
|
|
template <typename OptionalT> inline void get_col_from_db(database_binder &db, int inx, optional<OptionalT> &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 <typename OptionalT> inline void get_val_from_db(sqlite3_value *value, optional<OptionalT> &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 <typename BoostOptionalT>
|
|
inline database_binder &operator<<(database_binder &db, const boost::optional<BoostOptionalT> &val)
|
|
{
|
|
if(val)
|
|
{
|
|
return db << std::move(*val);
|
|
}
|
|
else
|
|
{
|
|
return db << nullptr;
|
|
}
|
|
}
|
|
template <typename BoostOptionalT>
|
|
inline void store_result_in_db(sqlite3_context *db, const boost::optional<BoostOptionalT> &val)
|
|
{
|
|
if(val)
|
|
{
|
|
store_result_in_db(db, *val);
|
|
}
|
|
sqlite3_result_null(db);
|
|
}
|
|
|
|
template <typename BoostOptionalT>
|
|
inline void get_col_from_db(database_binder &db, int inx, boost::optional<BoostOptionalT> &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 <typename BoostOptionalT> inline void get_val_from_db(sqlite3_value *value, boost::optional<BoostOptionalT> &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 <typename... Args> inline database_binder &operator<<(database_binder &db, const std::variant<Args...> &val)
|
|
{
|
|
std::visit([&](auto &&opt) { db << std::forward<decltype(opt)>(opt); }, val);
|
|
return db;
|
|
}
|
|
template <typename... Args> inline void store_result_in_db(sqlite3_context *db, const std::variant<Args...> &val)
|
|
{
|
|
std::visit([&](auto &&opt) { store_result_in_db(db, std::forward<decltype(opt)>(opt)); }, val);
|
|
}
|
|
template <typename... Args> inline void get_col_from_db(database_binder &db, int inx, std::variant<Args...> &val)
|
|
{
|
|
utility::variant_select<Args...>(sqlite3_column_type(db._stmt.get(), inx))([&](auto v) {
|
|
get_col_from_db(db, inx, v);
|
|
val = std::move(v);
|
|
});
|
|
}
|
|
template <typename... Args> inline void get_val_from_db(sqlite3_value *value, std::variant<Args...> &val)
|
|
{
|
|
utility::variant_select<Args...>(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 <typename T> database_binder &&operator<<(database_binder &&db, const T &val)
|
|
{
|
|
db << val;
|
|
return std::move(db);
|
|
}
|
|
|
|
namespace sql_function_binder
|
|
{
|
|
template <class T> 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<AggregateCtxt<ContextType> *>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt<ContextType>)));
|
|
if(!ctxt)
|
|
return;
|
|
try
|
|
{
|
|
if(!ctxt->constructed)
|
|
new(ctxt) AggregateCtxt<ContextType>();
|
|
step<Count, Functions>(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<sizeof...(Values)>>::type>::type value{};
|
|
get_val_from_db(vals[sizeof...(Values) - 1], value);
|
|
|
|
step<Count, Functions>(db, count, vals, std::forward<Values>(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<Functions *>(sqlite3_user_data(db))->first(std::forward<Values>(values)...);
|
|
}
|
|
|
|
template <typename ContextType, typename Functions> inline void final(sqlite3_context *db)
|
|
{
|
|
auto ctxt =
|
|
static_cast<AggregateCtxt<ContextType> *>(sqlite3_aggregate_context(db, sizeof(AggregateCtxt<ContextType>)));
|
|
try
|
|
{
|
|
if(!ctxt)
|
|
return;
|
|
if(!ctxt->constructed)
|
|
new(ctxt) AggregateCtxt<ContextType>();
|
|
store_result_in_db(db, static_cast<Functions *>(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<Function>::template argument<sizeof...(Values)>>::type>::type value{};
|
|
get_val_from_db(vals[sizeof...(Values)], value);
|
|
|
|
scalar<Count, Function>(db, count, vals, std::forward<Values>(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<Function *>(sqlite3_user_data(db)))(std::forward<Values>(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);
|
|
}
|
|
}
|
|
} // namespace sql_function_binder
|
|
} // namespace sqlite
|