Skip to content

fix(server): predicate floor parity — ne/like divergence (ADR-0013)#17

Merged
grrowl merged 1 commit into
mainfrom
fix/003-predicate-parity
Jun 13, 2026
Merged

fix(server): predicate floor parity — ne/like divergence (ADR-0013)#17
grrowl merged 1 commit into
mainfrom
fix/003-predicate-parity

Conversation

@grrowl

@grrowl grrowl commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Resolves the plan-003 predicate divergence: a filtered subscription's membership was decided by two evaluators that disagreed — the SQL snapshot (sql-compiler) vs the JS delta/catch-up path (@tanstack/db's compileSingleRowExpression). Two clients could see different rows for the same where by connection timing.

What was wrong

  • ne crashed + hung the client. SQL floor accepted ne (→ !=), but @tanstack/db has no ne (not-equal is not(eq(...))). Its compile error escaped handleSub past the UnsupportedPredicateError catch → no reset → indefinite hang.
  • like diverged by case. SQLite LIKE is ASCII case-insensitive; @tanstack/db's like is case-sensitive. "HELLO" matched like "hello%" in the snapshot but not in deltas.

Fix (ADR-0013)

@tanstack/db's evaluator is the source of truth (it already decides delta, catch-up, and client-preflight membership). The SQL snapshot must reproduce it.

  • Drop ne from the floor → rejected with reset. Use not(eq(...)).
  • compilePredicate rethrows any @tanstack/db compile failure as UnsupportedPredicateError, caught around subs.addreset (defensive fail-loud for any future floor skew).
  • PRAGMA case_sensitive_like = ON in the DO constructor (connection-scoped; re-applied on every wake; the compiler is the only LIKE producer).
  • Restrict the like pattern to a string literal (evaluateLike returns false for non-strings; SQL would coerce).

Floor is now eq, gt, gte, lt, lte, like, in, and, or, not — exactly the verified-agreeing set.

Tests

  • tests/predicate-parity.test.ts — 6 cases, snapshot and delta paths, asserting agreement (was it.fails characterizations; now passing assertions).
  • tests/subscriptions.test.ts — JS-floor guard at the unit level.
  • tests/sql-compiler.test.tsne rejected, not(eq) lowering, non-string like pattern rejected.

171/171 green locally, typecheck clean. Reviewed adversarially by codex (gpt-5.5) — the non-string-like hole it surfaced is fixed here.

Targets 0.3.3. Supersedes the parked characterization PR #6 (against feat/ssr).

🤖 Generated with Claude Code

…luators (ADR-0013)

A filtered subscription's membership was decided by two evaluators that
disagreed: the SQL snapshot (sql-compiler) and the JS delta/catch-up path
(@tanstack/db's compileSingleRowExpression). Two clients could see different
rows for the same `where` depending on connection timing.

- ne: the SQL floor accepted `ne` but @tanstack/db has no `ne` (not-equal is
  `not(eq(...))`). Its compile error escaped handleSub past the
  UnsupportedPredicateError catch (which only wraps compileSubsetQuery), so no
  `reset` was sent and the client hung forever. Drop `ne` from the floor →
  rejected with `reset`; and make compilePredicate rethrow any @tanstack/db
  compile failure as UnsupportedPredicateError, caught around subs.add → reset
  (defensive: fail loud, never a hang, for any future floor skew).

- like: SQLite LIKE is ASCII case-insensitive; @tanstack/db's `like` is
  case-sensitive. Set PRAGMA case_sensitive_like = ON in the DO constructor so
  the snapshot matches the delta path on every wake (connection-scoped; the
  IR→SQL compiler is the only LIKE producer). Also restrict the `like` pattern
  to a string literal — evaluateLike returns false for non-strings while SQL
  would coerce, so a numeric pattern desynced the paths.

Floor is now eq, gt, gte, lt, lte, like, in, and, or, not — exactly the set the
two evaluators agree on row-for-row. Pinned by tests/predicate-parity.test.ts
(6 cases, both paths), tests/subscriptions.test.ts (JS-floor guard), and the
sql-compiler ne/like-pattern rejections.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@grrowl grrowl merged commit f2b9b0d into main Jun 13, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant