fix(driver-turso): remote 模式对齐 NULL-safe 语义 —— $not/$ne/$nin/$notContains 四算子 + $exists 拒收闸 (#5903) - #6047
Merged
Merged
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
Contributor
📓 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:
|
Contributor
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31135524302 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
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 #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