Ash isn’t strictly speaking an ORM although it does quack like that duck for most applications using it 
If all we’re talking about is running queries and getting their results in Elixir structs and that’s the most that you want out of a given tool then its a different discussion, but with Ash we’re much more concerned with generalized modeling problems, or application building primitives and running SQL queries is just “one way” you might do that.
Some examples:
policies do
policy action_type(:read) do
authorize_if expr(public == true)
end
end
If you had a policy like this, Ash will lower it into SQL when running the query:
Ash.read!(Resource)
#=> SELECT * FROM "table" WHERE public = TRUE
But you can also leverage that policy for a lot more things. For example, you can ask for a policy flow chart to hand over for a security audit. Or you can evaluate the policy in memory:
Ash.can?(Resource, :read, %User{},
data: %Resource{public: false},
reuse_values?: true # use the values we have in memory
)
In which case Ash will run the given expression in memory.
Or, for example, the following validation:
update :increment_score do
argument :amount, :integer, default: 1
change atomic_update(:score, expr(score + ^arg(:amount)))
validate compare(:score, less_than: 100)
end
When you run this action, you will get SQL like this:
UPDATE things
SET
id = CASE WHEN
score + {amount} > 100 THEN
ash_raise_error(...) -- We raise an equivalent error
ELSE
id
END
score = score + {amount}
WHERE id = {id}
AND public = true -- policies embedded into queries
Ash is far more than an ORM these are just some examples of that (and likely not really the best ones).
In every non-trivial application I’ve ever worked with ultimately your business logic crosses multiple systems, or multiple queries etc. that must be operated on in concert.
If composition is limited to statements to a data layer, you’re constrained by the capabilities of that data layer. Likewise what Ash brings to the table primarily has to do with a whole suite of intelligent composition that may result in many queries, or even zero queries when running actions.
Even with that in mind my question was more about what concrete limitations you see in Ash that could be otherwise addressed by something more SQL-native as it could easily represent gaps in Ash. With that said, Ash isn’t intended to replace all interaction with a database, it’s about your domain logic. This is perfectly valid Ash code:
action :list_users, {:array, :struct} do
constraints items: [instance_of: __MODULE__]
run fn input, _ ->
result =
~SQL[from users select *]
|> SQL.map(fn row -> struct(User, row) end)
|> Enum.to_list()
{:ok, result}
end
end