Bylaw.Ecto.Query.Checks.DeterministicOrder
(bylaw_ecto_query v0.1.0-alpha.1)
Copy Markdown
View Source
Validates that ordered queries include the root schema primary key.
This is useful when callers page through ordered rows or use helpers such as
Repo.one/2 with Ecto.Query.first/2 or Ecto.Query.last/2. Ordering by a
non-unique field such as :inserted_at or :name leaves rows with the same
value free to move between executions unless the query also orders by a
deterministic tie-breaker.
For now, this check only trusts the root Ecto schema primary key. Ecto schemas do not expose arbitrary database unique indexes, and this check should not ask callers to manually assert uniqueness that Bylaw cannot verify. If a query is intentionally ordered by another unique database key, use the explicit escape hatch until a DB-aware check can verify those constraints directly.
Examples
Bad:
from(Post, as: :post)
|> order_by([post: p], desc: p.inserted_at)
|> limit(10)Why this is bad:
inserted_at is not guaranteed to be unique. Rows with the same timestamp can
move between executions, which can make paginated queries skip or duplicate
rows.
Better:
from(Post, as: :post)
|> order_by([post: p], desc: p.inserted_at)
|> order_by([post: p], asc: p.id)
|> limit(10)Why this is better:
The root primary key resolves ties in the visible sort key, so every row has a stable relative position.
Better for a composite primary key:
from(Membership, as: :membership)
|> order_by([membership: mem], asc: mem.inserted_at)
|> order_by([membership: mem], asc: mem.organization_id)
|> order_by([membership: mem], asc: mem.sequence)Notes
This check only trusts the root Ecto schema primary key. It cannot verify arbitrary unique database indexes or schema-less query sources.
The check is static. It infers root schema primary keys with Ecto schema reflection. Schema-less queries and schemas without primary keys cannot be proven deterministic by this check, so ordered queries in those cases return an issue unless validation is explicitly disabled.
Options
:validate- explicitfalsedisables the check. Defaults totrue.
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
Implements the Bylaw.Ecto.Query.Check validation callback.
Functions
@spec validate( Bylaw.Ecto.Query.Check.operation(), Bylaw.Ecto.Query.Check.query(), opts() ) :: Bylaw.Ecto.Query.Check.result()
Implements the Bylaw.Ecto.Query.Check validation callback.