Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow literal struct in insert_all + add recent changes to docs #4489

Merged
merged 2 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions lib/ecto/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1490,16 +1490,19 @@ defmodule Ecto.Repo do
## Source query

A query can be given instead of a list with entries. This query needs to select
into a map containing only keys that are available as writeable columns in the
schema. This will query and insert the values all inside one query, without
another round trip to the application.
into a map, struct or source containing only keys that are available as writeable
columns in the schema. This will query and insert the values all inside one query,
without another round trip to the application.

## Examples

# List of keyword lists
MyRepo.insert_all(Post, [[title: "My first post"], [title: "My second post"]])

# List of maps
MyRepo.insert_all(Post, [%{title: "My first post"}, %{title: "My second post"}])

# Query selecting into a map
query = from p in Post,
join: c in assoc(p, :comments),
select: %{
Expand All @@ -1508,8 +1511,29 @@ defmodule Ecto.Repo do
interactions: sum(p.likes) + count(c.id)
},
group_by: p.author_id

MyRepo.insert_all(AuthorStats, query)

query = from p in Post, select: map(p, [:author_id, :title])
MyRepo.insert_all(PostAuthors, query)

# Query selecting into a struct
query = from p in Post, select: %Post{author_id: p.author_id, title: p.title}
MyRepo.insert_all(PostAuthors, query)

query = from p in Post, select: struct(p, [:author_id, :title])
MyRepo.insert_all(PostAuthors, query)

# Query selecting into a source
query = from p in Post, select: p
MyRepo.insert_all(PostBackup, query)

query = from p in Post, select: %{p | public: false}
MyRepo.insert_all(PostBackup, query)

query = from p in Post, join: c in assoc(p, :comments), where: p.public, select: c
MyRepo.insert_all(PublicCommentBackup, query)

## Upserts

`c:insert_all/3` provides upserts (update or inserts) via the `:on_conflict`
Expand Down
9 changes: 5 additions & 4 deletions lib/ecto/repo/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ defmodule Ecto.Repo.Schema do
%Ecto.Query.SelectExpr{expr: {:&, _, [_ix]}, fields: fields} ->
Enum.map(fields, &insert_all_select_dump!(&1))

%Ecto.Query.SelectExpr{expr: {:%, _, [_, {:%{}, _, args}]}} ->
Enum.map(args, fn {field, _} -> insert_all_select_dump!(field, dumper) end)

_ ->
raise ArgumentError, """
cannot generate a fields list for insert_all from the given source query:
Expand All @@ -154,10 +157,8 @@ defmodule Ecto.Repo.Schema do
* A single `struct/2` or several `struct/2` expressions combined with `select_merge`
* A source such as `p` in the query `from p in Post`
* A single literal map or several literal maps combined with `select_merge`. If
combining several literal maps, there cannot be any query interpolations
except in the last `select_merge`. Consider using `Ecto.Query.exclude/2`
to rebuild the select expression from scratch if you need multiple `select_merge`
statements with interpolations
combining several literal maps, there cannot be any overwrites to keys containing
query interpolations.

All keys must exist in the schema that is being inserted into
"""
Expand Down
19 changes: 19 additions & 0 deletions test/ecto/repo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,25 @@ defmodule Ecto.RepoTest do
assert ["ten"] = params
end

test "takes query selecting on struct" do
threshold = "ten"

query =
from s in MySchema,
where: s.x > ^threshold,
select: %MySchema{
x: s.x,
y: "bar",
z: nil
}

TestRepo.insert_all(MySchema, query)

assert_received {:insert_all, %{source: "my_schema"}, {%Ecto.Query{} = query, params}}
assert [{{:., _, [{:&, [], [0]}, :x]}, _, []}, "bar", nil] = query.select.fields
assert ["ten"] = params
end

test "takes query selecting on struct/2" do
query =
from s in MySchema,
Expand Down
Loading