forked from cheng/wallet
221 lines
7.0 KiB
C
221 lines
7.0 KiB
C
|
#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 null {};
|
|||
|
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< T, std::span<const byte>>::value) {
|
|||
|
return (*this)->Isqlite3_column_blob(i);
|
|||
|
}
|
|||
|
else if constexpr (std::is_same< T, const char*>::value) {
|
|||
|
auto sz{ (*this)->Isqlite3_column_text(i) };
|
|||
|
// if (!IsValidUtf8String(sz)) throw NonUtf8DataInDatabase();
|
|||
|
return sz;
|
|||
|
}
|
|||
|
else {
|
|||
|
static_assert(false, "Don't know how to read this datatype from database");
|
|||
|
return null();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
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< T, std::span<const byte>>::value) {
|
|||
|
j = (*this)->Isqlite3_column_blob(i);
|
|||
|
}
|
|||
|
else if constexpr (std::is_same< T, const char*>::value) {
|
|||
|
j = (*this)->Isqlite3_column_text(i);
|
|||
|
}
|
|||
|
else {
|
|||
|
static_assert(false, "Don't know how to read this datatype from database");
|
|||
|
}
|
|||
|
return *this;
|
|||
|
}
|
|||
|
|
|||
|
void bind(int i, null) { (*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 {
|
|||
|
public:
|
|||
|
ro::sql csql_begin;
|
|||
|
ro::sql csql_into_names;
|
|||
|
ro::sql csql_namekey_into_keys;
|
|||
|
ro::sql csql_commit;
|
|||
|
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);
|