如何使用原始sql与ecto回购

时间:2015-01-03 02:40:03

标签: elixir ecto

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

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

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

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

但不确定如何将其送到模型中。我是elixir的新手,似乎我应该可以使用Ecto.Model.Schem。架构 / 3但是这个失败了

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

8 个答案:

答案 0 :(得分:32)

在Postgres的Ecto 2.0(测试版)上,您可以使用Ecto.Adapters.SQL.query()current docs2.0-beta2 docs)来执行任意SQL;除了行本身列表(“rows”)之外,它还会返回列名列表(“columns”)。

在下面的例子中,我

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

    (您可能希望运行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
    

答案 1 :(得分: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

用法:

Repo.execute_and_load("SELECT * FROM users WHERE id = $1", [1], User)

答案 2 :(得分: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

并按原样使用:

Repo.execute_and_load("SELECT * FROM users WHERE id = $1", [1], User)

答案 3 :(得分: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)

答案 4 :(得分: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

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

答案 5 :(得分:4)

Ecto,至少从版本〜> 0.7你应该使用:

Ecto.Adapters.SQL.query / 4

def query(repo, sql, params, opts \\ [])

在给定的repo上运行自定义SQL查询。

如果成功,它必须返回一个包含至少一个地图的ok元组 两把钥匙:

•:num_rows - 受影响的行数   •:rows - 将结果集设置为列表。可以返回nil而不是     如果命令没有产生任何行作为结果列出(但仍然产生     受影响的行数,如没有返回的删除命令)

选项

•:timeout - 等待呼叫完成的时间(以毫秒为单位),     :无穷大将无限期等待(默认值:5000)   •:log - 如果为false,则不记录查询

实施例

  

IEX> Ecto.Adapters.SQL.query(MyRepo," SELECT $ 1 + $ 2",[40,2])

     

%{rows:[{42}],num_rows:1}

答案 6 :(得分:3)

这是https://stackoverflow.com/users/1758892/thousandsofthem样本,但只是缩小了一点(信用:他/她)

defmodule MyApp.Repo do
  [...]
  def execute_and_load(sql, params, schema) do
    response = query!(sql, params)
    Enum.map(response.rows, fn row ->
      fields = Enum.zip(response.columns, row) |> Enum.into(%{})
      Ecto.Schema.__load__(schema, nil, nil, nil, fields,
        &Ecto.Type.adapter_load(__adapter__(), &1, &2))
    end)
  end
end

答案 7 :(得分:-3)

至少使用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)