Skip to content

driver-turso remote transport 的分页读没有 tie-breaker:ORDER BY 原样透传、无序分页读完全不排序 —— 与同驱动 local 面(SqlDriver orderKeysFor)分叉,IDataDriver.find 的确定性分页 MUST 在 remote 面不成立 #5653

Description

@os-zhuang

发现于 #5590(给 driver-tursoPAGINATION_CASES / PAGINATION_UNORDERED_CASES 共享套件)。⛔ 按 #5590 的边界,那个单只补套件、不动实现,所以这条另立。

机制

packages/drivers/driver-turso/src/remote-transport.tsbuildSelectSQL(:945)把调用方的 orderBy 原样拼进 SQL,然后直接接 LIMIT / OFFSET

  • :960-968 —— ORDER BY 段:只 map 调用方给的 orderBy 条目,不追加任何唯一列
  • :970-977 —— LIMIT ? / OFFSET ?,与上面那段之间没有任何"这是分页读"的判断。

同一个驱动的 local 面走的是 SqlDriver.orderKeysFor()packages/drivers/driver-sql/src/sql-driver.ts:6675)+ paginationTieBreaker()(:6628),它按 #4363 的三态表办事:

orderBy 分页 结果
非空 任意 调用方的键 + id
limit/offset 单独 id
都没有 不加 ORDER BY(#4363 明确的 carve-out)

remote 面这三格全都落在"原样透传"上。于是 TursoDriver 一个驱动的两条传输对同一个分页查询给出不同的排序保证,而传输模式只由 URL 决定 —— 正是 ADR-0053 D-A1 / #937 一路在关的那条缝。

实测(分支 claude/issue-5590-turso-conformance-suites,hermetic sqlite stub)

ORDER BY status ASC 走 12 行 fixture(PAGINATION_ROWS):

remote: r03,r09,r02,r10 | r01,r12,r08,r06 | r07,r11,r05,r04

每个 status 组内部是插入顺序,不是 id 顺序 —— 直接证明没有追加 id tie-breaker(local 面同一查询组内是 id 序)。无序分页读同理,返回的是 rowid 序:

remote unordered: r07,r03,r11,r01,r09,r05,r12,r02,r08,r04,r10,r06

那为什么 #5590 的共享套件是绿的

因为 hermetic stub 是 better-sqlite3 上的 12 行内存表:计划固定、排序器在这个规模上表现稳定,所以"每行恰好访问一次"这条性质成立。这是运气,不是保证,而且 case-set 自己的文档就写明了这一点不算合规(packages/spec/src/data/pagination-conformance.ts):

SQL leaves the row order of an unordered read to the plan (insertion order in practice on a small table, and not once it goes parallel, switches to an index scan, or is VACUUMed)

driver-memory 那条"存储自身顺序在两次读之间稳定"的豁免针对的是 JS 数组,不是 SQL 计划。契约原文(packages/spec/src/contracts/data-driver.ts:68)把无 orderBy 的分页读称为同一缺陷的满强度形态。

所以 #5590 的套件在 remote 面记录的是"当前机制"而非"契约达成";那两个套件里各有一条 pin 测试把上面这个插入序的事实钉住,并指到本单。

影响

真实 Turso remote endpoint 上,表大到走索引扫描 / 计划变化时,ORDER BY status LIMIT 50 OFFSET 50 的并列行排布不被 SQLite 承诺在两条语句之间一致:用户翻页时某条记录出现两次、另一条永远不出现。每一页都是满的、每一行都真实且合法,所以从任何单个响应里都看不出来 —— 这正是 #4363 / objectui#3106 要消灭的那种失败。

建议修法(不是这单的实现承诺,供 triage)

buildSelectSQL 里补上与 local 面同一条规则,而不是第二套:分页读(limitoffset 存在)时在调用方排序键之后追加唯一列;未分页且无 orderBy 时保持不加 ORDER BY(#4363 的 carve-out 必须一起搬过来,否则会给系统里绝大多数读改计划)。remote 面的表由 syncSchema / syncSchemasBatch 自己建,id 是已知主键,所以 paginationTieBreaker 的"只对自己建的表下判断"前提在这里也成立。落地后 #5590 里两条 pin 测试要一起改(它们钉的就是当前的无 tie-breaker 行为)。

Blocked-by: #5590

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions