diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c24b2482..f4bac6b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -243,3 +243,63 @@ jobs: JAVA_OPTS: '-XX:MaxHeapSize=6g' run: | yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" + + build-android-libsql: + runs-on: ubuntu-latest + env: + TURBO_CACHE_DIR: .turbo/android + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Turn on libsql + run: | + node ./scripts/turnOnLibsql.js + + - 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: Install JDK + if: env.turbo_cache_hit != 1 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + + - name: Finalize Android SDK + if: env.turbo_cache_hit != 1 + run: | + /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null" + + - 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: | + yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" diff --git a/cpp/DBHostObject.cpp b/cpp/DBHostObject.cpp index cca07d04..13a0d9a2 100644 --- a/cpp/DBHostObject.cpp +++ b/cpp/DBHostObject.cpp @@ -556,6 +556,16 @@ void DBHostObject::create_jsi_functions() { return promise; }); +#ifdef OP_SQLITE_USE_LIBSQL + auto sync = HOSTFN("sync", 0) { + BridgeResult result = opsqlite_libsql_sync(db_name); + if (result.type == SQLiteError) { + throw std::runtime_error(result.message); + } + return {}; + }); +#endif + #ifndef OP_SQLITE_USE_LIBSQL auto load_file = HOSTFN("loadFile", 1) { if (sizeof(args) < 1) { @@ -797,7 +807,9 @@ void DBHostObject::create_jsi_functions() { function_map["executeBatchAsync"] = std::move(execute_batch_async); function_map["prepareStatement"] = std::move(prepare_statement); function_map["getDbPath"] = std::move(get_db_path); -#ifndef OP_SQLITE_USE_LIBSQL +#ifdef OP_SQLITE_USE_LIBSQL + function_map["sync"] = std::move(sync); +#else OP_SQLITE_USE_LIBSQL function_map["loadFile"] = std::move(load_file); function_map["updateHook"] = std::move(update_hook); function_map["commitHook"] = std::move(commit_hook); @@ -850,6 +862,9 @@ jsi::Value DBHostObject::get(jsi::Runtime &rt, if (name == "getDbPath") { return jsi::Value(rt, function_map["getDbPath"]); } + if (name == "sync") { + return jsi::Value(rt, function_map["sync"]); + } #ifdef OP_SQLITE_USE_LIBSQL if (name == "loadFile") { return HOSTFN("loadFile", 0) { diff --git a/cpp/libsql/bridge.cpp b/cpp/libsql/bridge.cpp index 5cd79841..89533ff3 100644 --- a/cpp/libsql/bridge.cpp +++ b/cpp/libsql/bridge.cpp @@ -186,6 +186,21 @@ BridgeResult opsqlite_libsql_detach(std::string const &mainDBName, }; } +BridgeResult opsqlite_libsql_sync(std::string const &name) { + check_db_open(name); + + auto db = db_map[name].db; + const char *err = NULL; + + int status = libsql_sync(db, &err); + + if (status != 0) { + return {.type = SQLiteError, .message = err}; + } + + return {.type = SQLiteOk}; +} + BridgeResult opsqlite_libsql_remove(std::string const &name, std::string const &path) { if (db_map.count(name) == 1) { diff --git a/cpp/libsql/bridge.h b/cpp/libsql/bridge.h index 2236562e..be354bb5 100644 --- a/cpp/libsql/bridge.h +++ b/cpp/libsql/bridge.h @@ -51,6 +51,8 @@ BridgeResult opsqlite_libsql_attach(std::string const &mainDBName, BridgeResult opsqlite_libsql_detach(std::string const &mainDBName, std::string const &alias); +BridgeResult opsqlite_libsql_sync(std::string const &name); + BridgeResult opsqlite_libsql_execute( std::string const &name, std::string const &query, const std::vector *params, std::vector *results, diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 0e8d7180..dcb27311 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -7,7 +7,7 @@ PODS: - hermes-engine (0.74.0): - hermes-engine/Pre-built (= 0.74.0) - hermes-engine/Pre-built (0.74.0) - - op-sqlite (6.1.3): + - op-sqlite (6.1.4): - React - React-callinvoker - React-Core @@ -1358,7 +1358,7 @@ SPEC CHECKSUMS: fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 6eae7edb2f563ee41d7c1f91f4f2e57c26d8a5c3 - op-sqlite: 3b399d7e847783963ba53b0fe979e667225b76de + op-sqlite: 6681bc41de4af899fd7014c936b039ac330c007e RCT-Folly: 045d6ecaa59d826c5736dfba0b2f4083ff8d79df RCTDeprecation: 3ca8b6c36bfb302e1895b72cfe7db0de0c92cd47 RCTRequired: 9fc183af555fd0c89a366c34c1ae70b7e03b1dc5 diff --git a/example/package.json b/example/package.json index 3d136ad5..071dd911 100644 --- a/example/package.json +++ b/example/package.json @@ -6,7 +6,8 @@ "android": "react-native run-android", "ios": "react-native run-ios --scheme='debug' --simulator='iPhone 15 Pro'", "start": "react-native start", - "pods": "cd ios && rm -rf Pods && rm -rf Podfile.lock && bundle exec pod install", + "pods": "cd ios && bundle exec pod install", + "pods:nuke": "cd ios && rm -rf Pods && rm -rf Podfile.lock && bundle exec pod install", "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" }, @@ -60,7 +61,7 @@ "crsqlite": false, "performanceMode": "2", "iosSqlite": false, - "fts5": false, - "libsql": true + "fts5": true, + "libsql": false } } diff --git a/example/src/tests/queries.spec.ts b/example/src/tests/queries.spec.ts index d1ff4b7a..c10b225c 100644 --- a/example/src/tests/queries.spec.ts +++ b/example/src/tests/queries.spec.ts @@ -7,7 +7,7 @@ import { type DB, type SQLBatchTuple, } from '@op-engineering/op-sqlite'; -import {beforeEach, describe, it} from './MochaRNAdapter'; +import {beforeEach, describe, it, itOnly} from './MochaRNAdapter'; import chai from 'chai'; const expect = chai.expect; @@ -50,7 +50,7 @@ export function queriesTests() { expect(res.rowsAffected).to.equal(0); }); - it('Open a libsql database replicated to turso', async () => { + itOnly('Open a libsql database replicated to turso', async () => { const remoteDb = openSync({ url: 'libsql://foo-ospfranco.turso.io', authToken: @@ -59,6 +59,9 @@ export function queriesTests() { }); const res = remoteDb.execute('SELECT 1'); + + remoteDb.sync(); + expect(res.rowsAffected).to.equal(0); }); } diff --git a/scripts/turnOnLibsql.js b/scripts/turnOnLibsql.js new file mode 100644 index 00000000..ff5cdedd --- /dev/null +++ b/scripts/turnOnLibsql.js @@ -0,0 +1,17 @@ +const fs = require('fs'); + +console.log('Current working directory:', process.cwd()); + +// Read the package.json file +const packageJson = JSON.parse(fs.readFileSync('./example/package.json')); + +// Modify the op-sqlite.sqlcipher key to true +packageJson['op-sqlite']['libsql'] = true; + +// Save the updated package.json file +fs.writeFileSync( + './example/package.json', + JSON.stringify(packageJson, null, 2) +); + +console.log('package.json updated successfully!'); diff --git a/scripts/turnOnSQLCipher.js b/scripts/turnOnSQLCipher.js index f14c93f4..85d44111 100644 --- a/scripts/turnOnSQLCipher.js +++ b/scripts/turnOnSQLCipher.js @@ -5,12 +5,13 @@ console.log('Current working directory:', process.cwd()); // Read the package.json file const packageJson = JSON.parse(fs.readFileSync('./example/package.json')); -console.log("ROPO package.json:", packageJson); - // Modify the op-sqlite.sqlcipher key to true packageJson['op-sqlite']['sqlcipher'] = true; // Save the updated package.json file -fs.writeFileSync('./example/package.json', JSON.stringify(packageJson, null, 2)); +fs.writeFileSync( + './example/package.json', + JSON.stringify(packageJson, null, 2) +); -console.log('package.json updated successfully!'); \ No newline at end of file +console.log('package.json updated successfully!'); diff --git a/src/index.ts b/src/index.ts index e8d9ff72..4eb39699 100644 --- a/src/index.ts +++ b/src/index.ts @@ -59,19 +59,20 @@ export const { export type QueryResult = { insertId?: number; rowsAffected: number; + res?: any[]; rows?: { /** Raw array with all dataset */ _array: any[]; - /** The lengh of the dataset */ + /** The length of the dataset */ length: number; - /** A convenience function to acess the index based the row object + /** A convenience function to access the index based the row object * @param idx the row index * @returns the row structure identified by column names */ item: (idx: number) => any; }; /** - * Query metadata, avaliable only for select query results + * Query metadata, available only for select query results */ metadata?: ColumnMetadata[]; }; @@ -81,12 +82,12 @@ export type QueryResult = { * Describes some information about columns fetched by the query */ export type ColumnMetadata = { - /** The name used for this column for this resultset */ + /** The name used for this column for this result set */ name: string; /** The declared column type for this column, when fetched directly from a table or a View resulting from a table column. "UNKNOWN" for dynamic values, like function returned ones. */ type: string; /** - * The index for this column for this resultset*/ + * The index for this column for this result set*/ index: number; }; @@ -186,6 +187,7 @@ export type DB = { }[]; callback: (response: any) => void; }) => () => void; + sync: () => void; }; type OPSQLiteProxy = { @@ -211,7 +213,6 @@ const locks: Record< > = {}; // Enhance some host functions - // Add 'item' function to result object to allow the sqlite-storage typeorm driver to work function enhanceQueryResult(result: QueryResult): void { // Add 'item' function to result object to allow the sqlite-storage typeorm driver to work @@ -222,6 +223,7 @@ function enhanceQueryResult(result: QueryResult): void { item: (idx: number) => result.rows?._array[idx], }; } else { + result.res = result.rows._array; result.rows.item = (idx: number) => result.rows?._array[idx]; } } @@ -268,6 +270,7 @@ function enhanceDB(db: DB, options: any): DB { executeRawAsync: db.executeRawAsync, getDbPath: db.getDbPath, reactiveExecute: db.reactiveExecute, + sync: db.sync, close: () => { db.close(); delete locks[options.url];