diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0fce183..436e69ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,17 +53,62 @@ jobs: uses: actions/cache@v3 with: path: ${{ env.TURBO_CACHE_DIR }} - key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock', 'cpp/**') }} restore-keys: | ${{ runner.os }}-turborepo-android- - - name: Check turborepo cache for Android + # - name: Check turborepo cache for Android + # run: | + # TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") + + # if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then + # echo "turbo_cache_hit=1" >> $GITHUB_ENV + # fi + + - name: Cache Gradle + if: env.turbo_cache_hit != 1 + uses: actions/cache@v3 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Build example for Android + working-directory: example + env: + JAVA_OPTS: '-XX:MaxHeapSize=6g' run: | - TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") + yarn run build:android - if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then - echo "turbo_cache_hit=1" >> $GITHUB_ENV - fi + build-android-sqlcipher: + runs-on: self-hosted + env: + TURBO_CACHE_DIR: .turbo/android + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Cache turborepo for Android + uses: actions/cache@v3 + with: + path: ${{ env.TURBO_CACHE_DIR }} + key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-turborepo-android- + + # - name: Check turborepo cache for Android + # run: | + # TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") + + # if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then + # echo "turbo_cache_hit=1" >> $GITHUB_ENV + # fi - name: Cache Gradle if: env.turbo_cache_hit != 1 @@ -77,10 +122,11 @@ jobs: ${{ runner.os }}-gradle- - name: Build example for Android + working-directory: example env: JAVA_OPTS: '-XX:MaxHeapSize=6g' run: | - yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" + OP_SQLITE_USE_SQLCIPHER=1 yarn run build:android build-ios: runs-on: self-hosted @@ -106,13 +152,13 @@ jobs: restore-keys: | ${{ runner.os }}-turborepo-ios- - - name: Check turborepo cache for iOS - run: | - TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") + # - name: Check turborepo cache for iOS + # run: | + # TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") - if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then - echo "turbo_cache_hit=1" >> $GITHUB_ENV - fi + # if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then + # echo "turbo_cache_hit=1" >> $GITHUB_ENV + # fi - name: Cache cocoapods if: env.turbo_cache_hit != 1 @@ -134,8 +180,9 @@ jobs: NO_FLIPPER: 1 - name: Build example for iOS + working-directory: example run: | - yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" + yarn run build:ios build-ios-sqlcipher: runs-on: self-hosted @@ -161,13 +208,13 @@ jobs: restore-keys: | ${{ runner.os }}-turborepo-ios- - - name: Check turborepo cache for iOS - run: | - TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") + # - name: Check turborepo cache for iOS + # run: | + # TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:ios').cache.status") - if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then - echo "turbo_cache_hit=1" >> $GITHUB_ENV - fi + # if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then + # echo "turbo_cache_hit=1" >> $GITHUB_ENV + # fi - name: Cache cocoapods if: env.turbo_cache_hit != 1 @@ -189,49 +236,6 @@ jobs: NO_FLIPPER: 1 - name: Build example for iOS + working-directory: example run: | - yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}" - - build-android-sqlcipher: - runs-on: self-hosted - env: - TURBO_CACHE_DIR: .turbo/android - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup - uses: ./.github/actions/setup - - - name: Cache turborepo for Android - uses: actions/cache@v3 - with: - path: ${{ env.TURBO_CACHE_DIR }} - key: ${{ runner.os }}-turborepo-android-${{ hashFiles('yarn.lock') }} - restore-keys: | - ${{ runner.os }}-turborepo-android- - - - name: Check turborepo cache for Android - run: | - TURBO_CACHE_STATUS=$(node -p "($(yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" --dry=json)).tasks.find(t => t.task === 'build:android').cache.status") - - if [[ $TURBO_CACHE_STATUS == "HIT" ]]; then - echo "turbo_cache_hit=1" >> $GITHUB_ENV - fi - - - name: Cache Gradle - if: env.turbo_cache_hit != 1 - uses: actions/cache@v3 - with: - path: | - ~/.gradle/wrapper - ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Build example for Android - env: - JAVA_OPTS: '-XX:MaxHeapSize=6g' - run: | - OP_SQLITE_USE_SQLCIPHER=1 yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" + yarn run build:ios diff --git a/android/.project b/android/.project index 0e0a1bac..c243e249 100644 --- a/android/.project +++ b/android/.project @@ -5,6 +5,11 @@ + + org.eclipse.jdt.core.javabuilder + + + org.eclipse.buildship.core.gradleprojectbuilder @@ -12,6 +17,18 @@ + org.eclipse.jdt.core.javanature org.eclipse.buildship.core.gradleprojectnature + + + 1712182270120 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index 8c253d67..c17f03e1 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -2,7 +2,7 @@ arguments= auto.sync=false build.scans.enabled=false connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0)) -connection.project.dir= +connection.project.dir=../example/android eclipse.preferences.version=1 gradle.user.home= java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 2d38f0ce..abfbaea2 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -20,6 +20,16 @@ add_library( SHARED ../cpp/bridge.cpp ../cpp/bridge.h + ../cpp/DbHostObject.cpp + ../cpp/DbHostObject.h + ../cpp/validators/DbOpenValidator.cpp + ../cpp/validators/DbOpenValidator.h + ../cpp/validators/DbAttachValidator.cpp + ../cpp/validators/DbAttachValidator.h + ../cpp/validators/DbDetachValidator.cpp + ../cpp/validators/DbDetachValidator.h + ../cpp/validators/DbCloseValidator.cpp + ../cpp/validators/DbCloseValidator.h ../cpp/bindings.cpp ../cpp/bindings.h ../cpp/utils.h diff --git a/cpp/DbHostObject.cpp b/cpp/DbHostObject.cpp new file mode 100644 index 00000000..ebe733bf --- /dev/null +++ b/cpp/DbHostObject.cpp @@ -0,0 +1,177 @@ +// +// Created by jplc on 4/2/24. +// + +#include "DbHostObject.h" +#include "bridge.h" +#include "macros.h" +#include "types.h" +#include "validators/DbAttachValidator.h" +#include "validators/DbCloseValidator.h" +#include "validators/DbDetachValidator.h" +#include "validators/DbOpenValidator.h" +#include + +namespace opsqlite { + +const std::string DbHostObject::F_OPEN = "open"; +const int DbHostObject::F_OPEN_ARGS_COUNT = 3; +const std::string DbHostObject::F_ATTACH = "attach"; +const int DbHostObject::F_ATTACH_ARGS_COUNT = 4; +const std::string DbHostObject::F_DETACH = "detach"; +const int DbHostObject::F_DETACH_ARGS_COUNT = 2; +const std::string DbHostObject::F_CLOSE = "close"; +const int DbHostObject::F_CLOSE_ARGS_COUNT = 1; + +jsi::Value DbHostObject::get(jsi::Runtime &runtime, + const jsi::PropNameID &propNameId) { + auto methodName = propNameId.utf8(runtime); + if (methodName == F_OPEN) { + return &DbHostObject::open; + } else if (methodName == F_ATTACH) { + return &DbHostObject::attach; + } else if (methodName == F_DETACH) { + return &DbHostObject::detach; + } else if (methodName == F_CLOSE) { + return &DbHostObject::close; + } + return nullptr; +} + +void DbHostObject::set(jsi::Runtime &runtime, const jsi::PropNameID &propNameId, + const jsi::Value &value) { + // no attributes to set at moment. +} + +std::vector +DbHostObject::getPropertyNames(jsi::Runtime &runtime) { + std::vector properties; + properties.push_back(jsi::PropNameID::forAscii(runtime, F_OPEN)); + properties.push_back(jsi::PropNameID::forAscii(runtime, F_ATTACH)); + properties.push_back(jsi::PropNameID::forAscii(runtime, F_DETACH)); + properties.push_back(jsi::PropNameID::forAscii(runtime, F_CLOSE)); + return properties; +} + +jsi::Function DbHostObject::open(jsi::Runtime &rt, + const std::string &basePath) { + using opsqlite::validators::DbOpenValidator; + return HOSTFN(F_OPEN, F_OPEN_ARGS_COUNT) { + std::string errMsg; + if (DbOpenValidator::invalidArgsNumber(errMsg, count)) { + throw std::runtime_error(errMsg); + } + + jsi::Object options = args[0].asObject(rt); + std::string dbName = options.getProperty(rt, "name").asString(rt).utf8(rt); + std::string path = DbOpenValidator::getPath(rt, options, basePath); + std::string encryptionKey = DbOpenValidator::getEncryptionKey(rt, options); + +#ifdef OP_SQLITE_USE_SQLCIPHER + if (encryptionKey.empty()) { + throw std::runtime_error( + "[OP SQLite] using SQLCipher encryption key is required"); + } + // TODO(osp) find a way to display the yellow box from c++ + BridgeResult result = opsqlite_open(dbName, path, encryptionKey); +#else + // if (!encryptionKey.empty()) { + // // RCTLogWarn(@"Your message") + // throw std::runtime_error("[OP SQLite] SQLCipher is not enabled, " + // "encryption key is not allowed"); + // } + BridgeResult result = opsqlite_open(dbName, path); +#endif + + if (result.type == SQLiteError) { + throw std::runtime_error(result.message); + } + + return {}; + }); +} + +jsi::Function DbHostObject::attach(jsi::Runtime &rt, + const std::string &basePath) { + using opsqlite::validators::DbAttachValidator; + return HOSTFN(F_ATTACH, F_ATTACH_ARGS_COUNT) { + std::string errMsg; + if (DbAttachValidator::invalidArgsNumber(errMsg, count)) { + throw jsi::JSError(rt, errMsg); + } + if (DbAttachValidator::noStringArgs(errMsg, args[0], args[1], args[2])) { + throw jsi::JSError(rt, errMsg); + return {}; + } + + std::string tempDocPath = std::string(basePath); + if (DbAttachValidator::locationArgDefined(count, args[3])) { + if (DbAttachValidator::locationArgIsNotString(errMsg, args[3])) { + throw std::runtime_error(errMsg); + } + tempDocPath = tempDocPath + "/" + args[3].asString(rt).utf8(rt); + } + + std::string dbName = args[0].asString(rt).utf8(rt); + std::string databaseToAttach = args[1].asString(rt).utf8(rt); + std::string alias = args[2].asString(rt).utf8(rt); + BridgeResult result = + opsqlite_attach(dbName, tempDocPath, databaseToAttach, alias); + + if (result.type == SQLiteError) { + throw std::runtime_error(result.message); + } + + return {}; + }); +} + +jsi::Function DbHostObject::detach(jsi::Runtime &rt) { + using opsqlite::validators::DbDetachValidator; + return HOSTFN(F_DETACH, F_DETACH_ARGS_COUNT) { + std::string errMsg; + if (DbDetachValidator::invalidArgsNumber(errMsg, count)) { + throw std::runtime_error(errMsg); + } + if (DbDetachValidator::noStringArgs(errMsg, args[0], args[1])) { + throw std::runtime_error(errMsg); + return {}; + } + + std::string dbName = args[0].asString(rt).utf8(rt); + std::string alias = args[1].asString(rt).utf8(rt); + BridgeResult result = opsqlite_detach(dbName, alias); + + if (result.type == SQLiteError) { + throw jsi::JSError(rt, result.message.c_str()); + } + + return {}; + }); +} + +jsi::Function DbHostObject::close(jsi::Runtime &rt) { + using opsqlite::validators::DbCloseValidator; + return HOSTFN(F_CLOSE, F_CLOSE_ARGS_COUNT) { + std::string errMsg; + if (DbCloseValidator::invalidArgsNumber(errMsg, count)) { + throw std::runtime_error(errMsg); + } + + if (DbCloseValidator::noStringArgs(errMsg, args[0])) { + throw std::runtime_error(errMsg); + } + + std::string dbName = args[0].asString(rt).utf8(rt); + + BridgeResult result = opsqlite_close(dbName); + + if (result.type == SQLiteError) { + throw jsi::JSError(rt, result.message.c_str()); + } + + return {}; + }); +} + +} // namespace opsqlite diff --git a/cpp/DbHostObject.h b/cpp/DbHostObject.h new file mode 100644 index 00000000..bde03085 --- /dev/null +++ b/cpp/DbHostObject.h @@ -0,0 +1,41 @@ +// +// Created by jplc on 4/2/24. +// + +#ifndef OPSQLITEEXAMPLE_DBHOSTOBJECT_H +#define OPSQLITEEXAMPLE_DBHOSTOBJECT_H + +#include +#include + +namespace jsi = facebook::jsi; + +namespace opsqlite { + +class JSI_EXPORT DbHostObject : public jsi::HostObject { +public: + jsi::Value get(jsi::Runtime &runtime, + const jsi::PropNameID &propNameId) override; + void set(jsi::Runtime &runtime, const jsi::PropNameID &propNameId, + const jsi::Value &value) override; + std::vector getPropertyNames(jsi::Runtime &runtime) override; + + static jsi::Function open(jsi::Runtime &rt, const std::string &basePath); + static jsi::Function attach(jsi::Runtime &rt, const std::string &basePath); + static jsi::Function detach(jsi::Runtime &rt); + static jsi::Function close(jsi::Runtime &rt); + +private: + static const std::string F_OPEN; // open + static const int F_OPEN_ARGS_COUNT; + static const std::string F_ATTACH; // attach + static const int F_ATTACH_ARGS_COUNT; + static const std::string F_DETACH; // detach + static const int F_DETACH_ARGS_COUNT; + static const std::string F_CLOSE; // close + static const int F_CLOSE_ARGS_COUNT; +}; + +} // namespace opsqlite + +#endif // OPSQLITEEXAMPLE_DBHOSTOBJECT_H diff --git a/cpp/bindings.cpp b/cpp/bindings.cpp index a884f32c..5d91b348 100644 --- a/cpp/bindings.cpp +++ b/cpp/bindings.cpp @@ -8,10 +8,13 @@ #include "sqlbatchexecutor.h" #include "utils.h" #include +#include #include #include #include +#include "DbHostObject.h" + namespace opsqlite { namespace jsi = facebook::jsi; @@ -49,139 +52,13 @@ void install(jsi::Runtime &rt, basePath = std::string(docPath); invoker = jsCallInvoker; - auto open = HOSTFN("open", 3) { - if (count == 0) { - throw std::runtime_error("[op-sqlite][open] database name is required"); - } - - jsi::Object options = args[0].asObject(rt); - std::string dbName = options.getProperty(rt, "name").asString(rt).utf8(rt); - std::string path = std::string(basePath); - std::string location; - std::string encryptionKey; - - if (options.hasProperty(rt, "location")) { - location = options.getProperty(rt, "location").asString(rt).utf8(rt); - } - - if (options.hasProperty(rt, "encryptionKey")) { - encryptionKey = - options.getProperty(rt, "encryptionKey").asString(rt).utf8(rt); - } - -#ifdef OP_SQLITE_USE_SQLCIPHER - if (encryptionKey.empty()) { - throw std::runtime_error( - "[OP SQLite] using SQLCipher encryption key is required"); - } -// TODO(osp) find a way to display the yellow box from c++ -#else - // if (!encryptionKey.empty()) { - // // RCTLogWarn(@"Your message") - // throw std::runtime_error("[OP SQLite] SQLCipher is not enabled, " - // "encryption key is not allowed"); - // } -#endif - - if (!location.empty()) { - if (location == ":memory:") { - path = ":memory:"; - } else if (location.rfind("/", 0) == 0) { - path = location; - } else { - path = path + "/" + location; - } - } - -#ifdef OP_SQLITE_USE_SQLCIPHER - BridgeResult result = opsqlite_open(dbName, path, encryptionKey); -#else - BridgeResult result = opsqlite_open(dbName, path); -#endif - - if (result.type == SQLiteError) { - throw std::runtime_error(result.message); - } - - return {}; - }); - - auto attach = HOSTFN("attach", 4) { - if (count < 3) { - throw jsi::JSError(rt, - "[op-sqlite][attach] Incorrect number of arguments"); - } - if (!args[0].isString() || !args[1].isString() || !args[2].isString()) { - throw jsi::JSError( - rt, "dbName, databaseToAttach and alias must be a strings"); - return {}; - } - - std::string tempDocPath = std::string(basePath); - if (count > 3 && !args[3].isUndefined() && !args[3].isNull()) { - if (!args[3].isString()) { - throw std::runtime_error( - "[op-sqlite][attach] database location must be a string"); - } - - tempDocPath = tempDocPath + "/" + args[3].asString(rt).utf8(rt); - } - - std::string dbName = args[0].asString(rt).utf8(rt); - std::string databaseToAttach = args[1].asString(rt).utf8(rt); - std::string alias = args[2].asString(rt).utf8(rt); - BridgeResult result = - opsqlite_attach(dbName, tempDocPath, databaseToAttach, alias); - - if (result.type == SQLiteError) { - throw std::runtime_error(result.message); - } - - return {}; - }); - - auto detach = HOSTFN("detach", 2) { - if (count < 2) { - throw std::runtime_error( - "[op-sqlite][detach] Incorrect number of arguments"); - } - if (!args[0].isString() || !args[1].isString()) { - throw std::runtime_error( - "dbName, databaseToAttach and alias must be a strings"); - return {}; - } - - std::string dbName = args[0].asString(rt).utf8(rt); - std::string alias = args[1].asString(rt).utf8(rt); - BridgeResult result = opsqlite_detach(dbName, alias); - - if (result.type == SQLiteError) { - throw jsi::JSError(rt, result.message.c_str()); - } - - return {}; - }); - - auto close = HOSTFN("close", 1) { - if (count == 0) { - throw std::runtime_error("[op-sqlite][close] database name is required"); - } - - if (!args[0].isString()) { - throw std::runtime_error( - "[op-sqlite][close] database name must be a string"); - } + auto open = DbHostObject::open(rt, basePath); - std::string dbName = args[0].asString(rt).utf8(rt); + auto attach = DbHostObject::attach(rt, basePath); - BridgeResult result = opsqlite_close(dbName); + auto detach = DbHostObject::detach(rt); - if (result.type == SQLiteError) { - throw jsi::JSError(rt, result.message.c_str()); - } - - return {}; - }); + auto close = DbHostObject::close(rt); auto remove = HOSTFN("delete", 2) { if (count == 0) { @@ -301,18 +178,18 @@ void install(jsi::Runtime &rt, }); auto execute_async = HOSTFN("executeAsync", 3) { - if (count < 3) { - throw std::runtime_error( - "[op-sqlite][executeAsync] Incorrect arguments for executeAsync"); - } + if (count < 3) { + throw std::runtime_error( + "[op-sqlite][executeAsync] Incorrect arguments for executeAsync"); + } - const std::string dbName = args[0].asString(rt).utf8(rt); - const std::string query = args[1].asString(rt).utf8(rt); - const jsi::Value &originalParams = args[2]; + const std::string dbName = args[0].asString(rt).utf8(rt); + const std::string query = args[1].asString(rt).utf8(rt); + const jsi::Value &originalParams = args[2]; - std::vector params = toVariantVec(rt, originalParams); + std::vector params = toVariantVec(rt, originalParams); - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor(rt, HOSTFN("executor", 2) { auto resolve = std::make_shared(rt, args[0]); @@ -455,16 +332,16 @@ void install(jsi::Runtime &rt, }); auto load_file = HOSTFN("loadFile", 2) { - if (sizeof(args) < 2) { - throw std::runtime_error( - "[op-sqlite][loadFile] Incorrect parameter count"); - return {}; - } + if (sizeof(args) < 2) { + throw std::runtime_error( + "[op-sqlite][loadFile] Incorrect parameter count"); + return {}; + } - const std::string dbName = args[0].asString(rt).utf8(rt); - const std::string sqlFileName = args[1].asString(rt).utf8(rt); + const std::string dbName = args[0].asString(rt).utf8(rt); + const std::string sqlFileName = args[1].asString(rt).utf8(rt); - auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); + auto promiseCtr = rt.global().getPropertyAsFunction(rt, "Promise"); auto promise = promiseCtr.callAsConstructor(rt, HOSTFN("executor", 2) { auto resolve = std::make_shared(rt, args[0]); auto reject = std::make_shared(rt, args[1]); @@ -501,162 +378,167 @@ void install(jsi::Runtime &rt, }); auto update_hook = HOSTFN("updateHook", 2) { - if (sizeof(args) < 2) { - throw std::runtime_error("[op-sqlite][updateHook] Incorrect parameters: " - "dbName and callback needed"); - return {}; - } + if (sizeof(args) < 2) { + throw std::runtime_error( + "[op-sqlite][updateHook] Incorrect parameters: " + "dbName and callback needed"); + return {}; + } - auto dbName = args[0].asString(rt).utf8(rt); - auto callback = std::make_shared(rt, args[1]); + auto dbName = args[0].asString(rt).utf8(rt); + auto callback = std::make_shared(rt, args[1]); - if (callback->isUndefined() || callback->isNull()) { - opsqlite_deregister_update_hook(dbName); - return {}; - } + if (callback->isUndefined() || callback->isNull()) { + opsqlite_deregister_update_hook(dbName); + return {}; + } - updateHooks[dbName] = callback; + updateHooks[dbName] = callback; - auto hook = [&rt, callback](std::string dbName, std::string tableName, - std::string operation, int rowId) { - std::vector params; - std::vector results; - std::shared_ptr> metadata = - std::make_shared>(); + auto hook = [&rt, callback](std::string dbName, std::string tableName, + std::string operation, int rowId) { + std::vector params; + std::vector results; + std::shared_ptr> metadata = + std::make_shared>(); - if (operation != "DELETE") { - std::string query = "SELECT * FROM " + tableName + - " where rowid = " + std::to_string(rowId) + ";"; - opsqlite_execute(dbName, query, ¶ms, &results, metadata); - } + if (operation != "DELETE") { + std::string query = "SELECT * FROM " + tableName + + " where rowid = " + std::to_string(rowId) + ";"; + opsqlite_execute(dbName, query, ¶ms, &results, metadata); + } - invoker->invokeAsync( - [&rt, - results = std::make_shared>(results), - callback, tableName = std::move(tableName), - operation = std::move(operation), &rowId] { - auto res = jsi::Object(rt); - res.setProperty(rt, "table", - jsi::String::createFromUtf8(rt, tableName)); - res.setProperty(rt, "operation", - jsi::String::createFromUtf8(rt, operation)); - res.setProperty(rt, "rowId", jsi::Value(rowId)); - if (results->size() != 0) { - res.setProperty( - rt, "row", - jsi::Object::createFromHostObject( - rt, std::make_shared(results->at(0)))); - } + invoker->invokeAsync( + [&rt, + results = std::make_shared>(results), + callback, tableName = std::move(tableName), + operation = std::move(operation), &rowId] { + auto res = jsi::Object(rt); + res.setProperty(rt, "table", + jsi::String::createFromUtf8(rt, tableName)); + res.setProperty(rt, "operation", + jsi::String::createFromUtf8(rt, operation)); + res.setProperty(rt, "rowId", jsi::Value(rowId)); + if (results->size() != 0) { + res.setProperty(rt, "row", + jsi::Object::createFromHostObject( + rt, std::make_shared( + results->at(0)))); + } - callback->asObject(rt).asFunction(rt).call(rt, res); - }); - }; + callback->asObject(rt).asFunction(rt).call(rt, res); + }); + }; - opsqlite_register_update_hook(dbName, std::move(hook)); + opsqlite_register_update_hook(dbName, std::move(hook)); - return {}; + return {}; }); auto commit_hook = HOSTFN("commitHook", 2) { - if (sizeof(args) < 2) { - throw std::runtime_error("[op-sqlite][commitHook] Incorrect parameters: " - "dbName and callback needed"); - return {}; - } + if (sizeof(args) < 2) { + throw std::runtime_error( + "[op-sqlite][commitHook] Incorrect parameters: " + "dbName and callback needed"); + return {}; + } - auto dbName = args[0].asString(rt).utf8(rt); - auto callback = std::make_shared(rt, args[1]); - if (callback->isUndefined() || callback->isNull()) { - opsqlite_deregister_commit_hook(dbName); - return {}; - } - commitHooks[dbName] = callback; + auto dbName = args[0].asString(rt).utf8(rt); + auto callback = std::make_shared(rt, args[1]); + if (callback->isUndefined() || callback->isNull()) { + opsqlite_deregister_commit_hook(dbName); + return {}; + } + commitHooks[dbName] = callback; - auto hook = [&rt, callback](std::string dbName) { - invoker->invokeAsync( - [&rt, callback] { callback->asObject(rt).asFunction(rt).call(rt); }); - }; + auto hook = [&rt, callback](std::string dbName) { + invoker->invokeAsync([&rt, callback] { + callback->asObject(rt).asFunction(rt).call(rt); + }); + }; - opsqlite_register_commit_hook(dbName, std::move(hook)); + opsqlite_register_commit_hook(dbName, std::move(hook)); - return {}; + return {}; }); auto rollback_hook = HOSTFN("rollbackHook", 2) { - if (sizeof(args) < 2) { - throw std::runtime_error( - "[op-sqlite][rollbackHook] Incorrect parameters: " - "dbName and callback needed"); - return {}; - } + if (sizeof(args) < 2) { + throw std::runtime_error( + "[op-sqlite][rollbackHook] Incorrect parameters: " + "dbName and callback needed"); + return {}; + } - auto dbName = args[0].asString(rt).utf8(rt); - auto callback = std::make_shared(rt, args[1]); + auto dbName = args[0].asString(rt).utf8(rt); + auto callback = std::make_shared(rt, args[1]); - if (callback->isUndefined() || callback->isNull()) { - opsqlite_deregister_rollback_hook(dbName); - return {}; - } - rollbackHooks[dbName] = callback; + if (callback->isUndefined() || callback->isNull()) { + opsqlite_deregister_rollback_hook(dbName); + return {}; + } + rollbackHooks[dbName] = callback; - auto hook = [&rt, callback](std::string dbName) { - invoker->invokeAsync( - [&rt, callback] { callback->asObject(rt).asFunction(rt).call(rt); }); - }; + auto hook = [&rt, callback](std::string dbName) { + invoker->invokeAsync([&rt, callback] { + callback->asObject(rt).asFunction(rt).call(rt); + }); + }; - opsqlite_register_rollback_hook(dbName, std::move(hook)); - return {}; + opsqlite_register_rollback_hook(dbName, std::move(hook)); + return {}; }); auto prepare_statement = HOSTFN("prepareStatement", 1) { - auto dbName = args[0].asString(rt).utf8(rt); - auto query = args[1].asString(rt).utf8(rt); + auto dbName = args[0].asString(rt).utf8(rt); + auto query = args[1].asString(rt).utf8(rt); - sqlite3_stmt *statement = opsqlite_prepare_statement(dbName, query); + sqlite3_stmt *statement = opsqlite_prepare_statement(dbName, query); - auto preparedStatementHostObject = - std::make_shared(dbName, statement); + auto preparedStatementHostObject = + std::make_shared(dbName, statement); - return jsi::Object::createFromHostObject(rt, preparedStatementHostObject); + return jsi::Object::createFromHostObject(rt, + preparedStatementHostObject); }); auto load_extension = HOSTFN("loadExtension", 2) { - auto db_name = args[0].asString(rt).utf8(rt); - auto path = args[1].asString(rt).utf8(rt); - std::string entryPoint = ""; - if (count > 2 && args[2].isString()) { - entryPoint = args[2].asString(rt).utf8(rt); - } + auto db_name = args[0].asString(rt).utf8(rt); + auto path = args[1].asString(rt).utf8(rt); + std::string entryPoint = ""; + if (count > 2 && args[2].isString()) { + entryPoint = args[2].asString(rt).utf8(rt); + } - auto result = opsqlite_load_extension(db_name, path, entryPoint); - if (result.type == SQLiteError) { - throw std::runtime_error(result.message); - } - return {}; + auto result = opsqlite_load_extension(db_name, path, entryPoint); + if (result.type == SQLiteError) { + throw std::runtime_error(result.message); + } + return {}; }); auto get_db_path = HOSTFN("getDbPath", 2) { - std::string db_name = args[0].asString(rt).utf8(rt); - std::string path = std::string(basePath); - if (count > 1 && !args[1].isUndefined() && !args[1].isNull()) { - if (!args[1].isString()) { - throw std::runtime_error( - "[op-sqlite][open] database location must be a string"); - } + std::string db_name = args[0].asString(rt).utf8(rt); + std::string path = std::string(basePath); + if (count > 1 && !args[1].isUndefined() && !args[1].isNull()) { + if (!args[1].isString()) { + throw std::runtime_error( + "[op-sqlite][open] database location must be a string"); + } - std::string lastPath = args[1].asString(rt).utf8(rt); + std::string lastPath = args[1].asString(rt).utf8(rt); - if (lastPath == ":memory:") { - path = ":memory:"; - } else if (lastPath.rfind("/", 0) == 0) { - path = lastPath; - } else { - path = path + "/" + lastPath; - } - } + if (lastPath == ":memory:") { + path = ":memory:"; + } else if (lastPath.rfind("/", 0) == 0) { + path = lastPath; + } else { + path = path + "/" + lastPath; + } + } - auto result = opsqlite_get_db_path(db_name, path); - return jsi::String::createFromUtf8(rt, result); + auto result = opsqlite_get_db_path(db_name, path); + return jsi::String::createFromUtf8(rt, result); }); jsi::Object module = jsi::Object(rt); @@ -679,6 +561,10 @@ void install(jsi::Runtime &rt, module.setProperty(rt, "executeRawAsync", std::move(execute_raw_async)); module.setProperty(rt, "getDbPath", std::move(get_db_path)); + auto dbHostObj = std::make_shared(); + module.setProperty(rt, "dbHostObject", + jsi::Object::createFromHostObject(rt, dbHostObj)); + rt.global().setProperty(rt, "__OPSQLiteProxy", std::move(module)); } diff --git a/cpp/sqlite3.h b/cpp/sqlite3.h index 6f5dff13..0a83ecb7 100644 --- a/cpp/sqlite3.h +++ b/cpp/sqlite3.h @@ -6031,7 +6031,7 @@ SQLITE_API int sqlite3_set_clientdata(sqlite3 *, const char *, void *, */ typedef void (*sqlite3_destructor_type)(void *); #define SQLITE_STATIC ((sqlite3_destructor_type)0) -#define SQLITE_TRANSIENT ((sqlite3_destructor_type) - 1) +#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) /* ** CAPI3REF: Setting The Result Of An SQL Function diff --git a/cpp/validators/DbAttachValidator.cpp b/cpp/validators/DbAttachValidator.cpp new file mode 100644 index 00000000..d4aa8de8 --- /dev/null +++ b/cpp/validators/DbAttachValidator.cpp @@ -0,0 +1,39 @@ +// +// Created by jplc on 4/4/24. +// + +#include "DbAttachValidator.h" + +namespace opsqlite { +namespace validators { + +bool DbAttachValidator::invalidArgsNumber(std::string &msg, int count) { + if (count < 3) { + msg = "[op-sqlite][attach] Incorrect number of arguments"; + return true; + } + return false; +} + +bool DbAttachValidator::noStringArgs(std::string &msg, const jsi::Value &arg0, const jsi::Value &arg1, const jsi::Value &arg2) { + if (!arg0.isString() || !arg1.isString() || !arg2.isString()) { + msg = "dbName, databaseToAttach and alias must be a strings"; + return true; + } + return false; +} + +bool DbAttachValidator::locationArgDefined(const int count, const jsi::Value &arg3) { + return count > 3 && !arg3.isUndefined() && !arg3.isNull(); +} + +bool DbAttachValidator::locationArgIsNotString(std::string &msg, const jsi::Value &arg3) { + if (!arg3.isString()) { + msg = "[op-sqlite][attach] database location must be a string"; + return true; + } + return false; +} + +} +} \ No newline at end of file diff --git a/cpp/validators/DbAttachValidator.h b/cpp/validators/DbAttachValidator.h new file mode 100644 index 00000000..910a20a2 --- /dev/null +++ b/cpp/validators/DbAttachValidator.h @@ -0,0 +1,30 @@ +// +// Created by jplc on 4/4/24. +// + +#ifndef OPSQLITEEXAMPLE_VALIDATORS_DBATTACHVALIDATOR_H +#define OPSQLITEEXAMPLE_VALIDATORS_DBATTACHVALIDATOR_H + +#include +#include + +namespace jsi = facebook::jsi; + +namespace opsqlite { +namespace validators { + +class DbAttachValidator { +public: + static bool invalidArgsNumber(std::string &msg, int count); + static bool noStringArgs(std::string &msg, const jsi::Value &arg0, const jsi::Value &arg1, const jsi::Value &arg2); + static bool locationArgDefined(const int count, const jsi::Value &arg3); + static bool locationArgIsNotString(std::string &msg, const jsi::Value &arg3); + +private: + DbAttachValidator(); +}; + +} +} + +#endif //OPSQLITEEXAMPLE_VALIDATORS_DBATTACHVALIDATOR_H diff --git a/cpp/validators/DbCloseValidator.cpp b/cpp/validators/DbCloseValidator.cpp new file mode 100644 index 00000000..c7ef605a --- /dev/null +++ b/cpp/validators/DbCloseValidator.cpp @@ -0,0 +1,27 @@ +// +// Created by jplc on 4/4/24. +// + +#include "DbCloseValidator.h" + +namespace opsqlite { +namespace validators { + +bool DbCloseValidator::invalidArgsNumber(std::string &msg, int count) { + if (count == 0) { + msg = "[op-sqlite][close] database name is required"; + return true; + } + return false; +} + +bool DbCloseValidator::noStringArgs(std::string &msg, const jsi::Value &arg0) { + if (!arg0.isString()) { + msg = "[op-sqlite][close] database name must be a string"; + return true; + } + return false; +} + +} +} \ No newline at end of file diff --git a/cpp/validators/DbCloseValidator.h b/cpp/validators/DbCloseValidator.h new file mode 100644 index 00000000..28f6bb7f --- /dev/null +++ b/cpp/validators/DbCloseValidator.h @@ -0,0 +1,28 @@ +// +// Created by jplc on 4/4/24. +// + +#ifndef OPSQLITEEXAMPLE_VALIDATORS_DBCLOSEVALIDATOR_H +#define OPSQLITEEXAMPLE_VALIDATORS_DBCLOSEVALIDATOR_H + +#include +#include + +namespace jsi = facebook::jsi; + +namespace opsqlite { +namespace validators { + +class DbCloseValidator { +public: + static bool invalidArgsNumber(std::string &msg, int count); + static bool noStringArgs(std::string &msg, const jsi::Value &arg0); + +private: + DbCloseValidator(); +}; + +} +} + +#endif //OPSQLITEEXAMPLE_VALIDATORS_DBCLOSEVALIDATOR_H diff --git a/cpp/validators/DbDetachValidator.cpp b/cpp/validators/DbDetachValidator.cpp new file mode 100644 index 00000000..96a107a1 --- /dev/null +++ b/cpp/validators/DbDetachValidator.cpp @@ -0,0 +1,27 @@ +// +// Created by jplc on 4/4/24. +// + +#include "DbDetachValidator.h" + +namespace opsqlite { +namespace validators { + +bool DbDetachValidator::invalidArgsNumber(std::string &msg, int count) { + if (count < 2) { + msg = "[op-sqlite][detach] Incorrect number of arguments"; + return true; + } + return false; +} + +bool DbDetachValidator::noStringArgs(std::string &msg, const jsi::Value &arg0, const jsi::Value &arg1) { + if (!arg0.isString() || !arg1.isString()) { + msg = "dbName, databaseToAttach and alias must be a strings"; + return true; + } + return false; +} + +} +} \ No newline at end of file diff --git a/cpp/validators/DbDetachValidator.h b/cpp/validators/DbDetachValidator.h new file mode 100644 index 00000000..0dd2ad17 --- /dev/null +++ b/cpp/validators/DbDetachValidator.h @@ -0,0 +1,28 @@ +// +// Created by jplc on 4/4/24. +// + +#ifndef OPSQLITEEXAMPLE_VALIDATORS_DBDETACHVALIDATOR_H +#define OPSQLITEEXAMPLE_VALIDATORS_DBDETACHVALIDATOR_H + +#include +#include + +namespace jsi = facebook::jsi; + +namespace opsqlite { +namespace validators { + +class DbDetachValidator { +public: + static bool invalidArgsNumber(std::string &msg, int count); + static bool noStringArgs(std::string &msg, const jsi::Value &arg0, const jsi::Value &arg1); + +private: + DbDetachValidator(); +}; + +} +} + +#endif //OPSQLITEEXAMPLE_VALIDATORS_DBDETACHVALIDATOR_H diff --git a/cpp/validators/DbOpenValidator.cpp b/cpp/validators/DbOpenValidator.cpp new file mode 100644 index 00000000..c92b3821 --- /dev/null +++ b/cpp/validators/DbOpenValidator.cpp @@ -0,0 +1,46 @@ +#include "DbOpenValidator.h" + +namespace opsqlite { +namespace validators { + +bool DbOpenValidator::invalidArgsNumber(std::string &msg, int count) { + if (count == 0) { + msg = "[op-sqlite][open] database name is required"; + return true; + } + return false; +} + +std::string DbOpenValidator::getPath(jsi::Runtime &rt, const jsi::Object &options, const std::string &basePath) { + std::string path = basePath; + std::string location = getLocation(rt, options); + if (!location.empty()) { + if (location == ":memory:") { + path = ":memory:"; + } else if (location.rfind("/", 0) == 0) { + path = location; + } else { + path = path + "/" + location; + } + } + return path; +} + +std::string DbOpenValidator::getLocation(jsi::Runtime &rt, const jsi::Object &options) { + std::string location = ""; + if (options.hasProperty(rt, "location")) { + location = options.getProperty(rt, "location").asString(rt).utf8(rt); + } + return location; +} + +std::string DbOpenValidator::getEncryptionKey(jsi::Runtime &rt, const jsi::Object &options) { + std::string encryptionKey = ""; + if (options.hasProperty(rt, "encryptionKey")) { + encryptionKey = options.getProperty(rt, "encryptionKey").asString(rt).utf8(rt); + } + return encryptionKey; +} + +} +} \ No newline at end of file diff --git a/cpp/validators/DbOpenValidator.h b/cpp/validators/DbOpenValidator.h new file mode 100644 index 00000000..c201ce20 --- /dev/null +++ b/cpp/validators/DbOpenValidator.h @@ -0,0 +1,26 @@ +#ifndef OPSQLITEEXAMPLE_VALIDATORS_DBOPENVALIDATOR_H +#define OPSQLITEEXAMPLE_VALIDATORS_DBOPENVALIDATOR_H + +#include +#include + +namespace jsi = facebook::jsi; + +namespace opsqlite { +namespace validators { + +class DbOpenValidator { +public: + static bool invalidArgsNumber(std::string &msg, int count); + static std::string getPath(jsi::Runtime &rt, const jsi::Object &options, const std::string &basePath); + static std::string getLocation(jsi::Runtime &rt, const jsi::Object &options); + static std::string getEncryptionKey(jsi::Runtime &rt, const jsi::Object &options); + +private: + DbOpenValidator(); +}; + +} +} + +#endif // OPSQLITEEXAMPLE_VALIDATORS_DBOPENVALIDATOR_H \ No newline at end of file diff --git a/example/.yarn/install-state.gz b/example/.yarn/install-state.gz index cf4bc1bd..2763d14f 100644 Binary files a/example/.yarn/install-state.gz and b/example/.yarn/install-state.gz differ diff --git a/turbo.json b/turbo.json index b3e6acb3..8741b865 100644 --- a/turbo.json +++ b/turbo.json @@ -8,6 +8,8 @@ "!android/build", "src/*.ts", "src/*.tsx", + "cpp/*.h", + "cpp/*.cpp", "example/package.json", "example/android", "!example/android/.gradle", @@ -20,6 +22,7 @@ "inputs": [ "package.json", "*.podspec", + "cpp/**", "ios", "src/*.ts", "src/*.tsx",