Skip to content

fix(objectql,driver-sql): 播种解析按声明的 suffix 定位计数器,两侧收敛 (#6468) - #6553

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-6468-autonumber-suffix-parse
Aug 8, 2026
Merged

fix(objectql,driver-sql): 播种解析按声明的 suffix 定位计数器,两侧收敛 (#6468)#6553
baozhoutao merged 2 commits into
mainfrom
claude/issue-6468-autonumber-suffix-parse

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #6468

自增号格式允许序号槽 {0..0} 后面还有 token —— renderAutonumbersuffix
声明返回值,契约写死为 prefix + zero-padded(seq) + suffix
(packages/spec/src/data/autonumber-format.ts:165-214)。这类格式渲染出的值序号
不在串尾
,而两侧播种解析都假定「串尾的数字就是计数器」,并且各错各的。

前提复核表(动手前在 origin/main 逐条实测)

前提 结论 证据
P1 两处解析仍是「取串尾数字段」形状,双错读数仍在 ✅ 成立 锚点已随 #6467 漂移,按内容重定位:engine.ts:2182 seedAutonumber(分诊锚 :2178)、sql-driver.ts:3033 scanMaxNumericTail(锚未动)。探针直调 spec 的 parseAutonumberFormat / renderAutonumber 后复刻两侧解析,复现 issue 正文原表(见下)
P2 spec 侧契约原样正确,只读引用 ✅ 成立,零 diff :165 suffix: string;:172 "Final value: prefix + zero-padded(seq) + suffix"、:199-200 width === null 前后分流、:213
P3 #5495 无在飞认领、无要求动这两个函数 ✅ 成立 #5495 的认领已由 PM 释放(comment 5216947320:摘 pm:dispatchedpm:queue、清 assignee),且重锚结论指向 getNextSequenceValue 的序列重同步 —— 与本单两处解析零函数交集
P4 本单解析在 #6467 的新扫描形状下生效 ✅ 成立 引擎侧解析的输入正是 keysetWalk 分页返回的行(engine.ts:2206-2226 就在 for await (const page of walk.pages()) 内);扫描结构未触碰,并有 pin 断言 $startsWith 下推形状不变

P1 实测(origin/main @ 2fdb36eb9,真实计数器均为 1):

fmt="{000}-{YYYY}"  value=001-2026   prefix='' suffix='-2026' engineSeed=2026 sqlDriverSeed=12026 agree=false
fmt="D-{0000}"      value=D-0001     prefix='D-' suffix=''    engineSeed=1    sqlDriverSeed=1     agree=true
fmt="{0000}"        value=0001       prefix='' suffix=''      engineSeed=1    sqlDriverSeed=1     agree=true
fmt=""              value=1          prefix='' suffix=''      engineSeed=1    sqlDriverSeed=1     agree=true

{000}-{YYYY} 是很常见的单号写法。三行 001-2026/002-2026/003-2026 之后,引擎
年份当计数器播种成 2026,下一个号跳到 2027-2026;driver-sql 把 0012026
拼成 12026,下一个号是 12027-2026同一份元数据、同一批行,换个驱动号段就不一样,
而中间跳过的号已经烧掉、事后无法回收。

双侧收敛设计

一条规则,两侧同一对字符串。 prefixsuffix 都取自 renderAutonumber
的返回值,由调用方算好后传入:

  • prefix / suffix 任一非空 ⇒ 计数器「有锚」:取 prefix 之后的首个数字段,
    并在该行确实带有声明的 suffix 时先剥离;
  • 两者皆空 ⇒ 「无锚」:各自既有读法逐字保留 —— 引擎取整串最后一个数字段,
    driver-sql 拼接全部数字。无 {0..0} 槽的格式渲染的就是串尾裸计数器,而早于格式
    存在的历史值根本没有锚可依。

suffix 只在匹配时剥离,绝不要求匹配。 计数器 scope 是渲染后的 prefix,
{000}-{YYYY} 的 prefix 为空 ⇒ 全局一个计数器、只有显示的年份在变。去年的
007-2025 因此持有计数器 7,必须计入。同理 SQL 谓词保持 like 'prefix%',不下推
%suffix
:按当前 suffix 过滤会把这些行整批漏掉、播种低于真实 max —— 正是 #6249
修掉的重复单号伤害,自己再造一遍。suffix 只在 JS 侧逐行使用,不匹配即「换了个 suffix、
同一个计数器」。两侧各有一条 case 直接 pin 这个方向。

依赖方向取舍(报告项)

@objectstack/objectql@objectstack/driver-sql 互不依赖,两者都只依赖
@objectstack/spec;@objectstack/core 反过来依赖 spec,不在两者之下。因此能同时被两侧
import 的共享位置只有 packages/spec,而本单裁定 spec 只读引用。取舍如下:

  • 两侧都不再自行理解格式 —— 定位参数(prefix / suffix)一律由 spec 导出的
    renderAutonumber 产出,调用方算好传入,driver 只收参数(分诊给出的 fallback 形态)。
    两侧都没有第二套 token 解析。
  • 残余重复是「拿到两个字符串后怎么用」的那几行。为此加了跨侧一致性测试直接断言
    两侧答案相等(见下),使收敛由测试强制而非靠人记住。
  • 更彻底的做法是把这条「读回」规则作为 renderAutonumber 的逆放进
    packages/spec/src/data/autonumber-format.ts(该文件的头注释本就写明它存在的理由是
    「shared by the ObjectQL engine and the SQL driver」)。这超出本单裁定的文件面,
    未做,已在报告中作为待定问题交回维护者。

getNextSequenceValue 只多转发一个位置参数(签名末尾 suffix = '' + 调用处一行),
其锁/插入/更新与序列语义一行未改 —— 给 #5495 的预期落点留出干净的接续面。

测试

新增三个文件,共 23 个用例:

文件
① 引擎侧(9) packages/objectql/src/engine-autonumber-seed-suffix.test.ts
② driver-sql 侧(9) packages/drivers/driver-sql/src/sql-driver-autonumber-suffix.test.ts
③ 跨侧一致性(5) packages/runtime/src/autonumber-seed-cross-side-parity.integration.test.ts

③ 之所以落在 packages/runtime:objectql 与 driver-sql 互不依赖,能同时看见两侧实现的
最小包就是 runtime
(它同时依赖 objectql / driver-memory / driver-sql,且已有
bulk-write-real-driver.integration.test.ts 这一真实驱动集成先例)。仅新增测试文件,
runtime 源码零改动。用例用真实 InMemoryDriver(supports = {},正是引擎兜底路径)
真实 SqlDriver(better-sqlite3)各持同一批 fixture 行,断言
expect(engineValue).toBe(sqlValue)

④⑤ 对照 pin:无后缀格式(D-{0000}{0000})两侧现行为不变;无格式的 legacy 无锚
读法两侧逐字保留(含 driver-sql 对 SO-2024-0007 仍拼成 20240007 的显式 pin)。

反向验证(方向先写死,再运行;预测 vs 实测)

预测在执行前写入 predictions.md⚠️ packages/runtimedist 解析工作区依赖,
所以每次回退都重新构建了对应包 —— 未重建时跨侧用例会假绿(§9 陈旧产物陷阱,已实测踩到一次)。

回退 用例 预测 实测
引擎 引擎 A {000}-{YYYY}(2 例) 🔴 🔴 2 failed / 7 passed
引擎 引擎 B CASE-{000}-{YYYY} 🟢 反向 🟢
引擎 引擎 ④ 对照 / ⑤ legacy 🟢 🟢
引擎 driver 侧 9 例 🟢 🟢
引擎 跨侧 A + 跨年 🔴 🔴 expected '2027-2026' to be '004-2026'
引擎 跨侧 B + 对照 🟢 反向 🟢
driver driver A(3)+ B(2) 🔴 🔴 5 failed / 4 passed
driver driver ④ 对照 / ⑤ legacy 🟢 🟢
driver 引擎侧 9 例 🟢 🟢 9 passed
driver 跨侧 A + B + 跨年 🔴 🔴 3 failed / 2 passed
driver 跨侧 ④ 对照 🟢 🟢

全部命中,含一处预期内的方向反转。 派发模板写的是「回退引擎侧 ⇒ ① ③ 转红」;这对
fixture A({000}-{YYYY},prefix 为空)成立,对 fixture B(CASE-{000}-{YYYY},
prefix 非空)不成立 —— 引擎的 prefix 分支在 main 上本来就把 B 读对了,B 只有
driver 一侧是坏的
。这里如实记录方向,而不是把用例硬凑成模板预设的红。回退引擎后跨侧
断言给出的 expected '2027-2026' to be '004-2026',正是 issue 正文预言的那个烧号。

命令输出

pnpm --filter @objectstack/objectql --filter @objectstack/driver-sql test
  driver-sql   Test Files  71 passed | 4 skipped (75)    Tests  976 passed | 46 skipped (1022)
  objectql     Test Files  147 passed (147)              Tests  2463 passed (2463)

pnpm --filter @objectstack/runtime test
  Test Files  111 passed (111)                           Tests  1611 passed (1611)

typecheck(objectql / driver-sql / runtime)          三包 Done,无错误
check:engine-double-contract   OK — 84 pinned, 133 in the DEBT ledger, 2 exempt.
check:query-options-erasure    OK — ratchet holds; test surface 263 site(s), 未新增(无 `{} as any`)
check:verify-stand-in          OK — 2 guarded, 5 exempt, 10 call sites, 0 asserted driver arguments
check:type-check-debt          OK — none above its recorded number(台账只缩不抬;先跑了
                               turbo run build 覆盖 packages 全量,70/70 成功,
                               满足 #6510 的「拒绝在未构建闭包上测量」)
node scripts/check-nul-bytes.mjs   OK (scanned 6132 tracked text file(s))

三个新测试文件各自的 tsc --noEmit 贡献为 0 条错误(逐文件过滤确认)。

未触碰

顺带发现(未在本 PR 修)

无格式的 autonumber 字段两侧渲染结果不同:driver-sql 用 {0000} 兜底
(sql-driver.ts:4322 / :4404 const fmt = rawFmt || '{0000}')渲染出 0001,而引擎
engine.ts:2115 用空格式渲染出裸 1。这与本单的播种解析缺陷机制不同(在渲染兜底,
不在解析),按 Prime Directive #10 单独开单。


Generated by Claude Code

claude added 2 commits August 8, 2026 04:18
自增号格式允许序号槽 `{0..0}` 之后还有 token(`renderAutonumber` 的 `suffix`
是声明返回值,契约为 `prefix + zero-padded(seq) + suffix`),这类格式渲染出的
值序号不在串尾。两侧播种解析都假定「串尾数字段 = 计数器」,各错各的:引擎
`seedAutonumber` 取最后一个数字段读成年份 2026,driver-sql `scanMaxNumericTail`
把 tail 全部数字拼接读成 12026,真实计数器是 1 —— 同一份元数据换驱动号段不同,
跳过的号无法回收。

两侧改为:prefix / suffix 任一非空则计数器「有锚」,取 prefix 之后首个数字段
(该行带声明 suffix 时先剥离);两者皆空则各自既有读法逐字保留。两个字符串都由
调用方从 renderAutonumber 取得后传入,两侧都不再自行理解格式。

suffix 只在匹配时剥离、不要求匹配,SQL 谓词也保持 `like 'prefix%'`:计数器
scope 是渲染后的 prefix,`{000}-{YYYY}` 全局一个计数器,去年的 `007-2025` 仍
持有计数器 7,按当前 suffix 过滤会播种低于真实 max(#6249 的重复单号伤害)。

不触 getNextSequenceValue 的序列逻辑(仅多转发一个位置参数)、不触 #6467 的
扫描结构、不改 packages/spec。

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

vercel Bot commented Aug 8, 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 8, 2026 4:29am

Request Review

@github-actions github-actions Bot added the size/l label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-sql)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/driver-sql, @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/plugins/anatomy.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/index.mdx (via @objectstack/objectql)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-sql, @objectstack/objectql)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/driver-sql, @objectstack/objectql)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/driver-sql, packages/objectql)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/driver-sql, @objectstack/objectql)

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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 8, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review August 8, 2026 04:44
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 6f6fec7 Aug 8, 2026
25 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-6468-autonumber-suffix-parse branch August 8, 2026 05:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants