fix(objectql,driver-sql): 播种解析按声明的 suffix 定位计数器,两侧收敛 (#6468) - #6553
Merged
Conversation
自增号格式允许序号槽 `{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
…onumber-suffix-parse
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 18 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
baozhoutao
marked this pull request as ready for review
August 8, 2026 04:44
This was referenced Aug 8, 2026
This was referenced Aug 8, 2026
This was referenced Aug 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6468
自增号格式允许序号槽
{0..0}后面还有 token ——renderAutonumber的suffix是声明返回值,契约写死为
prefix + zero-padded(seq) + suffix(
packages/spec/src/data/autonumber-format.ts:165-214)。这类格式渲染出的值序号不在串尾,而两侧播种解析都假定「串尾的数字就是计数器」,并且各错各的。
前提复核表(动手前在
origin/main逐条实测)engine.ts:2182seedAutonumber(分诊锚 :2178)、sql-driver.ts:3033scanMaxNumericTail(锚未动)。探针直调 spec 的parseAutonumberFormat/renderAutonumber后复刻两侧解析,复现 issue 正文原表(见下):165suffix: string;、:172"Final value: prefix + zero-padded(seq) + suffix"、:199-200width === null前后分流、:213pm:dispatched→pm:queue、清 assignee),且重锚结论指向getNextSequenceValue的序列重同步 —— 与本单两处解析零函数交集keysetWalk分页返回的行(engine.ts:2206-2226就在for await (const page of walk.pages())内);扫描结构未触碰,并有 pin 断言$startsWith下推形状不变P1 实测(
origin/main@2fdb36eb9,真实计数器均为 1):{000}-{YYYY}是很常见的单号写法。三行001-2026/002-2026/003-2026之后,引擎把年份当计数器播种成 2026,下一个号跳到
2027-2026;driver-sql 把001和2026拼成 12026,下一个号是
12027-2026。同一份元数据、同一批行,换个驱动号段就不一样,而中间跳过的号已经烧掉、事后无法回收。
双侧收敛设计
一条规则,两侧同一对字符串。
prefix与suffix都取自renderAutonumber的返回值,由调用方算好后传入:
并在该行确实带有声明的 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 只读引用。取舍如下: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 个用例:
packages/objectql/src/engine-autonumber-seed-suffix.test.tspackages/drivers/driver-sql/src/sql-driver-autonumber-suffix.test.tspackages/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/runtime从dist解析工作区依赖,所以每次回退都重新构建了对应包 —— 未重建时跨侧用例会假绿(§9 陈旧产物陷阱,已实测踩到一次)。
{000}-{YYYY}(2 例)2 failed / 7 passedCASE-{000}-{YYYY}expected '2027-2026' to be '004-2026'5 failed / 4 passed9 passed3 failed / 2 passed全部命中,含一处预期内的方向反转。 派发模板写的是「回退引擎侧 ⇒ ① ③ 转红」;这对
fixture A(
{000}-{YYYY},prefix 为空)成立,对 fixture B(CASE-{000}-{YYYY},prefix 非空)不成立 —— 引擎的 prefix 分支在 main 上本来就把 B 读对了,B 只有
driver 一侧是坏的。这里如实记录方向,而不是把用例硬凑成模板预设的红。回退引擎后跨侧
断言给出的
expected '2027-2026' to be '004-2026',正是 issue 正文预言的那个烧号。命令输出
三个新测试文件各自的
tsc --noEmit贡献为 0 条错误(逐文件过滤确认)。未触碰
getNextSequenceValue的序列逻辑(Autonumber counter neither syncs to MAX(existing) per tenant nor re-checks on collision — warm-DB creates 409 in bursts, each failure burning a number (25 retries observed) #5495 预期落点)—— 仅末尾多一个转发参数keysetWalk扫描结构 —— 并有 pin 断言下推形状不变packages/spec—— 零 diff顺带发现(未在本 PR 修)
无格式的 autonumber 字段两侧渲染结果不同:driver-sql 用
{0000}兜底(
sql-driver.ts:4322/:4404const fmt = rawFmt || '{0000}')渲染出0001,而引擎engine.ts:2115用空格式渲染出裸1。这与本单的播种解析缺陷机制不同(在渲染兜底,不在解析),按 Prime Directive #10 单独开单。
Generated by Claude Code