Skip to content

feat(orm): chunk_by_id_desc + Laravel orderedChunkById parity#167

Merged
tmgbedu merged 1 commit into
mainfrom
task/orm-chunk-desc-869
Jul 11, 2026
Merged

feat(orm): chunk_by_id_desc + Laravel orderedChunkById parity#167
tmgbedu merged 1 commit into
mainfrom
task/orm-chunk-desc-869

Conversation

@tmgbedu

@tmgbedu tmgbedu commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #166. Brings the async ORM's keyset chunking to full parity with Laravel's orderedChunkById and adds the descending variant, without changing the already-merged chunk() / chunk_by_id() public behaviour.

async for items in Item.chunk_by_id_desc(100):
    for item in items:
        print(item.id)   # 10, 9, 8, ...

What changed

  • QueryBuilder.chunk_by_id_desc(count, column=None, alias=None) + Model.chunk_by_id_desc(...) — descending keyset pagination (ORDER BY col DESC, WHERE col < last_seen), mirroring Laravel chunkByIdDesc. Works as a classmethod and chained after builder methods.
  • orderedChunkById parity in chunk_by_id:
    • Honours a pre-set builder offset (first page only) and limit with limit-remaining semantics — e.g. .limit(250) chunked by 100 yields 100 + 100 + 50.
    • Alias guard: raises RuntimeError when the alias column is absent from the result set (the gap flagged in the feat(orm): Laravel-style chunk() and chunk_by_id() on the async ORM #166 review), read from raw attributes so the model caster doesn't mask it.
    • Adds a descending flag (default False) shared with chunk_by_id_desc.

No public behaviour change

chunk() and chunk_by_id() behave exactly as merged in #166: the new descending flag defaults to False, and offset/limit are only consulted when the caller set them.

Testing

Chunk test suite rewritten in unittest assertion style (repo precedent: task #357), on a dedicated seeded chunk_items sqlite table (ids 1..10) plus the seeded countries table for a non-id primary key. Covers: ascending/descending ordering and batch sizes, where(...) chaining, offset/limit-remaining, the alias-missing guard, a dedicated alias test, and keyset delete-safety in both directions.

  • uv run pytest --ignore=tests/masoniteorm/postgres --cov1799 passed, 7 skipped, coverage 78.68% (>= fail_under 68).
  • ruff check + ruff format --check clean.

Separate from #166 — this PR should get its own review pass.

Extend keyset chunking to full Laravel orderedChunkById parity:

- chunk_by_id_desc(size, column, alias): descending keyset pagination
  (ORDER BY col DESC, WHERE col < last_seen), as a Model classmethod and
  builder-chainable, mirroring Laravel chunkByIdDesc.
- chunk_by_id now honours a pre-set builder offset (first page only) and
  limit with limit-remaining semantics, e.g. limit(250) chunked by 100
  yields 100 + 100 + 50; and raises when the alias column is absent from
  the result set.

The chunk()/chunk_by_id() public behaviour is unchanged: the new
descending flag defaults to False and offset/limit are only consulted
when the caller set them.

Rewrites the chunk test suite in unittest assertion style on a dedicated
seeded sqlite table, covering ascending/descending ordering, where
chaining, offset/limit-remaining, the alias guard, and delete-safety in
both directions.
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.66667% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...src/fastapi_startkit/masoniteorm/models/builder.py 96.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@tmgbedu tmgbedu merged commit 9f01c54 into main Jul 11, 2026
6 checks passed
@tmgbedu tmgbedu deleted the task/orm-chunk-desc-869 branch July 11, 2026 19:25
@tmgbedu

tmgbedu commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Code Review Verdict — APPROVE ✅ (posted as comment; GitHub blocks self-approval on this account)

Reviewed the full diff (builder.py, model.py, test_model_chunk.py — 176/125, no unrelated files touched). Verified statically and reproduced all claims locally from fastapi_startkit/.

Correctness — verified

  • Descending keyset: operator="<" / direction="desc" correctly implement ORDER BY col DESC, WHERE col < last_seen. Delete-safety holds — confirmed by seeded test + my own probes.
  • Offset on first page only: self._offset = offset if page == 1 else False — page 1 applies pre-set offset with no keyset (last_id None); later pages drop offset and drive via keyset. No double-skip. offset=0 compiles to no OFFSET (process_offset returns "" for falsy), consistent with the False sentinel.
  • Limit-remaining: remaining captured once from self._limit, decremented per page, limit=min(count,remaining), if limit==0: break. Proven for limit(250)/100 → 100+100+50 (scaled probe limit(7)/3 → 3+3+1; exact-multiple limit(6)/3 → 3+3 no trailing empty batch). Identical descending (limit(4)/3 desc → [10,9,8],[7]).
  • Alias guard: results.last().get_attributes().get(alias) + RuntimeError on None fires correctly for select("name").chunk_by_id(alias="id"). is None checks (not truthiness) correctly allow a legit 0 keyset value.
  • Termination: count_results != count == < count here (limit<=count); no infinite loop.

Laravel fidelity

Mirrors chunkByIdDesc/orderedChunkById — offset first-page-only, limit-remaining, alias abort. Good parity.

Tests

Real sqlite-backed unittest-style on seeded chunk_items + countries (non-id PK), meaningful id-sequence/size/exception assertions, asc+desc delete-safety. No hollow mocks — meets the PR #159 bar.

Reproduced independently (from fastapi_startkit/)

  • pytest --ignore=tests/masoniteorm/postgres --cov1799 passed, 7 skipped, coverage 78.68% (≥ 68) ✅
  • ruff check (changed + full) → All checks passed
  • Chunk suite: 22 passed ✅

Non-blocking notes (optional)

  1. Alias-None RuntimeError is evaluated before the terminal break, so it also runs on the final partial page. Harmless with the default non-null unique PK alias; only a nullable keyset column with a NULL last row would raise instead of ending cleanly — matches Laravel late-failure. Noting only.
  2. Descending on a non-id PK isn't directly tested (only ascending); shared code path makes it low-risk, but a chunk_by_id_desc(column=...) case would round it out.

No behaviour change to the merged chunk()/chunk_by_id() public API. Verdict: APPROVE — safe to merge (PM/maintainer to perform merge).

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