mirror of https://github.com/OpenMW/openmw.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
292 lines
11 KiB
C++
292 lines
11 KiB
C++
#ifndef OPENMW_COMPONENTS_SQLITE3_REQUEST_H
|
|
#define OPENMW_COMPONENTS_SQLITE3_REQUEST_H
|
|
|
|
#include "statement.hpp"
|
|
#include "types.hpp"
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <iterator>
|
|
#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)));
|
|
}
|
|
|
|
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 <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
|