Differential watched queries ignore pure reorders (same rows, new ORDER BY) so stale order is served

Aperta Adatta ai principianti
#1,156 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à
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
83/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
sql, typescript

Direzione di ricerca

Inizia in packages/shared-internals/src/client/watched/DifferentialQueryProcessor.ts e segui come differentiate() imposta hasChanged durante la creazione di diff.all. Usa la riproduzione nell’issue per confermare che invertire le stesse righe attualmente non segnala alcuna modifica, quindi aggiungi un test di regressione per questo caso. Il lavoro è completato quando un semplice riordino segnala una modifica ed emette le righe nel loro nuovo ordine.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

DifferentialQueryProcessor.differentiate() reports hasChanged: false when a query returns the same rows in a different order (e.g. the user switches ORDER BY, or a watched query's sort parameter changes while the row set and row contents stay identical). Because nothing is emitted, useQuery / differential watched queries keep serving the previous order indefinitely, until some row is added, removed, or updated.

diff.all is already built in the new order (the code comments say looping over the current result set "is important to preserve the ordering of the result set"), but it is never emitted because hasChanged only considers added / removed / updated rows.

Versions

  • @powersync/shared-internals 1.3.1 and 1.4.0 (latest, pulled in by @powersync/react-native 2.3.3); same code on main in packages/shared-internals/src/client/watched/DifferentialQueryProcessor.ts
  • Seen in practice with @powersync/react-native 2.3.1 + useQuery with a rowComparator (differential mode)

Minimal reproduction

import { DifferentialQueryProcessor } from '@powersync/shared-internals';

const self = { comparator: { keyBy: (r) => r.id, compareBy: (r) => JSON.stringify(r) } };
const first = DifferentialQueryProcessor.prototype.differentiate.call(self, [{ id: 'a' }, { id: 'b' }], new Map());
const reordered = DifferentialQueryProcessor.prototype.differentiate.call(self, [{ id: 'b' }, { id: 'a' }], first.map);

console.log(reordered.hasChanged);               // false  <- expected true
console.log(reordered.diff.all.map((r) => r.id)); // ['b', 'a'] (correct order, but never emitted)

App-level symptom: a list rendered from a differential useQuery whose SQL ORDER BY changes (same filter, same rows) keeps showing the old order.

Expected

A pure reorder should count as a change, so the new diff.all (in the new order) is emitted to onDiff / data.

Suggested fix

previousMap is a Map, so it already preserves the previous result's insertion order. Comparing keys positionally while iterating is O(n) with no extra allocation:

         const removedTracker = new Set(previousMap.keys());
+        // previousMap keeps the previous result's row order. A pure reorder (same rows,
+        // new ORDER BY) must count as a change, or the old order is served forever.
+        const previousKeys = previousMap.keys();
 ...
             const previousItem = previousMap.get(key);
+            if (previousKeys.next().value !== key) {
+                hasChanged = true;
+            }
             if (!previousItem) {

We have been running this as a local patch with a regression test (rows [a, b] -> [b, a] must emit). Happy to open a PR if this approach looks right. One open question for maintainers: whether a reorder should also be surfaced somehow in the diff object (today it would show up only as all rows unchanged with a different all order).

Lingua principale
TypeScript
Stelle
729
Fork
96
Merge medio
3g 11h
PR unite (30g)
34

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

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 powersync-ja/powersync-js

Tutte le issue di powersync-ja/powersync-js

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.