Skip to content

drivers(sql family): 文本算子的大小写折叠是「方言的」而非「契约的」—— $contains 在 SQLite 过折叠、$icontains 在 PG/MySQL 过折叠 #6518

Description

@os-zhuang

Part of #4706(裁决 Q1=A / Q2=A)。从 #5702 实施期实测拆出 —— #5702 交付了 $regex 拒收与 $icontains 实现,没有交付这一条,原因见下「为什么不能在 #5702 里顺手做」。

两个方向的同一个缺陷

SqlDriver.applyLike 发的 SQL 里,大小写由方言决定,不由契约决定。两个算子各在一半方言上跑偏:

算子 契约(#4706) SQLite / turso / sqlite-wasm Postgres MySQL
$contains / $notContains / $startsWith / $endsWith 大小写敏感(Q2=A) LIKE 自带 ASCII 折叠 ✅ 恰好正确 ⚠️ 随 collation
$icontains 折叠 ASCII only(Q1=A) ✅ SQLite lower() 只折 ASCII LOWER() 折全 Unicode ❌ 同左

两格都是过匹配(返回了不该返回的行),不是漏匹配。

实测(better-sqlite3,FILTER_TEXT_ROWS 九行,origin/main + PR of #5702)

$contains  'acme'  → ["1","2"]     契约要求 ["2"]      (行 1 = "ACME Corp")
$contains  'ACME'  → ["1","2"]     契约要求 ["1"]
$icontains 'acme'  → ["1","2"]     ✅
$icontains 'café'  → ["4"]         ✅(É 不折)

顺带一条对分诊有用的读数:在 SQLite 上 $contains$icontains 对任意比较数返回完全相同的行 —— 因为 LIKE 已经折了 ASCII,外面再套一层 LOWER() 是幂等的。也就是说这条不修,$icontains 在 SQLite 上是行为不可观测的(它的正确性只能靠编译出的 SQL 文本和 PG 见证)。#5702remote-transport-text-predicates.test.ts / sql-driver-icontains-and-retired-operators.test.ts 已把这两条如实钉住。

为什么不能在 #5702 里顺手做(这是本单独立的理由)

$contains 的大小写需要在 SQLite 上换构造(GLOB / instr() / 二进制 collation —— LIKE 不受 collation 影响,只能靠 PRAGMA case_sensitive_like,而那是连接级全局开关)。这个下译在三处并存,必须同批移动:

  1. packages/drivers/driver-sql/src/sql-driver.tsapplyLike;
  2. packages/drivers/driver-turso/src/remote-transport.tspushLike(remote 不走 knex,自带一份);
  3. RLS / 分析面的孪生实现 —— read-scope-sqlpackages/services/service-analytics/src/like-pattern.ts(后者的注释自陈「held to THIS expression, character for character」)。

只改 driver 不改第 3 组,等于一条权限规则编出两个行集 —— #3948 那一类 P0,比现状更糟。所以它不是 domain:drivers 单车道能闭环的改动。

验收

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions