Bylaw.Ecto.Query.Checks.RequiredOrder (bylaw_ecto_query v0.1.0-alpha.1)

Copy Markdown View Source

Validates that query shapes requiring stable row order include order_by.

This check only answers whether an order_by clause is required and present. It intentionally does not decide whether the existing order is deterministic; use Bylaw.Ecto.Query.Checks.DeterministicOrder for that separate question.

Examples

Bad:

from(Post, as: :post)
|> limit(10)

Why this is bad:

A limited query without order_by asks the database for any matching 10 rows. Pagination, retries, and repeated calls can see rows appear, disappear, or move because the selected window has no stable order.

Better:

from(Post, as: :post)
|> order_by([post: p], desc: p.inserted_at)
|> limit(10)

Why this is better:

The selected window is taken from a declared order, so callers can reason about which rows are first.

Bad:

from(Post, as: :post)
|> offset(50)
|> limit(25)

Why this is bad:

offset skips an undefined set of rows when no order exists. Page boundaries can shift between executions.

Better:

from(Post, as: :post)
|> order_by([post: p], desc: p.inserted_at)
|> offset(50)
|> limit(25)

Why this is better:

Rows are skipped from a known order, so the page boundary has a defined meaning.

Bad:

from(Post, as: :post)
|> Repo.stream()

Better:

from(Post, as: :post)
|> order_by([post: p], asc: p.id)
|> Repo.stream()

Notes

This check only requires that some order_by exists. It does not prove that the order is deterministic. If rows can tie on the ordered field, pair this check with DeterministicOrder to require a primary-key tie-breaker:

from(Post, as: :post)
|> order_by([post: p], desc: p.inserted_at)
|> order_by([post: p], asc: p.id)
|> limit(10)

Ecto rewrites Repo.exists?/2 queries to select 1 with limit 1. This synthetic limit is ignored because existence checks do not depend on which row is returned. A preserved offset still requires ordering because the skipped rows are otherwise undefined.

Options

  • :validate - explicit false disables the check. Defaults to true.

Queries with limit, offset, or the :stream operation require an order_by clause. If any order_by exists, this check passes and leaves deterministic tie-breaker validation to DeterministicOrder.

Usage

Add this module to the explicit check list passed through Bylaw.Ecto.Query. See Bylaw.Ecto.Query for the full Ecto.Repo.prepare_query/3 setup.

Summary

Functions

validate(operation, query, opts)

Implements the Bylaw.Ecto.Query.Check validation callback.