Merge branch 'sqlite3_helpers' into 'master'

Add sqlite3 helpers

See merge request OpenMW/openmw!1299
pull/3179/head
psi29a 3 years ago
commit c051298848

@ -43,6 +43,11 @@ if (GTEST_FOUND AND GMOCK_FOUND)
../openmw/options.cpp
openmw/options.cpp
sqlite3/db.cpp
sqlite3/request.cpp
sqlite3/statement.cpp
sqlite3/transaction.cpp
)
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})

@ -0,0 +1,15 @@
#include <components/sqlite3/db.hpp>
#include <gtest/gtest.h>
namespace
{
using namespace testing;
using namespace Sqlite3;
TEST(Sqlite3DbTest, makeDbShouldCreateInMemoryDbWithSchema)
{
const auto db = makeDb(":memory:", "CREATE TABLE test ( id INTEGER )");
EXPECT_NE(db, nullptr);
}
}

@ -0,0 +1,270 @@
#include <components/sqlite3/db.hpp>
#include <components/sqlite3/request.hpp>
#include <components/sqlite3/statement.hpp>
#include <components/sqlite3/transaction.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <limits>
#include <tuple>
#include <vector>
namespace
{
using namespace testing;
using namespace Sqlite3;
template <class T>
struct InsertInt
{
static std::string_view text() noexcept { return "INSERT INTO ints (value) VALUES (:value)"; }
static void bind(sqlite3& db, sqlite3_stmt& statement, T value)
{
bindParameter(db, statement, ":value", value);
}
};
struct InsertReal
{
static std::string_view text() noexcept { return "INSERT INTO reals (value) VALUES (:value)"; }
static void bind(sqlite3& db, sqlite3_stmt& statement, double value)
{
bindParameter(db, statement, ":value", value);
}
};
struct InsertText
{
static std::string_view text() noexcept { return "INSERT INTO texts (value) VALUES (:value)"; }
static void bind(sqlite3& db, sqlite3_stmt& statement, std::string_view value)
{
bindParameter(db, statement, ":value", value);
}
};
struct InsertBlob
{
static std::string_view text() noexcept { return "INSERT INTO blobs (value) VALUES (:value)"; }
static void bind(sqlite3& db, sqlite3_stmt& statement, const std::vector<std::byte>& value)
{
bindParameter(db, statement, ":value", value);
}
};
struct GetAll
{
std::string mQuery;
explicit GetAll(const std::string& table) : mQuery("SELECT value FROM " + table + " ORDER BY value") {}
std::string_view text() noexcept { return mQuery; }
static void bind(sqlite3&, sqlite3_stmt&) {}
};
template <class T>
struct GetExact
{
std::string mQuery;
explicit GetExact(const std::string& table) : mQuery("SELECT value FROM " + table + " WHERE value = :value") {}
std::string_view text() noexcept { return mQuery; }
static void bind(sqlite3& db, sqlite3_stmt& statement, const T& value)
{
bindParameter(db, statement, ":value", value);
}
};
struct GetInt64
{
static std::string_view text() noexcept { return "SELECT value FROM ints WHERE value = :value"; }
static void bind(sqlite3& db, sqlite3_stmt& statement, std::int64_t value)
{
bindParameter(db, statement, ":value", value);
}
};
struct GetNull
{
static std::string_view text() noexcept { return "SELECT NULL"; }
static void bind(sqlite3&, sqlite3_stmt&) {}
};
struct Int
{
int mValue = 0;
Int() = default;
explicit Int(int value) : mValue(value) {}
Int& operator=(int value)
{
mValue = value;
return *this;
}
friend bool operator==(const Int& l, const Int& r)
{
return l.mValue == r.mValue;
}
};
constexpr const char schema[] = R"(
CREATE TABLE ints ( value INTEGER );
CREATE TABLE reals ( value REAL );
CREATE TABLE texts ( value TEXT );
CREATE TABLE blobs ( value BLOB );
)";
struct Sqlite3RequestTest : Test
{
const Db mDb = makeDb(":memory:", schema);
};
TEST_F(Sqlite3RequestTest, executeShouldSupportInt)
{
Statement insert(*mDb, InsertInt<int> {});
EXPECT_EQ(execute(*mDb, insert, 13), 1);
EXPECT_EQ(execute(*mDb, insert, 42), 1);
Statement select(*mDb, GetAll("ints"));
std::vector<std::tuple<int>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max());
EXPECT_THAT(result, ElementsAre(std::tuple(13), std::tuple(42)));
}
TEST_F(Sqlite3RequestTest, executeShouldSupportInt64)
{
Statement insert(*mDb, InsertInt<std::int64_t> {});
const std::int64_t value = 1099511627776;
EXPECT_EQ(execute(*mDb, insert, value), 1);
Statement select(*mDb, GetAll("ints"));
std::vector<std::tuple<int>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max());
EXPECT_THAT(result, ElementsAre(std::tuple(value)));
}
TEST_F(Sqlite3RequestTest, executeShouldSupportReal)
{
Statement insert(*mDb, InsertReal {});
EXPECT_EQ(execute(*mDb, insert, 3.14), 1);
Statement select(*mDb, GetAll("reals"));
std::vector<std::tuple<double>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max());
EXPECT_THAT(result, ElementsAre(std::tuple(3.14)));
}
TEST_F(Sqlite3RequestTest, executeShouldSupportText)
{
Statement insert(*mDb, InsertText {});
const std::string text = "foo";
EXPECT_EQ(execute(*mDb, insert, text), 1);
Statement select(*mDb, GetAll("texts"));
std::vector<std::tuple<std::string>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max());
EXPECT_THAT(result, ElementsAre(std::tuple(text)));
}
TEST_F(Sqlite3RequestTest, executeShouldSupportBlob)
{
Statement insert(*mDb, InsertBlob {});
const std::vector<std::byte> blob({std::byte(42), std::byte(13)});
EXPECT_EQ(execute(*mDb, insert, blob), 1);
Statement select(*mDb, GetAll("blobs"));
std::vector<std::tuple<std::vector<std::byte>>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max());
EXPECT_THAT(result, ElementsAre(std::tuple(blob)));
}
TEST_F(Sqlite3RequestTest, requestShouldSupportInt)
{
Statement insert(*mDb, InsertInt<int> {});
const int value = 42;
EXPECT_EQ(execute(*mDb, insert, value), 1);
Statement select(*mDb, GetExact<int>("ints"));
std::vector<std::tuple<int>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max(), value);
EXPECT_THAT(result, ElementsAre(std::tuple(value)));
}
TEST_F(Sqlite3RequestTest, requestShouldSupportInt64)
{
Statement insert(*mDb, InsertInt<std::int64_t> {});
const std::int64_t value = 1099511627776;
EXPECT_EQ(execute(*mDb, insert, value), 1);
Statement select(*mDb, GetExact<std::int64_t>("ints"));
std::vector<std::tuple<int>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max(), value);
EXPECT_THAT(result, ElementsAre(std::tuple(value)));
}
TEST_F(Sqlite3RequestTest, requestShouldSupportReal)
{
Statement insert(*mDb, InsertReal {});
const double value = 3.14;
EXPECT_EQ(execute(*mDb, insert, value), 1);
Statement select(*mDb, GetExact<double>("reals"));
std::vector<std::tuple<double>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max(), value);
EXPECT_THAT(result, ElementsAre(std::tuple(value)));
}
TEST_F(Sqlite3RequestTest, requestShouldSupportText)
{
Statement insert(*mDb, InsertText {});
const std::string text = "foo";
EXPECT_EQ(execute(*mDb, insert, text), 1);
Statement select(*mDb, GetExact<std::string>("texts"));
std::vector<std::tuple<std::string>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max(), text);
EXPECT_THAT(result, ElementsAre(std::tuple(text)));
}
TEST_F(Sqlite3RequestTest, requestShouldSupportBlob)
{
Statement insert(*mDb, InsertBlob {});
const std::vector<std::byte> blob({std::byte(42), std::byte(13)});
EXPECT_EQ(execute(*mDb, insert, blob), 1);
Statement select(*mDb, GetExact<std::vector<std::byte>>("blobs"));
std::vector<std::tuple<std::vector<std::byte>>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max(), blob);
EXPECT_THAT(result, ElementsAre(std::tuple(blob)));
}
TEST_F(Sqlite3RequestTest, requestResultShouldSupportNull)
{
Statement select(*mDb, GetNull {});
std::vector<std::tuple<void*>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max());
EXPECT_THAT(result, ElementsAre(std::tuple(nullptr)));
}
TEST_F(Sqlite3RequestTest, requestResultShouldSupportConstructibleFromInt)
{
Statement insert(*mDb, InsertInt<int> {});
const int value = 42;
EXPECT_EQ(execute(*mDb, insert, value), 1);
Statement select(*mDb, GetExact<int>("ints"));
std::vector<std::tuple<Int>> result;
request(*mDb, select, std::back_inserter(result), std::numeric_limits<std::size_t>::max(), value);
EXPECT_THAT(result, ElementsAre(std::tuple(Int(value))));
}
TEST_F(Sqlite3RequestTest, requestShouldLimitOutput)
{
Statement insert(*mDb, InsertInt<int> {});
EXPECT_EQ(execute(*mDb, insert, 13), 1);
EXPECT_EQ(execute(*mDb, insert, 42), 1);
Statement select(*mDb, GetAll("ints"));
std::vector<std::tuple<int>> result;
request(*mDb, select, std::back_inserter(result), 1);
EXPECT_THAT(result, ElementsAre(std::tuple(13)));
}
}

