diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 885c47d2..6859d06d 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -42,17 +42,17 @@ add_library( ${PACKAGE_NAME} SHARED ../cpp/OPSqlite.cpp + ../cpp/OPDB.cpp ../cpp/utils.cpp ../cpp/OPThreadPool.cpp ../cpp/SmartHostObject.cpp ../cpp/PreparedStatementHostObject.cpp ../cpp/DumbHostObject.cpp - ../cpp/DBHostObject.cpp cpp-adapter.cpp ) if (USE_SQLCIPHER) - target_sources(${PACKAGE_NAME} PRIVATE ../cpp/sqlcipher/sqlite3.h ../cpp/sqlcipher/sqlite3.c ../cpp/bridge.cpp ../cpp/bridge.h) + target_sources(${PACKAGE_NAME} PRIVATE ../cpp/sqlcipher/sqlite3.h ../cpp/sqlcipher/sqlite3.c ../cpp/OPBridge.cpp ../cpp/OPBridge.hpp) add_definitions( -DOP_SQLITE_USE_SQLCIPHER=1 @@ -64,7 +64,7 @@ if (USE_SQLCIPHER) find_package(openssl REQUIRED CONFIG) elseif (USE_LIBSQL) - target_sources(${PACKAGE_NAME} PRIVATE ../cpp/libsql/bridge.cpp) + target_sources(${PACKAGE_NAME} PRIVATE ../cpp/libsql/OPBridge.cpp) add_definitions( -DOP_SQLITE_USE_LIBSQL=1 @@ -76,13 +76,7 @@ elseif (USE_TURSO) -DOP_SQLITE_USE_TURSO=1 ) else() - target_sources(${PACKAGE_NAME} PRIVATE ../cpp/sqlite3.c ../cpp/bridge.cpp) -endif() - -if (USE_CRSQLITE) - add_definitions( - -DOP_SQLITE_USE_CRSQLITE=1 - ) + target_sources(${PACKAGE_NAME} PRIVATE ../cpp/sqlite3.c ../cpp/OPBridge.cpp) endif() if (USE_SQLITE_VEC) diff --git a/android/build.gradle b/android/build.gradle index 75d10447..5d63be16 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -35,7 +35,6 @@ def reactNativeArchitectures() { def useSQLCipher = false def useLibsql = false def useTurso = false -def useCRSQLite = false def performanceMode = false def sqliteFlags = "" def enableFTS5 = false @@ -74,7 +73,6 @@ def opsqliteConfig = packageJson["op-sqlite"] if(opsqliteConfig) { println "[OP-SQLITE] Detected op-sqlite config from package.json at: " + packageJsonFile.absolutePath useSQLCipher = !!opsqliteConfig["sqlcipher"] - useCRSQLite = !!opsqliteConfig["crsqlite"] useSqliteVec = !!opsqliteConfig["sqliteVec"] performanceMode = opsqliteConfig["performanceMode"] sqliteFlags = opsqliteConfig["sqliteFlags"] ? opsqliteConfig["sqliteFlags"] : "" @@ -97,10 +95,6 @@ if(useSQLCipher) { println "[OP-SQLITE] using libsql. Report any issues to Turso" } -if(useCRSQLite) { - println "[OP-SQLITE] using CR-SQLite" -} - if(performanceMode) { println "[OP-SQLITE] Performance mode enabled" } @@ -171,10 +165,6 @@ android { cFlags += "-DOP_SQLITE_USE_TURSO=1" cppFlags += "-DOP_SQLITE_USE_TURSO=1" } - if(useCRSQLite) { - cFlags += "-DOP_SQLITE_USE_CRSQLITE=1" - cppFlags += "-DOP_SQLITE_USE_CRSQLITE=1" - } if(useSqliteVec) { cFlags += "-DOP_SQLITE_USE_SQLITE_VEC=1" cppFlags += "-DOP_SQLITE_USE_SQLITE_VEC=1" @@ -202,7 +192,6 @@ android { "-DDEFAULT_SQLITE_FLAGS='${defaultSqliteFlags.join(' ')}'", "-DSQLITE_FLAGS='$sqliteFlags'", "-DUSE_SQLCIPHER=${useSQLCipher ? 1 : 0}", - "-DUSE_CRSQLITE=${useCRSQLite ? 1 : 0}", "-DUSE_LIBSQL=${useLibsql ? 1 : 0}", "-DUSE_TURSO=${useTurso ? 1 : 0}", "-DUSE_SQLITE_VEC=${useSqliteVec ? 1 : 0}", @@ -273,9 +262,6 @@ android { if (useTurso) { srcDirs += 'src/main/tursoLibs' } - if (useCRSQLite) { - srcDirs += 'src/main/libcrsqlite' - } if (useSqliteVec) { srcDirs += 'src/main/libsqlitevec' } diff --git a/android/cpp-adapter.cpp b/android/cpp-adapter.cpp index 5912d7b1..2313fe31 100644 --- a/android/cpp-adapter.cpp +++ b/android/cpp-adapter.cpp @@ -33,7 +33,7 @@ struct OPSQLiteBridge : jni::JavaClass { std::string dbPathStr = dbPath->toStdString(); opsqlite::install(*jsiRuntime, jsCallInvoker, dbPathStr.c_str(), - "libcrsqlite", "libsqlite_vec"); + "libsqlite_vec"); } static void clearStateNativeJsi(jni::alias_ref thiz) { diff --git a/android/src/main/libcrsqlite/arm64-v8a/libcrsqlite.so b/android/src/main/libcrsqlite/arm64-v8a/libcrsqlite.so deleted file mode 100644 index 4d584cc4..00000000 Binary files a/android/src/main/libcrsqlite/arm64-v8a/libcrsqlite.so and /dev/null differ diff --git a/android/src/main/libcrsqlite/armeabi-v7a/libcrsqlite.so b/android/src/main/libcrsqlite/armeabi-v7a/libcrsqlite.so deleted file mode 100644 index b6ff67c4..00000000 Binary files a/android/src/main/libcrsqlite/armeabi-v7a/libcrsqlite.so and /dev/null differ diff --git a/android/src/main/libcrsqlite/x86/libcrsqlite.so b/android/src/main/libcrsqlite/x86/libcrsqlite.so deleted file mode 100644 index 568d1921..00000000 Binary files a/android/src/main/libcrsqlite/x86/libcrsqlite.so and /dev/null differ diff --git a/android/src/main/libcrsqlite/x86_64/libcrsqlite.so b/android/src/main/libcrsqlite/x86_64/libcrsqlite.so deleted file mode 100644 index e04ab9b1..00000000 Binary files a/android/src/main/libcrsqlite/x86_64/libcrsqlite.so and /dev/null differ diff --git a/cpp/DBHostObject.cpp b/cpp/DBHostObject.cpp deleted file mode 100644 index 05d9a850..00000000 --- a/cpp/DBHostObject.cpp +++ /dev/null @@ -1,790 +0,0 @@ -#include "DBHostObject.h" -#include "PreparedStatementHostObject.h" -#if OP_SQLITE_USE_LIBSQL -#include "libsql/bridge.hpp" -#else -#include "bridge.h" -#endif -#include "logs.h" -#include -#include "macros.hpp" -#include "utils.hpp" -#include -#include - -namespace opsqlite { - -namespace jsi = facebook::jsi; -namespace react = facebook::react; - -#ifdef OP_SQLITE_USE_LIBSQL -void DBHostObject::flush_pending_reactive_queries( - const std::shared_ptr &resolve) { - invoker->invokeAsync([resolve](jsi::Runtime &rt) { - resolve->asObject(rt).asFunction(rt).call(rt, {}); - }); -} -#elif defined(OP_SQLITE_USE_TURSO) - -std::string turso_remote_db_name(const std::string &url) { - return "turso_remote_" + std::to_string(std::hash{}(url)) + - ".sqlite"; -} - -void DBHostObject::flush_pending_reactive_queries( - const std::shared_ptr &resolve) { - invoker->invokeAsync([resolve](jsi::Runtime &rt) { - resolve->asObject(rt).asFunction(rt).call(rt, {}); - }); -} -#else -void DBHostObject::flush_pending_reactive_queries( - const std::shared_ptr &resolve) { - for (const auto &query_ptr : pending_reactive_queries) { - auto query = query_ptr.get(); - - std::vector results; - std::shared_ptr> metadata = - std::make_shared>(); - - auto status = opsqlite_execute_prepared_statement(db, query->stmt, &results, - metadata); - - invoker->invokeAsync( - [results = std::make_shared>(results), - callback = query->callback, metadata, - status = std::move(status)](jsi::Runtime &rt) { - auto jsiResult = create_result(rt, status, results.get(), metadata); - callback->asObject(rt).asFunction(rt).call(rt, jsiResult); - }); - } - - pending_reactive_queries.clear(); - - invoker->invokeAsync([resolve](jsi::Runtime &rt) { - resolve->asObject(rt).asFunction(rt).call(rt, {}); - }); -} - -void DBHostObject::on_commit() { - invoker->invokeAsync([this](jsi::Runtime &rt) { - commit_hook_callback->asObject(rt).asFunction(rt).call(rt); - }); -} - -void DBHostObject::on_rollback() { - invoker->invokeAsync([this](jsi::Runtime &rt) { - rollback_hook_callback->asObject(rt).asFunction(rt).call(rt); - }); -} - -void DBHostObject::on_update(const std::string &table, - const std::string &operation, long long row_id) { - if (update_hook_callback != nullptr) { - invoker->invokeAsync([callback = update_hook_callback, table, operation, - row_id](jsi::Runtime &rt) { - auto res = jsi::Object(rt); - res.setProperty(rt, "table", jsi::String::createFromUtf8(rt, table)); - res.setProperty(rt, "operation", - jsi::String::createFromUtf8(rt, operation)); - res.setProperty(rt, "rowId", jsi::Value(static_cast(row_id))); - - callback->asObject(rt).asFunction(rt).call(rt, res); - }); - } - - for (const auto &query_ptr : reactive_queries) { - auto query = query_ptr.get(); - - // The JS environment might have cleared the query while the update was - // queued For now this seems to prevent a EXC_BAD_ACCESS - if (query == nullptr) { - continue; - } - - if (query->discriminators.empty()) { - continue; - } - - bool shouldFire = false; - - for (const auto &discriminator : query->discriminators) { - // Tables don't match then skip - if (discriminator.table != table) { - continue; - } - - // If no ids are specified, then we should fire - if (discriminator.ids.empty()) { - shouldFire = true; - break; - } - - // If ids are specified, then we should check if the rowId matches - for (const auto &discrimator_id : discriminator.ids) { - if (row_id == discrimator_id) { - shouldFire = true; - break; - } - } - } - - if (shouldFire) { - pending_reactive_queries.insert(query_ptr); - } - } -} - -void DBHostObject::auto_register_update_hook() { - if (update_hook_callback == nullptr && reactive_queries.empty() && - is_update_hook_registered) { - opsqlite_deregister_update_hook(db); - is_update_hook_registered = false; - return; - } - - if (is_update_hook_registered) { - return; - } - - opsqlite_register_update_hook(db, this); - is_update_hook_registered = true; -} -#endif - -// _____ _ _ -// / ____| | | | | -// | | ___ _ __ ___| |_ _ __ _ _ ___| |_ ___ _ __ -// | | / _ \| '_ \/ __| __| '__| | | |/ __| __/ _ \| '__| -// | |___| (_) | | | \__ \ |_| | | |_| | (__| || (_) | | -// \_____\___/|_| |_|___/\__|_| \__,_|\___|\__\___/|_| -#ifdef OP_SQLITE_USE_LIBSQL -// Remote connection constructor -DBHostObject::DBHostObject(jsi::Runtime &rt, 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); -} - -// 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, - std::string &remote_encryption_key) - : base_path(path), db_name(db_name), delete_db_name(db_name) { - - thread_pool = std::make_shared(); - - db = - opsqlite_libsql_open_sync(db_name, path, url, auth_token, sync_interval, - offline, encryption_key, remote_encryption_key); - - create_jsi_functions(rt); -} - -#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) - : 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); -} - -// Sync connection constructor -DBHostObject::DBHostObject(jsi::Runtime &rt, 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) { - - thread_pool = std::make_shared(); - - db = opsqlite_open_sync(db_name, path, url, auth_token, - remote_encryption_key); - - create_jsi_functions(rt); -} - -#endif - -DBHostObject::DBHostObject(jsi::Runtime &rt, std::string &base_path, - std::string &db_name, std::string &path, - std::string &crsqlite_path, - std::string &sqlite_vec_path, - std::string &encryption_key) - : base_path(base_path), db_name(db_name), delete_db_name(db_name) { - thread_pool = std::make_shared(); - -#ifdef OP_SQLITE_USE_SQLCIPHER - db = opsqlite_open(db_name, path, crsqlite_path, sqlite_vec_path, - encryption_key); -#elif OP_SQLITE_USE_LIBSQL - db = opsqlite_libsql_open(db_name, path, crsqlite_path); -#else - db = opsqlite_open(db_name, path, crsqlite_path, sqlite_vec_path); -#endif - create_jsi_functions(rt); -}; - -void DBHostObject::create_jsi_functions(jsi::Runtime &rt) { - function_map["attach"] = HFN(this) { - std::string secondary_db_path = std::string(base_path); - - auto obj_params = args[0].asObject(rt); - - std::string secondary_db_name = - obj_params.getProperty(rt, "secondaryDbFileName").asString(rt).utf8(rt); - std::string alias = - obj_params.getProperty(rt, "alias").asString(rt).utf8(rt); - - if (obj_params.hasProperty(rt, "location")) { - std::string location = - obj_params.getProperty(rt, "location").asString(rt).utf8(rt); - secondary_db_path = secondary_db_path + location; - } - - // Reject zero bytes uniformly: libsql's libsql_bind_string takes a - // C-string and would silently truncate at the first zero byte; - // SQLite and Turso bind with explicit lengths. Failing loudly across - // all backends keeps behaviour consistent. - if (secondary_db_name.find('\0') != std::string::npos) { - throw std::runtime_error( - "[op-sqlite] attach secondaryDbFileName must not contain a zero byte"); - } - if (alias.find('\0') != std::string::npos) { - throw std::runtime_error( - "[op-sqlite] attach alias must not contain a zero byte"); - } - -#ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_attach(db, secondary_db_path, secondary_db_name, alias); -#else - opsqlite_attach(db, secondary_db_path, secondary_db_name, alias); -#endif - - return {}; - }); - - function_map["detach"] = HFN(this) { - if (!args[0].isString()) { - throw std::runtime_error("[op-sqlite] alias must be a strings"); - } - - std::string alias = args[0].asString(rt).utf8(rt); - if (alias.find('\0') != std::string::npos) { - throw std::runtime_error( - "[op-sqlite] detach alias must not contain a zero byte"); - } -#ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_detach(db, alias); -#else - opsqlite_detach(db, alias); -#endif - - return {}; - }); - - function_map["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) - if (db != nullptr) { - sqlite3_interrupt(db); - } -#endif - // Drain any in-flight async queries before closing the db handle. - // Without this, a queued/running execute() on the thread pool may - // dereference the freed sqlite3* pointer → heap corruption / SIGABRT. - thread_pool->waitFinished(); -#ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_close(db); - db = {}; -#else - opsqlite_close(db); - db = nullptr; -#endif - - return {}; - }); - - function_map["interrupt"] = HFN(this) { - if (invalidated) { - throw std::runtime_error("[op-sqlite][interrupt] database is closed"); - } - -#ifdef OP_SQLITE_USE_LIBSQL - throw std::runtime_error("[op-sqlite][interrupt] sqlite3_interrupt is not " - "supported with libsql"); -#elif defined(OP_SQLITE_USE_TURSO) - throw std::runtime_error("[op-sqlite][interrupt] sqlite3_interrupt is not " - "supported with Turso"); -#else - if (db == nullptr) { - throw std::runtime_error("[op-sqlite][interrupt] database is null"); - } - - sqlite3_interrupt(db); - return {}; -#endif - }); - - function_map["delete"] = HFN(this) { - if (count != 0) { - throw std::runtime_error("[op-sqlite] Delete no longer takes arguments"); - } - - invalidated = true; - // Abort pending native SQLite work before waiting on the thread pool. -#if !defined(OP_SQLITE_USE_LIBSQL) && !defined(OP_SQLITE_USE_TURSO) - if (db != nullptr) { - sqlite3_interrupt(db); - } -#endif - // Drain any in-flight async queries before closing/removing the db handle. - // Without this, queued/running work may dereference a freed sqlite handle. - thread_pool->waitFinished(); - - if (delete_db_name.empty()) { - throw std::runtime_error( - "[op-sqlite][delete] delete() is not supported for remote-only databases"); - } - -#ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_remove(db, delete_db_name, base_path); -#else - opsqlite_remove(db, delete_db_name, base_path); -#endif - - return {}; - }); - - function_map["executeRaw"] = HFN(this) { - const std::string query = args[0].asString(rt).utf8(rt); - const std::vector params = count == 2 && args[1].isObject() - ? to_variant_vec(rt, args[1]) - : std::vector(); - - return promisify( - rt, thread_pool, - [this, query, params]() { - std::vector> results; -#ifdef OP_SQLITE_USE_LIBSQL - auto status = - opsqlite_libsql_execute_raw(db, query, ¶ms, &results); -#else - auto status = opsqlite_execute_raw(db, query, ¶ms, &results); -#endif - return std::make_tuple(status, results); - }, - [](jsi::Runtime &rt, std::any prev) { - auto tuple = std::any_cast< - std::tuple>>>( - prev); - - return create_raw_result(rt, std::get<0>(tuple), &std::get<1>(tuple)); - }); - }); - - function_map["executeSync"] = HFN(this) { - std::string query = args[0].asString(rt).utf8(rt); - std::vector params; - - if (count == 2) { - params = to_variant_vec(rt, args[1]); - } -#ifdef OP_SQLITE_USE_LIBSQL - auto status = opsqlite_libsql_execute(db, query, ¶ms); -#else - auto status = opsqlite_execute(db, query, ¶ms); -#endif - - return create_js_rows(rt, status); - }); - - function_map["executeRawSync"] = HFN(this) { - const std::string query = args[0].asString(rt).utf8(rt); - std::vector params = count == 2 && args[1].isObject() - ? to_variant_vec(rt, args[1]) - : std::vector(); - - std::vector> results; - -#ifdef OP_SQLITE_USE_LIBSQL - auto status = opsqlite_libsql_execute_raw(db, query, ¶ms, &results); -#else - auto status = opsqlite_execute_raw(db, query, ¶ms, &results); -#endif - - return create_raw_result(rt, status, &results); - }); - - function_map["execute"] = HFN(this) { - const std::string query = args[0].asString(rt).utf8(rt); - std::vector params = count == 2 && args[1].isObject() - ? to_variant_vec(rt, args[1]) - : std::vector(); - - return promisify( - rt, thread_pool, - [this, query, params]() { -#ifdef OP_SQLITE_USE_LIBSQL - auto status = opsqlite_libsql_execute(db, query, ¶ms); -#else - auto status = opsqlite_execute(db, query, ¶ms); -#endif - return status; - }, - [](jsi::Runtime &rt, std::any prev) { - auto status = std::any_cast(prev); - return create_js_rows(rt, status); - }); - }); - - function_map["executeWithHostObjects"] = HFN(this) { - const std::string query = args[0].asString(rt).utf8(rt); - std::vector params = count == 2 && args[1].isObject() - ? to_variant_vec(rt, args[1]) - : std::vector(); - - return promisify( - rt, thread_pool, - [this, query, params]() { - std::vector results; - std::shared_ptr> metadata = - std::make_shared>(); -#ifdef OP_SQLITE_USE_LIBSQL - auto status = opsqlite_libsql_execute_with_host_objects( - db, query, ¶ms, &results, metadata); -#else - auto status = opsqlite_execute_host_objects(db, query, ¶ms, - &results, metadata); -#endif - return std::make_tuple(status, results, metadata); - }, - [](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)); - return create_result(rt, std::get<0>(tuple), results.get(), - std::get<2>(tuple)); - }); - }); - - function_map["executeBatch"] = HFN(this) { - if (count < 1) { - throw std::runtime_error( - "[op-sqlite][executeAsyncBatch] Incorrect parameter count"); - } - - const jsi::Value ¶ms = args[0]; - - if (params.isNull() || params.isUndefined()) { - throw std::runtime_error( - "[op-sqlite][executeAsyncBatch] - An array of SQL " - "commands or parameters is needed"); - } - - const jsi::Array &batchParams = params.asObject(rt).asArray(rt); - - std::vector commands; - to_batch_arguments(rt, batchParams, &commands); - - return promisify( - rt, thread_pool, - [this, commands]() { -#ifdef OP_SQLITE_USE_LIBSQL - auto batchResult = opsqlite_libsql_execute_batch(db, &commands); -#else - auto batchResult = opsqlite_execute_batch(db, &commands); -#endif - return batchResult; - }, - [](jsi::Runtime &rt, std::any prev) { - auto batchResult = std::any_cast(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) { -#ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_sync(db); -#else - opsqlite_sync(db); -#endif - return {}; - }); - -#ifdef OP_SQLITE_USE_LIBSQL - - function_map["setReservedBytes"] = HFN(this) { - auto reserved_bytes = static_cast(args[0].asNumber()); - opsqlite_libsql_set_reserved_bytes(db, reserved_bytes); - return {}; - }); - - function_map["getReservedBytes"] = HFN(this) { - 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) { - if (count < 1) { - throw std::runtime_error( - "[op-sqlite][loadFile] Incorrect parameter count"); - } - - const std::string sqlFileName = args[0].asString(rt).utf8(rt); - - return promisify( - rt, thread_pool, - [this, sqlFileName]() { return import_sql_file(db, sqlFileName); }, - [](jsi::Runtime &rt, std::any prev) { - auto result = std::any_cast(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) { - auto callback = std::make_shared(rt, args[0]); - - if (callback->isUndefined() || callback->isNull()) { - update_hook_callback = nullptr; - } else { - update_hook_callback = callback; - } - - auto_register_update_hook(); - return {}; - }); - - function_map["commitHook"] = HFN(this) { - if (count < 1) { - throw std::runtime_error("[op-sqlite][commitHook] callback needed"); - } - - auto callback = std::make_shared(rt, args[0]); - if (callback->isUndefined() || callback->isNull()) { - opsqlite_deregister_commit_hook(db); - return {}; - } - commit_hook_callback = callback; - opsqlite_register_commit_hook(db, this); - - return {}; - }); - - function_map["rollbackHook"] = HFN(this) { - if (count < 1) { - throw std::runtime_error("[op-sqlite][rollbackHook] callback needed"); - } - - auto callback = std::make_shared(rt, args[0]); - - if (callback->isUndefined() || callback->isNull()) { - opsqlite_deregister_rollback_hook(db); - return {}; - } - rollback_hook_callback = callback; - - opsqlite_register_rollback_hook(db, this); - return {}; - }); - - function_map["loadExtension"] = HFN(this) { - auto path = args[0].asString(rt).utf8(rt); - std::string entry_point; - if (count > 1 && args[1].isString()) { - entry_point = args[1].asString(rt).utf8(rt); - } - - opsqlite_load_extension(db, path, entry_point); - return {}; - }); - - function_map["reactiveExecute"] = HFN(this) { - auto query = args[0].asObject(rt); - - const std::string query_str = - query.getProperty(rt, "query").asString(rt).utf8(rt); - auto js_args = query.getProperty(rt, "arguments"); - auto js_discriminators = - query.getProperty(rt, "fireOn").asObject(rt).asArray(rt); - auto variant_args = to_variant_vec(rt, js_args); - - sqlite3_stmt *stmt = opsqlite_prepare_statement(db, query_str); - opsqlite_bind_statement(stmt, &variant_args); - - auto callback = - std::make_shared(query.getProperty(rt, "callback")); - - std::vector discriminators; - - for (size_t i = 0; i < js_discriminators.length(rt); i++) { - auto js_discriminator = - js_discriminators.getValueAtIndex(rt, i).asObject(rt); - std::string table = - js_discriminator.getProperty(rt, "table").asString(rt).utf8(rt); - std::vector ids; - if (js_discriminator.hasProperty(rt, "ids")) { - auto js_ids = - js_discriminator.getProperty(rt, "ids").asObject(rt).asArray(rt); - for (size_t j = 0; j < js_ids.length(rt); j++) { - ids.push_back( - static_cast(js_ids.getValueAtIndex(rt, j).asNumber())); - } - } - discriminators.push_back({table, ids}); - } - - std::shared_ptr reactiveQuery = - std::make_shared( - ReactiveQuery{stmt, discriminators, callback}); - - reactive_queries.push_back(reactiveQuery); - - auto_register_update_hook(); - - auto unsubscribe = HFN2(this, reactiveQuery) { - auto it = std::find(reactive_queries.begin(), reactive_queries.end(), - reactiveQuery); - if (it != reactive_queries.end()) { - reactive_queries.erase(it); - } - auto_register_update_hook(); - return {}; - }); - - return unsubscribe; - }); -#endif - - function_map["prepareStatement"] = HFN(this) { - auto query = args[0].asString(rt).utf8(rt); -#ifdef OP_SQLITE_USE_LIBSQL - libsql_stmt_t statement = opsqlite_libsql_prepare_statement(db, query); -#else - sqlite3_stmt *statement = opsqlite_prepare_statement(db, query); -#endif - auto preparedStatementHostObject = - std::make_shared(db, statement, - thread_pool); - - return jsi::Object::createFromHostObject(rt, preparedStatementHostObject); - }); - - function_map["getDbPath"] = HFN(this) { - std::string path = std::string(base_path); - - if (count == 1) { - if (!args[0].isString()) { - throw std::runtime_error( - "[op-sqlite][open] database location must be a string"); - } - - std::string last_path = args[0].asString(rt).utf8(rt); - - if (last_path == ":memory:") { - path = ":memory:"; - } else if (last_path.rfind('/', 0) == 0) { - path = last_path; - } else { - path = path + "/" + last_path; - } - } - - auto result = opsqlite_get_db_path(db_name, path); - return jsi::String::createFromUtf8(rt, result); - }); - - function_map["flushPendingReactiveQueries"] = HFN(this) { - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); - auto promise = promiseCtr.callAsConstructor(rt, HFN(this) { - auto resolve = std::make_shared(rt, args[0]); - - auto task = [this, resolve]() { - flush_pending_reactive_queries(resolve); - }; - - thread_pool->queueWork(task); - - return {}; - })); - - 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() { - if (invalidated) { - return; - } - - invalidated = true; - // Drain in-flight thread pool work before closing the db handle. - // restartPool() joins threads (waiting for the current task) but then - // needlessly re-creates the pool. waitFinished() is sufficient: it - // blocks until the queue is empty and no worker is busy, then the - // ThreadPool destructor (via shared_ptr release) joins the threads. - thread_pool->waitFinished(); - -#ifdef OP_SQLITE_USE_LIBSQL - opsqlite_libsql_close(db); -#else - if (db != nullptr) { - opsqlite_close(db); - db = nullptr; - } -#endif -} - -DBHostObject::~DBHostObject() { invalidate(); } - -} // namespace opsqlite diff --git a/cpp/DBHostObject.h b/cpp/DBHostObject.h deleted file mode 100644 index 7029a152..00000000 --- a/cpp/DBHostObject.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include "OPThreadPool.h" -#include "types.hpp" -#include -#include -#include -#ifdef OP_SQLITE_USE_LIBSQL -#include "libsql/bridge.hpp" -#else -#ifdef __ANDROID__ -#include "sqlite3.h" -#else -#include -#endif -#endif -#include -#include - -namespace opsqlite { - -namespace jsi = facebook::jsi; -namespace react = facebook::react; - -struct PendingReactiveInvocation { - std::string db_name; - std::string table; - std::string rowid; -}; - -struct TableRowDiscriminator { - std::string table; - std::vector ids; -}; - -struct ReactiveQuery { -#ifndef OP_SQLITE_USE_LIBSQL - sqlite3_stmt *stmt; -#endif - std::vector discriminators; - std::shared_ptr callback; -}; - -class JSI_EXPORT DBHostObject : public jsi::HostObject { -public: - // Normal constructor shared between all backends - DBHostObject(jsi::Runtime &rt, std::string &base_path, std::string &db_name, - std::string &path, std::string &crsqlite_path, - std::string &sqlite_vec_path, 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); - - // 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, - 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); - - // 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, - 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; - -private: - std::set> pending_reactive_queries; - void auto_register_update_hook(); - void create_jsi_functions(jsi::Runtime &rt); - void flush_pending_reactive_queries(const std::shared_ptr &resolve); - - std::unordered_map function_map; - std::string base_path; - std::shared_ptr thread_pool; - std::string db_name; - std::string delete_db_name; - std::shared_ptr update_hook_callback; - std::shared_ptr commit_hook_callback; - std::shared_ptr rollback_hook_callback; - std::vector> reactive_queries; - std::vector pending_reactive_invocations; - bool is_update_hook_registered = false; - bool invalidated = false; -#ifdef OP_SQLITE_USE_LIBSQL - DB db; -#else - sqlite3 *db; -#endif -}; - -} // namespace opsqlite diff --git a/cpp/bridge.cpp b/cpp/OPBridge.cpp similarity index 92% rename from cpp/bridge.cpp rename to cpp/OPBridge.cpp index adafe298..267ca579 100644 --- a/cpp/bridge.cpp +++ b/cpp/OPBridge.cpp @@ -1,9 +1,8 @@ // 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 the DB state -#include "bridge.h" -#include "DBHostObject.h" +#include "OPBridge.hpp" #include "DumbHostObject.h" #include "SmartHostObject.h" #include "logs.h" @@ -80,12 +79,10 @@ std::string opsqlite_get_db_path(std::string const &db_name, #ifdef OP_SQLITE_USE_SQLCIPHER sqlite3 *opsqlite_open(std::string const &name, std::string const &path, - std::string const &crsqlite_path, std::string const &sqlite_vec_path, std::string const &encryption_key) { #else sqlite3 *opsqlite_open(std::string const &name, std::string const &path, - [[maybe_unused]] std::string const &crsqlite_path, [[maybe_unused]] std::string const &sqlite_vec_path) { #endif std::string final_path = opsqlite_get_db_path(name, path); @@ -127,17 +124,6 @@ sqlite3 *opsqlite_open(std::string const &name, std::string const &path, sqlite3_enable_load_extension(db, 1); #endif -#ifdef OP_SQLITE_USE_CRSQLITE - const char *crsqliteEntryPoint = "sqlite3_crsqlite_init"; - - sqlite3_load_extension(db, crsqlite_path.c_str(), crsqliteEntryPoint, - &errMsg); - - if (errMsg != nullptr) { - throw std::runtime_error(errMsg); - } -#endif - #ifdef OP_SQLITE_USE_SQLITE_VEC const char *vec_entry_point = "sqlite3_vec_init"; @@ -167,10 +153,6 @@ void create_dirs_if_needed(const std::string &path) { } void opsqlite_close(sqlite3 *db) { -#ifdef OP_SQLITE_USE_CRSQLITE - opsqlite_execute(db, "select crsql_finalize();", nullptr); -#endif - sqlite3_close_v2(db); } @@ -800,49 +782,6 @@ std::string operation_to_string(int operation_type) { } } -void update_callback(void *db_host_object_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); -} - -void opsqlite_register_update_hook(sqlite3 *db, void *db_host_object) { - sqlite3_update_hook(db, &update_callback, (void *)db_host_object); -} - -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(); - 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_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 opsqlite_register_rollback_hook(sqlite3 *db, void *db_host_object_ptr) { - sqlite3_rollback_hook(db, &rollback_callback, db_host_object_ptr); -} - -void opsqlite_deregister_rollback_hook(sqlite3 *db) { - sqlite3_rollback_hook(db, nullptr, nullptr); -} - void opsqlite_load_extension(sqlite3 *db, std::string &path, std::string &entry_point) { #ifdef OP_SQLITE_USE_PHONE_VERSION diff --git a/cpp/bridge.h b/cpp/OPBridge.hpp similarity index 77% rename from cpp/bridge.h rename to cpp/OPBridge.hpp index 9c6d53f1..ffdf5942 100644 --- a/cpp/bridge.h +++ b/cpp/OPBridge.hpp @@ -15,24 +15,15 @@ namespace opsqlite { namespace jsi = facebook::jsi; -/// Convenience types to avoid super long types -typedef std::function - UpdateCallback; -typedef std::function CommitCallback; -typedef std::function RollbackCallback; - std::string opsqlite_get_db_path(std::string const &db_name, std::string const &location); #ifdef OP_SQLITE_USE_SQLCIPHER sqlite3 *opsqlite_open(std::string const &dbName, std::string const &path, - std::string const &crsqlite_path, std::string const &sqlite_vec_path, std::string const &encryption_key); #else sqlite3 *opsqlite_open(std::string const &name, std::string const &path, - [[maybe_unused]] std::string const &crsqlite_path, std::string const &sqlite_vec_path); #endif @@ -75,13 +66,6 @@ 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_deregister_update_hook(sqlite3 *db); -void opsqlite_register_commit_hook(sqlite3 *db, void *db_host_object_ptr); -void opsqlite_deregister_commit_hook(sqlite3 *db); -void opsqlite_register_rollback_hook(sqlite3 *db, void *db_host_object_ptr); -void opsqlite_deregister_rollback_hook(sqlite3 *db); - sqlite3_stmt *opsqlite_prepare_statement(sqlite3 *db, std::string const &query); void opsqlite_finalize_statement(sqlite3_stmt *statement); diff --git a/cpp/OPDB.cpp b/cpp/OPDB.cpp new file mode 100644 index 00000000..14ffae43 --- /dev/null +++ b/cpp/OPDB.cpp @@ -0,0 +1,282 @@ +#include "OPDB.hpp" +#if OP_SQLITE_USE_LIBSQL +#include "libsql/OPBridge.hpp" +#else +#include "OPBridge.hpp" +#endif +#include "utils.hpp" + +namespace opsqlite { + +namespace jsi = facebook::jsi; + +#ifdef OP_SQLITE_USE_TURSO +namespace { + +std::string turso_remote_db_name(const std::string &url) { + return "turso_remote_" + std::to_string(std::hash{}(url)) + + ".sqlite"; +} + +} // namespace +#endif + +#if !defined(OP_SQLITE_USE_LIBSQL) && !defined(OP_SQLITE_USE_TURSO) +namespace { + +std::string opdb_operation_to_string(int operation_type) { + switch (operation_type) { + case SQLITE_INSERT: + return "INSERT"; + case SQLITE_DELETE: + return "DELETE"; + case SQLITE_UPDATE: + return "UPDATE"; + default: + throw std::runtime_error("Unknown SQLite operation on hook"); + } +} + +void opdb_update_callback(void *db_ptr, int operation_type, + [[maybe_unused]] char const *database, + char const *table, sqlite3_int64 row_id) { + auto opdb = reinterpret_cast(db_ptr); + opdb->on_update(std::string(table), + opdb_operation_to_string(operation_type), row_id); +} + +int opdb_commit_callback(void *db_ptr) { + auto opdb = reinterpret_cast(db_ptr); + opdb->on_commit(); + return 0; +} + +void opdb_rollback_callback(void *db_ptr) { + auto opdb = reinterpret_cast(db_ptr); + opdb->on_rollback(); +} + +} // namespace +#endif + +OPDB::OPDB(const std::string &name, const std::string &base_path, + const std::string &sqlite_vec_path, + const std::string &encryption_key) + : base_path(base_path), db_name(name), delete_db_name(name) { + thread_pool = std::make_shared(); + +#ifdef OP_SQLITE_USE_SQLCIPHER + db = opsqlite_open(db_name, base_path, sqlite_vec_path, encryption_key); +#elif OP_SQLITE_USE_LIBSQL + db = opsqlite_libsql_open(db_name, base_path); +#else + db = opsqlite_open(db_name, base_path, sqlite_vec_path); +#endif +} + +#ifdef OP_SQLITE_USE_LIBSQL +OPDB::OPDB(const std::string &url, const std::string &auth_token) + : db_name(url) { + thread_pool = std::make_shared(); + db = opsqlite_libsql_open_remote(url, auth_token); +} + +OPDB::OPDB(const std::string &name, const std::string &path, + const std::string &url, const std::string &auth_token, + int sync_interval, bool offline, + const std::string &encryption_key, + const std::string &remote_encryption_key) + : base_path(path), db_name(name), delete_db_name(name) { + thread_pool = std::make_shared(); + db = opsqlite_libsql_open_sync(name, path, url, auth_token, sync_interval, + offline, encryption_key, + remote_encryption_key); +} +#elif defined(OP_SQLITE_USE_TURSO) +OPDB::OPDB(const std::string &url, const std::string &auth_token, + const 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); +} + +OPDB::OPDB(const std::string &name, const std::string &path, + const std::string &url, const std::string &auth_token, + const std::string &remote_encryption_key) + : base_path(path), db_name(name), delete_db_name(name) { + thread_pool = std::make_shared(); + db = opsqlite_open_sync(name, path, url, auth_token, + remote_encryption_key); +} +#endif + +#ifdef OP_SQLITE_USE_LIBSQL +void OPDB::flush_pending_reactive_queries( + const std::shared_ptr &resolve) { + invoker->invokeAsync([resolve](jsi::Runtime &rt) { + resolve->asObject(rt).asFunction(rt).call(rt, {}); + }); +} +#elif defined(OP_SQLITE_USE_TURSO) +void OPDB::flush_pending_reactive_queries( + const std::shared_ptr &resolve) { + invoker->invokeAsync([resolve](jsi::Runtime &rt) { + resolve->asObject(rt).asFunction(rt).call(rt, {}); + }); +} +#else +void OPDB::flush_pending_reactive_queries( + const std::shared_ptr &resolve) { + for (const auto &query_ptr : pending_reactive_queries) { + auto query = query_ptr.get(); + + std::vector results; + std::shared_ptr> metadata = + std::make_shared>(); + + auto status = opsqlite_execute_prepared_statement(db, query->stmt, &results, + metadata); + + invoker->invokeAsync( + [results = std::make_shared>(results), + callback = query->callback, metadata, + status = std::move(status)](jsi::Runtime &rt) { + auto jsiResult = create_result(rt, status, results.get(), metadata); + callback->asObject(rt).asFunction(rt).call(rt, jsiResult); + }); + } + + pending_reactive_queries.clear(); + + invoker->invokeAsync([resolve](jsi::Runtime &rt) { + resolve->asObject(rt).asFunction(rt).call(rt, {}); + }); +} + +void OPDB::on_commit() { + auto callback = commit_hook_callback; + if (callback == nullptr) { + return; + } + + invoker->invokeAsync([callback](jsi::Runtime &rt) { + callback->asObject(rt).asFunction(rt).call(rt); + }); +} + +void OPDB::on_rollback() { + auto callback = rollback_hook_callback; + if (callback == nullptr) { + return; + } + + invoker->invokeAsync([callback](jsi::Runtime &rt) { + callback->asObject(rt).asFunction(rt).call(rt); + }); +} + +void OPDB::on_update(const std::string &table, const std::string &operation, + long long row_id) { + if (update_hook_callback != nullptr) { + invoker->invokeAsync([callback = update_hook_callback, table, operation, + row_id](jsi::Runtime &rt) { + auto res = jsi::Object(rt); + res.setProperty(rt, "table", jsi::String::createFromUtf8(rt, table)); + res.setProperty(rt, "operation", + jsi::String::createFromUtf8(rt, operation)); + res.setProperty(rt, "rowId", jsi::Value(static_cast(row_id))); + + callback->asObject(rt).asFunction(rt).call(rt, res); + }); + } + + for (const auto &query_ptr : reactive_queries) { + auto query = query_ptr.get(); + + if (query == nullptr) { + continue; + } + + if (query->discriminators.empty()) { + continue; + } + + bool shouldFire = false; + + for (const auto &discriminator : query->discriminators) { + if (discriminator.table != table) { + continue; + } + + if (discriminator.ids.empty()) { + shouldFire = true; + break; + } + + for (const auto &discriminator_id : discriminator.ids) { + if (row_id == discriminator_id) { + shouldFire = true; + break; + } + } + } + + if (shouldFire) { + pending_reactive_queries.insert(query_ptr); + } + } +} + +void OPDB::auto_register_update_hook() { + if (update_hook_callback == nullptr && reactive_queries.empty() && + is_update_hook_registered) { + sqlite3_update_hook(db, nullptr, nullptr); + is_update_hook_registered = false; + return; + } + + if (is_update_hook_registered) { + return; + } + + sqlite3_update_hook(db, &opdb_update_callback, this); + is_update_hook_registered = true; +} + +void OPDB::register_commit_hook() { + sqlite3_commit_hook(db, &opdb_commit_callback, this); +} + +void OPDB::deregister_commit_hook() { sqlite3_commit_hook(db, nullptr, nullptr); } + +void OPDB::register_rollback_hook() { + sqlite3_rollback_hook(db, &opdb_rollback_callback, this); +} + +void OPDB::deregister_rollback_hook() { + sqlite3_rollback_hook(db, nullptr, nullptr); +} +#endif + +void OPDB::invalidate() { + if (invalidated) { + return; + } + + invalidated = true; + thread_pool->waitFinished(); + +#ifdef OP_SQLITE_USE_LIBSQL + opsqlite_libsql_close(db); +#else + if (db != nullptr) { + opsqlite_close(db); + db = nullptr; + } +#endif +} + +OPDB::~OPDB() { invalidate(); } + +} // namespace opsqlite diff --git a/cpp/OPDB.hpp b/cpp/OPDB.hpp new file mode 100644 index 00000000..def4e9f5 --- /dev/null +++ b/cpp/OPDB.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include "OPThreadPool.h" +#include "types.hpp" +#include +#include +#include +#ifdef OP_SQLITE_USE_LIBSQL +#include "libsql/OPBridge.hpp" +#else +#ifdef __ANDROID__ +#include "sqlite3.h" +#else +#include +#endif +#endif +#include +#include + +namespace opsqlite { + +namespace jsi = facebook::jsi; + +struct OPPendingReactiveInvocation { + std::string db_name; + std::string table; + std::string rowid; +}; + +struct OPTableRowDiscriminator { + std::string table; + std::vector ids; +}; + +struct OPReactiveQuery { +#if !defined(OP_SQLITE_USE_LIBSQL) && !defined(OP_SQLITE_USE_TURSO) + sqlite3_stmt *stmt; +#endif + std::vector discriminators; + std::shared_ptr callback; +}; + +class OPDB : public jsi::NativeState { +public: + OPDB(const std::string &name, const std::string &base_path, + const std::string &sqlite_vec_path, + const std::string &encryption_key); +#ifdef OP_SQLITE_USE_LIBSQL + OPDB(const std::string &url, const std::string &auth_token); + OPDB(const std::string &name, const std::string &path, + const std::string &url, const std::string &auth_token, + int sync_interval, bool offline, + const std::string &encryption_key, + const std::string &remote_encryption_key); +#elif defined(OP_SQLITE_USE_TURSO) + OPDB(const std::string &url, const std::string &auth_token, + const std::string &base_path); + OPDB(const std::string &name, const std::string &path, + const std::string &url, const std::string &auth_token, + const std::string &remote_encryption_key); +#endif + ~OPDB() override; + + void auto_register_update_hook(); + void register_commit_hook(); + void deregister_commit_hook(); + void register_rollback_hook(); + void deregister_rollback_hook(); + void flush_pending_reactive_queries( + const std::shared_ptr &resolve); + void on_update(const std::string &table, const std::string &operation, + long long row_id); + void on_commit(); + void on_rollback(); + void invalidate(); + + std::set> pending_reactive_queries; + std::string base_path; + std::shared_ptr thread_pool; + std::string db_name; + std::string delete_db_name; + std::shared_ptr update_hook_callback; + std::shared_ptr commit_hook_callback; + std::shared_ptr rollback_hook_callback; + std::vector> reactive_queries; + std::vector pending_reactive_invocations; + bool is_update_hook_registered = false; + bool invalidated = false; +#ifdef OP_SQLITE_USE_LIBSQL + DB db; +#else + sqlite3 *db; +#endif +}; + +} // namespace opsqlite + + diff --git a/cpp/OPSqlite.cpp b/cpp/OPSqlite.cpp index d892ec59..ce1b9d42 100644 --- a/cpp/OPSqlite.cpp +++ b/cpp/OPSqlite.cpp @@ -1,18 +1,21 @@ #include "OPSqlite.hpp" -#include "DBHostObject.h" #include "DumbHostObject.h" #include "OPThreadPool.h" +#include "OPDB.hpp" +#include "PreparedStatementHostObject.h" #ifdef OP_SQLITE_USE_LIBSQL -#include "libsql/bridge.hpp" +#include "libsql/OPBridge.hpp" #else -#include "bridge.h" +#include "OPBridge.hpp" #endif #include "logs.h" #include "macros.hpp" #include "utils.hpp" +#include #include #include #include +#include #include #include @@ -22,9 +25,8 @@ namespace jsi = facebook::jsi; namespace react = facebook::react; std::string _base_path; -std::string _crsqlite_path; std::string _sqlite_vec_path; -std::vector> dbs; +std::vector> native_dbs; bool invalidated = false; std::shared_ptr invoker; @@ -34,42 +36,623 @@ void invalidate() { // Global flag used by the threads to stop work invalidated = true; - for (const auto &db : dbs) { + for (const auto &db : native_dbs) { db->invalidate(); } // Clear our existing vector of shared pointers so they can be garbage // collected - dbs.clear(); + native_dbs.clear(); } +jsi::Object create_db(jsi::Runtime &rt, const std::shared_ptr &opdb) { + jsi::Object db(rt); + native_dbs.emplace_back(opdb); + db.setNativeState(rt, opdb); + + db.setProperty(rt, "attach", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + std::string secondary_db_path = std::string(opdb->base_path); + + auto obj_params = args[0].asObject(rt); + + std::string secondary_db_name = + obj_params.getProperty(rt, "secondaryDbFileName").asString(rt).utf8(rt); + std::string alias = + obj_params.getProperty(rt, "alias").asString(rt).utf8(rt); + + if (obj_params.hasProperty(rt, "location")) { + std::string location = + obj_params.getProperty(rt, "location").asString(rt).utf8(rt); + secondary_db_path = secondary_db_path + location; + } + + if (secondary_db_name.find('\0') != std::string::npos) { + throw std::runtime_error( + "[op-sqlite] attach secondaryDbFileName must not contain a zero byte"); + } + if (alias.find('\0') != std::string::npos) { + throw std::runtime_error( + "[op-sqlite] attach alias must not contain a zero byte"); + } + +#ifdef OP_SQLITE_USE_LIBSQL + opsqlite_libsql_attach(opdb->db, secondary_db_path, secondary_db_name, + alias); +#else + opsqlite_attach(opdb->db, secondary_db_path, secondary_db_name, alias); +#endif + + return {}; + })); + + db.setProperty(rt, "detach", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + if (!args[0].isString()) { + throw std::runtime_error("[op-sqlite] alias must be a strings"); + } + + std::string alias = args[0].asString(rt).utf8(rt); + if (alias.find('\0') != std::string::npos) { + throw std::runtime_error( + "[op-sqlite] detach alias must not contain a zero byte"); + } +#ifdef OP_SQLITE_USE_LIBSQL + opsqlite_libsql_detach(opdb->db, alias); +#else + opsqlite_detach(opdb->db, alias); +#endif + + return {}; + })); + + db.setProperty(rt, "close", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + opdb->invalidated = true; +#if !defined(OP_SQLITE_USE_LIBSQL) && !defined(OP_SQLITE_USE_TURSO) + if (opdb->db != nullptr) { + sqlite3_interrupt(opdb->db); + } +#endif + opdb->thread_pool->waitFinished(); +#ifdef OP_SQLITE_USE_LIBSQL + opsqlite_libsql_close(opdb->db); + opdb->db = {}; +#else + opsqlite_close(opdb->db); + opdb->db = nullptr; +#endif + + return {}; + })); + + db.setProperty(rt, "interrupt", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + if (opdb->invalidated) { + throw std::runtime_error("[op-sqlite][interrupt] database is closed"); + } + +#ifdef OP_SQLITE_USE_LIBSQL + throw std::runtime_error("[op-sqlite][interrupt] sqlite3_interrupt is not " + "supported with libsql"); +#elif defined(OP_SQLITE_USE_TURSO) + throw std::runtime_error("[op-sqlite][interrupt] sqlite3_interrupt is not " + "supported with Turso"); +#else + if (opdb->db == nullptr) { + throw std::runtime_error("[op-sqlite][interrupt] database is null"); + } + + sqlite3_interrupt(opdb->db); + return {}; +#endif + })); + + db.setProperty(rt, "delete", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + if (count != 0) { + throw std::runtime_error("[op-sqlite] Delete no longer takes arguments"); + } + + opdb->invalidated = true; +#if !defined(OP_SQLITE_USE_LIBSQL) && !defined(OP_SQLITE_USE_TURSO) + if (opdb->db != nullptr) { + sqlite3_interrupt(opdb->db); + } +#endif + opdb->thread_pool->waitFinished(); + + if (opdb->delete_db_name.empty()) { + throw std::runtime_error( + "[op-sqlite][delete] delete() is not supported for remote-only databases"); + } + +#ifdef OP_SQLITE_USE_LIBSQL + opsqlite_libsql_remove(opdb->db, opdb->delete_db_name, opdb->base_path); +#else + opsqlite_remove(opdb->db, opdb->delete_db_name, opdb->base_path); +#endif + + return {}; + })); + + db.setProperty(rt, "executeRaw", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + const std::string query = args[0].asString(rt).utf8(rt); + const std::vector params = count == 2 && args[1].isObject() + ? to_variant_vec(rt, args[1]) + : std::vector(); + + return promisify( + rt, opdb->thread_pool, + [opdb, query, params]() { + std::vector> results; +#ifdef OP_SQLITE_USE_LIBSQL + auto status = + opsqlite_libsql_execute_raw(opdb->db, query, ¶ms, &results); +#else + auto status = opsqlite_execute_raw(opdb->db, query, ¶ms, &results); +#endif + return std::make_tuple(status, results); + }, + [](jsi::Runtime &rt, std::any prev) { + auto tuple = std::any_cast< + std::tuple>>>( + prev); + + return create_raw_result(rt, std::get<0>(tuple), &std::get<1>(tuple)); + }); + })); + + db.setProperty(rt, "executeSync", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + std::string query = args[0].asString(rt).utf8(rt); + std::vector params; + + if (count == 2) { + params = to_variant_vec(rt, args[1]); + } +#ifdef OP_SQLITE_USE_LIBSQL + auto status = opsqlite_libsql_execute(opdb->db, query, ¶ms); +#else + auto status = opsqlite_execute(opdb->db, query, ¶ms); +#endif + + return create_js_rows(rt, status); + })); + + db.setProperty(rt, "executeRawSync", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + const std::string query = args[0].asString(rt).utf8(rt); + std::vector params = count == 2 && args[1].isObject() + ? to_variant_vec(rt, args[1]) + : std::vector(); + + std::vector> results; + +#ifdef OP_SQLITE_USE_LIBSQL + auto status = + opsqlite_libsql_execute_raw(opdb->db, query, ¶ms, &results); +#else + auto status = opsqlite_execute_raw(opdb->db, query, ¶ms, &results); +#endif + + return create_raw_result(rt, status, &results); + })); + + db.setProperty(rt, "execute", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + const std::string query = args[0].asString(rt).utf8(rt); + std::vector params = count == 2 && args[1].isObject() + ? to_variant_vec(rt, args[1]) + : std::vector(); + + return promisify( + rt, opdb->thread_pool, + [opdb, query, params]() { +#ifdef OP_SQLITE_USE_LIBSQL + auto status = opsqlite_libsql_execute(opdb->db, query, ¶ms); +#else + auto status = opsqlite_execute(opdb->db, query, ¶ms); +#endif + return status; + }, + [](jsi::Runtime &rt, std::any prev) { + auto status = std::any_cast(prev); + return create_js_rows(rt, status); + }); + })); + + db.setProperty(rt, "executeWithHostObjects", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + const std::string query = args[0].asString(rt).utf8(rt); + std::vector params = count == 2 && args[1].isObject() + ? to_variant_vec(rt, args[1]) + : std::vector(); + + return promisify( + rt, opdb->thread_pool, + [opdb, query, params]() { + std::vector results; + std::shared_ptr> metadata = + std::make_shared>(); +#ifdef OP_SQLITE_USE_LIBSQL + auto status = opsqlite_libsql_execute_with_host_objects( + opdb->db, query, ¶ms, &results, metadata); +#else + auto status = opsqlite_execute_host_objects(opdb->db, query, ¶ms, + &results, metadata); +#endif + return std::make_tuple(status, results, metadata); + }, + [](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)); + return create_result(rt, std::get<0>(tuple), results.get(), + std::get<2>(tuple)); + }); + })); + + db.setProperty(rt, "executeBatch", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + if (count < 1) { + throw std::runtime_error( + "[op-sqlite][executeAsyncBatch] Incorrect parameter count"); + } + + const jsi::Value ¶ms = args[0]; + + if (params.isNull() || params.isUndefined()) { + throw std::runtime_error( + "[op-sqlite][executeAsyncBatch] - An array of SQL commands or parameters is needed"); + } + + const jsi::Array &batchParams = params.asObject(rt).asArray(rt); + + std::vector commands; + to_batch_arguments(rt, batchParams, &commands); + + return promisify( + rt, opdb->thread_pool, + [opdb, commands]() { +#ifdef OP_SQLITE_USE_LIBSQL + auto batchResult = opsqlite_libsql_execute_batch(opdb->db, &commands); +#else + auto batchResult = opsqlite_execute_batch(opdb->db, &commands); +#endif + return batchResult; + }, + [](jsi::Runtime &rt, std::any prev) { + auto batchResult = std::any_cast(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) + db.setProperty(rt, "sync", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); +#ifdef OP_SQLITE_USE_LIBSQL + opsqlite_libsql_sync(opdb->db); +#else + opsqlite_sync(opdb->db); +#endif + return {}; + })); + +#ifdef OP_SQLITE_USE_LIBSQL + db.setProperty(rt, "setReservedBytes", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + auto reserved_bytes = static_cast(args[0].asNumber()); + opsqlite_libsql_set_reserved_bytes(opdb->db, reserved_bytes); + return {}; + })); + + db.setProperty(rt, "getReservedBytes", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + return {opsqlite_libsql_get_reserved_bytes(opdb->db)}; + })); +#elif defined(OP_SQLITE_USE_TURSO) + db.setProperty(rt, "setReservedBytes", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function setReservedBytes not implemented for current backend (libsql or sqlcipher)"); + })); + + db.setProperty(rt, "getReservedBytes", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function getReservedBytes not implemented for current backend (libsql or sqlcipher)"); + })); +#endif +#else + db.setProperty(rt, "sync", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function sync not implemented for current backend (libsql or sqlcipher)"); + })); + + db.setProperty(rt, "setReservedBytes", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function setReservedBytes not implemented for current backend (libsql or sqlcipher)"); + })); + + db.setProperty(rt, "getReservedBytes", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function getReservedBytes not implemented for current backend (libsql or sqlcipher)"); + })); +#endif + +#if !defined(OP_SQLITE_USE_LIBSQL) && !defined(OP_SQLITE_USE_TURSO) + db.setProperty(rt, "loadFile", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + if (count < 1) { + throw std::runtime_error( + "[op-sqlite][loadFile] Incorrect parameter count"); + } + + const std::string sqlFileName = args[0].asString(rt).utf8(rt); + + return promisify( + rt, opdb->thread_pool, + [opdb, sqlFileName]() { return import_sql_file(opdb->db, sqlFileName); }, + [](jsi::Runtime &rt, std::any prev) { + auto result = std::any_cast(prev); + auto res = jsi::Object(rt); + res.setProperty(rt, "rowsAffected", jsi::Value(result.affectedRows)); + res.setProperty(rt, "commands", jsi::Value(result.commands)); + return res; + }); + })); + + db.setProperty(rt, "updateHook", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + auto callback = std::make_shared(rt, args[0]); + + if (callback->isUndefined() || callback->isNull()) { + opdb->update_hook_callback = nullptr; + } else { + opdb->update_hook_callback = callback; + } + + opdb->auto_register_update_hook(); + return {}; + })); + + db.setProperty(rt, "commitHook", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + if (count < 1) { + throw std::runtime_error("[op-sqlite][commitHook] callback needed"); + } + + auto callback = std::make_shared(rt, args[0]); + if (callback->isUndefined() || callback->isNull()) { + opdb->commit_hook_callback = nullptr; + opdb->deregister_commit_hook(); + return {}; + } + opdb->commit_hook_callback = callback; + opdb->register_commit_hook(); + + return {}; + })); + + db.setProperty(rt, "rollbackHook", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + if (count < 1) { + throw std::runtime_error("[op-sqlite][rollbackHook] callback needed"); + } + + auto callback = std::make_shared(rt, args[0]); + + if (callback->isUndefined() || callback->isNull()) { + opdb->rollback_hook_callback = nullptr; + opdb->deregister_rollback_hook(); + return {}; + } + opdb->rollback_hook_callback = callback; + + opdb->register_rollback_hook(); + return {}; + })); + + db.setProperty(rt, "loadExtension", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + auto path = args[0].asString(rt).utf8(rt); + std::string entry_point; + if (count > 1 && args[1].isString()) { + entry_point = args[1].asString(rt).utf8(rt); + } + + opsqlite_load_extension(opdb->db, path, entry_point); + return {}; + })); + + db.setProperty(rt, "reactiveExecute", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + auto query = args[0].asObject(rt); + + const std::string query_str = + query.getProperty(rt, "query").asString(rt).utf8(rt); + auto js_args = query.getProperty(rt, "arguments"); + auto js_discriminators = + query.getProperty(rt, "fireOn").asObject(rt).asArray(rt); + auto variant_args = to_variant_vec(rt, js_args); + + sqlite3_stmt *stmt = opsqlite_prepare_statement(opdb->db, query_str); + opsqlite_bind_statement(stmt, &variant_args); + + auto callback = + std::make_shared(query.getProperty(rt, "callback")); + + std::vector discriminators; + + for (size_t i = 0; i < js_discriminators.length(rt); i++) { + auto js_discriminator = + js_discriminators.getValueAtIndex(rt, i).asObject(rt); + std::string table = + js_discriminator.getProperty(rt, "table").asString(rt).utf8(rt); + std::vector ids; + if (js_discriminator.hasProperty(rt, "ids")) { + auto js_ids = + js_discriminator.getProperty(rt, "ids").asObject(rt).asArray(rt); + for (size_t j = 0; j < js_ids.length(rt); j++) { + ids.push_back( + static_cast(js_ids.getValueAtIndex(rt, j).asNumber())); + } + } + discriminators.push_back({table, ids}); + } + + std::shared_ptr reactiveQuery = + std::make_shared( + OPReactiveQuery{stmt, discriminators, callback}); + + opdb->reactive_queries.push_back(reactiveQuery); + + opdb->auto_register_update_hook(); + + auto unsubscribe = HFN2(opdb, reactiveQuery) { + auto it = std::find(opdb->reactive_queries.begin(), + opdb->reactive_queries.end(), reactiveQuery); + if (it != opdb->reactive_queries.end()) { + opdb->reactive_queries.erase(it); + } + opdb->auto_register_update_hook(); + return {}; + }); + + return unsubscribe; + })); +#else + db.setProperty(rt, "loadFile", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function loadFile not implemented for current backend (libsql or sqlcipher)"); + })); + + db.setProperty(rt, "updateHook", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function updateHook not implemented for current backend (libsql or sqlcipher)"); + })); + + db.setProperty(rt, "commitHook", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function commitHook not implemented for current backend (libsql or sqlcipher)"); + })); + + db.setProperty(rt, "rollbackHook", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function rollbackHook not implemented for current backend (libsql or sqlcipher)"); + })); + + db.setProperty(rt, "loadExtension", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function loadExtension not implemented for current backend (libsql or sqlcipher)"); + })); + + db.setProperty(rt, "reactiveExecute", HFN0 { + throw std::runtime_error( + "[op-sqlite] Function reactiveExecute not implemented for current backend (libsql or sqlcipher)"); + })); +#endif + + db.setProperty(rt, "prepareStatement", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + auto query = args[0].asString(rt).utf8(rt); +#ifdef OP_SQLITE_USE_LIBSQL + libsql_stmt_t statement = opsqlite_libsql_prepare_statement(opdb->db, query); +#else + sqlite3_stmt *statement = opsqlite_prepare_statement(opdb->db, query); +#endif + auto preparedStatementHostObject = + std::make_shared(opdb->db, statement, + opdb->thread_pool); + + return jsi::Object::createFromHostObject(rt, preparedStatementHostObject); + })); + + db.setProperty(rt, "getDbPath", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + std::string path = std::string(opdb->base_path); + + if (count == 1) { + if (!args[0].isString()) { + throw std::runtime_error( + "[op-sqlite][open] database location must be a string"); + } + + std::string last_path = args[0].asString(rt).utf8(rt); + + if (last_path == ":memory:") { + path = ":memory:"; + } else if (last_path.rfind('/', 0) == 0) { + path = last_path; + } else { + path = path + "/" + last_path; + } + } + + auto result = opsqlite_get_db_path(opdb->db_name, path); + return jsi::String::createFromUtf8(rt, result); + })); + + db.setProperty(rt, "flushPendingReactiveQueries", HFN0 { + auto opdb = that.asObject(rt).getNativeState(rt); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promise = promiseCtr.callAsConstructor(rt, HFN(opdb) { + auto resolve = std::make_shared(rt, args[0]); + + auto task = [opdb, resolve]() { + opdb->flush_pending_reactive_queries(resolve); + }; + + opdb->thread_pool->queueWork(task); + + return {}; + })); + + return promise; + })); + + return db; +} + +jsi::Object create_db(jsi::Runtime &rt, const std::string &name, + const std::string &path, + const std::string &sqlite_vec_path, + const std::string &encryption_key) { + return create_db( + rt, std::make_shared(name, path, sqlite_vec_path, encryption_key)); +} + + void install(jsi::Runtime &rt, const std::shared_ptr &_invoker, - const char *base_path, const char *crsqlite_path, - const char *sqlite_vec_path) { + const char *base_path, const char *sqlite_vec_path) { _base_path = std::string(base_path); - _crsqlite_path = std::string(crsqlite_path); _sqlite_vec_path = std::string(sqlite_vec_path); opsqlite::invoker = _invoker; opsqlite::invalidated = false; - + auto open = HFN0 { jsi::Object options = args[0].asObject(rt); std::string name = options.getProperty(rt, "name").asString(rt).utf8(rt); std::string path = std::string(_base_path); std::string location; std::string encryption_key; - + if (options.hasProperty(rt, "location")) { location = options.getProperty(rt, "location").asString(rt).utf8(rt); } - + if (options.hasProperty(rt, "encryptionKey")) { encryption_key = - options.getProperty(rt, "encryptionKey").asString(rt).utf8(rt); + options.getProperty(rt, "encryptionKey").asString(rt).utf8(rt); } - + if (!location.empty()) { if (location == ":memory:") { path = ":memory:"; @@ -79,11 +662,8 @@ void install(jsi::Runtime &rt, path = path + "/" + location; } } - - std::shared_ptr db = std::make_shared( - rt, path, name, path, _crsqlite_path, _sqlite_vec_path, encryption_key); - dbs.emplace_back(db); - return jsi::Object::createFromHostObject(rt, db); + + return create_db(rt, name, path, _sqlite_vec_path, encryption_key); }); auto is_sqlcipher = HFN(=) { @@ -128,17 +708,13 @@ void install(jsi::Runtime &rt, options.getProperty(rt, "authToken").asString(rt).utf8(rt); #ifdef OP_SQLITE_USE_LIBSQL - std::shared_ptr db = - std::make_shared(rt, url, auth_token); + auto db = std::make_shared(url, auth_token); #else std::string path = std::string(_base_path); - std::shared_ptr db = - std::make_shared(rt, url, auth_token, path); + auto db = std::make_shared(url, auth_token, path); #endif - dbs.emplace_back(db); - - return jsi::Object::createFromHostObject(rt, db); + return create_db(rt, db); }); auto open_sync = HFN(=) { @@ -187,20 +763,18 @@ void install(jsi::Runtime &rt, } #ifdef OP_SQLITE_USE_LIBSQL - std::shared_ptr db = std::make_shared( - rt, name, path, url, auth_token, sync_interval, offline, encryption_key, + auto db = std::make_shared( + 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); + auto db = std::make_shared( + name, path, url, auth_token, remote_encryption_key); #endif - dbs.emplace_back(db); - - return jsi::Object::createFromHostObject(rt, db); + return create_db(rt, db); }); #endif @@ -223,7 +797,7 @@ void expoUpdatesWorkaround(const char *base_path) { std::string path = std::string(base_path); // Open a DB before anything else so that expo-updates does not mess up the // configuration - opsqlite_libsql_open("__dummy", path, ""); + opsqlite_libsql_open("__dummy", path); #endif } diff --git a/cpp/OPSqlite.hpp b/cpp/OPSqlite.hpp index 91511ab8..279b3eb3 100644 --- a/cpp/OPSqlite.hpp +++ b/cpp/OPSqlite.hpp @@ -11,8 +11,7 @@ namespace react = facebook::react; void install(jsi::Runtime &rt, const std::shared_ptr &invoker, - const char *base_path, const char *crsqlite_path, - const char *sqlite_vec_path); + const char *base_path, const char *sqlite_vec_path); void invalidate(); void expoUpdatesWorkaround(const char *base_path); diff --git a/cpp/PreparedStatementHostObject.cpp b/cpp/PreparedStatementHostObject.cpp index f6ea1349..463912c7 100644 --- a/cpp/PreparedStatementHostObject.cpp +++ b/cpp/PreparedStatementHostObject.cpp @@ -1,8 +1,8 @@ #include "PreparedStatementHostObject.h" #if OP_SQLITE_USE_LIBSQL -#include "libsql/bridge.hpp" +#include "libsql/OPBridge.hpp" #else -#include "bridge.h" +#include "OPBridge.hpp" #endif #include "macros.hpp" #include "utils.hpp" diff --git a/cpp/PreparedStatementHostObject.h b/cpp/PreparedStatementHostObject.h index 5621d4e5..5acd5038 100644 --- a/cpp/PreparedStatementHostObject.h +++ b/cpp/PreparedStatementHostObject.h @@ -5,7 +5,7 @@ #include #ifdef OP_SQLITE_USE_LIBSQL #include "libsql.h" -#include "libsql/bridge.hpp" +#include "libsql/OPBridge.hpp" #else #ifdef __ANDROID__ #include "sqlite3.h" diff --git a/cpp/libsql/bridge.cpp b/cpp/libsql/OPBridge.cpp similarity index 97% rename from cpp/libsql/bridge.cpp rename to cpp/libsql/OPBridge.cpp index 444dbe65..393f0b32 100644 --- a/cpp/libsql/bridge.cpp +++ b/cpp/libsql/OPBridge.cpp @@ -1,4 +1,4 @@ -#include "bridge.hpp" +#include "OPBridge.hpp" #include "DumbHostObject.h" #include "SmartHostObject.h" #include "libsql.h" @@ -57,12 +57,12 @@ DB opsqlite_libsql_open_sync(std::string const &name, .read_your_writes = '1', .encryption_key = encryption_key.empty() ? nullptr : encryption_key.c_str(), - .remote_encryption_key = remote_encryption_key.empty() - ? nullptr - : remote_encryption_key.c_str(), .sync_interval = sync_interval, .with_webpki = '1', .offline = offline, + .remote_encryption_key = remote_encryption_key.empty() + ? nullptr + : remote_encryption_key.c_str(), }; status = libsql_open_sync_with_config(config, &db, &err); @@ -79,8 +79,7 @@ DB opsqlite_libsql_open_sync(std::string const &name, return {.db = db, .c = c}; } -DB opsqlite_libsql_open(std::string const &name, std::string const &last_path, - std::string const &crsqlitePath) { +DB opsqlite_libsql_open(std::string const &name, std::string const &last_path) { std::string path = opsqlite_get_db_path(name, last_path); int status; @@ -99,21 +98,6 @@ DB opsqlite_libsql_open(std::string const &name, std::string const &last_path, if (status != 0) { throw std::runtime_error(err); } - -#ifdef OP_SQLITE_USE_CRSQLITE - const char *errMsg; - const char *crsqliteEntryPoint = "sqlite3_crsqlite_init"; - - status = libsql_load_extension(c, crsqlitePath.c_str(), crsqliteEntryPoint, - &errMsg); - - if (status != 0) { - throw std::runtime_error(errMsg); - } else { - LOGI("Loaded CRSQlite successfully"); - } -#endif - return {.db = db, .c = c}; } diff --git a/cpp/libsql/bridge.hpp b/cpp/libsql/OPBridge.hpp similarity index 97% rename from cpp/libsql/bridge.hpp rename to cpp/libsql/OPBridge.hpp index cbe55eed..d1f8ac61 100644 --- a/cpp/libsql/bridge.hpp +++ b/cpp/libsql/OPBridge.hpp @@ -32,8 +32,7 @@ struct DB { std::string opsqlite_get_db_path(std::string const &name, std::string const &location); -DB opsqlite_libsql_open(std::string const &name, std::string const &path, - std::string const &crsqlitePath); +DB opsqlite_libsql_open(std::string const &name, std::string const &path); DB opsqlite_libsql_open_remote(std::string const &url, std::string const &auth_token); diff --git a/cpp/turso_bridge.cpp b/cpp/turso_bridge.cpp index 43e410cc..2c0fb3b6 100644 --- a/cpp/turso_bridge.cpp +++ b/cpp/turso_bridge.cpp @@ -1,5 +1,4 @@ -#include "bridge.h" -#include "DBHostObject.h" +#include "OPBridge.hpp" #include "DumbHostObject.h" #include "SmartHostObject.h" #include "utils.hpp" @@ -367,7 +366,6 @@ std::string opsqlite_get_db_path(std::string const &db_name, } sqlite3 *opsqlite_open(std::string const &name, std::string const &path, - [[maybe_unused]] std::string const &crsqlite_path, [[maybe_unused]] std::string const &sqlite_vec_path) { auto *handle = new TursoDbHandle(); handle->path = opsqlite_get_db_path(name, path); @@ -843,21 +841,6 @@ BridgeResult opsqlite_execute_raw( .insertId = response.insertId}; } -void opsqlite_register_update_hook([[maybe_unused]] sqlite3 *db, - [[maybe_unused]] void *db_host_object_ptr) {} - -void opsqlite_deregister_update_hook([[maybe_unused]] sqlite3 *db) {} - -void opsqlite_register_commit_hook([[maybe_unused]] sqlite3 *db, - [[maybe_unused]] void *db_host_object_ptr) {} - -void opsqlite_deregister_commit_hook([[maybe_unused]] sqlite3 *db) {} - -void opsqlite_register_rollback_hook([[maybe_unused]] sqlite3 *db, - [[maybe_unused]] void *db_host_object_ptr) {} - -void opsqlite_deregister_rollback_hook([[maybe_unused]] sqlite3 *db) {} - void opsqlite_load_extension([[maybe_unused]] sqlite3 *db, [[maybe_unused]] std::string &path, [[maybe_unused]] std::string &entry_point) { diff --git a/cpp/utils.cpp b/cpp/utils.cpp index 9d5b80dd..c8ca6311 100644 --- a/cpp/utils.cpp +++ b/cpp/utils.cpp @@ -2,7 +2,7 @@ #include "SmartHostObject.h" #include "types.hpp" #ifndef OP_SQLITE_USE_LIBSQL -#include "bridge.h" +#include "OPBridge.hpp" #endif #include "OPThreadPool.h" #include "macros.hpp" diff --git a/docs/docs/api.md b/docs/docs/api.md index cd01ecb0..696b1d90 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -447,13 +447,13 @@ Loading runtime extensions is supported. You need compile your extension to the /main /jniLibs /arm64-v8a - libcrsqlite.so + libcustom_extension.so /armeabi-v7a - libcrsqlite.so + libcustom_extension.so /x86 - libcrsqlite.so + libcustom_extension.so /x86_64 - libcrsqlite.so + libcustom_extension.so ``` ### iOS @@ -469,9 +469,9 @@ Loading runtime extensions is supported. You need compile your extension to the import {open, getDylibPath} from '@op-sqlite/op-engineering'; const db = open(...); - let path = "libcrsqlite" // in Android it will be the name of the .so + let path = "libcustom_extension" // in Android it will be the name of the .so if (Platform.os == "ios") { - path = getDylibPath("io.vlcn.crsqlite", "crsqlite"); // You need to get the bundle name from the .framework/plist.info inside of the .xcframework you created and then the canonical name inside the same plist + path = getDylibPath("com.example.custom-extension", "custom_extension"); // Use your framework bundle identifier and canonical dylib name } // Extensions usually have a default entry point to be loaded, if the documentation says nothing, you should assume no entry point change db.loadExtension(path); diff --git a/docs/docs/installation.md b/docs/docs/installation.md index 6710cb41..3ec9cb8e 100644 --- a/docs/docs/installation.md +++ b/docs/docs/installation.md @@ -60,7 +60,6 @@ SQLite is very customizable on compilation level. op-sqlite also allows you add // All the keys are optional, see the usage below "op-sqlite": { "sqlcipher": false - // "crsqlite": false, // "performanceMode": true, // "iosSqlite": false, // "sqliteFlags": "-DSQLITE_DQS=0 -DSQLITE_MY_FLAG=1", @@ -77,7 +76,6 @@ SQLite is very customizable on compilation level. op-sqlite also allows you add All keys are optional, only turn on the features you want: - `sqlcipher` allows to change the base sqlite implementation to [sqlcipher](https://www.zetetic.net/sqlcipher/), which encrypts all the database data with minimal overhead. You will still need to keep your encryption key secure. Read more about security in React Native [here](https://ospfranco.com/react-native-security-guide/). -- `crsqlite` is an extension that allows replication to a server backed sqlite database copy. [Repo here](https://github.com/vlcn-io/cr-sqlite). - `performanceMode` turns on certain compilation flags that make sqlite speedier at the cost of disabling some features. You should almost always turn this on, but test your app thoroughly. - `iosSqlite` uses the embedded iOS version from sqlite, which saves disk space but may use an older version and cannot load extensions as Apple disables it due to security concerns. On Android SQLite is always compiled from source as each vendor messes with sqlite or uses outdated versions. - `sqliteFlags` allows you to pass your own compilation flags to further disable/enable features and extensions. It follows the C flag format: `-D[YOUR_FLAG]=[YOUR_VALUE]`. If you are running large queries on large databases sometimes on Android devices you might get a IO exception. You can disable temporary files by using adding the `"-DSQLITE_TEMP_STORE=2"` flag. Flags listed here are applied AFTER the library defaults (including those added by `performanceMode`), so they override any default with the same name on both iOS and Android. For example, setting `"sqliteFlags": "-DSQLITE_DQS=3"` re-enables double-quoted string literals even when `performanceMode` is on. diff --git a/example/ios/OPSQLiteExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/OPSQLiteExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/example/ios/OPSQLiteExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index c1f0e3bc..bcb999dd 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2002,7 +2002,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: 2e5b5553df729e080483373db6f045201ff4e6db hermes-engine: 273e30e7fb618279934b0b95ffab60ecedb7acf5 - op-sqlite: 4df330bec6c23e6d9c9a5e256831869c58a618ea + op-sqlite: acfc46c737efe6e0499d03b01b1d354771ac9df0 RCTDeprecation: c6b36da89aa26090c8684d29c2868dcca2cd4554 RCTRequired: 1413a0844770d00fa1f1bb2da4680adfa8698065 RCTTypeSafety: 354b4bb344998550c45d054ef66913837948f958 diff --git a/example/package.json b/example/package.json index 667f0289..c2f88a42 100644 --- a/example/package.json +++ b/example/package.json @@ -1,71 +1,70 @@ { - "name": "op_sqlite_example", - "version": "0.0.1", - "private": true, - "scripts": { - "android": "react-native run-android", - "ios": "react-native run-ios --scheme='debug' --simulator='iPhone 16 Pro'", - "run:ios:unused": "xcodebuild -workspace ios/OPSQLiteExample.xcworkspace -scheme release -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 16 Pro' clean build", - "run:ios:release": "react-native run-ios --scheme='release' --no-packager", - "postinstall": "patch-package", - "start": "react-native start", - "pods": "cd ios && bundle exec pod install && rm -f .xcode.env.local", - "pods:nuke": "cd ios && rm -rf Pods && rm -rf Podfile.lock && bundle exec pod install", - "run:android:release": "cd android && ./gradlew assembleRelease && adb install -r app/build/outputs/apk/release/app-release.apk && adb shell am start -n com.op.sqlite.example/.MainActivity", - "build:android": "cd android && ./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a", - "build:ios": "cd ios && xcodebuild -workspace OPSQLiteExample.xcworkspace -scheme debug -configuration Debug -sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO", - "web": "vite", - "web:build": "vite build", - "web:preview": "vite preview" - }, - "dependencies": { - "@op-engineering/op-test": "0.2.7", - "@sqlite.org/sqlite-wasm": "^3.51.2-build8", - "chance": "^1.1.9", - "clsx": "^2.0.0", - "events": "^3.3.0", - "react": "19.1.1", - "react-dom": "19.1.1", - "react-native": "0.82.1", - "react-native-safe-area-context": "^5.6.2", - "react-native-web": "^0.21.2" - }, - "devDependencies": { - "@babel/core": "^7.25.2", - "@babel/preset-env": "^7.25.3", - "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "^18.0.0", - "@react-native-community/cli-platform-android": "18.0.0", - "@react-native-community/cli-platform-ios": "18.0.0", - "@react-native/babel-preset": "0.82.1", - "@react-native/metro-config": "0.82.1", - "@react-native/typescript-config": "0.81.5", - "@types/chance": "^1.1.7", - "@types/react": "^19.1.1", - "@vitejs/plugin-react": "^5.1.0", - "patch-package": "^8.0.1", - "react-native-builder-bob": "^0.40.13", - "react-native-monorepo-config": "^0.1.9", - "react-native-restart": "^0.0.27", - "tailwindcss": "3.3.2", - "vite": "^7.1.9" - }, - "engines": { - "node": ">=18" - }, - "op-sqlite": { - "libsql": false, - "turso": false, - "sqlcipher": false, - "iosSqlite": false, - "fts5": true, - "rtree": true, - "crsqlite": false, - "sqliteVec": false, - "performanceMode": true, - "tokenizers": [ - "wordtokenizer", - "porter" - ] - } -} \ No newline at end of file + "name": "op_sqlite_example", + "version": "0.0.1", + "private": true, + "scripts": { + "android": "react-native run-android", + "ios": "react-native run-ios --scheme='debug' --simulator='iPhone 16 Pro'", + "run:ios:unused": "xcodebuild -workspace ios/OPSQLiteExample.xcworkspace -scheme release -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 16 Pro' clean build", + "run:ios:release": "react-native run-ios --scheme='release' --no-packager", + "postinstall": "patch-package", + "start": "react-native start", + "pods": "cd ios && bundle exec pod install && rm -f .xcode.env.local", + "pods:nuke": "cd ios && rm -rf Pods && rm -rf Podfile.lock && bundle exec pod install", + "run:android:release": "cd android && ./gradlew assembleRelease && adb install -r app/build/outputs/apk/release/app-release.apk && adb shell am start -n com.op.sqlite.example/.MainActivity", + "build:android": "cd android && ./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a", + "build:ios": "cd ios && xcodebuild -workspace OPSQLiteExample.xcworkspace -scheme debug -configuration Debug -sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO", + "web": "vite", + "web:build": "vite build", + "web:preview": "vite preview" + }, + "dependencies": { + "@op-engineering/op-test": "0.2.7", + "@sqlite.org/sqlite-wasm": "^3.51.2-build8", + "chance": "^1.1.9", + "clsx": "^2.0.0", + "events": "^3.3.0", + "react": "19.1.1", + "react-dom": "19.1.1", + "react-native": "0.82.1", + "react-native-safe-area-context": "^5.6.2", + "react-native-web": "^0.21.2" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", + "@babel/runtime": "^7.25.0", + "@react-native-community/cli": "^18.0.0", + "@react-native-community/cli-platform-android": "18.0.0", + "@react-native-community/cli-platform-ios": "18.0.0", + "@react-native/babel-preset": "0.82.1", + "@react-native/metro-config": "0.82.1", + "@react-native/typescript-config": "0.81.5", + "@types/chance": "^1.1.7", + "@types/react": "^19.1.1", + "@vitejs/plugin-react": "^5.1.0", + "patch-package": "^8.0.1", + "react-native-builder-bob": "^0.40.13", + "react-native-monorepo-config": "^0.1.9", + "react-native-restart": "^0.0.27", + "tailwindcss": "3.3.2", + "vite": "^7.1.9" + }, + "engines": { + "node": ">=18" + }, + "op-sqlite": { + "libsql": false, + "turso": false, + "sqlcipher": false, + "iosSqlite": false, + "fts5": true, + "rtree": true, + "sqliteVec": false, + "performanceMode": true, + "tokenizers": [ + "wordtokenizer", + "porter" + ] + } +} diff --git a/example/src/tests/queries.ts b/example/src/tests/queries.ts index 1b3db324..5b1ff1a9 100644 --- a/example/src/tests/queries.ts +++ b/example/src/tests/queries.ts @@ -98,7 +98,10 @@ describe("Queries tests", () => { try { // @ts-expect-error await db.execute("SELECT ?", [{ foo: "bar" }]); - } catch (e: any) { + } catch (e: unknown) { + if (!(e instanceof Error)) { + throw e; + } expect( e.message.includes( "Object is not an ArrayBuffer or ArrayBuffer view", @@ -145,7 +148,10 @@ describe("Queries tests", () => { let interrupted = false; try { await queryPromise; - } catch (e: any) { + } catch (e: unknown) { + if (!(e instanceof Error)) { + throw e; + } interrupted = /interrupt|interrupted|abort|code 9|SQLITE_INTERRUPT/i.test( String(e?.message ?? e), ); @@ -154,7 +160,7 @@ describe("Queries tests", () => { expect(interrupted).toEqual(true); const count = await db.execute("SELECT COUNT(*) AS n FROM InterruptTest;"); - expect(count.rows[0]!.n).toEqual(0); + expect(count.rows[0]?.n).toEqual(0); }); it("close interrupts an in-flight query before teardown", async () => { @@ -338,15 +344,15 @@ describe("Queries tests", () => { const sumRes = await db.execute("SELECT SUM(age) as sum FROM User;"); - expect(sumRes.rows[0]!.sum).toEqual(age + age2); + expect(sumRes.rows[0]?.sum).toEqual(age + age2); const maxRes = await db.execute("SELECT MAX(networth) as `max` FROM User;"); const minRes = await db.execute("SELECT MIN(networth) as `min` FROM User;"); const maxNetworth = Math.max(networth, networth2); const minNetworth = Math.min(networth, networth2); - expect(maxRes.rows[0]!.max).toEqual(maxNetworth); - expect(minRes.rows[0]!.min).toEqual(minNetworth); + expect(maxRes.rows[0]?.max).toEqual(maxNetworth); + expect(minRes.rows[0]?.min).toEqual(minNetworth); }); it("Executes all the statements in a single string", async () => { @@ -362,13 +368,13 @@ describe("Queries tests", () => { "SELECT name FROM sqlite_master WHERE type='table' AND name='T1';", ); - expect(t1name.rows[0]!.name).toEqual("T1"); + expect(t1name.rows[0]?.name).toEqual("T1"); const t2name = await db.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='T2';", ); - expect(t2name.rows[0]!.name).toEqual("T2"); + expect(t2name.rows[0]?.name).toEqual("T2"); }); it("Failed insert", async () => { @@ -381,7 +387,10 @@ describe("Queries tests", () => { "INSERT INTO User (id, name, age, networth) VALUES(?, ?, ?, ?)", [id, name, age, networth], ); - } catch (e: any) { + } catch (e: unknown) { + if (!(e instanceof Error)) { + throw e; + } expect(typeof e).toEqual("object"); expect(!!e.message).toEqual(true); @@ -485,7 +494,7 @@ describe("Queries tests", () => { [id], ); - actual.push(results.rows[0]!.networth); + actual.push(results.rows[0].networth); }); promises.push(promised); @@ -572,7 +581,7 @@ describe("Queries tests", () => { 'INSERT INTO "User" (id, name, age, networth) VALUES(?, ?, ?, ?)', [id, name, age, networth], ); - } catch (e: any) { + } catch (e: unknown) { expect(!!e).toEqual(true); } }); @@ -754,9 +763,9 @@ describe("Queries tests", () => { }, ]); - res.rows[0]!.name = "quack_changed"; + res.rows[0].name = "quack_changed"; - expect(res.rows[0]!.name).toEqual("quack_changed"); + expect(res.rows[0].name).toEqual("quack_changed"); }); it("DumbHostObject allows to write new props", async () => { @@ -771,7 +780,7 @@ describe("Queries tests", () => { const res = await db.executeWithHostObjects("SELECT * FROM User"); - expect(res.rows!).toDeepEqual([ + expect(res.rows).toDeepEqual([ { id, name, @@ -781,9 +790,9 @@ describe("Queries tests", () => { }, ]); - res.rows[0]!.myWeirdProp = "quack_changed"; + res.rows[0].myWeirdProp = "quack_changed"; - expect(res.rows[0]!.myWeirdProp).toEqual("quack_changed"); + expect(res.rows[0].myWeirdProp).toEqual("quack_changed"); }); it("Execute raw should return just an array of objects", async () => { diff --git a/ios/OPSQLite.mm b/ios/OPSQLite.mm index e4e65e0e..28e12d63 100644 --- a/ios/OPSQLite.mm +++ b/ios/OPSQLite.mm @@ -72,17 +72,6 @@ - (NSDictionary *)getConstants { documentPath = [paths objectAtIndex:0]; } -#ifdef OP_SQLITE_USE_CRSQLITE - NSString *crsqlite_bundle_path = - [[[NSBundle mainBundle] privateFrameworksPath] - stringByAppendingPathComponent:@"crsqlite.framework"]; - NSBundle *crsqlite_bundle = [NSBundle bundleWithPath:crsqlite_bundle_path]; - NSString *crsqlite_path = [crsqlite_bundle pathForResource:@"crsqlite" - ofType:@""]; -#else - NSString *crsqlite_path = @""; -#endif - #ifdef OP_SQLITE_USE_SQLITE_VEC NSString *sqlitevec_bundle_path = [[[NSBundle mainBundle] privateFrameworksPath] @@ -96,7 +85,7 @@ - (NSDictionary *)getConstants { #endif opsqlite::install(runtime, callInvoker, [documentPath UTF8String], - [crsqlite_path UTF8String], [sqlite_vec_path UTF8String]); + [sqlite_vec_path UTF8String]); return @true; } diff --git a/ios/crsqlite.xcframework/Info.plist b/ios/crsqlite.xcframework/Info.plist deleted file mode 100644 index 6947e0d6..00000000 --- a/ios/crsqlite.xcframework/Info.plist +++ /dev/null @@ -1,46 +0,0 @@ - - - - - AvailableLibraries - - - LibraryIdentifier - ios-arm64 - LibraryPath - crsqlite.framework - SupportedArchitectures - - arm64 - - SupportedPlatform - ios - - - LibraryIdentifier - ios-arm64_x86_64-simulator - LibraryPath - crsqlite.framework - SupportedArchitectures - - arm64 - x86_64 - - SupportedPlatform - ios - SupportedPlatformVariant - simulator - - - CFBundlePackageType - XFWK - XCFrameworkFormatVersion - 1.0 - CFBundleVersion - 1.0.0 - CFBundleShortVersionString - 1.0.0 - MinimumOSVersion - 8.0 - - diff --git a/ios/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist b/ios/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist deleted file mode 100644 index 3deb06e6..00000000 --- a/ios/crsqlite.xcframework/ios-arm64/crsqlite.framework/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - crsqlite - CFBundleIdentifier - io.vlcn.crsqlite - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - FMWK - CFBundleSignature - ???? - CFBundleVersion - 1.0.0 - CFBundleShortVersionString - 1.0.0 - MinimumOSVersion - 8.0 - - diff --git a/ios/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite b/ios/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite deleted file mode 100755 index e2ed0a40..00000000 Binary files a/ios/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite and /dev/null differ diff --git a/ios/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist b/ios/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist deleted file mode 100644 index 3deb06e6..00000000 --- a/ios/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - crsqlite - CFBundleIdentifier - io.vlcn.crsqlite - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - FMWK - CFBundleSignature - ???? - CFBundleVersion - 1.0.0 - CFBundleShortVersionString - 1.0.0 - MinimumOSVersion - 8.0 - - diff --git a/ios/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite b/ios/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite deleted file mode 100755 index dfe145da..00000000 Binary files a/ios/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite and /dev/null differ diff --git a/op-sqlite.podspec b/op-sqlite.podspec index 8452cc2b..dfda287f 100644 --- a/op-sqlite.podspec +++ b/op-sqlite.podspec @@ -40,7 +40,6 @@ app_package = JSON.parse(File.read(package_json_path)) op_sqlite_config = app_package["op-sqlite"] use_sqlcipher = false -use_crsqlite = false use_libsql = false use_turso = false performance_mode = false @@ -53,7 +52,6 @@ tokenizers = [] if(op_sqlite_config != nil) use_sqlcipher = op_sqlite_config["sqlcipher"] == true - use_crsqlite = op_sqlite_config["crsqlite"] == true use_libsql = op_sqlite_config["libsql"] == true use_turso = op_sqlite_config["turso"] == true performance_mode = op_sqlite_config["performanceMode"] || false @@ -70,10 +68,6 @@ if phone_version then raise "SQLCipher is not supported with phone version. It cannot load extensions." end - if use_crsqlite then - raise "CRSQLite is not supported with phone version. It cannot load extensions." - end - if rtree then raise "RTree is not supported with phone version. It cannot load extensions." end @@ -147,23 +141,18 @@ Pod::Spec.new do |s| if use_sqlcipher then log_message.call("[OP-SQLITE] using SQLCipher") - exclude_files += ["cpp/sqlite3.c", "cpp/sqlite3.h", "cpp/libsql/bridge.c", "cpp/libsql/bridge.h", "cpp/libsql/bridge.cpp", "cpp/libsql/libsql.h", "ios/libsql.xcframework/**/*"] + exclude_files += ["cpp/sqlite3.c", "cpp/sqlite3.h", "cpp/libsql/OPBridge.cpp", "cpp/libsql/OPBridge.hpp", "cpp/libsql/libsql.h", "ios/libsql.xcframework/**/*"] xcconfig[:GCC_PREPROCESSOR_DEFINITIONS] += " OP_SQLITE_USE_SQLCIPHER=1 HAVE_FULLFSYNC=1 SQLITE_HAS_CODEC SQLITE_TEMP_STORE=3 SQLITE_EXTRA_INIT=sqlcipher_extra_init SQLITE_EXTRA_SHUTDOWN=sqlcipher_extra_shutdown" s.dependency "OpenSSL-Universal" elsif use_turso then log_message.call("[OP-SQLITE] using Turso SDK kit") - exclude_files += ["cpp/sqlite3.c", "cpp/sqlite3.h", "cpp/bridge.h", "cpp/bridge.cpp", "cpp/sqlcipher/sqlite3.c", "cpp/sqlcipher/sqlite3.h", "cpp/libsql/bridge.h", "cpp/libsql/bridge.cpp", "cpp/libsql/libsql.h", "ios/libsql_experimental.xcframework/**/*"] + exclude_files += ["cpp/sqlite3.c", "cpp/sqlite3.h", "cpp/OPBridge.hpp", "cpp/OPBridge.cpp", "cpp/sqlcipher/sqlite3.c", "cpp/sqlcipher/sqlite3.h", "cpp/libsql/OPBridge.hpp", "cpp/libsql/OPBridge.cpp", "cpp/libsql/libsql.h", "ios/libsql_experimental.xcframework/**/*"] elsif use_libsql then log_message.call("[OP-SQLITE] ⚠️ Using libsql. If you have libsql questions please ask in the Turso Discord server.") - exclude_files += ["cpp/sqlite3.c", "cpp/sqlite3.h", "cpp/sqlcipher/sqlite3.c", "cpp/sqlcipher/sqlite3.h", "cpp/bridge.h", "cpp/bridge.cpp", "ios/turso_sdk_kit.xcframework/**/*"] + exclude_files += ["cpp/sqlite3.c", "cpp/sqlite3.h", "cpp/sqlcipher/sqlite3.c", "cpp/sqlcipher/sqlite3.h", "cpp/OPBridge.hpp", "cpp/OPBridge.cpp", "ios/turso_sdk_kit.xcframework/**/*"] else log_message.call("[OP-SQLITE] using pure SQLite") - exclude_files += ["cpp/sqlcipher/sqlite3.c", "cpp/sqlcipher/sqlite3.h", "cpp/libsql/bridge.c", "cpp/libsql/bridge.h", "cpp/libsql/bridge.cpp", "cpp/libsql/libsql.h", "ios/libsql_experimental.xcframework/**/*", "ios/turso_sdk_kit.xcframework/**/*"] - end - - # Exclude xcframeworks that aren't being used - if !use_crsqlite then - exclude_files += ["ios/crsqlite.xcframework/**/*"] + exclude_files += ["cpp/sqlcipher/sqlite3.c", "cpp/sqlcipher/sqlite3.h", "cpp/libsql/OPBridge.cpp", "cpp/libsql/OPBridge.hpp", "cpp/libsql/libsql.h", "ios/libsql_experimental.xcframework/**/*", "ios/turso_sdk_kit.xcframework/**/*"] end if !use_sqlite_vec then @@ -194,12 +183,6 @@ Pod::Spec.new do |s| other_cflags += optimizedCflags end - if use_crsqlite then - log_message.call("[OP-SQLITE] using CRQSQLite 🤖") - xcconfig[:GCC_PREPROCESSOR_DEFINITIONS] += " OP_SQLITE_USE_CRSQLITE=1" - frameworks.push("ios/crsqlite.xcframework") - end - if use_sqlite_vec then log_message.call("[OP-SQLITE] using Sqlite Vec ↗️") xcconfig[:GCC_PREPROCESSOR_DEFINITIONS] += " OP_SQLITE_USE_SQLITE_VEC=1" @@ -208,11 +191,7 @@ Pod::Spec.new do |s| if use_libsql then xcconfig[:GCC_PREPROCESSOR_DEFINITIONS] += " OP_SQLITE_USE_LIBSQL=1" - if use_crsqlite then - frameworks = ["ios/libsql_experimental.xcframework", "ios/crsqlite.xcframework"] - else - frameworks = ["ios/libsql_experimental.xcframework"] - end + frameworks = ["ios/libsql_experimental.xcframework"] end if use_turso then diff --git a/scripts/turnOffEverything.js b/scripts/turnOffEverything.js index 7a966e98..e3159700 100644 --- a/scripts/turnOffEverything.js +++ b/scripts/turnOffEverything.js @@ -12,7 +12,6 @@ packageJson['op-sqlite']['sqlcipher'] = false; packageJson['op-sqlite']['iosSqlite'] = false; packageJson['op-sqlite']['fts5'] = true; packageJson['op-sqlite']['rtree'] = true; -packageJson['op-sqlite']['crsqlite'] = false; packageJson['op-sqlite']['sqliteVec'] = false; packageJson['op-sqlite']['tokenizers'] = ["wordtokenizer", "porter"]; diff --git a/scripts/turnOnIOSEmbedded.js b/scripts/turnOnIOSEmbedded.js index eb78b8fc..0c6af9b8 100644 --- a/scripts/turnOnIOSEmbedded.js +++ b/scripts/turnOnIOSEmbedded.js @@ -6,7 +6,6 @@ const packageJson = JSON.parse(fs.readFileSync('./example/package.json')); // Modify the op-sqlite.sqlcipher key to true packageJson['op-sqlite']['iosSqlite'] = true; packageJson['op-sqlite']['sqlcipher'] = false; -packageJson['op-sqlite']['crsqlite'] = false; packageJson['op-sqlite']['libsql'] = false; packageJson['op-sqlite']['turso'] = false; packageJson['op-sqlite']['sqliteVec'] = false; diff --git a/src/functions.ts b/src/functions.ts index ca88b238..5097295a 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -71,25 +71,25 @@ function enhanceDB(db: _InternalDB, options: DBParams): DB { // spreading the object does not work with HostObjects (db) // We need to manually assign the fields const enhancedDb = { - delete: db.delete, - attach: db.attach, - detach: db.detach, - loadFile: db.loadFile, - updateHook: db.updateHook, - commitHook: db.commitHook, - rollbackHook: db.rollbackHook, - loadExtension: db.loadExtension, - getDbPath: db.getDbPath, - reactiveExecute: db.reactiveExecute, - sync: db.sync, - setReservedBytes: db.setReservedBytes, - getReservedBytes: db.getReservedBytes, - close: db.close, - interrupt: db.interrupt, + delete: db.delete.bind(db), + attach: db.attach.bind(db), + detach: db.detach.bind(db), + loadFile: db.loadFile.bind(db), + updateHook: db.updateHook.bind(db), + commitHook: db.commitHook.bind(db), + rollbackHook: db.rollbackHook.bind(db), + loadExtension: db.loadExtension.bind(db), + getDbPath: db.getDbPath.bind(db), + reactiveExecute: db.reactiveExecute.bind(db), + sync: db.sync.bind(db), + setReservedBytes: db.setReservedBytes.bind(db), + getReservedBytes: db.getReservedBytes.bind(db), + close: db.close.bind(db), + interrupt: db.interrupt.bind(db), closeAsync: async () => { db.close(); }, - flushPendingReactiveQueries: db.flushPendingReactiveQueries, + flushPendingReactiveQueries: db.flushPendingReactiveQueries.bind(db), executeBatch: async ( commands: SQLBatchTuple[], ): Promise => {