Chunkr.PaginatedQueries (Chunkr v0.1.0) View Source
Provides a set of macros for generating functions to assist with paginating queries. For example:
defmodule MyApp.PaginatedQueries do
use Chunkr.PaginatedQueries
paginate_by :user_created_at do
sort :desc, as(:user).inserted_at
sort :desc, as(:user).id, type: :binary_id
end
paginate_by :user_name do
sort :asc, fragment("lower(coalesce(?, 'zzz')"), as(:user).name).inserted_at
sort :desc, as(:user).id, type: :binary_id
end
end
The paginate_by/1
macro above takes a query name and sets up the necessary beyond_cursor/4
,
apply_order/4
, and apply_select/2
functions based on the number of sort options passed in the
block as well as the sort directions specified.
Each call to sort/3
must include the sort direction, the field to be sorted, and an optional
:type
keyword. If :type
is provided, the cursor value will be cast as that type for the
sake of comparisons. See Ecto.Query.API.type/2.
Ordering
In keyset-based pagination, it is essential that results are deterministically ordered, otherwise you may see unexpected results. Therefore, the final column used for sorting must always be unique and non-NULL.
Ordering of paginated results can be based on columns from the primary table, any joined table, any subquery, or any dynamically computed value based on other fields. Regardless of where the column resides, named bindings are always required…
Named bindings
Because these sort/3
clauses must reference bindings that have not yet been established, each
sort clause must use :as
to take advantage of late binding. A parallel :as
must then be used
within the query that gets passed to Chunkr.paginate/3
or the query will fail. See
Ecto Named bindings for more.
NULL values in sort fields
When using comparison operators in SQL, records involving comparisons against NULL
get dropped.
This is generally undesirable for pagination, as the goal is usually to work your way through an
entire result set in chunks—not just through the part of the result set that doesn't have NULL
values in the important fields. For example, when sorting users by [last name, first name,
middle name], you most likely don't want to exclude users without a known middle name.
To work around this awkwardness, you'll need to pick a value that is almost sure to come before
or after the rest of your results (depending on whether you want NULL
values to sort to the
beginning or the end of your results). It's not good enough to think you can simply use a strategy
like ordering by NULLS LAST
because the filtering of values up to the cursor values will use
comparison operators—which will cause records with relevant NULL values to be dropped entirely.
The following fragment
example sets up names to be compared in a case-insensitive fashion
and places records with a NULL
name at the end of the list (assuming no names will sort beyond
"zzz"!).
sort :asc, fragment("lower(coalesce(?, 'zzz')"), as(:user).name).inserted_at
Limitations
Note that Chunkr limits the number of sort
clauses to 4.
Link to this section Summary
Functions
Implements the functions necessary for pagination.
Link to this section Functions
Implements the functions necessary for pagination.
paginate_by :user_id do
sort :asc, as(:user).id
end