Skip to content

fix(driver-turso): remote 模式对齐 NULL-safe 语义 —— $not/$ne/$nin/$notContains 四算子 + $exists 拒收闸 (#5903) - #6047

Merged
os-zhuang merged 5 commits into
mainfrom
claude/issue-5903-turso-remote-null-safe
Aug 7, 2026
Merged

fix(driver-turso): remote 模式对齐 NULL-safe 语义 —— $not/$ne/$nin/$notContains 四算子 + $exists 拒收闸 (#5903)#6047
os-zhuang merged 5 commits into
mainfrom
claude/issue-5903-turso-remote-null-safe

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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:

filter LOCAL REMOTE(修复前)
{ 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. $not NULL-safe(#5146) — 取反前先把操作数逐叶 totalise,NOT (…) 对每一行都是 TRUE/FALSE 而不落入 SQL 的 UNKNOWN。

  • 守卫挂在叶子而不是 NOT 上:De Morgan 只对二值叶子成立,把守卫提到 $not 之上(操作数是 $or 时)会把 JS 后端排除的行放回来。
  • 极性按算子各自的答案,不是一律 OR col IS NULL:{ $not: { a: { $ne: 5 } } } 意思是「a 是 5」,无值行必须排除;一刀切会把它排除的行原样送回 —— 本文件反复付过学费的静默放宽。
  • 守卫以 filter 树结构发射($null 约束 + $and/$or 节点)交回 buildWhereSQL,所以每一对括号都是那个编译器自己写的。

2. $ne / $nin / $notContains 非否定路径 NULL-safe(#5298) — 发射 (col IS NULL OR <原谓词>)

  • 统一 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 之后)。选择复制而非导出,理由写在文件头:

  1. remote-transport.ts 的模块契约明写「No local SQLite or Knex dependency」,而 driver-sql 的入口会拉进 knex;
  2. 非否定路径上的 $ne / $nin / $notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了 $not) #5298 立下的不变量是「每张极性表钉的是它自己发射器的拼写」(read-scope-sql 按真值读 $null,driver-sql 按恒等读,各自匹配自家发射器),而不是「三份源码字面相同」;
  3. 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,红的就会是它们。
  • 11 个 harness 全绿:driver-sql 878、driver-memory 524、driver-sqlite-wasm 254、driver-mongodb translation 34 + 真 mongod conformance 26(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.mjs OK(driver-turso × FILTER_LOGIC = ok)。
  • pnpm --filter @objectstack/driver-turso test 745 passed / 23 files;@objectstack/spec 8309 passed / 325 files;两包 typecheck Done;eslint 干净;check:generated 10/10 up to date;check-nul-bytes OK + 控制字符自扫描零命中。
  • 合入 origin/main(efedd289f 动了 packages/spec)后按 §9/§10 重建 spec 并全部重跑,结果同上。

changeset:@objectstack/driver-turso patch,带升级说明(依赖 remote 静默丢弃无值行的查询要改写成显式 $and + $null: false)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx


Generated by Claude Code

claude added 2 commits August 6, 2026 15:31
…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
@vercel

vercel Bot commented Aug 6, 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 6, 2026 11:59pm

Request Review

…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
@os-zhuang os-zhuang closed this Aug 6, 2026
@os-zhuang os-zhuang reopened this Aug 6, 2026
claude added 2 commits August 6, 2026 22:13
…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
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-turso, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/driver-turso, @objectstack/spec)
  • content/docs/deployment/environment-variables.mdx (via @objectstack/driver-turso)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/driver-turso)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-turso)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-turso, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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.

@os-zhuang
os-zhuang marked this pull request as ready for review August 7, 2026 00:32
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 8a2ea6c Aug 7, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5903-turso-remote-null-safe branch August 7, 2026 00:53
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31135524302 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 78 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

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 protocol:data size/xl tests tooling

Projects

None yet

2 participants