Skip to content

Instantly share code, notes, and snippets.

@bwoods
Last active October 13, 2017 05:56
Show Gist options
  • Save bwoods/e2c5233a178ad745c9a0ba098673c947 to your computer and use it in GitHub Desktop.
Save bwoods/e2c5233a178ad745c9a0ba098673c947 to your computer and use it in GitHub Desktop.
A (light) C⁺⁺¹⁴ wrapper for SQLite₃
#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