在 Ecto 查询中使用变量来引用命名绑定

Gab*_*Prá 6 elixir ecto

我正在尝试构建一个函数,用于在查询中的给定表的字段中搜索术语。

对于像这样的查询

initial_query = 
  Answer
  |> join(:left, [a], q in assoc(a, :question), as: :question)
  |> join(:left, [a, q], s in assoc(a, :survey), as: :survey)
Run Code Online (Sandbox Code Playgroud)

我希望能够通过引用的表中进行搜索:question:survey

现在,此代码有效:

initial_query
|> or_where(
  [question: t], #:question hard coded
  fragment(
    "CAST(? AS varchar) ILIKE ?",
    field(t, ^field),
    ^"%#{search_term}%"
  )
)
Run Code Online (Sandbox Code Playgroud)

但是,我想要一个将命名绑定作为参数的函数,但我找不到方法来做到这一点。

我的尝试:

defp search_field(initial_query, table, field, search_term) do
  initial_query
  |> or_where(
    [{table, t}],
    fragment(
      "CAST(? AS varchar) ILIKE ?",
      field(t, ^field),
      ^"%#{search_term}%"
    )
  )
end
Run Code Online (Sandbox Code Playgroud)

给出错误

** (Ecto.Query.CompileError) unbound variable t in query. If you are attempting to interpolate a value, use ^var expanding macro: Ecto.Query.or_where/3

when called like this:

search_field(initial_query, :question, :text, search_text)
Run Code Online (Sandbox Code Playgroud)

and

defp search_field(initial_query, table, field, search_term) do
  initial_query
  |> or_where(
    [{^table, t}],
    fragment(
      "CAST(? AS varchar) ILIKE ?",
      field(t, ^field),
      ^"%#{search_term}%"
    )
  )
end
Run Code Online (Sandbox Code Playgroud)

Gives

** (Ecto.Query.CompileError) binding list should contain only variables or {as, var} tuples, got: {^table, t} expanding macro: Ecto.Query.or_where/3


Is there a way to use a variable to reference a named binding in an Ecto Query?

Gab*_*Prá 6

所以这个问题的答案似乎是 Ecto 不支持这样做。@maartenvanvliet 解决方案效果很好,但缺点是依赖于内部实现。

我对这个问题的解决方案是search_field使用此处...描述的语法让函数始终在最后连接的表中进行搜索:

# Searches for the `search_term` in the `field` in the last joined table in `initial_query`.
defp search_field(initial_query, field, search_term) do
  initial_query
  |> or_where(
    [..., t],
    fragment(
      "CAST(? AS varchar) ILIKE ?",
      field(t, ^field),
      ^"%#{search_term}%"
    )
  )
end
Run Code Online (Sandbox Code Playgroud)

所以这个函数会像这样使用:

Answer
|> join(:left, [a], q in assoc(a, :question), as: :question)
|> search_field(:text, search_text)
|> join(:left, [a, q], s in assoc(a, :survey), as: :survey)
|> search_field(:title, search_text)
Run Code Online (Sandbox Code Playgroud)

在我看来,这仍然很好读,缺点是要求我们能够更改initial_query.