Uh oh!
There was an error while loading. Please reload this page.
fix(driver-turso): remote 模式对齐 NULL-safe 语义 —— $not/$ne/$nin/$notContains 四算子 + $exists 拒收闸 (#5903) - #6047
Conversation
…ains 四算子 + $exists 拒收闸 (#5903) TursoDriver 有两个 filter 编译器:local/replica 继承 SqlDriver.applyFilterCondition, remote 走 RemoteTransport.buildWhereSQL 这个独立发射器。#5146(PR #5296)与 #5298 (PR #5962)两次裁决只落在前者,于是同一个 driver 对同一条 filter 按 url 给两个答案。 在共享 fixture(1-2 行有值、3-4 行 NULL)上实测 origin/main @ b5bdf48: {d:{$ne:'v1'}} / {d:{$nin:['v1']}} / {d:{$notContains:'v1'}} / {$not:{d:'v1'}} local ['2','3','4'] remote ['2'] {d:{$exists:'yes'}} local INVALID_FILTER remote ['1','2'] 本次改动(全部在 remote 侧,local 一行未动): - $not:先把操作数逐叶 totalise 再取反,NOT(…) 对每一行都是 TRUE/FALSE 而不落入 UNKNOWN。守卫挂在叶子而不是 NOT 上,因为 De Morgan 只对二值叶子成立;极性按算子 各自的答案,不是一律 OR col IS NULL —— 否则 {$not:{a:{$ne:5}}} 会把它排除的行送回来。 - $ne/$nin/$notContains:非否定路径发射 (col IS NULL OR <原谓词>),统一 OR 展开, 不用 IS DISTINCT FROM / IS NOT / <=>(NOT LIKE 无此形式;SQLite 拼写依赖本仓不 pin 的引擎版本;实测执行计划相同)。$ne:null 保持 IS NOT NULL 不变 —— 极性看比较数, 不看算子名。括号不可省:buildWhereSQL 用裸 AND 拼接同级子句,裸 OR 会结合得更松。 - $exists:非布尔比较值按 #5347-A 拒收(INVALID_FILTER / 400)。#1116 当初留的栅栏 写明了自己的解除条件(#5299 裁决 + 不制造 local/remote 分叉),两条今天都已满足, 且 PR #5962 让 local 先严格之后,栅栏本身变成了分叉。 polarity 表在本包内第三次实现(driver-sql / read-scope-sql 之后),理由与 read-scope-sql 相同并写在文件头:本模块声明不依赖 knex,且每张表钉的是各自发射器的 拼写。互钉由 turso-local-remote-null-parity.test.ts 承担 —— 同一 driver 两个 transport 跑同一批 filter,先断言两边相等,再断言等于裁决要求的行集(只断相等会被"两边一起错" 满足)。 FILTER_LOGIC_CASES 入表 N1($ne)/N4($not) 两行:本单是它们共同的最后一个 blocker (Cube 面已由 #5977 清除),表文档的「RULED but not yet enrolled」family 2 与 blocker 矩阵随之作废,改写为毕业记录。11 个 harness 全绿。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
…s outage The 15:40-17:00 UTC actions-resolution outage left this PR's checks in a tangle of superseded generations; a fresh head starts one clean wave. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
…utage Scheduler is draining again (close-event workflow ran at 21:30 with ~30min lag); a fresh push event enters the recovered queue. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
…allowed by the outage Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
📓 Docs Drift CheckThis PR changes 2 package(s): 115 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31135524302 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes#5903
#5298 NULL-safe 程序的终棒。前置 PR #5921 / #5962 / #6004 / #6006 均已 MERGED,本 PR 基于合入它们之后的 origin/main。
前提复核(实测,不是转述)
TursoDriver有两个 filter 编译器:local/replica 继承SqlDriver.applyFilterCondition,remote 走RemoteTransport.buildWhereSQL这个独立发射器。#5146(PR #5296)与 #5298(PR #5962)两次裁决只落在前者 —— 于是同一个 driver 对同一条 filter,按构造它的url给两个答案。在共享 fixture(
FILTER_LOGIC_ROWS,1-2 行d有值、3-4 行 NULL)上实测origin/main@b5bdf48af:{ d: { $ne: 'v1' } }['2','3','4']['2']{ d: { $nin: ['v1'] } }['2','3','4']['2']{ d: { $notContains: 'v1' } }['2','3','4']['2']{ $not: { d: 'v1' } }['2','3','4']['2']{ d: { $exists: 'yes' } }INVALID_FILTER['1','2']{ d: { $ne: null } }['1','2']['1','2'](已一致)前四行证实了本单标题的
$not,并把 PM 扩程注记的另外三个算子逐一坐实;第五行是$exists闸 —— 注意它的分叉方向:PR #5962 让 local 先严格之后,#1116 当初立的那道「不要单独收紧,免得制造 local/remote 分叉」栅栏,本身变成了分叉。改了什么(全部在 remote 侧,local 一行未动)
1.
$notNULL-safe(#5146) — 取反前先把操作数逐叶 totalise,NOT (…)对每一行都是 TRUE/FALSE 而不落入 SQL 的 UNKNOWN。NOT上:De Morgan 只对二值叶子成立,把守卫提到$not之上(操作数是$or时)会把 JS 后端排除的行放回来。OR col IS NULL:{ $not: { a: { $ne: 5 } } }意思是「a 是 5」,无值行必须排除;一刀切会把它排除的行原样送回 —— 本文件反复付过学费的静默放宽。$null约束 +$and/$or节点)交回buildWhereSQL,所以每一对括号都是那个编译器自己写的。2.
$ne/$nin/$notContains非否定路径 NULL-safe(#5298) — 发射(col IS NULL OR <原谓词>)。IS DISTINCT FROM/IS NOT/<=>):NOT LIKE根本没有这种形式,一个编译器为一条裁决扛两种形状;SQLite 拼写依赖本仓不 pin 的引擎版本(remote 端跑的是对端 libSQL,比本地那对更不受控);实测执行计划相同。$ne: null保持IS NOT NULL不变 —— 极性看比较数,不看算子名。buildWhereSQL用裸AND拼接同级子句,裸OR会结合得更松 ——a = ? AND b IS NULL OR b <> ?会被解析成(a = ? AND b IS NULL) OR b <> ?,返回调用方a谓词明确排除的行。测试里有专门钉这一格的用例。3.
$exists非布尔比较值拒收(#5369,按 #5347-A) —INVALID_FILTER/ 400,与$null同信封、首句与driver-sql逐字一致。语义按 #5298 ③ 裁的「有值」,$exists: true/false编译结果一字未改。栅栏当初写明了自己的解除条件(#5299 裁决 + 不制造 local/remote 分叉),两条今天都已满足。4.
FILTER_LOGIC_CASES入表 N1($ne)/ N4($not)两行,并把表文档里「RULED but not yet enrolled」的 family 2 与 blocker 矩阵改写为毕业记录 —— 本单是这两行共同的最后一个 blocker,Cube 面已由 #5977 清除。关于「第三次实现」
polarity 表在本包内是第三份(
driver-sql/read-scope-sql之后)。选择复制而非导出,理由写在文件头:remote-transport.ts的模块契约明写「No local SQLite or Knex dependency」,而driver-sql的入口会拉进 knex;$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298 立下的不变量是「每张极性表钉的是它自己发射器的拼写」(read-scope-sql按真值读$null,driver-sql按恒等读,各自匹配自家发射器),而不是「三份源码字面相同」;driver-sql那份对非节点的$or元素会抛TypeError(它靠reduceFilterKey前置拦截),本编译器是发射时才拒收 —— 直接导入会把{ $not: { $or: [null] } }的具名拒收换成一个裸 TypeError。复制owed 一个「漂移即红」的互钉,这就是新增的
turso-local-remote-null-parity.test.ts:同一个 driver 的两个 transport 跑同一批 filter,先断言两边相等,再断言等于裁决要求的行集 —— 只断相等会被「两边一起错」满足。验证
remote-transport.ts、测试与表保持新期望 →turso-remote-filter-logic-conformance上新入表的 N1/N4 两格 + count 聚合共 3 格转红,parity 套件红 11 格,而turso-filter-logic-conformance(LOCAL)全程全绿 —— 正是「分叉只在 remote」的正证。补回后全绿。$not $ne/$ne: null/$not $null/$not $exists四个反向用例仍是绿的。这是对的,也是这套用例的价值 —— 它们本来就已正确,而如果实现用的是一刀切OR col IS NULL,红的就会是它们。OS_TEST_MONGODB_MEMORY_SERVER_ENABLED=1显式跑通,该套件按 driver-mongodb 测试:两个套件并发下载 MongoDB 二进制,mongodb-memory-server 的 rename 竞态让全绿测试 exit 1(队列 flaky) #5517 默认 opt-out)、service-analytics 1120、formula 367、driver-turso local + remote。grep全仓FILTER_LOGIC_CASES消费方(11 个 harness + 1 个 ledger)与driver-turso/RemoteTransport的包外引用;driver-turso 之外无任何 fixture 钉 remote 的 SQL 串。node scripts/check-driver-conformance.mjsOK(driver-turso × FILTER_LOGIC = ok)。pnpm --filter @objectstack/driver-turso test745 passed / 23 files;@objectstack/spec8309 passed / 325 files;两包typecheckDone;eslint干净;check:generated10/10 up to date;check-nul-bytesOK + 控制字符自扫描零命中。efedd289f动了 packages/spec)后按 §9/§10 重建 spec 并全部重跑,结果同上。changeset:
@objectstack/driver-tursopatch,带升级说明(依赖 remote 静默丢弃无值行的查询要改写成显式$and+$null: false)。🤖 Generated with Claude Code
https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
Generated by Claude Code