Skip to content

refactor(driver-sql)!: analyzeQuery / findWithWindowFunctions 收窄 query 签名 (#6212 批 A+E) - #6355

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-6212-batch-a-e-sql-query-signatures
Aug 7, 2026
Merged

refactor(driver-sql)!: analyzeQuery / findWithWindowFunctions 收窄 query 签名 (#6212 批 A+E)#6355
os-zhuang merged 3 commits into
mainfrom
claude/issue-6212-batch-a-e-sql-query-signatures

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Part of #6212 —— 只做 批 A + 批 E(外加实测判定为零冲突的 批 F)。⛔ 不碰 aggregate(批 B,等 #6203/PR #6312)、不碰 memory/mongodb(批 C)、不碰 distinct(已立单 #6320)、不碰 packages/spec

基线取合并后的最新 origin/main85ec26d#5234/PR #6296sql-driver.ts 改动已在其中,本分支在其之上;文本合并干净,合并后全套复验见下)。

前提复核(PM 测量棒的结论仍成立)

论断复核结果
两处仍是 anysql-driver.tsfindWithWindowFunctions / analyzeQuery 收窄前均为 query: any
explain 本来就是 DriverQuery、一行转发✅ 转发前这一对自相矛盾:契约门声明 AST,其实现声明 any
analyzeQuery 只读 fields/where/orderBy/limit/offset✅ 全在 DriverQuery
批 A 实测零报错、零 fixture 改动✅ driver-sql + driver-sqlite-wasm 两个包 typecheck 全绿,测试 fixture 一行未动
windowFunctionsretiredKey() 墓碑 ⇒ 不能标 DriverQuery✅ 见下「为什么必须 Omit
两道门非测试生产者为零✅ 全仓 13 个 driver-sql 依赖包中,findWithWindowFunctions / analyzeQuery / buildWindowFunction 零命中

改了什么

批 A —— analyzeQuery(object, query: DriverQuery, options?) 纯注解。它是 explain() 的实现体,而 explain() 早已声明 DriverQuery 并转发过来。

批 E —— findWithWindowFunctions(object, query: SqlWindowFunctionQuery, options?),按 PM 裁决 B 在 driver-sql 本地声明扁平形,并从 @objectstack/driver-sql 导出(嵌入方要能叫出这个类型的名字——#4286 把这道门写成了已发布的迁移处方):

exportinterfaceSqlWindowFunctionSpec{function: string;// 无参发射:`rank` -> `RANK()`alias: string;partitionBy?: string[];orderBy?: {field: string;order?: 'asc'|'desc'}[];// 门读 `s.order || 'asc'`,故 order 可省}exporttypeSqlWindowFunctionQuery=Omit<DriverQuery,'windowFunctions'>&{windowFunctions?: SqlWindowFunctionSpec[];};

成员以实测方法体为准:buildWindowFunction 只读 function / partitionBy / orderBywf.alias 只在 as ?? 绑定里读。这与 spec 删除注记和 migrations/registry.ts 迁移处方里逐字写的 { function, alias, partitionBy?, orderBy? } 完全一致——三处必须始终说同一句话。buildWindowFunction(spec: any) 一并跟进为 SqlWindowFunctionSpec

为什么必须 Omit 而不是直接交集。query.windowFunctionsretiredKey()z.never().optional()),QueryAST['windowFunctions'] 解析为 undefined;写成 DriverQuery & { windowFunctions?: SqlWindowFunctionSpec[] } 会把数组与 undefined 求交,属性变成不可写——这道门自己文档里的载荷会静默地编译不过,而报错点离类型声明十万八千里。这个坑本身被一条 pin 钉住了(pins WHY the type Omit s the tombstoned key):谁把 Omit 「简化」掉,红在测试里而不是红在文档里。

⛔ 不进 spec,⛔ 不退役。 类型放在能力真正所属的驱动层,是接着 #4286 的判断往下走(那次删 WindowFunctionNodeSchema 的理由正是它声明了 field/over/frame 这些门从不读的成员);退役该方法要撤回 6 处已发布文档的迁移处方,是维护者的取舍,PM 已记录该选项仍在桌上、但不在本单。请求面的墓碑没有被重新打开——analyzeQuery('o', { windowFunctions: [...] }) 依然是编译错误,有 pin 为证。

批 F(顺带,实测后判定并入)@objectstack/verifyBucketableDriver.aggregatequery: unknown 收到 DriverQuery,并删掉该文件两处 AST 字面量里多写一遍的 object 键。同时删掉一处 as never——它只是因为字面量推断把 'count' 放宽成了 string,注上类型就不需要了。这不预断批 B 对驱动自身 aggregate 参数类型的选择:方法参数按双变比较,驱动那边声明 anyQueryAST 还是收窄后的类型,都照样满足这个替身。(实测:改动 9 行、verify typecheck 全绿、删掉而非新增逃逸口,故判定「零冲突且改动微小」。)

反向验证(方向先判后跑)

两个方向都是 red,都先预测后执行:

  1. 给门传门不认的键 ⇒ 应红(在 driver-sqlite-wasm 上做,因为它才是门禁盲区所在)。临时把 spec 已删词汇塞进 wasm 的 window fixture、并给 analyzeQuery 传一个拼错的键:
src/sqlite-wasm-driver-advanced.test.ts(404,13): error TS2353: Object literal may only specify known
properties, and 'field' does not exist in type 'SqlWindowFunctionSpec'.
src/sqlite-wasm-driver-advanced.test.ts(411,45): error TS2353: Object literal may only specify known
properties, and 'sortBy' does not exist in type 'DriverQuery'.

同时证明了盲区 1 已覆盖:driver-sqlite-wasm 读的确实是 driver-sql 重建后的 dist/*.d.ts,不是假绿。已还原。

  1. 把两处签名改回 any ⇒ 新 pin 应红
src/sql-driver-query-signature.test.ts(110,23): error TS2322: Type 'string' is not assignable to type 'never'.
src/sql-driver-query-signature.test.ts(134,7): error TS2578: Unused '@ts-expect-error' directive.
src/sql-driver-query-signature.test.ts(144,9): error TS2578: Unused '@ts-expect-error' directive.
src/sql-driver-query-signature.test.ts(191,13): error TS2322: Type '"dropped"' is not assignable to type 'never'.

这里有一处值得写下来:DropsObject 这个既有工具顺带也抓 any——keyof anystring | number | symbol,所以 'object' extends keyof any 为真,DropsObject< any > 解析为 never。一条 pin 同时挡住「把 object 加回来」和「把参数放宽回 any 」两种回退,不需要再写第二条。

验收判定标准

「让门自己文档里的载荷编译得过」是判定标准而非可选项,所以它是一条 pin,载荷逐字抄自 content/docs/data-modeling/queries.mdx

constdocumented: SqlWindowFunctionQuery={windowFunctions: [{function: 'rank',alias: 'salary_rank',partitionBy: ['department'],orderBy: [{field: 'salary',order: 'desc'}]},],};

三个门禁盲区

  1. driver-sqlite-wasm 读 driver-sql 的 dist/*.d.ts ⇒ 已点名进验证面:先 build driver-sql 再 typecheck 它,并按上面的反向验证证明它确实读到了新 d.ts(不是假绿)。
  2. erasure 棘轮双向pnpm check:query-options-erasure 合并前后各跑一次,267 未动(本批既不新增也不删除 as any):
✓ query-options-erasure ratchet holds: 83 unswept non-test site(s) in 19 file(s), none new.
test surface: 267 site(s) in 51 file(s) — at the ceiling
baseline key set verified against 85ec26d: no files added.
  1. 全仓 pnpm check:type-check-debt re-measure34 ledger entr(ies) re-measured in 214.9s, 2024 raw tsc error(s) total, none above its recorded number. 本 PR 触及的三个包均不在台账内,只减不增成立。

另跑:check:nul-bytes OK(6000 个文件),check:driver-conformance OK,check:engine-double-contract OK,check:published-files OK,check:empty-changeset OK(1 declaring changeset added),改动文件 eslint 零输出,并按字节纪律自查了控制字符(零命中)。

测试

合并 origin/main(15 个提交,含对 sql-driver.ts 的改动)后重跑:

packages/drivers/driver-sql Test Files 68 passed | 4 skipped Tests 953 passed | 46 skipped
packages/drivers/driver-sqlite-wasm Test Files 18 passed Tests 254 passed
packages/drivers/driver-turso Test Files 25 passed Tests 804 passed
packages/verify Test Files 4 passed Tests 17 passed

@objectstack/dogfood 520 passed(含 date-bucket-parity-conformance,即批 F 改动后的 checkDateBucketParity 对真实 SqlDriver / SqliteWasmDriver 的行为证明)。typecheck:driver-sql / driver-sqlite-wasm / driver-turso / verify 四个包全绿。

零运行时改动:全部是类型注解,加上两处冗余 object 键的删除——全仓驱动实测无一读 query.object

消费半径

按规则枚举了 @objectstack/driver-sql 的 13 个依赖包(rest / runtime / service-datasource / service-automation / dogfood / cli / driver-sqlite-wasm / driver-turso / trigger-record-change / plugin-auth / plugin-approvals / app-showcase),grep 三个符号零命中;两道门只有各自驱动包的测试在用。changeset 标 major,依据与 #5181 / #6075 一致:源码级破坏性(调用点内联字面量与 BucketableDriver 的导出形状),运行时零变化,且 check:api-surface 不记录签名,所以 changeset 是该变更唯一的下游载体。

顺带发现(已另立单,不在本 PR 修)


🤖 Generated with Claude Code

https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx


Generated by Claude Code

… a query type (#6212 batch A+E)
`IDataDriver`'s six query methods were narrowed to `DriverQuery` by #5181 and
followed through on five drivers by #6075. The SQL driver's OWN query doors —
not on the contract, therefore never in scope of either — kept `query: any`.
`any` there is not "the object name goes unchecked", it is every check off:
`where`'s filter dialect, `orderBy`'s sort-node shape, `limit`/`offset` being
numbers. Those are exactly the members both bodies read.
- `analyzeQuery` -> `DriverQuery`. It is `explain()`'s implementation and
`explain()` already declared `DriverQuery` and forwarded here, so the pair was
self-inconsistent. Pure annotation: zero errors and zero fixture changes in
driver-sql and driver-sqlite-wasm.
- `findWithWindowFunctions` -> `SqlWindowFunctionQuery`, a driver-local flat
type (exported, with `SqlWindowFunctionSpec`). It cannot take `DriverQuery`:
`query.windowFunctions` is a `retiredKey()` tombstone since #4286, so
`QueryAST['windowFunctions']` is `undefined` and this door's own published
payload would stop compiling. The type is `Omit`-ed, not intersected, for
exactly that reason, and a pin holds that trap still. It stays OUT of
`packages/spec` deliberately: #4286 removed `WindowFunctionNodeSchema` because
it declared `field`/`over`/`frame` members this door never reads, and the
door's flat shape is quoted verbatim by the spec's removal note and the
published migration prescription.
- `buildWindowFunction(spec: any)` follows to `SqlWindowFunctionSpec`.
Also (#6212 batch F): `@objectstack/verify`'s `BucketableDriver.aggregate`
declares `DriverQuery` instead of `unknown`. It is a PUBLISHED structural double
an out-of-tree driver implements, and `unknown` let the file's own two aggregate
literals each repeat the object name argument one carries. Drops one `as never`
that only existed because an inferred literal widened `'count'` to `string`. It
does not presume batch B's choice for the drivers' own `aggregate` parameter —
method parameters compare bivariantly either way.
Zero runtime change: type annotations plus two redundant keys removed (no driver
reads `query.object`).
Part of #6212
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
@vercel

vercelBot commented Aug 7, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 7, 2026 3:04pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/driver-sql, @objectstack/verify.

10 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx(via @objectstack/driver-sql)
  • content/docs/getting-started/glossary.mdx(via @objectstack/driver-sql)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/driver-sql)
  • content/docs/plugins/anatomy.mdx(via @objectstack/driver-sql)
  • content/docs/plugins/packages.mdx(via @objectstack/driver-sql)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/driver-sql)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/driver-sql)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/driver-sql)
  • content/docs/releases/implementation-status.mdx(via @objectstack/driver-sql, @objectstack/verify)
  • content/docs/releases/v15.mdx(via @objectstack/verify)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude