Pagination not working properly with timestamps

I have a resource that uses pagination and also sort by inserted_at

Until version 2.18 it worked fine even using timestamps(). Currently I’m on master and I noticed that pagination only works properly when I use timestamps(private?: false)

I tried to create a test based on pagination_test.exs (from master)

defmodule Ash.Actions.PaginationTest do
  use ExUnit.Case, async: true

  require Ash.Query

  defmodule Post do
    @moduledoc false
    use Ash.Resource, data_layer: Ash.DataLayer.Ets

    ets do
      private?(true)
    end

    attributes do
      uuid_primary_key :id

      attribute :user_id, :uuid
      attribute :body, :string

      timestamps()
    end

    actions do
      defaults [:create, :update, :destroy]

      read :read do
        primary? true
        pagination offset?: true, required?: true, default_limit: 25
      end

      read :read_and_sort do
        pagination keyset?: true, required?: true, default_limit: 25

        prepare build(sort: [inserted_at: :desc])
      end
    end

    relationships do
      belongs_to :user, Ash.Actions.PaginationTest.User, define_attribute?: false
    end
  end

  defmodule User do
    # same
  end

  defmodule Registry do
    @moduledoc false
    use Ash.Registry

    entries do
      entry User
      entry Post
    end
  end

  defmodule Api do
    use Ash.Api

    resources do
      registry Registry
    end
  end

# ... 

  describe "testing timestamp sort" do
    setup do
      for i <- 0..9 do
        user = Api.create!(Ash.Changeset.new(User, %{name: "#{i}"}))

        if i != 0 do
          for x <- 1..i do
            Api.create!(Ash.Changeset.new(Post, %{body: "#{i}-#{x}", user_id: user.id}))
          end
        end
      end

      :ok
    end

    @tag :wip
    test "todo" do

      page = Ash.Query.for_read(Post, :read_and_sort) |> Api.read!(page: [limit: 1])

      cursor = List.last(page.results).__metadata__.keyset

      page_2 = Ash.Query.for_read(Post, :read_and_sort) |> Api.read!(page: [limit: 1, after: cursor])

      dbg(page_2)
    end
  end
end

