From 297232d21594b138bb368a42b5b0d085ff9ed6aa Mon Sep 17 00:00:00 2001 From: rtk0c Date: Thu, 19 Oct 2023 22:50:07 -0700 Subject: The great renaming: switch to "module style" --- src/brussel.codegen.comp/SQLiteHelper.hpp | 220 ++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 src/brussel.codegen.comp/SQLiteHelper.hpp (limited to 'src/brussel.codegen.comp/SQLiteHelper.hpp') diff --git a/src/brussel.codegen.comp/SQLiteHelper.hpp b/src/brussel.codegen.comp/SQLiteHelper.hpp new file mode 100644 index 0000000..c24e476 --- /dev/null +++ b/src/brussel.codegen.comp/SQLiteHelper.hpp @@ -0,0 +1,220 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct SQLiteDatabase { + sqlite3* database = nullptr; + + ~SQLiteDatabase() { + // NOTE: calling with NULL is a harmless no-op + int result = sqlite3_close(database); + assert(result == SQLITE_OK); + } + + operator sqlite3*() const { return database; } + sqlite3** operator&() { return &database; } +}; + +struct SQLiteStatement { + sqlite3_stmt* stmt = nullptr; + + SQLiteStatement(const SQLiteStatement&) = delete; + SQLiteStatement& operator=(const SQLiteStatement&) = delete; + + SQLiteStatement() = default; + + SQLiteStatement(sqlite3* database, std::string_view sql) { + Initialize(database, sql); + } + + ~SQLiteStatement() { + // NOTE: calling with NULL is a harmless no-op + // NOTE: we don't care about the error code, because they are returned if the statement has errored in the most recent execution + // but deleting it will succeeed anyways + sqlite3_finalize(stmt); + } + + operator sqlite3_stmt*() const { return stmt; } + sqlite3_stmt** operator&() { return &stmt; } + + void Initialize(sqlite3* database, std::string_view sql) { + int result = sqlite3_prepare_v2(database, sql.data(), sql.size(), &stmt, nullptr); + if (result != SQLITE_OK) { + auto msg = fmt::format( + "Failed to prepare SQLite3 statement, error message: {}", + sqlite3_errmsg(sqlite3_db_handle(stmt))); + throw std::runtime_error(msg); + } + } + + bool InitializeLazily(sqlite3* database, std::string_view sql) { + if (!stmt) { + Initialize(database, sql); + return true; + } + return false; + } +}; + +struct SQLiteRunningStatement { + sqlite3_stmt* stmt; + + SQLiteRunningStatement(sqlite3_stmt* stmt) + : stmt{ stmt } { + } + + SQLiteRunningStatement(const SQLiteStatement& stmt) + : stmt{ stmt.stmt } { + } + + ~SQLiteRunningStatement() { + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + } + + void BindArgument(int index, int32_t value) { + sqlite3_bind_int(stmt, index, (int)value); + } + + void BindArgument(int index, uint32_t value) { + sqlite3_bind_int(stmt, index, (int)value); + } + + void BindArgument(int index, int64_t value) { + sqlite3_bind_int64(stmt, index, value); + } + + void BindArgument(int index, uint64_t value) { + sqlite3_bind_int64(stmt, index, (int64_t)value); + } + + void BindArgument(int index, const char* value) { + sqlite3_bind_text(stmt, index, value, -1, nullptr); + } + + void BindArgument(int index, std::string_view value) { + sqlite3_bind_text(stmt, index, value.data(), value.size(), nullptr); + } + + void BindArgument(int index, std::nullptr_t) { + // Noop + } + + template + void BindArguments(Ts&&... args) { + // NOTE: SQLite3 argument index starts at 1 + size_t idx = 1; + auto HandleEachArgument = [this, &idx](T&& arg) { + BindArgument(idx, std::forward(arg)); + ++idx; + }; + (HandleEachArgument(std::forward(args)), ...); + } + + int Step() { + return sqlite3_step(stmt); + } + + void StepAndCheck(int forErr) { + int err = sqlite3_step(stmt); + assert(err == forErr); + } + + int StepAndCheckError() { + int err = sqlite3_step(stmt); + if (err != SQLITE_DONE || err != SQLITE_ROW) { + auto msg = fmt::format( + "Error {} executing SQLite3 statement, error message: {}", + sqlite3_errstr(err), + sqlite3_errmsg(sqlite3_db_handle(stmt))); + throw std::runtime_error(msg); + } + return err; + } + + void StepUntilDone() { + while (true) { + int err = sqlite3_step(stmt); + // SQLITE_OK is never returned for sqlite3_step() //TODO fact check this + if (err == SQLITE_DONE) { + break; + } + if (err == SQLITE_ROW) { + continue; + } + + auto msg = fmt::format( + "Error {} executing SQLite3 statement, error message: {}", + sqlite3_errstr(err), + sqlite3_errmsg(sqlite3_db_handle(stmt))); + throw std::runtime_error(msg); + } + } + + using TimePoint = std::chrono::time_point; + using TpFromUnixTimestamp = std::pair; + using TpFromDateTime = std::pair; + + // TODO replace with overloads? + template + auto ResultColumn(int column) const { + if constexpr (std::is_enum_v) { + auto value = sqlite3_column_int64(stmt, column); + return static_cast(value); + } else if constexpr (std::is_same_v || std::is_same_v) { + return (T)sqlite3_column_int(stmt, column); + } else if constexpr (std::is_same_v) { + return (T)sqlite3_column_int64(stmt, column); + } else if constexpr (std::is_same_v) { + return (const char*)sqlite3_column_text(stmt, column); + } else if constexpr (std::is_same_v || std::is_same_v) { + // SQLite3 uses `unsigned char` to represent UTF-8 code units, on all platforms we care about this is the same as plain `char` + auto cstr = (const char*)sqlite3_column_text(stmt, column); + // For std::string_view, this finds size based on null terminator and stores reference to pointer + // For std::string, this also allocates buffer and copies `cstr` content + return T(cstr); + } else if constexpr (std::is_same_v) { + auto unixTimestamp = sqlite3_column_int64(stmt, column); + auto chrono = std::chrono::seconds(unixTimestamp); + return TimePoint(chrono); + } else if constexpr (std::is_same_v) { + // TODO wait for libstdc++ and libc++ implement c++20 std::chrono addition +#ifdef _MSC_VER + auto datetime = (const char*)sqlite3_column_text(stmt, column); + if (datetime) { + std::stringstream ss(datetime); + TimePoint timepoint; + ss >> std::chrono::parse("%F %T", timepoint); + return timepoint; + } else { + return TimePoint(); + } +#else + static_assert(false && sizeof(T), "Unimplemented"); +#endif + } else { + static_assert(false && sizeof(T), "Unknown type"); + } + } + + template + auto ResultColumns() { + // NOTE: SQLite3 column index starts at 0 + // NOTE: ((size_t)-1) + 1 == 0 + size_t idx = -1; + // NOTE: std::make_tuple() -- variadic template function + // std::tuple() -- CTAD constructor + // Both of these cause make the comma operator unsequenced, not viable here + return std::tuple{ (++idx, ResultColumn(idx))... }; + } +}; -- cgit v1.2.3-70-g09d2