使用动态运算符创建 Ecto 查询

Posted

技术标签:

【中文标题】使用动态运算符创建 Ecto 查询【英文标题】:Create Ecto query with dynamic operators 【发布时间】:2020-01-09 13:28:38 【问题描述】:

我知道如何在 Ecto 查询中使用动态字段和值,例如:

field = :age
value = 20
Ecto.Query.where(App.User, [x], field(x, ^field) < ^value)

但是,可以动态定义运算符(在本例中为&lt;)吗?以及如何?

我已经用fragment 和插值字符串试过了:

operator = ">"
Ecto.Query.where(App.User, [x], fragment("? #operator ?", field(x, ^field), ^value))

但它引发了异常:Ecto.Query.CompileError) to prevent SQL injection attacks, fragment(...) does not allow strings...

【问题讨论】:

【参考方案1】:

你可以,通过一点元编程。 ecto 支持自定义宏。

defmacrop custom_where(t, f, v, sign) do
  sign, [context: Elixir, import: Kernel],
   [
     :field, [], [t, :^, [], [f]],
     :^, [], [v]
   ]
end

并像使用它

field = :age
value = 20

Ecto.Query.where(User, [x],
  custom_where(x, field, value, :<))
#⇒ #Ecto.Query<from u0 in User, where: u0.age < ^20>

这里的诀窍是我们欺骗ecto 就地注入 AST,因此它不执行检查。

【讨论】:

从变量传递符号时不起作用:Ecto.Query.where(User, [x], custom_where(x, field, value, sign)。它显示此错误:(Ecto.Query.CompileError) sign(field(x, ^field), ^value) is not a valid query expression 任何建议? 这显然行不通,因为传递给 macros 的是 AST。它应该以不同的方式处理,可能是显式引用宏的结果并取消引用在引用块中传递的变量。

以上是关于使用动态运算符创建 Ecto 查询的主要内容,如果未能解决你的问题,请参考以下文章

Linq 动态查询问题 - 运算符“或”与操作数类型“布尔”和“字符串”不兼容

使用 ecto 查询连接 3 个表

Postgres 从动态 sql 字符串创建本地临时表(在提交删除时)

Ecto 子查询中的 SQL WITH AS 语句

在过滤器数组上使用扩展运算符创建 Firestore 查询

Ecto - 如何通过特定查询获取“has_many”