diff --git a/Package.swift b/Package.swift index f4b8501b..afbfe9f3 100644 --- a/Package.swift +++ b/Package.swift @@ -460,7 +460,7 @@ let package = Package( // priority over some other path on a future toolchain. .headerSearchPath(useSqlcipher ? "cpp/sqlcipher" : "cpp"), // cpp/libsql/bridge.hpp unconditionally `#include`s libsql.h (it's - // pulled in from always-compiled files like DBHostObject.hpp, not + // pulled in from always-compiled files like OPDatabase.hpp, not // just the libsql backend), so libsql.h must resolve regardless of // whether useLibsql links the actual xcframework binary. CocoaPods // papered over this with a broad recursive header search path; diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 430f45aa..6835d167 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -47,7 +47,7 @@ add_library( ../cpp/SmartHostObject.cpp ../cpp/PreparedStatementHostObject.cpp ../cpp/DumbHostObject.cpp - ../cpp/DBHostObject.cpp + ../cpp/OPDatabase.cpp cpp-adapter.cpp ) diff --git a/cpp/DBHostObject.cpp b/cpp/OPDatabase.cpp similarity index 84% rename from cpp/DBHostObject.cpp rename to cpp/OPDatabase.cpp index 74722437..7d369139 100644 --- a/cpp/DBHostObject.cpp +++ b/cpp/OPDatabase.cpp @@ -1,4 +1,4 @@ -#include "DBHostObject.hpp" +#include "OPDatabase.hpp" #include "PreparedStatementHostObject.hpp" #if OP_SQLITE_USE_LIBSQL #include "libsql/bridge.hpp" @@ -18,7 +18,7 @@ namespace jsi = facebook::jsi; namespace react = facebook::react; #ifdef OP_SQLITE_USE_LIBSQL -void DBHostObject::flush_pending_reactive_queries( +void OPDatabase::flush_pending_reactive_queries( const std::shared_ptr &resolve) { if (alive != nullptr && !alive->load()) { return; @@ -34,7 +34,7 @@ std::string turso_remote_db_name(const std::string &url) { ".sqlite"; } -void DBHostObject::flush_pending_reactive_queries( +void OPDatabase::flush_pending_reactive_queries( const std::shared_ptr &resolve) { if (alive != nullptr && !alive->load()) { return; @@ -44,7 +44,7 @@ void DBHostObject::flush_pending_reactive_queries( }); } #else -void DBHostObject::flush_pending_reactive_queries( +void OPDatabase::flush_pending_reactive_queries( const std::shared_ptr &resolve) { if (alive != nullptr && !alive->load()) { return; @@ -75,7 +75,7 @@ void DBHostObject::flush_pending_reactive_queries( }); } -void DBHostObject::on_commit() { +void OPDatabase::on_commit() { if (alive != nullptr && !alive->load()) { return; } @@ -84,7 +84,7 @@ void DBHostObject::on_commit() { }); } -void DBHostObject::on_rollback() { +void OPDatabase::on_rollback() { if (alive != nullptr && !alive->load()) { return; } @@ -93,7 +93,7 @@ void DBHostObject::on_rollback() { }); } -void DBHostObject::on_update(const std::string &table, +void OPDatabase::on_update(const std::string &table, const std::string &operation, long long row_id) { if (alive != nullptr && !alive->load()) { return; @@ -154,7 +154,7 @@ void DBHostObject::on_update(const std::string &table, } } -void DBHostObject::auto_register_update_hook() { +void OPDatabase::sync_update_hook_registration() { if (invalidated || db == nullptr) { return; } @@ -175,14 +175,14 @@ void DBHostObject::auto_register_update_hook() { } #endif -void DBHostObject::throw_if_closed(const char *function_name) const { +void OPDatabase::throw_if_closed(const char *function_name) const { if (invalidated) { throw std::runtime_error(std::string("[op-sqlite][") + function_name + "] database is closed"); } } -void DBHostObject::release_hooks() { +void OPDatabase::release_hooks() { reactive_queries.clear(); pending_reactive_queries.clear(); update_hook_callback = nullptr; @@ -199,20 +199,21 @@ void DBHostObject::release_hooks() { // \_____\___/|_| |_|___/\__|_| \__,_|\___|\__\___/|_| #ifdef OP_SQLITE_USE_LIBSQL // Remote connection constructor -DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &url, - std::string &auth_token) +OPDatabase::OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, + std::string &url, std::string &auth_token) : db_name(url) { thread_pool = std::make_shared(); db = opsqlite_libsql_open_remote(url, auth_token); - create_jsi_functions(rt); + create_jsi_functions(rt, js_object); } // Sync connection constructor -DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &db_name, - std::string &path, std::string &url, - std::string &auth_token, int sync_interval, - bool offline, std::string &encryption_key, +OPDatabase::OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, + std::string &db_name, std::string &path, + std::string &url, std::string &auth_token, + int sync_interval, bool offline, + std::string &encryption_key, std::string &remote_encryption_key) : base_path(path), db_name(db_name), delete_db_name(db_name) { @@ -222,25 +223,26 @@ DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &db_name, opsqlite_libsql_open_sync(db_name, path, url, auth_token, sync_interval, offline, encryption_key, remote_encryption_key); - create_jsi_functions(rt); + create_jsi_functions(rt, js_object); } #elif defined(OP_SQLITE_USE_TURSO) // Remote connection constructor -DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &url, - std::string &auth_token, std::string &base_path) +OPDatabase::OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, + std::string &url, std::string &auth_token, + std::string &base_path) : base_path(base_path), db_name(url), delete_db_name(turso_remote_db_name(url)) { thread_pool = std::make_shared(); db = opsqlite_open_remote(url, auth_token, base_path); - create_jsi_functions(rt); + create_jsi_functions(rt, js_object); } // Sync connection constructor -DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &db_name, - std::string &path, std::string &url, - std::string &auth_token, +OPDatabase::OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, + std::string &db_name, std::string &path, + std::string &url, std::string &auth_token, std::string &remote_encryption_key) : base_path(path), db_name(db_name), delete_db_name(db_name) { @@ -249,15 +251,15 @@ DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &db_name, db = opsqlite_open_sync(db_name, path, url, auth_token, remote_encryption_key); - create_jsi_functions(rt); + create_jsi_functions(rt, js_object); } #endif -DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &base_path, - std::string &db_name, std::string &path, - bool readOnly, bool failOnCreate, - std::string &encryption_key) +OPDatabase::OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, + std::string &base_path, std::string &db_name, + std::string &path, bool readOnly, + bool failOnCreate, std::string &encryption_key) : base_path(base_path), db_name(db_name), delete_db_name(db_name) { thread_pool = std::make_shared(); @@ -271,11 +273,12 @@ DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &base_path, #else db = opsqlite_open(db_name, path, readOnly, failOnCreate); #endif - create_jsi_functions(rt); + create_jsi_functions(rt, js_object); }; -void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { - function_map["attach"] = HFN(this) { +void OPDatabase::create_jsi_functions(jsi::Runtime &rt, + jsi::Object &js_object) { + js_object.setProperty(rt, "attach", HFN(this) { throw_if_closed("attach"); std::string secondary_db_path = std::string(base_path); @@ -313,9 +316,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { #endif return {}; - }); + })); - function_map["detach"] = HFN(this) { + js_object.setProperty(rt, "detach", HFN(this) { throw_if_closed("detach"); if (!args[0].isString()) { @@ -334,9 +337,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { #endif return {}; - }); + })); - function_map["close"] = HFN(this) { + js_object.setProperty(rt, "close", HFN(this) { invalidated = true; // Abort pending native SQLite work before waiting on the thread pool. #if !defined(OP_SQLITE_USE_LIBSQL) && !defined(OP_SQLITE_USE_TURSO) @@ -358,9 +361,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { #endif return {}; - }); + })); - function_map["interrupt"] = HFN(this) { + js_object.setProperty(rt, "interrupt", HFN(this) { if (invalidated) { throw std::runtime_error("[op-sqlite][interrupt] database is closed"); } @@ -379,9 +382,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { sqlite3_interrupt(db); return {}; #endif - }); + })); - function_map["delete"] = HFN(this) { + js_object.setProperty(rt, "delete", HFN(this) { if (count != 0) { throw std::runtime_error("[op-sqlite] Delete no longer takes arguments"); } @@ -412,9 +415,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { #endif return {}; - }); + })); - function_map["executeRaw"] = HFN(this) { + js_object.setProperty(rt, "executeRaw", HFN(this) { throw_if_closed("executeRaw"); const std::string query = args[0].asString(rt).utf8(rt); @@ -437,13 +440,13 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { [](jsi::Runtime &rt, std::any prev) { auto tuple = std::any_cast< std::tuple>>>( - prev); + std::move(prev)); return create_raw_result(rt, std::get<0>(tuple), &std::get<1>(tuple)); }); - }); + })); - function_map["executeSync"] = HFN(this) { + js_object.setProperty(rt, "executeSync", HFN(this) { throw_if_closed("executeSync"); std::string query = args[0].asString(rt).utf8(rt); @@ -459,9 +462,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { #endif return create_js_rows(rt, status); - }); + })); - function_map["executeRawSync"] = HFN(this) { + js_object.setProperty(rt, "executeRawSync", HFN(this) { throw_if_closed("executeRawSync"); const std::string query = args[0].asString(rt).utf8(rt); @@ -478,9 +481,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { #endif return create_raw_result(rt, status, &results); - }); + })); - function_map["execute"] = HFN(this) { + js_object.setProperty(rt, "execute", HFN(this) { throw_if_closed("execute"); const std::string query = args[0].asString(rt).utf8(rt); @@ -499,12 +502,12 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { return status; }, [](jsi::Runtime &rt, std::any prev) { - auto status = std::any_cast(prev); + auto status = std::any_cast(std::move(prev)); return create_js_rows(rt, status); }); - }); + })); - function_map["executeWithHostObjects"] = HFN(this) { + js_object.setProperty(rt, "executeWithHostObjects", HFN(this) { throw_if_closed("executeWithHostObjects"); const std::string query = args[0].asString(rt).utf8(rt); @@ -530,15 +533,16 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { [](jsi::Runtime &rt, std::any prev) { auto tuple = std::any_cast< std::tuple, - std::shared_ptr>>>(prev); - auto results = - std::make_shared>(std::get<1>(tuple)); + std::shared_ptr>>>( + std::move(prev)); + auto results = std::make_shared>( + std::move(std::get<1>(tuple))); return create_result(rt, std::get<0>(tuple), results.get(), std::get<2>(tuple)); }); - }); + })); - function_map["executeBatch"] = HFN(this) { + js_object.setProperty(rt, "executeBatch", HFN(this) { throw_if_closed("executeBatch"); if (count < 1) { @@ -570,16 +574,16 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { return batchResult; }, [](jsi::Runtime &rt, std::any prev) { - auto batchResult = std::any_cast(prev); + auto batchResult = std::any_cast(std::move(prev)); auto res = jsi::Object(rt); res.setProperty(rt, "rowsAffected", jsi::Value(batchResult.affectedRows)); return res; }); - }); + })); #if defined(OP_SQLITE_USE_LIBSQL) || defined(OP_SQLITE_USE_TURSO) - function_map["sync"] = HFN(this) { + js_object.setProperty(rt, "sync", HFN(this) { throw_if_closed("sync"); #ifdef OP_SQLITE_USE_LIBSQL @@ -588,29 +592,29 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { opsqlite_sync(db); #endif return {}; - }); + })); #ifdef OP_SQLITE_USE_LIBSQL - function_map["setReservedBytes"] = HFN(this) { + js_object.setProperty(rt, "setReservedBytes", HFN(this) { throw_if_closed("setReservedBytes"); auto reserved_bytes = static_cast(args[0].asNumber()); opsqlite_libsql_set_reserved_bytes(db, reserved_bytes); return {}; - }); + })); - function_map["getReservedBytes"] = HFN(this) { + js_object.setProperty(rt, "getReservedBytes", HFN(this) { throw_if_closed("getReservedBytes"); return {opsqlite_libsql_get_reserved_bytes(db)}; - }); + })); #endif #endif #if !defined(OP_SQLITE_USE_LIBSQL) && !defined(OP_SQLITE_USE_TURSO) - function_map["loadFile"] = HFN(this) { + js_object.setProperty(rt, "loadFile", HFN(this) { throw_if_closed("loadFile"); if (count < 1) { @@ -624,15 +628,15 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { rt, thread_pool, [this, sqlFileName]() { return import_sql_file(db, sqlFileName); }, [](jsi::Runtime &rt, std::any prev) { - auto result = std::any_cast(prev); + auto result = std::any_cast(std::move(prev)); auto res = jsi::Object(rt); res.setProperty(rt, "rowsAffected", jsi::Value(result.affectedRows)); res.setProperty(rt, "commands", jsi::Value(result.commands)); return res; }); - }); + })); - function_map["updateHook"] = HFN(this) { + js_object.setProperty(rt, "updateHook", HFN(this) { throw_if_closed("updateHook"); auto callback = std::make_shared(rt, args[0]); @@ -643,11 +647,11 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { update_hook_callback = callback; } - auto_register_update_hook(); + sync_update_hook_registration(); return {}; - }); + })); - function_map["commitHook"] = HFN(this) { + js_object.setProperty(rt, "commitHook", HFN(this) { throw_if_closed("commitHook"); if (count < 1) { @@ -663,9 +667,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { opsqlite_register_commit_hook(db, this); return {}; - }); + })); - function_map["rollbackHook"] = HFN(this) { + js_object.setProperty(rt, "rollbackHook", HFN(this) { throw_if_closed("rollbackHook"); if (count < 1) { @@ -682,9 +686,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { opsqlite_register_rollback_hook(db, this); return {}; - }); + })); - function_map["loadExtension"] = HFN(this) { + js_object.setProperty(rt, "loadExtension", HFN(this) { throw_if_closed("loadExtension"); auto path = args[0].asString(rt).utf8(rt); @@ -695,9 +699,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { opsqlite_load_extension(db, path, entry_point); return {}; - }); + })); - function_map["reactiveExecute"] = HFN(this) { + js_object.setProperty(rt, "reactiveExecute", HFN(this) { throw_if_closed("reactiveExecute"); auto query = args[0].asObject(rt); @@ -740,7 +744,7 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { reactive_queries.push_back(reactiveQuery); - auto_register_update_hook(); + sync_update_hook_registration(); auto weak_self = weak_from_this(); @@ -754,15 +758,15 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { if (it != self->reactive_queries.end()) { self->reactive_queries.erase(it); } - self->auto_register_update_hook(); + self->sync_update_hook_registration(); return {}; }); return unsubscribe; - }); + })); #endif - function_map["prepareStatement"] = HFN(this) { + js_object.setProperty(rt, "prepareStatement", HFN(this) { throw_if_closed("prepareStatement"); auto query = args[0].asString(rt).utf8(rt); @@ -776,9 +780,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { thread_pool); return jsi::Object::createFromHostObject(rt, preparedStatementHostObject); - }); + })); - function_map["getDbPath"] = HFN(this) { + js_object.setProperty(rt, "getDbPath", HFN(this) { std::string path = std::string(base_path); if (count == 1) { @@ -800,9 +804,9 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { auto result = opsqlite_get_db_path(db_name, path); return jsi::String::createFromUtf8(rt, result); - }); + })); - function_map["flushPendingReactiveQueries"] = HFN(this) { + js_object.setProperty(rt, "flushPendingReactiveQueries", HFN(this) { throw_if_closed("flushPendingReactiveQueries"); auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); @@ -819,38 +823,10 @@ void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { })); return promise; - }); -} - -std::vector DBHostObject::getPropertyNames(jsi::Runtime &_rt) { - std::vector keys; - keys.reserve(function_map.size()); - for (const auto &pair : function_map) { - keys.emplace_back(jsi::PropNameID::forUtf8(_rt, pair.first)); - } - return keys; -} - -jsi::Value DBHostObject::get(jsi::Runtime &rt, - const jsi::PropNameID &propNameID) { - auto name = propNameID.utf8(rt); - if (function_map.count(name) != 1) { - return HFN(name) { - throw std::runtime_error( - "[op-sqlite] Function " + name + - " not implemented for current backend (libsql or sqlcipher)"); - }); - } - - return {rt, function_map[name]}; -} - -void DBHostObject::set(jsi::Runtime &_rt, const jsi::PropNameID &name, - const jsi::Value &value) { - throw std::runtime_error("You cannot write to this object!"); + })); } -void DBHostObject::invalidate() { +void OPDatabase::invalidate() { if (invalidated) { return; } @@ -882,6 +858,6 @@ void DBHostObject::invalidate() { #endif } -DBHostObject::~DBHostObject() { invalidate(); } +OPDatabase::~OPDatabase() { invalidate(); } } // namespace opsqlite diff --git a/cpp/DBHostObject.hpp b/cpp/OPDatabase.hpp similarity index 72% rename from cpp/DBHostObject.hpp rename to cpp/OPDatabase.hpp index 4e476a3e..75d32660 100644 --- a/cpp/DBHostObject.hpp +++ b/cpp/OPDatabase.hpp @@ -15,7 +15,6 @@ #endif #endif #include -#include #include namespace opsqlite { @@ -42,55 +41,52 @@ struct ReactiveQuery { std::shared_ptr callback; }; -class JSI_EXPORT DBHostObject - : public jsi::HostObject, - public std::enable_shared_from_this { +class JSI_EXPORT OPDatabase + : public jsi::NativeState, + public std::enable_shared_from_this { public: // Normal constructor shared between all backends - DBHostObject(jsi::Runtime &rt, std::string &base_path, std::string &db_name, + OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, + std::string &base_path, std::string &db_name, std::string &path, bool readOnly, bool failOnCreate, std::string &encryption_key); #ifdef OP_SQLITE_USE_LIBSQL // Constructor for remoteOpen, purely for remote databases - DBHostObject(jsi::Runtime &rt, std::string &url, std::string &auth_token); + OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, std::string &url, + std::string &auth_token); // Constructor for a local database with remote sync - DBHostObject(jsi::Runtime &rt, std::string &db_name, std::string &path, - std::string &url, std::string &auth_token, int sync_interval, - bool offline, std::string &encryption_key, + OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, std::string &db_name, + std::string &path, std::string &url, std::string &auth_token, + int sync_interval, bool offline, std::string &encryption_key, std::string &remote_encryption_key); #elif defined(OP_SQLITE_USE_TURSO) // Constructor for remoteOpen, purely for remote databases - DBHostObject(jsi::Runtime &rt, std::string &url, std::string &auth_token, - std::string &base_path); + OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, std::string &url, + std::string &auth_token, std::string &base_path); // Constructor for a local database with remote sync - DBHostObject(jsi::Runtime &rt, std::string &db_name, std::string &path, - std::string &url, std::string &auth_token, + OPDatabase(jsi::Runtime &rt, jsi::Object &js_object, std::string &db_name, + std::string &path, std::string &url, std::string &auth_token, std::string &remote_encryption_key); #endif - std::vector getPropertyNames(jsi::Runtime &rt) override; - jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID) override; - void set(jsi::Runtime &rt, const jsi::PropNameID &name, - const jsi::Value &value) override; void on_update(const std::string &table, const std::string &operation, long long row_id); void on_commit(); void on_rollback(); void invalidate(); - ~DBHostObject() override; + ~OPDatabase() override; private: std::set> pending_reactive_queries; - void auto_register_update_hook(); + void sync_update_hook_registration(); void release_hooks(); void throw_if_closed(const char *function_name) const; - void create_jsi_functions(jsi::Runtime &rt); + void create_jsi_functions(jsi::Runtime &rt, jsi::Object &js_object); void flush_pending_reactive_queries(const std::shared_ptr &resolve); - std::unordered_map function_map; std::string base_path; // Bound at construction, on the JS thread, to the generation that created // this database. diff --git a/cpp/OPSqlite.cpp b/cpp/OPSqlite.cpp index 5ee14bdc..5bd970b5 100644 --- a/cpp/OPSqlite.cpp +++ b/cpp/OPSqlite.cpp @@ -1,5 +1,5 @@ #include "OPSqlite.hpp" -#include "DBHostObject.hpp" +#include "OPDatabase.hpp" #include "DumbHostObject.hpp" #include "OPThreadPool.hpp" #ifdef OP_SQLITE_USE_LIBSQL @@ -26,13 +26,17 @@ std::string _sqlite_vec_path; std::shared_ptr invoker; std::shared_ptr> generation_alive; -// React native will try to clean the module on JS context invalidation -// (CodePush/Hot Reload) The clearState function is called. Each currently -// open DBHostObject cleans itself up independently -- ~DBHostObject() -// already calls invalidate() (interrupt + drain + close) whenever the JS -// runtime destroys it, so there's no registry to walk here. All this needs -// to do is mark THIS generation dead so in-flight async work drops its -// result instead of resolving into whichever runtime replaces it. +// Each platform module calls its own invalidate() lifecycle hook when React +// Native tears down the JS context (CodePush/Hot Reload, or any other +// runtime teardown) -- OPSQLiteModule.invalidate() on Android, which reaches +// this function through JNI's clearStateNativeJsi (see android/cpp-adapter.cpp), +// and -[OPSQLite invalidate] on iOS (see ios/OPSQLite.mm). Each currently +// open OPDatabase cleans itself up independently -- OPDatabase's own +// invalidate() (interrupt + drain + close), called from its destructor, +// already runs whenever the JS runtime destroys it, so there's no registry to +// walk here. All THIS invalidate() needs to do is mark THIS generation dead so +// in-flight async work drops its result instead of resolving into whichever +// runtime replaces it. void invalidate(const std::shared_ptr> &generation_alive) { if (generation_alive != nullptr) { generation_alive->store(false); @@ -40,15 +44,15 @@ void invalidate(const std::shared_ptr> &generation_alive) { } std::shared_ptr> -install(jsi::Runtime &rt, const std::shared_ptr &_invoker, +install(jsi::Runtime &rt, const std::shared_ptr &invoker, const char *base_path, const char *sqlite_vec_path) { _base_path = std::string(base_path); _sqlite_vec_path = std::string(sqlite_vec_path); - opsqlite::invoker = _invoker; + opsqlite::invoker = invoker; // Also returned to the caller: DBs opened by this generation shadow-copy - // the global at construction time (see DBHostObject.hpp), while + // the global at construction time (see OPDatabase.hpp), while // invalidate() needs the shared_ptr handed back directly so it flips THIS // generation's flag even if a newer, overlapping generation's install() // has already reassigned the global. @@ -91,9 +95,11 @@ install(jsi::Runtime &rt, const std::shared_ptr &_invoker, } } - std::shared_ptr db = std::make_shared( - rt, path, name, path, readOnly, failOnCreate, encryption_key); - return jsi::Object::createFromHostObject(rt, db); + jsi::Object js_db(rt); + std::shared_ptr db = std::make_shared( + rt, js_db, path, name, path, readOnly, failOnCreate, encryption_key); + js_db.setNativeState(rt, db); + return js_db; }); auto is_sqlcipher = HFN(=) { @@ -137,16 +143,18 @@ install(jsi::Runtime &rt, const std::shared_ptr &_invoker, std::string auth_token = options.getProperty(rt, "authToken").asString(rt).utf8(rt); + jsi::Object js_db(rt); #ifdef OP_SQLITE_USE_LIBSQL - std::shared_ptr db = - std::make_shared(rt, url, auth_token); + std::shared_ptr db = + std::make_shared(rt, js_db, url, auth_token); #else std::string path = std::string(_base_path); - std::shared_ptr db = - std::make_shared(rt, url, auth_token, path); + std::shared_ptr db = + std::make_shared(rt, js_db, url, auth_token, path); #endif - return jsi::Object::createFromHostObject(rt, db); + js_db.setNativeState(rt, db); + return js_db; }); auto open_sync = HFN(=) { @@ -194,19 +202,21 @@ install(jsi::Runtime &rt, const std::shared_ptr &_invoker, } } + jsi::Object js_db(rt); #ifdef OP_SQLITE_USE_LIBSQL - std::shared_ptr db = std::make_shared( - rt, name, path, url, auth_token, sync_interval, offline, encryption_key, - remote_encryption_key); + std::shared_ptr db = std::make_shared( + rt, js_db, name, path, url, auth_token, sync_interval, offline, + encryption_key, remote_encryption_key); #else (void)sync_interval; (void)offline; - std::shared_ptr db = std::make_shared( - rt, name, path, url, auth_token, remote_encryption_key); + std::shared_ptr db = std::make_shared( + rt, js_db, name, path, url, auth_token, remote_encryption_key); #endif - return jsi::Object::createFromHostObject(rt, db); + js_db.setNativeState(rt, db); + return js_db; }); #endif diff --git a/cpp/bridge.cpp b/cpp/bridge.cpp index 0c67b488..861aeb3e 100644 --- a/cpp/bridge.cpp +++ b/cpp/bridge.cpp @@ -1,9 +1,9 @@ // This file contains pure sqlite operations without JSI interaction // Allows a clear defined boundary between the JSI and the SQLite operations -// so that threading operations are safe and contained within DBHostObject +// so that threading operations are safe and contained within OPDatabase #include "bridge.hpp" -#include "DBHostObject.hpp" +#include "OPDatabase.hpp" #include "DumbHostObject.hpp" #include "SmartHostObject.hpp" #include "logs.h" @@ -805,43 +805,43 @@ std::string operation_to_string(int operation_type) { } } -void update_callback(void *db_host_object_ptr, int operation_type, +void update_callback(void *opsqlite_db_ptr, int operation_type, [[maybe_unused]] char const *database, char const *table, sqlite3_int64 row_id) { - auto db_host_object = reinterpret_cast(db_host_object_ptr); - db_host_object->on_update(std::string(table), - operation_to_string(operation_type), row_id); + auto opsqlite_db = reinterpret_cast(opsqlite_db_ptr); + opsqlite_db->on_update(std::string(table), + operation_to_string(operation_type), row_id); } -void opsqlite_register_update_hook(sqlite3 *db, void *db_host_object) { - sqlite3_update_hook(db, &update_callback, (void *)db_host_object); +void opsqlite_register_update_hook(sqlite3 *db, void *opsqlite_db_ptr) { + sqlite3_update_hook(db, &update_callback, opsqlite_db_ptr); } void opsqlite_deregister_update_hook(sqlite3 *db) { sqlite3_update_hook(db, nullptr, nullptr); } -int commit_callback(void *db_host_object_ptr) { - auto db_host_object = reinterpret_cast(db_host_object_ptr); - db_host_object->on_commit(); +int commit_callback(void *opsqlite_db_ptr) { + auto opsqlite_db = reinterpret_cast(opsqlite_db_ptr); + opsqlite_db->on_commit(); return 0; } -void opsqlite_register_commit_hook(sqlite3 *db, void *db_host_object_ptr) { - sqlite3_commit_hook(db, &commit_callback, db_host_object_ptr); +void opsqlite_register_commit_hook(sqlite3 *db, void *opsqlite_db_ptr) { + sqlite3_commit_hook(db, &commit_callback, opsqlite_db_ptr); } void opsqlite_deregister_commit_hook(sqlite3 *db) { sqlite3_commit_hook(db, nullptr, nullptr); } -void rollback_callback(void *db_host_object_ptr) { - auto db_host_object = reinterpret_cast(db_host_object_ptr); - db_host_object->on_rollback(); +void rollback_callback(void *opsqlite_db_ptr) { + auto opsqlite_db = reinterpret_cast(opsqlite_db_ptr); + opsqlite_db->on_rollback(); } -void opsqlite_register_rollback_hook(sqlite3 *db, void *db_host_object_ptr) { - sqlite3_rollback_hook(db, &rollback_callback, db_host_object_ptr); +void opsqlite_register_rollback_hook(sqlite3 *db, void *opsqlite_db_ptr) { + sqlite3_rollback_hook(db, &rollback_callback, opsqlite_db_ptr); } void opsqlite_deregister_rollback_hook(sqlite3 *db) { diff --git a/cpp/bridge.hpp b/cpp/bridge.hpp index a4b9231b..c6ea221c 100644 --- a/cpp/bridge.hpp +++ b/cpp/bridge.hpp @@ -78,11 +78,11 @@ BridgeResult opsqlite_execute_raw(sqlite3 *db, std::string const &query, const std::vector *params, std::vector> *results); -void opsqlite_register_update_hook(sqlite3 *db, void *db_host_object_ptr); +void opsqlite_register_update_hook(sqlite3 *db, void *opsqlite_db_ptr); void opsqlite_deregister_update_hook(sqlite3 *db); -void opsqlite_register_commit_hook(sqlite3 *db, void *db_host_object_ptr); +void opsqlite_register_commit_hook(sqlite3 *db, void *opsqlite_db_ptr); void opsqlite_deregister_commit_hook(sqlite3 *db); -void opsqlite_register_rollback_hook(sqlite3 *db, void *db_host_object_ptr); +void opsqlite_register_rollback_hook(sqlite3 *db, void *opsqlite_db_ptr); void opsqlite_deregister_rollback_hook(sqlite3 *db); sqlite3_stmt *opsqlite_prepare_statement(sqlite3 *db, std::string const &query); diff --git a/cpp/turso/turso_bridge.cpp b/cpp/turso/turso_bridge.cpp index 120ecbd3..7f667353 100644 --- a/cpp/turso/turso_bridge.cpp +++ b/cpp/turso/turso_bridge.cpp @@ -1,4 +1,4 @@ -#include "DBHostObject.hpp" +#include "OPDatabase.hpp" #include "DumbHostObject.hpp" #include "SmartHostObject.hpp" #include "bridge.hpp" diff --git a/cpp/utils.cpp b/cpp/utils.cpp index 8537a273..c9a4c5a2 100644 --- a/cpp/utils.cpp +++ b/cpp/utils.cpp @@ -412,8 +412,8 @@ promisify(jsi::Runtime &rt, std::shared_ptr thread_pool, // so it can be safely disposed on the JS thread invoker->invokeAsync( [result = std::move(result), resolve = resolve, reject = reject, - resolve_callback = resolve_callback](jsi::Runtime &rt) { - auto jsi_result = resolve_callback(rt, result); + resolve_callback = resolve_callback](jsi::Runtime &rt) mutable { + auto jsi_result = resolve_callback(rt, std::move(result)); resolve->asObject(rt).asFunction(rt).call(rt, jsi_result); }); } catch (std::runtime_error &e) { diff --git a/example/ios/OPSQLiteExample.xcodeproj/project.pbxproj b/example/ios/OPSQLiteExample.xcodeproj/project.pbxproj index b9f83469..d913c2ca 100644 --- a/example/ios/OPSQLiteExample.xcodeproj/project.pbxproj +++ b/example/ios/OPSQLiteExample.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -155,9 +155,9 @@ ); mainGroup = 83CBB9F61A601CBA00E9B192; packageReferences = ( - 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "xcframeworks" */, - C2FECF1430FDAAE3BF355839 /* XCLocalSwiftPackageReference "autolinking" */, - A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "ios" */, + 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */, + C2FECF1430FDAAE3BF355839 /* XCLocalSwiftPackageReference "build/generated/autolinking" */, + A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "build/generated/ios" */, ); productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; projectDirPath = ""; @@ -346,6 +346,8 @@ ); ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 24CMR7378R; ENABLE_BITCODE = NO; @@ -367,6 +369,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.op.sqlite.example; PRODUCT_NAME = OPSQLiteExample; + PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -449,6 +452,8 @@ ); ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 24CMR7378R; GCC_OPTIMIZATION_LEVEL = s; @@ -470,6 +475,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.op.sqlite.example; PRODUCT_NAME = OPSQLiteExample; + PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -697,15 +703,15 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "xcframeworks" */ = { + 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */ = { isa = XCLocalSwiftPackageReference; relativePath = build/xcframeworks; }; - A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "ios" */ = { + A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "build/generated/ios" */ = { isa = XCLocalSwiftPackageReference; relativePath = build/generated/ios; }; - C2FECF1430FDAAE3BF355839 /* XCLocalSwiftPackageReference "autolinking" */ = { + C2FECF1430FDAAE3BF355839 /* XCLocalSwiftPackageReference "build/generated/autolinking" */ = { isa = XCLocalSwiftPackageReference; relativePath = build/generated/autolinking; }; @@ -714,32 +720,32 @@ /* Begin XCSwiftPackageProductDependency section */ 3AFDEFB917B01458307FDE0A /* Autolinked */ = { isa = XCSwiftPackageProductDependency; - package = C2FECF1430FDAAE3BF355839 /* XCLocalSwiftPackageReference "autolinking" */; + package = C2FECF1430FDAAE3BF355839 /* XCLocalSwiftPackageReference "build/generated/autolinking" */; productName = Autolinked; }; 512EBECF5434FD7A6AF7C967 /* ReactHeaders */ = { isa = XCSwiftPackageProductDependency; - package = 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "xcframeworks" */; + package = 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */; productName = ReactHeaders; }; 8332BE36B9C49BD2E49347C1 /* ReactNativeDependenciesHeaders */ = { isa = XCSwiftPackageProductDependency; - package = 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "xcframeworks" */; + package = 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */; productName = ReactNativeDependenciesHeaders; }; 920F69A9DF98D37C8308D020 /* ReactNativeHeaders */ = { isa = XCSwiftPackageProductDependency; - package = 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "xcframeworks" */; + package = 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */; productName = ReactNativeHeaders; }; 947DD5EF3BE079FB7F5AFF87 /* ReactCodegen */ = { isa = XCSwiftPackageProductDependency; - package = A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "ios" */; + package = A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "build/generated/ios" */; productName = ReactCodegen; }; F3857EDDA38AFC0011322D6D /* ReactAppDependencyProvider */ = { isa = XCSwiftPackageProductDependency; - package = A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "ios" */; + package = A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "build/generated/ios" */; productName = ReactAppDependencyProvider; }; /* End XCSwiftPackageProductDependency section */ diff --git a/example/ios/OPSQLiteExample.xcodeproj/xcshareddata/xcschemes/debug.xcscheme b/example/ios/OPSQLiteExample.xcodeproj/xcshareddata/xcschemes/debug.xcscheme index bdd98a7f..b4baa89d 100644 --- a/example/ios/OPSQLiteExample.xcodeproj/xcshareddata/xcschemes/debug.xcscheme +++ b/example/ios/OPSQLiteExample.xcodeproj/xcshareddata/xcschemes/debug.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> diff --git a/example/src/App.tsx b/example/src/App.tsx index 43bc84ca..abfc54da 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -5,7 +5,7 @@ import { } from "@op-engineering/op-test"; import { useEffect, useState } from "react"; import "./tests"; // import all tests to register them -import {performanceTest, insertTest} from './performance_test'; +import {performanceTest, performanceTestAsync, insertTest} from './performance_test'; import { StyleSheet, Text, View } from "react-native"; import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; // import {open} from '@op-engineering/op-sqlite'; @@ -13,6 +13,7 @@ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; export default function App() { const [results, setResults] = useState(null); const [perfResult, setPerfResult] = useState(0); + const [perfResultAsync, setPerfResultAsync] = useState(0); const [openTime, setOpenTime] = useState(0); useEffect(() => { @@ -37,14 +38,21 @@ export default function App() { } setTimeout(() => { - try { - global?.gc?.(); - // let perfRes = performanceTest(); - let perfRes = insertTest(); - setPerfResult(perfRes); - } catch (e) { - // intentionally left blank - } + const runPerf = async () => { + try { + // global?.gc?.(); + // let perfRes = performanceTest(); + // let perfRes = insertTest(); + // setPerfResult(perfRes); + + // global?.gc?.(); + // let perfResAsync = await performanceTestAsync(); + // setPerfResultAsync(perfResAsync); + } catch (e) { + // intentionally left blank + } + }; + runPerf(); }, 4000); }; @@ -83,7 +91,10 @@ export default function App() { Open DB time: {openTime.toFixed(0)} ms - 100_000 query time: {perfResult.toFixed(0)} ms + perf/insert test time: {perfResult.toFixed(0)} ms + + + perf test (async) time: {perfResultAsync.toFixed(0)} ms {displayResults(results)} diff --git a/example/src/performance_test.ts b/example/src/performance_test.ts index 673720b9..4a23df18 100644 --- a/example/src/performance_test.ts +++ b/example/src/performance_test.ts @@ -42,6 +42,45 @@ export function performanceTest() { return end - start; } +export async function performanceTestAsync() { + const db = open({ + name: 'perfTestAsync.sqlite', + }); + + // Create table with 14 columns + await db.execute( + `CREATE TABLE IF NOT EXISTS perf_table ( + id INTEGER PRIMARY KEY, + col1 TEXT, col2 TEXT, col3 TEXT, col4 TEXT, col5 TEXT, col6 TEXT, col7 TEXT, + col8 TEXT, col9 TEXT, col10 TEXT, col11 TEXT, col12 TEXT, col13 TEXT, col14 TEXT + )`, + ); + // Clear table + await db.execute('DELETE FROM perf_table'); + const testRow = Array(14).fill('test'); + + let start = performance.now(); + + for (let i = 0; i < 1_000; i++) { + // Insert a single row for querying + await db.execute( + `INSERT INTO perf_table ( + col1, col2, col3, col4, col5, col6, col7, + col8, col9, col10, col11, col12, col13, col14 + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + testRow, + ); + } + + for (let i = 0; i < 100000; i++) { + await db.execute('SELECT * FROM perf_table WHERE id = 1'); + } + const end = performance.now(); + + // await db.close(); + return end - start; +} + export function insertTest() { const db = open({ name: 'insertTest.sqlite'