Ecto query introspection

Does anyone know of a way to introspect an Ecto.Query (at compile time)?

Specifically I was looking at a way to know the query select keys.
For example if I have a query and inspect it I can see select: [:field1], I would like a way to get to the list of fields in the select.

iex> inspect q
#Ecto.Query<from a0 in A, select: [:field1]>

I know schemas have reflection built in via __schema__/1 for example, but I was looking for something similar for an ecto query. I went spelunking in the source, but assume the dynamic nature of the query builder means this wouldn’t be generally feasible. And I don’t want to try and rely on undocumented API or AST traversal.

If anyone knows something I don’t I would be happy to hear about it :slight_smile:

1 Like

It is not possible right now without using internal and (by nature) hidden fields of Ecto.Query structure. Main question is what do you need that for?

Thanks, I thought as much. It was part of a light DSL wrapper around some queries, where I did some other work based off the query fields at compile time, without having to explicitly say, and duplicate which fields.

Can you elaborate what this means? Are you constructing queries at compile time?

1 Like

Sure, so something along the lines of (at call site)

my_macro() do
  from q in query, select: [fields]

Where my_macro does some things before compile. And the query is a var!

Think of it a little like how plug router passes in the conn to a block.

Hope that makes sense :thinking:


Sure, so the main point of confusion here is that my_macro doesn’t receive an ecto query, it receives Elixir AST. That is to say, it receives:

iex(1)> quote do:
...(1)> from q in query, select: [fields]
{:from, [],
   {:in, [context: Elixir, import: Kernel],
    [{:q, [], Elixir}, {:query, [], Elixir}]},
   [select: [{:fields, [], Elixir}]]

The variable query doesn’t exist yet, all that exists is AST talking about a variable named query. There isn’t a variable until the program is actually run. You can’t introspect query at compile time as if it’s an ecto query struct, it isn’t.

Now, what you can do is embed that block in another block which contains code that at runtime introspects the query:

# roughly
defmacro my_macro(query_ast) do
  quote do
     var!(query) |> IO.inspect

But really this is just an overly complicated way of calling a function. You’d be a lot better off just having:

my_introspector_fun(from q in query, select: [fields])

Ultimately the unavoidable truth is: there is no query at compile time, just AST. If you want to introspect a query you need to do it at runtime, and if you’re doing it at runtime you may as well just use a function and ditch macros altogether.

Thanks Ben that’s really useful advise on the AST front. I didn’t even know you could do a nested unquote like that. I’ll continue to play around with it.

He meant quote do, not unquote do. :slight_smile:

Oops! Good catch thanks, fixed!

1 Like