fix(objectql): seedAutonumber 播种扫描覆盖 scope 内每一行,不再取 5000 行窗口 (#6249) - #6467
Merged
Conversation
引擎兜底路径的自增号播种此前是一次 `limit: 5000`、无排序、无过滤的 `find`,把「任意 5000 行窗口内的最大值」当成全表 MAX。对象超过 5000 行、 或某 scope 的行被其他 scope 挤出窗口时,播种低于真实 MAX,计数器从已被 占用的号段起号 —— 对 `unique` 记录号字段就是直接发出重复业务标识符。 改为完整扫描:`keysetWalk` 按 `id` 游标分页(非 offset,#4363),前缀 下推为 `$startsWith`,数值最大值在引擎侧逐值解析。刻意不委托给 `orderBy desc + limit 1` 或聚合 `max` —— 两者按文本排序,字典序等于 数值序仅当 scope 内全部补零到同一定宽,而格式语言不保证(无 `{0..0}` 槽位时渲染裸计数器,`'9' > '10'`;定宽越过后溢出)。 扫描无法走完时拒绝播种并大声失败,不用已读部分的下界起号 —— 与 #6114 对读故障的处置同族。声明 `supports.autonumber` 的驱动不受影响。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Q7oc7ASjh8yxyS3Yz78We
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 14 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 7, 2026 23:59
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 #6249
引擎兜底路径(驱动未声明
supports.autonumber)的自增号播种,此前是一次limit: 5000、无排序、无过滤的find,把「任意 5000 行窗口内的最大值」当成了全表 MAX。对象超过 5000 行、或某个 scope(日期 /{field}分组)的行被其他 scope 的行挤出窗口时,播种出低于真实 MAX 的号,计数器从一个已被占用的号段起号 —— 对声明了unique的记录号字段,就是直接发出重复的业务标识符。前提复核(动手前逐条实测 origin/main@48f98b0)
seedAutonumber仍是窗口形状engine.ts:2142(#6456 落地后行号漂移,按内容定位):find({ fields: ['id', field], limit: 5000, context })—— 无orderBy、无whereengine.ts:2083if (driverOwnsAutonumber) return;;sql-driver.ts:3033scanMaxNumericTail用.where(field, 'like', prefix%)+whereNotNull,无 limit;driver-turso/driver-sqlite-wasm继承SqlDriver。⛔ 驱动侧零改动pm:queue、assignee 已清空(PM 于 12:16Z 采纳前提证伪后释放),评论区无在飞认领对;其真凶经实测落在driver-sql.getNextSequenceValue,与本单零文件交集isMissingTableError判别 catch 原样,git diff在} catch (error) {之后无任何 diff;两条 pin 测试在反向验证中保持绿选型
采用:
keysetWalk游标分页全扫 + 前缀下推,数值最大值在引擎侧逐值解析。keysetWalk(packages/types/src/keyset-walk.ts),与summary-backfill同一把,而不是再手抄一份游标合并;id游标 seek 而非offset—— 该模块自己的理由(分页读取在没有 orderBy 时同样不确定:tie-breaker 只覆盖了「排了序的翻页」 #4363):offset walk 无法保证访问过每一行,而这里漏掉的一行正好就是不能漏的那个号;prefix非空时下推{ [field]: { $startsWith: prefix } },让日期 /{field}scope 只读自己的行;JS 侧的startsWith复核保留为权威,这样匹配更宽松的驱动(大小写不敏感的LIKE)无法用别的 scope 的行抬高 max;这与 SQL 驱动自身的播种形状(
scanMaxNumericTail,无 limit 的 scope 内全扫)一致 —— 兜底路径不再比它弱。被否路线:
orderBy desc + limit 1取值最大{0}不补零;任何定宽在计数器越过后溢出(CASE-99999排在CASE-100000之上);legacy 空前缀路径取整串最后一个数字段,根本没有对应的字典序读法。要救它就得再加一道「本格式是否定宽」的判定加溢出逃生口 —— 更多假设,而猜错的代价是重复的业务标识符aggregate的maxMAX对文本列即字典序),另外还要依赖驱动的 aggregate 支持与引擎的内存聚合兜底(后者本身仍要读全部行)。收益为零,依赖面更大offsetkeyset-walk.ts模块注释已写明:offset walk 会漏行且是 O(n²/p)未新增任何驱动能力声明,因此不触发分诊指定的
needs_decision出口。查询词汇全部对着仓内词表核实:where/orderBy/limit/fields均在ENGINE_FIND_OPTION_KEYS(engine.ts:271);$startsWith/$gt/$and均在FILTER_OPERATORS/LOGICAL_OPERATORS(filter.zod.ts:1085);排序节点用order而非direction—— 后者被SortNodeSchema按名拒绝(#4721),派单示例里的写法正是词表外的那个。字典序陷阱的处置
结构上不进入。 排序只用于游标推进(按
id),从不用于判定最大值;数值最大值由既有的数字段解析逻辑(有前缀取紧随其后的数字串,无前缀取最后一个数字串)逐值算出,该逻辑一字未改。因此定宽、不定宽、溢出、legacy 空前缀四种形态走同一条正确路径,不需要豁免任何一种。有一条专门的 pin 测试守住这一点:legacy 裸计数器
'7' '8' '9' '10'的字典序最大是'9',据此播种会重发已存在的'10';测试断言得到'11'。任何未来把这段换成排序捷径的改动都会撞红它。无法正确计算时的处置
扫描走不完(行缺
id游标、或驱动未执行游标谓词)时,walk.truncated为真 —— 此时「已读部分的最大值」是下界而不是最大值。不静默播这个低号,而是抛错、不分配号、不写入,与 #6114 对读故障的处置同族。该拒绝错误刻意不匹配isMissingTableError的任何模式,不会被兜回「播种 0」。反向验证(方向先写死,再运行)
预测写在
predictions.md后才执行:回退窗口修复 ⇒ 9 红 4 绿。实测与预测逐条相符。expected 'D-5001' to be 'D-6001'expected 1 to be greater than 1expected 'D-5001' to be 'D-6001'expected 'APAC-0001' to be 'APAC-0004'expected undefined to deeply equal { ticket_no … }expected 'APAC-0001' to be 'APAC-0004'expected '5001' to be '6001'supports.autonumber驱动无播种扫描orderBy desc + limit 1/ 聚合max的捷径。按报告纪律照实写出来,而不是硬凑成红。(4)(5) 的绿正是它们该有的方向 —— 驱动侧对照与 #6114 行为都不该被本改动移动。
APAC-0001这一条尤其值得看:它不是「号偏小」,是与库内已存在行逐字相同的值。命令输出
文件面
packages/objectql/src/engine.ts—— 仅seedAutonumber()一带 + 其 import 与页大小常量。⛔ 未触同文件的 sys_file hydrate catch 位(fix(objectql): sys_file hydrate 读故障与「无文件」可分辨 (#6116) #6456)与其他区域;fix(objectql): seedAutonumber 把非「表未建」的读故障上抛,不再从 0 重发自增号 (#5979) #6114 的 catch 在git diff中零变更。packages/objectql/src/engine-autonumber-seed-scan.test.ts(新增,13 例).changeset/tidy-pugs-tap.md—— patch@objectstack/objectqlGenerated by Claude Code