forked from cheng/wallet
5a00115c56
albeit they are dud wallet files, delete is not recording the delete. Something incomprehensible is happening with tracking the last file. ui is useless for any useful purpose, it exists only so that we have some ui that can be fixed up later to actually work ui fails to track added names, names are wrongly displayed in order added.
223 lines
7.2 KiB
C++
223 lines
7.2 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 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);
|