Search code examples
postgresqlelixirecto

Ecto - casting an array of strings to integers in a fragment


In an Ecto query I'm running against Postgres (13.6), I need to interpolate a list of ids into a fragment - this is generally not something I have a problem with, but in this case, the list of ids is being received as a list of strings that need to be cast to integers (or, more specifically, BIGINT). The query that I think that I need is as follows, which the troublesome bit being ANY(ARRAY?::BIGINT[]):

ModelA
|> where(
  [ma],
  fragment(
    "EXISTS (SELECT * FROM model_b mb WHERE mb.a_id = ? AND mb.c_id = ANY(ARRAY?::BIGINT[]))",
    a.id,
    ^c_ids
  )
)

where c_ids would be a list like ["1449441579", "2345556834"]

However, when I run this, I get the error

(Postgrex.Error) ERROR 42703 (undefined_column) column "array$4" does not exist

referring to the generated SQL

ANY(ARRAY$4::BIGINT[])

Of course, I could convert the array of c_ids to integers beforehand in my app code, but I'd like to see if I can get it to cast in the query itself.

Writing the fragment in straight SQL works out just fine:

SELECT * FROM model_b mb WHERE mb.a_id = 1 AND mb.c_id = ANY(ARRAY['1449441579', '2345556834']::BIGINT[]);

What is the idiomatic way to get this kind of array casting to work in an Ecto fragment? Many thanks.


Solution

  • Just to codify my comment, I would do the integer conversion before the query. You can use the dynamic macro to support IN queries:

    import Ecto.Query
    
    alias YourApp.Repo
    alias YourApp.SomeSchema, as: ModelA
    
    strs = ["1", "2", "3"]
    
    ids = Enum.map(strs, fn x -> String.to_integer(x) end)
    
    conditions = dynamic([tbl], tbl.id in ^ids)
    
    Repo.all(
      from ma in ModelA,
      where: ^conditions
    )
    |> IO.inspect()