Ecto.Types and named bindings

Hi,

We are using Ecto types, a query like this works just fine (using `Repo.all/1):

from n in FileSystem.Schema.FSNode,  
           where: n.fs_type == ^FileSystem.Node.Type.file

This however using a named binding causes the cast to fail with a Postgrex error. For example:

from n in FileSystem.Schema.FSNode, as: :node, 
            where: field(as(:node), :fs_type) == ^FileSystem.Node.Type.file

The error:

** (DBConnection.EncodeError) Postgrex expected an integer in -32768..32767, got %FileSystem.Node.Type{value: :file, db: 1}.

I have to manually cast the types.

Is there a workaround for this?

Thanks

1 Like

It’s not clear to me why you’d want to use the named binding in this case. Everywhere you want to use the binding, just using the dot notation should work.

However, from the docs they use the as/1 function like this:

where: as(:node).fs_type == ^FileSystem.Node.Type.file

Maybe that does the trick? (Didn’t try it)

It’s for an engineering access API, so the field names are specified as atoms., so as(:node).fs_type won’t work. I need to do field(as(^name), :fs_type), or rather field(as(^name), ^field_name)

If the field/column you’re referring to is dynamic, the there is no way that ecto can know the type without help. So i doubt there is a way without explicitly specifying the type (and then you’ll have to guard that the passed in fieldname matches that type).

I use the following when I need to use a named binding dynamically:

dynamic([{^binding, b}], field(b, ^field) == ^value)
4 Likes

Wow, this is nice! I had to think for a while how this works. It combines two lesser known building blocks:

  1. You can match on a specific binding, with the tuple (it’s mentioned under named bindings in the docs, as the last example, and that’s totally new to me).
  2. Using dynamic/2 to make sure the params are casted correctly. I think this is made explicit in this fragment of the documentation:

Type casting does not cross dynamic boundaries. When you write a dynamic expression, such as dynamic([p], p.visits > ^param), Ecto will automatically cast ^param to the type of p.visits.

I’m still surprised ^value is casted correctly in this case, given that the field is only known at runtime. Maybe someone can explain better?

Thanks for sharing! And please correct me if I’m wrong.

This sounds strange. I wouldn’t expect this to work any different than without using dynamic - and maybe the docs just highlight exactly that.

I tend to agree, seems like an oversight. The EctoQuery struct without dynamic:

  wheres: [
    %Ecto.Query.BooleanExpr{
      op: :and,
      expr: {:==, [],
       [{{:., [], [{:&, [], [0]}, :fs_type]}, [], []}, {:^, [], [0]}]},
      file: "iex",
      line: 4,
      params: [
        {%FileSystem.Node.Type{value: :file, db: 1},
         {0, :fs_type}}
      ],
      subqueries: []
    }
  ],

and with:

  wheres: [
    %Ecto.Query.BooleanExpr{
      op: :and,
      expr: {:==, [],
       [{{:., [], [{:as, [], [:node]}, :fs_type]}, [], []}, {:^, [], [0]}]},
      file: "iex",
      line: 21,
      params: [
        {%FileSystem.Node.Type{value: :file, db: 1}, :any}
      ],
      subqueries: []
    }
  ],

Seems like the information is there for it to work.