Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

OpSQLiteDriver: executeAsync result format not handled by extractRowsFromStatementResult, causing silent data loss on SELECT queries

Chiusa
#1,499 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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

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:

  • execute returns: { rows: Array<Record<string, Scalar>> } (object rows)
  • executeAsync returns: { rowsAffected: number, rawRows: unknown[][], columnNames: string[] } (raw columnar format)

extractRowsFromStatementResult only handles the { rows } and { resultRows } shapes. When it receives an executeAsync result:

  1. toRowArray(value.rows) -> value.rows is undefined -> returns null
  2. toRowArray(value.resultRows) -> value.resultRows is undefined -> returns null
  3. hasWriteResultMarker(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:

  1. ensureCollectionReadyInternal SELECTs from collection_registry -> gets [] instead of the existing row
  2. Code takes the INSERT branch -> fails with UNIQUE constraint failed: collection_registry.tombstone_table_name
  3. getStreamPosition rejects -> the persistence runtime's ensureStartupMetadataLoaded fails
  4. The Electric sync function is never called -> the collection stays in loading state 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

  1. Create a collection with persistedCollectionOptions and createReactNativeSQLitePersistence using op-sqlite v14+
  2. Let it sync data from an Electric shape
  3. Kill the app (full process kill, not hot reload)
  4. Relaunch the app
  5. The persistence layer crashes on startup — the collection never reaches ready status

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:

  1. Add rawRows + columnNames handling to extractRowsFromStatementResult:
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
}
  1. Or change resolveExecuteMethod to prefer execute over executeAsync, since execute returns 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

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di TanStack/db

Tutte le issue di TanStack/db

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.