Commit dcad825
fix(service-analytics): compile the $icontains ASCII fold per dialect — translate() is not a SQLite function (#16020)
* fix(service-analytics): compile the $icontains ASCII fold per dialect — translate() is not a SQLite function
All three of this package's SQL compilers spelled the #6520 fold as
`translate(col, 'ABC…', 'abc…')` on EVERY dialect. `translate()` is
PostgreSQL/Oracle; SQLite has none, so on a SQLite datasource an analytics
`where` carrying `$icontains` — and an ADR-0021 D-C read scope carrying it —
compiled a statement the engine refuses to parse. Measured on sql.js 1.14.1
(SQLite 3.49.1, the engine driver-sqlite-wasm runs): `SELECT
translate('ABC','ABC','abc')` answers `no such function: translate`.
`$icontains` now goes through `text-match-sql.ts`'s per-dialect construct table
with one `fold` flag, set on that operator alone:
- sqlite → `lower(col) GLOB lower(?)`, ASCII-only there (`lower('CAFÉ')` is
`cafÉ`), which is the #4706 Q1 = A boundary rather than an
approximation of it.
- postgres → `translate()`, byte-identical to before. Never broken.
- unknown → `translate()`, byte-identical to before. The residue keeps the
shape it had; note this diverges from driver-sql, whose unknown
arm folds with LOWER(), and neither face claims the other's.
- mysql → the nested-REPLACE fold over CAST(… AS BINARY), matching
driver-sql. TEXT ONLY — no MySQL server is provisionable here.
The `sql` keyword field on `ObjectQLStrategy`'s LIKE_SQL_OPS lost its last
reader in this move and is removed: a dead field named `sql` beside a compiler
invites exactly the misreading this defect was.
#15684's `$icontains` control asserted "the fold arm still emits translate() on
every dialect". That was a PROXY for the property it protected — the two text
families must not collapse onto one path — and this change makes the fold
dialect-DEPENDENT by design, so the proxy no longer states the property. It is
re-aimed rather than deleted or loosened: `$icontains` and `$contains` must now
compile to DIFFERENT text on each dialect, and `$contains` must carry no fold in
any of its three spellings. That discriminates against the collapse in both
directions where dialect-invariance discriminated against one.
Fixes #15780
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
* docs(service-analytics): correct the $icontains changeset — state the unknown-is-SQLite carve-out and name the measured set
Round 2 of the Clause-② contract review on PR #16020. Text only: no file
under packages/ moves, and the review found no code defect.
The changeset said the `unknown` arm was "never broken". That is false for
an `unknown` that is really SQLite — the review drove four off-repo
constructions that land there, and for each of them `translate()` still
reaches the engine and still fails to parse. The carve-out is now stated
here word for word with the PR body, and tracked as #16028.
"byte-identical" and "every dialect" were unqualified here while the PR
body qualified them; both now name the same measured sets (the six verbatim
in-suite cells widened to the review's 2,721-cell construction with 0
deltas, and the 510-cell family-separation set).
The MySQL arm is byte-equal to driver-sql's `textMatchPredicate` on 60/60
cells but was executed nowhere — text-only on both faces, said plainly
rather than left under "measured". The deliberate divergence from
driver-sql's `unknown` arm is now recorded in the changeset too.
No card-relation trailer here on purpose: this branch squashes, and the
PR body declares the relation once.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
* docs(service-analytics): retract the "more tightly than the proxy" clause from the case-exactness suite header
The round-1 contract review measured that clause false: FOLD_PER_DIALECT
inspects the COLUMN side only, so a one-sided column fold on the postgres /
unknown arm leaves this file green (exit 0, 14 passed) where the pre-fix
`LIKE translate($1,` pin caught it. The PR body and the changeset were
corrected in round 2; this source header was not, and a PR body cannot reach
someone who opens this suite six months from now.
The header now carries the two halves the PR body already states: tighter on
family separation (the four collapse mutations the review drove all went red
here), looser on both-sides folding, plus the cross-reference that
icontains-dialect-sql.test.ts holds the verbatim postgres / unknown byte pin
for that second case. Concluded as not net-weakened across the two files,
never as a strict tightening.
Comment-only, proven two ways with firing controls: every added and removed
diff line is a comment line (28 added, 8 removed, 0 non-comment; four real
code lines fed to the same filter classify as CODE), and the file transpiled
with removeComments is byte-identical to its 711db06 blob (sha256/16
941867ad0f3e45a7, 14888 bytes on both sides, while injecting one code line in
memory moves that hash). The other seven files this PR touches are untouched:
git hash-object equals the 711db06 blob for each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 1157e7b commit dcad825
8 files changed
Lines changed: 668 additions & 138 deletions
File tree
- .changeset
- packages/services/service-analytics/src
- __tests__
- strategies
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
0 commit comments