When I import Ecto.Query
I also get access to functions from Ecto.Query.API
like max
, count
.
How?
Also functions in Ecto.Query.API
seem to be there just for documentation. Where do they actually come from?
When I import Ecto.Query
I also get access to functions from Ecto.Query.API
like max
, count
.
How?
Also functions in Ecto.Query.API
seem to be there just for documentation. Where do they actually come from?
I’m not an ecto user, so I’m not aware of that fact and can’t really believe it’s true. Can you give an example snippet that would work and uses the remote and not imported functions?
Sure, https://github.com/idi-ot/postgres-tutorial/blob/part-1/part-1/rdb/lib/rdb/ch2/ch2.ex#L251-L262
@spec agg1 :: integer
def agg1 do
import Ecto.Query
# want
# SELECT max(temp_lo) FROM weather;
query = from(w in Weather, select: max(w.temp_lo))
Repo.one(query)
# is actually
# SELECT max(w0."temp_lo") FROM "weather" AS w0 []
end
I’m not deep-diving code today, but from/2
is a macro, so it will probably rewrite the max
.
From the Ecto.Query.API moduledocs
Note the functions in this module exist for documentation purposes and one should never need to invoke them directly.
If you look at the module code, none of the functions actually do anything. This is to say, the module isn’t actually used at all when evaluating queries. In reality the from
macro just takes the AST of the query and handles everything. Ecot.Query.API
is docs only.
So where does max/1
come from? I’ve skimmed through Ecto.Query
, Ecto.Query.Builder
, Ecto.Query.Builder.From
and couldn’t see any functions from Ecto.Query.API
being mentioned there.
It, like everything else you pass to from
, is just treated as AST and transformed into a query struct.
transformed into a query struct
This is what I want to know, how and where this transformation happens.
iex(1)> from(a in "asdf", select: %{sum: asdf(a.id)})
** (Ecto.Query.CompileError) `asdf(a.id())` is not a valid query expression.
* If you intended to call a database function, please check the documentation
for Ecto.Query to see the supported database expressions
* If you intended to call an Elixir function or introduce a value,
you need to explicitly interpolate it with ^
(ecto) expanding macro: Ecto.Query.select/3
iex:1: (file)
(ecto) expanding macro: Ecto.Query.from/2
iex:1: (file)
iex(1)> from(a in "asdf", select: %{sum: sum(a.id)})
#Ecto.Query<from a in "asdf", select: %{sum: sum(a.id)}>
They must be defined somewhere, otherwise how does ecto know what functions like max
or sum
are valid.
Well, your adventure begins here: ecto/lib/ecto/query.ex at v2.2.9 · elixir-ecto/ecto · GitHub
It’s pretty complicated. If I had to summarize the answer to the original question though which was
When I import Ecto.Query I also get access to functions from Ecto.Query.API like max, count.
The answer is "you don’t get access to functions like max or count`. The thing with quoting is that you can quote literally any valid elixir syntax without anything inside needing to be compilable yet. So for example just open IEX and do:
iex(4)> quote do: this_doesnt_exist(foo)
{:this_doesnt_exist, [], [{:foo, [], Elixir}]}
You haven’t really gained access to some this_doesnt_exist
function, rather you’re simply grabbing the AST of that code. This is what happens with From, and then it takes that AST and does… a lot with it.
Yeah, I have some understanding how this works, I just can’t find any mentions of functions from Ecto.Query.API
elsewhere in code.
Nevermind, found them in lib/ecto/query/builder.ex
, lib/ecto/query/builder/select.ex
.