Repository navigation
driver-sql 的 LIKE 转义(自标 P0 的过滤器旁路)只在 SQLite 上验证过 —— live PG + MySQL 矩阵已存在、testkit 也已存在,LIKE 族一个用例都没接进去 #5589
Description
做 #5567(analytics 三个 SQL 编译器不转义 LIKE 比较值)时,为了给 PR 里的「方言确认」找一个能站住的断言面,去查 LIKE … ESCAPE 在本仓到底被哪些引擎跑过。发现的覆盖缺口,按 Prime Directive #10 单独记在这里,unassigned。
不属于 #5567 的范围面:那一单裁的是 analytics 三个编译器自己不转义(已修,PR #5587);本条裁的是 driver-sql 那份正确实现的验证面只覆盖三个已发布方言中的一个。两者都不是「逻辑错」——本条目前没有已知的错误行为,所以是 observation-class。
事实
driver-sql 的 applyLike(packages/plugins/driver-sql/src/sql-driver.ts ~:6276)转义 \ / % / _ 并绑显式 ESCAPE ?,它头上的注释自己把未转义的 % 标为 P0 过滤器旁路:
`%` matches every row (a filter-bypass, P0). Binds an explicit `ESCAPE '\'`
它的回归守卫是 packages/plugins/driver-sql/src/sql-driver-like-escape.test.ts(文件头写着 "P0-3 regression")。该文件硬编码单一方言:
driver = new SqlDriver({
client: 'better-sqlite3',
connection: { filename: ':memory:' },
useNullAsDefault: true,
});而 CI 里已经有 live Postgres 16 + MySQL 8.0 的服务容器作业(.github/workflows/ci.yml,Temporal Conformance (live PG + MySQL)),它注入 OS_TEST_POSTGRES_URL / OS_TEST_MYSQL_URL,并且跑的就是整个 driver-sql 套件:
pnpm --filter @objectstack/driver-sql test
也就是说这个守卫在那个作业里也跑了,只是因为自己写死了 client,仍然只在 SQLite 上跑一遍。
实测:driver-sql 里读那两个 URL 的测试文件共 8 个,其中提及 $contains / LIKE / startsWith / endsWith 的有 0 个:
$ grep -rln "OS_TEST_POSTGRES_URL\|OS_TEST_MYSQL_URL" packages/plugins/driver-sql/src/
sql-driver-temporal-conformance.test.ts
live-dialect-matrix.testkit.ts
sql-driver-or-filter.test.ts
sql-driver-pagination-conformance.test.ts
sql-driver-date-now-default-live.test.ts
sql-driver-datetime-postgres-timezone.test.ts
sql-driver-datetime-mysql-storage.test.ts
sql-driver-time-live-dialects.test.ts
→ 其中含 LIKE 族的:0 个
注意 live-dialect-matrix.testkit.ts 已经是一个可复用的方言矩阵 testkit(ADR-0053 D-A3 那条轴用的),所以缺的不是基础设施,只是没人把 LIKE 族接上去。
为什么值得记一笔
- 方言差异在这条路径上是真实的,不是理论的。 三家默认转义符不一致 —— PG 默认反斜杠、MySQL 默认
\(除非NO_BACKSLASH_ESCAPES)、SQLite 没有默认转义符。applyLike的注释正是靠这一点解释它为什么必须绑显式ESCAPE。恰恰是「唯一没有默认转义符」的那一个,成了唯一被跑到的那一个:守卫覆盖的是差异最小的一端。 - MySQL 那一侧多一条文档约束没人验证过。 MySQL 手册说
ESCAPE的实参 "must evaluate as a constant at execution time"。applyLike绑的是占位符ESCAPE ?;经 knex + mysql2 的默认query()路径它会被客户端插值成字面量(因此满足该条件),但这是推断,不是实测 —— 而且它依赖 mysql2 走query()而不是 server-side preparedexecute()。若哪天该路径改成真正的服务端预处理语句,这条约束是否仍满足,没有任何用例会告诉我们。 - 同一个 P0 的另一半刚刚才被发现过一次。 analytics 侧三个 SQL 编译器都不转义 LIKE 比较值:
$contains: '_admin'命中xyadmin、$contains: '50%'命中off 5012 now—— driver-sql 自己把这条旁路标为 P0 —— 实测 #5567 证明这族 pattern 拼接的缺陷会在同一个仓里被重新犯一遍(analytics 三处,全部漏)。一个只覆盖 1/3 方言的守卫,对「换个方言重新犯一遍」是看不见的。 - 不是「declared ≠ enforced」的代码面,而是它的测试面。 实现是对的;被断言的只是它在一个引擎上是对的。
建议(不代裁决)
把 sql-driver-like-escape.test.ts 的三个用例(字面 % / 字面 _ / 普通子串)接到已有的 live-dialect-matrix.testkit.ts 上,再补一条字面 \ 的用例 —— 后者是三方言分歧最大的一格(SQLite 上 \ 本就是字面量,PG/MySQL 上它是默认转义符),也是 #5587 里唯一只能靠文档 + 模拟论证、无法在 SQLite 上做前红后绿的一格。URL 缺失时按现有惯例 skip,OS_EXPECT_LIVE_DIALECT_MATRIX 已经负责把「本该跑却 skip」变成命名的红。
未验证的部分
- 没有在真实 PG / MySQL 上跑过任何 LIKE 用例(本条正是这个缺口本身),所以不声称那两个方言上今天有错误行为;文档口径与 SQLite 实测都指向
applyLike是对的。 - 没有核实 knex + mysql2 在本仓配置下是否可能走服务端预处理路径(上面第 2 点的前提)。
- 严重度请 PM 按 triage 定:按「今天没有用户会撞到」记为 observation-class(
finding,不入pm:queue),但它守的是一条被自己标为 P0 的旁路,所以两个方向都不敢自判 —— 只把实测摆在这里。
关联:#5567 / PR #5587(analytics 侧同族缺陷,本条在其「方言确认」环节被发现)、#4191(ADR-0053 D-A3 storage-form 轴的同类覆盖缺口 —— 不同范围面,那条是 Field.datetime / Field.time 的存储形态,不含 LIKE,故本条独立立单而非挂为其子单)、#5499(冻结裁决明确 driver-sql 不在冻结范围)。
Activity
os-zhuang commentedon Aug 5, 2026
发现分诊(#4949 纪律,上轮漏判的 19:56Z 新增,本轮补判):晋级
pm:queue+pm:blocked(域已在domain:drivers,摘finding)。packages/plugins/driver-*平移到packages/drivers/*;迁移窗口内向旧路径新增测试文件必撞,落地后在新路径接线)。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
解锁 + 认领:PM 循环第 5 轮(drivers 车道)
解锁依据:分诊的 Blocked-by 条件为「#4645 Phase A 目录迁移落地,在新路径接线」— PR #5618 已合入 main(merge commit
06ba03627),driver-sql 现居packages/drivers/driver-sql/,pm:blocked已摘。会话:
session_01WyvqvKMG6asi9aXjKE6xtx分支:
claude/issue-5589-like-escape-live-matrixWorktree:
objectstack-issue-5589域:
domain:drivers文件面:
packages/drivers/driver-sql/src/(sql-driver-like-escape.test.ts接入live-dialect-matrix.testkit.ts+ 补字面\用例;test-only)。与同批在飞的 #5590(driver-turso)、#4645 Phase B(cloud 仓)互不相交。Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
复核:ACCEPT → PR #5655 已 MERGED(合并队列)。留档要点:
sql-driver-like-escape.test.ts (18 tests)= 本地 10 + live PG/MySQL 两 cell 各 5(-2 skip)—— issue「未验证」第一条(MySQLESCAPE实参常量约束)由推断变实测通过;\\转义支 → 恰好新用例红);预设注释值与实测不符处按实测改写;\无法在 SQLite 前红后绿」被证伪并注记进代码注释(显式ESCAPE使该格算术进程内可判,live cell 独占的是传输层);OS_EXPECT_LIVE_DIALECT_MATRIX=1缺 URL 转具名红已双态验证。P0 过滤器旁路守卫的方言盲区(2/3)就此补上。
Generated by Claude Code
test(driver-sql): LIKE-escape guard joins the live PG + MySQL dialect…