From 92999107dc1a83779fd503b6c8036dd70fd5c15b Mon Sep 17 00:00:00 2001 From: eliran goshen Date: Thu, 6 Aug 2026 15:05:17 +0200 Subject: [PATCH] Patch nitro-sqlite: stop failed ROLLBACKs masking the original SQLite error When a batch statement fails and SQLite has already auto-rolled the transaction back (disk-full I/O errors do this), the library's own ROLLBACK throws 'cannot rollback - no transaction is active' and replaces the real error (~2.8k masked lines/day on iOS). Swallow the rollback failure and rethrow the original. Patch over 9.6.0 instead of bumping to 9.7.0 because 9.7.0 force-enables SQLITE_THREADSAFE=0 on iOS and breaks double-open. Co-Authored-By: Claude Fable 5 --- patches/react-native-nitro-sqlite/details.md | 17 +++++++++++ ...k-original-error-on-rollback-failure.patch | 30 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 patches/react-native-nitro-sqlite/details.md create mode 100644 patches/react-native-nitro-sqlite/react-native-nitro-sqlite+9.6.0+001+dont-mask-original-error-on-rollback-failure.patch diff --git a/patches/react-native-nitro-sqlite/details.md b/patches/react-native-nitro-sqlite/details.md new file mode 100644 index 000000000000..fae807b6534d --- /dev/null +++ b/patches/react-native-nitro-sqlite/details.md @@ -0,0 +1,17 @@ +# `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 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 new file mode 100644 index 000000000000..0dadc3344a4a --- /dev/null +++ b/patches/react-native-nitro-sqlite/react-native-nitro-sqlite+9.6.0+001+dont-mask-original-error-on-rollback-failure.patch @@ -0,0 +1,30 @@ +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; + } + }