如何使用原始sql与ecto回购

Kru*_*rut 42 elixir ecto

我有一个upsert要求,所以我需要调用postgres存储过程或使用公用表表达式.我还使用pgcrypto exgtension进行密码,并希望使用postgres函数(例如"crypt"来编码/解码密码).

但是我无法找到一种方法让Ecto部分或全部使用原始sql,它是否意味着ecto只支持elixir dsl并且当dsl不够时不允许shell-out到原始sql?

我发现我可以通过适配器查询(Rocket是应用程序的名称)

q = Ecto.Adapters.Postgres.query(Rocket.Repo,"select * from users limit 1",[])
Run Code Online (Sandbox Code Playgroud)

但不确定如何将其转化为模型.我是elixir的新手,似乎我应该可以使用Ecto.Model.Schem.schema/3但是失败了

Rocket.User.__schema__(:load,q.rows |> List.first,0)
** (FunctionClauseError) no function clause matching in Rocket.User.__schema__/3    
Run Code Online (Sandbox Code Playgroud)

jam*_*svl 34

在Postgres的Ecto 2.0(beta)上,您可以使用Ecto.Adapters.SQL.query()(当前的文档,2.0-beta2文档)来执行任意SQL; 除了行本身(" rows")的列表之外,它恰好返回列名称列表(" columns").

在下面的例子中,我

  1. 运行没有参数的自定义查询,
  2. 将结果的列名从字符串转换为原子,和
  3. 将这些与结果的每一行结合起来,并将其映射到一个带有Kernel.struct()的结构中

(你可能想要运行query()版本(没有爆炸!)并检查{ok, res}.)

qry = "SELECT * FROM users"
res = Ecto.Adapters.SQL.query!(Repo, qry, []) # a

cols = Enum.map res.columns, &(String.to_atom(&1)) # b

roles = Enum.map res.rows, fn(row) ->
  struct(MyApp.User, Enum.zip(cols, row)) # c
end
Run Code Online (Sandbox Code Playgroud)


小智 9

针对Ecto 2.0的修改解决方案:

在repo.ex:

  def execute_and_load(sql, params, model) do
    Ecto.Adapters.SQL.query!(__MODULE__, sql, params)
    |> load_into(model)
  end

  defp load_into(response, model) do
    Enum.map(response.rows, fn row ->
      fields = Enum.reduce(Enum.zip(response.columns, row), %{}, fn({key, value}, map) ->
        Map.put(map, key, value)
      end)
      Ecto.Schema.__load__(model, nil, nil, nil, fields,
                           &Ecto.Type.adapter_load(__adapter__, &1, &2))
    end)
  end
Run Code Online (Sandbox Code Playgroud)

用法:

Repo.execute_and_load("SELECT * FROM users WHERE id = $1", [1], User)
Run Code Online (Sandbox Code Playgroud)


Sea*_*n S 7

现在Ecto 1.0已经出来了,这应该有一段时间了:

将以下功能添加到您的Repo模块:

def execute_and_load(sql, params, model) do
  Ecto.Adapters.SQL.query!(__MODULE__, sql, params)
  |> load_into(model)
end

defp load_into(response, model) do
  Enum.map response.rows, fn(row) ->
    fields = Enum.reduce(Enum.zip(response.columns, row), %{}, fn({key, value}, map) ->
      Map.put(map, key, value)
    end)

    Ecto.Schema.__load__(model, nil, nil, [], fields, &__MODULE__.__adapter__.load/2)
  end
end
Run Code Online (Sandbox Code Playgroud)

并使用如下:

Repo.execute_and_load("SELECT * FROM users WHERE id = $1", [1], User)
Run Code Online (Sandbox Code Playgroud)


lic*_*eus 6

除了Ecto.Adapters.SQL.query/4之外,还有Ecto.Query.API.fragment/1,它可用于将查询表达式发送到数据库.例如,要使用Postgres的数组函数array_upper,可以使用

Ecto.Query.where([x], fragment("array_upper(some_array_field, 1)]" == 1)
Run Code Online (Sandbox Code Playgroud)


eah*_*son 5

Ecto 2.2.8提供了Ecto.Query.load/2,因此您可以执行以下操作:

use Ecto.Repo

def execute_and_load(sql, params, model) do
  result = query!(sql, params)
  Enum.map(result.rows, &load(model, {result.columns, &1}))
end
Run Code Online (Sandbox Code Playgroud)

参见https://hexdocs.pm/ecto/Ecto.Repo.html#c:load/2


Kru*_*rut -5

至少在 ecto 4.0 中,您可以使用适配器进行查询,然后将结果提供给 Ecto.Model。架构/3:

q = Ecto.Adapters.Postgres.query(Rocket.Repo,"select * from users limit 1",[])
Rocket.User.__schema__(:load,q.rows |> List.first,0)
Run Code Online (Sandbox Code Playgroud)

  • 目前还没有 Ecto 4.0。 (3认同)