fix(server): predicate floor parity — ne/like divergence (ADR-0013)#17
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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'scompileSingleRowExpression). Two clients could see different rows for the samewhereby connection timing.What was wrong
necrashed + hung the client. SQL floor acceptedne(→!=), but@tanstack/dbhas none(not-equal isnot(eq(...))). Its compile error escapedhandleSubpast theUnsupportedPredicateErrorcatch → noreset→ indefinite hang.likediverged by case. SQLiteLIKEis ASCII case-insensitive;@tanstack/db'slikeis case-sensitive."HELLO"matchedlike "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.nefrom the floor → rejected withreset. Usenot(eq(...)).compilePredicaterethrows any@tanstack/dbcompile failure asUnsupportedPredicateError, caught aroundsubs.add→reset(defensive fail-loud for any future floor skew).PRAGMA case_sensitive_like = ONin the DO constructor (connection-scoped; re-applied on every wake; the compiler is the onlyLIKEproducer).likepattern to a string literal (evaluateLikereturns 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 (wasit.failscharacterizations; now passing assertions).tests/subscriptions.test.ts— JS-floor guard at the unit level.tests/sql-compiler.test.ts—nerejected,not(eq)lowering, non-stringlikepattern rejected.171/171 green locally, typecheck clean. Reviewed adversarially by codex (gpt-5.5) — the non-string-
likehole it surfaced is fixed here.Targets
0.3.3. Supersedes the parked characterization PR #6 (againstfeat/ssr).🤖 Generated with Claude Code