Skip to content

test(driver-sql): LIKE-escape 守卫接入 live PG + MySQL 方言矩阵,补字面反斜杠用例 (#5589) - #5655

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5589-like-escape-live-matrix
Aug 5, 2026
Merged

test(driver-sql): LIKE-escape 守卫接入 live PG + MySQL 方言矩阵,补字面反斜杠用例 (#5589)#5655
os-zhuang merged 1 commit into
mainfrom
claude/issue-5589-like-escape-live-matrix

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5589

Test-only。无任何运行时改动,sql-driver.ts 一个字节都没动。

前提复核(issue 是线索不是规格)

issue 写于 #4645 Phase A 之前,引用的是旧路径。基于当前 origin/main7bf3d1ce2)重核,结论:前提全部成立,只有路径与行号偏移

issue 的说法 复核结果
applyLikepackages/plugins/driver-sql/src/sql-driver.ts ~:6276 现居 packages/drivers/driver-sql/src/sql-driver.ts:6292(Phase A 已 MERGED,06ba03627);注释里的 "a filter-bypass, P0" 与绑定的 ESCAPE ? 与描述一致
守卫 sql-driver-like-escape.test.ts 硬编码单一方言 成立:client: 'better-sqlite3', connection: { filename: ':memory:' }
读两个 live URL 的测试文件共 8 个 成立,逐字符相同的 8 个文件(含 testkit 本身)
其中提及 $contains / LIKE / startsWith / endsWith 的:0 个 成立(grep 覆盖这四个词,命中数 0)
CI 的 Temporal Conformance (live PG + MySQL) 已跑整个 driver-sql 套件并注入两个 URL + OS_EXPECT_LIVE_DIALECT_MATRIX=1 成立(.github/workflows/ci.yml:378 起;测试步骤跑 pnpm --filter @objectstack/driver-sql test

所以缺的确实不是基础设施,只是没接线。不新增 CI 作业。

改了什么

单文件:packages/drivers/driver-sql/src/sql-driver-like-escape.test.ts

  1. 既有 SQLite 硬编码守卫原样保留(一行未改)。它是「任何机器、任何 pnpm test、不需要 provision 任何东西」路径上唯一必跑的那份;矩阵是它的扩展不是替代
  2. 新增方言轴:三个既有用例(字面 % / 字面 _ / 普通子串)按 DIALECT_CELLS 逐 cell 重跑,表名带 issue 前缀 os5589_like_escape(live cell 与本包其它套件共库,裸表名会把撞名读成过滤器旁路回归)。
  3. 补第四个用例:字面 \ —— 转义符自身。这是 MySQL 那两条约束从「推断」变成「实测」的地方:手册要求 ESCAPE 实参 "must evaluate as a constant at execution time",以及 MySQL 在字符串字面量里应用 C 转义语法(那一个反斜杠要挺过 mysql2 的客户端转义 + 服务端 lexer 才轮得到 LIKE 看见)。
  4. fixture 完整性断言前置:先断言 C:\logs 存进去是一个反斜杠。进库路上被吃掉的反斜杠会让 \ 用例因为跟 applyLike 毫无关系的原因变红;更糟的是被加倍的那种,会让一个已经停止转义的构建因为错误的理由变绿
  5. 未 provision 的 cell 走 testkit 既有的 declareUnprovisionedCell —— 具名 skip,在 OS_EXPECT_LIVE_DIALECT_MATRIX=1 下转具名红。新用例自动落在这个语义里。

D-B3 服务器时区轴不引入:这里没有任何时间量,要求非 UTC 服务器只会制造与 LIKE 无关的红。与 sql-driver-or-filter.test.ts / 分页矩阵同一判断。

反向验证:预测方向 = 实测方向,但 issue 的那句话被证伪

先定方向后跑:预测「删掉转义字符类里的 \\ 那一支 → 新的字面 \ 用例在 SQLite cell 上变红,其余三个保持绿」。

实测完全命中applyLike/[\\%_]/g 临时改成 /[%_]/g):

FAIL  SqlDriver LIKE-metacharacter escape (sqlite) > a "\" value matches only rows containing a literal backslash
AssertionError: ... expected [] to deeply equal [ 'bsl' ]
Tests  1 failed | 7 passed | 2 skipped (10)

两处需要如实纠正,都已写进代码注释而不是留给下一个读者去踩:

  • issue 说这一格「无法在 SQLite 上做前红后绿」—— 不成立。 因为 applyLike 绑的是显式 ESCAPE,三方言被放进同一条规则,所以这一格的算术在进程内就可判定,SQLite 能前红后绿。live cell 真正独占的不是算术,是传输:绑定参数经 mysql2 插值 / PG 协议之后,那一个反斜杠和 ESCAPE 实参是否还成立。注释里把这个区分写死了,免得把 SQLite cell 读成装饰品。
  • 收到的值是 [],不是我原先注释里写的 ['pct'] 因为 pattern 退化成 %\% 时,contains 形状结尾的那个通配符被反斜杠吃掉了,语义变成「任意内容 + 结尾一个字面 %」,本 fixture 无行命中。注释已按实测改写。

本地能证明什么、不能证明什么

⚠️ 本容器没有 live PG / MySQL。 本地已证明的:

结果
默认模式(无 URL) Tests 8 passed | 2 skipped (10) —— sqlite cell 4 个用例 + fixture 断言全绿,两个 live cell 具名 skip
OS_EXPECT_LIVE_DIALECT_MATRIX=1 且缺 URL Tests 2 failed | 8 passed —— 两个 live cell 转具名红,消息点名 OS_TEST_POSTGRES_URL / OS_TEST_MYSQL_URL 与 ADR-0053 D-A3
driver-sql 包全量 Test Files 63 passed | 4 skipped (67) / Tests 860 passed | 46 skipped (906)
pnpm --filter @objectstack/driver-sql typecheck 绿(tsc --noEmit 无输出)
eslint --no-inline-config 变更文件 绿
check:query-options-erasure test surface: 267 site(s) ... at the ceiling,未涨
check:nul-bytes(含 --self-test)+ 变更文件控制字节自扫 绿 / 无命中
check:adr-anchors 绿

两个 live 方言的真实执行只会发生在 CI 的 Temporal Conformance (live PG + MySQL) 作业里。该作业绿是本 PR 的合并前提。

若该作业在这两个新 cell 上变红,那个失败输出就是本单最有价值的产出(很可能坐实 issue 第 2 点的 MySQL ESCAPE 约束问题)—— 届时按 Prime Directive #10 另立单,⛔ 不在本 PR 里改实现

未验证

  • 未核实 knex + mysql2 在本仓配置下是否可能走服务端预处理 execute() 路径(issue 自己也把这条列为未验证)。本 PR 只让当前路径被执行,不对该路径的选择做任何断言。
  • $notContains / $startsWith / $endsWith 三个 shape 未接入矩阵:它们与 $contains 共用同一个 applyLike 转义表达式,shape 只决定 % 的位置。按 issue + 分诊的范围面(三个既有用例 + 一个 \)收口,不扩。

标签

test-only,请挂 skip-changeset(本 agent 无标签写权限,PM 代挂)。


Generated by Claude Code

… matrix, plus a literal-backslash case (#5589)

`SqlDriver.applyLike`'s own TSDoc grades an unescaped `%` a P0 filter bypass,
but its regression guard hard-coded `client: 'better-sqlite3'` — so the P0 was
verified on exactly one of the three shipped dialects, and on the end of the
range with the least to say (SQLite honours no default escape character at
all, which is why the explicit bound `ESCAPE` exists).

The matrix and the CI job were already here: `live-dialect-matrix.testkit.ts`
is the shared D-A3 cell list, and `Temporal Conformance (live PG + MySQL)`
already runs this whole package against Postgres 16 and MySQL 8.0. The LIKE
family was simply 0 of the 8 files reading `OS_TEST_POSTGRES_URL` /
`OS_TEST_MYSQL_URL`. This wires it in — no new CI job.

- the three existing cases (literal `%`, literal `_`, ordinary substring) now
  also run once per `DIALECT_CELLS` cell, over an issue-prefixed table;
- a fourth case pins the escape character itself (literal `\`), which is where
  MySQL's "ESCAPE must evaluate as a constant at execution time" and its
  string-literal backslash doubling stop being inferences and get executed;
- a fixture-integrity assertion first, so a backslash eaten on the way IN
  cannot be misread as an `applyLike` verdict;
- the hard-coded SQLite guard is KEPT: it is the one that runs on every
  `pnpm test` with nothing to provision. The matrix extends it, not replaces it.

An unprovisioned cell is a named skip and a red under
`OS_EXPECT_LIVE_DIALECT_MATRIX=1`, via the testkit's `declareUnprovisionedCell`.

Test-only; no runtime change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 10:08pm

Request Review

@github-actions github-actions Bot added the tests label Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

@github-actions github-actions Bot added the size/m label Aug 5, 2026
@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 5, 2026 — with Claude

Copy link
Copy Markdown
Contributor Author

CI 红旗说明(无需修复动作,#5562 同款):失败的 Check Changeset(22:08:39 起跑)来自 PR-opened 事件,载荷冻结于标签落上之前;标签事件触发的同名新 run(22:09:22)已 skipped(skip-changeset 豁免,test-only),最新结论取代该次失败。不重跑旧 run(冻结载荷再跑仍红)。本 PR 的关键合并前提是 Temporal Conformance (live PG + MySQL) 作业 — 两个新 live cell 首次真实执行,进行中。


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

合并前提已满足:live PG 16 + MySQL 8.0 作业绿,且新 cell 确实跑到了

Temporal Conformance (live PG + MySQL)run 31051590042 / job 92459805475conclusion: success(22:08:54Z → 22:11:17Z)。

关键证据不是「作业绿」,是用例计数。作业日志里本文件那一行:

sql-driver-like-escape.test.ts (18 tests) 253ms

对账:

环境 用例数 构成
本地(无 URL) 10(8 passed + 2 skipped) 3 P0-3 + 5 sqlite cell + 2 个未 provision 的具名 skip
CI(两个 URL 都在) 18,全过 3 P0-3 + 5 sqlite + 5 live postgres + 5 live mysql

5 = fixture 反斜杠完整性 + 字面 % + 字面 _ + 普通子串 + 字面 \。18 这个数字只有在两个 live cell 展开成完整 describe 时才成立 —— 也就是说四个 LIKE 用例在真实 PG 16 与 MySQL 8.0 上各执行了一遍,而不是被跳过。

driver-sql 那一条腿的整体:

Test Files  67 passed (67)
     Tests  1086 passed (1086)
  Start at  18:09:59        <- TZ=America/New_York,进程时区确实偏斜

skipped0(本地同一套是 860 passed | 46 skipped)—— 全包的 live cell 都跑满了。

因此,issue 里两条「未验证」当中的第一条已被验证

MySQL 侧那条文档约束 —— ESCAPE 实参 "must evaluate as a constant at execution time" —— 在 MySQL 8.0.46 上实测通过applyLike 绑占位符 ESCAPE ? 经 knex + mysql2 之后被服务器接受,且字面反斜杠用例返回正确行。同时那一个反斜杠挺过了 mysql2 的客户端转义与服务端 lexer(fixture 完整性断言 + \ 用例两头夹住)。

第二条(knex + mysql2 是否可能走服务端预处理 execute() 路径)仍未核实,本 PR 不对此作断言 —— 现在的价值是:若哪天该路径变化,这四个用例会在这个作业里说话,而在此之前它们一句话都说不出来。

日志阅读范围(如实)

作业日志约 449k 字符、单行结构,我只做了定向 grepsql-driver-like-escapeTest FilesTests),没有逐字读完全文。上面引用的三处是原文;未读部分不排除有与本 PR 无关的告警。


Generated by Claude Code


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants