From fbb26deb27909f5603746739822c0d9c338955b9 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Sat, 30 Jul 2022 22:09:22 -0700 Subject: Changeset: 88 (untested) replace manual sqlite3_step() calls with SQLiteRunningStatement wrapper --- source/20-codegen-compiler/SQLiteHelper.hpp | 204 ++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 source/20-codegen-compiler/SQLiteHelper.hpp (limited to 'source/20-codegen-compiler/SQLiteHelper.hpp') diff --git a/source/20-codegen-compiler/SQLiteHelper.hpp b/source/20-codegen-compiler/SQLiteHelper.hpp new file mode 100644 index 0000000..c33c2a3 --- /dev/null +++ b/source/20-codegen-compiler/SQLiteHelper.hpp @@ -0,0 +1,204 @@ +#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](auto arg) { + BindArgument(idx, 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); + } + } + + 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) { + return sqlite3_column_int(stmt, column); + } else if constexpr (std::is_same_v) { + return 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) { + auto cstr = (const char*)sqlite3_column_text(stmt, column); + 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 + static_assert(false && sizeof(T), "Unimplemented"); + } 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