When calling the second read (using after and cursor) I got an error:

 ** (MatchError) no match of right hand side value: {#Ash.Query<resource: Ash.Actions.PaginationTest.Post, sort: [inserted_at: :desc, id: :asc], limit: 2, errors: [%Ash.Error.Query.NoSuchAttributeOrRelationship{attribute_or_relationship: :inserted_at, resource: Ash.Actions.PaginationTest.Post, changeset: nil, query: nil, error_context: [], vars: [], path: [:filter], stacktrace: #Stacktrace<>, class: :invalid}], select: [:id, :inserted_at, :user_id, :body, :updated_at]>, []}
     code: page_2 = Ash.Query.for_read(Post, :read_and_sort) |> Api.read!(page: [limit: 1, after: cursor])
     stacktrace:
       (ash 2.18.1) lib/ash/actions/read/read.ex:352: anonymous fn/5 in Ash.Actions.Read.do_read/4
       (ash 2.18.1) lib/ash/actions/read/read.ex:472: Ash.Actions.Read.maybe_in_transaction/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:200: Ash.Actions.Read.do_run/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:49: anonymous fn/3 in Ash.Actions.Read.run/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:48: Ash.Actions.Read.run/3
       (ash 2.18.1) lib/ash/api/api.ex:2415: Ash.Api.read!/3
       test/actions/pagination_test.exs:906: (test)

Observation:

  1. Does not use inserted_at to sort make the code works
  2. Uses timestamps(private?: false), instead of timestamps(), also make it works

Is this the expected behavior (using private?: false) or am I doing something wrong?

Interesting…I’m not sure what the root issue might be, but I’ve pushed a fix for that specific stack trace that might allow us to gather some more information. We were using left = right in a with statement, which was a typo as we meant to be using left <- right.

Well… after pull the error change at least. Still only works with private?: false :smiling_face_with_tear:

** (Ash.Error.Invalid) Input Invalid

     * No such attribute or relationship :inserted_at for Ash.Actions.PaginationTest.Post
         at filter
       (elixir 1.16.0) lib/process.ex:860: Process.info/2
       (ash 2.18.1) lib/ash/error/exception.ex:50: Ash.Error.Query.NoSuchAttributeOrRelationship.exception/1
       (ash 2.18.1) lib/ash/filter/filter.ex:2754: Ash.Filter.add_expression_part/3
       (ash 2.18.1) lib/ash/filter/filter.ex:2332: anonymous fn/3 in Ash.Filter.parse_expression/2
       (elixir 1.16.0) lib/enum.ex:4842: Enumerable.List.reduce/3
       (elixir 1.16.0) lib/enum.ex:2582: Enum.reduce_while/3
       (ash 2.18.1) lib/ash/filter/filter.ex:3566: Ash.Filter.parse_and_join/3
       (ash 2.18.1) lib/ash/filter/filter.ex:2376: Ash.Filter.add_expression_part/3
       (ash 2.18.1) lib/ash/filter/filter.ex:2332: anonymous fn/3 in Ash.Filter.parse_expression/2
       (elixir 1.16.0) lib/enum.ex:4842: Enumerable.List.reduce/3
       (elixir 1.16.0) lib/enum.ex:2582: Enum.reduce_while/3
       (ash 2.18.1) lib/ash/filter/filter.ex:256: Ash.Filter.parse_input/5
       (ash 2.18.1) lib/ash/query/query.ex:270: Ash.Query.filter_input/2
       (ash 2.18.1) lib/ash/actions/read/read.ex:1717: Ash.Actions.Read.keyset_pagination/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:1659: Ash.Actions.Read.do_paginate/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:297: anonymous fn/5 in Ash.Actions.Read.do_read/4
       (ash 2.18.1) lib/ash/actions/read/read.ex:472: Ash.Actions.Read.maybe_in_transaction/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:200: Ash.Actions.Read.do_run/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:49: anonymous fn/3 in Ash.Actions.Read.run/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:48: Ash.Actions.Read.run/3
       (ash 2.18.1) lib/ash/api/api.ex:2415: Ash.Api.read!/3
       test/actions/pagination_test.exs:906: Ash.Actions.PaginationTest."test testing timestamp sort ahoy"/1
       (ex_unit 1.16.0) lib/ex_unit/runner.ex:472: ExUnit.Runner.exec_test/2
       (stdlib 5.0.2) timer.erl:270: :timer.tc/2
       (ex_unit 1.16.0) lib/ex_unit/runner.ex:394: anonymous fn/6 in ExUnit.Runner.spawn_test_monitor/4
     code: page_2 = Ash.Query.for_read(Post, :read_and_sort) |> Api.read!(page: [limit: 1, after: cursor])
     stacktrace:
       (elixir 1.16.0) lib/process.ex:860: Process.info/2
       (ash 2.18.1) lib/ash/error/exception.ex:50: Ash.Error.Invalid.exception/1
       (ash 2.18.1) lib/ash/error/error.ex:606: Ash.Error.choose_error/2
       (ash 2.18.1) lib/ash/error/error.ex:260: Ash.Error.to_error_class/2
       (ash 2.18.1) lib/ash/actions/read/read.ex:257: Ash.Actions.Read.do_run/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:49: anonymous fn/3 in Ash.Actions.Read.run/3
       (ash 2.18.1) lib/ash/actions/read/read.ex:48: Ash.Actions.Read.run/3
       (ash 2.18.1) lib/ash/api/api.ex:2415: Ash.Api.read!/3
       test/actions/pagination_test.exs:906: (test)

Okay, try again :slight_smile:

1 Like

Yay!!! Working

Thank you :partying_face: :partying_face:

My pleasure! Thanks for the report :slight_smile:

1 Like