nf7/core/sqlite/database.cc

209 lines
5.2 KiB
C++

// No copyright
#include "core/sqlite/database.hh"
#include <limits>
#include <string>
#include <utility>
#include <variant>
#include "core/sqlite/util.hh"
namespace nf7::core::sqlite {
class Database::Sql final :
public nf7::Sql,
public nf7::Sql::Command,
public std::enable_shared_from_this<Database::Sql> {
public:
Sql(const std::shared_ptr<Database>& db, sqlite3_stmt* stmt) noexcept
: db_(db), stmt_(stmt) {
assert(nullptr != stmt_);
}
~Sql() noexcept override {
if (nullptr != stmt_) {
db_->concurrency_->Exec([db = db_, stmt = stmt_](auto&) {
db->Run([stmt](auto&) {
sqlite3_finalize(stmt);
return Void {};
});
});
}
}
Future<Void> Run(Handler&& f) noexcept override {
auto self = shared_from_this();
return db_->Run([self, f = std::move(f)](auto&) mutable {
f(*self);
return Void {};
});
}
void Bind(uint64_t idx, const Value& v) override {
if (idx > std::numeric_limits<int>::max()) {
throw Exception {"too large index"};
}
struct A {
public:
A(sqlite3_stmt* a, int b) noexcept : a_(a), b_(b) { }
public:
int operator()(Null) noexcept {
return sqlite3_bind_null(a_, b_);
}
int operator()(int64_t c) noexcept {
return sqlite3_bind_int64(a_, b_, c);
}
int operator()(double c) noexcept {
return sqlite3_bind_double(a_, b_, c);
}
int operator()(const std::string& c) noexcept {
return sqlite3_bind_text64(
a_, b_, c.data(), static_cast<uint64_t>(c.size()),
SQLITE_TRANSIENT, SQLITE_UTF8);
}
private:
sqlite3_stmt* const a_;
int b_;
};
Enforce(std::visit(A {stmt_, static_cast<int>(idx)}, v));
}
Value Fetch(uint64_t idx) const override {
if (idx > std::numeric_limits<int>::max()) {
throw Exception {"too large index"};
}
auto v = sqlite3_column_value(stmt_, static_cast<int>(idx));
switch (sqlite3_value_type(v)) {
case SQLITE_NULL: {
return Sql::Null {};
}
case SQLITE_INTEGER: {
return sqlite3_value_int64(v);
}
case SQLITE_FLOAT: {
return sqlite3_value_double(v);
}
case SQLITE_TEXT: {
const auto n = sqlite3_value_bytes(v);
const auto p = reinterpret_cast<const char*>(sqlite3_value_text(v));
return std::string {p, p+n};
}
default:
throw Exception {"unsupported type"};
}
}
void Reset() override {
Enforce(sqlite3_reset(stmt_));
}
Result Exec() override {
const auto ret = sqlite3_step(stmt_);
switch (ret) {
case SQLITE_ROW:
return kRow;
case SQLITE_DONE:
return kDone;
default:
Enforce(ret);
std::unreachable();
}
}
private:
const std::shared_ptr<Database> db_;
sqlite3_stmt* const stmt_;
};
sqlite3* Database::MakeConn(const char* addr) {
sqlite3* ret = nullptr;
Enforce(sqlite3_open(addr, &ret));
assert(nullptr != ret);
return ret;
}
Future<std::shared_ptr<Sql::Command>> Database::Compile(
std::string_view cmd) noexcept
try {
if (cmd.size() > std::numeric_limits<int>::max()) {
return Exception::MakePtr("too long SQL command");
}
return Run([this, cmd = std::string {cmd}](auto&) {
sqlite3_stmt* stmt;
Enforce(
sqlite3_prepare_v3(
conn_,
cmd.c_str(),
static_cast<int>(cmd.size()),
SQLITE_PREPARE_PERSISTENT,
&stmt,
nullptr));
try {
return std::static_pointer_cast<nf7::Sql::Command>(
std::make_shared<Database::Sql>(shared_from_this(), stmt));
} catch (const std::bad_alloc&) {
sqlite3_finalize(stmt);
throw;
}
});
} catch (...) {
return std::current_exception();
}
Future<Void> Database::Exec(std::string_view cmd, ColumnHandler&& f) noexcept {
class A final : public nf7::Sql {
public:
static int callback(void* ptr, int n, char** v, char**) noexcept
try {
auto self = reinterpret_cast<Database*>(ptr);
A a {n, v};
return int {self->column_handler_(a)? 0: 1};
} catch (...) {
return 1;
}
private:
A(int n, char** v) noexcept : n_(static_cast<uint64_t>(n)), v_(v) { }
private:
void Bind(uint64_t, const Value&) noexcept override { std::unreachable(); }
void Reset() noexcept override { std::unreachable(); }
Result Exec() noexcept override { std::unreachable(); }
Value Fetch(uint64_t idx) const override {
if (idx >= n_) {
throw Exception {"index overflow"};
}
return std::string {v_[idx]};
}
private:
uint64_t n_;
char** v_;
};
if (cmd.size() > std::numeric_limits<int>::max()) {
return Exception::MakePtr("too long SQL command");
}
return Run([this, cmd = std::string {cmd}, f = std::move(f)](auto&) mutable {
char* errmsg {nullptr};
column_handler_ = std::move(f);
const auto ret = sqlite3_exec(
conn_,
cmd.c_str(),
column_handler_? A::callback: nullptr,
this,
&errmsg);
column_handler_ = {};
if (nullptr != errmsg) {
const auto msg = std::string {"SQL error: "}+errmsg;
sqlite3_free(errmsg);
throw Exception {msg};
}
Enforce(ret);
return Void {};
});
}
} // namespace nf7::core::sqlite