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.
openmw/components/sqlite3/request.hpp

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