web-dev-qa-db-fra.com

Comment utiliser SQL brut avec ecto Repo

J'ai une exigence upsert, j'ai donc besoin d'appeler une procédure stockée postgres ou d'utiliser une expression de table commune. J'utilise également l'extension pgcrypto pour les mots de passe et je voudrais utiliser des fonctions postgres (telles que "crypt" pour encoder/décoder les mots de passe).

Mais je ne peux pas trouver un moyen d'amener Ecto à jouer avec sql brut en partie ou en totalité, est-il prévu que ecto ne supporte que l'élixir dsl et ne permette pas de décortiquer en sql brut lorsque le dsl n'est pas suffisant?

J'ai trouvé que je peux interroger via l'adaptateur (Rocket est le nom de l'application)

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

Mais je ne sais pas comment obtenir cela sur le modèle. Je suis nouveau dans l'élixir et il semble que je devrais pouvoir utiliser Ecto.Model.Schem .schema/3 mais cela échoue

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

Sur Ecto 2.0 (beta) avec Postgres, vous pouvez utiliser Ecto.Adapters.SQL.query() ( docs actuels , 2.0-beta2 docs ) pour exécuter SQL arbitraire; en plus d'une liste des lignes elles-mêmes ("rows"), il se trouve qu'il retourne une liste de noms de colonnes ("columns").

Dans l'exemple ci-dessous, je

  1. exécuter une requête personnalisée sans paramètres,
  2. convertir les noms de colonnes du résultat des chaînes en atomes, et
  3. combinez-les avec chaque ligne des résultats et mappez-les dans une structure avec Kernel.struct ()

(Vous voudrez probablement exécuter la version query() (sans le bang!) Et vérifier {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
33
jamesvl

Solution modifiée pour Ecto 2.0:

dans 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

Usage:

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

Maintenant qu'Ecto 1.0 est sorti, cela devrait fonctionner pendant un certain temps:

Ajoutez les fonctions suivantes à votre module 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

Et utiliser comme tel:

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

En plus de Ecto.Adapters.SQL.query/4 , il existe également Ecto.Query.API.fragment/1 , qui peut être utilisé pour envoyer des expressions de requête au base de données. Par exemple, pour utiliser la fonction tableau de Postgres array_upper, on pourrait utiliser

Ecto.Query.where([x], fragment("array_upper(some_array_field, 1)]" == 1)
6
licyeus

Ecto 2.2.8 fournit Ecto.Query.load/2, vous pouvez donc faire quelque chose comme ceci:

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

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

5
eahanson

Ecto, au moins à partir de la version ~> 0.7, vous devez utiliser:

Ecto.Adapters.SQL.query/4

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

Exécute une requête SQL personnalisée sur un référentiel donné.

En cas de succès, il doit retourner un: ok Tuple contenant une carte avec au moins deux clés:

•: num_rows - le nombre de lignes affectées •: rows - l'ensemble de résultats sous forme de liste. nil peut être renvoyé à la place de la liste si la commande ne donne aucune ligne comme résultat (mais donne toujours le nombre de lignes affectées, comme une commande de suppression sans retourner le ferait)

Les options

•: timeout - Le temps en millisecondes pour attendre la fin de l'appel,: l'infini attendra indéfiniment (par défaut: 5000) •: log - Si faux, n'enregistre pas la requête

Exemples

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

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

4
Peck

C'est https://stackoverflow.com/users/1758892/thousandsofthem échantillon, mais juste un peu rétréci (crédit: lui/elle)

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
3
Michael Bishop