2022-08-30 20:22:15 +12:00
# Pagination
2024-01-12 04:04:20 +13:00
Pagination when reading records is configured on a per-action basis. Ash supports two kinds of pagination: `keyset` and `offset` .
A single action can use both kinds of pagination if desired, but typically you would use one or the other.
For information on configuring actions to support pagination, see `d:Ash.Resource.Dsl.actions.read|prepare` .
> #### Counting records {: .tip}
>
> When calling an action that uses pagination, the full count of records can be requested by adding the option `page: [count: true]`.
> Note that this will perform a similar query a second time to fetch the count, which can be expensive on large data sets.
2022-08-30 20:22:15 +12:00
## Offset Pagination
2024-01-12 04:04:20 +13:00
Offset pagination is done via providing a `limit` and an `offset` when making queries.
* The `limit` determines how many records should be returned in the query.
* The `offset` describes how many records from the beginning should be skipped.
Using this, you might make requests like the following:
2022-08-30 20:22:15 +12:00
```elixir
# Get the first ten records
Api.read(Resource, page: [limit: 10])
2024-01-12 04:04:20 +13:00
# or by using an action named `read` directly
Resource.read(page: [limit: 10])
# Get the next ten records
2022-08-30 20:22:15 +12:00
Api.read(Resource, page: [limit: 10, offset: 10])
2024-01-12 04:04:20 +13:00
# or by using an action named `read` directly
Resource.read(page: [limit: 10, offset: 10])
2022-08-30 20:22:15 +12:00
```
2024-01-12 04:04:20 +13:00
Next/previous page requests can also be made in memory, using an existing page of search results:
```elixir
# Return page three of search results
{:ok, third_page} = Resource.read(page: [limit: 10, offset: 20])
# Use `:prev` and `:next` to go backwards and forwards.
2024-01-12 18:16:41 +13:00
# `:first`, `:last`, `:self` and specifying a page number are also supported.
2024-01-12 04:04:20 +13:00
{:ok, second_page} = Api.page(third_page, :prev)
{:ok, fourth_page} = Api.page(third_page, :next)
```
### Pros of offset pagination
2022-08-30 20:22:15 +12:00
- Simple to think about
- Possible to skip to a page by number. E.g the 5th page of 10 records is `offset: 40`
- Easy to reason about what page you are currently on (if the total number of records is requested)
2024-01-12 04:04:20 +13:00
- Can go to the last page (though data may have changed between calculating the last page details, and requesting it)
2022-08-30 20:22:15 +12:00
2024-01-12 04:04:20 +13:00
### Cons of offset pagination
2022-08-30 20:22:15 +12:00
2022-10-26 04:11:54 +13:00
- Does not perform well on large datasets (if you have to ask if your dataset is "large", it probably isn't)
2024-01-12 04:04:20 +13:00
- When moving between pages, if data was created or deleted, individual records may be missing or appear on multiple pages
2022-08-30 20:22:15 +12:00
## Keyset Pagination
2024-01-12 04:04:20 +13:00
Keyset pagination is done via providing an `after` or `before` option, as well as a `limit` .
* The `limit` determines how many records should be returned in the query.
* The `after` or `before` value should be a `keyset` value that has been returned from a previous request. Keyset values are returned whenever there is any read action on a resource that supports keyset pagination, and they are stored in the `__metadata__` key of each record.
> #### Keysets are directly tied to the sorting applied to the query {: .warning}
>
> You can't change the sort applied to a request being paginated, and use the same keyset. If you want to change the sort, but *keep* the record who's keyset you are using in the `before` or `after` option, you must first request the individual record, with the new sort applied. Then, you can use the new keyset.
2022-08-30 20:22:15 +12:00
For example:
```elixir
2024-01-12 04:04:20 +13:00
{:ok, page} = Api.read(Resource, page: [limit: 10])
# Returns `{:ok, %Ash.Page.Keyset{results: [...], before: nil, after: nil}}`
# The `before`/`after` values are the keysets used for this request.
2022-08-30 20:22:15 +12:00
2024-01-12 04:04:20 +13:00
# Fetch the keyset for the next request from the results list
2022-08-30 20:22:15 +12:00
last_record = List.last(page.results)
2024-01-12 04:04:20 +13:00
# Returns `%Resource{__metadata__: %{keyset: "g2wAAAABbQAAACQzOWNjNTcwNy00NjlmL..."}, ...}``
# Use this keyset value to fetch the next page
{:ok, next_page} = Api.read(Resource, page: [limit: 10, after: last_record.__metadata__.keyset])
```
Like offset pagination, next/previous page requests can also be made in memory, using an existing page of search results:
```elixir
# Return page three of search results
{:ok, third_page} = Resource.read(page: [limit: 10])
2022-08-30 20:22:15 +12:00
2024-01-12 04:04:20 +13:00
# Use `:prev` and `:next` to go backwards and forwards.
2024-01-12 18:16:41 +13:00
# `:first` and `:self` can also be used, but `:last` and specifying a page number are not supported.
2024-01-12 04:04:20 +13:00
{:ok, second_page} = Api.page(third_page, :prev)
{:ok, fourth_page} = Api.page(third_page, :next)
2022-08-30 20:22:15 +12:00
```
2024-01-12 04:04:20 +13:00
### Pros of keyset pagination
2022-08-30 20:22:15 +12:00
- Performs very well on large datasets (assuming indices exist on the columns being sorted on)
- Behaves well as data changes. The record specified will always be the first or last item in the page
2024-01-12 04:04:20 +13:00
### Cons of keyset paginations
2022-08-30 20:22:15 +12:00
2022-10-26 04:11:54 +13:00
- A bit more complex to use
2022-08-30 20:22:15 +12:00
- Can't go to a specific page number
2023-10-03 11:56:48 +13:00
2024-01-12 04:04:20 +13:00
## Example implementation
2023-10-03 11:56:48 +13:00
2024-01-12 04:04:20 +13:00
### Setting up the resource
2023-10-03 11:56:48 +13:00
2024-01-12 04:04:20 +13:00
Add the `pagination` macro call to the [action ](glossary.md#action ) of the [resource ](glossary.md#resource ) that you want to be paginated.
2023-10-03 11:56:48 +13:00
2024-01-12 04:04:20 +13:00
```elixir
defmodule AppName.ResourceName do
use Ash.Resource
actions do
read :read_action_name do
pagination offset?: true, default_limit: 3, countable: true
end
# ...
2023-10-03 11:56:48 +13:00
```
2024-01-12 04:04:20 +13:00
For all available pagination options, see `d:Ash.Resource.Dsl.actions.read|pagination` .
### Querying the paginated resource
For all available querying options, see [`Ash.Api.read.pagination` ](https://hexdocs.pm/ash/Ash.Api.html#c:read/2-pagination ).
2023-10-03 11:56:48 +13:00
2024-01-12 04:04:20 +13:00
> #### Check the updated query return type! {: .info}
> Pagination will modify the return type of calling the query action.
>
> Without pagination, Ash will return a list of records.
>
> But _with_ pagination, Ash will return an `Ash.Page.Offset` struct (for offset pagination) or `Ash.Page.Keyset` struct (for keyset pagination). Both structs contain the list of records in the `results` key of the struct.