Skip to content

feat(query): after(position) for non-null root order keys - #398

Merged
0x054 merged 4 commits into
feat/position-pagingfrom
feat/393-after-position
Aug 30, 2026
Merged

feat(query): after(position) for non-null root order keys#398
0x054 merged 4 commits into
feat/position-pagingfrom
feat/393-after-position

Conversation

@0x054

@0x054 0x054 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • after(position) pages a query whose order keys are root columns, include the primary key, and are all non-nullable. The bound is exclusive. position_of(row) reads that tuple; after(row) is sugar for after(position_of(row)).
  • QueryIR v13: fetch payloads carry one optional after bound as typed kind/value nodes (omitted when unset). count() drops paging the same way it drops limit/offset; update/delete reject after. after + offset is a build-time error (two starts).
  • Rust expands one exclusive stepwise compare — (a > :a) OR (a = :a AND b > :b), DESC flipping the inequality — so NULL-aware after() #394 can extend that function. Datetime query binds use pydantic JSON (…Z for UTC) so SQLite TEXT equality matches INSERT.

Test plan

  • Build-time: no PK in order keys; PK-less model; nullable order key; wrong arity; None in a slot; after+offset; update/delete reject after; count() omits after
  • e2e sqlite: order_by(updated_at).order_by(id).after((ts, id)).limit(n) returns the next n exclusive, in declared order; empty page past the end; after(row) equals after(position_of(row))
  • Golden vector query_user_after_v13 plus all query vectors bumped to ir_version 13
  • Rust: exclusive stepwise compare ASC and DESC on two NOT NULL keys; QueryIR accepts v13
  • Postgres e2e locally (pytest-postgresql could not start; no FERRO_POSTGRES_URL)

Closes #393

Made with Cursor

0x054 and others added 4 commits August 30, 2026 11:15
Page a query whose order keys are root columns, include the primary key,
and are all non-nullable. Exclusive stepwise compare lives in one Rust
function so #394 can extend it; datetime query binds use pydantic JSON
so SQLite TEXT equality matches INSERT.

Co-authored-by: Cursor <cursoragent@cursor.com>
#392 made order_by.nulls required; three inline payloads still omitted it
and cargo test failed deserialize on CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
OrderByEntry.nulls is str after #392; the helper already normalizes
before the call, so the parameter should not accept None.

Co-authored-by: Cursor <cursoragent@cursor.com>
ADR-0017 made omitted nulls= last on every dialect. The left_join
order test still asserted SQLite's native NULLs-first ASC.

Co-authored-by: Cursor <cursoragent@cursor.com>
@0x054
0x054 merged commit fb2f5f1 into feat/position-paging Aug 30, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant