如何用Elixir的Ecto查询格式表示此查询?

时间:2017-10-09 17:26:12

标签: elixir ecto

我无法将以下SQL查询“转换”为有效的Ecto查询:

C11

我相信要采用的方法是将此查询拆分为两个。这是我尝试过的:

SELECT *
FROM file_modules 
WHERE file_id =
  (SELECT f.file_id
  FROM files AS f
  LEFT JOIN file_modules AS fm ON f.file_id = fm.file_id
  WHERE f.storage_id = '20:1:0:86d:1591:c89c:512:de52' AND fm.name = 'bruteforce'
  ORDER BY f.version DESC
  LIMIT 1)

q1 = File |> where([f], f.storage_id == ^storage_id) |> join(:left, [f], fm in FileModule, f.file_id == fm.file_id) |> where([..., fm], fm.name == ^module_name) |> order_by([..., fm], desc: fm.version) |> select([fm], fm.file_id) |> limit(1) # q1 works and returns the expected file_id q2 = q1 |> subquery() |> where([fm, fm2], fm.file_id == fm2.file_id) # Here's where I'm stuck |> preload([..., m], [modules: m]) 具有以下Ecto.Query结果:

q1

我的问题似乎与#Ecto.Query<from f0 in Helix.Software.Model.File, left_join: f1 in Helix.Software.Model.FileModule, on: f0.file_id == f1.file_id, where: f0.storage_id == ^(storage_id), where: f1.name == ^:bruteforce, order_by: [desc: f1.version], limit: 1, select: f0.file_id> 有关。我应该如何格式化才能使用 来自q2的结果作为输入?

提前致谢。

我认为这个问题与我的底层架构无关,但它是:

q1

正如Jablanović在评论中所提到的,ORM抽象有时可能会失败。在这种情况下,是否可以使用上面的原始sql:

1)转换File.ID和Storage.ID类型,以避免难看的字符串连接?

2)返回结果后,将此结果预加载或保存到模式中?

我想到的界面是:

schema "files" do
  field :file_id, ID,
    primary_key: true

  field :name, :string
  field :path, :string
  field :software_type, Constant
  field :file_size, :integer
  field :storage_id, Storage.ID

  field :crypto_version, :integer

  field :full_path, :string

  belongs_to :type, SoftwareType,
    foreign_key: :software_type,
    references: :software_type,
    define_field: false
  belongs_to :storage, Storage,
    foreign_key: :storage_id,
    references: :storage_id,
    define_field: false

  has_many :modules, FileModule,
    foreign_key: :file_id,
    references: :file_id,
    on_replace: :delete

  timestamps()
end



schema "file_modules" do
  field :file_id, File.ID,
    primary_key: true
  field :name, Constant,
    primary_key: true
  field :version, :integer

  belongs_to :file, File,
    foreign_key: :file_id,
    references: :file_id,
    define_field: false,
    on_replace: :update
end

根据Ecto.Adapters.SQL.query/4的例子,看起来我可以实现1. 2怎么样?

我的最终目标是使用上面的嵌套SQL查询,同时安全地投射file_id和storage_id,并正确使用%File {}架构,并在q = "SELECT * FROM files WHERE file_id = $1", ^file_id file = Repo.get(q) |> Repo.preload() file.file_id # Returns file id 加载关联。

请注意,存储过程或视图会给我一个更好的界面,我可以使用file.modules正确地投射,但我觉得我仍然遇到将数据“预加载”到模式中的问题。

2 个答案:

答案 0 :(得分:1)

Subquerys are currently not allowed in where clauses; the documentation recommends using a JOIN instead.

我还没有测试过它,但是下面的查询应该产生与原始SQL相同的结果:

q1 = ... # Same as in the question.
q2 =
  FileModule
  |> join(:left, [fm], fid in subquery(q1), fm.file_id == fid)
  |> select([fm], fm)

答案 1 :(得分:0)

我不打算回答我自己的问题,但嘿,这是我提出的另一种解决方案。正如在这个问题上所指出的,如果我找到一个可以让我的好界面,我会同样高兴:

  • 使用原始SQL查询
  • 同时仍然利用Ecto型铸造
  • 并且能够将关联数据预加载到架构中。

混合使用Repo.loadEcto.Adapters.SQL.query,我就能做到这一点。如果它对其他人有帮助,我会分享界面和代码。

sql = "
  SELECT *
  FROM file_modules 
  WHERE file_id =
    (SELECT f.file_id
    FROM files AS f
    LEFT JOIN file_modules AS fm ON f.file_id = fm.file_id
    WHERE f.storage_id = ##1::storage_id AND fm.name = ##2::module_name
    ORDER BY f.version DESC
    LIMIT 1)"

caster = fn type, value -> 
  case type do
    :storage_id ->
      Storage.ID.cast!(value) && to_string(value)
    :module_name ->
      Software.Module.exists?(value) && value || {:error, :bad_value}
    _ ->
      Hector.std_caster(type, value)
  end
end

query = Hector.query(sql, [storage_id, module_name], caster)

loader = fn repo, {columns, rows} ->
  rows
  |> Enum.map(fn row ->
    repo
    |> apply(:load, [File, {columns, row}])
    |> File.format()
  end)
end

{:ok, entries} = Hector.get(Repo, query, loader)

# Where `entries` is a list of %File{}, a valid Ecto Schema.

Hector是处理此接口的库。上面的例子是最复杂的情​​况:我们需要一个自定义脚轮和一个自定义加载器。在大多数情况下,Hector的默认加载器会很好,并且只要存在潜在的不安全输入,就需要自定义脚轮。

代码可以找到heretests上有一些额外的例子。

当然这远不是​​最好的解决方案,即使我能够为这个或那个查询找出正确的Ecto语法,但为原始查询提供一个很好的抽象/接口总是很方便。