Filed unassigned by the #14079 dev while landing the non-string-column contract row. Recording only — no severity asserted, routing is triage's. Dedup: one targeted MCP search_issues (2026-09-05) found #6518 (closed — the DRIVER family's fix) and nothing on the analytics compilers.
Measured
service-analytics' three SQL compilers emit a plain LIKE for the case-SENSITIVE $contains family, so on SQLite — where LIKE folds ASCII case unconditionally — they answer rows the #4706 Q2 = A contract excludes:
| compiler |
{ name: { $contains: 'acme' } } over FILTER_TEXT_ROWS on sql.js |
contract (FILTER_TEXT_CASES) |
NativeSQLStrategy.buildFilterClause (the query's own where) |
['1', '2'] — ACME Corp AND acme corp |
['2'] |
compileScopedFilterToSql (read-scope-sql.ts, the RLS read scope) |
same shape: "t"."name" LIKE ? ESCAPE ? |
same |
ObjectQLStrategy SQL echo |
prints the same LIKE |
same |
Measured on 2026-09-05 through NativeSQLStrategy.generateSql executed on sql.js over the shared fixture (the #14079 suite text-operator-non-text-column.test.ts had to steer its control comparand away from the case axis because of it). $icontains is unaffected — those arms fold both sides with asciiLowerSqlExpr. On Postgres LIKE is case-exact, so the drift is per dialect: one analytics query, one read scope, two row sets chosen by the datasource.
Why it matters more on the read scope
#6518 moved the DRIVER family onto GLOB on the SQLite dialects (and CAST(… AS BINARY) on MySQL) for exactly this reason, and its header records the security reading: a read scope that ADMITS rows the policy's case-sensitive predicate excludes is over-reach (#3948), not a loose filter. read-scope-sql.ts states the same #3948 reading for its own LIKE escaping (#5567) but its LIKE keyword never got the #6518 treatment, and this package depends on no driver, so it cannot borrow textMatchPredicate.
What is NOT here
Escaping (% / _, #5567) is correct on all three compilers; the fold of $icontains (#6520) is correct on all three. Only the case-EXACTNESS of $contains / $notContains / $startsWith / $endsWith is dialect-dependent, and only on SQLite.
Related
#6518 (the driver family's GLOB fix — the same defect one layer up) · #4706 (Q2 = A: the $contains family is case-sensitive) · #5567 (the analytics compilers' escaping, where the #3948 reading is already written) · #14079
Generated by Claude Code
Filed unassigned by the #14079 dev while landing the non-string-column contract row. Recording only — no severity asserted, routing is triage's. Dedup: one targeted MCP
search_issues(2026-09-05) found #6518 (closed — the DRIVER family's fix) and nothing on the analytics compilers.Measured
service-analytics' three SQL compilers emit a plainLIKEfor the case-SENSITIVE$containsfamily, so on SQLite — whereLIKEfolds ASCII case unconditionally — they answer rows the #4706 Q2 = A contract excludes:{ name: { $contains: 'acme' } }overFILTER_TEXT_ROWSon sql.jsFILTER_TEXT_CASES)NativeSQLStrategy.buildFilterClause(the query's ownwhere)['1', '2']—ACME CorpANDacme corp['2']compileScopedFilterToSql(read-scope-sql.ts, the RLS read scope)"t"."name" LIKE ? ESCAPE ?ObjectQLStrategySQL echoLIKEMeasured on 2026-09-05 through
NativeSQLStrategy.generateSqlexecuted on sql.js over the shared fixture (the#14079suitetext-operator-non-text-column.test.tshad to steer its control comparand away from the case axis because of it).$icontainsis unaffected — those arms fold both sides withasciiLowerSqlExpr. On PostgresLIKEis case-exact, so the drift is per dialect: one analytics query, one read scope, two row sets chosen by the datasource.Why it matters more on the read scope
#6518 moved the DRIVER family onto
GLOBon the SQLite dialects (andCAST(… AS BINARY)on MySQL) for exactly this reason, and its header records the security reading: a read scope that ADMITS rows the policy's case-sensitive predicate excludes is over-reach (#3948), not a loose filter.read-scope-sql.tsstates the same #3948 reading for its own LIKE escaping (#5567) but itsLIKEkeyword never got the #6518 treatment, and this package depends on no driver, so it cannot borrowtextMatchPredicate.What is NOT here
Escaping (
%/_, #5567) is correct on all three compilers; the fold of$icontains(#6520) is correct on all three. Only the case-EXACTNESS of$contains/$notContains/$startsWith/$endsWithis dialect-dependent, and only on SQLite.Related
#6518 (the driver family's
GLOBfix — the same defect one layer up) · #4706 (Q2 = A: the$containsfamily is case-sensitive) · #5567 (the analytics compilers' escaping, where the #3948 reading is already written) · #14079Generated by Claude Code