AE
Ash Elixir•2y ago
Blibs

Keyset pagination with calculations

I have the following action in my resource:
read :all_by_name_or_address do
argument :text, :string, allow_nil?: false

pagination keyset?: true, default_limit: 10, required?: true

filter expr(
fragment("? %> ?", normalized_full_address, ^arg(:text)) or
fragment("? %> ?", name, ^arg(:text))
)

prepare build(
sort: [
similarity: {:desc, %{address: arg(:text), name: arg(:text)}},
updated_at: :desc
]
)
end
read :all_by_name_or_address do
argument :text, :string, allow_nil?: false

pagination keyset?: true, default_limit: 10, required?: true

filter expr(
fragment("? %> ?", normalized_full_address, ^arg(:text)) or
fragment("? %> ?", name, ^arg(:text))
)

prepare build(
sort: [
similarity: {:desc, %{address: arg(:text), name: arg(:text)}},
updated_at: :desc
]
)
end
As you can see, that action uses a :similarity calculation during sort, the calculation is as follows:
calculations do
calculate :normalized_full_address_similarity,
:float,
expr(fragment("word_similarity(?, ?)", ^arg(:address), normalized_full_address)) do
argument :address, :string, allow_nil?: false
end

calculate :name_similarity,
:float,
expr(fragment("word_similarity(?, ?)", ^arg(:name), name)) do
argument :name, :string, allow_nil?: false
end

calculate :similarity,
:float,
expr(
fragment(
"greatest(?, ?)",
normalized_full_address_similarity(address: arg(:address)),
name_similarity(name: arg(:name))
)
) do
argument :address, :string, allow_nil?: false
argument :name, :string, allow_nil?: false
end
end
calculations do
calculate :normalized_full_address_similarity,
:float,
expr(fragment("word_similarity(?, ?)", ^arg(:address), normalized_full_address)) do
argument :address, :string, allow_nil?: false
end

calculate :name_similarity,
:float,
expr(fragment("word_similarity(?, ?)", ^arg(:name), name)) do
argument :name, :string, allow_nil?: false
end

calculate :similarity,
:float,
expr(
fragment(
"greatest(?, ?)",
normalized_full_address_similarity(address: arg(:address)),
name_similarity(name: arg(:name))
)
) do
argument :address, :string, allow_nil?: false
argument :name, :string, allow_nil?: false
end
end
If I run the action, it will work for the first page:
{:ok, reply} = PredefinedSchool.all_by_name_or_address(%{text: "Johnson Alternative"}, page: [limit: 5])
{:ok, reply} = PredefinedSchool.all_by_name_or_address(%{text: "Johnson Alternative"}, page: [limit: 5])
But now, if I try to get the second page, it fails:
after_keyset = List.last(reply.results).__metadata__.keyset

PredefinedSchool.all_by_name_or_address(%{text: "Johnson Alternative"}, page: [limit: 5, after: after_keyset])
after_keyset = List.last(reply.results).__metadata__.keyset

