如何使用片段作为更新变更集属性的一部分?

How to use a fragment as part of update changeset attributes?

我正在尝试使用新属性和与现有列交互的数据库调用来更新现有模型。 SQL.

中的 UPDATE companies SET a = 1, b = jsonb_set(b, '{key}', CURRENT_TIMESTAMP(), true)

我如何使用 Ecto 和片段实现这一点?

这是我的尝试,但失败了,因为 fragment 是一个宏:

enhanced =
  %{ attrs | b: fragment(
    "jsonb_set(b, ?, CURRENT_TIMESTAMP(), true)",
    "{#{Atom.to_string(key)}}"
  )}
result =
  company
  |> Company.changeset(enhanced)
  |> Repo.update()

我没有找到将片段添加到原始查询的方法,但是调用 jsonb_set 可以使用事务自动实现。

Repo.transaction(fn ->
  updated = case update_company(company, attrs) do
    {:ok, company} -> company
    {:error, changeset} -> Repo.rollback(changeset)
  end

  from(c in Company,
      update: [set: [b: fragment(
        "jsonb_set(b, ?, to_jsonb(now()), true)",
        ^key
      )]],
      where: c.id == ^company.id)
    |> Repo.update_all([])

  updated
end)

通过 Ecto.Query.update 使用片段宏是可行的方法。为此,我们需要使更新 成为查询表达式 的一部分。

{count, results} =
  Company
  |> where([c], <some_condition>)
  |> select([e], e) ## Optional: this will return our changes
  |> update(set: [
        string_field: "value",
        map_field: fragment(~S<jsonb_set("map_field"::jsonb, '{key_name_here}', 'value_here')>)
      ]
  |> Repo.update_all([])

~S<…> 用于避免在我们的片段中转义引号 "

通过将字符串中的变量替换为 ? 并将它们添加为固定的 ^ 参数,将变量添加到片段中。

fragment(~S<jsonb_set("map_field"::jsonb, '{?}', 'value_here')>, ^key)