1
0
forked from cheng/wallet
wallet/db_accessors.h

223 lines
7.2 KiB
C
Raw Normal View History

#pragma once
namespace ro {
// Compile time test to see if a type can be directly read from or written to an sqlite3 file
// This can be used in if constexpr (is_sqlite3_field_type<T>::value)
template <class T> struct is_sqlite3_field_type
{
template <typename U> static constexpr decltype(std::declval<Icompiled_sql>().Isqlite3_bind(1, std::declval<U>()), bool()) test() {
return true;
}
template <typename U> static constexpr bool test(int = 0) {
return false;
}
static constexpr bool value = is_sqlite3_field_type::template test<T>();
};
static_assert(is_sqlite3_field_type<int>::value);
//Owns a compiled sql statement and destroys it when it is deconstructed.
//Has move semantics.
class sql : public std::unique_ptr<Icompiled_sql> {
public:
class monostate {};
sql(ISqlite3* p, const char* sz) :std::unique_ptr<Icompiled_sql>(sqlite3_prepare(p, sz)) {}
sql(const std::unique_ptr<ISqlite3>& p, const char* sz) :std::unique_ptr<Icompiled_sql>(sqlite3_prepare(p.get(), sz)) {}
// copy constructor
sql(const sql& a) = delete;
// move constructor
sql(sql&& p) :std::unique_ptr<Icompiled_sql>(p.release()) { }
// copy assignment
sql& operator=(const sql) = delete;
// Move assignment
sql& operator=(sql&& p) {
std::unique_ptr<Icompiled_sql>::reset(p.release());
}
sql(Icompiled_sql* p) :std::unique_ptr<Icompiled_sql>(p) {}
sql(std::unique_ptr<Icompiled_sql>&& p) :std::unique_ptr<Icompiled_sql>(p.release()) { }
~sql() = default;
template <typename T>auto column(int i) const {
if constexpr (ro::is_blob_field_type<T>::value) {
auto st = (*this)->Isqlite3_column_blob(i);
if (st.size_bytes() != sizeof(T)) throw BadDataException();
static_assert (std::is_standard_layout<T>(), "not standard layout");
static_assert (std::is_trivial<T>(), "not trivial");
return reinterpret_cast<const T*>(&st[0]);
}
else if constexpr (std::is_integral<T>::value) {
if constexpr (sizeof(T) > sizeof(int_least32_t)) {
T retval = (*this)->Isqlite3_column_int64(i);
return retval;
}
else {
T retval = (*this)->Isqlite3_column_int(i);
return retval;
}
}
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::span<const byte>>) {
return (*this)->Isqlite3_column_blob(i);
}
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, const char*>) {
auto sz{ (*this)->Isqlite3_column_text(i) };
// if (!IsValidUtf8String(sz)) throw NonUtf8DataInDatabase();
return sz;
}
else {
assert(false);
//, "Don't know how to read this datatype from database");
return monostate(); //should generate somewhat relevant type error at compile time.
}
}
template <typename T>const sql& read(int i, T& j) const{
if constexpr (ro::is_blob_field_type<T>::value) {
auto st = (*this)->Isqlite3_column_blob(i);
if (st.size_bytes() != sizeof(T)) throw BadDataException();
static_assert (std::is_standard_layout<T>(), "not standard layout");
static_assert (std::is_trivial<T>(), "not trivial");
j = *reinterpret_cast<const T*>(&st[0]);
}
else if constexpr (std::is_integral<T>::value) {
if constexpr (sizeof(T) > sizeof(int_least32_t)) {
j = (*this)->Isqlite3_column_int64(i);
}
else {
j = (*this)->Isqlite3_column_int(i);
}
}
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::span<const byte>>) {
j = (*this)->Isqlite3_column_blob(i);
}
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, const char*>) {
j = (*this)->Isqlite3_column_text(i);
}
else {
assert(false);
//, "Don't know how to read this datatype from database")
j = monostate(); //Should generate somewhat relevant error at compile time.
}
return *this;
}
void bind(int i, monostate) { (*this)->Isqlite3_bind(i); }
template < typename T,
typename std::enable_if<is_sqlite3_field_type<T>::value, int >::type dummy_arg = 0 >
void bind(int i, T j) {
(*this)->Isqlite3_bind(i, j);
}
template < typename T,
typename std::enable_if<!is_sqlite3_field_type<T>::value, int >::type dummy_arg = 0 >
void bind(int i, const T& j) {
static_assert(ro::is_serializable<T>::value, "Don't know how to store this type in a database");
(*this)->Isqlite3_bind(i, ro::serialize(j));
}
typedef Icompiled_sql::sql_result result;
result step() {
return (*this)->Isqlite3_step();
}
void final_step() {
if (step() != result::DONE) throw SQLexception("SQL: Unexpected rows remaining");
}
void reset() {
(*this)->Isqlite3_reset();
}// https://sqlite.org/c3ref/reset.html
template<typename T, typename ...Args> void bind(int i, const T& first, const Args& ...args) {
bind(i, first);
bind(i + 1, args...);
}
template<typename ...Args> void do_one(const Args& ...args) {
reset();
if constexpr (sizeof...(args) > 0) {
bind(1, args...);
}
final_step();
}
template<typename ...Args> auto read_one(const Args& ...args) {
reset();
if constexpr (sizeof...(args) > 0) {
bind(1, args...);
}
return step() == result::ROW;
}
};
class sql_update :sql {
public:
using sql::sql;
sql_update(ISqlite3* p, const char* sz) : sql(p, sz) {}
template<typename ...Args> void operator()(const Args& ...args) {
do_one(args...);
}
};
}
class sql_update_to_misc :ro::sql {
public:
sql_update_to_misc(ISqlite3* p) : sql(p, R"|(REPLACE INTO "Misc" VALUES(?1, ?2);)|") {}
sql_update_to_misc(const std::unique_ptr<ISqlite3>& p) : sql_update_to_misc(p.get()) {}
template<typename T>void operator()(int i, const T& j) {
do_one(i, j);
}
template<typename T>void operator()(int i, T* j) {
do_one(i, j);
}
};
class sql_read_from_misc :ro::sql {
public:
sql_read_from_misc(ISqlite3 *p) : sql(p, R"|(SELECT "m" FROM "Misc" WHERE "index" = ?1;)|") {}
sql_read_from_misc(const std::unique_ptr<ISqlite3>& p) : sql_read_from_misc(p.get()){}
auto operator()(int i) {
return read_one(i);
}
template<typename T>auto value() {
return column<T>(0);
}
template <typename T> void read(T& j) const {
sql::read<T>(0,j);
}
};
class sql_insert_name {
ro::sql csql_begin;
ro::sql csql_into_names;
ro::sql csql_namekey_into_keys;
ro::sql csql_commit;
public:
sql_insert_name(ISqlite3* p) :
csql_begin(p, R"|(BEGIN;)|"),
csql_into_names(p, R"|(INSERT OR ROLLBACK INTO "Names" VALUES(?1);)|"),
csql_namekey_into_keys(p, R"|(INSERT OR ROLLBACK INTO "Keys" VALUES(?1, last_insert_rowid(), 1);)|"),
csql_commit(p, R"|(COMMIT;)|") {}
sql_insert_name(const std::unique_ptr<ISqlite3>& p) : sql_insert_name(p.get()) {}
void operator()(const char* psz, const ristretto255::point& pt) {
csql_begin.do_one();
try {
csql_into_names.do_one(psz);
csql_namekey_into_keys.do_one(pt);
}
catch (const std::exception & e) {
csql_commit.do_one();
throw e;
}
csql_commit.do_one();
}
};
class sql_read_name :ro::sql {
public:
sql_read_name(ISqlite3* p) : sql(p, R"|(SELECT * FROM "Names" WHERE OID = ?1;)|") {}
sql_read_name(const std::unique_ptr<ISqlite3>& p) : sql_read_name(p.get()) {}
bool operator()(int i) {
return read_one(i) == Icompiled_sql::ROW;
}
auto name() const {
return sql::column<const char*>(0);
}
};
constexpr auto WALLET_FILE_IDENTIFIER (0x56d34bc5a655dd1fi64);
constexpr auto WALLET_FILE_SCHEMA_VERSION_0_0(1);