@ -0,0 +1,25 @@
#include <components/sqlite3/db.hpp>
#include <components/sqlite3/statement.hpp>
#include <gtest/gtest.h>
namespace
{
using namespace testing;
using namespace Sqlite3;
struct Query
{
static std::string_view text() noexcept { return "SELECT 1"; }
static void bind(sqlite3&, sqlite3_stmt&) {}
};
TEST(Sqlite3StatementTest, makeStatementShouldCreateStatementWithPreparedQuery)
{
const auto db = makeDb(":memory:", "CREATE TABLE test ( id INTEGER )");
const Statement statement(*db, Query {});
EXPECT_FALSE(statement.mNeedReset);
EXPECT_NE(statement.mHandle, nullptr);
EXPECT_EQ(statement.mQuery.text(), "SELECT 1");
}
}

@ -0,0 +1,67 @@
#include <components/sqlite3/db.hpp>
#include <components/sqlite3/request.hpp>
#include <components/sqlite3/statement.hpp>
#include <components/sqlite3/transaction.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <limits>
#include <tuple>
#include <vector>
namespace
{
using namespace testing;
using namespace Sqlite3;
struct InsertId
{
static std::string_view text() noexcept { return "INSERT INTO test (id) VALUES (42)"; }
static void bind(sqlite3&, sqlite3_stmt&) {}
};
struct GetIds
{
static std::string_view text() noexcept { return "SELECT id FROM test"; }
static void bind(sqlite3&, sqlite3_stmt&) {}
};
struct Sqlite3TransactionTest : Test
{
const Db mDb = makeDb(":memory:", "CREATE TABLE test ( id INTEGER )");
void insertId() const
{
Statement insertId(*mDb, InsertId {});
EXPECT_EQ(execute(*mDb, insertId), 1);
}
std::vector<std::tuple<int>> getIds() const
{
Statement getIds(*mDb, GetIds {});
std::vector<std::tuple<int>> result;
request(*mDb, getIds, std::back_inserter(result), std::numeric_limits<std::size_t>::max());
return result;
}
};
TEST_F(Sqlite3TransactionTest, shouldRollbackOnDestruction)
{
{
const Transaction transaction(*mDb);
insertId();
}
EXPECT_THAT(getIds(), IsEmpty());
}
TEST_F(Sqlite3TransactionTest, commitShouldCommitTransaction)
{
{
Transaction transaction(*mDb);
insertId();
transaction.commit();
}
EXPECT_THAT(getIds(), ElementsAre(std::tuple(42)));
}
}

