mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-26 09:35:28 +00:00
Merge branch 'sqlite3_helpers' into 'master'
Add sqlite3 helpers See merge request OpenMW/openmw!1299
This commit is contained in:
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})
|
||||
|
15
apps/openmw_test_suite/sqlite3/db.cpp
Normal file
15
apps/openmw_test_suite/sqlite3/db.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
270
apps/openmw_test_suite/sqlite3/request.cpp
Normal file
270
apps/openmw_test_suite/sqlite3/request.cpp
Normal file
@ -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)));
|
||||
}
|
||||
}
|
25
apps/openmw_test_suite/sqlite3/statement.cpp
Normal file
25
apps/openmw_test_suite/sqlite3/statement.cpp
Normal file
@ -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");
|
||||
}
|
||||
}
|
67
apps/openmw_test_suite/sqlite3/transaction.cpp
Normal file
67
apps/openmw_test_suite/sqlite3/transaction.cpp
Normal file
@ -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})
|
||||
|
31
components/sqlite3/db.cpp
Normal file
31
components/sqlite3/db.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
21
components/sqlite3/db.hpp
Normal file
21
components/sqlite3/db.hpp
Normal file
@ -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
|
275
components/sqlite3/request.hpp
Normal file
275
components/sqlite3/request.hpp
Normal file
@ -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
|
24
components/sqlite3/statement.cpp
Normal file
24
components/sqlite3/statement.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
35
components/sqlite3/statement.hpp
Normal file
35
components/sqlite3/statement.hpp
Normal file
@ -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
|
33
components/sqlite3/transaction.cpp
Normal file
33
components/sqlite3/transaction.cpp
Normal file
@ -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();
|
||||
}
|
||||
}
|
27
components/sqlite3/transaction.hpp
Normal file
27
components/sqlite3/transaction.hpp
Normal file
@ -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…
x
Reference in New Issue
Block a user