Problem with Ash.Type.dump_to_native/2 after upgrade to 3.5.41

After upgrading to 3.5.41 my tests fail when there is a "NOT IN" query, apparently where before dump_to_native cas called with the atom (:cancelled) now a string "cancelled" is used causing the postgres query to fail. This is my custom type:
defmodule Admin.Resources.Types.TimerStatus do
@moduledoc """
Custom Ash type for timer status enum that maps atoms to integers
"""
use Ash.Type

@values [running: 0, complete: 1, cancelled: 2, invoiced: 3]
@atom_to_int Map.new(@values)
@int_to_atom Map.new(@values, fn {k, v} -> {v, k} end)

@impl Ash.Type
def storage_type(_), do: :integer

@impl Ash.Type
def cast_input(value, _) when is_atom(value) do
if value in Map.keys(@atom_to_int) do
{:ok, value}
else
:error
end
end

def cast_input(value, _) when is_integer(value) do
if Map.has_key?(@int_to_atom, value) do
{:ok, Map.get(@int_to_atom, value)}
else
:error
end
end

def cast_input(value, _) when is_binary(value) do
case String.to_existing_atom(value) do
atom when is_atom(atom) -> cast_input(atom, nil)
_ -> :error
end
rescue
_ -> :error
end

def cast_input(_, _), do: :error

@impl Ash.Type
def cast_stored(nil, _), do: {:ok, nil}

def cast_stored(value, _) when is_integer(value) do
case Map.get(@int_to_atom, value) do
nil -> :error
atom -> {:ok, atom}
end
end

def cast_stored(_, _), do: :error

@impl Ash.Type
def dump_to_native(value, _) when is_atom(value) do
case Map.get(@atom_to_int, value) do
nil -> :error
int -> {:ok, int}
end
end

def dump_to_native(_, _), do: :error
end
defmodule Admin.Resources.Types.TimerStatus do
@moduledoc """
Custom Ash type for timer status enum that maps atoms to integers
"""
use Ash.Type

@values [running: 0, complete: 1, cancelled: 2, invoiced: 3]
@atom_to_int Map.new(@values)
@int_to_atom Map.new(@values, fn {k, v} -> {v, k} end)

@impl Ash.Type
def storage_type(_), do: :integer

@impl Ash.Type
def cast_input(value, _) when is_atom(value) do
if value in Map.keys(@atom_to_int) do
{:ok, value}
else
:error
end
end

def cast_input(value, _) when is_integer(value) do
if Map.has_key?(@int_to_atom, value) do
{:ok, Map.get(@int_to_atom, value)}
else
:error
end
end

def cast_input(value, _) when is_binary(value) do
case String.to_existing_atom(value) do
atom when is_atom(atom) -> cast_input(atom, nil)
_ -> :error
end
rescue
_ -> :error
end

def cast_input(_, _), do: :error

@impl Ash.Type
def cast_stored(nil, _), do: {:ok, nil}

def cast_stored(value, _) when is_integer(value) do
case Map.get(@int_to_atom, value) do
nil -> :error
atom -> {:ok, atom}
end
end

def cast_stored(_, _), do: :error

@impl Ash.Type
def dump_to_native(value, _) when is_atom(value) do
case Map.get(@atom_to_int, value) do
nil -> :error
int -> {:ok, int}
end
end

def dump_to_native(_, _), do: :error
end
and this is the now failing code:
@spec list_active() :: [Timer.t()]
def list_active do
Timer
|> Ash.Query.filter(status not in [:cancelled, :invoiced])
|> Ash.Query.load([:customer])
|> Ash.Query.sort(:id)
|> Ash.read!()
end
@spec list_active() :: [Timer.t()]
def list_active do
Timer
|> Ash.Query.filter(status not in [:cancelled, :invoiced])
|> Ash.Query.load([:customer])
|> Ash.Query.sort(:id)
|> Ash.read!()
end
And the error in the test as attachment.
8 Replies
Herman
HermanOP2w ago
It appears that the error started with ash_sql version 0.2.93 According to claude: The regression is triggered at /Users/herman/Projects/_playground/ash/lib/ash/type/atom.ex:115: def dump_tonative(value, ) when is_atom(value) do {:ok, to_string(value)} end Root Cause Analysis: The regression path: 1. ash_sql 0.2.93 introduced evaluate_right() function in commit 3cad640 2. This calls maybe_type_expr() at /Users/herman/Projects/_playground/ash_sql/lib/expr.ex:3411-3418 3. Which wraps expressions in Ash.Query.Function.Type 4. The Type function calls Ash.Type.coerce() 5. Coerce determines the values are atoms and processes them with Ash.Type.Atom.dump_to_native/2 6. This converts :cancelled → "cancelled" at line 115 7. When strings reach TimerStatus.dump_to_native/2, it expects atoms but gets strings 8. Results in Postgrex expected an integer...got "cancelled" error The issue is that the new Type coercion layer in ash_sql 0.2.93 prematurely converts atoms to strings before they reach custom types that expect to handle the original atom values.
ZachDaniel
ZachDaniel2w ago
🤔 interesting please put together a reproduction, too many moving parts for me to know how to address as it is
Herman
HermanOP2w ago
I had Claude generate me a project to show the problem, it is at https://github.com/Hermanverschooten/ash_sql_reproduction
GitHub
GitHub - Hermanverschooten/ash_sql_reproduction: Test Repo for Ash ...
Test Repo for Ash SQL problem. Contribute to Hermanverschooten/ash_sql_reproduction development by creating an account on GitHub.
Herman
HermanOP2w ago
The test currently succeeds because it expects the error, when you downgrade ash_sql to 0.2.92, the tests will fail because there is no error.
ZachDaniel
ZachDaniel2w ago
Please open an issue as well if you haven't
Herman
HermanOP2w ago
In what repo?
ZachDaniel
ZachDaniel2w ago
ash_sql Or ash_sqlite
Herman
HermanOP2w ago
Done issue #174 for ash_sql. Thanks for taking the time.

Did you find this page helpful?