Как использовать 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
Ответы
Ответ 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)