Подтвердить что ты не робот

Как использовать raw sql с ecto Repo

У меня есть требование upsert, поэтому мне нужно вызвать хранимую процедуру postgres или использовать общее табличное выражение. Я также использую pgcrypto exgtension для паролей и хотел бы использовать функции postgres (такие как "crypt" для кодирования/декодирования паролей).

Но я не могу найти способ заставить Ecto играть с сырым sql в части или целом, предполагается ли это, что ecto будет поддерживать только elixir dsl и не разрешать обходить исходный sql, когда dsl недостаточно?

Я обнаружил, что могу запросить через адаптер (Rocket - это имя приложения)

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

Но не уверен, как это сделать с моделью. Я новичок в эликсире, и мне кажется, что я должен использовать Ecto.Model.Schem. схема/3, но это не удается

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

Ответ 1

В Ecto 2.0 (beta) с Postgres вы можете использовать Ecto.Adapters.SQL.query() (текущие документы, 2.0-beta2 docs) для выполнения произвольного SQL; в дополнение к списку самих строк ( "rows" ), происходит возврат списка имен столбцов ( "columns" ).

В приведенном ниже примере I

  • запустить пользовательский запрос без параметров,
  • конвертировать имена столбцов результата из строк в атомы и
  • объединить их с каждой строкой результатов и перевести их в структуру с помощью 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

Ответ 2

Модифицированное решение для 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)

Ответ 3

Теперь, когда 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)

Ответ 4

В дополнение к 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)

Ответ 5

Ecto, по крайней мере, с версии ~ > 0.7, вы должны использовать:

Ecto.Adapters.SQL.query/4

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

Запускает пользовательский 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

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.

Ответ 7

Это 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

Ответ 8

С по крайней мере 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)