From 25acc2d79387b0dd3672091f0436e4ebf219f7fb Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 9 Jun 2023 15:08:49 -0400 Subject: [PATCH] improvement: better keyset pagination behavior on first and last pages --- lib/ash/api/api.ex | 32 +++++++++++++++++++++++++++----- test/actions/pagination_test.exs | 11 +++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/ash/api/api.ex b/lib/ash/api/api.ex index 5fe7f58d..5aa612d5 100644 --- a/lib/ash/api/api.ex +++ b/lib/ash/api/api.ex @@ -1541,17 +1541,27 @@ defmodule Ash.Api do {:ok, page} end - def page(_, %Ash.Page.Keyset{results: []} = page, :prev) do + def page(_, %Ash.Page.Keyset{before: nil, after: nil} = page, :prev) do {:ok, page} end + def page(api, %Ash.Page.Keyset{results: [], before: before, rerun: {query, opts}}, :prev) + when not is_nil(before) do + new_page_opts = + opts[:page] + |> Keyword.delete(:before) + |> Keyword.put(:after, before) + + read(api, query, Keyword.put(opts, :page, new_page_opts)) + end + def page(_, %Ash.Page.Keyset{}, n) when is_integer(n) do {:error, "Cannot seek to a specific page with keyset based pagination"} end def page( api, - %Ash.Page.Keyset{results: results, rerun: {query, opts}}, + %Ash.Page.Keyset{results: results, rerun: {query, opts}} = page, :next ) do last_keyset = @@ -1565,10 +1575,16 @@ defmodule Ash.Api do |> Keyword.delete(:before) |> Keyword.put(:after, last_keyset) - read(api, query, Keyword.put(opts, :page, new_page_opts)) + case read(api, query, Keyword.put(opts, :page, new_page_opts)) do + {:ok, %{results: []}} -> + {:ok, page} + + other -> + other + end end - def page(api, %Ash.Page.Keyset{results: results, rerun: {query, opts}}, :prev) do + def page(api, %Ash.Page.Keyset{results: results, rerun: {query, opts}} = page, :prev) do first_keyset = results |> List.first() @@ -1580,7 +1596,13 @@ defmodule Ash.Api do |> Keyword.put(:before, first_keyset) |> Keyword.delete(:after) - read(api, query, Keyword.put(opts, :page, new_page_opts)) + case read(api, query, Keyword.put(opts, :page, new_page_opts)) do + {:ok, %{results: []}} -> + {:ok, page} + + other -> + other + end end def page(api, %Ash.Page.Keyset{rerun: {query, opts}}, :first) do diff --git a/test/actions/pagination_test.exs b/test/actions/pagination_test.exs index 67621ac4..d9ed4a94 100644 --- a/test/actions/pagination_test.exs +++ b/test/actions/pagination_test.exs @@ -765,6 +765,17 @@ defmodule Ash.Actions.PaginationTest do assert %{results: [%{name: "3"}]} = page = Api.page!(page, :next) assert %{results: [%{name: "4"}]} = Api.page!(page, :first) end + + test "the prev request right after the initial query remains the same as the initial result (like offset pagination)" do + assert %{results: [%{name: "4"}]} = + page = + User + |> Ash.Query.sort(name: :desc) + |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"]) + |> Api.read!(action: :keyset, page: [limit: 1]) + + assert %{results: [%{name: "4"}]} = page = Api.page!(page, :prev) + end end describe "when both are supported" do