Skip to content

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

Queued
os-zhuang wants to merge 5 commits into
mainfrom
claude/issue-5903-turso-remote-null-safe
Queued

fix(driver-turso): remote 模式对齐 NULL-safe 语义 —— $not/$ne/$nin/$notContains 四算子 + $exists 拒收闸 (#5903)#6047
os-zhuang wants to merge 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
Any commits made after this event will not be merged.
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