@ -201,6 +201,12 @@ add_component_dir(loadinglistener
reporter
)
add_component_dir(sqlite3
db
statement
transaction
)
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
)
@ -236,6 +242,8 @@ endif ()
include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
find_package(SQLite3 REQUIRED)
add_library(components STATIC ${COMPONENT_FILES})
target_link_libraries(components
@ -268,6 +276,7 @@ target_link_libraries(components
RecastNavigation::Recast
Base64
SQLite::SQLite3
)
target_link_libraries(components ${BULLET_LIBRARIES})

@ -0,0 +1,31 @@
#include "db.hpp"
#include <sqlite3.h>
#include <stdexcept>
#include <string>
#include <string_view>
namespace Sqlite3
{
void CloseSqlite3::operator()(sqlite3* handle) const noexcept
{
sqlite3_close(handle);
}
Db makeDb(std::string_view path, const char* schema)
{
sqlite3* handle = nullptr;
const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
if (const int ec = sqlite3_open_v2(std::string(path).c_str(), &handle, flags, nullptr); ec != SQLITE_OK)
{
const std::string message(sqlite3_errmsg(handle));
sqlite3_close(handle);
throw std::runtime_error("Failed to open database: " + message);
}
Db result(handle);
if (const int ec = sqlite3_exec(result.get(), schema, nullptr, nullptr, nullptr); ec != SQLITE_OK)
throw std::runtime_error("Failed create database schema: " + std::string(sqlite3_errmsg(handle)));
return result;
}
}

@ -0,0 +1,21 @@
#ifndef OPENMW_COMPONENTS_SQLITE3_DB_H
#define OPENMW_COMPONENTS_SQLITE3_DB_H
#include <memory>
#include <string_view>
struct sqlite3;
namespace Sqlite3
{
struct CloseSqlite3
{
void operator()(sqlite3* handle) const noexcept;
};
using Db = std::unique_ptr<sqlite3, CloseSqlite3>;
Db makeDb(std::string_view path, const char* schema);
}
#endif

@ -0,0 +1,275 @@
#ifndef OPENMW_COMPONENTS_SQLITE3_REQUEST_H
#define OPENMW_COMPONENTS_SQLITE3_REQUEST_H
#include "statement.hpp"
#include <sqlite3.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
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<int>(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<std::byte>& value)
{
if (sqlite3_bind_blob(&stmt, index, value.data(), static_cast<int>(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)));
}
template <typename T>
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 <class T>
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 <class T>
inline auto copyColumn(sqlite3& /*db*/, sqlite3_stmt& statement, int index, int type, T& value)
{
switch (type)
{
case SQLITE_INTEGER:
value = static_cast<T>(sqlite3_column_int64(&statement, index));
return;
case SQLITE_FLOAT:
value = static_cast<T>(sqlite3_column_double(&statement, index));
return;
case SQLITE_NULL:
value = std::decay_t<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<std::size_t>(size));
value.assign(reinterpret_cast<const char*>(text), reinterpret_cast<const char*>(text) + size);
}
inline void copyColumn(sqlite3& db, sqlite3_stmt& statement, int index, int type, std::vector<std::byte>& 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<std::size_t>(size));
value.assign(static_cast<const std::byte*>(blob), static_cast<const std::byte*>(blob) + size);
}
template <int index, class T>
inline void getColumnsImpl(sqlite3& db, sqlite3_stmt& statement, T& row)
{
if constexpr (0 < index && index <= std::tuple_size_v<T>)
{
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<index - 1>(row));
break;
default:
throw std::runtime_error("Column " + std::to_string(column)
+ " has unnsupported column type: " + sqliteTypeToString(type));
}
getColumnsImpl<index - 1>(db, statement, row);
}
}
template <class T>
inline void getColumns(sqlite3& db, sqlite3_stmt& statement, T& row)
{
getColumnsImpl<std::tuple_size_v<T>>(db, statement, row);
}
template <class T>
inline void getRow(sqlite3& db, sqlite3_stmt& statement, T& row)
{
auto tuple = std::tie(row);
getColumns(db, statement, tuple);
}
template <class ... Args>
inline void getRow(sqlite3& db, sqlite3_stmt& statement, std::tuple<Args ...>& row)
{
getColumns(db, statement, row);
}
template <class T>
inline void getRow(sqlite3& db, sqlite3_stmt& statement, std::back_insert_iterator<T>& it)
{
typename T::value_type row;
getRow(db, statement, row);
it = std::move(row);
}
template <class T, class ... Args>
inline void prepare(sqlite3& db, Statement<T>& 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>(args) ...);
}
template <class T>
inline bool executeStep(sqlite3& db, const Statement<T>& 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 <class T, class I, class ... Args>
inline I request(sqlite3& db, Statement<T>& statement, I out, std::size_t max, Args&& ... args)
{
try
{
statement.mNeedReset = true;
prepare(db, statement, std::forward<Args>(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 <class T, class ... Args>
inline int execute(sqlite3& db, Statement<T>& statement, Args&& ... args)
{
try
{
statement.mNeedReset = true;
prepare(db, statement, std::forward<Args>(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

@ -0,0 +1,24 @@
#include "statement.hpp"
#include <sqlite3.h>
#include <stdexcept>
#include <string>
#include <string_view>
namespace Sqlite3
{
void CloseSqlite3Stmt::operator()(sqlite3_stmt* handle) const noexcept
{
sqlite3_finalize(handle);
}
StatementHandle makeStatementHandle(sqlite3& db, std::string_view query)
{
sqlite3_stmt* stmt = nullptr;
if (const int ec = sqlite3_prepare_v2(&db, query.data(), static_cast<int>(query.size()), &stmt, nullptr); ec != SQLITE_OK)
throw std::runtime_error("Failed to prepare statement for query \"" + std::string(query) + "\": "
+ std::string(sqlite3_errmsg(&db)));
return StatementHandle(stmt);
}
}

@ -0,0 +1,35 @@
#ifndef OPENMW_COMPONENTS_SQLITE3_STATEMENT_H
#define OPENMW_COMPONENTS_SQLITE3_STATEMENT_H
#include <memory>
#include <string_view>
#include <utility>
struct sqlite3;
struct sqlite3_stmt;
namespace Sqlite3
{
struct CloseSqlite3Stmt
{
void operator()(sqlite3_stmt* handle) const noexcept;
};
using StatementHandle = std::unique_ptr<sqlite3_stmt, CloseSqlite3Stmt>;
StatementHandle makeStatementHandle(sqlite3& db, std::string_view query);
template <class Query>
struct Statement
{
bool mNeedReset = false;
StatementHandle mHandle;
Query mQuery;
explicit Statement(sqlite3& db, Query query = Query {})
: mHandle(makeStatementHandle(db, query.text())),
mQuery(std::move(query)) {}
};
}
#endif

@ -0,0 +1,33 @@
#include "transaction.hpp"
#include <components/debug/debuglog.hpp>
#include <sqlite3.h>
#include <stdexcept>
#include <string>
namespace Sqlite3
{
void Rollback::operator()(sqlite3* db) const
{
if (db == nullptr)
return;
if (const int ec = sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr); ec != SQLITE_OK)
Log(Debug::Warning) << "Failed to rollback SQLite3 transaction: " << std::string(sqlite3_errmsg(db));
}
Transaction::Transaction(sqlite3& db)
: mDb(&db)
{
if (const int ec = sqlite3_exec(mDb.get(), "BEGIN", nullptr, nullptr, nullptr); ec != SQLITE_OK)
throw std::runtime_error("Failed to start transaction: " + std::string(sqlite3_errmsg(mDb.get())));
}
void Transaction::commit()
{
if (const int ec = sqlite3_exec(mDb.get(), "COMMIT", nullptr, nullptr, nullptr); ec != SQLITE_OK)
throw std::runtime_error("Failed to commit transaction: " + std::string(sqlite3_errmsg(mDb.get())));
(void) mDb.release();
}
}

@ -0,0 +1,27 @@
#ifndef OPENMW_COMPONENTS_SQLITE3_TRANSACTION_H
#define OPENMW_COMPONENTS_SQLITE3_TRANSACTION_H
#include <memory>
struct sqlite3;
namespace Sqlite3
{
struct Rollback
{
void operator()(sqlite3* handle) const;
};
class Transaction
{
public:
Transaction(sqlite3& db);
void commit();
private:
std::unique_ptr<sqlite3, Rollback> mDb;
};
}
#endif
Loading…
Cancel
Save