2021-10-05 02:33:24 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <epee/misc_log_ex.h>
|
|
|
|
|
|
|
|
#include <SQLiteCpp/SQLiteCpp.h>
|
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <exception>
|
|
|
|
#include <string_view>
|
|
|
|
#include <shared_mutex>
|
|
|
|
#include <thread>
|
2022-05-18 20:28:58 +02:00
|
|
|
#include <unordered_map>
|
2021-10-05 02:33:24 +02:00
|
|
|
#include <unordered_set>
|
2021-10-14 02:29:35 +02:00
|
|
|
#include <optional>
|
2021-10-05 02:33:24 +02:00
|
|
|
|
2021-10-27 15:36:51 +02:00
|
|
|
#include "common/fs.h"
|
|
|
|
|
2021-10-05 02:33:24 +02:00
|
|
|
namespace db
|
|
|
|
{
|
|
|
|
template <typename T>
|
|
|
|
constexpr bool is_cstr = false;
|
|
|
|
template <size_t N>
|
2022-05-18 20:28:58 +02:00
|
|
|
inline constexpr bool is_cstr<char[N]> = true;
|
2021-10-05 02:33:24 +02:00
|
|
|
template <size_t N>
|
2022-05-18 20:28:58 +02:00
|
|
|
inline constexpr bool is_cstr<const char[N]> = true;
|
2021-10-05 02:33:24 +02:00
|
|
|
template <>
|
2022-04-29 01:51:14 +02:00
|
|
|
inline constexpr bool is_cstr<char*> = true;
|
2021-10-05 02:33:24 +02:00
|
|
|
template <>
|
2022-04-29 01:51:14 +02:00
|
|
|
inline constexpr bool is_cstr<const char*> = true;
|
2021-10-05 02:33:24 +02:00
|
|
|
|
|
|
|
// Simple wrapper class that can be used to bind a blob through the templated binding code below.
|
|
|
|
// E.g. `exec_query(st, 100, 42, blob_binder{data})` binds the third parameter using no-copy blob
|
|
|
|
// binding of the contained data.
|
|
|
|
struct blob_binder
|
|
|
|
{
|
|
|
|
std::string_view data;
|
|
|
|
explicit blob_binder(std::string_view d) : data{d}
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Binds a string_view as a no-copy blob at parameter index i.
|
2021-10-15 23:21:44 +02:00
|
|
|
inline void
|
2021-10-05 02:33:24 +02:00
|
|
|
bind_blob_ref(SQLite::Statement& st, int i, std::string_view blob)
|
|
|
|
{
|
|
|
|
st.bindNoCopy(i, static_cast<const void*>(blob.data()), blob.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace detail
|
|
|
|
{
|
|
|
|
template <typename T>
|
|
|
|
void
|
|
|
|
bind_oneshot_single(SQLite::Statement& st, int i, const T& val)
|
|
|
|
{
|
|
|
|
if constexpr (std::is_same_v<T, std::string> || is_cstr<T>)
|
|
|
|
st.bindNoCopy(i, val);
|
|
|
|
else if constexpr (std::is_same_v<T, blob_binder>)
|
|
|
|
bind_blob_ref(st, i, val.data);
|
|
|
|
else
|
|
|
|
st.bind(i, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename... T, int... Index>
|
|
|
|
void
|
|
|
|
bind_oneshot(SQLite::Statement& st, std::integer_sequence<int, Index...>, const T&... bind)
|
|
|
|
{
|
|
|
|
(bind_oneshot_single(st, Index + 1, bind), ...);
|
|
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
|
|
|
|
// Called from exec_query and similar to bind statement parameters for immediate execution.
|
|
|
|
// strings (and c strings) use no-copy binding; integer values are bound by value. You can bind a
|
|
|
|
// blob (by reference, like strings) by passing `blob_binder{data}`.
|
|
|
|
template <typename... T>
|
|
|
|
void
|
|
|
|
bind_oneshot(SQLite::Statement& st, const T&... bind)
|
|
|
|
{
|
|
|
|
detail::bind_oneshot(st, std::make_integer_sequence<int, sizeof...(T)>{}, bind...);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Executes a query that does not expect results. Optionally binds parameters, if provided.
|
|
|
|
// Returns the number of affected rows; throws on error or if results are returned.
|
|
|
|
template <typename... T, int... Index>
|
|
|
|
int
|
|
|
|
exec_query(SQLite::Statement& st, const T&... bind)
|
|
|
|
{
|
|
|
|
bind_oneshot(st, bind...);
|
|
|
|
return st.exec();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Same as above, but prepares a literal query on the fly for use with queries that are only used
|
|
|
|
// once.
|
|
|
|
template <typename... T>
|
|
|
|
int
|
|
|
|
exec_query(SQLite::Database& db, const char* query, const T&... bind)
|
|
|
|
{
|
|
|
|
SQLite::Statement st{db, query};
|
|
|
|
return exec_query(st, bind...);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename T, typename... More>
|
|
|
|
struct first_type
|
|
|
|
{
|
|
|
|
using type = T;
|
|
|
|
};
|
|
|
|
template <typename... T>
|
|
|
|
using first_type_t = typename first_type<T...>::type;
|
|
|
|
|
|
|
|
template <typename... T>
|
|
|
|
using type_or_tuple = std::conditional_t<sizeof...(T) == 1, first_type_t<T...>, std::tuple<T...>>;
|
|
|
|
|
|
|
|
// Retrieves a single row of values from the current state of a statement (i.e. after a
|
|
|
|
// executeStep() call that is expecting a return value). If `T...` is a single type then this
|
|
|
|
// returns the single T value; if T... has multiple types then you get back a tuple of values.
|
|
|
|
template <typename T>
|
|
|
|
T
|
|
|
|
get(SQLite::Statement& st)
|
|
|
|
{
|
|
|
|
return static_cast<T>(st.getColumn(0));
|
|
|
|
}
|
|
|
|
template <typename T1, typename T2, typename... Tn>
|
|
|
|
std::tuple<T1, T2, Tn...>
|
|
|
|
get(SQLite::Statement& st)
|
|
|
|
{
|
|
|
|
return st.getColumns<std::tuple<T1, T2, Tn...>, 2 + sizeof...(Tn)>();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Steps a statement to completion that is expected to return at most one row, optionally binding
|
|
|
|
// values into it (if provided). Returns a filled out optional<T> (or optional<std::tuple<T...>>)
|
|
|
|
// if a row was retrieved, otherwise a nullopt. Throws if more than one row is retrieved.
|
|
|
|
template <typename... T, typename... Args>
|
|
|
|
std::optional<type_or_tuple<T...>>
|
|
|
|
exec_and_maybe_get(SQLite::Statement& st, const Args&... bind)
|
|
|
|
{
|
2021-10-17 04:32:32 +02:00
|
|
|
bind_oneshot(st, bind...);
|
2021-10-05 02:33:24 +02:00
|
|
|
std::optional<type_or_tuple<T...>> result;
|
|
|
|
while (st.executeStep())
|
|
|
|
{
|
|
|
|
if (result)
|
|
|
|
{
|
|
|
|
MERROR("Expected single-row result, got multiple rows from {}" << st.getQuery());
|
|
|
|
throw std::runtime_error{"DB error: expected single-row result, got multiple rows"};
|
|
|
|
}
|
|
|
|
result = get<T...>(st);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Executes a statement to completion that is expected to return exactly one row, optionally
|
|
|
|
// binding values into it (if provided). Returns a T or std::tuple<T...> (depending on whether or
|
|
|
|
// not more than one T is provided) for the row. Throws an exception if no rows or more than one
|
|
|
|
// row are returned.
|
|
|
|
template <typename... T, typename... Args>
|
|
|
|
type_or_tuple<T...>
|
|
|
|
exec_and_get(SQLite::Statement& st, const Args&... bind)
|
|
|
|
{
|
|
|
|
auto maybe_result = exec_and_maybe_get<T...>(st, bind...);
|
|
|
|
if (!maybe_result)
|
|
|
|
{
|
|
|
|
MERROR("Expected single-row result, got no rows from {}" << st.getQuery());
|
2021-10-14 02:29:35 +02:00
|
|
|
throw std::runtime_error{"DB error: expected single-row result, got no rows"};
|
2021-10-05 02:33:24 +02:00
|
|
|
}
|
|
|
|
return *std::move(maybe_result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Executes a query to completion, collecting each row into a vector<T> (or vector<tuple<T...>> if
|
|
|
|
// multiple T are given). Can optionally bind before executing.
|
|
|
|
template <typename... T, typename... Bind>
|
|
|
|
std::vector<type_or_tuple<T...>>
|
|
|
|
get_all(SQLite::Statement& st, const Bind&... bind)
|
|
|
|
{
|
2021-10-17 04:32:32 +02:00
|
|
|
bind_oneshot(st, bind...);
|
2021-10-05 02:33:24 +02:00
|
|
|
std::vector<type_or_tuple<T...>> results;
|
|
|
|
while (st.executeStep())
|
|
|
|
results.push_back(get<T...>(st));
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Takes a query prefix and suffix and places <count> ? separated by commas between them
|
|
|
|
// Example: multi_in_query("foo(", 3, ")bar") will return "foo(?,?,?)bar"
|
2022-05-18 20:28:58 +02:00
|
|
|
std::string multi_in_query(std::string_view prefix, size_t count, std::string_view suffix);
|
2021-10-05 02:33:24 +02:00
|
|
|
|
|
|
|
// Storage database class.
|
|
|
|
class Database
|
|
|
|
{
|
2021-10-15 23:21:44 +02:00
|
|
|
public:
|
2021-10-17 04:32:32 +02:00
|
|
|
// This must be declared *before* the prepared statements container,
|
|
|
|
// so that it is destroyed *after* because sqlite_close() fails if any
|
|
|
|
// prepared statements are not finalized.
|
2021-10-15 23:21:44 +02:00
|
|
|
SQLite::Database db;
|
|
|
|
|
|
|
|
private:
|
2021-10-05 02:33:24 +02:00
|
|
|
// SQLiteCpp's statements are not thread-safe, so we prepare them thread-locally when needed
|
|
|
|
std::unordered_map<std::thread::id, std::unordered_map<std::string, SQLite::Statement>>
|
|
|
|
prepared_sts;
|
|
|
|
std::shared_mutex prepared_sts_mutex;
|
|
|
|
|
|
|
|
/** Wrapper around a SQLite::Statement that calls `tryReset()` on destruction of the wrapper. */
|
|
|
|
class StatementWrapper
|
|
|
|
{
|
2022-05-26 19:53:32 +02:00
|
|
|
protected:
|
2021-10-05 02:33:24 +02:00
|
|
|
SQLite::Statement& st;
|
|
|
|
|
|
|
|
public:
|
|
|
|
/// Whether we should reset on destruction; can be set to false if needed.
|
|
|
|
bool reset_on_destruction = true;
|
|
|
|
|
2022-05-26 19:53:32 +02:00
|
|
|
explicit StatementWrapper(SQLite::Statement& st) noexcept : st{st} {}
|
|
|
|
|
|
|
|
StatementWrapper(StatementWrapper&& sw) noexcept : st{sw.st}
|
|
|
|
{
|
|
|
|
sw.reset_on_destruction = false;
|
|
|
|
}
|
|
|
|
|
2021-10-05 02:33:24 +02:00
|
|
|
~StatementWrapper() noexcept
|
|
|
|
{
|
|
|
|
if (reset_on_destruction)
|
|
|
|
st.tryReset();
|
|
|
|
}
|
2022-05-26 19:53:32 +02:00
|
|
|
SQLite::Statement& operator*() noexcept { return st; }
|
|
|
|
SQLite::Statement* operator->() noexcept { return &st; }
|
|
|
|
operator SQLite::Statement&() noexcept { return st; }
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Extends the above with the ability to iterate through results. */
|
|
|
|
template <typename... T>
|
|
|
|
class IterableStatementWrapper : StatementWrapper
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
using StatementWrapper::StatementWrapper;
|
|
|
|
|
|
|
|
class iterator {
|
|
|
|
IterableStatementWrapper& sw;
|
|
|
|
bool finished;
|
|
|
|
explicit iterator(IterableStatementWrapper& sw, bool finished = false) : sw{sw}, finished{finished}
|
|
|
|
{
|
|
|
|
++*this;
|
|
|
|
}
|
|
|
|
friend class IterableStatementWrapper;
|
|
|
|
|
|
|
|
public:
|
|
|
|
iterator(const iterator&) = delete;
|
|
|
|
iterator(iterator&&) = delete;
|
|
|
|
void operator=(const iterator&) = delete;
|
|
|
|
void operator=(iterator&&) = delete;
|
|
|
|
|
|
|
|
type_or_tuple<T...> operator*() { return get<T...>(sw); }
|
|
|
|
|
|
|
|
iterator& operator++()
|
|
|
|
{
|
|
|
|
if (!finished)
|
|
|
|
finished = !sw->executeStep();
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
void operator++(int) { ++*this; }
|
|
|
|
bool operator==(const iterator& other) { return &sw == &other.sw && finished == other.finished; }
|
|
|
|
bool operator!=(const iterator& other) { return !(*this == other); }
|
|
|
|
|
|
|
|
using value_type = type_or_tuple<T...>;
|
|
|
|
using reference = value_type;
|
|
|
|
using difference_type = std::ptrdiff_t;
|
|
|
|
using pointer = const value_type*;
|
|
|
|
using iterator_category = std::input_iterator_tag;
|
|
|
|
};
|
|
|
|
|
|
|
|
iterator begin() { return iterator{*this}; }
|
|
|
|
iterator end() { return iterator{*this, true}; }
|
2021-10-05 02:33:24 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
2022-05-26 18:57:01 +02:00
|
|
|
/// Prepares a query, caching it, and returns a wrapper that automatically resets the prepared
|
|
|
|
/// statement on destruction.
|
2021-10-05 02:33:24 +02:00
|
|
|
StatementWrapper
|
2022-05-18 20:28:58 +02:00
|
|
|
prepared_st(const std::string& query);
|
2021-10-05 02:33:24 +02:00
|
|
|
|
2022-05-26 18:57:01 +02:00
|
|
|
/// Prepares (with caching) and binds a query, returning the active statement handle. Like
|
|
|
|
/// `prepared_st` the wrapper resets the prepared statement on destruction.
|
|
|
|
template <typename... T>
|
|
|
|
StatementWrapper
|
|
|
|
prepared_bind(const std::string& query, const T&... bind)
|
|
|
|
{
|
|
|
|
auto st = prepared_st(query);
|
|
|
|
bind_oneshot(st, bind...);
|
|
|
|
return st;
|
|
|
|
}
|
|
|
|
|
2022-05-26 19:53:32 +02:00
|
|
|
/// Prepares (with caching), binds parameters, then returns an object that lets you iterate
|
|
|
|
/// through results where each row is a T or tuple<T...>:
|
|
|
|
template <typename... T, typename... Bind, typename = std::enable_if_t<sizeof...(T) != 0>>
|
|
|
|
IterableStatementWrapper<T...>
|
|
|
|
prepared_results(const std::string& query, const Bind&... bind)
|
|
|
|
{
|
|
|
|
return IterableStatementWrapper<T...>{prepared_bind(query, bind...)};
|
|
|
|
}
|
|
|
|
|
2022-05-26 18:57:01 +02:00
|
|
|
/// Prepares (with caching) a query and then executes it, optionally binding the given
|
|
|
|
/// parameters when executing.
|
2021-10-05 02:33:24 +02:00
|
|
|
template <typename... T>
|
|
|
|
int
|
|
|
|
prepared_exec(const std::string& query, const T&... bind)
|
|
|
|
{
|
|
|
|
return exec_query(prepared_st(query), bind...);
|
|
|
|
}
|
|
|
|
|
2022-05-26 18:57:01 +02:00
|
|
|
/// Prepares (with caching) a query that returns a single row (with optional bind parameters),
|
|
|
|
/// executes it, and returns the value. Throws if the query returns 0 or more than 1 rows.
|
2021-10-05 02:33:24 +02:00
|
|
|
template <typename... T, typename... Bind>
|
|
|
|
auto
|
|
|
|
prepared_get(const std::string& query, const Bind&... bind)
|
|
|
|
{
|
|
|
|
return exec_and_get<T...>(prepared_st(query), bind...);
|
|
|
|
}
|
|
|
|
|
2022-05-26 18:57:01 +02:00
|
|
|
/// Prepares (with caching) a query that returns at most a single row (with optional bind
|
|
|
|
/// parameters), executes it, and returns the value or nullopt if the query returned no rows.
|
|
|
|
/// Throws if the query returns more than 1 rows.
|
2021-10-14 02:29:35 +02:00
|
|
|
template <typename... T, typename... Bind>
|
|
|
|
auto
|
|
|
|
prepared_maybe_get(const std::string& query, const Bind&... bind)
|
|
|
|
{
|
|
|
|
return exec_and_maybe_get<T...>(prepared_st(query), bind...);
|
|
|
|
}
|
|
|
|
|
2022-05-18 20:28:58 +02:00
|
|
|
explicit Database(const fs::path& db_path, const std::string_view db_password);
|
2021-10-05 02:33:24 +02:00
|
|
|
|
|
|
|
~Database() = default;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace db
|