OpSQLiteDriver: executeAsync result format not handled by extractRowsFromStatementResult, causing silent data loss on SELECT queries
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 72/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Tranquilla
- Stack tecnologico
- react-native, typescript
- Ambito
- database, mobile-dev
Direzione di ricerca
Individua extractRowsFromStatementResult e resolveExecuteMethod nel pacchetto di persistenza React Native SQLite, quindi esamina come vengono normalizzati i risultati degli statement. Riproduci lo scenario di riavvio con op-sqlite 14.1.4 e verifica che i risultati di SELECT conservino le relative righe e che l'avvio della collection non raggiunga più un errore di vincolo UNIQUE.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
- I've validated the bug against the latest version of DB packages
Describe the bug
OpSQLiteDriver in @tanstack/react-native-db-sqlite-persistence silently returns empty arrays for SELECT queries when op-sqlite's executeAsync method is used. This causes the persistence layer to think collections don't exist in collection_registry on app restart, leading to UNIQUE constraint violations.
Root cause
resolveExecuteMethod picks the first available method from [executeAsync, execute, executeRaw, execAsync]. On op-sqlite v14, executeAsync is available and gets selected.
The problem is that op-sqlite's executeAsync returns a different result format than execute:
executereturns:{ rows: Array<Record<string, Scalar>> }(object rows)executeAsyncreturns:{ rowsAffected: number, rawRows: unknown[][], columnNames: string[] }(raw columnar format)
extractRowsFromStatementResult only handles the { rows } and { resultRows } shapes. When it receives an executeAsync result:
toRowArray(value.rows)->value.rowsis undefined -> returns nulltoRowArray(value.resultRows)->value.resultRowsis undefined -> returns nullhasWriteResultMarker(value)->"rowsAffected" in value-> true -> returns[]
The SELECT result is silently treated as a write result with zero rows, even though the data is present in rawRows.
Impact
This causes a cascade of failures on app restart when the database already has data:
ensureCollectionReadyInternalSELECTs fromcollection_registry-> gets[]instead of the existing row- Code takes the INSERT branch -> fails with
UNIQUE constraint failed: collection_registry.tombstone_table_name getStreamPositionrejects -> the persistence runtime'sensureStartupMetadataLoadedfails- The Electric sync function is never called -> the collection stays in
loadingstate forever with no data
The ALTER TABLE ADD COLUMN errors in ensureInitialized are a separate but related symptom — executeAsync is used for those DDL statements too, and the existing error handling (isDuplicateColumnAddError) works but still logs errors.
To Reproduce
- Create a collection with
persistedCollectionOptionsandcreateReactNativeSQLitePersistenceusing op-sqlite v14+ - Let it sync data from an Electric shape
- Kill the app (full process kill, not hot reload)
- Relaunch the app
- The persistence layer crashes on startup — the collection never reaches
readystatus
Expected behavior
extractRowsFromStatementResult should handle the { rawRows, columnNames } format returned by executeAsync, converting it into the expected Array<Record<string, unknown>> shape. Alternatively, resolveExecuteMethod should prefer execute over executeAsync.
Smartphone (please complete the following information):
- Device: iOS Simulator & physical Android device
- OS: iOS 18, Android 14
- Version: N/A (React Native app, not browser)
Additional context
Package versions:
@tanstack/db-sqlite-persistence-core: 0.1.9@tanstack/react-native-db-sqlite-persistence: 0.1.9@op-engineering/op-sqlite: 14.1.4- React Native (Expo)
Current workaround:
Remove executeAsync from the database handle before passing it to createReactNativeSQLitePersistence, forcing the driver to fall back to execute:
const database = open({ name: 'my-db.sqlite', location: 'default' });
delete (database as any).executeAsync;
const persistence = createReactNativeSQLitePersistence({ database });
Suggested fix:
Either:
- Add
rawRows+columnNameshandling toextractRowsFromStatementResult:
function extractRowsFromStatementResult(value) {
// Handle op-sqlite executeAsync format: { rawRows, columnNames }
if (Array.isArray(value.rawRows) && Array.isArray(value.columnNames)) {
return value.rawRows.map((row) =>
Object.fromEntries(value.columnNames.map((col, i) => [col, row[i]]))
);
}
// ... existing logic
}
- Or change
resolveExecuteMethodto preferexecuteoverexecuteAsync, sinceexecutereturns the{ rows }format the driver already handles.
- Lingua principale
- TypeScript
- Stelle
- 3.9k
- Fork
- 272
- Merge medio
- 1g 1h
- PR unite (30g)
- 212
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TanStack/db
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
electric-db-collection: on-demand + persistence fails after relaunch with "Snapshot requests are not supported in full mode"Forse già presa @KyleAMathews l’ha presa 3 giorni fa. Aperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
TanStack/db#2056 · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
TanStack/db#1972 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 45/100
I maintainer di solito rispondono entro 1 giorno
Issue simili
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 72/100
supadata-ai/mcp#27 ·
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
capricorn86/happy-dom#2485 ·
I maintainer di solito rispondono entro 2 giorni
-
优化导入 OCR 模型选择文件的按钮样式Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
siyuan-note/siyuan#20430 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
Albert-Weasker/niubigeo#194 ·
I maintainer di solito rispondono entro 1 giorno