PredefinedSchool.all_by_name_or_address(%{text: "Johnson Alternative"}, page: [limit: 5, after: after_keyset])
5 Replies
Blibs
BlibsOP•2y ago
With the following error:
** (CaseClauseError) no case clause matching: {:halt, {:error, %Ash.Error.Query.InvalidCalculationArgument{calculation: :similarity, field: :address, message: "is required", value: nil, changeset: nil, query: nil, error_context: [], vars: [], path: [], stacktrace: #Stacktrace<>, class: :invalid}}}
(ash 2.9.27) lib/ash/filter/filter.ex:2076: anonymous fn/3 in Ash.Filter.parse_expression/2
(elixir 1.14.4) lib/enum.ex:4751: Enumerable.List.reduce/3
(elixir 1.14.4) lib/enum.ex:2514: Enum.reduce_while/3
(ash 2.9.27) lib/ash/filter/filter.ex:312: Ash.Filter.parse/5
(ash 2.9.27) lib/ash/actions/read.ex:1518: Ash.Actions.Read.apply_keyset_filter/2
(ash 2.9.27) lib/ash/actions/read.ex:1331: anonymous fn/4 in Ash.Actions.Read.data_field/3
(ash 2.9.27) lib/ash/engine/engine.ex:537: anonymous fn/2 in Ash.Engine.run_iteration/1
(ash 2.9.27) lib/ash/engine/engine.ex:558: anonymous fn/4 in Ash.Engine.async/2
(elixir 1.14.4) lib/task/supervised.ex:89: Task.Supervised.invoke_mfa/2
(elixir 1.14.4) lib/task/supervised.ex:34: Task.Supervised.reply/4
(stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
(ash 2.9.27) lib/ash/engine/engine.ex:552: Ash.Engine.async/2
(elixir 1.14.4) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
(ash 2.9.27) lib/ash/engine/engine.ex:702: Ash.Engine.start_pending_tasks/1
(ash 2.9.27) lib/ash/engine/engine.ex:323: Ash.Engine.run_to_completion/1
(ash 2.9.27) lib/ash/engine/engine.ex:252: Ash.Engine.do_run/2
(ash 2.9.27) lib/ash/engine/engine.ex:148: Ash.Engine.run/2
(ash 2.9.27) lib/ash/actions/read.ex:173: Ash.Actions.Read.do_run/3
(ash 2.9.27) lib/ash/actions/read.ex:96: Ash.Actions.Read.run/3
(feedback_cupcake 0.1.0) lib/feedback_cupcake/onboarding.ex:1: FeedbackCupcake.Onboarding.read/2
iex:8: (file)
** (CaseClauseError) no case clause matching: {:halt, {:error, %Ash.Error.Query.InvalidCalculationArgument{calculation: :similarity, field: :address, message: "is required", value: nil, changeset: nil, query: nil, error_context: [], vars: [], path: [], stacktrace: #Stacktrace<>, class: :invalid}}}
(ash 2.9.27) lib/ash/filter/filter.ex:2076: anonymous fn/3 in Ash.Filter.parse_expression/2
(elixir 1.14.4) lib/enum.ex:4751: Enumerable.List.reduce/3
(elixir 1.14.4) lib/enum.ex:2514: Enum.reduce_while/3
(ash 2.9.27) lib/ash/filter/filter.ex:312: Ash.Filter.parse/5
(ash 2.9.27) lib/ash/actions/read.ex:1518: Ash.Actions.Read.apply_keyset_filter/2
(ash 2.9.27) lib/ash/actions/read.ex:1331: anonymous fn/4 in Ash.Actions.Read.data_field/3
(ash 2.9.27) lib/ash/engine/engine.ex:537: anonymous fn/2 in Ash.Engine.run_iteration/1
(ash 2.9.27) lib/ash/engine/engine.ex:558: anonymous fn/4 in Ash.Engine.async/2
(elixir 1.14.4) lib/task/supervised.ex:89: Task.Supervised.invoke_mfa/2
(elixir 1.14.4) lib/task/supervised.ex:34: Task.Supervised.reply/4
(stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
(ash 2.9.27) lib/ash/engine/engine.ex:552: Ash.Engine.async/2
(elixir 1.14.4) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
(ash 2.9.27) lib/ash/engine/engine.ex:702: Ash.Engine.start_pending_tasks/1
(ash 2.9.27) lib/ash/engine/engine.ex:323: Ash.Engine.run_to_completion/1
(ash 2.9.27) lib/ash/engine/engine.ex:252: Ash.Engine.do_run/2
(ash 2.9.27) lib/ash/engine/engine.ex:148: Ash.Engine.run/2
(ash 2.9.27) lib/ash/actions/read.ex:173: Ash.Actions.Read.do_run/3
(ash 2.9.27) lib/ash/actions/read.ex:96: Ash.Actions.Read.run/3
(feedback_cupcake 0.1.0) lib/feedback_cupcake/onboarding.ex:1: FeedbackCupcake.Onboarding.read/2
iex:8: (file)
ZachDaniel
ZachDaniel•2y ago
🤔 yeah, I think I can see how that might be happening can you open an issue with this info on ash?
Blibs
BlibsOP•2y ago
GitHub
Fetching next page in a query with calculations with keyset paginat...
Describe the bug Trying to fetch a new page of a read action with keyset pagination that uses a calculation inside the query (sorting in this case), will not work. To Reproduce I have the following...
ZachDaniel
ZachDaniel•2y ago
can you check main, I think this should be fixed now
Blibs
BlibsOP•2y ago
Yep, working fine on main 😄

Did you find this page helpful?