#ifndef OPENMW_COMPONENTS_SQLITE3_REQUEST_H #define OPENMW_COMPONENTS_SQLITE3_REQUEST_H #include "statement.hpp" #include "types.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace Sqlite3 { inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, int value) { if (const int ec = sqlite3_bind_int(&stmt, index, value); ec != SQLITE_OK) throw std::runtime_error( "Failed to bind int to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, std::int64_t value) { if (const int ec = sqlite3_bind_int64(&stmt, index, value); ec != SQLITE_OK) throw std::runtime_error( "Failed to bind int64 to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, double value) { if (const int ec = sqlite3_bind_double(&stmt, index, value); ec != SQLITE_OK) throw std::runtime_error("Failed to bind double to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, std::string_view value) { if (sqlite3_bind_text(&stmt, index, value.data(), static_cast(value.size()), SQLITE_STATIC) != SQLITE_OK) throw std::runtime_error( "Failed to bind text to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, const std::vector& value) { if (sqlite3_bind_blob(&stmt, index, value.data(), static_cast(value.size()), SQLITE_STATIC) != SQLITE_OK) throw std::runtime_error( "Failed to bind blob to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, int index, const ConstBlob& value) { if (sqlite3_bind_blob(&stmt, index, value.mData, value.mSize, SQLITE_STATIC) != SQLITE_OK) throw std::runtime_error( "Failed to bind blob to parameter " + std::to_string(index) + ": " + std::string(sqlite3_errmsg(&db))); } template inline void bindParameter(sqlite3& db, sqlite3_stmt& stmt, const char* name, const T& value) { const int index = sqlite3_bind_parameter_index(&stmt, name); if (index == 0) throw std::logic_error("Parameter \"" + std::string(name) + "\" is not found"); bindParameter(db, stmt, index, value); } inline std::string sqliteTypeToString(int value) { switch (value) { case SQLITE_INTEGER: return "SQLITE_INTEGER"; case SQLITE_FLOAT: return "SQLITE_FLOAT"; case SQLITE_TEXT: return "SQLITE_TEXT"; case SQLITE_BLOB: return "SQLITE_BLOB"; case SQLITE_NULL: return "SQLITE_NULL"; } return "unsupported(" + std::to_string(value) + ")"; } template inline auto copyColumn(sqlite3& /*db*/, sqlite3_stmt& /*statement*/, int index, int type, T*& value) { if (type != SQLITE_NULL) throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + " that does not match expected output type: SQLITE_INTEGER or SQLITE_FLOAT"); value = nullptr; } template inline auto copyColumn(sqlite3& /*db*/, sqlite3_stmt& statement, int index, int type, T& value) { switch (type) { case SQLITE_INTEGER: value = static_cast(sqlite3_column_int64(&statement, index)); return; case SQLITE_FLOAT: value = static_cast(sqlite3_column_double(&statement, index)); return; case SQLITE_NULL: value = std::decay_t{}; return; } throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + " that does not match expected output type: SQLITE_INTEGER or SQLITE_FLOAT or SQLITE_NULL"); } inline void copyColumn(sqlite3& db, sqlite3_stmt& statement, int index, int type, std::string& value) { if (type != SQLITE_TEXT) throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + " that does not match expected output type: SQLITE_TEXT"); const unsigned char* const text = sqlite3_column_text(&statement, index); if (text == nullptr) { if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) throw std::runtime_error( "Failed to read text from column " + std::to_string(index) + ": " + sqlite3_errmsg(&db)); value.clear(); return; } const int size = sqlite3_column_bytes(&statement, index); if (size <= 0) { if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) throw std::runtime_error( "Failed to get column bytes " + std::to_string(index) + ": " + sqlite3_errmsg(&db)); value.clear(); return; } value.reserve(static_cast(size)); value.assign(reinterpret_cast(text), reinterpret_cast(text) + size); } inline void copyColumn(sqlite3& db, sqlite3_stmt& statement, int index, int type, std::vector& value) { if (type != SQLITE_BLOB) throw std::logic_error("Type of column " + std::to_string(index) + " is " + sqliteTypeToString(type) + " that does not match expected output type: SQLITE_BLOB"); const void* const blob = sqlite3_column_blob(&statement, index); if (blob == nullptr) { if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) throw std::runtime_error( "Failed to read blob from column " + std::to_string(index) + ": " + sqlite3_errmsg(&db)); value.clear(); return; } const int size = sqlite3_column_bytes(&statement, index); if (size <= 0) { if (const int ec = sqlite3_errcode(&db); ec != SQLITE_OK) throw std::runtime_error( "Failed to get column bytes " + std::to_string(index) + ": " + sqlite3_errmsg(&db)); value.clear(); return; } value.reserve(static_cast(size)); value.assign(static_cast(blob), static_cast(blob) + size); } template inline void getColumnsImpl(sqlite3& db, sqlite3_stmt& statement, T& row) { if constexpr (0 < index && index <= std::tuple_size_v) { const int column = index - 1; if (const int number = sqlite3_column_count(&statement); column >= number) throw std::out_of_range( "Column number is out of range: " + std::to_string(column) + " >= " + std::to_string(number)); const int type = sqlite3_column_type(&statement, column); switch (type) { case SQLITE_INTEGER: case SQLITE_FLOAT: case SQLITE_TEXT: case SQLITE_BLOB: case SQLITE_NULL: copyColumn(db, statement, column, type, std::get(row)); break; default: throw std::runtime_error("Column " + std::to_string(column) + " has unnsupported column type: " + sqliteTypeToString(type)); } getColumnsImpl(db, statement, row); } } template inline void getColumns(sqlite3& db, sqlite3_stmt& statement, T& row) { getColumnsImpl>(db, statement, row); } template inline void getRow(sqlite3& db, sqlite3_stmt& statement, T& row) { auto tuple = std::tie(row); getColumns(db, statement, tuple); } template inline void getRow(sqlite3& db, sqlite3_stmt& statement, std::tuple& row) { getColumns(db, statement, row); } template inline void getRow(sqlite3& db, sqlite3_stmt& statement, std::back_insert_iterator& it) { typename T::value_type row; getRow(db, statement, row); it = std::move(row); } template inline void prepare(sqlite3& db, Statement& statement, Args&&... args) { if (statement.mNeedReset) { if (sqlite3_reset(statement.mHandle.get()) == SQLITE_OK && sqlite3_clear_bindings(statement.mHandle.get()) == SQLITE_OK) statement.mNeedReset = false; else statement.mHandle = makeStatementHandle(db, statement.mQuery.text()); } statement.mQuery.bind(db, *statement.mHandle, std::forward(args)...); } template inline bool executeStep(sqlite3& db, const Statement& statement) { switch (sqlite3_step(statement.mHandle.get())) { case SQLITE_ROW: return true; case SQLITE_DONE: return false; } throw std::runtime_error("Failed to execute statement step: " + std::string(sqlite3_errmsg(&db))); } template inline I request(sqlite3& db, Statement& statement, I out, std::size_t max, Args&&... args) { try { statement.mNeedReset = true; prepare(db, statement, std::forward(args)...); for (std::size_t i = 0; executeStep(db, statement) && i < max; ++i) getRow(db, *statement.mHandle, *out++); return out; } catch (const std::exception& e) { throw std::runtime_error( "Failed perform request \"" + std::string(statement.mQuery.text()) + "\": " + std::string(e.what())); } } template inline int execute(sqlite3& db, Statement& statement, Args&&... args) { try { statement.mNeedReset = true; prepare(db, statement, std::forward(args)...); if (executeStep(db, statement)) throw std::logic_error("Execute cannot return rows"); return sqlite3_changes(&db); } catch (const std::exception& e) { throw std::runtime_error("Failed to execute statement \"" + std::string(statement.mQuery.text()) + "\": " + std::string(e.what())); } } } #endif