diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 5332ee65b7b6..039563fd0c25 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -95,6 +95,8 @@
RCTNewArchEnabled
+ RNNitroSQLite_DatabaseLocation
+ ApplicationSupport
UIAppFonts
ExpensifyNewKansas-Medium.otf
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index a6f6309b529e..5ef8a0f1bf4f 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -3935,7 +3935,7 @@ PODS:
- SocketRocket
- Turf
- Yoga
- - RNNitroSQLite (9.6.0):
+ - RNNitroSQLite (9.8.1):
- boost
- DoubleConversion
- fast_float
@@ -5118,7 +5118,7 @@ SPEC CHECKSUMS:
RNLiveMarkdown: 318b3defc20b75a21ca00d58342109230c044da5
RNLocalize: 05e367a873223683f0e268d0af9a8a8e6aed3b26
rnmapbox-maps: 8b7629ef3ae59dd96340470e568cbc7c08c54ff9
- RNNitroSQLite: a9b5965d511ed6e99ce903380e64934d043a0d2c
+ RNNitroSQLite: 06ff2497ab8b6c75b780f080f23c99555e7bf4a5
RNPermissions: 518f0a0c439acc74e2b9937e0e7d29e5031ae949
RNReactNativeHapticFeedback: 5f1542065f0b24c9252bd8cf3e83bc9c548182e4
RNReanimated: de3eaf0bd2aec5036554a125eb02c28b59de45ae
diff --git a/package-lock.json b/package-lock.json
index 29e82a430a0b..cd4d1281f05f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -124,7 +124,7 @@
"react-native-localize": "^3.5.4",
"react-native-nitro-fetch": "1.5.4",
"react-native-nitro-modules": "0.36.3",
- "react-native-nitro-sqlite": "9.6.0",
+ "react-native-nitro-sqlite": "9.8.1",
"react-native-onyx": "3.0.111",
"react-native-pager-view": "9.0.4",
"react-native-pdf": "7.0.2",
@@ -36126,9 +36126,9 @@
}
},
"node_modules/react-native-nitro-sqlite": {
- "version": "9.6.0",
- "resolved": "https://registry.npmjs.org/react-native-nitro-sqlite/-/react-native-nitro-sqlite-9.6.0.tgz",
- "integrity": "sha512-a/N1yGhM8RvCCnaYhEHhh35YS+HDOAcGKeKFsp2ExCzIjP8vPXuzQtHylgLQLeAh7rUaism5q0QQFfogXm1SXA==",
+ "version": "9.8.1",
+ "resolved": "https://registry.npmjs.org/react-native-nitro-sqlite/-/react-native-nitro-sqlite-9.8.1.tgz",
+ "integrity": "sha512-5IjcynOKpxYpQ5SVWc0r4q7wSYQW5DGjDfU3068ytI0vmC0FsqY6PZyUNkrik4qN9LIbvSwLOxoWdFI8/+zRHg==",
"license": "MIT",
"dependencies": {
"typeorm": "0.3.27"
diff --git a/package.json b/package.json
index 395a9c8e5bb3..2280bb41c41f 100644
--- a/package.json
+++ b/package.json
@@ -200,7 +200,7 @@
"react-native-localize": "^3.5.4",
"react-native-nitro-fetch": "1.5.4",
"react-native-nitro-modules": "0.36.3",
- "react-native-nitro-sqlite": "9.6.0",
+ "react-native-nitro-sqlite": "9.8.1",
"react-native-onyx": "3.0.111",
"react-native-pager-view": "9.0.4",
"react-native-pdf": "7.0.2",
diff --git a/patches/react-native-nitro-sqlite/details.md b/patches/react-native-nitro-sqlite/details.md
deleted file mode 100644
index 47198d20deda..000000000000
--- a/patches/react-native-nitro-sqlite/details.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# react-native-nitro-sqlite patches
-
-### [react-native-nitro-sqlite+9.6.0+001+dont-mask-original-error-on-rollback-failure.patch](react-native-nitro-sqlite+9.6.0+001+dont-mask-original-error-on-rollback-failure.patch)
-
-- Reason:
-
- ```
- When a statement inside executeBatch fails and SQLite has already auto-rolled the transaction
- back (disk-full I/O errors do this), the library's own ROLLBACK fails with "cannot rollback -
- no transaction is active" and that error is thrown instead of the original one, hiding the real
- failure (~2.8k masked log lines/day on iOS). The patch wraps both ROLLBACK calls in try/catch
- so the original error is rethrown.
- ```
-
-- Upstream PR/issue: Already fixed upstream in 9.7.0 via https://github.com/margelo/react-native-nitro-sqlite/pull/292. We stay on 9.6.0 because the 9.7.0 podspec force-enables `SQLITE_THREADSAFE=0` on iOS and its new per-database queue breaks second opens of the same database (used by `src/libs/ExportOnyxState/index.native.ts`). The patch can be dropped when those are resolved and we bump.
-- E/App issue: https://github.com/Expensify/App/issues/97908
-- PR introducing patch: https://github.com/Expensify/App/pull/97954
-
-### [react-native-nitro-sqlite+9.6.0+002+store-database-outside-documents.patch](react-native-nitro-sqlite+9.6.0+002+store-database-outside-documents.patch)
-
-- Reason:
-
- ```
- The library stores SQLite databases in the iOS Documents directory, which is exposed to users
- via the Files app when file sharing is enabled. This patch stores databases in
- Library/Application Support instead (persistent, backed up, never user-visible) and migrates
- databases created by older app versions out of Documents on first launch. The database and its
- -wal/-shm journal files are copied as a set before the originals are deleted, and if the copy
- fails the database keeps being opened from Documents and the migration retries on the next
- launch, so committed writes are never separated from their WAL.
- ```
-
-- Upstream PR/issue: https://github.com/margelo/react-native-nitro-sqlite/issues/289
-- E/App issue: https://github.com/Expensify/App/issues/96649
-- PR introducing patch: https://github.com/Expensify/App/pull/96531
diff --git a/patches/react-native-nitro-sqlite/react-native-nitro-sqlite+9.6.0+001+dont-mask-original-error-on-rollback-failure.patch b/patches/react-native-nitro-sqlite/react-native-nitro-sqlite+9.6.0+001+dont-mask-original-error-on-rollback-failure.patch
deleted file mode 100644
index 0dadc3344a4a..000000000000
--- a/patches/react-native-nitro-sqlite/react-native-nitro-sqlite+9.6.0+001+dont-mask-original-error-on-rollback-failure.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-diff --git a/node_modules/react-native-nitro-sqlite/cpp/sqliteExecuteBatch.cpp b/node_modules/react-native-nitro-sqlite/cpp/sqliteExecuteBatch.cpp
-index 4aca8b5..2ea8581 100644
---- a/node_modules/react-native-nitro-sqlite/cpp/sqliteExecuteBatch.cpp
-+++ b/node_modules/react-native-nitro-sqlite/cpp/sqliteExecuteBatch.cpp
-@@ -51,7 +51,11 @@ SQLiteOperationResult sqliteExecuteBatch(const std::string& dbName, const std::v
- auto result = sqliteExecute(dbName, command.sql, command.params);
- rowsAffected += result->getRowsAffected();
- } catch (NitroSQLiteException& e) {
-- sqliteExecuteLiteral(dbName, "ROLLBACK");
-+ // A failed ROLLBACK (SQLite may have already rolled back on its own) must not mask the original error.
-+ try {
-+ sqliteExecuteLiteral(dbName, "ROLLBACK");
-+ } catch (...) {
-+ }
- throw e;
- }
- }
-@@ -61,7 +65,11 @@ SQLiteOperationResult sqliteExecuteBatch(const std::string& dbName, const std::v
- .commands = (int)commandCount,
- };
- } catch (NitroSQLiteException& e) {
-- sqliteExecuteLiteral(dbName, "ROLLBACK");
-+ // A failed ROLLBACK (SQLite may have already rolled back on its own) must not mask the original error.
-+ try {
-+ sqliteExecuteLiteral(dbName, "ROLLBACK");
-+ } catch (...) {
-+ }
- throw e;
- }
- }
diff --git a/patches/react-native-nitro-sqlite/react-native-nitro-sqlite+9.6.0+002+store-database-outside-documents.patch b/patches/react-native-nitro-sqlite/react-native-nitro-sqlite+9.6.0+002+store-database-outside-documents.patch
deleted file mode 100644
index a98111df979c..000000000000
--- a/patches/react-native-nitro-sqlite/react-native-nitro-sqlite+9.6.0+002+store-database-outside-documents.patch
+++ /dev/null
@@ -1,95 +0,0 @@
-diff --git a/node_modules/react-native-nitro-sqlite/ios/OnLoad.mm b/node_modules/react-native-nitro-sqlite/ios/OnLoad.mm
-index 6ce7258..65c29cb 100644
---- a/node_modules/react-native-nitro-sqlite/ios/OnLoad.mm
-+++ b/node_modules/react-native-nitro-sqlite/ios/OnLoad.mm
-@@ -10,6 +10,63 @@
- using namespace margelo::nitro;
- using namespace margelo::nitro::rnnitrosqlite;
-
-+// The Documents directory can be exposed to the user (Files app) when file sharing
-+// is enabled, so databases are stored in Library/Application Support instead.
-+// Databases created by older app versions are moved out of Documents on first launch.
-+//
-+// The "OnyxDB" name must match the database the app opens: DEFAULT_DB_NAME in
-+// src/CONST/index.ts and DB_NAME in react-native-onyx's SQLiteProvider. A Jest test
-+// (MoveFilesOutOfDocumentsTest) fails if the names drift apart.
-+static void migrateDatabaseFiles(NSString *fromDirectory, NSString *toDirectory) {
-+ NSFileManager *fileManager = [NSFileManager defaultManager];
-+ NSArray *databaseFiles = @[ @"OnyxDB", @"OnyxDB-wal", @"OnyxDB-shm" ];
-+
-+ if (![fileManager fileExistsAtPath:[fromDirectory stringByAppendingPathComponent:@"OnyxDB"]]) {
-+ // Nothing to migrate. A previous run may have been interrupted after copying the set but
-+ // before removing the journal files, so sweep any leftovers out of Documents.
-+ [fileManager removeItemAtPath:[fromDirectory stringByAppendingPathComponent:@"OnyxDB-wal"] error:nil];
-+ [fileManager removeItemAtPath:[fromDirectory stringByAppendingPathComponent:@"OnyxDB-shm"] error:nil];
-+ return;
-+ }
-+
-+ // A database in Documents means an older build was writing there, so it is the live copy.
-+ // Remove whatever sits at the destination (e.g. after a downgrade and re-upgrade) so a -wal
-+ // from one database generation is never replayed into a database from another.
-+ for (NSString *file in databaseFiles) {
-+ [fileManager removeItemAtPath:[toDirectory stringByAppendingPathComponent:file] error:nil];
-+ }
-+
-+ // Copy the whole set before deleting anything: committed-but-uncheckpointed writes live in
-+ // the -wal file and SQLite only replays it when it sits next to its database, so the files
-+ // must never be separated. If any copy fails, the intact originals in Documents keep being
-+ // used (see the fallback in +load) and the migration retries on the next launch.
-+ for (NSString *file in databaseFiles) {
-+ NSString *sourcePath = [fromDirectory stringByAppendingPathComponent:file];
-+ if (![fileManager fileExistsAtPath:sourcePath]) {
-+ continue;
-+ }
-+
-+ NSError *error = nil;
-+ if (![fileManager copyItemAtPath:sourcePath toPath:[toDirectory stringByAppendingPathComponent:file] error:&error]) {
-+ NSLog(@"Failed to migrate database file %@: %@", file, error.localizedDescription);
-+ return;
-+ }
-+ }
-+
-+ // The database file is deleted first, and the journals only once that succeeds: if the
-+ // database cannot be removed, the existence check in +load keeps using the source database
-+ // on this launch, so its -wal must stay next to it or committed writes would be lost.
-+ // An interruption after the first delete can only leave journal files behind, which the
-+ // sweep above removes on the next launch.
-+ NSError *removeError = nil;
-+ if (![fileManager removeItemAtPath:[fromDirectory stringByAppendingPathComponent:@"OnyxDB"] error:&removeError]) {
-+ NSLog(@"Failed to remove migrated database from its old location: %@", removeError.localizedDescription);
-+ return;
-+ }
-+ [fileManager removeItemAtPath:[fromDirectory stringByAppendingPathComponent:@"OnyxDB-wal"] error:nil];
-+ [fileManager removeItemAtPath:[fromDirectory stringByAppendingPathComponent:@"OnyxDB-shm"] error:nil];
-+}
-+
- + (void)load {
- // Get appGroupID value from Info.plist using key "AppGroup"
- NSString *appGroupID = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RNNitroSQLite_AppGroup"];
-@@ -30,9 +80,24 @@
-
- documentPath = [storeUrl path];
- } else {
-- // Get iOS app's document directory (to safely store database .sqlite3 file)
-- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true);
-+ // Store databases in Library/Application Support, which is persistent, backed up,
-+ // and never exposed to the user via the Files app (unlike the Documents directory)
-+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, true);
- documentPath = [paths objectAtIndex:0];
-+
-+ NSFileManager *fileManager = [NSFileManager defaultManager];
-+ if (![fileManager fileExistsAtPath:documentPath]) {
-+ [fileManager createDirectoryAtPath:documentPath withIntermediateDirectories:YES attributes:nil error:nil];
-+ }
-+
-+ NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) objectAtIndex:0];
-+ migrateDatabaseFiles(documentsDirectory, documentPath);
-+
-+ // If the database could not be moved out of Documents, keep opening it there rather than
-+ // creating a fresh empty one; the migration retries on the next launch.
-+ if ([fileManager fileExistsAtPath:[documentsDirectory stringByAppendingPathComponent:@"OnyxDB"]]) {
-+ documentPath = documentsDirectory;
-+ }
- }
-
- HybridNitroSQLite::docPath = [documentPath UTF8String];
diff --git a/tests/unit/MoveFilesOutOfDocumentsTest.ts b/tests/unit/MoveFilesOutOfDocumentsTest.ts
index 0f92417f8531..b2736e6b3996 100644
--- a/tests/unit/MoveFilesOutOfDocumentsTest.ts
+++ b/tests/unit/MoveFilesOutOfDocumentsTest.ts
@@ -165,18 +165,12 @@ describe('MoveFilesOutOfDocuments migration (native)', () => {
await expect(MoveFilesOutOfDocuments()).resolves.toBeUndefined();
});
- it('keeps the native database migration patch in sync with the database name', () => {
- const patchDir = path.resolve(__dirname, '../../patches/react-native-nitro-sqlite');
- const patchFileName = fs.readdirSync(patchDir).find((fileName) => fileName.includes('store-database-outside-documents'));
- expect(patchFileName).toBeDefined();
- const patchContent = fs.readFileSync(path.join(patchDir, String(patchFileName)), 'utf8');
-
- // The patch migrates database files by name at app startup, before any JS runs, so the
- // name is hardcoded there. If the database the app opens is ever renamed, the patch (and
- // this test) must be updated with it, or the migration would silently strand user data.
- expect(patchContent).toContain(`@"${CONST.DEFAULT_DB_NAME}"`);
-
+ it('keeps the Onyx database name aligned with the upstream per-database migration', () => {
const sqliteProviderContent = fs.readFileSync(path.resolve(__dirname, '../../node_modules/react-native-onyx/dist/storage/providers/SQLiteProvider.js'), 'utf8');
expect(sqliteProviderContent).toContain(`'${CONST.DEFAULT_DB_NAME}'`);
+
+ // NitroSQLite now migrates each database when it opens, using the caller's database name.
+ const nitroSQLiteContent = fs.readFileSync(path.resolve(__dirname, '../../node_modules/react-native-nitro-sqlite/cpp/hybridObjects/HybridNitroSQLite.cpp'), 'utf8');
+ expect(nitroSQLiteContent).toContain('return migrateDatabase(dbName,');
});
});