#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::value) template struct is_sqlite3_field_type { template static constexpr decltype(std::declval().Isqlite3_bind(1, std::declval()), bool()) test() { return true; } template static constexpr bool test(int = 0) { return false; } static constexpr bool value = is_sqlite3_field_type::template test(); }; static_assert(is_sqlite3_field_type::value); //Owns a compiled sql statement and destroys it when it is deconstructed. //Has move semantics. class sql : public std::unique_ptr { public: class null {}; sql(ISqlite3* p, const char* sz) :std::unique_ptr(sqlite3_prepare(p, sz)) {} sql(const std::unique_ptr& p, const char* sz) :std::unique_ptr(sqlite3_prepare(p.get(), sz)) {} // copy constructor sql(const sql& a) = delete; // move constructor sql(sql&& p) :std::unique_ptr(p.release()) { } // copy assignment sql& operator=(const sql) = delete; // Move assignment sql& operator=(sql&& p) { std::unique_ptr::reset(p.release()); } sql(Icompiled_sql* p) :std::unique_ptr(p) {} sql(std::unique_ptr&& p) :std::unique_ptr(p.release()) { } ~sql() = default; template auto column(int i) const { if constexpr (ro::is_blob_field_type::value) { auto st = (*this)->Isqlite3_column_blob(i); if (st.size_bytes() != sizeof(T)) throw BadDataException(); static_assert (std::is_standard_layout(), "not standard layout"); static_assert (std::is_trivial(), "not trivial"); return reinterpret_cast(&st[0]); } else if constexpr (std::is_integral::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::span>) { return (*this)->Isqlite3_column_blob(i); } else if constexpr (std::is_same_v, 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 null(); } } template const sql& read(int i, T& j) const{ if constexpr (ro::is_blob_field_type::value) { auto st = (*this)->Isqlite3_column_blob(i); if (st.size_bytes() != sizeof(T)) throw BadDataException(); static_assert (std::is_standard_layout(), "not standard layout"); static_assert (std::is_trivial(), "not trivial"); j = *reinterpret_cast(&st[0]); } else if constexpr (std::is_integral::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::span>) { j = (*this)->Isqlite3_column_blob(i); } else if constexpr (std::is_same_v, const char*>) { j = (*this)->Isqlite3_column_text(i); } else { 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::value, int >::type dummy_arg = 0 > void bind(int i, T j) { (*this)->Isqlite3_bind(i, j); } template < typename T, typename std::enable_if::value, int >::type dummy_arg = 0 > void bind(int i, const T& j) { static_assert(ro::is_serializable::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 void bind(int i, const T& first, const Args& ...args) { bind(i, first); bind(i + 1, args...); } template void do_one(const Args& ...args) { reset(); if constexpr (sizeof...(args) > 0) { bind(1, args...); } final_step(); } template 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 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& p) : sql_update_to_misc(p.get()) {} templatevoid operator()(int i, const T& j) { do_one(i, j); } templatevoid 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& p) : sql_read_from_misc(p.get()){} auto operator()(int i) { return read_one(i); } templateauto value() { return column(0); } template void read(T& j) const { sql::read(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& 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& p) : sql_read_name(p.get()) {} bool operator()(int i) { return read_one(i) == Icompiled_sql::ROW; } auto name() const { return sql::column(0); } }; constexpr auto WALLET_FILE_IDENTIFIER (0x56d34bc5a655dd1fi64); constexpr auto WALLET_FILE_SCHEMA_VERSION_0_0(1);