Last active
October 13, 2017 05:56
-
-
Save bwoods/e2c5233a178ad745c9a0ba098673c947 to your computer and use it in GitHub Desktop.
A (light) C⁺⁺¹⁴ wrapper for SQLite₃
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdexcept> | |
#include <memory> | |
#include <limits> | |
#include <sqlite3.h> | |
#include <assert.h> | |
namespace sql { | |
namespace internal { | |
inline void column_variable(sqlite3_stmt * stmt, unsigned column, sqlite3_int64 value, std::true_type, std::true_type) { | |
sqlite3_bind_int64(stmt, column, value); | |
} | |
inline void column_variable(sqlite3_stmt * stmt, unsigned column, double value, std::true_type, std::false_type) { | |
sqlite3_bind_double(stmt, column, value); | |
} | |
inline void column_variable(sqlite3_stmt * stmt, unsigned column, const char * value, std::false_type, std::false_type) { | |
if (sqlite3_bind_text(stmt, column, value, -1, SQLITE_TRANSIENT) != SQLITE_OK) | |
throw std::range_error(sqlite3_errmsg(sqlite3_db_handle(stmt))); | |
} | |
template <class Container> | |
inline void column_variable(sqlite3_stmt * stmt, unsigned column, Container const& value, std::false_type, std::false_type) { | |
if (value.size() > std::numeric_limits<int>::max()) | |
throw std::length_error("sqlite₃ only handles BLOBs up to 2147483647 bytes in length."); | |
if (sqlite3_bind_text(stmt, column, value.data(), int(value.size()), SQLITE_TRANSIENT)) | |
throw std::range_error(sqlite3_errmsg(sqlite3_db_handle(stmt))); | |
} | |
template <typename... Args> | |
inline int column_variables(sqlite3_stmt * stmt, Args... args) { | |
unsigned i = 0; | |
bool unused[] = { (column_variable(stmt, ++i, args, std::is_arithmetic<Args>(), std::is_integral<Args>()), true)... }; | |
return sizeof(unused); | |
} | |
template <typename T> | |
inline auto column_value(sqlite3_stmt * stmt, unsigned column) { | |
if constexpr (std::is_integral<T>()) { | |
assert(sqlite3_column_type(stmt, column) == SQLITE_INTEGER); | |
return sqlite3_column_int64(stmt, column); | |
} else if constexpr (std::is_arithmetic<T>()) { | |
assert(sqlite3_column_type(stmt, column) == SQLITE_FLOAT); | |
return sqlite3_column_double(stmt, column); | |
} else { | |
assert(sqlite3_column_type(stmt, column) >= SQLITE_TEXT); | |
return reinterpret_cast<const char *>(sqlite3_column_text(stmt, column)); | |
} | |
} | |
template <class Lambda, typename R, typename... Args, unsigned... Indexes> | |
inline auto call(sqlite3_stmt * stmt, Lambda&& lambda, R (Lambda::*)(Args...) const, std::integer_sequence<unsigned, Indexes...>) { | |
return lambda(column_value<Args>(stmt, Indexes)...); | |
} | |
template <class Lambda, typename R, typename... Args> | |
inline auto call(sqlite3_stmt * stmt, Lambda&& lambda, R (Lambda::*op)(Args...) const) { | |
return call(stmt, std::forward<Lambda>(lambda), op, std::make_integer_sequence<unsigned, sizeof...(Args)>{}); | |
} | |
template <class Lambda, typename R, typename... Args> | |
inline void step(sqlite3_stmt * stmt, Lambda&& lambda, R (Lambda::*op)(Args...) const) { | |
int status; assert(sizeof...(Args) <= sqlite3_column_count(stmt)); // this overload exists just for this assert… | |
while ((status = sqlite3_step(stmt)) == SQLITE_ROW) | |
internal::call(stmt, std::forward<Lambda>(lambda), op); | |
sqlite3_reset(stmt); | |
if (status != SQLITE_DONE) | |
throw std::runtime_error(sqlite3_errmsg(sqlite3_db_handle(stmt))); | |
} | |
} // end private namespace | |
inline auto open(const char * path, const char * vfs = nullptr) { | |
sqlite3 * db; | |
if (sqlite3_open_v2(path, &db, SQLITE_OPEN_READWRITE+SQLITE_OPEN_CREATE+SQLITE_OPEN_URI, vfs) != SQLITE_OK) | |
throw std::runtime_error(sqlite3_errmsg(db)); | |
return std::unique_ptr<sqlite3, int (*)(sqlite3*)>{db, sqlite3_close_v2}; | |
} | |
template <typename... Args> | |
inline auto prepare(sqlite3 * db, const char * sql, Args... args) { | |
sqlite3_stmt * stmt; | |
if (sqlite3_prepare_v3(db, sql, -1, SQLITE_PREPARE_PERSISTENT, &stmt, nullptr) != SQLITE_OK) | |
throw std::invalid_argument(sqlite3_errmsg(db)); | |
assert(sizeof...(Args) == 0 or sizeof...(Args) == sqlite3_bind_parameter_count(stmt)); | |
internal::column_variables(stmt, args...); | |
return std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt*)>{stmt, sqlite3_finalize}; | |
} | |
template <typename... Args> | |
inline auto prepare(std::unique_ptr<sqlite3, int (*)(sqlite3 *)>& db, const char * sql, Args... args) { | |
return prepare(db.get(), sql, std::forward<Args>(args)...); | |
} | |
template <typename... Args> | |
inline auto bind(sqlite3_stmt * stmt, Args... args) { | |
assert(sizeof...(Args) <= sqlite3_bind_parameter_count(stmt)); | |
internal::column_variables(stmt, args...); | |
} | |
template <class Lambda> | |
inline void bind(std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt *)>& stmt, Lambda&& lambda) { | |
return bind(stmt.get(), std::forward<Lambda>(lambda)); | |
} | |
template <class Lambda> | |
inline void step(sqlite3_stmt * stmt, Lambda&& lambda) { | |
return internal::step(stmt, std::forward<Lambda>(lambda), &Lambda::operator()); | |
} | |
template <class Lambda> | |
inline void step(std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt *)>& stmt, Lambda&& lambda) { | |
return step(stmt.get(), std::forward<Lambda>(lambda)); | |
} | |
inline void step(sqlite3_stmt * stmt) { | |
int status = sqlite3_step(stmt); | |
if (not (status == SQLITE_DONE or status == SQLITE_OK)) | |
throw std::runtime_error(sqlite3_errmsg(sqlite3_db_handle(stmt))); | |
sqlite3_reset(stmt); | |
} | |
inline void step(std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt *)>& stmt) { | |
return step(stmt.get()); | |
} | |
template <typename... Args> | |
inline sqlite3_int64 integer(std::unique_ptr<sqlite3, int (*)(sqlite3 *)>& db, std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt *)>& stmt, Args... args) { | |
sqlite3_int64 value = 0; | |
sql::step(stmt, [&] (sqlite3_int64 n) { | |
value = n; | |
}); | |
return value; | |
} | |
template <typename... Args> | |
inline sqlite3_int64 integer(std::unique_ptr<sqlite3, int (*)(sqlite3 *)>& db, const char * sql, Args... args) { | |
auto stmt = sql::prepare(db.get(), sql, std::forward<Args>(args)...); | |
return integer(db, stmt); | |
} | |
} // end namespace sql | |
inline auto& operator<< (std::unique_ptr<sqlite3, int (*)(sqlite3 *)>& db, const char * sql) { | |
if (sqlite3_exec(db.get(), sql, nullptr, nullptr, nullptr) != SQLITE_OK) | |
throw std::runtime_error(sqlite3_errmsg(db.get())); | |
return db; | |
} | |
// | |
// int main() | |
// { | |
// auto db = sql::open(""); // create a TEMP database | |
// db << "CREATE TABLE uids(uid INTEGER PRIMARY KEY)"; | |
// | |
// auto uid = 64, count = 0; | |
// auto insert = sql::prepare(db, "INSERT INTO uids(uid) VALUES(?)", uid); | |
// sql::step(insert); | |
// | |
// auto query = sql::prepare(db, "SELECT uid FROM uids"); | |
// sql::step(query, [&](uint64_t id) { | |
// assert(id == uid); ++count; | |
// }); | |
// | |
// assert(count == 1); | |
// return 0; | |
// } | |
// |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment