changed CHANGELOG.md
 
@@ -1,5 +1,51 @@
1
1
# Changelog for v3.x
2
2
3
+ ## v3.12.0 (2024-08-12)
4
+
5
+ ### Enhancements
6
+
7
+ * [Ecto.Changeset] Allow `{message, opts}` to be given as message for several validation APIs
8
+ * [Ecto.Query] Introduce `is_named_binding` guard
9
+ * [Ecto.Query] Subqueries are now supported in `distinct`, `group_by`, `order_by` and `window` expressions
10
+ * [Ecto.Query] Allow `select_merge` to be used in more `insert_all` and subquery operations by merging distinct fields
11
+ * [Ecto.Query] Allow literal maps inside `dynamic/2`
12
+ * [Ecto.Query] Support macro expansion at the root level of `order_by`
13
+ * [Ecto.Query] Support preloading subquery sources in `from` and `join`
14
+ * [Ecto.Query] Allow map updates with dynamic values in `select`
15
+ * [Ecto.Query] Allow any data structure that implements the Enumerable protocol on the right side of `in`
16
+ * [Ecto.Repo] Support 2-arity preload functions that receive ids and the association metadata
17
+ * [Ecto.Repo] Allow Hot Updates on upsert queries in Postgres by removing duplicate fields during replace_all
18
+ * [Ecto.Repo] `insert_all` supports queries with only source
19
+ * [Ecto.Repo] `insert_all` supports queries with the update syntax
20
+ * [Ecto.Repo] Support `:allow_stale` on Repo struct/changeset operations
21
+ * [Ecto.Schema] Allow schema fields to be read-only via `:writable` option
22
+ * [Ecto.Schema] Add `:defaults_to_struct` option to `embeds_one`
23
+ * [Ecto.Schema] Support `:duration` type which maps to Elixir v1.17 duration
24
+ * [Ecto.Type] Bubble up custom cast errors of the inner type for `{:map, type}` and `{:array, type}`
25
+ * [Ecto.Type] Add `Ecto.Type.cast!/2`
26
+
27
+ ### Bug fixes
28
+
29
+ * [Ecto.Query] Ignore query prefix in CTE sources
30
+ * [Ecto.Query] Fix a bug of `preload` when a through association is used in a join and has a nested separate query preload. Now the association chain is no longer preloaded and we simply preload directly onto the loaded through association.
31
+ * [Ecto.Query] Validate `:prefix` is a string/binary, warn otherwise
32
+ * [Ecto.Query] Fix inspection when select has `map/struct` modifiers
33
+ * [Ecto.Query] Disable query cache for `values` lists
34
+ * [Ecto.Repo] Convert fields to their sources in `insert_all`
35
+ * [Ecto.Repo] Raise if empty list is given to `{:replace, fields}`
36
+ * [Ecto.Repo] Validate `:prefix` is a string/binary, warn otherwise
37
+ * [Ecto.Repo] Remove compile dependency on `:preload_order` MFA in `has_many`
38
+
39
+ ### Adapter changes
40
+
41
+ * `distinct`, `group_by`, `order_by` and `window` expressions use the new `Ecto.Query.ByExpr`
42
+ struct rather than the old `Ecto.Query.QueryExpr` struct
43
+
44
+ ### Potential incompatibilities
45
+
46
+ * [Ecto.ParameterizedType] Parameterized types are now represented internally as `{:parameterized, {mod, state}}`. While this representation is private, projects may have been relying on it, and therefore they need to adapt accordingly. Use `Ecto.ParameterizedType.init/2` to instantiate parameterized types.
47
+ * [Ecto.Query] Drop `:array_join` join type. It was added for Clickhouse support but it is no longer used
48
+
3
49
## v3.11.2 (2024-03-07)
4
50
5
51
### Bug fixes
changed README.md
 
@@ -77,7 +77,7 @@ Ecto is commonly used to interact with databases, such as PostgreSQL and MySQL v
77
77
78
78
See the [getting started guide](https://hexdocs.pm/ecto/getting-started.html) and the [online documentation](https://hexdocs.pm/ecto) for more information. Other resources available are:
79
79
80
- * [Programming Ecto](https://pragprog.com/book/wmecto/programming-ecto), by Darin Wilson and Eric Meadows-Jönsson, which guides you from fundamentals up to advanced concepts
80
+ * [Programming Ecto](https://pragprog.com/titles/wmecto/programming-ecto/), by Darin Wilson and Eric Meadows-Jönsson, which guides you from fundamentals up to advanced concepts
81
81
82
82
* [The Little Ecto Cookbook](https://dashbit.co/ebooks/the-little-ecto-cookbook), a free ebook by Dashbit, which is a curation of the existing Ecto guides with some extra contents
83
83
 
@@ -129,12 +129,12 @@ defmodule MyApp.Repo do
129
129
130
130
| Branch | Support |
131
131
| ----------------- | ------------------------ |
132
- | v3.11 | Bug fixes |
132
+ | v3.12 | Bug fixes |
133
+ | v3.11 | Security patches only |
133
134
| v3.10 | Security patches only |
134
135
| v3.9 | Security patches only |
135
136
| v3.8 | Security patches only |
136
- | v3.7 | Security patches only |
137
- | v3.6 and earlier | Unsupported |
137
+ | v3.7 and earlier | Unsupported |
138
138
139
139
With version 3.0, Ecto API has become stable. Our main focus is on providing
140
140
bug fixes and incremental changes.
changed hex_metadata.config
 
@@ -1,6 +1,6 @@
1
1
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/elixir-ecto/ecto">>}]}.
2
2
{<<"name">>,<<"ecto">>}.
3
- {<<"version">>,<<"3.11.2">>}.
3
+ {<<"version">>,<<"3.12.0">>}.
4
4
{<<"description">>,
5
5
<<"A toolkit for data mapping and language integrated query for Elixir">>}.
6
6
{<<"elixir">>,<<"~> 1.11">>}.
changed integration_test/cases/joins.exs
 
@@ -589,4 +589,84 @@ defmodule Ecto.Integration.JoinsTest do
589
589
assert [post] = TestRepo.all(query)
590
590
assert post.post_user_composite_pk
591
591
end
592
+
593
+ test "joining a through association with a nested preloads" do
594
+ post = TestRepo.insert!(%Post{title: "1"})
595
+ user = TestRepo.insert!(%User{name: "1"})
596
+ TestRepo.insert!(%Comment{text: "1", post_id: post.id})
597
+ TestRepo.insert!(%Permalink{post_id: post.id, user_id: user.id})
598
+
599
+ query =
600
+ from c in Comment,
601
+ join: pp in assoc(c, :post_permalink),
602
+ join: u in assoc(pp, :user),
603
+ preload: [post_permalink: {pp, [:post, user: u]}]
604
+
605
+ [comment] = TestRepo.all(query)
606
+
607
+ assert not Ecto.assoc_loaded?(comment.post)
608
+ assert %Permalink{user: %User{}, post: %Post{}} = comment.post_permalink
609
+ end
610
+
611
+ test "joining multiple through associations with a nested preloads" do
612
+ post = TestRepo.insert!(%Post{title: "1"})
613
+ user = TestRepo.insert!(%User{name: "1"})
614
+ TestRepo.insert!(%Comment{text: "1", post_id: post.id, author_id: user.id})
615
+ TestRepo.insert!(%Permalink{post_id: post.id, user_id: user.id})
616
+
617
+ query =
618
+ from c in Comment,
619
+ join: pp in assoc(c, :post_permalink),
620
+ join: ap in assoc(c, :author_permalink),
621
+ join: u1 in assoc(pp, :user),
622
+ join: u2 in assoc(ap, :user),
623
+ preload: [post_permalink: {pp, [:post, user: u1]}, author_permalink: {ap, [:post, user: u2]}]
624
+
625
+ [comment] = TestRepo.all(query)
626
+
627
+ assert not Ecto.assoc_loaded?(comment.post)
628
+ assert not Ecto.assoc_loaded?(comment.author)
629
+ assert %Permalink{user: %User{}, post: %Post{}} = comment.post_permalink
630
+ assert %Permalink{user: %User{}, post: %Post{}} = comment.author_permalink
631
+ end
632
+
633
+ test "joining nested through associations with a nested preloads" do
634
+ user = TestRepo.insert!(%User{name: "1"})
635
+ post = TestRepo.insert!(%Post{title: "1", author_id: user.id})
636
+ TestRepo.insert!(%Comment{text: "1", post_id: post.id})
637
+ TestRepo.insert!(%Permalink{post_id: post.id, user_id: user.id})
638
+
639
+ query =
640
+ from c in Comment,
641
+ join: pp in assoc(c, :post_permalink),
642
+ join: up in assoc(pp, :user_posts),
643
+ preload: [post_permalink: {pp, [:post, user_posts: {up, :comments}]}]
644
+
645
+ [comment] = TestRepo.all(query)
646
+
647
+ assert not Ecto.assoc_loaded?(comment.post)
648
+ assert %Permalink{post: %Post{}, user_posts: [%Post{}]} = comment.post_permalink
649
+ assert not Ecto.assoc_loaded?(comment.post_permalink.user)
650
+ end
651
+
652
+ test "joining and preloading through a subquery" do
653
+ %{id: p_id} = TestRepo.insert!(%Post{})
654
+ %{id: c1_id} = TestRepo.insert!(%Comment{post_id: p_id})
655
+ %{id: c2_id} = TestRepo.insert!(%Comment{post_id: p_id})
656
+
657
+ q =
658
+ from p1 in Post,
659
+ left_join: u in User,
660
+ on: p1.author_id == u.id,
661
+ inner_join: c in subquery(from c in Comment),
662
+ on: p1.id == c.post_id,
663
+ join: p2 in Post,
664
+ on: c.post_id == p2.id,
665
+ preload: [author: u, force_comments: {c, post: p2}]
666
+
667
+ assert [%Post{id: ^p_id, force_comments: comments}] = TestRepo.all(q)
668
+ [comment1, comment2] = Enum.sort_by(comments, & &1.id)
669
+ assert %Comment{id: ^c1_id, post: %Post{id: ^p_id}} = comment1
670
+ assert %Comment{id: ^c2_id, post: %Post{id: ^p_id}} = comment2
671
+ end
592
672
end
changed integration_test/cases/preload.exs
 
@@ -329,7 +329,7 @@ defmodule Ecto.Integration.PreloadTest do
329
329
330
330
## With queries
331
331
332
- test "preload with function" do
332
+ test "preload with 1-arity function" do
333
333
p1 = TestRepo.insert!(%Post{title: "1"})
334
334
p2 = TestRepo.insert!(%Post{title: "2"})
335
335
p3 = TestRepo.insert!(%Post{title: "3"})
 
@@ -347,6 +347,23 @@ defmodule Ecto.Integration.PreloadTest do
347
347
assert [] = pe3.comments
348
348
end
349
349
350
+ test "preload with 2-arity function" do
351
+ p = TestRepo.insert!(%Post{title: "1"})
352
+ c1 = TestRepo.insert!(%Comment{post_id: p.id})
353
+ c2 = TestRepo.insert!(%Comment{post_id: p.id})
354
+
355
+ # making a simple preloader so that it works across all adapters
356
+ preloader = fn parent_ids, assoc ->
357
+ %{related_key: related_key, queryable: queryable} = assoc
358
+
359
+ from(q in queryable, where: field(q, ^related_key) in ^parent_ids, order_by: q.id)
360
+ |> TestRepo.all()
361
+ end
362
+
363
+ assert p = TestRepo.preload(p, comments: preloader)
364
+ assert [^c1, ^c2] = p.comments
365
+ end
366
+
350
367
test "preload many_to_many with function" do
351
368
p1 = TestRepo.insert!(%Post{title: "1"})
352
369
p2 = TestRepo.insert!(%Post{title: "2"})
 
@@ -471,6 +488,19 @@ defmodule Ecto.Integration.PreloadTest do
471
488
[%{id: u1.id}, %{id: u2.id}, %{id: u3.id}, %{id: u4.id}]
472
489
end
473
490
491
+ test "preload into a subquery source" do
492
+ %{id: p_id} = TestRepo.insert!(%Post{})
493
+ %{id: c_id} = TestRepo.insert!(%Comment{post_id: p_id})
494
+
495
+ q =
496
+ from c in subquery(from c in Comment),
497
+ join: p in Post,
498
+ on: c.post_id == p.id,
499
+ preload: [post: p]
500
+
501
+ assert [%Comment{id: ^c_id, post: %Post{id: ^p_id}}] = TestRepo.all(q)
502
+ end
503
+
474
504
## With take
475
505
476
506
test "preload with take" do
changed integration_test/cases/repo.exs
 
@@ -961,6 +961,14 @@ defmodule Ecto.Integration.RepoTest do
961
961
962
962
assert %Post{title: ^expected_title} = TestRepo.get(Post, expected_id)
963
963
end
964
+
965
+ test "insert_all with query and source field" do
966
+ %{id: post_id} = TestRepo.insert!(%Post{})
967
+ TestRepo.insert!(%Permalink{url: "url", title: "title"})
968
+
969
+ source = from p in Permalink, select: %{url: p.title, post_id: ^post_id}
970
+ assert {1, _} = TestRepo.insert_all(Permalink, source)
971
+ end
964
972
end
965
973
966
974
@tag :invalid_prefix
 
@@ -1347,6 +1355,12 @@ defmodule Ecto.Integration.RepoTest do
1347
1355
assert [%Post{title: "new title", visits: 13}] =
1348
1356
TestRepo.all(from p in Post, select: %Post{p | title: "new title"})
1349
1357
1358
+ assert [%Post{:title => "1", visits: -1}] =
1359
+ TestRepo.all(from p in Post, select: %{p | visits: ^"-1"})
1360
+
1361
+ assert [%Post{title: "1", visits: -1}] =
1362
+ TestRepo.all(from p in Post, select: %Post{p | visits: ^"-1"})
1363
+
1350
1364
assert_raise KeyError, fn ->
1351
1365
TestRepo.all(from p in Post, select: %{p | unknown: "new title"})
1352
1366
end
changed integration_test/cases/type.exs
 
@@ -1,7 +1,7 @@
1
1
defmodule Ecto.Integration.TypeTest do
2
2
use Ecto.Integration.Case, async: Application.compile_env(:ecto, :async_integration_tests, true)
3
3
4
- alias Ecto.Integration.{Comment, Custom, Item, ItemColor, Order, Post, User, Tag, Usec}
4
+ alias Ecto.Integration.{Bitstring, Comment, Custom, Item, ItemColor, Order, Post, User, Tag, Usec}
5
5
alias Ecto.Integration.TestRepo
6
6
import Ecto.Query
7
7
 
@@ -67,6 +67,66 @@ defmodule Ecto.Integration.TypeTest do
67
67
assert [^datetime] = TestRepo.all(from u in Usec, where: u.utc_datetime_usec == ^datetime, select: u.utc_datetime_usec)
68
68
end
69
69
70
+ @tag :bitstring_type
71
+ test "bitstring type" do
72
+ bitstring = <<2::3>>
73
+
74
+ TestRepo.insert!(%Bitstring{bs: bitstring, bs_with_size: <<5::10>>})
75
+
76
+ # Bitstrings
77
+ assert [^bitstring] = TestRepo.all(from p in Bitstring, where: p.bs == ^bitstring, select: p.bs)
78
+ assert [^bitstring] = TestRepo.all(from p in Bitstring, where: p.bs == <<2::3>>, select: p.bs)
79
+
80
+ assert [<<42::6>>] = TestRepo.all(from p in Bitstring, limit: 1, select: p.bs_with_default)
81
+ end
82
+
83
+ if Code.ensure_loaded?(Duration) do
84
+ @tag :duration_type
85
+ test "duration type" do
86
+ duration = %Duration{year: 1, month: 1, second: 1, microsecond: {100, 6}}
87
+
88
+ struct = %Ecto.Integration.Duration{
89
+ dur: duration,
90
+ dur_with_fields: duration,
91
+ dur_with_precision: duration,
92
+ dur_with_fields_and_precision: duration
93
+ }
94
+
95
+ TestRepo.insert!(struct)
96
+
97
+ persisted_duration =
98
+ from(d in Ecto.Integration.Duration, where: d.dur == ^duration)
99
+ |> TestRepo.one()
100
+
101
+ assert persisted_duration.dur == duration
102
+
103
+ # `:field` option set to MONTH so it ignores all units lower than `:month`
104
+ assert persisted_duration.dur_with_fields == %Duration{
105
+ year: 1,
106
+ month: 1,
107
+ microsecond: {0, 6}
108
+ }
109
+
110
+ assert persisted_duration.dur_with_precision == %Duration{
111
+ year: 1,
112
+ month: 1,
113
+ second: 1,
114
+ microsecond: {100, 4}
115
+ }
116
+
117
+ # `:field` option is set to HOUR TO SECOND so it ignores all units lower than `:second`
118
+ assert persisted_duration.dur_with_fields_and_precision == %Duration{
119
+ year: 1,
120
+ month: 1,
121
+ second: 1,
122
+ microsecond: {0, 1}
123
+ }
124
+
125
+ # `:default set in migration`
126
+ assert persisted_duration.dur_with_default == %Duration{month: 10, microsecond: {0, 6}}
127
+ end
128
+ end
129
+
70
130
@tag :select_not
71
131
test "primitive types boolean negate" do
72
132
TestRepo.insert!(%Post{public: true})
changed integration_test/support/schemas.exs
 
@@ -97,6 +97,7 @@ defmodule Ecto.Integration.Comment do
97
97
belongs_to :post, Ecto.Integration.Post
98
98
belongs_to :author, Ecto.Integration.User
99
99
has_one :post_permalink, through: [:post, :permalink]
100
+ has_one :author_permalink, through: [:author, :permalink]
100
101
end
101
102
102
103
def changeset(schema, params) do
 
@@ -123,6 +124,7 @@ defmodule Ecto.Integration.Permalink do
123
124
belongs_to :update_post, Ecto.Integration.Post, on_replace: :update, foreign_key: :post_id, define_field: false
124
125
belongs_to :user, Ecto.Integration.User
125
126
has_many :post_comments_authors, through: [:post, :comments_authors]
127
+ has_many :user_posts, through: [:user, :posts]
126
128
end
127
129
128
130
def changeset(schema, params) do
 
@@ -385,3 +387,37 @@ defmodule Ecto.Integration.ArrayLogging do
385
387
timestamps()
386
388
end
387
389
end
390
+
391
+ defmodule Ecto.Integration.Bitstring do
392
+ @moduledoc """
393
+ This module is used to test:
394
+
395
+ * Bitstring type
396
+
397
+ """
398
+ use Ecto.Integration.Schema
399
+
400
+ schema "bitstrings" do
401
+ field :bs, :bitstring
402
+ field :bs_with_default, :bitstring
403
+ field :bs_with_size, :bitstring
404
+ end
405
+ end
406
+
407
+ if Code.ensure_loaded?(Duration) do
408
+ defmodule Ecto.Integration.Duration do
409
+ @moduledoc """
410
+ This module is used to test:
411
+ * Duration type
412
+ """
413
+ use Ecto.Integration.Schema
414
+
415
+ schema "durations" do
416
+ field :dur, :duration
417
+ field :dur_with_fields, :duration
418
+ field :dur_with_precision, :duration
419
+ field :dur_with_fields_and_precision, :duration
420
+ field :dur_with_default, :duration
421
+ end
422
+ end
423
+ end
changed lib/ecto.ex
 
@@ -697,7 +697,9 @@ defmodule Ecto do
697
697
698
698
The third argument `format` is the format the data has been dumped as. For
699
699
example, databases may dump embedded to `:json`, this function allows such
700
- dumped data to be put back into the schemas.
700
+ dumped data to be put back into the schemas. If custom types are used,
701
+ Ecto will invoke the `c:Ecto.Type.embed_as/1` callback to decide if the data
702
+ should be loaded using `cast` or `load`.
701
703
702
704
Fields that are not present in the schema (or `types` map) are ignored.
703
705
If any of the values has invalid type, an error is raised.
changed lib/ecto/adapter.ex
 
@@ -45,7 +45,7 @@ defmodule Ecto.Adapter do
45
45
calls to the datastore to share cache information:
46
46
47
47
Repo.checkout(fn ->
48
- for _ <- 100 do
48
+ for _ <- 1..100 do
49
49
Repo.insert!(%Post{})
50
50
end
51
51
end)
changed lib/ecto/adapter/queryable.ex
 
@@ -50,7 +50,7 @@ defmodule Ecto.Adapter.Queryable do
50
50
Commands invoked to prepare a query.
51
51
52
52
It is used on `c:Ecto.Repo.all/2`, `c:Ecto.Repo.update_all/3`,
53
- and `c:Ecto.Repo.delete_all/2`. If returns a tuple, saying if
53
+ and `c:Ecto.Repo.delete_all/2`. It returns a tuple, indicating if
54
54
this query can be cached or not, and the `prepared` query.
55
55
The `prepared` query is any term that will be passed to the
56
56
adapter's `c:execute/5`.
changed lib/ecto/adapter/transaction.ex
 
@@ -9,7 +9,7 @@ defmodule Ecto.Adapter.Transaction do
9
9
Runs the given function inside a transaction.
10
10
11
11
Returns `{:ok, value}` if the transaction was successful where `value`
12
- is the value return by the function or `{:error, value}` if the transaction
12
+ is the value returned by the function or `{:error, value}` if the transaction
13
13
was rolled back where `value` is the value given to `rollback/1`.
14
14
"""
15
15
@callback transaction(adapter_meta, options :: Keyword.t(), function :: fun) ::
changed lib/ecto/changeset.ex
 
@@ -125,7 +125,16 @@ defmodule Ecto.Changeset do
125
125
126
126
When applying changes using `cast/4`, an empty value will be automatically
127
127
converted to the field's default value. If the field is an array type, any
128
- empty value inside the array will be removed.
128
+ empty value inside the array will be removed. When a plain map is used in
129
+ the data portion of a schemaless changeset, every field's default value is
130
+ considered to be `nil`. For example:
131
+
132
+ iex> data = %{name: "Bob"}
133
+ iex> types = %{name: :string}
134
+ iex> params = %{name: ""}
135
+ iex> changeset = Ecto.Changeset.cast({data, types}, params, Map.keys(types))
136
+ iex> changeset.changes
137
+ %{name: nil}
129
138
130
139
Empty values are stored as a list in the changeset's `:empty_values` field.
131
140
The list contains elements of type `t:empty_value/0`. Those are either values,
 
@@ -351,7 +360,7 @@ defmodule Ecto.Changeset do
351
360
constraints: [],
352
361
filters: %{},
353
362
action: nil,
354
- types: nil,
363
+ types: %{},
355
364
empty_values: @empty_values,
356
365
repo: nil,
357
366
repo_opts: []
 
@@ -367,10 +376,10 @@ defmodule Ecto.Changeset do
367
376
prepare: [(t -> t)],
368
377
errors: [{atom, error}],
369
378
constraints: [constraint],
370
- validations: [{atom, term}],
379
+ validations: [validation],
371
380
filters: %{optional(atom) => term},
372
381
action: action,
373
- types: nil | %{atom => Ecto.Type.t() | {:assoc, term()} | {:embed, term()}}
382
+ types: types
374
383
}
375
384
376
385
@type t :: t(Ecto.Schema.t() | map | nil)
 
@@ -385,8 +394,9 @@ defmodule Ecto.Changeset do
385
394
error_type: atom
386
395
}
387
396
@type data :: map()
388
- @type types :: map()
397
+ @type types :: %{atom => Ecto.Type.t() | {:assoc, term()} | {:embed, term()}}
389
398
@type traverse_result :: %{atom => [term] | traverse_result}
399
+ @type validation :: {atom, term}
390
400
391
401
@typedoc """
392
402
A possible value that you can pass to the `:empty_values` option.
 
@@ -470,10 +480,6 @@ defmodule Ecto.Changeset do
470
480
change(%Changeset{data: data, types: Enum.into(types, %{}), valid?: true}, changes)
471
481
end
472
482
473
- def change(%Changeset{types: nil}, _changes) do
474
- raise ArgumentError, "changeset does not have types information"
475
- end
476
-
477
483
def change(%Changeset{changes: changes, types: types} = changeset, new_changes)
478
484
when is_map(new_changes) or is_list(new_changes) do
479
485
{changes, errors, valid?} =
 
@@ -729,10 +735,6 @@ defmodule Ecto.Changeset do
729
735
cast(data, types, %{}, params, permitted, opts)
730
736
end
731
737
732
- def cast(%Changeset{types: nil}, _params, _permitted, _opts) do
733
- raise ArgumentError, "changeset does not have types information"
734
- end
735
-
736
738
def cast(%Changeset{} = changeset, params, permitted, opts) do
737
739
%{changes: changes, data: data, types: types, empty_values: empty_values} = changeset
738
740
 
@@ -1025,7 +1027,8 @@ defmodule Ecto.Changeset do
1025
1027
1026
1028
* If there is an associated child with an ID and its ID is not given
1027
1029
as parameter, the `:on_replace` callback for that association will
1028
- be invoked (see the "On replace" section on the module documentation)
1030
+ be invoked (see the ["On replace" section](#module-the-on_replace-option)
1031
+ on the module documentation)
1029
1032
1030
1033
If two or more addresses have the same IDs, Ecto will consider that an
1031
1034
error and add an error to the changeset saying that there are duplicate
 
@@ -1076,7 +1079,7 @@ defmodule Ecto.Changeset do
1076
1079
of `cast_assoc/3`. This opens up the possibility to work on a subset of the data,
1077
1080
instead of all associations in the database.
1078
1081
1079
- Taking the initial example of users having addresses imagine those addresses
1082
+ Taking the initial example of users having addresses, imagine those addresses
1080
1083
are set up to belong to a country. If you want to allow users to bulk edit all
1081
1084
addresses that belong to a single country, you can do so by changing the preload
1082
1085
query:
 
@@ -1193,7 +1196,17 @@ defmodule Ecto.Changeset do
1193
1196
`cast_assoc/3` for more information
1194
1197
1195
1198
"""
1196
- def cast_assoc(changeset, name, opts \\ []) when is_atom(name) do
1199
+ @spec cast_assoc(t, atom, Keyword.t()) :: t
1200
+ def cast_assoc(changeset, name, opts \\ [])
1201
+
1202
+ def cast_assoc(%Changeset{data: %{} = data}, _name, _opts)
1203
+ when not is_map_key(data, :__meta__) do
1204
+ raise ArgumentError,
1205
+ "cast_assoc/3 cannot be used to cast associations into embedded schemas or schemaless changesets. " <>
1206
+ "Please modify the association independently."
1207
+ end
1208
+
1209
+ def cast_assoc(changeset, name, opts) when is_atom(name) do
1197
1210
cast_relation(:assoc, changeset, name, opts)
1198
1211
end
1199
1212
 
@@ -1240,6 +1253,7 @@ defmodule Ecto.Changeset do
1240
1253
`cast_assoc/3` for more information
1241
1254
1242
1255
"""
1256
+ @spec cast_embed(t, atom, Keyword.t()) :: t
1243
1257
def cast_embed(changeset, name, opts \\ []) when is_atom(name) do
1244
1258
cast_relation(:embed, changeset, name, opts)
1245
1259
end
 
@@ -1284,7 +1298,7 @@ defmodule Ecto.Changeset do
1284
1298
1285
1299
{:error, {message, meta}} ->
1286
1300
meta = [validation: type] ++ meta
1287
- error = {key, {message(opts, :invalid_message, message), meta}}
1301
+ error = {key, message(opts, :invalid_message, message, meta)}
1288
1302
%{changeset | errors: [error | changeset.errors], valid?: false}
1289
1303
1290
1304
# ignore or ok with change == original
 
@@ -1391,7 +1405,7 @@ defmodule Ecto.Changeset do
1391
1405
1392
1406
if required? and Relation.empty?(relation, current_changes) do
1393
1407
errors = [
1394
- {name, {message(opts, :required_message, "can't be blank"), [validation: :required]}}
1408
+ {name, message(opts, :required_message, "can't be blank", validation: :required)}
1395
1409
| errors
1396
1410
]
1397
1411
 
@@ -1502,10 +1516,10 @@ defmodule Ecto.Changeset do
1502
1516
1503
1517
defp cast_merge(cs1, cs2) do
1504
1518
new_params = (cs1.params || cs2.params) && Map.merge(cs1.params || %{}, cs2.params || %{})
1519
+ new_types = Map.merge(cs1.types, cs2.types)
1505
1520
new_changes = Map.merge(cs1.changes, cs2.changes)
1506
1521
new_errors = Enum.uniq(cs1.errors ++ cs2.errors)
1507
1522
new_required = Enum.uniq(cs1.required ++ cs2.required)
1508
- new_types = cs1.types || cs2.types
1509
1523
new_valid? = cs1.valid? and cs2.valid?
1510
1524
1511
1525
%{
 
@@ -1675,6 +1689,7 @@ defmodule Ecto.Changeset do
1675
1689
[%Post{id: 1, title: "world"}]
1676
1690
1677
1691
"""
1692
+ @spec get_assoc(t, atom, :changeset | :struct) :: [t | Ecto.Schema.t()]
1678
1693
def get_assoc(changeset, name, as \\ :changeset)
1679
1694
1680
1695
def get_assoc(%Changeset{} = changeset, name, :struct) do
 
@@ -1721,10 +1736,6 @@ defmodule Ecto.Changeset do
1721
1736
get_relation(:embed, changeset, name)
1722
1737
end
1723
1738
1724
- defp get_relation(_tag, %{types: nil}, _name) do
1725
- raise ArgumentError, "changeset does not have types information"
1726
- end
1727
-
1728
1739
defp get_relation(tag, %{changes: changes, data: data, types: types}, name) do
1729
1740
_ = relation!(:get, tag, name, Map.get(types, name))
1730
1741
 
@@ -1809,7 +1820,7 @@ defmodule Ecto.Changeset do
1809
1820
1810
1821
The given `function` is invoked with the change value only if there
1811
1822
is a change for `key`. Once the function is invoked, it behaves as
1812
- `put_changed/3`.
1823
+ `put_change/3`.
1813
1824
1814
1825
Note that the value of the change can still be `nil` (unless the field
1815
1826
was marked as required on `validate_required/3`).
 
@@ -1864,10 +1875,6 @@ defmodule Ecto.Changeset do
1864
1875
1865
1876
"""
1866
1877
@spec put_change(t, atom, term) :: t
1867
- def put_change(%Changeset{types: nil}, _key, _value) do
1868
- raise ArgumentError, "changeset does not have types information"
1869
- end
1870
-
1871
1878
def put_change(%Changeset{data: data, types: types} = changeset, key, value) do
1872
1879
type = Map.get(types, key)
1873
1880
 
@@ -1981,7 +1988,7 @@ defmodule Ecto.Changeset do
1981
1988
update or delete them). Different to passing changesets, structs are not
1982
1989
change tracked in any fashion. In other words, if you change a comment
1983
1990
struct and give it to `put_assoc/4`, the updates in the struct won't be
1984
- persisted. You must use changesets instead. `put_assoc/4` with structs
1991
+ persisted. You must use changesets, keyword lists, or maps instead. `put_assoc/4` with structs
1985
1992
only takes care of guaranteeing that the comments and the parent data
1986
1993
are associated. This is extremely useful when associating existing data,
1987
1994
as we will see in the "Example: Adding tags to a post" section.
 
@@ -2027,7 +2034,7 @@ defmodule Ecto.Changeset do
2027
2034
beginning, which is that there is only one new comment.
2028
2035
2029
2036
In cases like above, when you want to work only on a single entry, it is
2030
- much easier to simply work on the associated directly. For example, we
2037
+ much easier to simply work on the association directly. For example, we
2031
2038
could instead set the `post` association in the comment:
2032
2039
2033
2040
%Comment{body: "better example"}
 
@@ -2093,8 +2100,19 @@ defmodule Ecto.Changeset do
2093
2100
* The [associations cheatsheet](associations.html)
2094
2101
* The [Constraints and Upserts guide](constraints-and-upserts.html)
2095
2102
* The [Polymorphic associations with many to many guide](polymorphic-associations-with-many-to-many.html)
2103
+
2096
2104
"""
2097
- def put_assoc(%Changeset{} = changeset, name, value, opts \\ []) do
2105
+ @spec put_assoc(t, atom, term, Keyword.t()) :: t
2106
+ def put_assoc(changeset, name, value, opts \\ [])
2107
+
2108
+ def put_assoc(%Changeset{data: %{} = data}, _name, _value, _opts)
2109
+ when not is_map_key(data, :__meta__) do
2110
+ raise ArgumentError,
2111
+ "put_assoc/4 cannot be used to put associations into embedded schemas or schemaless changesets. " <>
2112
+ "Please modify the association independently."
2113
+ end
2114
+
2115
+ def put_assoc(%Changeset{} = changeset, name, value, opts) do
2098
2116
put_relation(:assoc, changeset, name, value, opts)
2099
2117
end
2100
2118
 
@@ -2115,14 +2133,11 @@ defmodule Ecto.Changeset do
2115
2133
Although this function accepts an `opts` argument, there are no options
2116
2134
currently supported by `put_embed/4`.
2117
2135
"""
2136
+ @spec put_embed(t, atom, term, Keyword.t()) :: t
2118
2137
def put_embed(%Changeset{} = changeset, name, value, opts \\ []) do
2119
2138
put_relation(:embed, changeset, name, value, opts)
2120
2139
end
2121
2140
2122
- defp put_relation(_tag, %{types: nil}, _name, _value, _opts) do
2123
- raise ArgumentError, "changeset does not have types information"
2124
- end
2125
-
2126
2141
defp put_relation(tag, changeset, name, value, _opts) do
2127
2142
%{data: data, types: types, changes: changes, errors: errors, valid?: valid?} = changeset
2128
2143
relation = relation!(:put, tag, name, Map.get(types, name))
 
@@ -2155,10 +2170,6 @@ defmodule Ecto.Changeset do
2155
2170
2156
2171
"""
2157
2172
@spec force_change(t, atom, term) :: t
2158
- def force_change(%Changeset{types: nil}, _key, _value) do
2159
- raise ArgumentError, "changeset does not have types information"
2160
- end
2161
-
2162
2173
def force_change(%Changeset{types: types} = changeset, key, value) do
2163
2174
case Map.get(types, key) do
2164
2175
{tag, _} when tag in @relations ->
 
@@ -2242,7 +2253,7 @@ defmodule Ecto.Changeset do
2242
2253
iex> {:error, changeset} = apply_action(changeset, :update)
2243
2254
%Ecto.Changeset{action: :update}
2244
2255
"""
2245
- @spec apply_action(t, atom) :: {:ok, Ecto.Schema.t() | data} | {:error, t}
2256
+ @spec apply_action(t, action) :: {:ok, Ecto.Schema.t() | data} | {:error, t}
2246
2257
def apply_action(%Changeset{} = changeset, action) when is_atom(action) do
2247
2258
if changeset.valid? do
2248
2259
{:ok, apply_changes(changeset)}
 
@@ -2270,7 +2281,7 @@ defmodule Ecto.Changeset do
2270
2281
2271
2282
See `apply_action/2` for more information.
2272
2283
"""
2273
- @spec apply_action!(t, atom) :: Ecto.Schema.t() | data
2284
+ @spec apply_action!(t, action) :: Ecto.Schema.t() | data
2274
2285
def apply_action!(%Changeset{} = changeset, action) do
2275
2286
case apply_action(changeset, action) do
2276
2287
{:ok, data} ->
 
@@ -2417,7 +2428,7 @@ defmodule Ecto.Changeset do
2417
2428
@spec validate_change(
2418
2429
t,
2419
2430
atom,
2420
- (atom, term -> [{atom, String.t()} | {atom, {String.t(), Keyword.t()}}])
2431
+ (atom, term -> [{atom, String.t()} | {atom, error}])
2421
2432
) :: t
2422
2433
def validate_change(%Changeset{} = changeset, field, validator) when is_atom(field) do
2423
2434
%{changes: changes, types: types, errors: errors} = changeset
 
@@ -2463,7 +2474,7 @@ defmodule Ecto.Changeset do
2463
2474
t,
2464
2475
atom,
2465
2476
term,
2466
- (atom, term -> [{atom, String.t()} | {atom, {String.t(), Keyword.t()}}])
2477
+ (atom, term -> [{atom, String.t()} | {atom, error}])
2467
2478
) :: t
2468
2479
def validate_change(
2469
2480
%Changeset{validations: validations} = changeset,
 
@@ -2489,7 +2500,7 @@ defmodule Ecto.Changeset do
2489
2500
If a field is given to `validate_required/3` but it has not been passed
2490
2501
as parameter during `cast/3` (i.e. it has not been changed), then
2491
2502
`validate_required/3` will check for its current value in the data.
2492
- If the data contains an non-empty value for the field, then no error is
2503
+ If the data contains a non-empty value for the field, then no error is
2493
2504
added. This allows developers to use `validate_required/3` to perform
2494
2505
partial updates. For example, on `insert` all fields would be required,
2495
2506
because their default values on the data are all `nil`, but on `update`,
 
@@ -2507,7 +2518,9 @@ defmodule Ecto.Changeset do
2507
2518
2508
2519
## Options
2509
2520
2510
- * `:message` - the message on failure, defaults to "can't be blank"
2521
+ * `:message` - the message on failure, defaults to "can't be blank".
2522
+ Can also be a `{msg, opts}` tuple, to provide additional options
2523
+ when using `traverse_errors/2`.
2511
2524
2512
2525
## Examples
2513
2526
 
@@ -2531,8 +2544,12 @@ defmodule Ecto.Changeset do
2531
2544
%{changeset | required: fields ++ required}
2532
2545
2533
2546
_ ->
2534
- message = message(opts, "can't be blank")
2535
- new_errors = Enum.map(fields_with_errors, &{&1, {message, [validation: :required]}})
2547
+ new_errors =
2548
+ Enum.map(
2549
+ fields_with_errors,
2550
+ &{&1, message(opts, "can't be blank", validation: :required)}
2551
+ )
2552
+
2536
2553
changes = Map.drop(changes, fields_with_errors)
2537
2554
2538
2555
%{
 
@@ -2599,7 +2616,8 @@ defmodule Ecto.Changeset do
2599
2616
## Options
2600
2617
2601
2618
* `:message` - the message in case the constraint check fails,
2602
- defaults to "has already been taken".
2619
+ defaults to "has already been taken". Can also be a `{msg, opts}` tuple,
2620
+ to provide additional options when using `traverse_errors/2`.
2603
2621
2604
2622
* `:error_key` - the key to which changeset error will be added when
2605
2623
check fails, defaults to the first field name of the given list of
 
@@ -2700,10 +2718,10 @@ defmodule Ecto.Changeset do
2700
2718
if repo.exists?(query, repo_opts) do
2701
2719
error_key = Keyword.get(opts, :error_key, hd(fields))
2702
2720
2703
- add_error(changeset, error_key, message(opts, "has already been taken"),
2704
- validation: :unsafe_unique,
2705
- fields: fields
2706
- )
2721
+ {error_message, keys} =
2722
+ message(opts, "has already been taken", validation: :unsafe_unique, fields: fields)
2723
+
2724
+ add_error(changeset, error_key, error_message, keys)
2707
2725
else
2708
2726
changeset
2709
2727
end
 
@@ -2828,7 +2846,9 @@ defmodule Ecto.Changeset do
2828
2846
2829
2847
## Options
2830
2848
2831
- * `:message` - the message on failure, defaults to "has invalid format"
2849
+ * `:message` - the message on failure, defaults to "has invalid format".
2850
+ Can also be a `{msg, opts}` tuple, to provide additional options
2851
+ when using `traverse_errors/2`.
2832
2852
2833
2853
## Examples
2834
2854
 
@@ -2845,7 +2865,7 @@ defmodule Ecto.Changeset do
2845
2865
2846
2866
if value =~ format,
2847
2867
do: [],
2848
- else: [{field, {message(opts, "has invalid format"), [validation: :format]}}]
2868
+ else: [{field, message(opts, "has invalid format", validation: :format)}]
2849
2869
end)
2850
2870
end
2851
2871
 
@@ -2854,7 +2874,9 @@ defmodule Ecto.Changeset do
2854
2874
2855
2875
## Options
2856
2876
2857
- * `:message` - the message on failure, defaults to "is invalid"
2877
+ * `:message` - the message on failure, defaults to "is invalid".
2878
+ Can also be a `{msg, opts}` tuple, to provide additional options
2879
+ when using `traverse_errors/2`.
2858
2880
2859
2881
## Examples
2860
2882
 
@@ -2869,7 +2891,7 @@ defmodule Ecto.Changeset do
2869
2891
2870
2892
if Ecto.Type.include?(type, value, data),
2871
2893
do: [],
2872
- else: [{field, {message(opts, "is invalid"), [validation: :inclusion, enum: data]}}]
2894
+ else: [{field, message(opts, "is invalid", validation: :inclusion, enum: data)}]
2873
2895
end)
2874
2896
end
2875
2897
 
@@ -2884,7 +2906,9 @@ defmodule Ecto.Changeset do
2884
2906
2885
2907
## Options
2886
2908
2887
- * `:message` - the message on failure, defaults to "has an invalid entry"
2909
+ * `:message` - the message on failure, defaults to "has an invalid entry".
2910
+ Can also be a `{msg, opts}` tuple, to provide additional options
2911
+ when using `traverse_errors/2`.
2888
2912
2889
2913
## Examples
2890
2914
 
@@ -2908,7 +2932,7 @@ defmodule Ecto.Changeset do
2908
2932
2909
2933
case Enum.any?(value, fn element -> not Ecto.Type.include?(element_type, element, data) end) do
2910
2934
true ->
2911
- [{field, {message(opts, "has an invalid entry"), [validation: :subset, enum: data]}}]
2935
+ [{field, message(opts, "has an invalid entry", validation: :subset, enum: data)}]
2912
2936
2913
2937
false ->
2914
2938
[]
 
@@ -2921,7 +2945,9 @@ defmodule Ecto.Changeset do
2921
2945
2922
2946
## Options
2923
2947
2924
- * `:message` - the message on failure, defaults to "is reserved"
2948
+ * `:message` - the message on failure, defaults to "is reserved".
2949
+ Can also be a `{msg, opts}` tuple, to provide additional options
2950
+ when using `traverse_errors/2`.
2925
2951
2926
2952
## Examples
2927
2953
 
@@ -2934,7 +2960,7 @@ defmodule Ecto.Changeset do
2934
2960
type = Map.fetch!(changeset.types, field)
2935
2961
2936
2962
if Ecto.Type.include?(type, value, data),
2937
- do: [{field, {message(opts, "is reserved"), [validation: :exclusion, enum: data]}}],
2963
+ do: [{field, message(opts, "is reserved", validation: :exclusion, enum: data)}],
2938
2964
else: []
2939
2965
end)
2940
2966
end
 
@@ -2967,6 +2993,8 @@ defmodule Ecto.Changeset do
2967
2993
* "should have %{count} item(s)"
2968
2994
* "should have at least %{count} item(s)"
2969
2995
* "should have at most %{count} item(s)"
2996
+ Can also be a `{msg, opts}` tuple, to provide additional options
2997
+ when using `traverse_errors/2`.
2970
2998
2971
2999
## Examples
2972
3000
 
@@ -3029,66 +3057,114 @@ defmodule Ecto.Changeset do
3029
3057
3030
3058
defp wrong_length(:string, _length, value, opts),
3031
3059
do:
3032
- {message(opts, "should be %{count} character(s)"),
3033
- count: value, validation: :length, kind: :is, type: :string}
3060
+ message(opts, "should be %{count} character(s)",
3061
+ count: value,
3062
+ validation: :length,
3063
+ kind: :is,
3064
+ type: :string
3065
+ )
3034
3066
3035
3067
defp wrong_length(:binary, _length, value, opts),
3036
3068
do:
3037
- {message(opts, "should be %{count} byte(s)"),
3038
- count: value, validation: :length, kind: :is, type: :binary}
3069
+ message(opts, "should be %{count} byte(s)",
3070
+ count: value,
3071
+ validation: :length,
3072
+ kind: :is,
3073
+ type: :binary
3074
+ )
3039
3075
3040
3076
defp wrong_length(:list, _length, value, opts),
3041
3077
do:
3042
- {message(opts, "should have %{count} item(s)"),
3043
- count: value, validation: :length, kind: :is, type: :list}
3078
+ message(opts, "should have %{count} item(s)",
3079
+ count: value,
3080
+ validation: :length,
3081
+ kind: :is,
3082
+ type: :list
3083
+ )
3044
3084
3045
3085
defp wrong_length(:map, _length, value, opts),
3046
3086
do:
3047
- {message(opts, "should have %{count} item(s)"),
3048
- count: value, validation: :length, kind: :is, type: :map}
3087
+ message(opts, "should have %{count} item(s)",
3088
+ count: value,
3089
+ validation: :length,
3090
+ kind: :is,
3091
+ type: :map
3092
+ )
3049
3093
3050
3094
defp too_short(_type, length, value, _opts) when length >= value, do: nil
3051
3095
3052
3096
defp too_short(:string, _length, value, opts) do
3053
- {message(opts, "should be at least %{count} character(s)"),
3054
- count: value, validation: :length, kind: :min, type: :string}
3097
+ message(opts, "should be at least %{count} character(s)",
3098
+ count: value,
3099
+ validation: :length,
3100
+ kind: :min,
3101
+ type: :string
3102
+ )
3055
3103
end
3056
3104
3057
3105
defp too_short(:binary, _length, value, opts) do
3058
- {message(opts, "should be at least %{count} byte(s)"),
3059
- count: value, validation: :length, kind: :min, type: :binary}
3106
+ message(opts, "should be at least %{count} byte(s)",
3107
+ count: value,
3108
+ validation: :length,
3109
+ kind: :min,
3110
+ type: :binary
3111
+ )
3060
3112
end
3061
3113
3062
3114
defp too_short(:list, _length, value, opts) do
3063
- {message(opts, "should have at least %{count} item(s)"),
3064
- count: value, validation: :length, kind: :min, type: :list}
3115
+ message(opts, "should have at least %{count} item(s)",
3116
+ count: value,
3117
+ validation: :length,
3118
+ kind: :min,
3119
+ type: :list
3120
+ )
3065
3121
end
3066
3122
3067
3123
defp too_short(:map, _length, value, opts) do
3068
- {message(opts, "should have at least %{count} item(s)"),
3069
- count: value, validation: :length, kind: :min, type: :map}
3124
+ message(opts, "should have at least %{count} item(s)",
3125
+ count: value,
3126
+ validation: :length,
3127
+ kind: :min,
3128
+ type: :map
3129
+ )
3070
3130
end
3071
3131
3072
3132
defp too_long(_type, length, value, _opts) when length <= value, do: nil
3073
3133
3074
3134
defp too_long(:string, _length, value, opts) do
3075
- {message(opts, "should be at most %{count} character(s)"),
3076
- count: value, validation: :length, kind: :max, type: :string}
3135
+ message(opts, "should be at most %{count} character(s)",
3136
+ count: value,
3137
+ validation: :length,
3138
+ kind: :max,
3139
+ type: :string
3140
+ )
3077
3141
end
3078
3142
3079
3143
defp too_long(:binary, _length, value, opts) do
3080
- {message(opts, "should be at most %{count} byte(s)"),
3081
- count: value, validation: :length, kind: :max, type: :binary}
3144
+ message(opts, "should be at most %{count} byte(s)",
3145
+ count: value,
3146
+ validation: :length,
3147
+ kind: :max,
3148
+ type: :binary
3149
+ )
3082
3150
end
3083
3151
3084
3152
defp too_long(:list, _length, value, opts) do
3085
- {message(opts, "should have at most %{count} item(s)"),
3086
- count: value, validation: :length, kind: :max, type: :list}
3153
+ message(opts, "should have at most %{count} item(s)",
3154
+ count: value,
3155
+ validation: :length,
3156
+ kind: :max,
3157
+ type: :list
3158
+ )
3087
3159
end
3088
3160
3089
3161
defp too_long(:map, _length, value, opts) do
3090
- {message(opts, "should have at most %{count} item(s)"),
3091
- count: value, validation: :length, kind: :max, type: :map}
3162
+ message(opts, "should have at most %{count} item(s)",
3163
+ count: value,
3164
+ validation: :length,
3165
+ kind: :max,
3166
+ type: :map
3167
+ )
3092
3168
end
3093
3169
3094
3170
@doc """
 
@@ -3109,6 +3185,8 @@ defmodule Ecto.Changeset do
3109
3185
* "must be greater than or equal to %{number}"
3110
3186
* "must be equal to %{number}"
3111
3187
* "must be not equal to %{number}"
3188
+ Can also be a `{msg, opts}` tuple, to provide additional options
3189
+ when using `traverse_errors/2`.
3112
3190
3113
3191
## Examples
3114
3192
 
@@ -3121,14 +3199,14 @@ defmodule Ecto.Changeset do
3121
3199
def validate_number(changeset, field, opts) do
3122
3200
validate_change(changeset, field, {:number, opts}, fn
3123
3201
field, value ->
3124
- {message, opts} = Keyword.pop(opts, :message)
3125
-
3126
3202
unless valid_number?(value) do
3127
3203
raise ArgumentError,
3128
3204
"expected field `#{field}` to be a decimal, integer, or float, got: #{inspect(value)}"
3129
3205
end
3130
3206
3131
- Enum.find_value(opts, [], fn {spec_key, target_value} ->
3207
+ opts
3208
+ |> Keyword.drop([:message])
3209
+ |> Enum.find_value([], fn {spec_key, target_value} ->
3132
3210
case Map.fetch(@number_validators, spec_key) do
3133
3211
{:ok, {spec_function, default_message}} ->
3134
3212
unless valid_number?(target_value) do
 
@@ -3139,10 +3217,11 @@ defmodule Ecto.Changeset do
3139
3217
compare_numbers(
3140
3218
field,
3141
3219
value,
3142
- message || default_message,
3220
+ default_message,
3143
3221
spec_key,
3144
3222
spec_function,
3145
- target_value
3223
+ target_value,
3224
+ opts
3146
3225
)
3147
3226
3148
3227
:error ->
 
@@ -3167,31 +3246,32 @@ defmodule Ecto.Changeset do
3167
3246
defp compare_numbers(
3168
3247
field,
3169
3248
%Decimal{} = value,
3170
- message,
3249
+ default_message,
3171
3250
spec_key,
3172
3251
_spec_function,
3173
- %Decimal{} = target_value
3252
+ %Decimal{} = target_value,
3253
+ opts
3174
3254
) do
3175
3255
result = Decimal.compare(value, target_value)
3176
3256
3177
3257
case decimal_compare(result, spec_key) do
3178
3258
true -> nil
3179
- false -> [{field, {message, validation: :number, kind: spec_key, number: target_value}}]
3259
+ false -> [{field, message(opts, default_message, validation: :number, kind: spec_key, number: target_value)}]
3180
3260
end
3181
3261
end
3182
3262
3183
- defp compare_numbers(field, value, message, spec_key, spec_function, %Decimal{} = target_value) do
3184
- compare_numbers(field, decimal_new(value), message, spec_key, spec_function, target_value)
3263
+ defp compare_numbers(field, value, default_message, spec_key, spec_function, %Decimal{} = target_value, opts) do
3264
+ compare_numbers(field, decimal_new(value), default_message, spec_key, spec_function, target_value, opts)
3185
3265
end
3186
3266
3187
- defp compare_numbers(field, %Decimal{} = value, message, spec_key, spec_function, target_value) do
3188
- compare_numbers(field, value, message, spec_key, spec_function, decimal_new(target_value))
3267
+ defp compare_numbers(field, %Decimal{} = value, default_message, spec_key, spec_function, target_value, opts) do
3268
+ compare_numbers(field, value, default_message, spec_key, spec_function, decimal_new(target_value), opts)
3189
3269
end
3190
3270
3191
- defp compare_numbers(field, value, message, spec_key, spec_function, target_value) do
3271
+ defp compare_numbers(field, value, default_message, spec_key, spec_function, target_value, opts) do
3192
3272
case apply(spec_function, [value, target_value]) do
3193
3273
true -> nil
3194
- false -> [{field, {message, validation: :number, kind: spec_key, number: target_value}}]
3274
+ false -> [{field, message(opts, default_message, validation: :number, kind: spec_key, number: target_value)}]
3195
3275
end
3196
3276
end
3197
3277
 
@@ -3224,7 +3304,9 @@ defmodule Ecto.Changeset do
3224
3304
3225
3305
## Options
3226
3306
3227
- * `:message` - the message on failure, defaults to "does not match confirmation"
3307
+ * `:message` - the message on failure, defaults to "does not match confirmation".
3308
+ Can also be a `{msg, opts}` tuple, to provide additional options
3309
+ when using `traverse_errors/2`.
3228
3310
* `:required` - boolean, sets whether existence of confirmation parameter
3229
3311
is required for addition of error. Defaults to false
3230
3312
 
@@ -3253,8 +3335,7 @@ defmodule Ecto.Changeset do
3253
3335
3254
3336
%{^error_param => _} ->
3255
3337
[
3256
- {error_field,
3257
- {message(opts, "does not match confirmation"), [validation: :confirmation]}}
3338
+ {error_field, message(opts, "does not match confirmation", validation: :confirmation)}
3258
3339
]
3259
3340
3260
3341
%{} ->
 
@@ -3277,11 +3358,21 @@ defmodule Ecto.Changeset do
3277
3358
required = Keyword.get(opts, :required, false)
3278
3359
3279
3360
if required,
3280
- do: [{error_field, {message(opts, "can't be blank"), [validation: :required]}}],
3361
+ do: [{error_field, message(opts, "can't be blank", validation: :required)}],
3281
3362
else: []
3282
3363
end
3283
3364
3284
- defp message(opts, key \\ :message, default) do
3365
+ defp message(opts, key \\ :message, default, message_opts) do
3366
+ case Keyword.get(opts, key, default) do
3367
+ {message, extra_opts} when is_binary(message) and is_list(extra_opts) ->
3368
+ {message, Keyword.merge(message_opts, extra_opts)}
3369
+
3370
+ message when is_binary(message) ->
3371
+ {message, message_opts}
3372
+ end
3373
+ end
3374
+
3375
+ defp constraint_message(opts, key \\ :message, default) do
3285
3376
Keyword.get(opts, key, default)
3286
3377
end
3287
3378
 
@@ -3294,7 +3385,9 @@ defmodule Ecto.Changeset do
3294
3385
3295
3386
## Options
3296
3387
3297
- * `:message` - the message on failure, defaults to "must be accepted"
3388
+ * `:message` - the message on failure, defaults to "must be accepted".
3389
+ Can also be a `{msg, opts}` tuple, to provide additional options
3390
+ when using `traverse_errors/2`.
3298
3391
3299
3392
## Examples
3300
3393
 
@@ -3324,7 +3417,7 @@ defmodule Ecto.Changeset do
3324
3417
3325
3418
case Ecto.Type.cast(:boolean, value) do
3326
3419
{:ok, true} -> []
3327
- _ -> [{field, {message(opts, "must be accepted"), validation: :acceptance}}]
3420
+ _ -> [{field, message(opts, "must be accepted", validation: :acceptance)}]
3328
3421
end
3329
3422
end
3330
3423
 
@@ -3552,9 +3645,10 @@ defmodule Ecto.Changeset do
3552
3645
`starts_with?` `:name` to this changeset constraint.
3553
3646
3554
3647
"""
3648
+ @spec check_constraint(t, atom, Keyword.t()) :: t
3555
3649
def check_constraint(changeset, field, opts \\ []) do
3556
3650
name = opts[:name] || raise ArgumentError, "must supply the name of the constraint"
3557
- message = message(opts, "is invalid")
3651
+ message = constraint_message(opts, "is invalid")
3558
3652
match_type = Keyword.get(opts, :match, :exact)
3559
3653
3560
3654
add_constraint(changeset, :check, name, match_type, field, message, :check)
 
@@ -3699,7 +3793,7 @@ defmodule Ecto.Changeset do
3699
3793
3700
3794
def unique_constraint(changeset, [first_field | _] = fields, opts) do
3701
3795
name = opts[:name] || unique_index_name(changeset, fields)
3702
- message = message(opts, "has already been taken")
3796
+ message = constraint_message(opts, "has already been taken")
3703
3797
match_type = Keyword.get(opts, :match, :exact)
3704
3798
error_key = Keyword.get(opts, :error_key, first_field)
3705
3799
 
@@ -3765,7 +3859,7 @@ defmodule Ecto.Changeset do
3765
3859
def foreign_key_constraint(changeset, field, opts \\ []) do
3766
3860
name = opts[:name] || "#{get_source(changeset)}_#{get_field_source(changeset, field)}_fkey"
3767
3861
match_type = Keyword.get(opts, :match, :exact)
3768
- message = message(opts, "does not exist")
3862
+ message = constraint_message(opts, "does not exist")
3769
3863
3770
3864
add_constraint(changeset, :foreign_key, name, match_type, field, message, :foreign)
3771
3865
end
 
@@ -3823,7 +3917,7 @@ defmodule Ecto.Changeset do
3823
3917
end
3824
3918
3825
3919
match_type = Keyword.get(opts, :match, :exact)
3826
- message = message(opts, "does not exist")
3920
+ message = constraint_message(opts, "does not exist")
3827
3921
3828
3922
add_constraint(changeset, :foreign_key, name, match_type, assoc, message, :assoc)
3829
3923
end
 
@@ -3879,7 +3973,7 @@ defmodule Ecto.Changeset do
3879
3973
related: related
3880
3974
} ->
3881
3975
{opts[:name] || "#{related.__schema__(:source)}_#{related_key}_fkey",
3882
- message(opts, no_assoc_message(cardinality))}
3976
+ constraint_message(opts, no_assoc_message(cardinality))}
3883
3977
3884
3978
other ->
3885
3979
raise ArgumentError,
 
@@ -3917,7 +4011,7 @@ defmodule Ecto.Changeset do
3917
4011
name =
3918
4012
opts[:name] || "#{get_source(changeset)}_#{get_field_source(changeset, field)}_exclusion"
3919
4013
3920
- message = message(opts, "violates an exclusion constraint")
4014
+ message = constraint_message(opts, "violates an exclusion constraint")
3921
4015
match_type = Keyword.get(opts, :match, :exact)
3922
4016
3923
4017
add_constraint(changeset, :exclusion, name, match_type, field, message, :exclusion)
 
@@ -4129,7 +4223,7 @@ defmodule Ecto.Changeset do
4129
4223
"""
4130
4224
@spec traverse_validations(
4131
4225
t,
4132
- (error -> String.t()) | (Changeset.t(), atom, error -> String.t())
4226
+ (validation -> String.t()) | (Changeset.t(), atom, validation -> String.t())
4133
4227
) :: traverse_result
4134
4228
def traverse_validations(
4135
4229
%Changeset{validations: validations, changes: changes, types: types} = changeset,
 
@@ -4147,8 +4241,9 @@ defimpl Inspect, for: Ecto.Changeset do
4147
4241
import Inspect.Algebra
4148
4242
4149
4243
def inspect(%Ecto.Changeset{data: data} = changeset, opts) do
4244
+ # The trailing element is skipped later on
4150
4245
list =
4151
- for attr <- [:action, :changes, :errors, :data, :valid?] do
4246
+ for attr <- [:action, :changes, :errors, :data, :valid?, :action] do
4152
4247
{attr, Map.get(changeset, attr)}
4153
4248
end
4154
4249
 
@@ -4165,20 +4260,20 @@ defimpl Inspect, for: Ecto.Changeset do
4165
4260
[]
4166
4261
end
4167
4262
4168
- container_doc("#Ecto.Changeset<", list, ">", opts, fn
4169
- {:action, action}, opts ->
4263
+ container_doc("#Ecto.Changeset<", list, ">", %{limit: 5}, fn
4264
+ {:action, action}, _opts ->
4170
4265
concat("action: ", to_doc(action, opts))
4171
4266
4172
- {:changes, changes}, opts ->
4267
+ {:changes, changes}, _opts ->
4173
4268
concat("changes: ", changes |> filter(redacted_fields) |> to_doc(opts))
4174
4269
4175
4270
{:data, data}, _opts ->
4176
4271
concat("data: ", to_struct(data, opts))
4177
4272
4178
- {:errors, errors}, opts ->
4273
+ {:errors, errors}, _opts ->
4179
4274
concat("errors: ", to_doc(errors, opts))
4180
4275
4181
- {:valid?, valid?}, opts ->
4276
+ {:valid?, valid?}, _opts ->
4182
4277
concat("valid?: ", to_doc(valid?, opts))
4183
4278
end)
4184
4279
end
changed lib/ecto/changeset/relation.ex
 
@@ -469,7 +469,7 @@ defmodule Ecto.Changeset.Relation do
469
469
Enum.map_reduce(current, {%{}, 0}, fn struct, {acc, counter} ->
470
470
pks = get_pks.(struct)
471
471
key = if pks == [], do: map_size(acc), else: pks
472
- {pks, {Map.put(acc, key, struct), counter+ 1}}
472
+ {pks, {Map.put(acc, key, struct), counter + 1}}
473
473
end)
474
474
475
475
if map_size(map) != counter do
changed lib/ecto/enum.ex
 
@@ -4,7 +4,7 @@ defmodule Ecto.Enum do
4
4
5
5
`Ecto.Enum` must be used whenever you want to keep atom values in a field.
6
6
Since atoms cannot be persisted to the database, `Ecto.Enum` converts them
7
- to a string or an integer when writing to the database and converts them back
7
+ to strings or integers when writing to the database and converts them back
8
8
to atoms when loading data. It can be used in your schemas as follows:
9
9
10
10
# Stored as strings
 
@@ -15,7 +15,7 @@ defmodule Ecto.Enum do
15
15
# Stored as integers
16
16
field :status, Ecto.Enum, values: [foo: 1, bar: 2, baz: 5]
17
17
18
- Therefore, the type to be used in your migrations for enum fields depend
18
+ Therefore, the type to be used in your migrations for enum fields depends
19
19
on the choice above. For the cases above, one would do, respectively:
20
20
21
21
add :status, :string
 
@@ -38,24 +38,11 @@ defmodule Ecto.Enum do
38
38
by an atom in the list will be invalid.
39
39
40
40
The helper function `mappings/2` returns the mappings for a given schema and
41
- field, which can be used in places like form drop-downs. For example, given
42
- the following schema:
41
+ field, which can be used in places like form drop-downs. See `mappings/2` for
42
+ examples.
43
43
44
- defmodule EnumSchema do
45
- use Ecto.Schema
46
-
47
- schema "my_schema" do
48
- field :my_enum, Ecto.Enum, values: [:foo, :bar, :baz]
49
- end
50
- end
51
-
52
- You can call `mappings/2` like this:
53
-
54
- Ecto.Enum.mappings(EnumSchema, :my_enum)
55
- #=> [foo: "foo", bar: "bar", baz: "baz"]
56
-
57
- If you want the values only, you can use `Ecto.Enum.values/2`, and if you want
58
- the dump values only, you can use `Ecto.Enum.dump_values/2`.
44
+ If you want the values only, you can use `values/2`, and if you want
45
+ the "dump-able" values only, you can use `dump_values/2`.
59
46
60
47
## Embeds
61
48
 
@@ -175,10 +162,17 @@ defmodule Ecto.Enum do
175
162
176
163
def cast(data, params) do
177
164
case params do
178
- %{on_load: %{^data => as_atom}} -> {:ok, as_atom}
179
- %{on_dump: %{^data => _}} -> {:ok, data}
180
- %{on_cast: %{^data => as_atom}} -> {:ok, as_atom}
181
- _ -> :error
165
+ %{on_load: %{^data => as_atom}} ->
166
+ {:ok, as_atom}
167
+
168
+ %{on_dump: %{^data => _}} ->
169
+ {:ok, data}
170
+
171
+ %{on_cast: %{^data => as_atom}} ->
172
+ {:ok, as_atom}
173
+
174
+ params ->
175
+ {:error, validation: :inclusion, enum: Map.keys(params.on_cast)}
182
176
end
183
177
end
184
178
 
@@ -213,35 +207,128 @@ defmodule Ecto.Enum do
213
207
"#Ecto.Enum<values: #{inspect(Keyword.keys(mappings))}>"
214
208
end
215
209
216
- @doc "Returns the possible values for a given schema or types map and field"
217
- @spec values(map | module, atom) :: [atom()]
210
+ @doc """
211
+ Returns the possible values for a given schema or types map and field.
212
+
213
+ These values are the atoms that represent the different possible values
214
+ of the field.
215
+
216
+ ## Examples
217
+
218
+ Assuming this schema:
219
+
220
+ defmodule MySchema do
221
+ use Ecto.Schema
222
+
223
+ schema "my_schema" do
224
+ field :my_string_enum, Ecto.Enum, values: [:foo, :bar, :baz]
225
+ field :my_integer_enum, Ecto.Enum, values: [foo: 1, bar: 2, baz: 5]
226
+ end
227
+ end
228
+
229
+ Then:
230
+
231
+ Ecto.Enum.values(MySchema, :my_string_enum)
232
+ #=> [:foo, :bar, :baz]
233
+
234
+ Ecto.Enum.values(MySchema, :my_integer_enum)
235
+ #=> [:foo, :bar, :baz]
236
+
237
+ """
238
+ @spec values(map | Ecto.Schema.t(), atom) :: [atom()]
218
239
def values(schema_or_types, field) do
219
240
schema_or_types
220
241
|> mappings(field)
221
242
|> Keyword.keys()
222
243
end
223
244
224
- @doc "Returns the possible dump values for a given schema or types map and field"
225
- @spec dump_values(map | module, atom) :: [String.t()] | [integer()]
245
+ @doc """
246
+ Returns the possible dump values for a given schema or types map and field
247
+
248
+ "Dump values" are the values that can be dumped in the database. For enums stored
249
+ as strings, these are the strings that will be dumped in the database. For enums
250
+ stored as integers, these are the integers that will be dumped in the database.
251
+
252
+ ## Examples
253
+
254
+ Assuming this schema:
255
+
256
+ defmodule MySchema do
257
+ use Ecto.Schema
258
+
259
+ schema "my_schema" do
260
+ field :my_string_enum, Ecto.Enum, values: [:foo, :bar, :baz]
261
+ field :my_integer_enum, Ecto.Enum, values: [foo: 1, bar: 2, baz: 5]
262
+ end
263
+ end
264
+
265
+ Then:
266
+
267
+ Ecto.Enum.dump_values(MySchema, :my_string_enum)
268
+ #=> ["foo", "bar", "baz"]
269
+
270
+ Ecto.Enum.dump_values(MySchema, :my_integer_enum)
271
+ #=> [1, 2, 5]
272
+
273
+ `schema_or_types` can also be a types map. See `mappings/2` for more information.
274
+ """
275
+ @spec dump_values(map | Ecto.Schema.t(), atom) :: [String.t()] | [integer()]
226
276
def dump_values(schema_or_types, field) do
227
277
schema_or_types
228
278
|> mappings(field)
229
279
|> Keyword.values()
230
280
end
231
281
232
- @doc "Returns the mappings between values and dumped values"
233
- @spec mappings(map, atom) :: Keyword.t()
282
+ @doc """
283
+ Returns the mappings between values and dumped values.
284
+
285
+ ## Examples
286
+
287
+ Assuming this schema:
288
+
289
+ defmodule MySchema do
290
+ use Ecto.Schema
291
+
292
+ schema "my_schema" do
293
+ field :my_string_enum, Ecto.Enum, values: [:foo, :bar, :baz]
294
+ field :my_integer_enum, Ecto.Enum, values: [foo: 1, bar: 2, baz: 5]
295
+ end
296
+ end
297
+
298
+ Here are some examples of using `mappings/2` with it:
299
+
300
+ Ecto.Enum.mappings(MySchema, :my_string_enum)
301
+ #=> [foo: "foo", bar: "bar", baz: "baz"]
302
+
303
+ Ecto.Enum.mappings(MySchema, :my_integer_enum)
304
+ #=> [foo: 1, bar: 2, baz: 5]
305
+
306
+ Examples of calling `mappings/2` with a types map:
307
+
308
+ schemaless_types = %{
309
+ my_enum: Ecto.ParameterizedType.init(Ecto.Enum, values: [:foo, :bar, :baz]),
310
+ my_integer_enum: Ecto.ParameterizedType.init(Ecto.Enum, values: [foo: 1, bar: 2, baz: 5])
311
+ }
312
+
313
+ Ecto.Enum.mappings(schemaless_types, :my_enum)
314
+ #=> [foo: "foo", bar: "bar", baz: "baz"]
315
+ Ecto.Enum.mappings(schemaless_types, :my_integer_enum)
316
+ #=> [foo: 1, bar: 2, baz: 5]
317
+
318
+ """
319
+ @spec mappings(map | Ecto.Schema.t(), atom) :: keyword(String.t() | integer())
320
+ def mappings(schema_or_types, field)
321
+
234
322
def mappings(types, field) when is_map(types) do
235
323
case types do
236
- %{^field => {:parameterized, Ecto.Enum, %{mappings: mappings}}} -> mappings
237
- %{^field => {_, {:parameterized, Ecto.Enum, %{mappings: mappings}}}} -> mappings
324
+ %{^field => {:parameterized, {Ecto.Enum, %{mappings: mappings}}}} -> mappings
325
+ %{^field => {_, {:parameterized, {Ecto.Enum, %{mappings: mappings}}}}} -> mappings
238
326
%{^field => _} -> raise ArgumentError, "#{field} is not an Ecto.Enum field"
239
327
%{} -> raise ArgumentError, "#{field} does not exist"
240
328
end
241
329
end
242
330
243
- @spec mappings(module, atom) :: Keyword.t()
244
- def mappings(schema, field) do
331
+ def mappings(schema, field) when is_atom(schema) do
245
332
try do
246
333
schema.__changeset__()
247
334
rescue
changed lib/ecto/exceptions.ex
 
@@ -159,7 +159,7 @@ defmodule Ecto.InvalidURLError do
159
159
def exception(opts) do
160
160
url = Keyword.fetch!(opts, :url)
161
161
msg = Keyword.fetch!(opts, :message)
162
- msg = "invalid url #{url}, #{msg}. The parsed URL is: #{inspect(URI.parse(url))}"
162
+ msg = "invalid URL #{url}, #{msg}. The parsed URL is: #{inspect(URI.parse(url))}"
163
163
%__MODULE__{message: msg, url: url}
164
164
end
165
165
end
 
@@ -268,6 +268,12 @@ defmodule Ecto.StaleEntryError do
268
268
attempted to #{action} a stale struct:
269
269
270
270
#{inspect(changeset.data)}
271
+
272
+ This typically happens when the struct no longer exists in the database \
273
+ or a database trigger/rule has forbidden the action. If stale entries are \
274
+ expected, you may use `:stale_error_field` to convert this into a changeset \
275
+ error, or set `:allow_stale` to true if you would like stale operations to \
276
+ be considered a success (such as a stale deletion)
271
277
"""
272
278
273
279
%__MODULE__{message: msg, changeset: changeset}
changed lib/ecto/json.ex
 
@@ -29,7 +29,7 @@ if Code.ensure_loaded?(Jason.Encoder) do
29
29
exposed externally.
30
30
31
31
You can either map the schemas to remove the :__meta__ field before \
32
- encoding to JSON, or explicit list the JSON fields in your schema:
32
+ encoding to JSON, or explicitly list the JSON fields in your schema:
33
33
34
34
defmodule #{inspect(schema)} do
35
35
# ...
changed lib/ecto/multi.ex
 
@@ -278,7 +278,9 @@ defmodule Ecto.Multi do
278
278
@doc """
279
279
Adds an insert operation to the multi.
280
280
281
- Accepts the same arguments and options as `c:Ecto.Repo.insert/2` does.
281
+ The `name` must be unique from other statements in the multi.
282
+
283
+ The remaining arguments and options are the same as in `c:Ecto.Repo.insert/2`.
282
284
283
285
## Example
284
286
 
@@ -317,7 +319,9 @@ defmodule Ecto.Multi do
317
319
@doc """
318
320
Adds an update operation to the multi.
319
321
320
- Accepts the same arguments and options as `c:Ecto.Repo.update/2` does.
322
+ The `name` must be unique from other statements in the multi.
323
+
324
+ The remaining arguments and options are the same as in `c:Ecto.Repo.update/2`.
321
325
322
326
## Example
323
327
 
@@ -349,7 +353,9 @@ defmodule Ecto.Multi do
349
353
@doc """
350
354
Inserts or updates a changeset depending on whether the changeset was persisted or not.
351
355
352
- Accepts the same arguments and options as `c:Ecto.Repo.insert_or_update/2` does.
356
+ The `name` must be unique from other statements in the multi.
357
+
358
+ The remaining arguments and options are the same as in `c:Ecto.Repo.insert_or_update/2`.
353
359
354
360
## Example
355
361
 
@@ -391,7 +397,9 @@ defmodule Ecto.Multi do
391
397
@doc """
392
398
Adds a delete operation to the multi.
393
399
394
- Accepts the same arguments and options as `c:Ecto.Repo.delete/2` does.
400
+ The `name` must be unique from other statements in the multi.
401
+
402
+ The remaining arguments and options are the same as in `c:Ecto.Repo.delete/2`.
395
403
396
404
## Example
397
405
 
@@ -437,7 +445,9 @@ defmodule Ecto.Multi do
437
445
@doc """
438
446
Runs a query expecting one result and stores it in the multi.
439
447
440
- Accepts the same arguments and options as `c:Ecto.Repo.one/2`.
448
+ The `name` must be unique from other statements in the multi.
449
+
450
+ The remaining arguments and options are the same as in `c:Ecto.Repo.one/2`.
441
451
442
452
## Example
443
453
 
@@ -467,7 +477,9 @@ defmodule Ecto.Multi do
467
477
@doc """
468
478
Runs a query and stores all entries in the multi.
469
479
470
- Accepts the same arguments and options as `c:Ecto.Repo.all/2` does.
480
+ The `name` must be unique from other statements in the multi.
481
+
482
+ The remaining arguments and options are the same as in `c:Ecto.Repo.all/2` does.
471
483
472
484
## Example
473
485
 
@@ -498,7 +510,9 @@ defmodule Ecto.Multi do
498
510
@doc """
499
511
Checks if there exists an entry matching the given query and stores a boolean in the multi.
500
512
501
- Accepts the same arguments and options as `c:Ecto.Repo.exists?/2`.
513
+ The `name` must be unique from other statements in the multi.
514
+
515
+ The remaining arguments and options are the same as in `c:Ecto.Repo.exists?/2`.
502
516
503
517
## Example
changed lib/ecto/parameterized_type.ex
 
@@ -88,7 +88,7 @@ defmodule Ecto.ParameterizedType do
88
88
@type params :: term()
89
89
90
90
@doc """
91
- Callback to convert the options specified in the field macro into parameters
91
+ Callback to convert the options specified in the field macro into parameters
92
92
to be used in other callbacks.
93
93
94
94
This function is called at compile time, and should raise if invalid values are
 
@@ -193,7 +193,7 @@ defmodule Ecto.ParameterizedType do
193
193
Useful when manually initializing a type for schemaless changesets.
194
194
"""
195
195
def init(type, opts) do
196
- {:parameterized, type, type.init(opts)}
196
+ {:parameterized, {type, type.init(opts)}}
197
197
end
198
198
199
199
@doc false
changed lib/ecto/query.ex
 
@@ -353,7 +353,7 @@ defmodule Ecto.Query do
353
353
or `join` is used. The query prefix is used only if none of the above
354
354
are declared.
355
355
356
- Let's see some examples. To see the query prefix globally, the simplest
356
+ Let's see some examples. To set the query prefix globally, the simplest
357
357
mechanism is to pass an option to the repository operation:
358
358
359
359
results = Repo.all(query, prefix: "accounts")
 
@@ -431,6 +431,11 @@ defmodule Ecto.Query do
431
431
defstruct [:expr, :file, :line, params: []]
432
432
end
433
433
434
+ defmodule ByExpr do
435
+ @moduledoc false
436
+ defstruct [:expr, :file, :line, params: [], subqueries: []]
437
+ end
438
+
434
439
defmodule BooleanExpr do
435
440
@moduledoc false
436
441
defstruct [:op, :expr, :file, :line, params: [], subqueries: []]
 
@@ -865,7 +870,8 @@ defmodule Ecto.Query do
865
870
)
866
871
867
872
If you need to refer to a parent binding which is not known when writing the subquery,
868
- you can use `parent_as` as shown in the examples under "Named bindings" in this module doc.
873
+ you can use `parent_as` as shown in the examples under ["Named bindings"](#module-named-bindings)
874
+ in this module doc.
869
875
870
876
You can also use subquery directly in `select` and `select_merge`:
871
877
 
@@ -897,9 +903,7 @@ defmodule Ecto.Query do
897
903
:right_join,
898
904
:full_join,
899
905
:inner_lateral_join,
900
- :left_lateral_join,
901
- :array_join,
902
- :left_array_join
906
+ :left_lateral_join
903
907
]
904
908
905
909
@doc """
 
@@ -948,8 +952,6 @@ defmodule Ecto.Query do
948
952
Ecto.Query.exclude(query, :full_join)
949
953
Ecto.Query.exclude(query, :inner_lateral_join)
950
954
Ecto.Query.exclude(query, :left_lateral_join)
951
- Ecto.Query.exclude(query, :array_join)
952
- Ecto.Query.exclude(query, :left_array_join)
953
955
954
956
However, keep in mind that if a join is removed and its bindings
955
957
were referenced elsewhere, the bindings won't be removed, leading
 
@@ -1196,8 +1198,6 @@ defmodule Ecto.Query do
1196
1198
defp join_qual(:cross_lateral_join), do: :cross_lateral
1197
1199
defp join_qual(:left_lateral_join), do: :left_lateral
1198
1200
defp join_qual(:inner_lateral_join), do: :inner_lateral
1199
- defp join_qual(:array_join), do: :array
1200
- defp join_qual(:left_array_join), do: :left_array
1201
1201
1202
1202
defp collect_with_ties([{:with_ties, with_ties} | t], nil),
1203
1203
do: collect_with_ties(t, with_ties)
 
@@ -1249,12 +1249,11 @@ defmodule Ecto.Query do
1249
1249
Receives a source that is to be joined to the query and a condition for
1250
1250
the join. The join condition can be any expression that evaluates
1251
1251
to a boolean value. The qualifier must be one of `:inner`, `:left`,
1252
- `:right`, `:cross`, `:cross_lateral`, `:full`, `:inner_lateral`, `:left_lateral`,
1253
- `:array` or `:left_array`.
1252
+ `:right`, `:cross`, `:cross_lateral`, `:full`, `:inner_lateral` or `:left_lateral`.
1254
1253
1255
1254
For a keyword query the `:join` keyword can be changed to `:inner_join`,
1256
- `:left_join`, `:right_join`, `:cross_join`, `:cross_lateral_join`, `:full_join`, `:inner_lateral_join`,
1257
- `:left_lateral_join`, `:array_join` or `:left_array_join`. `:join` is equivalent to `:inner_join`.
1255
+ `:left_join`, `:right_join`, `:cross_join`, `:cross_lateral_join`, `:full_join`, `:inner_lateral_join`
1256
+ or `:left_lateral_join`. `:join` is equivalent to `:inner_join`.
1258
1257
1259
1258
Currently it is possible to join on:
1260
1259
 
@@ -1403,18 +1402,6 @@ defmodule Ecto.Query do
1403
1402
disclaimers about such functionality.
1404
1403
1405
1404
Join hints must be static compile-time strings when they are specified as (list of) strings.
1406
-
1407
- ## Array joins
1408
-
1409
- The `:array` and `:left_array` qualifiers can be used to join with array
1410
- columns in [Clickhouse:](https://clickhouse.com/docs/en/sql-reference/statements/select/array-join)
1411
-
1412
- from at in "arrays_test",
1413
- array_join: a in "arr",
1414
- select: %{s: at.s, arr: a}
1415
-
1416
- Note that only the columns in the base table (i.e. the table referenced in `FROM`) can be used in the array join.
1417
-
1418
1405
"""
1419
1406
@join_opts [:on | @from_join_opts]
1420
1407
 
@@ -1943,7 +1930,22 @@ defmodule Ecto.Query do
1943
1930
end
1944
1931
1945
1932
@doc """
1946
- Same as `order_by/3` except new expressions will be prepended to existing ones.
1933
+ An order by query expression that is prepended to existing ones.
1934
+
1935
+ Accepts the same input as `order_by/3` except the expression will
1936
+ come before any previously defined order by expression. This only
1937
+ works with the macro-based query syntax and not the keyword-based
1938
+ query syntax.
1939
+
1940
+ For example, the following will generate a query that orders by `human_popluation`
1941
+ and then `name`:
1942
+
1943
+ City |> order_by([c], c.name) |> prepend_order_by([c], c.human_population)
1944
+
1945
+ The corresponding keyword-based syntax will raise an error:
1946
+
1947
+ from c in City, order_by: c.name, prepend_order_by: c.human_population
1948
+
1947
1949
"""
1948
1950
defmacro prepend_order_by(query, binding \\ [], expr) do
1949
1951
Builder.OrderBy.build(query, binding, expr, :prepend, __CALLER__)
 
@@ -1955,25 +1957,46 @@ defmodule Ecto.Query do
1955
1957
Combines result sets of multiple queries. The `select` of each query
1956
1958
must be exactly the same, with the same types in the same order.
1957
1959
1958
- > ### Selecting literal atoms {: .warning}
1959
- >
1960
- > When selecting a literal atom, its value must be the same across
1961
- > all queries. Otherwise, the value from the parent query will be
1962
- > applied to all other queries. This also holds true for selecting
1963
- > maps with atom keys.
1964
-
1965
1960
Union expression returns only unique rows as if each query returned
1966
1961
distinct results. This may cause a performance penalty. If you need
1967
1962
to combine multiple result sets without removing duplicate rows
1968
1963
consider using `union_all/2`.
1969
1964
1970
- Note that the operations `order_by`, `limit` and `offset` of the
1971
- current `query` apply to the result of the union. `order_by` must
1972
- be specified in one of the following ways, since the union of two
1973
- or more queries is not automatically aliased:
1965
+ ## Combination behaviour
1974
1966
1975
- - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement that directly access the union fields.
1976
- - Wrap the union in a subquery and refer to the binding of the subquery.
1967
+ There are several behaviours of combination queries that must be taken
1968
+ into account, otherwise you may unexpectedly return the wrong query result.
1969
+
1970
+ ### Order by, limit and offset
1971
+
1972
+ The `order_by`, `limit` and `offset` expressions of the parent query apply
1973
+ to the result of the entire combination. `order_by` must be specified in one
1974
+ of the following ways, since the combination of two or more queries is not
1975
+ automatically aliased:
1976
+
1977
+ - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement
1978
+ that directly access the combination fields.
1979
+ - Wrap the combination in a subquery and refer to the binding of the subquery.
1980
+
1981
+ ### Column selection ordering
1982
+
1983
+ The columns of each of the queries in the combination must be specified in
1984
+ the exact same order. Otherwise, you may see the values of one column appearing
1985
+ in another. This holds for all types of select expressions, including maps.
1986
+
1987
+ For example, the following query will interchange the values of the supplier's
1988
+ name and city because that is the order the fields are specified in the customer
1989
+ query.
1990
+
1991
+ supplier_query = from s in Supplier, select: %{city: s.city, name: s.name}
1992
+ customer_query = from c in Customer, select: %{name: c.name, city: c.city}
1993
+ union(supplier_query, ^customer_query)
1994
+
1995
+ ### Selecting literal atoms
1996
+
1997
+ When selecting a literal atom, its value must be the same across all queries.
1998
+ Otherwise, the value from the parent query will be applied to all other queries.
1999
+ This also holds true for selecting maps with atom keys.
1977
2000
1978
2001
## Keywords examples
1979
2002
 
@@ -2008,20 +2031,41 @@ defmodule Ecto.Query do
2008
2031
Combines result sets of multiple queries. The `select` of each query
2009
2032
must be exactly the same, with the same types in the same order.
2010
2033
2011
- > ### Selecting literal atoms {: .warning}
2012
- >
2013
- > When selecting a literal atom, its value must be the same across
2014
- > all queries. Otherwise, the value from the parent query will be
2015
- > applied to all other queries. This also holds true for selecting
2016
- > maps with atom keys.
2034
+ ## Combination behaviour
2017
2035
2018
- Note that the operations `order_by`, `limit` and `offset` of the
2019
- current `query` apply to the result of the union. `order_by` must
2020
- be specified in one of the following ways, since the union of two
2021
- or more queries is not automatically aliased:
2036
+ There are several behaviours of combination queries that must be taken
2037
+ into account, otherwise you may unexpectedly return the wrong query result.
2022
2038
2023
- - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement that directly access the union fields.
2024
- - Wrap the union in a subquery and refer to the binding of the subquery.
2039
+ ### Order by, limit and offset
2040
+
2041
+ The `order_by`, `limit` and `offset` expressions of the parent query apply
2042
+ to the result of the entire combination. `order_by` must be specified in one
2043
+ of the following ways, since the combination of two or more queries is not
2044
+ automatically aliased:
2045
+
2046
+ - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement
2047
+ that directly access the combination fields.
2048
+ - Wrap the combination in a subquery and refer to the binding of the subquery.
2049
+
2050
+ ### Column selection ordering
2051
+
2052
+ The columns of each of the queries in the combination must be specified in
2053
+ the exact same order. Otherwise, you may see the values of one column appearing
2054
+ in another. This holds for all types of select expressions, including maps.
2055
+
2056
+ For example, the following query will interchange the values of the supplier's
2057
+ name and city because that is the order the fields are specified in the customer
2058
+ query.
2059
+
2060
+ supplier_query = from s in Supplier, select: %{city: s.city, name: s.name}
2061
+ customer_query = from c in Customer, select: %{name: c.name, city: c.city}
2062
+ union_all(supplier_query, ^customer_query)
2063
+
2064
+ ### Selecting literal atoms
2065
+
2066
+ When selecting a literal atom, its value must be the same across all queries.
2067
+ Otherwise, the value from the parent query will be applied to all other queries.
2068
+ This also holds true for selecting maps with atom keys.
2025
2069
2026
2070
## Keywords examples
2027
2071
 
@@ -2056,25 +2100,46 @@ defmodule Ecto.Query do
2056
2100
`select` of each query must be exactly the same, with the same
2057
2101
types in the same order.
2058
2102
2059
- > ### Selecting literal atoms {: .warning}
2060
- >
2061
- > When selecting a literal atom, its value must be the same across
2062
- > all queries. Otherwise, the value from the parent query will be
2063
- > applied to all other queries. This also holds true for selecting
2064
- > maps with atom keys.
2065
-
2066
2103
Except expression returns only unique rows as if each query returned
2067
2104
distinct results. This may cause a performance penalty. If you need
2068
2105
to take the difference of multiple result sets without
2069
2106
removing duplicate rows consider using `except_all/2`.
2070
2107
2071
- Note that the operations `order_by`, `limit` and `offset` of the
2072
- current `query` apply to the result of the set difference. `order_by`
2073
- must be specified in one of the following ways, since the set difference
2074
- of two or more queries is not automatically aliased:
2108
+ ## Combination behaviour
2075
2109
2076
- - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement that directly access the set difference fields.
2077
- - Wrap the set difference in a subquery and refer to the binding of the subquery.
2110
+ There are several behaviours of combination queries that must be taken
2111
+ into account, otherwise you may unexpectedly return the wrong query result.
2112
+
2113
+ ### Order by, limit and offset
2114
+
2115
+ The `order_by`, `limit` and `offset` expressions of the parent query apply
2116
+ to the result of the entire combination. `order_by` must be specified in one
2117
+ of the following ways, since the combination of two or more queries is not
2118
+ automatically aliased:
2119
+
2120
+ - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement
2121
+ that directly access the combination fields.
2122
+ - Wrap the combination in a subquery and refer to the binding of the subquery.
2123
+
2124
+ ### Column selection ordering
2125
+
2126
+ The columns of each of the queries in the combination must be specified in
2127
+ the exact same order. Otherwise, you may see the values of one column appearing
2128
+ in another. This holds for all types of select expressions, including maps.
2129
+
2130
+ For example, the following query will interchange the values of the supplier's
2131
+ name and city because that is the order the fields are specified in the customer
2132
+ query.
2133
+
2134
+ supplier_query = from s in Supplier, select: %{city: s.city, name: s.name}
2135
+ customer_query = from c in Customer, select: %{name: c.name, city: c.city}
2136
+ except(supplier_query, ^customer_query)
2137
+
2138
+ ### Selecting literal atoms
2139
+
2140
+ When selecting a literal atom, its value must be the same across all queries.
2141
+ Otherwise, the value from the parent query will be applied to all other queries.
2142
+ This also holds true for selecting maps with atom keys.
2078
2143
2079
2144
## Keywords examples
2080
2145
 
@@ -2109,20 +2174,41 @@ defmodule Ecto.Query do
2109
2174
`select` of each query must be exactly the same, with the same
2110
2175
types in the same order.
2111
2176
2112
- > ### Selecting literal atoms {: .warning}
2113
- >
2114
- > When selecting a literal atom, its value must be the same across
2115
- > all queries. Otherwise, the value from the parent query will be
2116
- > applied to all other queries. This also holds true for selecting
2117
- > maps with atom keys.
2177
+ ## Combination behaviour
2118
2178
2119
- Note that the operations `order_by`, `limit` and `offset` of the
2120
- current `query` apply to the result of the set difference. `order_by`
2121
- must be specified in one of the following ways, since the set difference
2122
- of two or more queries is not automatically aliased:
2179
+ There are several behaviours of combination queries that must be taken
2180
+ into account, otherwise you may unexpectedly return the wrong query result.
2123
2181
2124
- - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement that directly access the set difference fields.
2125
- - Wrap the set difference in a subquery and refer to the binding of the subquery.
2182
+ ### Order by, limit and offset
2183
+
2184
+ The `order_by`, `limit` and `offset` expressions of the parent query apply
2185
+ to the result of the entire combination. `order_by` must be specified in one
2186
+ of the following ways, since the combination of two or more queries is not
2187
+ automatically aliased:
2188
+
2189
+ - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement
2190
+ that directly access the combination fields.
2191
+ - Wrap the combination in a subquery and refer to the binding of the subquery.
2192
+
2193
+ ### Column selection ordering
2194
+
2195
+ The columns of each of the queries in the combination must be specified in
2196
+ the exact same order. Otherwise, you may see the values of one column appearing
2197
+ in another. This holds for all types of select expressions, including maps.
2198
+
2199
+ For example, the following query will interchange the values of the supplier's
2200
+ name and city because that is the order the fields are specified in the customer
2201
+ query.
2202
+
2203
+ supplier_query = from s in Supplier, select: %{city: s.city, name: s.name}
2204
+ customer_query = from c in Customer, select: %{name: c.name, city: c.city}
2205
+ except_all(supplier_query, ^customer_query)
2206
+
2207
+ ### Selecting literal atoms
2208
+
2209
+ When selecting a literal atom, its value must be the same across all queries.
2210
+ Otherwise, the value from the parent query will be applied to all other queries.
2211
+ This also holds true for selecting maps with atom keys.
2126
2212
2127
2213
## Keywords examples
2128
2214
 
@@ -2157,25 +2243,46 @@ defmodule Ecto.Query do
2157
2243
`select` of each query must be exactly the same, with the same
2158
2244
types in the same order.
2159
2245
2160
- > ### Selecting literal atoms {: .warning}
2161
- >
2162
- > When selecting a literal atom, its value must be the same across
2163
- > all queries. Otherwise, the value from the parent query will be
2164
- > applied to all other queries. This also holds true for selecting
2165
- > maps with atom keys.
2166
-
2167
2246
Intersect expression returns only unique rows as if each query returned
2168
2247
distinct results. This may cause a performance penalty. If you need
2169
2248
to take the intersection of multiple result sets without
2170
2249
removing duplicate rows consider using `intersect_all/2`.
2171
2250
2172
- Note that the operations `order_by`, `limit` and `offset` of the
2173
- current `query` apply to the result of the set difference. `order_by`
2174
- must be specified in one of the following ways, since the intersection
2175
- of two or more queries is not automatically aliased:
2251
+ ## Combination behaviour
2176
2252
2177
- - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement that directly access the intersection fields.
2178
- - Wrap the intersection in a subquery and refer to the binding of the subquery.
2253
+ There are several behaviours of combination queries that must be taken
2254
+ into account, otherwise you may unexpectedly return the wrong query result.
2255
+
2256
+ ### Order by, limit and offset
2257
+
2258
+ The `order_by`, `limit` and `offset` expressions of the parent query apply
2259
+ to the result of the entire combination. `order_by` must be specified in one
2260
+ of the following ways, since the combination of two or more queries is not
2261
+ automatically aliased:
2262
+
2263
+ - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement
2264
+ that directly access the combination fields.
2265
+ - Wrap the combination in a subquery and refer to the binding of the subquery.
2266
+
2267
+ ### Column selection ordering
2268
+
2269
+ The columns of each of the queries in the combination must be specified in
2270
+ the exact same order. Otherwise, you may see the values of one column appearing
2271
+ in another. This holds for all types of select expressions, including maps.
2272
+
2273
+ For example, the following query will interchange the values of the supplier's
2274
+ name and city because that is the order the fields are specified in the customer
2275
+ query.
2276
+
2277
+ supplier_query = from s in Supplier, select: %{city: s.city, name: s.name}
2278
+ customer_query = from c in Customer, select: %{name: c.name, city: c.city}
2279
+ intersect(supplier_query, ^customer_query)
2280
+
2281
+ ### Selecting literal atoms
2282
+
2283
+ When selecting a literal atom, its value must be the same across all queries.
2284
+ Otherwise, the value from the parent query will be applied to all other queries.
2285
+ This also holds true for selecting maps with atom keys.
2179
2286
2180
2287
## Keywords examples
2181
2288
 
@@ -2210,20 +2317,41 @@ defmodule Ecto.Query do
2210
2317
`select` of each query must be exactly the same, with the same
2211
2318
types in the same order.
2212
2319
2213
- > ### Selecting literal atoms {: .warning}
2214
- >
2215
- > When selecting a literal atom, its value must be the same across
2216
- > all queries. Otherwise, the value from the parent query will be
2217
- > applied to all other queries. This also holds true for selecting
2218
- > maps with atom keys.
2320
+ ## Combination behaviour
2219
2321
2220
- Note that the operations `order_by`, `limit` and `offset` of the
2221
- current `query` apply to the result of the set difference. `order_by`
2222
- must be specified in one of the following ways, since the intersection
2223
- of two or more queries is not automatically aliased:
2322
+ There are several behaviours of combination queries that must be taken
2323
+ into account, otherwise you may unexpectedly return the wrong query result.
2224
2324
2225
- - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement that directly access the intersection fields.
2226
- - Wrap the intersection in a subquery and refer to the binding of the subquery.
2325
+ ### Order by, limit and offset
2326
+
2327
+ The `order_by`, `limit` and `offset` expressions of the parent query apply
2328
+ to the result of the entire combination. `order_by` must be specified in one
2329
+ of the following ways, since the combination of two or more queries is not
2330
+ automatically aliased:
2331
+
2332
+ - Use `Ecto.Query.API.fragment/1` to pass an `order_by` statement
2333
+ that directly access the combination fields.
2334
+ - Wrap the combination in a subquery and refer to the binding of the subquery.
2335
+
2336
+ ### Column selection ordering
2337
+
2338
+ The columns of each of the queries in the combination must be specified in
2339
+ the exact same order. Otherwise, you may see the values of one column appearing
2340
+ in another. This holds for all types of select expressions, including maps.
2341
+
2342
+ For example, the following query will interchange the values of the supplier's
2343
+ name and city because that is the order the fields are specified in the customer
2344
+ query.
2345
+
2346
+ supplier_query = from s in Supplier, select: %{city: s.city, name: s.name}
2347
+ customer_query = from c in Customer, select: %{name: c.name, city: c.city}
2348
+ intersect_all(supplier_query, ^customer_query)
2349
+
2350
+ ### Selecting literal atoms
2351
+
2352
+ When selecting a literal atom, its value must be the same across all queries.
2353
+ Otherwise, the value from the parent query will be applied to all other queries.
2354
+ This also holds true for selecting maps with atom keys.
2227
2355
2228
2356
## Keywords examples
2229
2357
 
@@ -2393,6 +2521,19 @@ defmodule Ecto.Query do
2393
2521
2394
2522
from(u in User, update: [pull: [tags: "not cool"]])
2395
2523
2524
+ ## Composable
2525
+
2526
+ Remember that all query expressions are composable, so you can use `update`
2527
+ multiple times in the same query to merge the update expressions:
2528
+
2529
+ new_name = "new name"
2530
+ User
2531
+ |> update([u], set: [name: fragment("upper(?)", ^new_name)])
2532
+ |> update([u], set: [age: 42])
2533
+
2534
+ This can be useful to compose updates from different functions
2535
+ or when mixing interpolation, such as `set: ^updates`, with regular
2536
+ query expressions, such as `set: [age: u.age + 1]`.
2396
2537
"""
2397
2538
defmacro update(query, binding \\ [], expr) do
2398
2539
Builder.Update.build(query, binding, expr, __CALLER__)
 
@@ -2517,39 +2658,13 @@ defmodule Ecto.Query do
2517
2658
Nested associations can also be preloaded in both formats:
2518
2659
2519
2660
Repo.all from p in Post,
2520
- preload: [comments: :likes]
2661
+ preload: [:author, comments: :likes]
2521
2662
2522
2663
Repo.all from p in Post,
2523
2664
join: c in assoc(p, :comments),
2524
2665
join: l in assoc(c, :likes),
2525
2666
where: l.inserted_at > c.updated_at,
2526
- preload: [comments: {c, likes: l}]
2527
-
2528
- Applying a limit to the association can be achieved with `inner_lateral_join`:
2529
-
2530
- Repo.all from p in Post, as: :post,
2531
- join: c in assoc(p, :comments),
2532
- inner_lateral_join: top_five in subquery(
2533
- from Comment,
2534
- where: [post_id: parent_as(:post).id],
2535
- order_by: :popularity,
2536
- limit: 5,
2537
- select: [:id]
2538
- ), on: top_five.id == c.id,
2539
- preload: [comments: c]
2540
-
2541
- Preloaded joins can also be specified dynamically using `dynamic`:
2542
-
2543
- preloads = [comments: dynamic([comments: c], c)]
2544
-
2545
- Repo.all from p in Post,
2546
- join: c in assoc(p, :comments),
2547
- as: :comments,
2548
- where: c.published_at > p.updated_at,
2549
- preload: ^preloads
2550
-
2551
- See "`preload`" in the documentation for `dynamic/2` for more
2552
- details.
2667
+ preload: [:author, comments: {c, likes: l}]
2553
2668
2554
2669
## Preload queries
2555
2670
 
@@ -2563,9 +2678,9 @@ defmodule Ecto.Query do
2563
2678
then another for loading the comments associated with the posts.
2564
2679
Comments will be ordered by `published_at`.
2565
2680
2566
- When specifying a preload query, you can still preload the associations of
2567
- those records. For instance, you could preload an author's published posts and
2568
- the comments on those posts:
2681
+ When specifying a preload query, you can still nest preloads.
2682
+ For instance, you could preload an author's published posts and
2683
+ their comments as follows:
2569
2684
2570
2685
posts_query = from p in Post, where: p.state == :published
2571
2686
Repo.all from a in Author, preload: [posts: ^{posts_query, [:comments]}]
 
@@ -2576,11 +2691,6 @@ defmodule Ecto.Query do
2576
2691
posts_query =
2577
2692
from p in Post, where: p.state == :published, preload: :related_posts
2578
2693
2579
- The same can be written as pipe based query:
2580
-
2581
- posts_query =
2582
- Post |> where([p], p.state == :published) |> preload(:related_posts)
2583
-
2584
2694
Note: keep in mind operations like limit and offset in the preload
2585
2695
query will affect the whole result set and not each association. For
2586
2696
example, the query below:
 
@@ -2589,7 +2699,7 @@ defmodule Ecto.Query do
2589
2699
Repo.all from p in Post, preload: [comments: ^comments_query]
2590
2700
2591
2701
won't bring the top of comments per post. Rather, it will only bring
2592
- the 5 top comments across all posts. Instead, use a window:
2702
+ the 5 top comments across all posts. Instead, you must use a window:
2593
2703
2594
2704
ranking_query =
2595
2705
from c in Comment,
 
@@ -2603,19 +2713,22 @@ defmodule Ecto.Query do
2603
2713
2604
2714
Repo.all from p in Post, preload: [comments: ^comments_query]
2605
2715
2606
- Similarly, if you have a `:through` association, such as posts has many
2607
- `comments_authors` through comments (`posts->comments->comments_authors`),
2608
- the query will only customize the relationship between comments and
2609
- comments_authors, even if preloaded through posts. This means `order_by`
2610
- clauses on `:through` associations affect only the direct relationship
2611
- between `comments` and `comments_authors`, not between `posts` and
2612
- `comments_authors`.
2716
+ For `:through` associations, such as a post may have many comments_authors,
2717
+ written as `has_many :comments_authors, through: [:comments, :author]`
2718
+ the query given to preload customizes the relationship between comments and
2719
+ authors, even if preloaded through posts. Another way to put it, in case of
2720
+ `:through` associations, the query given to preload customizes the last join
2721
+ of the association chain. This means `order_by` clauses on `:through`
2722
+ associations affect only the direct relationship between `comments` and
2723
+ `authors`, not between posts and comments.
2613
2724
2614
2725
## Preload functions
2615
2726
2616
- Preload also allows functions to be given. In such cases, the function
2617
- receives the IDs of the parent association and it must return the associated
2618
- data. Ecto then will map this data and sort it by the relationship key:
2727
+ Preload also allows functions to be given. If the function has an arity of 1,
2728
+ it receives only the IDs of the parent association. If it has an arity of 2, it
2729
+ receives the IDS of the parent association as the first argument and the association
2730
+ metadata as the second argument. Both functions must return the associated data.
2731
+ Ecto then will map this data and sort it by the relationship key:
2619
2732
2620
2733
comment_preloader = fn post_ids -> fetch_comments_by_post_ids(post_ids) end
2621
2734
Repo.all from p in Post, preload: [comments: ^comment_preloader]
 
@@ -2648,7 +2761,46 @@ defmodule Ecto.Query do
2648
2761
function, the function will receive a list of "post_ids" as the argument
2649
2762
and it must return a tuple in the format of `{post_id, tag}`
2650
2763
2651
- If you want to reset the loaded fields, see `Ecto.reset_fields/2`.
2764
+ The 2-arity version of the function is especially useful if you would like to
2765
+ build a general preloader that works across all associations. For example, if
2766
+ you would like to build a preloader for lateral joins that finds the newest
2767
+ associations you may do the following:
2768
+
2769
+ lateral_preloader = fn ids, assoc -> newest_records(ids, assoc, 5) end
2770
+
2771
+ def newest_records(parent_ids, assoc, n) do
2772
+ %{related_key: related_key, queryable: queryable} = assoc
2773
+
2774
+ squery =
2775
+ from q in queryable,
2776
+ where: field(q, ^related_key) == parent_as(:parent_ids).id,
2777
+ order_by: {:desc, :created_at},
2778
+ limit: ^n
2779
+
2780
+ query =
2781
+ from f in fragment("SELECT id from UNNEST(?::int[]) AS id", ^parent_ids), as: :parent_ids,
2782
+ inner_lateral_join: s in subquery(squery), on: true,
2783
+ select: s
2784
+
2785
+ Repo.all(query)
2786
+ end
2787
+
2788
+ For the list of available metadata, see the module documentation of the association types.
2789
+ For example, see `Ecto.Association.BelongsTo`.
2790
+
2791
+ ## Dynamic preloads
2792
+
2793
+ Preloads can also be specified dynamically using the `dynamic` macro:
2794
+
2795
+ preloads = [comments: dynamic([comments: c], c)]
2796
+
2797
+ Repo.all from p in Post,
2798
+ join: c in assoc(p, :comments),
2799
+ as: :comments,
2800
+ where: c.published_at > p.updated_at,
2801
+ preload: ^preloads
2802
+
2803
+ See `dynamic/2` for more information.
2652
2804
2653
2805
## Keywords example
2654
2806
 
@@ -2731,7 +2883,7 @@ defmodule Ecto.Query do
2731
2883
schema = assert_schema!(query)
2732
2884
pks = schema.__schema__(:primary_key)
2733
2885
expr = for pk <- pks, do: {dir, field(0, pk)}
2734
- %QueryExpr{expr: expr, file: __ENV__.file, line: __ENV__.line}
2886
+ %ByExpr{expr: expr, file: __ENV__.file, line: __ENV__.line}
2735
2887
end
2736
2888
2737
2889
defp assert_schema!(%{from: %Ecto.Query.FromExpr{source: {_source, schema}}})
 
@@ -2745,7 +2897,8 @@ defmodule Ecto.Query do
2745
2897
@doc """
2746
2898
Returns `true` if the query has a binding with the given name, otherwise `false`.
2747
2899
2748
- For more information on named bindings see "Named bindings" in this module doc.
2900
+ For more information on named bindings see ["Named bindings"](#module-named-bindings)
2901
+ in this module doc.
2749
2902
"""
2750
2903
def has_named_binding?(%Ecto.Query{aliases: aliases}, key) do
2751
2904
Map.has_key?(aliases, key)
 
@@ -2779,11 +2932,12 @@ defmodule Ecto.Query do
2779
2932
2780
2933
With this function it can be simplified to:
2781
2934
2782
- with_named_binding(query, :comments, fn query, binding ->
2935
+ with_named_binding(query, :comments, fn query, binding ->
2783
2936
join(query, :left, [p], a in assoc(p, ^binding), as: ^binding)
2784
2937
end)
2785
2938
2786
- For more information on named bindings see "Named bindings" in this module doc or `has_named_binding?/2`.
2939
+ For more information on named bindings see ["Named bindings"](#module-named-bindings)
2940
+ in this module doc or `has_named_binding?/2`.
2787
2941
"""
2788
2942
def with_named_binding(%Ecto.Query{} = query, key, fun) do
2789
2943
if has_named_binding?(query, key) do
 
@@ -2823,6 +2977,13 @@ defmodule Ecto.Query do
2823
2977
"callback function for with_named_binding/3 should return an Ecto.Query struct, got: #{inspect(other)}"
2824
2978
end
2825
2979
2980
+ @doc """
2981
+ The same as `has_named_binding?/2` but allowed in guards.
2982
+ """
2983
+ @doc guard: true
2984
+ defguard is_named_binding(query, name)
2985
+ when is_struct(query, Ecto.Query) and is_map_key(query.aliases, name)
2986
+
2826
2987
@doc """
2827
2988
Reverses the ordering of the query.
changed lib/ecto/query/api.ex
 
@@ -48,62 +48,62 @@ defmodule Ecto.Query.API do
48
48
@doc """
49
49
Binary `==` operation.
50
50
"""
51
- def left == right, do: doc! [left, right]
51
+ def left == right, do: doc!([left, right])
52
52
53
53
@doc """
54
54
Binary `!=` operation.
55
55
"""
56
- def left != right, do: doc! [left, right]
56
+ def left != right, do: doc!([left, right])
57
57
58
58
@doc """
59
59
Binary `<=` operation.
60
60
"""
61
- def left <= right, do: doc! [left, right]
61
+ def left <= right, do: doc!([left, right])
62
62
63
63
@doc """
64
64
Binary `>=` operation.
65
65
"""
66
- def left >= right, do: doc! [left, right]
66
+ def left >= right, do: doc!([left, right])
67
67
68
68
@doc """
69
69
Binary `<` operation.
70
70
"""
71
- def left < right, do: doc! [left, right]
71
+ def left < right, do: doc!([left, right])
72
72
73
73
@doc """
74
74
Binary `>` operation.
75
75
"""
76
- def left > right, do: doc! [left, right]
76
+ def left > right, do: doc!([left, right])
77
77
78
78
@doc """
79
79
Binary `+` operation.
80
80
"""
81
- def left + right, do: doc! [left, right]
81
+ def left + right, do: doc!([left, right])
82
82
83
83
@doc """
84
84
Binary `-` operation.
85
85
"""
86
- def left - right, do: doc! [left, right]
86
+ def left - right, do: doc!([left, right])
87
87
88
88
@doc """
89
89
Binary `*` operation.
90
90
"""
91
- def left * right, do: doc! [left, right]
91
+ def left * right, do: doc!([left, right])
92
92
93
93
@doc """
94
94
Binary `/` operation.
95
95
"""
96
- def left / right, do: doc! [left, right]
96
+ def left / right, do: doc!([left, right])
97
97
98
98
@doc """
99
99
Binary `and` operation.
100
100
"""
101
- def left and right, do: doc! [left, right]
101
+ def left and right, do: doc!([left, right])
102
102
103
103
@doc """
104
104
Binary `or` operation.
105
105
"""
106
- def left or right, do: doc! [left, right]
106
+ def left or right, do: doc!([left, right])
107
107
108
108
@doc """
109
109
Unary `not` operation.
 
@@ -128,15 +128,16 @@ defmodule Ecto.Query.API do
128
128
)
129
129
130
130
"""
131
- def not(value), do: doc! [value]
131
+ def not value, do: doc!([value])
132
132
133
133
@doc """
134
134
Checks if the left-value is included in the right one.
135
135
136
136
from p in Post, where: p.id in [1, 2, 3]
137
137
138
- The right side may either be a list, a literal list
139
- or even a column in the database with array type:
138
+ The right side may either be a literal list, an interpolated list,
139
+ any struct that implements the `Enumerable` protocol, or even a
140
+ column in the database with array type:
140
141
141
142
from p in Post, where: "elixir" in p.tags
142
143
 
@@ -147,7 +148,7 @@ defmodule Ecto.Query.API do
147
148
from(p in Post, where: p.created_at > ^since, select: p.id)
148
149
)
149
150
"""
150
- def left in right, do: doc! [left, right]
151
+ def left in right, do: doc!([left, right])
151
152
152
153
@doc """
153
154
Evaluates to true if the provided subquery returns 1 or more rows.
 
@@ -168,7 +169,7 @@ defmodule Ecto.Query.API do
168
169
In the above example the query returns posts which have at least one comment that
169
170
has more than 5 replies.
170
171
"""
171
- def exists(subquery), do: doc! [subquery]
172
+ def exists(subquery), do: doc!([subquery])
172
173
173
174
@doc """
174
175
Tests whether one or more values returned from the provided subquery match in a comparison operation.
 
@@ -183,7 +184,7 @@ defmodule Ecto.Query.API do
183
184
Both `any` and `all` must be given a subquery as an argument, and they must be used on the right hand side of a comparison.
184
185
Both can be used with every comparison operator: `==`, `!=`, `>`, `>=`, `<`, `<=`.
185
186
"""
186
- def any(subquery), do: doc! [subquery]
187
+ def any(subquery), do: doc!([subquery])
187
188
188
189
@doc """
189
190
Evaluates whether all values returned from the provided subquery match in a comparison operation.
 
@@ -203,7 +204,7 @@ defmodule Ecto.Query.API do
203
204
Both `any` and `all` must be given a subquery as an argument, and they must be used on the right hand side of a comparison.
204
205
Both can be used with every comparison operator: `==`, `!=`, `>`, `>=`, `<`, `<=`.
205
206
"""
206
- def all(subquery), do: doc! [subquery]
207
+ def all(subquery), do: doc!([subquery])
207
208
208
209
@doc """
209
210
Searches for `search` in `string`.
 
@@ -220,7 +221,7 @@ defmodule Ecto.Query.API do
220
221
as part of LIKE query, since they allow to perform
221
222
[LIKE-injections](https://githubengineering.com/like-injection/).
222
223
"""
223
- def like(string, search), do: doc! [string, search]
224
+ def like(string, search), do: doc!([string, search])
224
225
225
226
@doc """
226
227
Searches for `search` in `string` in a case insensitive fashion.
 
@@ -230,7 +231,7 @@ defmodule Ecto.Query.API do
230
231
Translates to the underlying SQL ILIKE query. This operation is
231
232
only available on PostgreSQL.
232
233
"""
233
- def ilike(string, search), do: doc! [string, search]
234
+ def ilike(string, search), do: doc!([string, search])
234
235
235
236
@doc """
236
237
Checks if the given value is nil.
 
@@ -241,31 +242,31 @@ defmodule Ecto.Query.API do
241
242
242
243
from p in Post, where: not is_nil(p.published_at)
243
244
"""
244
- def is_nil(value), do: doc! [value]
245
+ def is_nil(value), do: doc!([value])
245
246
246
247
@doc """
247
248
Counts the entries in the table.
248
249
249
250
from p in Post, select: count()
250
251
"""
251
- def count, do: doc! []
252
+ def count, do: doc!([])
252
253
253
254
@doc """
254
255
Counts the given entry.
255
256
256
257
from p in Post, select: count(p.id)
257
258
"""
258
- def count(value), do: doc! [value]
259
+ def count(value), do: doc!([value])
259
260
260
261
@doc """
261
262
Counts the distinct values in given entry.
262
263
263
264
from p in Post, select: count(p.id, :distinct)
264
265
"""
265
- def count(value, :distinct), do: doc! [value, :distinct]
266
+ def count(value, :distinct), do: doc!([value, :distinct])
266
267
267
268
@doc """
268
- Takes whichever value is not null, or null if they both are.
269
+ Takes the first value which is not null, or null if they both are.
269
270
270
271
In SQL, COALESCE takes any number of arguments, but in ecto
271
272
it only takes two, so it must be chained to achieve the same
 
@@ -273,7 +274,7 @@ defmodule Ecto.Query.API do
273
274
274
275
from p in Payment, select: p.value |> coalesce(p.backup_value) |> coalesce(0)
275
276
"""
276
- def coalesce(value, expr), do: doc! [value, expr]
277
+ def coalesce(value, expr), do: doc!([value, expr])
277
278
278
279
@doc """
279
280
Applies the given expression as a FILTER clause against an
 
@@ -283,35 +284,35 @@ defmodule Ecto.Query.API do
283
284
284
285
from p in Payment, select: avg(p.value) |> filter(p.value < 0)
285
286
"""
286
- def filter(value, filter), do: doc! [value, filter]
287
+ def filter(value, filter), do: doc!([value, filter])
287
288
288
289
@doc """
289
290
Calculates the average for the given entry.
290
291
291
292
from p in Payment, select: avg(p.value)
292
293
"""
293
- def avg(value), do: doc! [value]
294
+ def avg(value), do: doc!([value])
294
295
295
296
@doc """
296
297
Calculates the sum for the given entry.
297
298
298
299
from p in Payment, select: sum(p.value)
299
300
"""
300
- def sum(value), do: doc! [value]
301
+ def sum(value), do: doc!([value])
301
302
302
303
@doc """
303
304
Calculates the minimum for the given entry.
304
305
305
306
from p in Payment, select: min(p.value)
306
307
"""
307
- def min(value), do: doc! [value]
308
+ def min(value), do: doc!([value])
308
309
309
310
@doc """
310
311
Calculates the maximum for the given entry.
311
312
312
313
from p in Payment, select: max(p.value)
313
314
"""
314
- def max(value), do: doc! [value]
315
+ def max(value), do: doc!([value])
315
316
316
317
@doc """
317
318
Adds a given interval to a datetime.
 
@@ -330,7 +331,7 @@ defmodule Ecto.Query.API do
330
331
331
332
See [Intervals](#module-intervals) for supported `interval` values.
332
333
"""
333
- def datetime_add(datetime, count, interval), do: doc! [datetime, count, interval]
334
+ def datetime_add(datetime, count, interval), do: doc!([datetime, count, interval])
334
335
335
336
@doc """
336
337
Adds a given interval to a date.
 
@@ -339,7 +340,7 @@ defmodule Ecto.Query.API do
339
340
340
341
See [Intervals](#module-intervals) for supported `interval` values.
341
342
"""
342
- def date_add(date, count, interval), do: doc! [date, count, interval]
343
+ def date_add(date, count, interval), do: doc!([date, count, interval])
343
344
344
345
@doc """
345
346
Adds the given interval to the current time in UTC.
 
@@ -354,7 +355,7 @@ defmodule Ecto.Query.API do
354
355
from a in Account, where: a.expires_at < from_now(3, "month")
355
356
356
357
"""
357
- def from_now(count, interval), do: doc! [count, interval]
358
+ def from_now(count, interval), do: doc!([count, interval])
358
359
359
360
@doc """
360
361
Subtracts the given interval from the current time in UTC.
 
@@ -368,7 +369,7 @@ defmodule Ecto.Query.API do
368
369
369
370
from p in Post, where: p.published_at > ago(3, "month")
370
371
"""
371
- def ago(count, interval), do: doc! [count, interval]
372
+ def ago(count, interval), do: doc!([count, interval])
372
373
373
374
@doc """
374
375
Send fragments directly to the database.
 
@@ -476,7 +477,7 @@ defmodule Ecto.Query.API do
476
477
where: fragment(title: ["$eq": ^some_value])
477
478
478
479
"""
479
- def fragment(fragments), do: doc! [fragments]
480
+ def fragment(fragments), do: doc!([fragments])
480
481
481
482
@doc """
482
483
Allows a literal identifier to be injected into a fragment:
 
@@ -489,7 +490,7 @@ defmodule Ecto.Query.API do
489
490
each different value of `collation` will emit a different query,
490
491
which will be independently prepared and cached.
491
492
"""
492
- def literal(binary), do: doc! [binary]
493
+ def literal(binary), do: doc!([binary])
493
494
494
495
@doc """
495
496
Allows a list argument to be spliced into a fragment.
 
@@ -499,8 +500,13 @@ defmodule Ecto.Query.API do
499
500
The example above will be transformed at runtime into the following:
500
501
501
502
from p in Post, where: fragment("? in (?,?,?)", p.id, ^1, ^2, ^3)
503
+
504
+ You may only splice runtime values. For example, this would not work because
505
+ query bindings are compile-time constructs:
506
+
507
+ from p in Post, where: fragment("concat(?)", splice(^[p.count, " ", "count"])
502
508
"""
503
- def splice(list), do: doc! [list]
509
+ def splice(list), do: doc!([list])
504
510
505
511
@doc """
506
512
Creates a values list/constant table.
 
@@ -515,6 +521,8 @@ defmodule Ecto.Query.API do
515
521
Each field must be given a type or an error is raised. Any type that can be specified in
516
522
a schema may be used.
517
523
524
+ Queries using a values list are not cacheable by Ecto.
525
+
518
526
## Select example
519
527
520
528
values = [%{id: 1, text: "abc"}, %{id: 2, text: "xyz"}]
 
@@ -551,7 +559,7 @@ defmodule Ecto.Query.API do
551
559
552
560
Repo.update_all(query, [])
553
561
"""
554
- def values(values, types), do: doc! [values, types]
562
+ def values(values, types), do: doc!([values, types])
555
563
556
564
@doc """
557
565
Allows a field to be dynamically accessed.
 
@@ -564,7 +572,7 @@ defmodule Ecto.Query.API do
564
572
In the example above, both `at_least_four(:doors)` and `at_least_four(:tires)`
565
573
would be valid calls as the field is dynamically generated.
566
574
"""
567
- def field(source, field), do: doc! [source, field]
575
+ def field(source, field), do: doc!([source, field])
568
576
569
577
@doc """
570
578
Used in `select` to specify which struct fields should be returned.
 
@@ -612,7 +620,7 @@ defmodule Ecto.Query.API do
612
620
MUST include the foreign keys used in the relationship,
613
621
otherwise Ecto will be unable to find associated records.
614
622
"""
615
- def struct(source, fields), do: doc! [source, fields]
623
+ def struct(source, fields), do: doc!([source, fields])
616
624
617
625
@doc """
618
626
Used in `select` to specify which fields should be returned as a map.
 
@@ -655,7 +663,7 @@ defmodule Ecto.Query.API do
655
663
MUST include the foreign keys used in the relationship,
656
664
otherwise Ecto will be unable to find associated records.
657
665
"""
658
- def map(source, fields), do: doc! [source, fields]
666
+ def map(source, fields), do: doc!([source, fields])
659
667
660
668
@doc """
661
669
Merges the map on the right over the map on the left.
 
@@ -669,7 +677,7 @@ defmodule Ecto.Query.API do
669
677
This function is primarily used by `Ecto.Query.select_merge/3`
670
678
to merge different select clauses.
671
679
"""
672
- def merge(left_map, right_map), do: doc! [left_map, right_map]
680
+ def merge(left_map, right_map), do: doc!([left_map, right_map])
673
681
674
682
@doc """
675
683
Returns value from the `json_field` pointed to by `path`.
 
@@ -725,7 +733,7 @@ defmodule Ecto.Query.API do
725
733
tries to compare incompatible types. You can, however, use `type/2`
726
734
to force the types on the database level.
727
735
"""
728
- def json_extract_path(json_field, path), do: doc! [json_field, path]
736
+ def json_extract_path(json_field, path), do: doc!([json_field, path])
729
737
730
738
@doc """
731
739
Casts the given value to the given type at the database level.
 
@@ -779,15 +787,21 @@ defmodule Ecto.Query.API do
779
787
child = from c in Comment, where: type(parent_as(:posts).id, :string) == c.text
780
788
from Post, as: :posts, inner_lateral_join: c in subquery(child), select: c.text
781
789
790
+ ## `type` vs `fragment`
791
+
792
+ `type/2` is all about Ecto types. Therefore, you can perform `type(expr, :string)`
793
+ but not `type(expr, :text)`, because `:text` is not an actual Ecto type. If you want
794
+ to perform casting exclusively at the database level, you can use fragment. For example,
795
+ in PostgreSQL, you might do `fragment("?::text", p.column)`.
782
796
"""
783
- def type(interpolated_value, type), do: doc! [interpolated_value, type]
797
+ def type(interpolated_value, type), do: doc!([interpolated_value, type])
784
798
785
799
@doc """
786
800
Refer to a named atom binding.
787
801
788
802
See the "Named binding" section in `Ecto.Query` for more information.
789
803
"""
790
- def as(binding), do: doc! [binding]
804
+ def as(binding), do: doc!([binding])
791
805
792
806
@doc """
793
807
Refer to a named atom binding in the parent query.
 
@@ -796,7 +810,7 @@ defmodule Ecto.Query.API do
796
810
797
811
See the "Named binding" section in `Ecto.Query` for more information.
798
812
"""
799
- def parent_as(binding), do: doc! [binding]
813
+ def parent_as(binding), do: doc!([binding])
800
814
801
815
@doc """
802
816
Refer to an alias of a selected value.
 
@@ -809,7 +823,7 @@ defmodule Ecto.Query.API do
809
823
referenced somewhere that is not allowed. Consult the documentation for the database
810
824
to ensure the alias is being referenced correctly.
811
825
"""
812
- def selected_as(name), do: doc! [name]
826
+ def selected_as(name), do: doc!([name])
813
827
814
828
@doc """
815
829
Creates an alias for the given selected value.
 
@@ -861,10 +875,10 @@ defmodule Ecto.Query.API do
861
875
The name given to `selected_as/2` can also be referenced in `selected_as/1`,
862
876
as in regular queries.
863
877
"""
864
- def selected_as(selected_value, name), do: doc! [selected_value, name]
878
+ def selected_as(selected_value, name), do: doc!([selected_value, name])
865
879
866
880
defp doc!(_) do
867
881
raise "the functions in Ecto.Query.API should not be invoked directly, " <>
868
- "they serve for documentation purposes only"
882
+ "they serve for documentation purposes only"
869
883
end
870
884
end
changed lib/ecto/query/builder.ex
 
@@ -52,7 +52,7 @@ defmodule Ecto.Query.Builder do
52
52
which include all primitive types and custom user types. Also
53
53
note custom user types do not show up during compilation time.
54
54
"""
55
- @type quoted_type :: Ecto.Type.primitive | {non_neg_integer, atom | Macro.t}
55
+ @type quoted_type :: Ecto.Type.primitive() | {non_neg_integer, atom | Macro.t()}
56
56
57
57
@typedoc """
58
58
The accumulator during escape.
 
@@ -74,12 +74,18 @@ defmodule Ecto.Query.Builder do
74
74
with `^index` in the query where index is a number indexing into the
75
75
map.
76
76
"""
77
- @spec escape(Macro.t, quoted_type | {:in, quoted_type} | {:out, quoted_type} | {:splice, quoted_type},
78
- {list, acc}, Keyword.t, Macro.Env.t | {Macro.Env.t, fun}) :: {Macro.t, {list, acc}}
77
+ @spec escape(
78
+ Macro.t(),
79
+ quoted_type | {:in, quoted_type} | {:out, quoted_type} | {:splice, quoted_type},
80
+ {list, acc},
81
+ Keyword.t(),
82
+ Macro.Env.t() | {Macro.Env.t(), fun}
83
+ ) :: {Macro.t(), {list, acc}}
79
84
def escape(expr, type, params_acc, vars, env)
80
85
81
86
# var.x - where var is bound
82
- def escape({{:., _, [callee, field]}, _, []}, _type, params_acc, vars, _env) when is_atom(field) do
87
+ def escape({{:., _, [callee, field]}, _, []}, _type, params_acc, vars, _env)
88
+ when is_atom(field) do
83
89
{escape_field!(callee, field, vars), params_acc}
84
90
end
85
91
 
@@ -103,7 +109,13 @@ defmodule Ecto.Query.Builder do
103
109
{expr, {params, acc}}
104
110
end
105
111
106
- def escape({:type, _, [{{:., _, [{var, _, context}, field]}, _, []} = expr, type]}, _type, params_acc, vars, env)
112
+ def escape(
113
+ {:type, _, [{{:., _, [{var, _, context}, field]}, _, []} = expr, type]},
114
+ _type,
115
+ params_acc,
116
+ vars,
117
+ env
118
+ )
107
119
when is_atom(var) and is_atom(context) and is_atom(field) do
108
120
escape_with_type(expr, type, params_acc, vars, env)
109
121
end
 
@@ -126,22 +138,40 @@ defmodule Ecto.Query.Builder do
126
138
escape_with_type(expr, type, params_acc, vars, env)
127
139
end
128
140
129
- def escape({:type, _, [{:json_extract_path, _, [_ | _]} = expr, type]}, _type, params_acc, vars, env) do
141
+ def escape(
142
+ {:type, _, [{:json_extract_path, _, [_ | _]} = expr, type]},
143
+ _type,
144
+ params_acc,
145
+ vars,
146
+ env
147
+ ) do
130
148
escape_with_type(expr, type, params_acc, vars, env)
131
149
end
132
150
133
- def escape({:type, _, [{{:., _, [Access, :get]}, _, _} = access_expr, type]}, _type, params_acc, vars, env) do
151
+ def escape(
152
+ {:type, _, [{{:., _, [Access, :get]}, _, _} = access_expr, type]},
153
+ _type,
154
+ params_acc,
155
+ vars,
156
+ env
157
+ ) do
134
158
escape_with_type(access_expr, type, params_acc, vars, env)
135
159
end
136
160
137
- def escape({:type, _, [{{:., _, [{:parent_as, _, [_parent]}, _field]}, _, []} = expr, type]}, _type, params_acc, vars, env) do
161
+ def escape(
162
+ {:type, _, [{{:., _, [{:parent_as, _, [_parent]}, _field]}, _, []} = expr, type]},
163
+ _type,
164
+ params_acc,
165
+ vars,
166
+ env
167
+ ) do
138
168
escape_with_type(expr, type, params_acc, vars, env)
139
169
end
140
170
141
171
def escape({:type, meta, [expr, type]}, given_type, params_acc, vars, env) do
142
172
case Macro.expand_once(expr, get_env(env)) do
143
173
^expr ->
144
- error! """
174
+ error!("""
145
175
the first argument of type/2 must be one of:
146
176
147
177
* interpolations, such as ^value
 
@@ -154,7 +184,7 @@ defmodule Ecto.Query.Builder do
154
184
* parent_as/1 (parent_as(:parent).field)
155
185
156
186
Got: #{Macro.to_string(expr)}
157
- """
187
+ """)
158
188
159
189
expanded ->
160
190
escape({:type, meta, [expanded, type]}, given_type, params_acc, vars, env)
 
@@ -165,6 +195,7 @@ defmodule Ecto.Query.Builder do
165
195
def escape({:fragment, _, [query]}, _type, params_acc, vars, env) when is_list(query) do
166
196
{escaped, params_acc} =
167
197
Enum.map_reduce(query, params_acc, &escape_kw_fragment(&1, &2, vars, env))
198
+
168
199
{{:{}, [], [:fragment, [], [escaped]]}, params_acc}
169
200
end
170
201
 
@@ -177,8 +208,10 @@ defmodule Ecto.Query.Builder do
177
208
pieces = expand_and_split_fragment(query, env)
178
209
179
210
if length(pieces) != length(frags) + 1 do
180
- error! "fragment(...) expects extra arguments in the same amount of question marks in string. " <>
181
- "It received #{length(frags)} extra argument(s) but expected #{length(pieces) - 1}"
211
+ error!(
212
+ "fragment(...) expects extra arguments in the same amount of question marks in string. " <>
213
+ "It received #{length(frags)} extra argument(s) but expected #{length(pieces) - 1}"
214
+ )
182
215
end
183
216
184
217
{frags, params_acc} = Enum.map_reduce(frags, params_acc, &escape_fragment(&1, &2, vars, env))
 
@@ -204,20 +237,23 @@ defmodule Ecto.Query.Builder do
204
237
205
238
def escape({:ago, meta, [count, interval]}, type, params_acc, vars, env) do
206
239
utc = quote do: ^DateTime.utc_now()
240
+
207
241
count =
208
242
case count do
209
243
{:^, meta, [value]} ->
210
244
negate = quote do: Ecto.Query.Builder.negate!(unquote(value))
211
245
{:^, meta, [negate]}
246
+
212
247
value ->
213
248
{:-, [], [value]}
214
249
end
250
+
215
251
escape({:datetime_add, meta, [utc, count, interval]}, type, params_acc, vars, env)
216
252
end
217
253
218
254
def escape({:datetime_add, _, [datetime, count, interval]} = expr, type, params_acc, vars, env) do
219
- assert_type!(expr, type, {:param, :any_datetime})
220
- {datetime, params_acc} = escape(datetime, {:param, :any_datetime}, params_acc, vars, env)
255
+ assert_type!(expr, type, {:supertype, :datetime})
256
+ {datetime, params_acc} = escape(datetime, {:supertype, :datetime}, params_acc, vars, env)
221
257
{count, interval, params_acc} = escape_interval(count, interval, params_acc, vars, env)
222
258
{{:{}, [], [:datetime_add, [], [datetime, count, interval]]}, params_acc}
223
259
end
 
@@ -257,7 +293,7 @@ defmodule Ecto.Query.Builder do
257
293
258
294
# lists
259
295
def escape(list, type, params_acc, vars, env) when is_list(list) do
260
- if Enum.all?(list, &is_binary(&1) or is_number(&1) or is_boolean(&1)) do
296
+ if Enum.all?(list, &(is_binary(&1) or is_number(&1) or is_boolean(&1))) do
261
297
{literal(list, type, vars), params_acc}
262
298
else
263
299
fun =
 
@@ -278,15 +314,18 @@ defmodule Ecto.Query.Builder do
278
314
279
315
# literals
280
316
def escape({:<<>>, _, args} = expr, type, params_acc, vars, _env) do
281
- valid? = Enum.all?(args, fn
282
- {:"::", _, [left, _]} -> is_integer(left) or is_binary(left)
283
- left -> is_integer(left) or is_binary(left)
284
- end)
317
+ valid? =
318
+ Enum.all?(args, fn
319
+ {:"::", _, [left, _]} -> is_integer(left) or is_binary(left)
320
+ left -> is_integer(left) or is_binary(left)
321
+ end)
285
322
286
323
unless valid? do
287
- error! "`#{Macro.to_string(expr)}` is not a valid query expression. " <>
288
- "Only literal binaries and strings are allowed, " <>
289
- "dynamic values need to be explicitly interpolated in queries with ^"
324
+ error!(
325
+ "`#{Macro.to_string(expr)}` is not a valid query expression. " <>
326
+ "Only literal binaries and strings are allowed, " <>
327
+ "dynamic values need to be explicitly interpolated in queries with ^"
328
+ )
290
329
end
291
330
292
331
{literal(expr, type, vars), params_acc}
 
@@ -294,12 +333,16 @@ defmodule Ecto.Query.Builder do
294
333
295
334
def escape({:-, _, [number]}, type, params_acc, vars, _env) when is_number(number),
296
335
do: {literal(-number, type, vars), params_acc}
336
+
297
337
def escape(number, type, params_acc, vars, _env) when is_number(number),
298
338
do: {literal(number, type, vars), params_acc}
339
+
299
340
def escape(binary, type, params_acc, vars, _env) when is_binary(binary),
300
341
do: {literal(binary, type, vars), params_acc}
342
+
301
343
def escape(nil, _type, params_acc, _vars, _env),
302
344
do: {nil, params_acc}
345
+
303
346
def escape(atom, type, params_acc, vars, _env) when is_atom(atom),
304
347
do: {literal(atom, type, vars), params_acc}
305
348
 
@@ -316,25 +359,26 @@ defmodule Ecto.Query.Builder do
316
359
assert_type!(expr, type, :boolean)
317
360
318
361
if is_nil(left) or is_nil(right) do
319
- error! "comparison with nil is forbidden as it is unsafe. " <>
320
- "If you want to check if a value is nil, use is_nil/1 instead"
362
+ error!(
363
+ "comparison with nil is forbidden as it is unsafe. " <>
364
+ "If you want to check if a value is nil, use is_nil/1 instead"
365
+ )
321
366
end
322
367
323
368
ltype = quoted_type(right, vars)
324
369
rtype = quoted_type(left, vars)
325
370
326
- {left, params_acc} = escape(left, ltype, params_acc, vars, env)
371
+ {left, params_acc} = escape(left, ltype, params_acc, vars, env)
327
372
{right, params_acc} = escape(right, rtype, params_acc, vars, env)
328
373
329
374
{params, acc} = params_acc
330
- {{:{}, [], [comp_op, [], [left, right]]},
331
- {params |> wrap_nil(left) |> wrap_nil(right), acc}}
375
+ {{:{}, [], [comp_op, [], [left, right]]}, {params |> wrap_nil(left) |> wrap_nil(right), acc}}
332
376
end
333
377
334
378
# mathematical operators
335
379
def escape({math_op, _, [left, right]}, type, params_acc, vars, env)
336
380
when math_op in ~w(+ - * /)a do
337
- {left, params_acc} = escape(left, type, params_acc, vars, env)
381
+ {left, params_acc} = escape(left, type, params_acc, vars, env)
338
382
{right, params_acc} = escape(right, type, params_acc, vars, env)
339
383
340
384
{{:{}, [], [math_op, [], [left, right]]}, params_acc}
 
@@ -404,7 +448,7 @@ defmodule Ecto.Query.Builder do
404
448
end
405
449
406
450
def escape({:selected_as, _, [_expr, _name]}, _type, _params_acc, _vars, _env) do
407
- error! """
451
+ error!("""
408
452
selected_as/2 can only be used at the root of a select statement. \
409
453
If you are trying to use it inside of an expression, consider putting the \
410
454
expression inside of `selected_as/2` instead. For instance, instead of:
 
@@ -414,7 +458,7 @@ defmodule Ecto.Query.Builder do
414
458
use:
415
459
416
460
from p in Post, select: selected_as(coalesce(p.visits, 0), :v)
417
- """
461
+ """)
418
462
end
419
463
420
464
def escape({:selected_as, _, [name]}, _type, params_acc, _vars, _env) do
 
@@ -423,20 +467,25 @@ defmodule Ecto.Query.Builder do
423
467
{expr, params_acc}
424
468
end
425
469
426
- def escape({quantifier, meta, [subquery]}, type, params_acc, vars, env) when quantifier in [:all, :any, :exists] do
470
+ def escape({quantifier, meta, [subquery]}, type, params_acc, vars, env)
471
+ when quantifier in [:all, :any, :exists] do
427
472
{subquery, params_acc} = escape({:subquery, meta, [subquery]}, type, params_acc, vars, env)
428
473
{{:{}, [], [quantifier, [], [subquery]]}, params_acc}
429
474
end
430
475
431
476
def escape({:=, _, _} = expr, _type, _params_acc, _vars, _env) do
432
- error! "`#{Macro.to_string(expr)}` is not a valid query expression. " <>
433
- "The match operator is not supported: `=`. " <>
434
- "Did you mean to use `==` instead?"
477
+ error!(
478
+ "`#{Macro.to_string(expr)}` is not a valid query expression. " <>
479
+ "The match operator is not supported: `=`. " <>
480
+ "Did you mean to use `==` instead?"
481
+ )
435
482
end
436
483
437
484
def escape({op, _, _}, _type, _params_acc, _vars, _env) when op in ~w(|| && !)a do
438
- error! "short-circuit operators are not supported: `#{op}`. " <>
439
- "Instead use boolean operators: `and`, `or`, and `not`"
485
+ error!(
486
+ "short-circuit operators are not supported: `#{op}`. " <>
487
+ "Instead use boolean operators: `and`, `or`, and `not`"
488
+ )
440
489
end
441
490
442
491
# Tuple
 
@@ -451,8 +500,9 @@ defmodule Ecto.Query.Builder do
451
500
list
452
501
|> Enum.zip(types)
453
502
|> Enum.map_reduce(params_acc, fn {expr, type}, params_acc ->
454
- escape(expr, type, params_acc, vars, env)
455
- end)
503
+ escape(expr, type, params_acc, vars, env)
504
+ end)
505
+
456
506
expr = {:{}, [], [:{}, [], list]}
457
507
{expr, params_acc}
458
508
else
 
@@ -462,7 +512,7 @@ defmodule Ecto.Query.Builder do
462
512
463
513
# Tuple
464
514
def escape({:{}, _, _}, _, _, _, _) do
465
- error! "Tuples can only be used in comparisons with literal tuples of the same size"
515
+ error!("Tuples can only be used in comparisons with literal tuples of the same size")
466
516
end
467
517
468
518
# Unnecessary parentheses around an expression
 
@@ -471,25 +521,28 @@ defmodule Ecto.Query.Builder do
471
521
end
472
522
473
523
# Other functions - no type casting
474
- def escape({name, _, args} = expr, type, params_acc, vars, env) when is_atom(name) and is_list(args) do
524
+ def escape({name, _, args} = expr, type, params_acc, vars, env)
525
+ when is_atom(name) and is_list(args) do
475
526
case call_type(name, length(args)) do
476
527
{in_type, out_type} ->
477
528
assert_type!(expr, type, out_type)
478
529
escape_call(expr, in_type, params_acc, vars, env)
530
+
479
531
nil ->
480
532
try_expansion(expr, type, params_acc, vars, env)
481
533
end
482
534
end
483
535
484
536
# Finally handle vars
485
- def escape({var, _, context}, _type, params_acc, vars, _env) when is_atom(var) and is_atom(context) do
537
+ def escape({var, _, context}, _type, params_acc, vars, _env)
538
+ when is_atom(var) and is_atom(context) do
486
539
{escape_var!(var, vars), params_acc}
487
540
end
488
541
489
542
# Raise nice error messages for fun calls.
490
543
def escape({fun, _, args} = other, _type, _params_acc, _vars, _env)
491
544
when is_atom(fun) and is_list(args) do
492
- error! """
545
+ error!("""
493
546
`#{Macro.to_string(other)}` is not a valid query expression. \
494
547
If you are trying to invoke a function that is not supported by Ecto, \
495
548
you can use fragments:
 
@@ -498,7 +551,7 @@ defmodule Ecto.Query.Builder do
498
551
499
552
See Ecto.Query.API to learn more about the supported functions and \
500
553
Ecto.Query.API.fragment/1 to learn more about fragments.
501
- """
554
+ """)
502
555
end
503
556
504
557
# Raise nice error message for remote calls
 
@@ -509,7 +562,7 @@ defmodule Ecto.Query.Builder do
509
562
510
563
# For everything else we raise
511
564
def escape(other, _type, _params_acc, _vars, _env) do
512
- error! "`#{Macro.to_string(other)}` is not a valid query expression"
565
+ error!("`#{Macro.to_string(other)}` is not a valid query expression")
513
566
end
514
567
515
568
defp escape_with_type(expr, {:^, _, [type]}, params_acc, vars, env) do
 
@@ -523,14 +576,18 @@ defmodule Ecto.Query.Builder do
523
576
{{:{}, [], [:type, [], [expr, escape_type(type)]]}, params_acc}
524
577
end
525
578
526
- defp escape_type({:parameterized, _, _} = param), do: Macro.escape(param)
579
+ defp escape_type({:parameterized, _} = param), do: Macro.escape(param)
527
580
defp escape_type(type), do: type
528
581
529
582
defp validate_json_field!({{:., _, _}, _, _}), do: :ok
530
583
defp validate_json_field!({:field, _, _}), do: :ok
531
- defp validate_json_field!(unsupported_field), do: error!("`#{Macro.to_string(unsupported_field)}` is not a valid json field")
532
584
533
- defp wrap_nil(params, {:{}, _, [:^, _, [ix]]}), do: wrap_nil(params, length(params) - ix - 1, [])
585
+ defp validate_json_field!(unsupported_field),
586
+ do: error!("`#{Macro.to_string(unsupported_field)}` is not a valid json field")
587
+
588
+ defp wrap_nil(params, {:{}, _, [:^, _, [ix]]}),
589
+ do: wrap_nil(params, length(params) - ix - 1, [])
590
+
534
591
defp wrap_nil(params, _other), do: params
535
592
536
593
defp wrap_nil([{val, type} | params], 0, acc) do
 
@@ -548,7 +605,7 @@ defmodule Ecto.Query.Builder do
548
605
split_fragment(binary, "")
549
606
550
607
_ ->
551
- error! bad_fragment_message(Macro.to_string(query))
608
+ error!(bad_fragment_message(Macro.to_string(query)))
552
609
end
553
610
end
554
611
 
@@ -559,12 +616,15 @@ defmodule Ecto.Query.Builder do
559
616
560
617
defp split_fragment(<<>>, consumed),
561
618
do: [consumed]
562
- defp split_fragment(<<??, rest :: binary>>, consumed),
619
+
620
+ defp split_fragment(<<??, rest::binary>>, consumed),
563
621
do: [consumed | split_fragment(rest, "")]
564
- defp split_fragment(<<?\\, ??, rest :: binary>>, consumed),
622
+
623
+ defp split_fragment(<<?\\, ??, rest::binary>>, consumed),
565
624
do: split_fragment(rest, consumed <> <<??>>)
566
- defp split_fragment(<<first :: utf8, rest :: binary>>, consumed),
567
- do: split_fragment(rest, consumed <> <<first :: utf8>>)
625
+
626
+ defp split_fragment(<<first::utf8, rest::binary>>, consumed),
627
+ do: split_fragment(rest, consumed <> <<first::utf8>>)
568
628
569
629
@doc "Returns fragment pieces, given a fragment string and arguments."
570
630
def fragment_pieces(frag, args) do
 
@@ -575,16 +635,21 @@ defmodule Ecto.Query.Builder do
575
635
576
636
defp escape_window_description([], params_acc, _vars, _env),
577
637
do: {[], params_acc}
578
- defp escape_window_description([window_name], params_acc, _vars, _env) when is_atom(window_name),
579
- do: {window_name, params_acc}
638
+
639
+ defp escape_window_description([window_name], params_acc, _vars, _env)
640
+ when is_atom(window_name),
641
+ do: {window_name, params_acc}
642
+
580
643
defp escape_window_description([kw], params_acc, vars, env) do
581
644
case Ecto.Query.Builder.Windows.escape(kw, params_acc, vars, env) do
582
645
{runtime, [], params_acc} ->
583
646
{runtime, params_acc}
584
647
585
648
{_, [{key, _} | _], _} ->
586
- error! "windows definitions given to over/2 do not allow interpolations at the root of " <>
587
- "`#{key}`. Please use Ecto.Query.windows/3 to explicitly define a window instead"
649
+ error!(
650
+ "windows definitions given to over/2 do not allow interpolations at the root of " <>
651
+ "`#{key}`. Please use Ecto.Query.windows/3 to explicitly define a window instead"
652
+ )
588
653
end
589
654
end
590
655
 
@@ -604,8 +669,11 @@ defmodule Ecto.Query.Builder do
604
669
else
605
670
case Macro.expand_once(expr, get_env(env)) do
606
671
^expr ->
607
- error! "unknown window function #{agg}/#{length(args)}. " <>
608
- "See Ecto.Query.WindowAPI for all available functions"
672
+ error!(
673
+ "unknown window function #{agg}/#{length(args)}. " <>
674
+ "See Ecto.Query.WindowAPI for all available functions"
675
+ )
676
+
609
677
expr ->
610
678
validate_window_function!(expr, env)
611
679
end
 
@@ -622,18 +690,18 @@ defmodule Ecto.Query.Builder do
622
690
623
691
defp escape_field!({var, _, context}, field, vars)
624
692
when is_atom(var) and is_atom(context) do
625
- var = escape_var!(var, vars)
693
+ var = escape_var!(var, vars)
626
694
field = quoted_atom!(field, "field/2")
627
- dot = {:{}, [], [:., [], [var, field]]}
695
+ dot = {:{}, [], [:., [], [var, field]]}
628
696
{:{}, [], [dot, [], []]}
629
697
end
630
698
631
699
defp escape_field!({kind, _, [value]}, field, _vars)
632
700
when kind in [:as, :parent_as] do
633
701
value = late_binding!(kind, value)
634
- as = {:{}, [], [kind, [], [value]]}
702
+ as = {:{}, [], [kind, [], [value]]}
635
703
field = quoted_atom!(field, "field/2")
636
- dot = {:{}, [], [:., [], [as, field]]}
704
+ dot = {:{}, [], [:., [], [as, field]]}
637
705
{:{}, [], [dot, [], []]}
638
706
end
639
707
 
@@ -650,17 +718,19 @@ defmodule Ecto.Query.Builder do
650
718
defp escape_interval(count, interval, params_acc, vars, env) do
651
719
type =
652
720
cond do
653
- is_float(count) -> :float
721
+ is_float(count) -> :float
654
722
is_integer(count) -> :integer
655
- true -> :decimal
723
+ true -> :decimal
656
724
end
657
725
658
726
{count, params_acc} = escape(count, type, params_acc, vars, env)
659
727
{count, quoted_interval!(interval), params_acc}
660
728
end
661
729
662
- defp escape_kw_fragment({key, [{_, _}|_] = exprs}, params_acc, vars, env) when is_atom(key) do
663
- {escaped, params_acc} = Enum.map_reduce(exprs, params_acc, &escape_kw_fragment(&1, &2, vars, env))
730
+ defp escape_kw_fragment({key, [{_, _} | _] = exprs}, params_acc, vars, env) when is_atom(key) do
731
+ {escaped, params_acc} =
732
+ Enum.map_reduce(exprs, params_acc, &escape_kw_fragment(&1, &2, vars, env))
733
+
664
734
{{key, escaped}, params_acc}
665
735
end
666
736
 
@@ -670,7 +740,9 @@ defmodule Ecto.Query.Builder do
670
740
end
671
741
672
742
defp escape_kw_fragment({key, _expr}, _params_acc, _vars, _env) do
673
- error! "fragment(...) with keywords accepts only atoms as keys, got `#{Macro.to_string(key)}`"
743
+ error!(
744
+ "fragment(...) with keywords accepts only atoms as keys, got `#{Macro.to_string(key)}`"
745
+ )
674
746
end
675
747
676
748
defp escape_fragment({:literal, _meta, [expr]}, params_acc, _vars, _env) do
 
@@ -681,7 +753,9 @@ defmodule Ecto.Query.Builder do
681
753
{escaped, params_acc}
682
754
683
755
_ ->
684
- error! "literal/1 in fragment expects an interpolated value, such as literal(^value), got `#{Macro.to_string(expr)}`"
756
+ error!(
757
+ "literal/1 in fragment expects an interpolated value, such as literal(^value), got `#{Macro.to_string(expr)}`"
758
+ )
685
759
end
686
760
end
687
761
 
@@ -691,11 +765,13 @@ defmodule Ecto.Query.Builder do
691
765
checked = quote do: Ecto.Query.Builder.splice!(unquote(value))
692
766
length = quote do: length(unquote(checked))
693
767
{expr, params_acc} = escape(expr, {:splice, :any}, params_acc, vars, env)
694
- escaped = {:{}, [], [:splice, [], [expr, length]]}
768
+ escaped = {:{}, [], [:splice, [], [expr, length]]}
695
769
{escaped, params_acc}
696
770
697
771
_ ->
698
- error! "splice/1 in fragment expects an interpolated value, such as splice(^value), got `#{Macro.to_string(splice)}`"
772
+ error!(
773
+ "splice/1 in fragment expects an interpolated value, such as splice(^value), got `#{Macro.to_string(splice)}`"
774
+ )
699
775
end
700
776
end
701
777
 
@@ -703,7 +779,7 @@ defmodule Ecto.Query.Builder do
703
779
escape(expr, :any, params_acc, vars, env)
704
780
end
705
781
706
- defp merge_fragments([h1|t1], [h2|t2]),
782
+ defp merge_fragments([h1 | t1], [h2 | t2]),
707
783
do: [{:raw, h1}, {:expr, h2} | merge_fragments(t1, t2)]
708
784
709
785
defp merge_fragments([h1], []),
 
@@ -737,38 +813,49 @@ defmodule Ecto.Query.Builder do
737
813
:ok
738
814
739
815
true ->
740
- error! "expression `#{Macro.to_string(expr)}` does not type check. " <>
741
- "It returns a value of type #{inspect actual} but a value of " <>
742
- "type #{inspect type} is expected"
816
+ error!(
817
+ "expression `#{Macro.to_string(expr)}` does not type check. " <>
818
+ "It returns a value of type #{inspect(actual)} but a value of " <>
819
+ "type #{inspect(type)} is expected"
820
+ )
743
821
end
744
822
end
745
823
746
824
@doc """
747
825
Validates the type with the given vars.
748
826
"""
827
+ def validate_type!({:parameterized, _} = type, _vars, _env),
828
+ do: type
829
+
749
830
def validate_type!({composite, type}, vars, env),
750
831
do: {composite, validate_type!(type, vars, env)}
832
+
751
833
def validate_type!({:^, _, [type]}, _vars, _env),
752
834
do: type
835
+
753
836
def validate_type!({:__aliases__, _, _} = type, _vars, env),
754
837
do: Macro.expand(type, get_env(env))
755
- def validate_type!({:parameterized, _, _} = type, _vars, _env),
756
- do: type
838
+
757
839
def validate_type!(type, _vars, _env) when is_atom(type),
758
840
do: type
841
+
759
842
def validate_type!({{:., _, [{var, _, context}, field]}, _, []}, vars, _env)
760
- when is_atom(var) and is_atom(context) and is_atom(field),
761
- do: {find_var!(var, vars), field}
843
+ when is_atom(var) and is_atom(context) and is_atom(field),
844
+ do: {find_var!(var, vars), field}
845
+
762
846
def validate_type!({:field, _, [{var, _, context}, field]}, vars, _env)
763
- when is_atom(var) and is_atom(context) and is_atom(field),
764
- do: {find_var!(var, vars), field}
847
+ when is_atom(var) and is_atom(context) and is_atom(field),
848
+ do: {find_var!(var, vars), field}
849
+
765
850
def validate_type!({:field, _, [{var, _, context}, {:^, _, [field]}]}, vars, _env)
766
- when is_atom(var) and is_atom(context),
767
- do: {find_var!(var, vars), field}
851
+ when is_atom(var) and is_atom(context),
852
+ do: {find_var!(var, vars), field}
768
853
769
854
def validate_type!(type, _vars, _env) do
770
- error! "type/2 expects an alias, atom, initialized parameterized type or " <>
771
- "source.field as second argument, got: `#{Macro.to_string(type)}`"
855
+ error!(
856
+ "type/2 expects an alias, atom, initialized parameterized type or " <>
857
+ "source.field as second argument, got: `#{Macro.to_string(type)}`"
858
+ )
772
859
end
773
860
774
861
@always_tagged [:binary]
 
@@ -777,11 +864,16 @@ defmodule Ecto.Query.Builder do
777
864
do: do_literal(value, expected, quoted_type(value, vars))
778
865
779
866
defp do_literal(value, _, current) when current in @always_tagged,
780
- do: {:%, [], [Ecto.Query.Tagged, {:%{}, [], [value: value, type: current]}]}
867
+ do:
868
+ {:%, [],
869
+ [Ecto.Query.Tagged, {:%{}, [], [value: value, type: normalize_type(value, current)]}]}
870
+
781
871
defp do_literal(value, :any, _current),
782
872
do: value
873
+
783
874
defp do_literal(value, expected, expected),
784
875
do: value
876
+
785
877
defp do_literal(value, expected, _current),
786
878
do: {:%, [], [Ecto.Query.Tagged, {:%{}, [], [value: value, type: expected]}]}
787
879
 
@@ -794,7 +886,7 @@ defmodule Ecto.Query.Builder do
794
886
@doc """
795
887
Escape the select alias map
796
888
"""
797
- @spec escape_select_aliases(map()) :: Macro.t
889
+ @spec escape_select_aliases(map()) :: Macro.t()
798
890
def escape_select_aliases(%{} = aliases), do: {:%{}, [], Map.to_list(aliases)}
799
891
800
892
@doc """
 
@@ -803,7 +895,7 @@ defmodule Ecto.Query.Builder do
803
895
A escaped variable is represented internally as
804
896
`&0`, `&1` and so on.
805
897
"""
806
- @spec escape_var!(atom, Keyword.t) :: Macro.t
898
+ @spec escape_var!(atom, Keyword.t()) :: Macro.t()
807
899
def escape_var!(var, vars) do
808
900
{:{}, [], [:&, [], [find_var!(var, vars)]]}
809
901
end
 
@@ -829,21 +921,24 @@ defmodule Ecto.Query.Builder do
829
921
** (Ecto.Query.CompileError) binding list should contain only variables or `{as, var}` tuples, got: :foo
830
922
831
923
"""
832
- @spec escape_binding(Macro.t, list, Macro.Env.t) :: {Macro.t, Keyword.t}
924
+ @spec escape_binding(Macro.t(), list, Macro.Env.t()) :: {Macro.t(), Keyword.t()}
833
925
def escape_binding(query, binding, _env) when is_list(binding) do
834
- vars = binding |> Enum.with_index |> Enum.map(&escape_bind/1)
926
+ vars = binding |> Enum.with_index() |> Enum.map(&escape_bind/1)
835
927
assert_no_duplicate_binding!(vars)
836
928
837
- {positional_vars, named_vars} = Enum.split_while(vars, &not named_bind?(&1))
929
+ {positional_vars, named_vars} = Enum.split_while(vars, &(not named_bind?(&1)))
838
930
assert_named_binds_in_tail!(named_vars, binding)
839
931
840
932
{query, positional_binds} = calculate_positional_binds(query, positional_vars)
841
933
{query, named_binds} = calculate_named_binds(query, named_vars)
842
934
{query, positional_binds ++ named_binds}
843
935
end
936
+
844
937
def escape_binding(_query, bind, _env) do
845
- error! "binding should be list of variables and `{as, var}` tuples " <>
846
- "at the end, got: #{Macro.to_string(bind)}"
938
+ error!(
939
+ "binding should be list of variables and `{as, var}` tuples " <>
940
+ "at the end, got: #{Macro.to_string(bind)}"
941
+ )
847
942
end
848
943
849
944
defp named_bind?({kind, _, _}), do: kind == :named
 
@@ -852,8 +947,10 @@ defmodule Ecto.Query.Builder do
852
947
if Enum.all?(named_vars, &named_bind?/1) do
853
948
:ok
854
949
else
855
- error! "named binds in the form of `{as, var}` tuples must be at the end " <>
856
- "of the binding list, got: #{Macro.to_string(binding)}"
950
+ error!(
951
+ "named binds in the form of `{as, var}` tuples must be at the end " <>
952
+ "of the binding list, got: #{Macro.to_string(binding)}"
953
+ )
857
954
end
858
955
end
859
956
 
@@ -861,16 +958,17 @@ defmodule Ecto.Query.Builder do
861
958
bound_vars = for {_, var, _} <- vars, var != :_, do: var
862
959
863
960
case bound_vars -- Enum.uniq(bound_vars) do
864
- [] -> :ok
865
- [var | _] -> error! "variable `#{var}` is bound twice"
961
+ [] -> :ok
962
+ [var | _] -> error!("variable `#{var}` is bound twice")
866
963
end
867
964
end
868
965
869
966
defp calculate_positional_binds(query, vars) do
870
- case Enum.split_while(vars, &elem(&1, 1) != :...) do
967
+ case Enum.split_while(vars, &(elem(&1, 1) != :...)) do
871
968
{vars, []} ->
872
969
vars = for {:pos, var, count} <- vars, do: {var, count}
873
970
{query, vars}
971
+
874
972
{vars, [_ | tail]} ->
875
973
query =
876
974
quote do
 
@@ -882,7 +980,9 @@ defmodule Ecto.Query.Builder do
882
980
tail =
883
981
tail
884
982
|> Enum.with_index(-length(tail))
885
- |> Enum.map(fn {{:pos, k, _}, count} -> {k, quote(do: escape_count + unquote(count))} end)
983
+ |> Enum.map(fn {{:pos, k, _}, count} ->
984
+ {k, quote(do: escape_count + unquote(count))}
985
+ end)
886
986
887
987
vars = for {:pos, var, count} <- vars, do: {var, count}
888
988
{query, vars ++ tail}
 
@@ -890,6 +990,7 @@ defmodule Ecto.Query.Builder do
890
990
end
891
991
892
992
defp calculate_named_binds(query, []), do: {query, []}
993
+
893
994
defp calculate_named_binds(query, vars) do
894
995
assignments =
895
996
for {:named, key, name} <- vars do
 
@@ -922,23 +1023,33 @@ defmodule Ecto.Query.Builder do
922
1023
ix
923
1024
924
1025
%{} ->
925
- raise Ecto.QueryError, message: "unknown bind name `#{inspect name}`", query: query
1026
+ raise Ecto.QueryError, message: "unknown bind name `#{inspect(name)}`", query: query
926
1027
end
927
1028
end
928
1029
929
1030
defp escape_bind({{:..., _, _context}, ix}),
930
1031
do: {:pos, :..., ix}
1032
+
931
1033
defp escape_bind({{var, _, context}, ix}) when is_atom(var) and is_atom(context),
932
1034
do: {:pos, var, ix}
1035
+
933
1036
defp escape_bind({{{var, _, context}, ix}, _}) when is_atom(var) and is_atom(context),
934
1037
do: {:pos, var, ix}
935
- defp escape_bind({{name, {var, _, context}}, _ix}) when is_atom(name) and is_atom(var) and is_atom(context),
936
- do: {:named, var, name}
937
- defp escape_bind({{{:^, _, [expr]}, {var, _, context}}, _ix}) when is_atom(var) and is_atom(context),
938
- do: {:named, var, expr}
1038
+
1039
+ defp escape_bind({{name, {var, _, context}}, _ix})
1040
+ when is_atom(name) and is_atom(var) and is_atom(context),
1041
+ do: {:named, var, name}
1042
+
1043
+ defp escape_bind({{{:^, _, [expr]}, {var, _, context}}, _ix})
1044
+ when is_atom(var) and is_atom(context),
1045
+ do: {:named, var, expr}
1046
+
939
1047
defp escape_bind({bind, _ix}),
940
- do: error!("binding list should contain only variables or " <>
941
- "`{as, var}` tuples, got: #{Macro.to_string(bind)}")
1048
+ do:
1049
+ error!(
1050
+ "binding list should contain only variables or " <>
1051
+ "`{as, var}` tuples, got: #{Macro.to_string(bind)}"
1052
+ )
942
1053
943
1054
defp try_expansion(expr, type, params, vars, %Macro.Env{} = env) do
944
1055
try_expansion(expr, type, params, vars, {env, &escape/5})
 
@@ -947,7 +1058,7 @@ defmodule Ecto.Query.Builder do
947
1058
defp try_expansion(expr, type, params, vars, {env, fun}) do
948
1059
case Macro.expand_once(expr, env) do
949
1060
^expr ->
950
- error! """
1061
+ error!("""
951
1062
`#{Macro.to_string(expr)}` is not a valid query expression.
952
1063
953
1064
* If you intended to call an Elixir function or introduce a value,
 
@@ -959,7 +1070,7 @@ defmodule Ecto.Query.Builder do
959
1070
* If you intended to extend Ecto's query DSL, make sure that you have required
960
1071
the module or imported the relevant function. Note that you need macros to
961
1072
extend Ecto's querying capabilities
962
- """
1073
+ """)
963
1074
964
1075
expanded ->
965
1076
fun.(expanded, type, params, vars, env)
 
@@ -970,12 +1081,15 @@ defmodule Ecto.Query.Builder do
970
1081
Finds the index value for the given var in vars or raises.
971
1082
"""
972
1083
def find_var!(var, vars) do
973
- vars[var] || error! "unbound variable `#{var}` in query. If you are attempting to interpolate a value, use ^var"
1084
+ vars[var] ||
1085
+ error!(
1086
+ "unbound variable `#{var}` in query. If you are attempting to interpolate a value, use ^var"
1087
+ )
974
1088
end
975
1089
976
1090
@doc """
977
1091
Checks if the field is an atom at compilation time or
978
- delegate the check to runtime for interpolation.
1092
+ delegates the check to runtime for interpolation.
979
1093
"""
980
1094
def quoted_atom!({:^, _, [expr]}, used_ref),
981
1095
do: quote(do: Ecto.Query.Builder.atom!(unquote(expr), unquote(used_ref)))
 
@@ -987,7 +1101,7 @@ defmodule Ecto.Query.Builder do
987
1101
do:
988
1102
error!(
989
1103
"expected literal atom or interpolated value in #{used_ref}, got: " <>
990
- "`#{Macro.to_string(other)}`"
1104
+ "`#{Macro.to_string(other)}`"
991
1105
)
992
1106
993
1107
@doc """
 
@@ -997,7 +1111,7 @@ defmodule Ecto.Query.Builder do
997
1111
do: atom
998
1112
999
1113
def atom!(other, used_ref),
1000
- do: error!("expected atom in #{used_ref}, got: `#{inspect other}`")
1114
+ do: error!("expected atom in #{used_ref}, got: `#{inspect(other)}`")
1001
1115
1002
1116
@doc """
1003
1117
Checks if the value of a late binding is an interpolation or
 
@@ -1017,7 +1131,7 @@ defmodule Ecto.Query.Builder do
1017
1131
Enum.map(path, &quoted_json_path_element!/1)
1018
1132
end
1019
1133
1020
- defp escape_json_path({:^, _, [path]}) do
1134
+ defp escape_json_path({:^, _, [path]}) do
1021
1135
quote do
1022
1136
path = Ecto.Query.Builder.json_path!(unquote(path))
1023
1137
Enum.map(path, &Ecto.Query.Builder.json_path_element!/1)
 
@@ -1025,7 +1139,9 @@ defmodule Ecto.Query.Builder do
1025
1139
end
1026
1140
1027
1141
defp escape_json_path(other) do
1028
- error!("expected JSON path to be a literal list or interpolated value, got: `#{Macro.to_string(other)}`")
1142
+ error!(
1143
+ "expected JSON path to be a literal list or interpolated value, got: `#{Macro.to_string(other)}`"
1144
+ )
1029
1145
end
1030
1146
1031
1147
defp quoted_json_path_element!({:^, _, [expr]}),
 
@@ -1041,7 +1157,7 @@ defmodule Ecto.Query.Builder do
1041
1157
do:
1042
1158
error!(
1043
1159
"expected JSON path to contain literal strings, literal integers, or interpolated values, got: " <>
1044
- "`#{Macro.to_string(other)}`"
1160
+ "`#{Macro.to_string(other)}`"
1045
1161
)
1046
1162
1047
1163
@doc """
 
@@ -1049,26 +1165,31 @@ defmodule Ecto.Query.Builder do
1049
1165
"""
1050
1166
def json_path_element!(binary) when is_binary(binary),
1051
1167
do: binary
1168
+
1052
1169
def json_path_element!(integer) when is_integer(integer),
1053
1170
do: integer
1171
+
1054
1172
def json_path_element!(other),
1055
- do: error!("expected string or integer in json_extract_path/2, got: `#{inspect other}`")
1173
+ do: error!("expected string or integer in json_extract_path/2, got: `#{inspect(other)}`")
1056
1174
1057
1175
@doc """
1058
1176
Called by escaper at runtime to verify that path is a list
1059
1177
"""
1060
1178
def json_path!(path) when is_list(path),
1061
1179
do: path
1180
+
1062
1181
def json_path!(path),
1063
- do: error!("expected `path` to be a list in json_extract_path/2, got: `#{inspect path}`")
1182
+ do: error!("expected `path` to be a list in json_extract_path/2, got: `#{inspect(path)}`")
1064
1183
1065
1184
@doc """
1066
1185
Called by escaper at runtime to verify that a value is not nil.
1067
1186
"""
1068
1187
def not_nil!(nil) do
1069
- raise ArgumentError, "comparison with nil is forbidden as it is unsafe. " <>
1070
- "If you want to check if a value is nil, use is_nil/1 instead"
1188
+ raise ArgumentError,
1189
+ "comparison with nil is forbidden as it is unsafe. " <>
1190
+ "If you want to check if a value is nil, use is_nil/1 instead"
1071
1191
end
1192
+
1072
1193
def not_nil!(not_nil) do
1073
1194
not_nil
1074
1195
end
 
@@ -1079,6 +1200,7 @@ defmodule Ecto.Query.Builder do
1079
1200
"""
1080
1201
def quoted_interval!({:^, _, [expr]}),
1081
1202
do: quote(do: Ecto.Query.Builder.interval!(unquote(expr)))
1203
+
1082
1204
def quoted_interval!(other),
1083
1205
do: interval!(other)
1084
1206
 
@@ -1123,10 +1245,15 @@ defmodule Ecto.Query.Builder do
1123
1245
@interval ~w(year month week day hour minute second millisecond microsecond)
1124
1246
def interval!(interval) when interval in @interval,
1125
1247
do: interval
1248
+
1126
1249
def interval!(other_string) when is_binary(other_string),
1127
- do: error!("invalid interval: `#{inspect other_string}` (expected one of #{Enum.join(@interval, ", ")})")
1250
+ do:
1251
+ error!(
1252
+ "invalid interval: `#{inspect(other_string)}` (expected one of #{Enum.join(@interval, ", ")})"
1253
+ )
1254
+
1128
1255
def interval!(not_string),
1129
- do: error!("invalid interval: `#{inspect not_string}` (expected a string)")
1256
+ do: error!("invalid interval: `#{inspect(not_string)}` (expected a string)")
1130
1257
1131
1258
@doc """
1132
1259
Negates the given number.
 
@@ -1137,12 +1264,12 @@ defmodule Ecto.Query.Builder do
1137
1264
@doc """
1138
1265
Returns the type of an expression at build time.
1139
1266
"""
1140
- @spec quoted_type(Macro.t, Keyword.t) :: quoted_type
1267
+ @spec quoted_type(Macro.t(), Keyword.t()) :: quoted_type
1141
1268
1142
1269
# Fields
1143
1270
def quoted_type({{:., _, [{var, _, context}, field]}, _, []}, vars)
1144
- when is_atom(var) and is_atom(context) and is_atom(field),
1145
- do: {find_var!(var, vars), field}
1271
+ when is_atom(var) and is_atom(context) and is_atom(field),
1272
+ do: {find_var!(var, vars), field}
1146
1273
1147
1274
def quoted_type({{:., _, [{kind, _, [value]}, field]}, _, []}, _vars)
1148
1275
when kind in [:as, :parent_as] do
 
@@ -1151,8 +1278,8 @@ defmodule Ecto.Query.Builder do
1151
1278
end
1152
1279
1153
1280
def quoted_type({:field, _, [{var, _, context}, field]}, vars)
1154
- when is_atom(var) and is_atom(context) and is_atom(field),
1155
- do: {find_var!(var, vars), field}
1281
+ when is_atom(var) and is_atom(context) and is_atom(field),
1282
+ do: {find_var!(var, vars), field}
1156
1283
1157
1284
def quoted_type({:field, _, [{kind, _, [value]}, field]}, _vars)
1158
1285
when kind in [:as, :parent_as] and is_atom(field) do
 
@@ -1165,8 +1292,8 @@ defmodule Ecto.Query.Builder do
1165
1292
# in the query itself. We are assuming this is not an issue
1166
1293
# as the solution is somewhat complicated.
1167
1294
def quoted_type({:field, _, [{var, _, context}, {:^, _, [code]}]}, vars)
1168
- when is_atom(var) and is_atom(context),
1169
- do: {find_var!(var, vars), code}
1295
+ when is_atom(var) and is_atom(context),
1296
+ do: {find_var!(var, vars), code}
1170
1297
1171
1298
def quoted_type({:field, _, [{kind, _, [value]}, {:^, _, [code]}]}, _vars)
1172
1299
when kind in [:as, :parent_as] do
 
@@ -1184,7 +1311,9 @@ defmodule Ecto.Query.Builder do
1184
1311
1185
1312
# Sigils
1186
1313
def quoted_type({sigil, _, [_, []]}, _vars) when sigil in ~w(sigil_s sigil_S)a, do: :string
1187
- def quoted_type({sigil, _, [_, []]}, _vars) when sigil in ~w(sigil_w sigil_W)a, do: {:array, :string}
1314
+
1315
+ def quoted_type({sigil, _, [_, []]}, _vars) when sigil in ~w(sigil_w sigil_W)a,
1316
+ do: {:array, :string}
1188
1317
1189
1318
# Lists
1190
1319
def quoted_type(list, vars) when is_list(list) do
 
@@ -1208,8 +1337,8 @@ defmodule Ecto.Query.Builder do
1208
1337
end
1209
1338
1210
1339
# Literals
1211
- def quoted_type(literal, _vars) when is_float(literal), do: :float
1212
- def quoted_type(literal, _vars) when is_binary(literal), do: :string
1340
+ def quoted_type(literal, _vars) when is_float(literal), do: :float
1341
+ def quoted_type(literal, _vars) when is_binary(literal), do: :string
1213
1342
def quoted_type(literal, _vars) when is_boolean(literal), do: :boolean
1214
1343
def quoted_type(literal, _vars) when is_atom(literal) and not is_nil(literal), do: :atom
1215
1344
def quoted_type(literal, _vars) when is_integer(literal), do: :integer
 
@@ -1221,7 +1350,7 @@ defmodule Ecto.Query.Builder do
1221
1350
def quoted_type({name, _, args}, _vars) when is_atom(name) and is_list(args) do
1222
1351
case call_type(name, length(args)) do
1223
1352
{_in, out} -> out
1224
- nil -> :any
1353
+ nil -> :any
1225
1354
end
1226
1355
end
1227
1356
 
@@ -1230,18 +1359,23 @@ defmodule Ecto.Query.Builder do
1230
1359
defp get_env({env, _}), do: env
1231
1360
defp get_env(env), do: env
1232
1361
1362
+ defp normalize_type(value, :binary),
1363
+ do: quote(do: (is_binary(unquote(value)) && :binary) || :bitstring)
1364
+
1233
1365
@doc """
1234
1366
Raises a query building error.
1235
1367
"""
1236
1368
def error!(message) when is_binary(message) do
1237
- {:current_stacktrace, [_|t]} = Process.info(self(), :current_stacktrace)
1369
+ {:current_stacktrace, [_ | t]} = Process.info(self(), :current_stacktrace)
1238
1370
1239
- t = Enum.drop_while t, fn
1240
- {mod, _, _, _} ->
1241
- String.starts_with?(Atom.to_string(mod), ["Elixir.Ecto.Query.", "Elixir.Enum"])
1242
- _ ->
1243
- false
1244
- end
1371
+ t =
1372
+ Enum.drop_while(t, fn
1373
+ {mod, _, _, _} ->
1374
+ String.starts_with?(Atom.to_string(mod), ["Elixir.Ecto.Query.", "Elixir.Enum"])
1375
+
1376
+ _ ->
1377
+ false
1378
+ end)
1245
1379
1246
1380
reraise Ecto.Query.CompileError, [message: message], t
1247
1381
end
 
@@ -1255,7 +1389,7 @@ defmodule Ecto.Query.Builder do
1255
1389
4
1256
1390
1257
1391
"""
1258
- @spec count_binds(Ecto.Query.t) :: non_neg_integer
1392
+ @spec count_binds(Ecto.Query.t()) :: non_neg_integer
1259
1393
def count_binds(%Query{joins: joins}) do
1260
1394
1 + length(joins)
1261
1395
end
 
@@ -1294,7 +1428,9 @@ defmodule Ecto.Query.Builder do
1294
1428
def add_select_alias(aliases, name) when is_map(aliases) and is_atom(name) do
1295
1429
case aliases do
1296
1430
%{^name => _} ->
1297
- error! "the alias `#{inspect(name)}` has been specified more than once using `selected_as/2`"
1431
+ error!(
1432
+ "the alias `#{inspect(name)}` has been specified more than once using `selected_as/2`"
1433
+ )
1298
1434
1299
1435
aliases ->
1300
1436
Map.put(aliases, name, @select_alias_dummy_value)
 
@@ -1352,7 +1488,7 @@ defmodule Ecto.Query.Builder do
1352
1488
the query properly, but they will be in their runtime form
1353
1489
when invoked at runtime.
1354
1490
"""
1355
- @spec apply_query(Macro.t, Macro.t, Macro.t, Macro.Env.t) :: Macro.t
1491
+ @spec apply_query(Macro.t(), Macro.t(), Macro.t(), Macro.Env.t()) :: Macro.t()
1356
1492
def apply_query(query, module, args, env) do
1357
1493
case Macro.expand(query, env) |> unescape_query() do
1358
1494
%Query{} = compiletime_query ->
 
@@ -1369,10 +1505,11 @@ defmodule Ecto.Query.Builder do
1369
1505
end
1370
1506
1371
1507
# Unescapes an `Ecto.Query` struct.
1372
- @spec unescape_query(Macro.t) :: Query.t | Macro.t
1508
+ @spec unescape_query(Macro.t()) :: Query.t() | Macro.t()
1373
1509
defp unescape_query({:%, _, [Query, {:%{}, _, list}]}) do
1374
1510
struct(Query, list)
1375
1511
end
1512
+
1376
1513
defp unescape_query({:%{}, _, list} = ast) do
1377
1514
if List.keyfind(list, :__struct__, 0) == {:__struct__, Query} do
1378
1515
Map.new(list)
 
@@ -1380,12 +1517,13 @@ defmodule Ecto.Query.Builder do
1380
1517
ast
1381
1518
end
1382
1519
end
1520
+
1383
1521
defp unescape_query(other) do
1384
1522
other
1385
1523
end
1386
1524
1387
1525
# Escapes an `Ecto.Query` and associated structs.
1388
- @spec escape_query(Query.t) :: Macro.t
1526
+ @spec escape_query(Query.t()) :: Macro.t()
1389
1527
defp escape_query(%Query{} = query), do: {:%{}, [], Map.to_list(query)}
1390
1528
1391
1529
defp parse_access_get({{:., _, [Access, :get]}, _, [left, right]}, acc) do
changed lib/ecto/query/builder/distinct.ex
 
@@ -30,11 +30,20 @@ defmodule Ecto.Query.Builder.Distinct do
30
30
Called at runtime to verify distinct.
31
31
"""
32
32
def distinct!(query, distinct, file, line) when is_boolean(distinct) do
33
- apply(query, %Ecto.Query.QueryExpr{expr: distinct, params: [], line: line, file: file})
33
+ apply(query, %Ecto.Query.ByExpr{expr: distinct, params: [], line: line, file: file})
34
34
end
35
35
def distinct!(query, distinct, file, line) do
36
- {expr, params} = Builder.OrderBy.order_by_or_distinct!(:distinct, query, distinct, [])
37
- expr = %Ecto.Query.QueryExpr{expr: expr, params: Enum.reverse(params), line: line, file: file}
36
+ {expr, params, subqueries} =
37
+ Builder.OrderBy.order_by_or_distinct!(:distinct, query, distinct, [])
38
+
39
+ expr = %Ecto.Query.ByExpr{
40
+ expr: expr,
41
+ params: Enum.reverse(params),
42
+ line: line,
43
+ file: file,
44
+ subqueries: subqueries
45
+ }
46
+
38
47
apply(query, expr)
39
48
end
40
49
 
@@ -54,12 +63,13 @@ defmodule Ecto.Query.Builder.Distinct do
54
63
55
64
def build(query, binding, expr, env) do
56
65
{query, binding} = Builder.escape_binding(query, binding, env)
57
- {expr, {params, _acc}} = escape(expr, {[], %{}}, binding, env)
66
+ {expr, {params, acc}} = escape(expr, {[], %{subqueries: []}}, binding, env)
58
67
params = Builder.escape_params(params)
59
68
60
- distinct = quote do: %Ecto.Query.QueryExpr{
69
+ distinct = quote do: %Ecto.Query.ByExpr{
61
70
expr: unquote(expr),
62
71
params: unquote(params),
72
+ subqueries: unquote(acc.subqueries),
63
73
file: unquote(env.file),
64
74
line: unquote(env.line)}
65
75
Builder.apply_query(query, __MODULE__, [distinct], env)
changed lib/ecto/query/builder/dynamic.ex
 
@@ -31,6 +31,10 @@ defmodule Ecto.Query.Builder.Dynamic do
31
31
Select.escape(expr, vars, env)
32
32
end
33
33
34
+ defp escape({:%{}, _, _} = expr, _params_acc, vars, env) do
35
+ Select.escape(expr, vars, env)
36
+ end
37
+
34
38
defp escape(expr, params_acc, vars, env) do
35
39
Builder.escape(expr, :any, params_acc, vars, {env, &escape_expansion/5})
36
40
end
changed lib/ecto/query/builder/group_by.ex
 
@@ -49,21 +49,21 @@ defmodule Ecto.Query.Builder.GroupBy do
49
49
Shared between group_by and partition_by.
50
50
"""
51
51
def group_or_partition_by!(kind, query, exprs, params) do
52
- {expr, {params, _}} =
53
- Enum.map_reduce(List.wrap(exprs), {params, length(params)}, fn
52
+ {expr, {params, _, subqueries}} =
53
+ Enum.map_reduce(List.wrap(exprs), {params, length(params), []}, fn
54
54
field, params_count when is_atom(field) ->
55
55
{to_field(field), params_count}
56
56
57
- %Ecto.Query.DynamicExpr{} = dynamic, {params, count} ->
58
- {expr, params, count} = Builder.Dynamic.partially_expand(kind, query, dynamic, params, count)
59
- {expr, {params, count}}
57
+ %Ecto.Query.DynamicExpr{} = dynamic, {params, count, subqueries} ->
58
+ {expr, params, subqueries, _aliases, count} = Builder.Dynamic.partially_expand(query, dynamic, params, subqueries, %{}, count)
59
+ {expr, {params, count, subqueries}}
60
60
61
61
other, _params_count ->
62
62
raise ArgumentError,
63
63
"expected a list of fields and dynamics in `#{kind}`, got: `#{inspect other}`"
64
64
end)
65
65
66
- {expr, params}
66
+ {expr, params, subqueries}
67
67
end
68
68
69
69
defp to_field(field), do: {{:., [], [{:&, [], [0]}, field]}, [], []}
 
@@ -72,8 +72,8 @@ defmodule Ecto.Query.Builder.GroupBy do
72
72
Called at runtime to assemble group_by.
73
73
"""
74
74
def group_by!(query, group_by, file, line) do
75
- {expr, params} = group_or_partition_by!(:group_by, query, group_by, [])
76
- expr = %Ecto.Query.QueryExpr{expr: expr, params: Enum.reverse(params), line: line, file: file}
75
+ {expr, params, subqueries} = group_or_partition_by!(:group_by, query, group_by, [])
76
+ expr = %Ecto.Query.ByExpr{expr: expr, params: Enum.reverse(params), line: line, file: file, subqueries: subqueries}
77
77
apply(query, expr)
78
78
end
79
79
 
@@ -93,12 +93,13 @@ defmodule Ecto.Query.Builder.GroupBy do
93
93
94
94
def build(query, binding, expr, env) do
95
95
{query, binding} = Builder.escape_binding(query, binding, env)
96
- {expr, {params, _acc}} = escape(:group_by, expr, {[], %{}}, binding, env)
96
+ {expr, {params, acc}} = escape(:group_by, expr, {[], %{subqueries: []}}, binding, env)
97
97
params = Builder.escape_params(params)
98
98
99
- group_by = quote do: %Ecto.Query.QueryExpr{
99
+ group_by = quote do: %Ecto.Query.ByExpr{
100
100
expr: unquote(expr),
101
101
params: unquote(params),
102
+ subqueries: unquote(acc.subqueries),
102
103
file: unquote(env.file),
103
104
line: unquote(env.line)}
104
105
Builder.apply_query(query, __MODULE__, [group_by], env)
changed lib/ecto/query/builder/join.ex
 
@@ -254,7 +254,7 @@ defmodule Ecto.Query.Builder.Join do
254
254
255
255
defp ensure_on(on, _assoc, _qual, _source, _env) when on != nil, do: on
256
256
257
- defp ensure_on(nil, _assoc = nil, qual, source, env) when qual not in [:cross, :cross_lateral, :array, :left_array] do
257
+ defp ensure_on(nil, _assoc = nil, qual, source, env) when qual not in [:cross, :cross_lateral] do
258
258
maybe_source =
259
259
with {source, alias} <- source,
260
260
source when source != nil <- source || alias do
 
@@ -341,7 +341,7 @@ defmodule Ecto.Query.Builder.Join do
341
341
end
342
342
end
343
343
344
- @qualifiers [:inner, :inner_lateral, :left, :left_lateral, :right, :full, :cross, :cross_lateral, :array, :left_array]
344
+ @qualifiers [:inner, :inner_lateral, :left, :left_lateral, :right, :full, :cross, :cross_lateral]
345
345
346
346
@doc """
347
347
Called at runtime to check dynamic qualifier.
changed lib/ecto/query/builder/order_by.ex
 
@@ -51,6 +51,7 @@ defmodule Ecto.Query.Builder.OrderBy do
51
51
{Macro.t(), {list, term}}
52
52
def escape(kind, expr, params_acc, vars, env) do
53
53
expr
54
+ |> Macro.expand_once(get_env(env))
54
55
|> List.wrap()
55
56
|> Enum.map_reduce(params_acc, &do_escape(&1, &2, kind, vars, env))
56
57
end
 
@@ -83,6 +84,9 @@ defmodule Ecto.Query.Builder.OrderBy do
83
84
{{:asc, ast}, params_acc}
84
85
end
85
86
87
+ defp get_env({env, _}), do: env
88
+ defp get_env(env), do: env
89
+
86
90
@doc """
87
91
Checks the variable is a quoted direction at compilation time or
88
92
delegate the check to runtime for interpolation.
 
@@ -101,7 +105,7 @@ defmodule Ecto.Query.Builder.OrderBy do
101
105
end
102
106
103
107
@doc """
104
- Called by at runtime to verify the direction.
108
+ Called at runtime to verify the direction.
105
109
"""
106
110
def dir!(_kind, dir) when dir in @directions,
107
111
do: dir
 
@@ -136,8 +140,8 @@ defmodule Ecto.Query.Builder.OrderBy do
136
140
Shared between order_by and distinct.
137
141
"""
138
142
def order_by_or_distinct!(kind, query, exprs, params) do
139
- {expr, {params, _}} =
140
- Enum.map_reduce(List.wrap(exprs), {params, length(params)}, fn
143
+ {expr, {params, _, subqueries}} =
144
+ Enum.map_reduce(List.wrap(exprs), {params, length(params), []}, fn
141
145
{dir, expr}, params_count when dir in @directions ->
142
146
{expr, params} = dynamic_or_field!(kind, expr, query, params_count)
143
147
{{dir, expr}, params}
 
@@ -147,21 +151,35 @@ defmodule Ecto.Query.Builder.OrderBy do
147
151
{{:asc, expr}, params}
148
152
end)
149
153
150
- {expr, params}
154
+ {expr, params, subqueries}
151
155
end
152
156
153
157
@doc """
154
158
Called at runtime to assemble order_by.
155
159
"""
156
160
def order_by!(query, exprs, op, file, line) do
157
- {expr, params} = order_by_or_distinct!(:order_by, query, exprs, [])
158
- expr = %Ecto.Query.QueryExpr{expr: expr, params: Enum.reverse(params), line: line, file: file}
161
+ {expr, params, subqueries} = order_by_or_distinct!(:order_by, query, exprs, [])
162
+ expr = %Ecto.Query.ByExpr{expr: expr, params: Enum.reverse(params), line: line, file: file, subqueries: subqueries}
159
163
apply(query, expr, op)
160
164
end
161
165
162
- defp dynamic_or_field!(kind, %Ecto.Query.DynamicExpr{} = dynamic, query, {params, count}) do
163
- {expr, params, count} = Builder.Dynamic.partially_expand(kind, query, dynamic, params, count)
164
- {expr, {params, count}}
166
+ defp dynamic_or_field!(
167
+ _kind,
168
+ %Ecto.Query.DynamicExpr{} = dynamic,
169
+ query,
170
+ {params, count, subqueries}
171
+ ) do
172
+ {expr, params, subqueries, _aliases, count} =
173
+ Ecto.Query.Builder.Dynamic.partially_expand(
174
+ query,
175
+ dynamic,
176
+ params,
177
+ subqueries,
178
+ %{},
179
+ count
180
+ )
181
+
182
+ {expr, {params, count, subqueries}}
165
183
end
166
184
167
185
defp dynamic_or_field!(_kind, field, _query, params_count) when is_atom(field) do
 
@@ -196,13 +214,14 @@ defmodule Ecto.Query.Builder.OrderBy do
196
214
197
215
def build(query, binding, expr, op, env) do
198
216
{query, binding} = Builder.escape_binding(query, binding, env)
199
- {expr, {params, _acc}} = escape(:order_by, expr, {[], %{}}, binding, env)
217
+ {expr, {params, acc}} = escape(:order_by, expr, {[], %{subqueries: []}}, binding, env)
200
218
params = Builder.escape_params(params)
201
219
202
220
order_by =
203
- quote do: %Ecto.Query.QueryExpr{
221
+ quote do: %Ecto.Query.ByExpr{
204
222
expr: unquote(expr),
205
223
params: unquote(params),
224
+ subqueries: unquote(acc.subqueries),
206
225
file: unquote(env.file),
207
226
line: unquote(env.line)
208
227
}
changed lib/ecto/query/builder/preload.ex
 
@@ -302,7 +302,7 @@ defmodule Ecto.Query.Builder.Preload do
302
302
raise ArgumentError,
303
303
"invalid preload for key `#{inspect(key)}`: #{inspect(other)}. " <>
304
304
"Preloads can be a query, a function (arity 1), or a dynamic that " <>
305
- "evalutes to a single binding."
305
+ "evaluates to a single binding."
306
306
end
307
307
308
308
defp assert_assoc!(mode, _atom) when mode in [:both, :assoc], do: :ok
changed lib/ecto/query/builder/select.ex
 
@@ -74,9 +74,9 @@ defmodule Ecto.Query.Builder.Select do
74
74
75
75
# Map
76
76
defp escape({:%{}, _, [{:|, _, [data, pairs]}]}, params_acc, vars, env) do
77
- {data, params_acc} = escape(data, params_acc, vars, env)
78
- {pairs, params_acc} = escape_pairs(pairs, params_acc, vars, env)
79
- {{:{}, [], [:%{}, [], [{:{}, [], [:|, [], [data, pairs]]}]]}, params_acc}
77
+ {escaped_data, params_acc} = escape(data, params_acc, vars, env)
78
+ {pairs, params_acc} = escape_pairs(pairs, data, params_acc, vars, env)
79
+ {{:{}, [], [:%{}, [], [{:{}, [], [:|, [], [escaped_data, pairs]]}]]}, params_acc}
80
80
end
81
81
82
82
# Merge
 
@@ -93,7 +93,7 @@ defmodule Ecto.Query.Builder.Select do
93
93
94
94
# Map
95
95
defp escape({:%{}, _, pairs}, params_acc, vars, env) do
96
- {pairs, params_acc} = escape_pairs(pairs, params_acc, vars, env)
96
+ {pairs, params_acc} = escape_pairs(pairs, nil, params_acc, vars, env)
97
97
{{:{}, [], [:%{}, [], pairs]}, params_acc}
98
98
end
99
99
 
@@ -128,14 +128,21 @@ defmodule Ecto.Query.Builder.Select do
128
128
escape(expr, params_acc, vars, env)
129
129
end
130
130
131
- defp escape_pairs(pairs, params_acc, vars, env) do
131
+ defp escape_pairs(pairs, update_data, params_acc, vars, env) do
132
132
Enum.map_reduce(pairs, params_acc, fn {k, v}, acc ->
133
+ v = tag_update_param(update_data, k, v)
133
134
{k, acc} = escape_key(k, acc, vars, env)
134
135
{v, acc} = escape(v, acc, vars, env)
135
136
{{k, v}, acc}
136
137
end)
137
138
end
138
139
140
+ defp tag_update_param({var, _, context}, field, {:^, _,[_]} = param) when is_atom(var) and is_atom(context) do
141
+ {:type, [], [param, {{:., [], [{var, [], context}, field]}, [], []}]}
142
+ end
143
+
144
+ defp tag_update_param(_, _, value), do: value
145
+
139
146
defp escape_key(k, params_acc, _vars, _env) when is_atom(k) do
140
147
{k, params_acc}
141
148
end
 
@@ -392,7 +399,7 @@ defmodule Ecto.Query.Builder.Select do
392
399
{:merge, [], [old_expr, new_expr]}
393
400
end
394
401
395
- {{:map, meta, old_fields}, {:map, _, new_fields}} when old_params == [] ->
402
+ {{:map, meta, old_fields}, {:map, _, new_fields}} ->
396
403
cond do
397
404
old_fields == [] ->
398
405
new_expr
 
@@ -400,11 +407,16 @@ defmodule Ecto.Query.Builder.Select do
400
407
new_fields == [] ->
401
408
old_expr
402
409
403
- Keyword.keyword?(old_fields) and Keyword.keyword?(new_fields) ->
404
- {:%{}, meta, Keyword.merge(old_fields, new_fields)}
405
-
406
410
true ->
407
- {:merge, [], [old_expr, new_expr]}
411
+ require_distinct_keys? = old_params != []
412
+
413
+ case merge_map_fields(old_fields, new_fields, require_distinct_keys?) do
414
+ fields when is_list(fields) ->
415
+ {:%{}, meta, fields}
416
+
417
+ :error ->
418
+ {:merge, [], [old_expr, new_expr]}
419
+ end
408
420
end
409
421
410
422
{_, {:map, _, _}} ->
 
@@ -463,6 +475,35 @@ defmodule Ecto.Query.Builder.Select do
463
475
:error
464
476
end
465
477
478
+ defp merge_map_fields(old_fields, new_fields, false) do
479
+ if Keyword.keyword?(old_fields) and Keyword.keyword?(new_fields) do
480
+ Keyword.merge(old_fields, new_fields)
481
+ else
482
+ :error
483
+ end
484
+ end
485
+
486
+ defp merge_map_fields(old_fields, new_fields, true) when is_list(old_fields) do
487
+ if Keyword.keyword?(new_fields) do
488
+ valid? =
489
+ Enum.reduce_while(old_fields, true, fn
490
+ {k, _v}, _ when is_atom(k) ->
491
+ if Keyword.has_key?(new_fields, k),
492
+ do: {:halt, false},
493
+ else: {:cont, true}
494
+
495
+ _, _ ->
496
+ {:halt, false}
497
+ end)
498
+
499
+ if valid?, do: old_fields ++ new_fields, else: :error
500
+ else
501
+ :error
502
+ end
503
+ end
504
+
505
+ defp merge_map_fields(_, _, true), do: :error
506
+
466
507
defp merge_argument_to_error({:&, _, [0]}, %{from: %{source: {source, alias}}}) do
467
508
"source #{inspect(source || alias)}"
468
509
end
changed lib/ecto/query/builder/update.ex
 
@@ -92,7 +92,7 @@ defmodule Ecto.Query.Builder.Update do
92
92
93
93
defp compile_error!(expr) do
94
94
Builder.error! "malformed update `#{Macro.to_string(expr)}` in query expression, " <>
95
- "expected a keyword list with set/push/pop as keys with field-value " <>
95
+ "expected a keyword list with set/push/pull as keys with field-value " <>
96
96
"pairs as values"
97
97
end
98
98
 
@@ -186,7 +186,7 @@ defmodule Ecto.Query.Builder.Update do
186
186
defp runtime_error!(value) do
187
187
raise ArgumentError,
188
188
"malformed update `#{inspect(value)}` in query expression, " <>
189
- "expected a keyword list with set/push/pop as keys with field-value pairs as values"
189
+ "expected a keyword list with set/push/pull as keys with field-value pairs as values"
190
190
end
191
191
192
192
defp validate_op!(key) when key in @keys, do: :ok
changed lib/ecto/query/builder/windows.ex
 
@@ -125,24 +125,25 @@ defmodule Ecto.Query.Builder.Windows do
125
125
end
126
126
127
127
defp escape_window(vars, {name, expr}, env) do
128
- {compile_acc, runtime_acc, {params, _acc}} = escape(expr, {[], %{}}, vars, env)
129
- {name, compile_acc, runtime_acc, Builder.escape_params(params)}
128
+ {compile_acc, runtime_acc, {params, acc}} = escape(expr, {[], %{subqueries: []}}, vars, env)
129
+ {name, compile_acc, runtime_acc, Builder.escape_params(params), acc}
130
130
end
131
131
132
- defp build_compile_window({name, compile_acc, _, params}, env) do
132
+ defp build_compile_window({name, compile_acc, _, params, acc}, env) do
133
133
{name,
134
134
quote do
135
- %Ecto.Query.QueryExpr{
135
+ %Ecto.Query.ByExpr{
136
136
expr: unquote(compile_acc),
137
137
params: unquote(params),
138
+ subqueries: unquote(acc.subqueries),
138
139
file: unquote(env.file),
139
140
line: unquote(env.line)
140
141
}
141
142
end}
142
143
end
143
144
144
- defp build_runtime_window({name, compile_acc, runtime_acc, params}, _env) do
145
- {:{}, [], [name, Enum.reverse(compile_acc), runtime_acc, Enum.reverse(params)]}
145
+ defp build_runtime_window({name, compile_acc, runtime_acc, params, acc}, _env) do
146
+ {:{}, [], [name, Enum.reverse(compile_acc), runtime_acc, Enum.reverse(params), {:%{}, [], Map.to_list(acc)}]}
146
147
end
147
148
148
149
@doc """
 
@@ -150,30 +151,32 @@ defmodule Ecto.Query.Builder.Windows do
150
151
"""
151
152
def runtime!(query, runtime, file, line) do
152
153
windows =
153
- Enum.map(runtime, fn {name, compile_acc, runtime_acc, params} ->
154
- {acc, params} = do_runtime_window!(runtime_acc, query, compile_acc, params)
155
- expr = %Ecto.Query.QueryExpr{expr: Enum.reverse(acc), params: Enum.reverse(params), file: file, line: line}
154
+ Enum.map(runtime, fn {name, compile_acc, runtime_acc, params, escape_acc} ->
155
+ {{acc, subqueries}, params} = do_runtime_window!(runtime_acc, query, {compile_acc, escape_acc.subqueries}, params)
156
+ expr = %Ecto.Query.ByExpr{expr: Enum.reverse(acc), params: Enum.reverse(params), file: file, line: line, subqueries: subqueries}
156
157
{name, expr}
157
158
end)
158
159
159
160
apply(query, windows)
160
161
end
161
162
162
- defp do_runtime_window!([{:order_by, order_by} | kw], query, acc, params) do
163
- {order_by, params} = OrderBy.order_by_or_distinct!(:order_by, query, order_by, params)
164
- do_runtime_window!(kw, query, [{:order_by, order_by} | acc], params)
163
+ defp do_runtime_window!([{:order_by, order_by} | kw], query, {acc, subqueries_acc}, params) do
164
+ {order_by, params, subqueries} = OrderBy.order_by_or_distinct!(:order_by, query, order_by, params)
165
+
166
+ do_runtime_window!(kw, query, {[{:order_by, order_by} | acc], subqueries_acc ++ subqueries}, params)
165
167
end
166
168
167
- defp do_runtime_window!([{:partition_by, partition_by} | kw], query, acc, params) do
168
- {partition_by, params} = GroupBy.group_or_partition_by!(:partition_by, query, partition_by, params)
169
- do_runtime_window!(kw, query, [{:partition_by, partition_by} | acc], params)
169
+ defp do_runtime_window!([{:partition_by, partition_by} | kw], query, {acc, subqueries_acc}, params) do
170
+ {partition_by, params, subqueries} = GroupBy.group_or_partition_by!(:partition_by, query, partition_by, params)
171
+
172
+ do_runtime_window!(kw, query, {[{:partition_by, partition_by} | acc], subqueries_acc ++ subqueries}, params)
170
173
end
171
174
172
- defp do_runtime_window!([{:frame, frame} | kw], query, acc, params) do
175
+ defp do_runtime_window!([{:frame, frame} | kw], query, {acc, subqueries_acc}, params) do
173
176
case frame do
174
177
%Ecto.Query.DynamicExpr{} ->
175
178
{frame, params, _count} = Builder.Dynamic.partially_expand(:windows, query, frame, params, length(params))
176
- do_runtime_window!(kw, query, [{:frame, frame} | acc], params)
179
+ do_runtime_window!(kw, query, {[{:frame, frame} | acc], subqueries_acc}, params)
177
180
178
181
_ ->
179
182
raise ArgumentError,
changed lib/ecto/query/inspect.ex
 
@@ -14,7 +14,7 @@ defimpl Inspect, for: Ecto.Query.DynamicExpr do
14
14
aliases =
15
15
for({as, _} when is_atom(as) <- binding, do: as)
16
16
|> Enum.with_index()
17
- |> Map.new
17
+ |> Map.new()
18
18
19
19
query = %Ecto.Query{joins: joins, aliases: aliases}
20
20
 
@@ -54,12 +54,19 @@ defimpl Inspect, for: Ecto.Query do
54
54
%WithExpr{recursive: recursive, queries: [_ | _] = queries} ->
55
55
with_ctes =
56
56
Enum.map(queries, fn {name, cte_opts, query} ->
57
- cte = case query do
58
- %Ecto.Query{} -> __MODULE__.inspect(query, opts)
59
- %Ecto.Query.QueryExpr{} -> expr(query, {})
60
- end
57
+ cte =
58
+ case query do
59
+ %Ecto.Query{} -> __MODULE__.inspect(query, opts)
60
+ %Ecto.Query.QueryExpr{} -> expr(query, {})
61
+ end
61
62
62
- concat(["|> with_cte(\"" <> name <> "\", materialized: ", inspect(cte_opts[:materialized]), ", as: ", cte, ")"])
63
+ concat([
64
+ "|> with_cte(\"" <> name <> "\", materialized: ",
65
+ inspect(cte_opts[:materialized]),
66
+ ", as: ",
67
+ cte,
68
+ ")"
69
+ ])
63
70
end)
64
71
65
72
result = if recursive, do: glue(result, "\n", "|> recursive_ctes(true)"), else: result
 
@@ -135,10 +142,15 @@ defimpl Inspect, for: Ecto.Query do
135
142
end
136
143
137
144
defp inspect_source(%{source: %Ecto.Query{} = query}, _names), do: "^" <> inspect(query)
138
- defp inspect_source(%{source: %Ecto.SubQuery{query: query}}, _names), do: "subquery(#{to_string(query)})"
145
+
146
+ defp inspect_source(%{source: %Ecto.SubQuery{query: query}}, _names),
147
+ do: "subquery(#{to_string(query)})"
148
+
139
149
defp inspect_source(%{source: {source, nil}}, _names), do: inspect(source)
140
150
defp inspect_source(%{source: {nil, schema}}, _names), do: inspect(schema)
141
- defp inspect_source(%{source: {:fragment, _, _} = source} = part, names), do: "#{expr(source, names, part)}"
151
+
152
+ defp inspect_source(%{source: {:fragment, _, _} = source} = part, names),
153
+ do: "#{expr(source, names, part)}"
142
154
143
155
defp inspect_source(%{source: {:values, _, [types | _]}}, _names) do
144
156
fields = Keyword.keys(types)
 
@@ -233,40 +245,27 @@ defimpl Inspect, for: Ecto.Query do
233
245
@doc false
234
246
def expr(expr, names, part) do
235
247
expr
236
- |> Macro.traverse(:ok, &{prewalk(&1), &2}, &{postwalk(&1, names, part), &2})
248
+ |> Macro.traverse(:ok, &{prewalk(&1, names), &2}, &{postwalk(&1, names, part), &2})
237
249
|> elem(0)
238
250
|> macro_to_string()
239
251
end
240
252
241
- if Version.match?(System.version(), ">= 1.11.0") do
242
- defp macro_to_string(expr), do: Macro.to_string(expr)
243
- else
244
- defp macro_to_string(expr) do
245
- Macro.to_string(expr, fn
246
- {{:., _, [_, _]}, _, []}, string -> String.replace_suffix(string, "()", "")
247
- _other, string -> string
248
- end)
249
- end
250
- end
253
+ defp macro_to_string(expr), do: Macro.to_string(expr)
251
254
252
255
# Tagged values
253
- defp prewalk(%Ecto.Query.Tagged{value: value, tag: nil}) do
256
+ defp prewalk(%Ecto.Query.Tagged{value: value, tag: nil}, _) do
254
257
value
255
258
end
256
259
257
- defp prewalk(%Ecto.Query.Tagged{value: value, tag: {:parameterized, type, opts}}) do
258
- {:type, [], [value, {:{}, [], [:parameterized, type, opts]}]}
259
- end
260
-
261
- defp prewalk(%Ecto.Query.Tagged{value: value, tag: tag}) do
260
+ defp prewalk(%Ecto.Query.Tagged{value: value, tag: tag}, _) do
262
261
{:type, [], [value, tag]}
263
262
end
264
263
265
- defp prewalk({:type, _, [value, {:parameterized, type, opts}]}) do
266
- {:type, [], [value, {:{}, [], [:parameterized, type, opts]}]}
264
+ defp prewalk({{:., dot_meta, [{:&, _, [ix]}, field]}, meta, []}, names) do
265
+ {{:., dot_meta, [binding(names, ix), field]}, meta, []}
267
266
end
268
267
269
- defp prewalk(node) do
268
+ defp prewalk(node, _) do
270
269
node
271
270
end
272
271
 
@@ -337,10 +336,6 @@ defimpl Inspect, for: Ecto.Query do
337
336
end
338
337
end
339
338
340
- defp type_to_expr({:parameterized, type, opts}, _names, _part) do
341
- {:{}, [], [:parameterized, type, opts]}
342
- end
343
-
344
339
defp type_to_expr({ix, type}, names, part) when is_integer(ix) do
345
340
{{:., [], [binding_to_expr(ix, names, part), type]}, [no_parens: true], []}
346
341
end
 
@@ -369,8 +364,6 @@ defimpl Inspect, for: Ecto.Query do
369
364
defp join_qual(:full), do: :full_join
370
365
defp join_qual(:cross), do: :cross_join
371
366
defp join_qual(:cross_lateral), do: :cross_lateral_join
372
- defp join_qual(:array), do: :array_join
373
- defp join_qual(:left_array), do: :left_array_join
374
367
375
368
defp collect_sources(%{from: nil, joins: joins}) do
376
369
["query" | join_sources(joins)]
changed lib/ecto/query/planner.ex
 
@@ -2,7 +2,16 @@ defmodule Ecto.Query.Planner do
2
2
# Normalizes a query and its parameters.
3
3
@moduledoc false
4
4
5
- alias Ecto.Query.{BooleanExpr, DynamicExpr, FromExpr, JoinExpr, QueryExpr, SelectExpr, LimitExpr}
5
+ alias Ecto.Query.{
6
+ BooleanExpr,
7
+ ByExpr,
8
+ DynamicExpr,
9
+ FromExpr,
10
+ JoinExpr,
11
+ QueryExpr,
12
+ SelectExpr,
13
+ LimitExpr
14
+ }
6
15
7
16
if map_size(%Ecto.Query{}) != 21 do
8
17
raise "Ecto.Query match out of date in builder"
 
@@ -29,7 +38,7 @@ defmodule Ecto.Query.Planner do
29
38
last = length(joins) + position
30
39
31
40
mapping = fn
32
- 0 -> last
41
+ 0 -> last
33
42
ix -> ix + position - 1
34
43
end
35
44
 
@@ -38,8 +47,12 @@ defmodule Ecto.Query.Planner do
38
47
end
39
48
end
40
49
41
- defp merge_expr_and_params(op, %QueryExpr{expr: left_expr, params: left_params} = struct,
42
- right_expr, right_params) do
50
+ defp merge_expr_and_params(
51
+ op,
52
+ %QueryExpr{expr: left_expr, params: left_params} = struct,
53
+ right_expr,
54
+ right_params
55
+ ) do
43
56
right_expr = Ecto.Query.Builder.bump_interpolations(right_expr, left_params)
44
57
%{struct | expr: merge_expr(op, left_expr, right_expr), params: left_params ++ right_params}
45
58
end
 
@@ -53,22 +66,25 @@ defmodule Ecto.Query.Planner do
53
66
"""
54
67
def rewrite_sources(%{expr: expr, params: params} = part, mapping) do
55
68
expr =
56
- Macro.prewalk expr, fn
69
+ Macro.prewalk(expr, fn
57
70
%Ecto.Query.Tagged{type: type, tag: tag} = tagged ->
58
71
%{tagged | type: rewrite_type(type, mapping), tag: rewrite_type(tag, mapping)}
72
+
59
73
{:&, meta, [ix]} ->
60
74
{:&, meta, [mapping.(ix)]}
75
+
61
76
other ->
62
77
other
63
- end
78
+ end)
64
79
65
80
params =
66
- Enum.map params, fn
81
+ Enum.map(params, fn
67
82
{val, type} ->
68
83
{val, rewrite_type(type, mapping)}
84
+
69
85
val ->
70
86
val
71
- end
87
+ end)
72
88
73
89
%{part | expr: expr, params: params}
74
90
end
 
@@ -127,10 +143,12 @@ defmodule Ecto.Query.Planner do
127
143
case query_lookup(key, query, operation, cache, adapter, counter) do
128
144
{_, select, prepared} ->
129
145
{build_meta(query, select), {:nocache, prepared}, cast_params, dump_params}
146
+
130
147
{_key, :cached, select, cached} ->
131
148
update = &cache_update(cache, key, &1)
132
149
reset = &cache_reset(cache, key, &1)
133
150
{build_meta(query, select), {:cached, update, reset, cached}, cast_params, dump_params}
151
+
134
152
{_key, :cache, select, prepared} ->
135
153
update = &cache_update(cache, key, &1)
136
154
{build_meta(query, select), {:cache, update, prepared}, cast_params, dump_params}
 
@@ -152,6 +170,7 @@ defmodule Ecto.Query.Planner do
152
170
case query_without_cache(query, operation, adapter, counter) do
153
171
{:cache, select, prepared} ->
154
172
cache_insert(cache, key, {key, :cache, select, prepared})
173
+
155
174
{:nocache, _, _} = nocache ->
156
175
nocache
157
176
end
 
@@ -161,6 +180,7 @@ defmodule Ecto.Query.Planner do
161
180
case :ets.insert_new(cache, elem) do
162
181
true ->
163
182
elem
183
+
164
184
false ->
165
185
[elem] = :ets.lookup(cache, key)
166
186
elem
 
@@ -197,77 +217,117 @@ defmodule Ecto.Query.Planner do
197
217
This function is called by the backend before invoking
198
218
any cache mechanism.
199
219
"""
200
- @spec plan(Ecto.Query.t, atom, module) :: {planned_query :: Ecto.Query.t, parameters :: list, cache_key :: any}
201
- def plan(query, operation, adapter) do
220
+ @spec plan(Ecto.Query.t(), atom(), module, map()) ::
221
+ {planned_query :: Ecto.Query.t(), parameters :: list(), cache_key :: any()}
222
+ def plan(query, operation, adapter, cte_names \\ %{}) do
223
+ {query, cte_names} = plan_ctes(query, adapter, cte_names)
224
+ plan_subquery = &plan_subquery(&1, query, nil, adapter, false, cte_names)
225
+
202
226
query
203
- |> plan_sources(adapter)
227
+ |> plan_sources(adapter, cte_names)
204
228
|> plan_assocs()
205
- |> plan_combinations(adapter)
206
- |> plan_ctes(adapter)
207
- |> plan_wheres(adapter)
208
- |> plan_select(adapter)
229
+ |> plan_combinations(adapter, cte_names)
230
+ |> plan_expr_subqueries(:wheres, plan_subquery)
231
+ |> plan_expr_subqueries(:havings, plan_subquery)
232
+ |> plan_expr_subqueries(:order_bys, plan_subquery)
233
+ |> plan_expr_subqueries(:group_bys, plan_subquery)
234
+ |> plan_expr_subquery(:distinct, plan_subquery)
235
+ |> plan_expr_subquery(:select, plan_subquery)
236
+ |> plan_windows(plan_subquery)
209
237
|> plan_cache(operation, adapter)
210
238
rescue
211
239
e ->
212
240
# Reraise errors so we ignore the planner inner stacktrace
213
- filter_and_reraise e, __STACKTRACE__
241
+ filter_and_reraise(e, __STACKTRACE__)
214
242
end
215
243
216
244
@doc """
217
245
Prepare all sources, by traversing and expanding from, joins, subqueries.
218
246
"""
219
- def plan_sources(query, adapter) do
220
- {from, source} = plan_from(query, adapter)
247
+ def plan_sources(query, adapter, cte_names) do
248
+ {from, source} = plan_from(query, adapter, cte_names)
221
249
222
250
# Set up the initial source so we can refer
223
251
# to the parent in subqueries in joins
224
252
query = %{query | sources: {source}}
225
253
226
- {joins, sources, tail_sources} = plan_joins(query, [source], length(query.joins), adapter)
254
+ {joins, sources, tail_sources} =
255
+ plan_joins(query, [source], length(query.joins), adapter, cte_names)
227
256
228
- %{query | from: from,
229
- joins: joins |> Enum.reverse,
230
- sources: (tail_sources ++ sources) |> Enum.reverse |> List.to_tuple()}
257
+ %{
258
+ query
259
+ | from: from,
260
+ joins: joins |> Enum.reverse(),
261
+ sources: (tail_sources ++ sources) |> Enum.reverse() |> List.to_tuple()
262
+ }
231
263
end
232
264
233
- defp plan_from(%{from: nil} = query, _adapter) do
265
+ defp plan_from(%{from: nil} = query, _adapter, _cte_names) do
234
266
error!(query, "query must have a from expression")
235
267
end
236
268
237
- defp plan_from(%{from: %{source: {kind, _, _}}, preloads: preloads, assocs: assocs} = query, _adapter)
269
+ defp plan_from(
270
+ %{from: %{source: {kind, _, _}}, preloads: preloads, assocs: assocs} = query,
271
+ _adapter,
272
+ _cte_names
273
+ )
238
274
when kind in [:fragment, :values] and (assocs != [] or preloads != []) do
239
275
error!(query, "cannot preload associations with a #{kind} source")
240
276
end
241
277
242
- defp plan_from(%{from: from} = query, adapter) do
243
- plan_source(query, from, adapter)
278
+ defp plan_from(%{from: from} = query, adapter, cte_names) do
279
+ plan_source(query, from, adapter, cte_names)
244
280
end
245
281
246
- defp plan_source(query, %{source: %Ecto.SubQuery{} = subquery, prefix: prefix} = expr, adapter) do
247
- subquery = plan_subquery(subquery, query, prefix, adapter, true)
282
+ defp plan_source(
283
+ query,
284
+ %{source: %Ecto.SubQuery{} = subquery, prefix: prefix} = expr,
285
+ adapter,
286
+ cte_names
287
+ ) do
288
+ subquery = plan_subquery(subquery, query, prefix, adapter, true, cte_names)
248
289
{%{expr | source: subquery}, subquery}
249
290
end
250
291
251
- defp plan_source(query, %{source: {nil, schema}} = expr, _adapter)
292
+ defp plan_source(query, %{source: {nil, schema}} = expr, _adapter, cte_names)
252
293
when is_atom(schema) and schema != nil do
253
294
source = schema.__schema__(:source)
254
- prefix = plan_source_schema_prefix(expr, schema) || query.prefix
295
+ source_prefix = plan_source_schema_prefix(expr, schema)
296
+
297
+ prefix =
298
+ case cte_names do
299
+ %{^source => _} -> source_prefix
300
+ _ -> source_prefix || query.prefix
301
+ end
302
+
255
303
{%{expr | source: {source, schema}}, {source, schema, prefix}}
256
304
end
257
305
258
- defp plan_source(query, %{source: {source, schema}, prefix: prefix} = expr, _adapter)
259
- when is_binary(source) and is_atom(schema),
260
- do: {expr, {source, schema, prefix || query.prefix}}
306
+ defp plan_source(query, %{source: {source, schema}, prefix: prefix} = expr, _adapter, cte_names)
307
+ when is_binary(source) and is_atom(schema) do
308
+ prefix =
309
+ case cte_names do
310
+ %{^source => _} -> prefix
311
+ _ -> prefix || query.prefix
312
+ end
261
313
262
- defp plan_source(_query, %{source: {kind, _, _} = source, prefix: nil} = expr, _adapter)
314
+ {expr, {source, schema, prefix}}
315
+ end
316
+
317
+ defp plan_source(
318
+ _query,
319
+ %{source: {kind, _, _} = source, prefix: nil} = expr,
320
+ _adapter,
321
+ _cte_names
322
+ )
263
323
when kind in [:fragment, :values],
264
324
do: {expr, source}
265
325
266
- defp plan_source(query, %{source: {kind, _, _}, prefix: prefix} = expr, _adapter)
326
+ defp plan_source(query, %{source: {kind, _, _}, prefix: prefix} = expr, _adapter, _cte_names)
267
327
when kind in [:fragment, :values],
268
328
do: error!(query, expr, "cannot set prefix: #{inspect(prefix)} option for #{kind} sources")
269
329
270
- defp plan_subquery(subquery, query, prefix, adapter, source?) do
330
+ defp plan_subquery(subquery, query, prefix, adapter, source?, cte_names) do
271
331
%{query: inner_query} = subquery
272
332
273
333
inner_query = %{
 
@@ -276,7 +336,7 @@ defmodule Ecto.Query.Planner do
276
336
aliases: Map.put(inner_query.aliases, @parent_as, query)
277
337
}
278
338
279
- {inner_query, params, key} = plan(inner_query, :all, adapter)
339
+ {inner_query, params, key} = plan(inner_query, :all, adapter, cte_names)
280
340
assert_no_subquery_assocs!(inner_query)
281
341
282
342
{inner_query, select} =
 
@@ -307,16 +367,26 @@ defmodule Ecto.Query.Planner do
307
367
end
308
368
309
369
defp normalize_subquery_select(query, adapter, source?) do
310
- {schema_or_source, expr, %{select: select} = query} = rewrite_subquery_select_expr(query, source?)
311
- {expr, _} = prewalk(expr, :select, query, select, 0, adapter)
312
- {{:map, types}, fields, _from} = collect_fields(expr, [], :none, query, select.take, true, %{})
370
+ {schema_or_source, expr, %{select: select} = query} =
371
+ rewrite_subquery_select_expr(query, source?)
372
+
373
+ {expr, counter} = prewalk(expr, :select, query, select, 0, adapter)
374
+
375
+ if counter != length(select.params) do
376
+ error!(query, "subqueries cannot select_merge onto an existing field that has an interpolation")
377
+ end
378
+
379
+ {{:map, types}, fields, _from} =
380
+ collect_fields(expr, [], :none, query, select.take, true, %{})
381
+
313
382
# types must take into account selected_as/2 aliases so that the correct fields are
314
383
# referenced when the outer query selects the entire subquery
315
384
types = normalize_subquery_types(types, Enum.reverse(fields), query.select.aliases, [])
316
385
{query, subquery_source(schema_or_source, types)}
317
386
end
318
387
319
- defp normalize_subquery_types(types, _fields, select_aliases, _acc) when select_aliases == %{} do
388
+ defp normalize_subquery_types(types, _fields, select_aliases, _acc)
389
+ when select_aliases == %{} do
320
390
types
321
391
end
322
392
 
@@ -324,11 +394,21 @@ defmodule Ecto.Query.Planner do
324
394
Enum.reverse(acc)
325
395
end
326
396
327
- defp normalize_subquery_types([{alias, _} = type | types], [{alias, _} | fields], select_aliases, acc) do
397
+ defp normalize_subquery_types(
398
+ [{alias, _} = type | types],
399
+ [{alias, _} | fields],
400
+ select_aliases,
401
+ acc
402
+ ) do
328
403
normalize_subquery_types(types, fields, select_aliases, [type | acc])
329
404
end
330
405
331
- defp normalize_subquery_types([{source_alias, type_value} | types], [field | fields], select_aliases, acc) do
406
+ defp normalize_subquery_types(
407
+ [{source_alias, type_value} | types],
408
+ [field | fields],
409
+ select_aliases,
410
+ acc
411
+ ) do
332
412
if Map.has_key?(select_aliases, source_alias) do
333
413
raise ArgumentError, """
334
414
the alias, #{inspect(source_alias)}, provided to `selected_as/2` conflicts
 
@@ -354,8 +434,11 @@ defmodule Ecto.Query.Planner do
354
434
355
435
defp subquery_source(nil, types), do: {:map, types}
356
436
defp subquery_source(name, types) when is_atom(name), do: {:struct, name, types}
437
+
357
438
defp subquery_source({:source, schema, prefix, types}, only) do
358
- types = Enum.map(only, fn {field, {:value, type}} -> {field, Keyword.get(types, field, type)} end)
439
+ types =
440
+ Enum.map(only, fn {field, {:value, type}} -> {field, Keyword.get(types, field, type)} end)
441
+
359
442
{:source, schema, prefix, types}
360
443
end
361
444
 
@@ -368,7 +451,10 @@ defmodule Ecto.Query.Planner do
368
451
{schema_or_source, expr, put_in(query.select.expr, expr)}
369
452
370
453
:error when source? ->
371
- error!(query, "subquery/cte must select a source (t), a field (t.field) or a map, got: `#{Macro.to_string(expr)}`")
454
+ error!(
455
+ query,
456
+ "subquery/cte must select a source (t), a field (t.field) or a map, got: `#{Macro.to_string(expr)}`"
457
+ )
372
458
373
459
:error ->
374
460
expr = {:%{}, [], [result: expr]}
 
@@ -381,10 +467,12 @@ defmodule Ecto.Query.Planner do
381
467
{right_struct, right_fields} = subquery_select(right, take, query)
382
468
{left_struct || right_struct, Keyword.merge(left_fields, right_fields)}
383
469
end
470
+
384
471
defp subquery_select({:%, _, [name, map]}, take, query) do
385
472
{_, fields} = subquery_select(map, take, query)
386
473
{name, fields}
387
474
end
475
+
388
476
defp subquery_select({:%{}, _, [{:|, _, [{:&, [], [ix]}, pairs]}]} = expr, take, query) do
389
477
assert_subquery_fields!(query, expr, pairs)
390
478
drop = Map.new(pairs, fn {key, _} -> {key, nil} end)
 
@@ -396,18 +484,22 @@ defmodule Ecto.Query.Planner do
396
484
kept_keys = subquery_source_fields(source) -- Keyword.keys(pairs)
397
485
{keep_source_or_struct(source), subquery_fields(kept_keys, ix) ++ pairs}
398
486
end
487
+
399
488
defp subquery_select({:%{}, _, pairs} = expr, _take, query) do
400
489
assert_subquery_fields!(query, expr, pairs)
401
490
{nil, pairs}
402
491
end
492
+
403
493
defp subquery_select({:&, _, [ix]}, take, query) do
404
494
{source, _} = source_take!(:select, query, take, ix, ix, %{})
405
495
fields = subquery_source_fields(source)
406
496
{keep_source_or_struct(source), subquery_fields(fields, ix)}
407
497
end
498
+
408
499
defp subquery_select({{:., _, [{:&, _, [_]}, field]}, _, []} = expr, _take, _query) do
409
500
{nil, [{field, expr}]}
410
501
end
502
+
411
503
defp subquery_select(_expr, _take, _query) do
412
504
:error
413
505
end
 
@@ -427,7 +519,10 @@ defmodule Ecto.Query.Planner do
427
519
defp subquery_source_fields({:map, types}), do: Keyword.keys(types)
428
520
429
521
defp subquery_type_for({:source, _, _, fields}, field), do: Keyword.fetch(fields, field)
430
- defp subquery_type_for({:struct, _name, types}, field), do: subquery_type_for_value(types, field)
522
+
523
+ defp subquery_type_for({:struct, _name, types}, field),
524
+ do: subquery_type_for_value(types, field)
525
+
431
526
defp subquery_type_for({:map, types}, field), do: subquery_type_for_value(types, field)
432
527
433
528
defp subquery_type_for_value(types, field) do
 
@@ -441,37 +536,55 @@ defmodule Ecto.Query.Planner do
441
536
defp assert_subquery_fields!(query, expr, pairs) do
442
537
Enum.each(pairs, fn
443
538
{key, _} when not is_atom(key) ->
444
- error!(query, "only atom keys are allowed when selecting a map in subquery, got: `#{Macro.to_string(expr)}`")
539
+ error!(
540
+ query,
541
+ "only atom keys are allowed when selecting a map in subquery, got: `#{Macro.to_string(expr)}`"
542
+ )
445
543
446
544
{key, value} ->
447
545
if valid_subquery_value?(value) do
448
546
{key, value}
449
547
else
450
- error!(query, "atoms, structs, maps, lists, tuples and sources are not allowed as map values in subquery, got: `#{Macro.to_string(expr)}`")
548
+ error!(
549
+ query,
550
+ "atoms, structs, maps, lists, tuples and sources are not allowed as map values in subquery, got: `#{Macro.to_string(expr)}`"
551
+ )
451
552
end
452
553
end)
453
554
end
454
555
455
556
defp valid_subquery_value?({_, _}), do: false
456
557
defp valid_subquery_value?(args) when is_list(args), do: false
558
+
457
559
defp valid_subquery_value?({container, _, args})
458
- when container in [:{}, :%{}, :&, :%] and is_list(args), do: false
560
+ when container in [:{}, :%{}, :&, :%] and is_list(args),
561
+ do: false
562
+
459
563
defp valid_subquery_value?(nil), do: true
460
564
defp valid_subquery_value?(arg) when is_atom(arg), do: is_boolean(arg)
461
565
defp valid_subquery_value?(_), do: true
462
566
463
- defp plan_joins(query, sources, offset, adapter) do
464
- plan_joins(query.joins, query, [], sources, [], 1, offset, adapter)
567
+ defp plan_joins(query, sources, offset, adapter, cte_names) do
568
+ plan_joins(query.joins, query, [], sources, [], 1, offset, adapter, cte_names)
465
569
end
466
570
467
- defp plan_joins([%JoinExpr{assoc: {ix, assoc}, qual: qual, on: on, prefix: prefix} = join|t],
468
- query, joins, sources, tail_sources, counter, offset, adapter) do
571
+ defp plan_joins(
572
+ [%JoinExpr{assoc: {ix, assoc}, qual: qual, on: on, prefix: prefix} = join | t],
573
+ query,
574
+ joins,
575
+ sources,
576
+ tail_sources,
577
+ counter,
578
+ offset,
579
+ adapter,
580
+ cte_names
581
+ ) do
469
582
source = fetch_source!(sources, ix)
470
583
schema = schema_for_association_join!(query, join, source)
471
584
refl = schema.__schema__(:association, assoc)
472
585
473
586
unless refl do
474
- error! query, join, "could not find association `#{assoc}` on schema #{inspect schema}"
587
+ error!(query, join, "could not find association `#{assoc}` on schema #{inspect(schema)}")
475
588
end
476
589
477
590
# If we have the following join:
 
@@ -499,16 +612,16 @@ defmodule Ecto.Query.Planner do
499
612
# 2. from and joins can have their prefixes explicitly
500
613
# overwritten by the join prefix
501
614
child = rewrite_prefix(child, query.prefix)
502
- child = update_in child.from, &rewrite_prefix(&1, prefix)
503
- child = update_in child.joins, &Enum.map(&1, fn join -> rewrite_prefix(join, prefix) end)
615
+ child = update_in(child.from, &rewrite_prefix(&1, prefix))
616
+ child = update_in(child.joins, &Enum.map(&1, fn join -> rewrite_prefix(join, prefix) end))
504
617
505
618
last_ix = length(child.joins)
506
619
source_ix = counter
507
620
508
- {_, child_from_source} = plan_source(child, child.from, adapter)
621
+ {_, child_from_source} = plan_source(child, child.from, adapter, cte_names)
509
622
510
623
{child_joins, child_sources, child_tail} =
511
- plan_joins(child, [child_from_source], offset + last_ix - 1, adapter)
624
+ plan_joins(child, [child_from_source], offset + last_ix - 1, adapter, cte_names)
512
625
513
626
# Rewrite joins indexes as mentioned above
514
627
child_joins = Enum.map(child_joins, &rewrite_join(&1, qual, ix, last_ix, source_ix, offset))
 
@@ -516,41 +629,115 @@ defmodule Ecto.Query.Planner do
516
629
# Drop the last resource which is the association owner (it is reversed)
517
630
child_sources = Enum.drop(child_sources, -1)
518
631
519
- [current_source|child_sources] = child_sources
632
+ [current_source | child_sources] = child_sources
520
633
child_sources = child_tail ++ child_sources
521
634
522
- plan_joins(t, query, attach_on(child_joins, on) ++ joins, [current_source|sources],
523
- child_sources ++ tail_sources, counter + 1, offset + length(child_sources), adapter)
635
+ plan_joins(
636
+ t,
637
+ query,
638
+ attach_on(child_joins, on) ++ joins,
639
+ [current_source | sources],
640
+ child_sources ++ tail_sources,
641
+ counter + 1,
642
+ offset + length(child_sources),
643
+ adapter,
644
+ cte_names
645
+ )
524
646
end
525
647
526
- defp plan_joins([%JoinExpr{source: %Ecto.Query{} = join_query, qual: qual, on: on, prefix: prefix} = join|t],
527
- query, joins, sources, tail_sources, counter, offset, adapter) do
648
+ defp plan_joins(
649
+ [
650
+ %JoinExpr{source: %Ecto.Query{} = join_query, qual: qual, on: on, prefix: prefix} =
651
+ join
652
+ | t
653
+ ],
654
+ query,
655
+ joins,
656
+ sources,
657
+ tail_sources,
658
+ counter,
659
+ offset,
660
+ adapter,
661
+ cte_names
662
+ ) do
528
663
case join_query do
529
- %{order_bys: [], limit: nil, offset: nil, group_bys: [], joins: [],
530
- havings: [], preloads: [], assocs: [], distinct: nil, lock: nil} ->
664
+ %{
665
+ order_bys: [],
666
+ limit: nil,
667
+ offset: nil,
668
+ group_bys: [],
669
+ joins: [],
670
+ havings: [],
671
+ preloads: [],
672
+ assocs: [],
673
+ distinct: nil,
674
+ lock: nil
675
+ } ->
531
676
join_query = rewrite_prefix(join_query, query.prefix)
532
677
from = rewrite_prefix(join_query.from, prefix)
533
- {from, source} = plan_source(join_query, from, adapter)
678
+ {from, source} = plan_source(join_query, from, adapter, cte_names)
534
679
[join] = attach_on(query_to_joins(qual, from.source, join_query, counter), on)
535
- plan_joins(t, query, [join|joins], [source|sources], tail_sources, counter + 1, offset, adapter)
680
+
681
+ plan_joins(
682
+ t,
683
+ query,
684
+ [join | joins],
685
+ [source | sources],
686
+ tail_sources,
687
+ counter + 1,
688
+ offset,
689
+ adapter,
690
+ cte_names
691
+ )
692
+
536
693
_ ->
537
- error! query, join, """
694
+ error!(query, join, """
538
695
invalid query was interpolated in a join.
539
696
If you want to pass a query to a join, you must either:
540
697
541
698
1. Make sure the query only has `where` conditions (which will be converted to ON clauses)
542
699
2. Or wrap the query in a subquery by calling subquery(query)
543
- """
700
+ """)
544
701
end
545
702
end
546
703
547
- defp plan_joins([%JoinExpr{} = join|t],
548
- query, joins, sources, tail_sources, counter, offset, adapter) do
549
- {join, source} = plan_source(query, %{join | ix: counter}, adapter)
550
- plan_joins(t, query, [join|joins], [source|sources], tail_sources, counter + 1, offset, adapter)
704
+ defp plan_joins(
705
+ [%JoinExpr{} = join | t],
706
+ query,
707
+ joins,
708
+ sources,
709
+ tail_sources,
710
+ counter,
711
+ offset,
712
+ adapter,
713
+ cte_names
714
+ ) do
715
+ {join, source} = plan_source(query, %{join | ix: counter}, adapter, cte_names)
716
+
717
+ plan_joins(
718
+ t,
719
+ query,
720
+ [join | joins],
721
+ [source | sources],
722
+ tail_sources,
723
+ counter + 1,
724
+ offset,
725
+ adapter,
726
+ cte_names
727
+ )
551
728
end
552
729
553
- defp plan_joins([], _query, joins, sources, tail_sources, _counter, _offset, _adapter) do
730
+ defp plan_joins(
731
+ [],
732
+ _query,
733
+ joins,
734
+ sources,
735
+ tail_sources,
736
+ _counter,
737
+ _offset,
738
+ _adapter,
739
+ _cte_names
740
+ ) do
554
741
{joins, sources, tail_sources}
555
742
end
556
743
 
@@ -563,19 +750,26 @@ defmodule Ecto.Query.Planner do
563
750
defp rewrite_prefix(expr, _prefix), do: expr
564
751
565
752
defp rewrite_join(%{on: on, ix: join_ix} = join, qual, ix, last_ix, source_ix, inc_ix) do
566
- expr = Macro.prewalk on.expr, fn
753
+ expr =
754
+ Macro.prewalk(on.expr, fn
567
755
{:&, meta, [join_ix]} ->
568
756
{:&, meta, [rewrite_ix(join_ix, ix, last_ix, source_ix, inc_ix)]}
757
+
569
758
expr = %Ecto.Query.Tagged{type: {type_ix, type}} when is_integer(type_ix) ->
570
759
%{expr | type: {rewrite_ix(type_ix, ix, last_ix, source_ix, inc_ix), type}}
760
+
571
761
other ->
572
762
other
573
- end
763
+ end)
574
764
575
765
params = Enum.map(on.params, &rewrite_param_ix(&1, ix, last_ix, source_ix, inc_ix))
576
766
577
- %{join | on: %{on | expr: expr, params: params}, qual: qual,
578
- ix: rewrite_ix(join_ix, ix, last_ix, source_ix, inc_ix)}
767
+ %{
768
+ join
769
+ | on: %{on | expr: expr, params: params},
770
+ qual: qual,
771
+ ix: rewrite_ix(join_ix, ix, last_ix, source_ix, inc_ix)
772
+ }
579
773
end
580
774
581
775
# We need to replace the source by the one from the assoc
 
@@ -590,11 +784,13 @@ defmodule Ecto.Query.Planner do
590
784
# All others need to be incremented by the offset sources
591
785
defp rewrite_ix(join_ix, _ix, _last_ix, _source_ix, inc_ix), do: join_ix + inc_ix
592
786
593
- defp rewrite_param_ix({value, {upper, {type_ix, field}}}, ix, last_ix, source_ix, inc_ix) when is_integer(type_ix) do
787
+ defp rewrite_param_ix({value, {upper, {type_ix, field}}}, ix, last_ix, source_ix, inc_ix)
788
+ when is_integer(type_ix) do
594
789
{value, {upper, {rewrite_ix(type_ix, ix, last_ix, source_ix, inc_ix), field}}}
595
790
end
596
791
597
- defp rewrite_param_ix({value, {type_ix, field}}, ix, last_ix, source_ix, inc_ix) when is_integer(type_ix) do
792
+ defp rewrite_param_ix({value, {type_ix, field}}, ix, last_ix, source_ix, inc_ix)
793
+ when is_integer(type_ix) do
598
794
{value, {rewrite_ix(type_ix, ix, last_ix, source_ix, inc_ix), field}}
599
795
end
600
796
 
@@ -602,23 +798,31 @@ defmodule Ecto.Query.Planner do
602
798
603
799
defp fetch_source!(sources, ix) when is_integer(ix) do
604
800
case Enum.reverse(sources) |> Enum.fetch(ix) do
605
- {:ok, source} -> source
606
- :error -> raise ArgumentError, "could not find a source with index `#{ix}` in `#{inspect sources}"
801
+ {:ok, source} ->
802
+ source
803
+
804
+ :error ->
805
+ raise ArgumentError, "could not find a source with index `#{ix}` in `#{inspect(sources)}"
607
806
end
608
807
end
609
808
610
809
defp fetch_source!(_, ix) do
611
- raise ArgumentError, "invalid binding index: `#{inspect ix}` (check if you're binding using a valid :as atom)"
810
+ raise ArgumentError,
811
+ "invalid binding index: `#{inspect(ix)}` (check if you're binding using a valid :as atom)"
612
812
end
613
813
614
814
defp schema_for_association_join!(query, join, source) do
615
815
case source do
616
816
{:fragment, _, _} ->
617
- error! query, join, "cannot perform association joins on fragment sources"
817
+ error!(query, join, "cannot perform association joins on fragment sources")
618
818
619
819
{source, nil, _} ->
620
- error! query, join, "cannot perform association join on #{inspect source} " <>
621
- "because it does not have a schema"
820
+ error!(
821
+ query,
822
+ join,
823
+ "cannot perform association join on #{inspect(source)} " <>
824
+ "because it does not have a schema"
825
+ )
622
826
623
827
{_, schema, _} ->
624
828
schema
 
@@ -630,48 +834,67 @@ defmodule Ecto.Query.Planner do
630
834
schema
631
835
632
836
%Ecto.SubQuery{} ->
633
- error! query, join, "can only perform association joins on subqueries " <>
634
- "that return a source with schema in select"
837
+ error!(
838
+ query,
839
+ join,
840
+ "can only perform association joins on subqueries " <>
841
+ "that return a source with schema in select"
842
+ )
635
843
636
844
_ ->
637
- error! query, join, "can only perform association joins on sources with a schema"
845
+ error!(query, join, "can only perform association joins on sources with a schema")
638
846
end
639
847
end
640
848
641
- @spec plan_wheres(Ecto.Query.t, module) :: Ecto.Query.t
642
- defp plan_wheres(query, adapter) do
643
- wheres =
644
- Enum.map(query.wheres, fn
645
- %{subqueries: []} = where ->
646
- where
647
-
648
- %{subqueries: subqueries} = where ->
649
- %{where | subqueries: Enum.map(subqueries, &plan_subquery(&1, query, nil, adapter, false))}
650
- end)
651
-
652
- havings =
653
- Enum.map(query.havings, fn
654
- %{subqueries: []} = having ->
655
- having
656
-
657
- %{subqueries: subqueries} = having ->
658
- %{having | subqueries: Enum.map(subqueries, &plan_subquery(&1, query, nil, adapter, false))}
659
- end)
660
-
661
- %{query | wheres: wheres, havings: havings}
849
+ # An optimized version of plan subqueries that only modifies the query when necessary.
850
+ defp plan_expr_subqueries(query, key, fun) do
851
+ query
852
+ |> Map.fetch!(key)
853
+ |> plan_expr_subqueries([], query, key, fun)
662
854
end
663
855
664
- @spec plan_select(Ecto.Query.t, module) :: Ecto.Query.t
665
- defp plan_select(query, adapter) do
666
- case query do
667
- %{select: %{subqueries: [_ | _] = subqueries}} ->
668
- subqueries = Enum.map(subqueries, &plan_subquery(&1, query, nil, adapter, false))
669
- put_in(query.select.subqueries, subqueries)
856
+ defp plan_expr_subqueries([%{subqueries: []} = head | tail], acc, query, key, fun) do
857
+ plan_expr_subqueries(tail, [head | acc], query, key, fun)
858
+ end
670
859
671
- query -> query
860
+ defp plan_expr_subqueries([head | tail], acc, query, key, fun) do
861
+ exprs =
862
+ Enum.reduce([head | tail], acc, fn
863
+ %{subqueries: []} = expr, acc ->
864
+ [expr | acc]
865
+
866
+ %{subqueries: subqueries} = expr, acc ->
867
+ [%{expr | subqueries: Enum.map(subqueries, fun)} | acc]
868
+ end)
869
+
870
+ %{query | key => Enum.reverse(exprs)}
871
+ end
872
+
873
+ defp plan_expr_subqueries([], _acc, query, _key, _fun) do
874
+ query
875
+ end
876
+
877
+ defp plan_expr_subquery(query, key, fun) do
878
+ with %{^key => %{subqueries: [_ | _] = subqueries} = expr} <- query do
879
+ %{query | key => %{expr | subqueries: Enum.map(subqueries, fun)}}
672
880
end
673
881
end
674
882
883
+ defp plan_windows(%{windows: []} = query, _fun), do: query
884
+
885
+ defp plan_windows(query, fun) do
886
+ windows =
887
+ Enum.map(query.windows, fn
888
+ {key, %{subqueries: []} = window} ->
889
+ {key, window}
890
+
891
+ {key, %{subqueries: subqueries} = window} ->
892
+ {key, %{window | subqueries: Enum.map(subqueries, fun)}}
893
+ end)
894
+
895
+ %{query | windows: windows}
896
+ end
897
+
675
898
@doc """
676
899
Prepare the parameters by merging and casting them according to sources.
677
900
"""
 
@@ -693,7 +916,7 @@ defmodule Ecto.Query.Planner do
693
916
end
694
917
695
918
defp merge_cache(kind, query, expr, {cache, params}, _operation, adapter)
696
- when kind in ~w(select distinct limit offset)a do
919
+ when kind in ~w(select distinct limit offset)a do
697
920
if expr do
698
921
{params, cacheable?} = cast_and_merge_params(kind, query, expr, params, adapter)
699
922
{merge_cache({kind, expr_to_cache(expr)}, cache, cacheable?), params}
 
@@ -703,56 +926,59 @@ defmodule Ecto.Query.Planner do
703
926
end
704
927
705
928
defp merge_cache(kind, query, exprs, {cache, params}, _operation, adapter)
706
- when kind in ~w(where update group_by having order_by)a do
929
+ when kind in ~w(where update group_by having order_by)a do
707
930
{expr_cache, {params, cacheable?}} =
708
- Enum.map_reduce exprs, {params, true}, fn expr, {params, cacheable?} ->
931
+ Enum.map_reduce(exprs, {params, true}, fn expr, {params, cacheable?} ->
709
932
{params, current_cacheable?} = cast_and_merge_params(kind, query, expr, params, adapter)
710
933
{expr_to_cache(expr), {params, cacheable? and current_cacheable?}}
711
- end
934
+ end)
712
935
713
936
case expr_cache do
714
937
[] -> {cache, params}
715
- _ -> {merge_cache({kind, expr_cache}, cache, cacheable?), params}
938
+ _ -> {merge_cache({kind, expr_cache}, cache, cacheable?), params}
716
939
end
717
940
end
718
941
719
942
defp merge_cache(:join, query, exprs, {cache, params}, _operation, adapter) do
720
943
{expr_cache, {params, cacheable?}} =
721
- Enum.map_reduce exprs, {params, true}, fn
944
+ Enum.map_reduce(exprs, {params, true}, fn
722
945
%JoinExpr{on: on, qual: qual, hints: hints} = join, {params, cacheable?} ->
723
946
{key, params} = source_cache(join, params)
724
947
{params, join_cacheable?} = cast_and_merge_params(:join, query, join, params, adapter)
725
948
{params, on_cacheable?} = cast_and_merge_params(:join, query, on, params, adapter)
949
+
726
950
{{qual, key, on.expr, hints},
727
951
{params, cacheable? and join_cacheable? and on_cacheable? and key != :nocache}}
728
- end
952
+ end)
729
953
730
954
case expr_cache do
731
955
[] -> {cache, params}
732
- _ -> {merge_cache({:join, expr_cache}, cache, cacheable?), params}
956
+ _ -> {merge_cache({:join, expr_cache}, cache, cacheable?), params}
733
957
end
734
958
end
735
959
736
960
defp merge_cache(:windows, query, exprs, {cache, params}, _operation, adapter) do
737
961
{expr_cache, {params, cacheable?}} =
738
- Enum.map_reduce exprs, {params, true}, fn {key, expr}, {params, cacheable?} ->
739
- {params, current_cacheable?} = cast_and_merge_params(:windows, query, expr, params, adapter)
962
+ Enum.map_reduce(exprs, {params, true}, fn {key, expr}, {params, cacheable?} ->
963
+ {params, current_cacheable?} =
964
+ cast_and_merge_params(:windows, query, expr, params, adapter)
965
+
740
966
{{key, expr_to_cache(expr)}, {params, cacheable? and current_cacheable?}}
741
- end
967
+ end)
742
968
743
969
case expr_cache do
744
970
[] -> {cache, params}
745
- _ -> {merge_cache({:windows, expr_cache}, cache, cacheable?), params}
971
+ _ -> {merge_cache({:windows, expr_cache}, cache, cacheable?), params}
746
972
end
747
973
end
748
974
749
975
defp merge_cache(:combination, _query, combinations, cache_and_params, operation, adapter) do
750
976
# In here we add each combination as its own entry in the cache key.
751
977
# We could group them to avoid multiple keys, but since they are uncommon, we keep it simple.
752
- Enum.reduce combinations, cache_and_params, fn {modifier, query}, {cache, params} ->
978
+ Enum.reduce(combinations, cache_and_params, fn {modifier, query}, {cache, params} ->
753
979
{_, params, inner_cache} = traverse_cache(query, operation, {[], params}, adapter)
754
980
{merge_cache({modifier, inner_cache}, cache, inner_cache != :nocache), params}
755
- end
981
+ end)
756
982
end
757
983
758
984
defp merge_cache(:with_cte, _query, nil, cache_and_params, _operation, _adapter) do
 
@@ -765,33 +991,56 @@ defmodule Ecto.Query.Planner do
765
991
766
992
# In here we add each cte as its own entry in the cache key.
767
993
# We could group them to avoid multiple keys, but since they are uncommon, we keep it simple.
768
- Enum.reduce queries, cache_and_params, fn
994
+ Enum.reduce(queries, cache_and_params, fn
769
995
{name, opts, %Ecto.Query{} = query}, {cache, params} ->
770
996
{_, params, inner_cache} = traverse_cache(query, :all, {[], params}, adapter)
771
- {merge_cache({key, name, opts[:materialized], opts[:operation], inner_cache}, cache, inner_cache != :nocache), params}
997
+
998
+ {merge_cache(
999
+ {key, name, opts[:materialized], opts[:operation], inner_cache},
1000
+ cache,
1001
+ inner_cache != :nocache
1002
+ ), params}
772
1003
773
1004
{name, opts, %Ecto.Query.QueryExpr{} = query_expr}, {cache, params} ->
774
- {params, cacheable?} = cast_and_merge_params(:with_cte, query, query_expr, params, adapter)
775
- {merge_cache({key, name, opts[:materialized], opts[:operation], expr_to_cache(query_expr)}, cache, cacheable?), params}
776
- end
1005
+ {params, cacheable?} =
1006
+ cast_and_merge_params(:with_cte, query, query_expr, params, adapter)
1007
+
1008
+ {merge_cache(
1009
+ {key, name, opts[:materialized], opts[:operation], expr_to_cache(query_expr)},
1010
+ cache,
1011
+ cacheable?
1012
+ ), params}
1013
+ end)
777
1014
end
778
1015
779
1016
defp expr_to_cache(%QueryExpr{expr: expr}), do: expr
1017
+
780
1018
defp expr_to_cache(%SelectExpr{expr: expr, subqueries: []}), do: expr
1019
+
781
1020
defp expr_to_cache(%SelectExpr{expr: expr, subqueries: subqueries}) do
782
1021
{expr, Enum.map(subqueries, fn %{cache: cache} -> {:subquery, cache} end)}
783
1022
end
1023
+
1024
+ defp expr_to_cache(%ByExpr{expr: expr, subqueries: []}), do: expr
1025
+
1026
+ defp expr_to_cache(%ByExpr{expr: expr, subqueries: subqueries}) do
1027
+ {expr, Enum.map(subqueries, fn %{cache: cache} -> {:subquery, cache} end)}
1028
+ end
1029
+
784
1030
defp expr_to_cache(%BooleanExpr{op: op, expr: expr, subqueries: []}), do: {op, expr}
1031
+
785
1032
defp expr_to_cache(%BooleanExpr{op: op, expr: expr, subqueries: subqueries}) do
786
1033
# Alternate implementation could be replace {:subquery, i} expression in expr.
787
1034
# Current strategy appends [{:subquery, i, cache}], where cache is the cache key for this subquery.
788
1035
{op, expr, Enum.map(subqueries, fn %{cache: cache} -> {:subquery, cache} end)}
789
1036
end
1037
+
790
1038
defp expr_to_cache(%LimitExpr{expr: expr, with_ties: with_ties}), do: {with_ties, expr}
791
1039
792
- @spec cast_and_merge_params(atom, Ecto.Query.t, any, list, module) :: {params :: list, cacheable? :: boolean}
1040
+ @spec cast_and_merge_params(atom, Ecto.Query.t(), any, list, module) ::
1041
+ {params :: list, cacheable? :: boolean}
793
1042
defp cast_and_merge_params(kind, query, expr, params, adapter) do
794
- Enum.reduce expr.params, {params, true}, fn
1043
+ Enum.reduce(expr.params, {params, true}, fn
795
1044
{:subquery, i}, {acc, cacheable?} ->
796
1045
# This is the place holder to intersperse subquery parameters.
797
1046
%Ecto.SubQuery{params: subparams, cache: cache} = Enum.fetch!(expr.subqueries, i)
 
@@ -803,16 +1052,16 @@ defmodule Ecto.Query.Planner do
803
1052
{cast_v, {:splice, dump_v}} -> {split_variadic_params(cast_v, dump_v, acc), cacheable?}
804
1053
cast_v_and_dump_v -> {[cast_v_and_dump_v | acc], cacheable?}
805
1054
end
806
- end
1055
+ end)
807
1056
end
808
1057
809
1058
defp split_variadic_params(cast_v, dump_v, acc) do
810
1059
Enum.zip(cast_v, dump_v) |> Enum.reverse(acc)
811
1060
end
812
1061
813
- defp merge_cache(_left, _right, false), do: :nocache
1062
+ defp merge_cache(_left, _right, false), do: :nocache
814
1063
defp merge_cache(_left, :nocache, true), do: :nocache
815
- defp merge_cache(left, right, true), do: [left|right]
1064
+ defp merge_cache(left, right, true), do: [left | right]
816
1065
817
1066
defp finalize_cache(_query, _operation, :nocache) do
818
1067
:nocache
 
@@ -826,16 +1075,17 @@ defmodule Ecto.Query.Planner do
826
1075
case select do
827
1076
%{take: take} when take != %{} ->
828
1077
[take: take] ++ cache
1078
+
829
1079
_ ->
830
1080
cache
831
1081
end
832
1082
833
1083
cache =
834
1084
cache
835
- |> prepend_if(assocs != [], [assocs: assocs])
836
- |> prepend_if(prefix != nil, [prefix: prefix])
837
- |> prepend_if(lock != nil, [lock: lock])
838
- |> prepend_if(aliases != %{}, [aliases: aliases])
1085
+ |> prepend_if(assocs != [], assocs: assocs)
1086
+ |> prepend_if(prefix != nil, prefix: prefix)
1087
+ |> prepend_if(lock != nil, lock: lock)
1088
+ |> prepend_if(aliases != %{}, aliases: aliases)
839
1089
840
1090
[operation | cache]
841
1091
end
 
@@ -845,26 +1095,47 @@ defmodule Ecto.Query.Planner do
845
1095
846
1096
defp source_cache(%{source: {_, nil} = source, prefix: prefix}, params),
847
1097
do: {{source, prefix}, params}
1098
+
848
1099
defp source_cache(%{source: {bin, schema}, prefix: prefix}, params),
849
1100
do: {{bin, schema, schema.__schema__(:hash), prefix}, params}
850
- defp source_cache(%{source: {kind, _, _} = source, prefix: prefix}, params)
851
- when kind in [:fragment, :values],
852
- do: {{source, prefix}, params}
1101
+
1102
+ defp source_cache(%{source: {:fragment, _, _} = source, prefix: prefix}, params),
1103
+ do: {{source, prefix}, params}
1104
+
1105
+ defp source_cache(%{source: {:values, _, _}}, params),
1106
+ do: {:nocache, params}
1107
+
853
1108
defp source_cache(%{source: %Ecto.SubQuery{params: inner, cache: key}}, params),
854
1109
do: {key, Enum.reverse(inner, params)}
855
1110
856
1111
defp cast_param(_kind, query, expr, %DynamicExpr{}, _type, _value) do
857
- error! query, expr, "invalid dynamic expression",
858
- "dynamic expressions can only be interpolated at the top level of where, having, group_by, order_by, select, update or a join's on"
1112
+ error!(
1113
+ query,
1114
+ expr,
1115
+ "invalid dynamic expression",
1116
+ "dynamic expressions can only be interpolated at the top level of where, having, group_by, order_by, select, update or a join's on"
1117
+ )
859
1118
end
1119
+
860
1120
defp cast_param(_kind, query, expr, [{key, _} | _], _type, _value) when is_atom(key) do
861
- error! query, expr, "invalid keyword list",
862
- "keyword lists are only allowed at the top level of where, having, distinct, order_by, update or a join's on"
1121
+ error!(
1122
+ query,
1123
+ expr,
1124
+ "invalid keyword list",
1125
+ "keyword lists are only allowed at the top level of where, having, distinct, order_by, update or a join's on"
1126
+ )
863
1127
end
864
- defp cast_param(_kind, query, expr, %x{}, {:in, _type}, _value) when x in [Ecto.Query, Ecto.SubQuery] do
865
- error! query, expr, "an #{inspect(x)} struct is not supported as right-side value of `in` operator",
866
- "Did you mean to write `expr in subquery(query)` instead?"
1128
+
1129
+ defp cast_param(_kind, query, expr, %x{}, {:in, _type}, _value)
1130
+ when x in [Ecto.Query, Ecto.SubQuery] do
1131
+ error!(
1132
+ query,
1133
+ expr,
1134
+ "an #{inspect(x)} struct is not supported as right-side value of `in` operator",
1135
+ "Did you mean to write `expr in subquery(query)` instead?"
1136
+ )
867
1137
end
1138
+
868
1139
defp cast_param(kind, query, expr, v, type, adapter) do
869
1140
type = field_type!(kind, query, expr, type)
870
1141
 
@@ -895,57 +1166,80 @@ defmodule Ecto.Query.Planner do
895
1166
end
896
1167
897
1168
defp plan_assocs(_query, _ix, []), do: :ok
898
- defp plan_assocs(query, ix, assocs) do
899
- # We validate the schema exists when preparing joins above
900
- {_, parent_schema, _} = get_preload_source!(query, ix)
901
1169
902
- Enum.each assocs, fn {assoc, {child_ix, child_assocs}} ->
1170
+ defp plan_assocs(query, ix, assocs) do
1171
+ # We validate the schema exists when preparing joins.
1172
+ parent_schema =
1173
+ case get_preload_source!(query, ix) do
1174
+ {_, schema, _} ->
1175
+ schema
1176
+
1177
+ %Ecto.SubQuery{select: {:source, {_, schema}, _, _}} ->
1178
+ schema
1179
+ end
1180
+
1181
+ Enum.each(assocs, fn {assoc, {child_ix, child_assocs}} ->
903
1182
refl = parent_schema.__schema__(:association, assoc)
904
1183
905
1184
unless refl do
906
- error! query, "field `#{inspect parent_schema}.#{assoc}` " <>
907
- "in preload is not an association"
1185
+ error!(
1186
+ query,
1187
+ "field `#{inspect(parent_schema)}.#{assoc}` " <>
1188
+ "in preload is not an association"
1189
+ )
908
1190
end
909
1191
910
1192
case find_source_expr(query, child_ix) do
911
1193
%JoinExpr{qual: qual} when qual in [:inner, :left, :inner_lateral, :left_lateral] ->
912
1194
:ok
1195
+
913
1196
%JoinExpr{qual: qual} ->
914
- error! query, "association `#{inspect parent_schema}.#{assoc}` " <>
915
- "in preload requires an inner, left or lateral join, got #{qual} join"
1197
+ error!(
1198
+ query,
1199
+ "association `#{inspect(parent_schema)}.#{assoc}` " <>
1200
+ "in preload requires an inner, left or lateral join, got #{qual} join"
1201
+ )
1202
+
916
1203
_ ->
917
1204
:ok
918
1205
end
919
1206
920
1207
plan_assocs(query, child_ix, child_assocs)
921
- end
1208
+ end)
922
1209
end
923
1210
924
- defp plan_combinations(query, adapter) do
1211
+ defp plan_combinations(query, adapter, cte_names) do
925
1212
combinations =
926
- Enum.map query.combinations, fn {type, combination_query} ->
927
- {prepared_query, _params, _key} = combination_query |> attach_prefix(query) |> plan(:all, adapter)
1213
+ Enum.map(query.combinations, fn {type, combination_query} ->
1214
+ {prepared_query, _params, _key} =
1215
+ combination_query |> attach_prefix(query) |> plan(:all, adapter, cte_names)
1216
+
928
1217
prepared_query = prepared_query |> ensure_select(true)
929
1218
{type, prepared_query}
930
- end
1219
+ end)
931
1220
932
1221
%{query | combinations: combinations}
933
1222
end
934
1223
935
- defp plan_ctes(%Ecto.Query{with_ctes: nil} = query, _adapter), do: query
936
- defp plan_ctes(%Ecto.Query{with_ctes: %{queries: queries}} = query, adapter) do
937
- queries =
938
- Enum.map queries, fn
939
- {name, opts, %Ecto.Query{} = cte_query} ->
940
- {planned_query, _params, _key} = cte_query |> attach_prefix(query) |> plan(:all, adapter)
1224
+ defp plan_ctes(%Ecto.Query{with_ctes: nil} = query, _adapter, cte_names), do: {query, cte_names}
1225
+
1226
+ defp plan_ctes(%Ecto.Query{with_ctes: %{queries: queries}} = query, adapter, cte_names) do
1227
+ {queries, cte_names} =
1228
+ Enum.map_reduce(queries, cte_names, fn
1229
+ {name, opts, %Ecto.Query{} = cte_query}, cte_names ->
1230
+ cte_names = Map.put(cte_names, name, [])
1231
+
1232
+ {planned_query, _params, _key} =
1233
+ cte_query |> attach_prefix(query) |> plan(:all, adapter, cte_names)
1234
+
941
1235
planned_query = planned_query |> ensure_select(true)
942
- {name, opts, planned_query}
1236
+ {{name, opts, planned_query}, cte_names}
943
1237
944
- {name, opts, other} ->
945
- {name, opts, other}
946
- end
1238
+ {name, opts, other}, cte_names ->
1239
+ {{name, opts, other}, cte_names}
1240
+ end)
947
1241
948
- put_in(query.with_ctes.queries, queries)
1242
+ {put_in(query.with_ctes.queries, queries), cte_names}
949
1243
end
950
1244
951
1245
defp find_source_expr(query, 0) do
 
@@ -953,7 +1247,7 @@ defmodule Ecto.Query.Planner do
953
1247
end
954
1248
955
1249
defp find_source_expr(query, ix) do
956
- Enum.find(query.joins, & &1.ix == ix)
1250
+ Enum.find(query.joins, &(&1.ix == ix))
957
1251
end
958
1252
959
1253
@doc """
 
@@ -962,22 +1256,35 @@ defmodule Ecto.Query.Planner do
962
1256
def ensure_select(%{select: select} = query, _fields) when select != nil do
963
1257
query
964
1258
end
1259
+
965
1260
def ensure_select(%{select: nil}, []) do
966
1261
raise ArgumentError, ":returning expects at least one field to be given, got an empty list"
967
1262
end
1263
+
968
1264
def ensure_select(%{select: nil} = query, fields) when is_list(fields) do
969
- %{query | select: %SelectExpr{expr: {:&, [], [0]}, take: %{0 => {:any, fields}},
970
- line: __ENV__.line, file: __ENV__.file}}
1265
+ %{
1266
+ query
1267
+ | select: %SelectExpr{
1268
+ expr: {:&, [], [0]},
1269
+ take: %{0 => {:any, fields}},
1270
+ line: __ENV__.line,
1271
+ file: __ENV__.file
1272
+ }
1273
+ }
971
1274
end
1275
+
972
1276
def ensure_select(%{select: nil, from: %{source: {_, nil}}} = query, true) do
973
- error! query, "queries that do not have a schema need to explicitly pass a :select clause"
1277
+ error!(query, "queries that do not have a schema need to explicitly pass a :select clause")
974
1278
end
1279
+
975
1280
def ensure_select(%{select: nil, from: %{source: {:fragment, _, _}}} = query, true) do
976
- error! query, "queries from a fragment need to explicitly pass a :select clause"
1281
+ error!(query, "queries from a fragment need to explicitly pass a :select clause")
977
1282
end
1283
+
978
1284
def ensure_select(%{select: nil} = query, true) do
979
1285
%{query | select: %SelectExpr{expr: {:&, [], [0]}, line: __ENV__.line, file: __ENV__.file}}
980
1286
end
1287
+
981
1288
def ensure_select(%{select: nil} = query, false) do
982
1289
query
983
1290
end
 
@@ -997,7 +1304,7 @@ defmodule Ecto.Query.Planner do
997
1304
rescue
998
1305
e ->
999
1306
# Reraise errors so we ignore the planner inner stacktrace
1000
- filter_and_reraise e, __STACKTRACE__
1307
+ filter_and_reraise(e, __STACKTRACE__)
1001
1308
end
1002
1309
1003
1310
defp keep_literals?(:insert_all, _), do: true
 
@@ -1007,30 +1314,39 @@ defmodule Ecto.Query.Planner do
1007
1314
case operation do
1008
1315
:all ->
1009
1316
assert_no_update!(query, operation)
1317
+
1010
1318
:insert_all ->
1011
1319
assert_no_update!(query, operation)
1320
+
1012
1321
:update_all ->
1013
1322
assert_update!(query, operation)
1014
1323
assert_only_filter_expressions!(query, operation)
1324
+
1015
1325
:delete_all ->
1016
1326
assert_no_update!(query, operation)
1017
1327
assert_only_filter_expressions!(query, operation)
1018
1328
end
1019
1329
1020
- traverse_exprs(query, operation, counter,
1021
- &validate_and_increment(&1, &2, &3, &4, operation, adapter))
1330
+ traverse_exprs(
1331
+ query,
1332
+ operation,
1333
+ counter,
1334
+ &validate_and_increment(&1, &2, &3, &4, operation, adapter)
1335
+ )
1022
1336
end
1023
1337
1024
- defp validate_and_increment(:from, query, %{source: %Ecto.SubQuery{}}, _counter, kind, _adapter) when kind not in ~w(all insert_all)a do
1025
- error! query, "`#{kind}` does not allow subqueries in `from`"
1338
+ defp validate_and_increment(:from, query, %{source: %Ecto.SubQuery{}}, _counter, kind, _adapter)
1339
+ when kind not in ~w(all insert_all)a do
1340
+ error!(query, "`#{kind}` does not allow subqueries in `from`")
1026
1341
end
1342
+
1027
1343
defp validate_and_increment(:from, query, %{source: source} = expr, counter, _kind, adapter) do
1028
1344
{source, acc} = prewalk_source(source, :from, query, expr, counter, adapter)
1029
1345
{%{expr | source: source}, acc}
1030
1346
end
1031
1347
1032
1348
defp validate_and_increment(kind, query, expr, counter, _operation, adapter)
1033
- when kind in ~w(select distinct limit offset)a do
1349
+ when kind in ~w(select distinct limit offset)a do
1034
1350
if expr do
1035
1351
prewalk(kind, query, expr, counter, adapter)
1036
1352
else
 
@@ -1040,15 +1356,16 @@ defmodule Ecto.Query.Planner do
1040
1356
1041
1357
defp validate_and_increment(kind, query, exprs, counter, _operation, adapter)
1042
1358
when kind in ~w(where group_by having order_by update)a do
1043
-
1044
1359
{exprs, counter} =
1045
1360
Enum.reduce(exprs, {[], counter}, fn
1046
1361
%{expr: []}, {list, acc} ->
1047
1362
{list, acc}
1363
+
1048
1364
expr, {list, acc} ->
1049
1365
{expr, acc} = prewalk(kind, query, expr, acc, adapter)
1050
- {[expr|list], acc}
1366
+ {[expr | list], acc}
1051
1367
end)
1368
+
1052
1369
{Enum.reverse(exprs), counter}
1053
1370
end
1054
1371
 
@@ -1060,7 +1377,7 @@ defmodule Ecto.Query.Planner do
1060
1377
fun = &validate_and_increment(&1, &2, &3, &4, :all, adapter)
1061
1378
1062
1379
{queries, counter} =
1063
- Enum.reduce with_expr.queries, {[], counter}, fn
1380
+ Enum.reduce(with_expr.queries, {[], counter}, fn
1064
1381
{name, opts, %Ecto.Query{} = inner_query}, {queries, counter} ->
1065
1382
inner_query = put_in(inner_query.aliases[@parent_as], query)
1066
1383
 
@@ -1071,35 +1388,41 @@ defmodule Ecto.Query.Planner do
1071
1388
1072
1389
# Now compute the fields as keyword lists so we emit AS in Ecto query.
1073
1390
%{select: %{expr: expr, take: take, aliases: aliases}} = inner_query
1074
- {{:map, types}, fields, _from} = collect_fields(expr, [], :none, inner_query, take, true, %{})
1391
+
1392
+ {{:map, types}, fields, _from} =
1393
+ collect_fields(expr, [], :none, inner_query, take, true, %{})
1394
+
1075
1395
fields = cte_fields(Keyword.keys(types), Enum.reverse(fields), aliases)
1076
1396
inner_query = put_in(inner_query.select.fields, fields)
1077
1397
{_, inner_query} = pop_in(inner_query.aliases[@parent_as])
1078
1398
1079
1399
{[{name, opts, inner_query} | queries], counter}
1080
1400
1081
- {name, opts, %QueryExpr{expr: {:fragment, _, _} = fragment} = query_expr}, {queries, counter} ->
1082
- {fragment, counter} = prewalk_source(fragment, :with_cte, query, with_expr, counter, adapter)
1401
+ {name, opts, %QueryExpr{expr: {:fragment, _, _} = fragment} = query_expr},
1402
+ {queries, counter} ->
1403
+ {fragment, counter} =
1404
+ prewalk_source(fragment, :with_cte, query, with_expr, counter, adapter)
1405
+
1083
1406
query_expr = %{query_expr | expr: fragment}
1084
1407
{[{name, opts, query_expr} | queries], counter}
1085
- end
1408
+ end)
1086
1409
1087
1410
{%{with_expr | queries: Enum.reverse(queries)}, counter}
1088
1411
end
1089
1412
1090
1413
defp validate_and_increment(:join, query, exprs, counter, _operation, adapter) do
1091
- Enum.map_reduce exprs, counter, fn join, acc ->
1414
+ Enum.map_reduce(exprs, counter, fn join, acc ->
1092
1415
{source, acc} = prewalk_source(join.source, :join, query, join, acc, adapter)
1093
1416
{on, acc} = prewalk(:join, query, join.on, acc, adapter)
1094
1417
{%{join | on: on, source: source, params: nil}, acc}
1095
- end
1418
+ end)
1096
1419
end
1097
1420
1098
1421
defp validate_and_increment(:windows, query, exprs, counter, _operation, adapter) do
1099
1422
{exprs, counter} =
1100
1423
Enum.reduce(exprs, {[], counter}, fn {name, expr}, {list, acc} ->
1101
1424
{expr, acc} = prewalk(:windows, query, expr, acc, adapter)
1102
- {[{name, expr}|list], acc}
1425
+ {[{name, expr} | list], acc}
1103
1426
end)
1104
1427
1105
1428
{Enum.reverse(exprs), counter}
 
@@ -1110,21 +1433,22 @@ defmodule Ecto.Query.Planner do
1110
1433
parent_aliases = query.aliases[@parent_as]
1111
1434
1112
1435
{combinations, counter} =
1113
- Enum.reduce combinations, {[], counter}, fn {type, combination_query}, {combinations, counter} ->
1436
+ Enum.reduce(combinations, {[], counter}, fn {type, combination_query},
1437
+ {combinations, counter} ->
1114
1438
combination_query = put_in(combination_query.aliases[@parent_as], parent_aliases)
1115
1439
{combination_query, counter} = traverse_exprs(combination_query, operation, counter, fun)
1116
1440
{combination_query, _} = combination_query |> normalize_select(true)
1117
1441
{_, combination_query} = pop_in(combination_query.aliases[@parent_as])
1118
1442
{[{type, combination_query} | combinations], counter}
1119
- end
1443
+ end)
1120
1444
1121
1445
{Enum.reverse(combinations), counter}
1122
1446
end
1123
1447
1124
- defp validate_json_path!([path_field | rest], field, {:parameterized, Ecto.Embedded, embed}) do
1448
+ defp validate_json_path!([path_field | rest], field, {:parameterized, {Ecto.Embedded, embed}}) do
1125
1449
case embed do
1126
1450
%{related: related, cardinality: :one} ->
1127
- unless Enum.any?(related.__schema__(:fields), &Atom.to_string(&1) == path_field) do
1451
+ unless Enum.any?(related.__schema__(:fields), &(Atom.to_string(&1) == path_field)) do
1128
1452
raise "field `#{path_field}` does not exist in #{inspect(related)}"
1129
1453
end
1130
1454
 
@@ -1137,7 +1461,7 @@ defmodule Ecto.Query.Planner do
1137
1461
end
1138
1462
1139
1463
updated_embed = %{embed | cardinality: :one}
1140
- validate_json_path!(rest, path_field, {:parameterized, Ecto.Embedded, updated_embed})
1464
+ validate_json_path!(rest, path_field, {:parameterized, {Ecto.Embedded, updated_embed}})
1141
1465
1142
1466
other ->
1143
1467
raise "expected field `#{field}` to be of type embed, got: `#{inspect(other)}`"
 
@@ -1155,7 +1479,7 @@ defmodule Ecto.Query.Planner do
1155
1479
{:map, _} ->
1156
1480
:ok
1157
1481
1158
- {:parameterized, type, _} ->
1482
+ {:parameterized, {type, _}} ->
1159
1483
validate_json_path!(path, field, type)
1160
1484
1161
1485
type ->
 
@@ -1171,21 +1495,30 @@ defmodule Ecto.Query.Planner do
1171
1495
{fragments, acc} = prewalk(fragments, kind, query, expr, acc, adapter)
1172
1496
{{:fragment, meta, fragments}, acc}
1173
1497
end
1498
+
1174
1499
defp prewalk_source({:values, meta, [types, num_rows]}, _kind, _query, _expr, acc, _adapter) do
1175
1500
length = num_rows * length(types)
1176
1501
# Adapters will use the schema types to cast the values
1177
1502
schema_types = Enum.map(types, fn {field, type} -> {field, Ecto.Type.type(type)} end)
1178
1503
{{:values, meta, [schema_types, acc, num_rows]}, acc + length}
1179
1504
end
1180
- defp prewalk_source(%Ecto.SubQuery{query: inner_query} = subquery, kind, query, _expr, counter, adapter) do
1505
+
1506
+ defp prewalk_source(
1507
+ %Ecto.SubQuery{query: inner_query} = subquery,
1508
+ kind,
1509
+ query,
1510
+ _expr,
1511
+ counter,
1512
+ adapter
1513
+ ) do
1181
1514
try do
1182
1515
inner_query = put_in(inner_query.aliases[@parent_as], query)
1183
1516
{inner_query, counter} = normalize_query(inner_query, :all, adapter, counter)
1184
1517
{inner_query, _} = normalize_select(inner_query, true)
1185
1518
{_, inner_query} = pop_in(inner_query.aliases[@parent_as])
1186
1519
1520
+ # If the subquery comes from a select, we are not really interested on the fields
1187
1521
inner_query =
1188
- # If the subquery comes from a select, we are not really interested on the fields
1189
1522
if kind == :where do
1190
1523
inner_query
1191
1524
else
 
@@ -1207,6 +1540,7 @@ defmodule Ecto.Query.Planner do
1207
1540
e -> raise Ecto.SubQueryError, query: query, exception: e
1208
1541
end
1209
1542
end
1543
+
1210
1544
defp prewalk_source(source, _kind, _query, _expr, acc, _adapter) do
1211
1545
{source, acc}
1212
1546
end
 
@@ -1215,17 +1549,19 @@ defmodule Ecto.Query.Planner do
1215
1549
source = get_source!(:update, query, 0)
1216
1550
1217
1551
{inner, acc} =
1218
- Enum.map_reduce expr.expr, counter, fn {op, kw}, counter ->
1552
+ Enum.map_reduce(expr.expr, counter, fn {op, kw}, counter ->
1219
1553
{kw, acc} =
1220
- Enum.map_reduce kw, counter, fn {field, value}, counter ->
1554
+ Enum.map_reduce(kw, counter, fn {field, value}, counter ->
1221
1555
{value, acc} = prewalk(value, :update, query, expr, counter, adapter)
1222
1556
{{field_source(source, field), value}, acc}
1223
- end
1557
+ end)
1558
+
1224
1559
{{op, kw}, acc}
1225
- end
1560
+ end)
1226
1561
1227
1562
{%{expr | expr: inner, params: nil}, acc}
1228
1563
end
1564
+
1229
1565
defp prewalk(kind, query, expr, counter, adapter) do
1230
1566
{inner, acc} = prewalk(expr.expr, kind, query, expr, counter, adapter)
1231
1567
{%{expr | expr: inner, params: nil}, acc}
 
@@ -1246,14 +1582,21 @@ defmodule Ecto.Query.Planner do
1246
1582
{right, acc} = prewalk(right, kind, query, expr, acc, adapter)
1247
1583
1248
1584
case right.query.select.fields do
1249
- [_] -> :ok
1250
- _ -> error!(query, "subquery must return a single field in order to be used on the right-side of `in`")
1585
+ [_] ->
1586
+ :ok
1587
+
1588
+ _ ->
1589
+ error!(
1590
+ query,
1591
+ "subquery must return a single field in order to be used on the right-side of `in`"
1592
+ )
1251
1593
end
1252
1594
1253
1595
{{:in, in_meta, [left, right]}, acc}
1254
1596
end
1255
1597
1256
- defp prewalk({quantifier, meta, [{:subquery, _} = subquery]}, kind, query, expr, acc, adapter) when quantifier in [:exists, :any, :all] do
1598
+ defp prewalk({quantifier, meta, [{:subquery, _} = subquery]}, kind, query, expr, acc, adapter)
1599
+ when quantifier in [:exists, :any, :all] do
1257
1600
{subquery, acc} = prewalk(subquery, kind, query, expr, acc, adapter)
1258
1601
1259
1602
case {quantifier, subquery.query.select.fields} do
 
@@ -1273,13 +1616,19 @@ defmodule Ecto.Query.Planner do
1273
1616
{{quantifier, meta, [subquery]}, acc}
1274
1617
end
1275
1618
1276
- defp prewalk({:splice, splice_meta, [{:^, meta, [_]}, length]}, _kind, _query, _expr, acc, _adapter) do
1619
+ defp prewalk(
1620
+ {:splice, splice_meta, [{:^, meta, [_]}, length]},
1621
+ _kind,
1622
+ _query,
1623
+ _expr,
1624
+ acc,
1625
+ _adapter
1626
+ ) do
1277
1627
param = {:^, meta, [acc, length]}
1278
1628
{{:splice, splice_meta, [param]}, acc + length}
1279
1629
end
1280
1630
1281
- defp prewalk({{:., dot_meta, [left, field]}, meta, []},
1282
- kind, query, expr, acc, _adapter) do
1631
+ defp prewalk({{:., dot_meta, [left, field]}, meta, []}, kind, query, expr, acc, _adapter) do
1283
1632
{ix, ix_expr, ix_query} = get_ix!(left, kind, query)
1284
1633
extra = if kind == :select, do: [type: type!(kind, ix_query, expr, ix, field)], else: []
1285
1634
field = field_source(get_source!(kind, ix_query, ix), field)
 
@@ -1329,16 +1678,21 @@ defmodule Ecto.Query.Planner do
1329
1678
# So it is best to be consistent and not support query-dumping of
1330
1679
# non-base types.
1331
1680
if is_binary(value) and Ecto.Type.type(type) in [:binary_id, :binary, :uuid] do
1332
- error = "cannot encode value `#{inspect v}` of type `#{inspect(type)}` within a query, please interpolate (using ^) instead"
1333
- error! query, expr, error
1681
+ error =
1682
+ "cannot encode value `#{inspect(v)}` of type `#{inspect(type)}` within a query, please interpolate (using ^) instead"
1683
+
1684
+ error!(query, expr, error)
1334
1685
else
1335
1686
{value, acc}
1336
1687
end
1337
1688
else
1338
1689
{:error, error} ->
1339
- error = error <> ". Or the value is incompatible or it must be " <>
1340
- "interpolated (using ^) so it may be cast accordingly"
1341
- error! query, expr, error
1690
+ error =
1691
+ error <>
1692
+ ". Or the value is incompatible or it must be " <>
1693
+ "interpolated (using ^) so it may be cast accordingly"
1694
+
1695
+ error!(query, expr, error)
1342
1696
end
1343
1697
end
1344
1698
end
 
@@ -1419,7 +1773,10 @@ defmodule Ecto.Query.Planner do
1419
1773
{fields, preprocess, {from_tag, from_source}}
1420
1774
1421
1775
:none when preloads != [] or assocs != [] ->
1422
- error! query, "the binding used in `from` must be selected in `select` when using `preload`"
1776
+ error!(
1777
+ query,
1778
+ "the binding used in `from` must be selected in `select` when using `preload`"
1779
+ )
1423
1780
1424
1781
:none ->
1425
1782
{Enum.reverse(fields), [], :none}
 
@@ -1444,14 +1801,25 @@ defmodule Ecto.Query.Planner do
1444
1801
# collected in the `from` information. Then, everything else refers to
1445
1802
# the preprocessed `from` as `{:source, :from}`.
1446
1803
1447
- defp collect_fields({:merge, _, [left, right]}, fields, from, query, take, keep_literals?, _drop) do
1804
+ defp collect_fields(
1805
+ {:merge, _, [left, right]},
1806
+ fields,
1807
+ from,
1808
+ query,
1809
+ take,
1810
+ keep_literals?,
1811
+ _drop
1812
+ ) do
1448
1813
case collect_fields(left, fields, from, query, take, keep_literals?, %{}) do
1449
1814
{{:source, :from}, fields, left_from} ->
1450
1815
{right, right_fields, _} =
1451
1816
collect_fields(right, [], left_from, query, take, keep_literals?, %{})
1452
1817
1453
1818
{from_expr, from_source, from_fields} = left_from
1454
- from = {{:merge, from_expr, right}, from_source, from_fields ++ Enum.reverse(right_fields)}
1819
+
1820
+ from =
1821
+ {{:merge, from_expr, right}, from_source, from_fields ++ Enum.reverse(right_fields)}
1822
+
1455
1823
{{:source, :from}, fields, from}
1456
1824
1457
1825
{left, left_fields, left_from} ->
 
@@ -1478,8 +1846,15 @@ defmodule Ecto.Query.Planner do
1478
1846
1479
1847
# Expression handling
1480
1848
1481
- defp collect_fields({agg, _, [{{:., dot_meta, [{:&, _, [_]}, _]}, _, []} | _]} = expr,
1482
- fields, from, _query, _take, _keep_literals?, _drop)
1849
+ defp collect_fields(
1850
+ {agg, _, [{{:., dot_meta, [{:&, _, [_]}, _]}, _, []} | _]} = expr,
1851
+ fields,
1852
+ from,
1853
+ _query,
1854
+ _take,
1855
+ _keep_literals?,
1856
+ _drop
1857
+ )
1483
1858
when agg in @aggs do
1484
1859
type =
1485
1860
case agg do
 
@@ -1491,24 +1866,48 @@ defmodule Ecto.Query.Planner do
1491
1866
# If it is possible to upcast, we do it, otherwise keep the DB value.
1492
1867
# For example, an average of integers will return a decimal, which can't be cast
1493
1868
# as an integer. But an average of "moneys" should be upcast.
1494
- _ -> {:maybe, Keyword.fetch!(dot_meta, :type)}
1869
+ _ -> {:try, Keyword.fetch!(dot_meta, :type)}
1495
1870
end
1496
1871
1497
1872
{{:value, type}, [expr | fields], from}
1498
1873
end
1499
1874
1500
- defp collect_fields({:filter, _, [call, _]} = expr, fields, from, query, take, keep_literals?, _drop) do
1875
+ defp collect_fields(
1876
+ {:filter, _, [call, _]} = expr,
1877
+ fields,
1878
+ from,
1879
+ query,
1880
+ take,
1881
+ keep_literals?,
1882
+ _drop
1883
+ ) do
1501
1884
case call do
1502
- {agg, _, _} when agg in @aggs -> :ok
1503
- {:fragment, _, [_ | _]} -> :ok
1504
- _ -> error!(query, "filter(...) expects the first argument to be an aggregate expression, got: `#{Macro.to_string(expr)}`")
1885
+ {agg, _, _} when agg in @aggs ->
1886
+ :ok
1887
+
1888
+ {:fragment, _, [_ | _]} ->
1889
+ :ok
1890
+
1891
+ _ ->
1892
+ error!(
1893
+ query,
1894
+ "filter(...) expects the first argument to be an aggregate expression, got: `#{Macro.to_string(expr)}`"
1895
+ )
1505
1896
end
1506
1897
1507
1898
{type, _, _} = collect_fields(call, fields, from, query, take, keep_literals?, %{})
1508
1899
{type, [expr | fields], from}
1509
1900
end
1510
1901
1511
- defp collect_fields({:coalesce, _, [left, right]} = expr, fields, from, query, take, _keep_literals?, _drop) do
1902
+ defp collect_fields(
1903
+ {:coalesce, _, [left, right]} = expr,
1904
+ fields,
1905
+ from,
1906
+ query,
1907
+ take,
1908
+ _keep_literals?,
1909
+ _drop
1910
+ ) do
1512
1911
{left_type, _, _} = collect_fields(left, fields, from, query, take, true, %{})
1513
1912
{right_type, _, _} = collect_fields(right, fields, from, query, take, true, %{})
1514
1913
 
@@ -1516,22 +1915,39 @@ defmodule Ecto.Query.Planner do
1516
1915
{type, [expr | fields], from}
1517
1916
end
1518
1917
1519
- defp collect_fields({:over, _, [call, window]} = expr, fields, from, query, take, keep_literals?, _drop) do
1918
+ defp collect_fields(
1919
+ {:over, _, [call, window]} = expr,
1920
+ fields,
1921
+ from,
1922
+ query,
1923
+ take,
1924
+ keep_literals?,
1925
+ _drop
1926
+ ) do
1520
1927
if is_atom(window) and not Keyword.has_key?(query.windows, window) do
1521
- error!(query, "unknown window #{inspect window} given to over/2")
1928
+ error!(query, "unknown window #{inspect(window)} given to over/2")
1522
1929
end
1523
1930
1524
1931
{type, _, _} = collect_fields(call, fields, from, query, take, keep_literals?, %{})
1525
1932
{type, [expr | fields], from}
1526
1933
end
1527
1934
1528
- defp collect_fields({{:., dot_meta, [{:&, _, [_]}, _]}, _, []} = expr,
1529
- fields, from, _query, _take, _keep_literals?, _drop) do
1935
+ defp collect_fields(
1936
+ {{:., dot_meta, [{:&, _, [_]}, _]}, _, []} = expr,
1937
+ fields,
1938
+ from,
1939
+ _query,
1940
+ _take,
1941
+ _keep_literals?,
1942
+ _drop
1943
+ ) do
1530
1944
{{:value, Keyword.fetch!(dot_meta, :type)}, [expr | fields], from}
1531
1945
end
1532
1946
1533
1947
defp collect_fields({left, right}, fields, from, query, take, keep_literals?, _drop) do
1534
- {args, fields, from} = collect_args([left, right], fields, from, query, take, keep_literals?, [])
1948
+ {args, fields, from} =
1949
+ collect_args([left, right], fields, from, query, take, keep_literals?, [])
1950
+
1535
1951
{{:tuple, args}, fields, from}
1536
1952
end
1537
1953
 
@@ -1540,7 +1956,15 @@ defmodule Ecto.Query.Planner do
1540
1956
{{:tuple, args}, fields, from}
1541
1957
end
1542
1958
1543
- defp collect_fields({:%{}, _, [{:|, _, [data, args]}]}, fields, from, query, take, keep_literals?, _drop) do
1959
+ defp collect_fields(
1960
+ {:%{}, _, [{:|, _, [data, args]}]},
1961
+ fields,
1962
+ from,
1963
+ query,
1964
+ take,
1965
+ keep_literals?,
1966
+ _drop
1967
+ ) do
1544
1968
drop = Map.new(args, fn {key, _} -> {key, nil} end)
1545
1969
{data, fields, from} = collect_fields(data, fields, from, query, take, keep_literals?, drop)
1546
1970
{args, fields, from} = collect_kv(args, fields, from, query, take, keep_literals?, [])
 
@@ -1552,8 +1976,15 @@ defmodule Ecto.Query.Planner do
1552
1976
{{:map, args}, fields, from}
1553
1977
end
1554
1978
1555
- defp collect_fields({:%, _, [name, {:%{}, _, [{:|, _, [data, args]}]}]},
1556
- fields, from, query, take, keep_literals?, _drop) do
1979
+ defp collect_fields(
1980
+ {:%, _, [name, {:%{}, _, [{:|, _, [data, args]}]}]},
1981
+ fields,
1982
+ from,
1983
+ query,
1984
+ take,
1985
+ keep_literals?,
1986
+ _drop
1987
+ ) do
1557
1988
drop = Map.new(args, fn {key, _} -> {key, nil} end)
1558
1989
{data, fields, from} = collect_fields(data, fields, from, query, take, keep_literals?, drop)
1559
1990
{args, fields, from} = collect_kv(args, fields, from, query, take, keep_literals?, [])
 
@@ -1561,27 +1992,52 @@ defmodule Ecto.Query.Planner do
1561
1992
{{:struct, name, data, args}, fields, from}
1562
1993
end
1563
1994
1564
- defp collect_fields({:%, _, [name, {:%{}, _, args}]}, fields, from, query, take, keep_literals?, _drop) do
1995
+ defp collect_fields(
1996
+ {:%, _, [name, {:%{}, _, args}]},
1997
+ fields,
1998
+ from,
1999
+ query,
2000
+ take,
2001
+ keep_literals?,
2002
+ _drop
2003
+ ) do
1565
2004
{args, fields, from} = collect_kv(args, fields, from, query, take, keep_literals?, [])
1566
2005
struct!(name, args)
1567
2006
{{:struct, name, args}, fields, from}
1568
2007
end
1569
2008
1570
- defp collect_fields({:date_add, _, [arg | _]} = expr, fields, from, query, take, keep_literals?, _drop) do
2009
+ defp collect_fields(
2010
+ {:date_add, _, [arg | _]} = expr,
2011
+ fields,
2012
+ from,
2013
+ query,
2014
+ take,
2015
+ keep_literals?,
2016
+ _drop
2017
+ ) do
1571
2018
case collect_fields(arg, fields, from, query, take, keep_literals?, %{}) do
1572
2019
{{:value, :any}, _, _} -> {{:value, :date}, [expr | fields], from}
1573
2020
{type, _, _} -> {type, [expr | fields], from}
1574
2021
end
1575
2022
end
1576
2023
1577
- defp collect_fields({:datetime_add, _, [arg | _]} = expr, fields, from, query, take, keep_literals?, _drop) do
2024
+ defp collect_fields(
2025
+ {:datetime_add, _, [arg | _]} = expr,
2026
+ fields,
2027
+ from,
2028
+ query,
2029
+ take,
2030
+ keep_literals?,
2031
+ _drop
2032
+ ) do
1578
2033
case collect_fields(arg, fields, from, query, take, keep_literals?, %{}) do
1579
2034
{{:value, :any}, _, _} -> {{:value, :naive_datetime}, [expr | fields], from}
1580
2035
{type, _, _} -> {type, [expr | fields], from}
1581
2036
end
1582
2037
end
1583
2038
1584
- defp collect_fields(args, fields, from, query, take, keep_literals?, _drop) when is_list(args) do
2039
+ defp collect_fields(args, fields, from, query, take, keep_literals?, _drop)
2040
+ when is_list(args) do
1585
2041
{args, fields, from} = collect_args(args, fields, from, query, take, keep_literals?, [])
1586
2042
{{:list, args}, fields, from}
1587
2043
end
 
@@ -1606,7 +2062,8 @@ defmodule Ecto.Query.Planner do
1606
2062
{{:value, :any}, [nil | fields], from}
1607
2063
end
1608
2064
1609
- defp collect_fields(expr, fields, from, _query, _take, _keep_literals?, _drop) when is_atom(expr) do
2065
+ defp collect_fields(expr, fields, from, _query, _take, _keep_literals?, _drop)
2066
+ when is_atom(expr) do
1610
2067
{expr, fields, from}
1611
2068
end
1612
2069
 
@@ -1615,7 +2072,15 @@ defmodule Ecto.Query.Planner do
1615
2072
{expr, fields, from}
1616
2073
end
1617
2074
1618
- defp collect_fields(%Ecto.Query.Tagged{tag: tag} = expr, fields, from, _query, _take, _keep_literals?, _drop) do
2075
+ defp collect_fields(
2076
+ %Ecto.Query.Tagged{tag: tag} = expr,
2077
+ fields,
2078
+ from,
2079
+ _query,
2080
+ _take,
2081
+ _keep_literals?,
2082
+ _drop
2083
+ ) do
1619
2084
{{:value, tag}, [expr | fields], from}
1620
2085
end
1621
2086
 
@@ -1629,7 +2094,15 @@ defmodule Ecto.Query.Planner do
1629
2094
{{:value, :boolean}, [expr | fields], from}
1630
2095
end
1631
2096
1632
- defp collect_fields({:selected_as, _, [select_expr, name]}, fields, from, query, take, keep_literals?, _drop) do
2097
+ defp collect_fields(
2098
+ {:selected_as, _, [select_expr, name]},
2099
+ fields,
2100
+ from,
2101
+ query,
2102
+ take,
2103
+ keep_literals?,
2104
+ _drop
2105
+ ) do
1633
2106
{type, _, _} = collect_fields(select_expr, fields, from, query, take, keep_literals?, %{})
1634
2107
{type, [{name, select_expr} | fields], from}
1635
2108
end
 
@@ -1652,6 +2125,7 @@ defmodule Ecto.Query.Planner do
1652
2125
{elem, fields, from} = collect_fields(elem, fields, from, query, take, keep_literals?, %{})
1653
2126
collect_args(elems, fields, from, query, take, keep_literals?, [elem | acc])
1654
2127
end
2128
+
1655
2129
defp collect_args([], fields, from, _query, _take, _keep_literals?, acc) do
1656
2130
{Enum.reverse(acc), fields, from}
1657
2131
end
 
@@ -1664,15 +2138,19 @@ defmodule Ecto.Query.Planner do
1664
2138
Map.update(acc, field, {index, children}, fn
1665
2139
{^index, current_children} ->
1666
2140
{index, merge_assocs(children ++ current_children, query)}
2141
+
1667
2142
{other_index, _} ->
1668
- error! query, "association `#{field}` is being set to binding at position #{index} " <>
1669
- "and at position #{other_index} at the same time"
2143
+ error!(
2144
+ query,
2145
+ "association `#{field}` is being set to binding at position #{index} " <>
2146
+ "and at position #{other_index} at the same time"
2147
+ )
1670
2148
end)
1671
2149
end)
1672
2150
|> Map.to_list()
1673
2151
end
1674
2152
1675
- defp collect_assocs(exprs, fields, query, tag, take, [{assoc, {ix, children}}|tail]) do
2153
+ defp collect_assocs(exprs, fields, query, tag, take, [{assoc, {ix, children}} | tail]) do
1676
2154
to_take = get_preload_source!(query, ix)
1677
2155
{fetch, take_children} = fetch_assoc(tag, take, assoc)
1678
2156
{expr, taken} = take!(to_take, query, fetch, assoc, ix, %{})
 
@@ -1682,6 +2160,7 @@ defmodule Ecto.Query.Planner do
1682
2160
{exprs, fields} = collect_assocs(exprs, fields, query, tag, take, tail)
1683
2161
{exprs, fields}
1684
2162
end
2163
+
1685
2164
defp collect_assocs(exprs, fields, _query, _tag, _take, []) do
1686
2165
{exprs, fields}
1687
2166
end
 
@@ -1701,16 +2180,19 @@ defmodule Ecto.Query.Planner do
1701
2180
defp take!(source, query, fetched, field, ix, drop) do
1702
2181
case {fetched, source} do
1703
2182
{{:ok, {:struct, _}}, {:fragment, _, _}} ->
1704
- error! query, "it is not possible to return a struct subset of a fragment"
2183
+ error!(query, "it is not possible to return a struct subset of a fragment")
1705
2184
1706
2185
{{:ok, {:struct, _}}, %Ecto.SubQuery{}} ->
1707
- error! query, "it is not possible to return a struct subset of a subquery"
2186
+ error!(query, "it is not possible to return a struct subset of a subquery")
1708
2187
1709
2188
{{:ok, {_, []}}, {_, _, _}} ->
1710
- error! query, "at least one field must be selected for binding `#{field}`, got an empty list"
2189
+ error!(
2190
+ query,
2191
+ "at least one field must be selected for binding `#{field}`, got an empty list"
2192
+ )
1711
2193
1712
2194
{{:ok, {:struct, _}}, {_, nil, _}} ->
1713
- error! query, "struct/2 in select expects a source with a schema"
2195
+ error!(query, "struct/2 in select expects a source with a schema")
1714
2196
1715
2197
{{:ok, {kind, fields}}, {source, schema, prefix}} when is_binary(source) ->
1716
2198
dumper = if schema, do: schema.__schema__(:dump), else: %{}
 
@@ -1719,14 +2201,19 @@ defmodule Ecto.Query.Planner do
1719
2201
{{:source, {source, schema}, prefix || query.prefix, types}, fields}
1720
2202
1721
2203
{{:ok, {_, fields}}, _} ->
1722
- {{:map, Enum.map(fields, &{&1, {:value, :any}})}, Enum.map(fields, &select_field(&1, ix))}
2204
+ {{:map, Enum.map(fields, &{&1, {:value, :any}})}, Enum.map(fields, &select_field(&1, ix, :always))}
1723
2205
1724
2206
{:error, {:fragment, _, _}} ->
1725
2207
{{:value, :map}, [{:&, [], [ix]}]}
1726
2208
1727
2209
{:error, {:values, _, [types, _]}} ->
1728
2210
fields = Keyword.keys(types)
1729
- dumper = types |> Enum.map(fn {field, type} -> {field, {field, type}} end) |> Enum.into(%{})
2211
+
2212
+ dumper =
2213
+ types
2214
+ |> Enum.map(fn {field, type} -> {field, {field, type, :always}} end)
2215
+ |> Enum.into(%{})
2216
+
1730
2217
{types, fields} = select_dump(fields, dumper, ix, drop)
1731
2218
{{:source, :values, nil, types}, fields}
1732
2219
 
@@ -1734,30 +2221,32 @@ defmodule Ecto.Query.Planner do
1734
2221
{{:value, :map}, [{:&, [], [ix]}]}
1735
2222
1736
2223
{:error, {source, schema, prefix}} ->
1737
- {types, fields} = select_dump(schema.__schema__(:query_fields), schema.__schema__(:dump), ix, drop)
2224
+ {types, fields} =
2225
+ select_dump(schema.__schema__(:query_fields), schema.__schema__(:dump), ix, drop)
1738
2226
1739
2227
{{:source, {source, schema}, prefix || query.prefix, types}, fields}
1740
2228
1741
2229
{:error, %Ecto.SubQuery{select: select}} ->
1742
2230
fields = subquery_source_fields(select)
1743
- {select, Enum.map(fields, &select_field(&1, ix))}
2231
+ {select, Enum.map(fields, &select_field(&1, ix, :always))}
1744
2232
end
1745
2233
end
1746
2234
1747
2235
defp select_dump(fields, dumper, ix, drop) do
1748
2236
fields
1749
- |> Enum.reverse
2237
+ |> Enum.reverse()
1750
2238
|> Enum.reduce({[], []}, fn
1751
2239
field, {types, exprs} when is_atom(field) and not is_map_key(drop, field) ->
1752
- {source, type} = Map.get(dumper, field, {field, :any})
1753
- {[{field, type} | types], [select_field(source, ix) | exprs]}
2240
+ {source, type, writable} = Map.get(dumper, field, {field, :any, :always})
2241
+ {[{field, type} | types], [select_field(source, ix, writable) | exprs]}
2242
+
1754
2243
_field, acc ->
1755
2244
acc
1756
2245
end)
1757
2246
end
1758
2247
1759
- defp select_field(field, ix) do
1760
- {{:., [], [{:&, [], [ix]}, field]}, [], []}
2248
+ defp select_field(field, ix, writable) do
2249
+ {{:., [writable: writable], [{:&, [], [ix]}, field]}, [], []}
1761
2250
end
1762
2251
1763
2252
defp get_ix!({:&, _, [ix]} = expr, _kind, query) do
 
@@ -1775,7 +2264,10 @@ defmodule Ecto.Query.Planner do
1775
2264
case query.aliases[@parent_as] do
1776
2265
%{aliases: %{^as => ix}, sources: sources} = query ->
1777
2266
if kind == :select and not (ix < tuple_size(sources)) do
1778
- error!(query, "the parent_as in a subquery select used as a join can only access the `from` binding")
2267
+ error!(
2268
+ query,
2269
+ "the parent_as in a subquery select used as a join can only access the `from` binding"
2270
+ )
1779
2271
else
1780
2272
{ix, {:parent_as, [], [as]}, query}
1781
2273
end
 
@@ -1792,17 +2284,28 @@ defmodule Ecto.Query.Planner do
1792
2284
elem(sources, ix)
1793
2285
rescue
1794
2286
ArgumentError ->
1795
- error! query, "invalid query has specified more bindings than bindings available " <>
1796
- "in `#{where}` (look for `unknown_binding!` in the printed query below)"
2287
+ error!(
2288
+ query,
2289
+ "invalid query has specified more bindings than bindings available " <>
2290
+ "in `#{where}` (look for `unknown_binding!` in the printed query below)"
2291
+ )
1797
2292
end
1798
2293
1799
2294
defp get_preload_source!(query, ix) do
1800
2295
case get_source!(:preload, query, ix) do
1801
2296
{source, schema, _} = all when is_binary(source) and schema != nil ->
1802
2297
all
2298
+
2299
+ %Ecto.SubQuery{select: {:source, {source, schema}, _, _}} = subquery
2300
+ when is_binary(source) and schema != nil ->
2301
+ subquery
2302
+
1803
2303
_ ->
1804
- error! query, "can only preload sources with a schema " <>
1805
- "(fragments, binary and subqueries are not supported)"
2304
+ error!(
2305
+ query,
2306
+ "can only preload sources with a schema" <>
2307
+ "(fragments, binaries and subqueries that do not select a schema are not supported)"
2308
+ )
1806
2309
end
1807
2310
end
1808
2311
 
@@ -1811,8 +2314,15 @@ defmodule Ecto.Query.Planner do
1811
2314
"""
1812
2315
def attach_prefix(%{prefix: nil} = query, opts) when is_list(opts) do
1813
2316
case Keyword.fetch(opts, :prefix) do
1814
- {:ok, prefix} -> %{query | prefix: prefix}
1815
- :error -> query
2317
+ {:ok, prefix} when is_binary(prefix) or is_nil(prefix) ->
2318
+ %{query | prefix: prefix}
2319
+
2320
+ {:ok, prefix} when is_atom(prefix) ->
2321
+ IO.warn("atom prefixes are deprecated. Please use a string instead.")
2322
+ %{query | prefix: prefix}
2323
+
2324
+ :error ->
2325
+ query
1816
2326
end
1817
2327
end
1818
2328
 
@@ -1824,9 +2334,21 @@ defmodule Ecto.Query.Planner do
1824
2334
1825
2335
## Helpers
1826
2336
1827
- @all_exprs [with_cte: :with_ctes, distinct: :distinct, select: :select, from: :from, join: :joins,
1828
- where: :wheres, group_by: :group_bys, having: :havings, windows: :windows,
1829
- combination: :combinations, order_by: :order_bys, limit: :limit, offset: :offset]
2337
+ @all_exprs [
2338
+ with_cte: :with_ctes,
2339
+ distinct: :distinct,
2340
+ select: :select,
2341
+ from: :from,
2342
+ join: :joins,
2343
+ where: :wheres,
2344
+ group_by: :group_bys,
2345
+ having: :havings,
2346
+ windows: :windows,
2347
+ combination: :combinations,
2348
+ order_by: :order_bys,
2349
+ limit: :limit,
2350
+ offset: :offset
2351
+ ]
1830
2352
1831
2353
# Although joins come before updates in the actual query,
1832
2354
# the on fields are moved to where, so they effectively
 
@@ -1834,11 +2356,22 @@ defmodule Ecto.Query.Planner do
1834
2356
# with parameters are not supported as a join on MySQL.
1835
2357
# The only way to address it is by splitting how join
1836
2358
# and their on expressions are processed.
1837
- @update_all_exprs [with_cte: :with_ctes, from: :from, update: :updates,
1838
- join: :joins, where: :wheres, select: :select]
2359
+ @update_all_exprs [
2360
+ with_cte: :with_ctes,
2361
+ from: :from,
2362
+ update: :updates,
2363
+ join: :joins,
2364
+ where: :wheres,
2365
+ select: :select
2366
+ ]
1839
2367
1840
- @delete_all_exprs [with_cte: :with_ctes, from: :from, join: :joins,
1841
- where: :wheres, select: :select]
2368
+ @delete_all_exprs [
2369
+ with_cte: :with_ctes,
2370
+ from: :from,
2371
+ join: :joins,
2372
+ where: :wheres,
2373
+ select: :select
2374
+ ]
1842
2375
1843
2376
# Traverse all query components with expressions.
1844
2377
# Therefore from, preload, assocs and lock are not traversed.
 
@@ -1851,19 +2384,26 @@ defmodule Ecto.Query.Planner do
1851
2384
:delete_all -> @delete_all_exprs
1852
2385
end
1853
2386
1854
- Enum.reduce exprs, {query, acc}, fn {kind, key}, {query, acc} ->
2387
+ Enum.reduce(exprs, {query, acc}, fn {kind, key}, {query, acc} ->
1855
2388
{traversed, acc} = fun.(kind, query, Map.fetch!(query, key), acc)
1856
2389
{%{query | key => traversed}, acc}
1857
- end
2390
+ end)
1858
2391
end
1859
2392
1860
2393
defp field_type!(kind, query, expr, type, allow_virtuals? \\ false)
1861
2394
1862
- defp field_type!(kind, query, expr, {composite, {ix, field}}, allow_virtuals?) when is_integer(ix) do
2395
+ defp field_type!(kind, query, expr, {composite, {ix, field}}, allow_virtuals?)
2396
+ when is_integer(ix) do
1863
2397
{composite, type!(kind, query, expr, ix, field, allow_virtuals?)}
1864
2398
end
1865
2399
1866
- defp field_type!(kind, query, expr, {composite, {{bind_kind, _, [_]} = bind_expr, field}}, allow_virtuals?)
2400
+ defp field_type!(
2401
+ kind,
2402
+ query,
2403
+ expr,
2404
+ {composite, {{bind_kind, _, [_]} = bind_expr, field}},
2405
+ allow_virtuals?
2406
+ )
1867
2407
when bind_kind in [:as, :parent_as] do
1868
2408
{ix, _, ix_query} = get_ix!(bind_expr, kind, query)
1869
2409
{composite, type!(kind, ix_query, expr, ix, field, allow_virtuals?)}
 
@@ -1898,7 +2438,7 @@ defmodule Ecto.Query.Planner do
1898
2438
type
1899
2439
1900
2440
:error ->
1901
- error! query, expr, "field `#{field}` in `#{kind}` does not exist in values list"
2441
+ error!(query, expr, "field `#{field}` in `#{kind}` does not exist in values list")
1902
2442
end
1903
2443
1904
2444
{_, schema, _} ->
 
@@ -1923,18 +2463,37 @@ defmodule Ecto.Query.Planner do
1923
2463
Map.has_key?(schema.__struct__(), field) ->
1924
2464
case schema.__schema__(:association, field) do
1925
2465
%Ecto.Association.BelongsTo{owner_key: owner_key} ->
1926
- error! query, expr, "field `#{field}` in `#{kind}` is an association in schema #{inspect schema}. " <>
1927
- "Did you mean to use `#{owner_key}`?"
2466
+ error!(
2467
+ query,
2468
+ expr,
2469
+ "field `#{field}` in `#{kind}` is an association in schema #{inspect(schema)}. " <>
2470
+ "Did you mean to use `#{owner_key}`?"
2471
+ )
2472
+
1928
2473
%_{} ->
1929
- error! query, expr, "field `#{field}` in `#{kind}` is an association in schema #{inspect schema}"
2474
+ error!(
2475
+ query,
2476
+ expr,
2477
+ "field `#{field}` in `#{kind}` is an association in schema #{inspect(schema)}"
2478
+ )
1930
2479
1931
2480
_ ->
1932
- error! query, expr, "field `#{field}` in `#{kind}` is a virtual field in schema #{inspect schema}"
2481
+ error!(
2482
+ query,
2483
+ expr,
2484
+ "field `#{field}` in `#{kind}` is a virtual field in schema #{inspect(schema)}"
2485
+ )
1933
2486
end
1934
2487
1935
2488
true ->
1936
2489
hint = closest_fields_hint(field, schema)
1937
- error! query, expr, "field `#{field}` in `#{kind}` does not exist in schema #{inspect schema}", hint
2490
+
2491
+ error!(
2492
+ query,
2493
+ expr,
2494
+ "field `#{field}` in `#{kind}` does not exist in schema #{inspect(schema)}",
2495
+ hint
2496
+ )
1938
2497
end
1939
2498
end
1940
2499
 
@@ -1944,7 +2503,7 @@ defmodule Ecto.Query.Planner do
1944
2503
schema.__schema__(:fields)
1945
2504
|> Enum.map(fn field -> {field, String.jaro_distance(input_string, Atom.to_string(field))} end)
1946
2505
|> Enum.filter(fn {_field, score} -> score >= 0.77 end)
1947
- |> Enum.sort(& elem(&1, 0) >= elem(&2, 0))
2506
+ |> Enum.sort(&(elem(&1, 0) >= elem(&2, 0)))
1948
2507
|> Enum.take(5)
1949
2508
|> Enum.map(&elem(&1, 0))
1950
2509
|> case do
 
@@ -1964,13 +2523,17 @@ defmodule Ecto.Query.Planner do
1964
2523
defp normalize_param(_kind, {:out, {:array, type}}, _value) do
1965
2524
{:ok, type}
1966
2525
end
2526
+
1967
2527
defp normalize_param(_kind, {:out, :any}, _value) do
1968
2528
{:ok, :any}
1969
2529
end
2530
+
1970
2531
defp normalize_param(kind, {:out, other}, value) do
1971
- {:error, "value `#{inspect value}` in `#{kind}` expected to be part of an array " <>
1972
- "but matched type is #{inspect other}"}
2532
+ {:error,
2533
+ "value `#{inspect(value)}` in `#{kind}` expected to be part of an array " <>
2534
+ "but matched type is #{inspect(other)}"}
1973
2535
end
2536
+
1974
2537
defp normalize_param(_kind, type, _value) do
1975
2538
{:ok, type}
1976
2539
end
 
@@ -1979,8 +2542,18 @@ defmodule Ecto.Query.Planner do
1979
2542
case Ecto.Type.cast(type, v) do
1980
2543
{:ok, v} ->
1981
2544
{:ok, v}
1982
- _ ->
1983
- {:error, "value `#{inspect v}` in `#{kind}` cannot be cast to type #{Ecto.Type.format(type)}"}
2545
+
2546
+ :error ->
2547
+ {:error,
2548
+ "value `#{inspect(v)}` in `#{kind}` cannot be cast to type #{Ecto.Type.format(type)}"}
2549
+
2550
+ {:error, _meta} ->
2551
+ {:error,
2552
+ "value `#{inspect(v)}` in `#{kind}` cannot be cast to type #{Ecto.Type.format(type)}"}
2553
+
2554
+ other ->
2555
+ raise "expected #{inspect(type)}.cast/1 to return {:ok, v}, :error, or {:error, meta}" <>
2556
+ ", got: #{inspect(other)}"
1984
2557
end
1985
2558
end
1986
2559
 
@@ -1988,8 +2561,9 @@ defmodule Ecto.Query.Planner do
1988
2561
case Ecto.Type.adapter_dump(adapter, type, v) do
1989
2562
{:ok, v} ->
1990
2563
{:ok, v}
2564
+
1991
2565
:error ->
1992
- {:error, "value `#{inspect v}` cannot be dumped to type #{Ecto.Type.format(type)}"}
2566
+ {:error, "value `#{inspect(v)}` cannot be dumped to type #{Ecto.Type.format(type)}"}
1993
2567
end
1994
2568
end
1995
2569
 
@@ -1998,6 +2572,7 @@ defmodule Ecto.Query.Planner do
1998
2572
# which will be checked and raise later.
1999
2573
schema.__schema__(:field_source, field) || field
2000
2574
end
2575
+
2001
2576
defp field_source(_, field) do
2002
2577
field
2003
2578
end
 
@@ -2027,46 +2602,84 @@ defmodule Ecto.Query.Planner do
2027
2602
defp cte_fields([], [], _aliases), do: []
2028
2603
2029
2604
defp assert_update!(%Ecto.Query{updates: updates} = query, operation) do
2605
+ dumper = dumper_for_update(query)
2606
+
2030
2607
changes =
2031
2608
Enum.reduce(updates, %{}, fn update, acc ->
2032
2609
Enum.reduce(update.expr, acc, fn {_op, kw}, acc ->
2033
2610
Enum.reduce(kw, acc, fn {k, v}, acc ->
2034
2611
if Map.has_key?(acc, k) do
2035
- error! query, "duplicate field `#{k}` for `#{operation}`"
2036
- else
2037
- Map.put(acc, k, v)
2612
+ error!(query, "duplicate field `#{k}` for `#{operation}`")
2038
2613
end
2614
+
2615
+ case dumper do
2616
+ %{^k => {_, _, :always}} -> :ok
2617
+ %{} -> error!(query, "cannot update non-updatable field `#{inspect(k)}`")
2618
+ nil -> :ok
2619
+ end
2620
+
2621
+ Map.put(acc, k, v)
2039
2622
end)
2040
2623
end)
2041
2624
end)
2042
2625
2043
2626
if changes == %{} do
2044
- error! query, "`#{operation}` requires at least one field to be updated"
2627
+ error!(query, "`#{operation}` requires at least one field to be updated")
2045
2628
end
2046
2629
end
2047
2630
2048
2631
defp assert_no_update!(query, operation) do
2049
2632
case query do
2050
- %Ecto.Query{updates: []} -> query
2633
+ %Ecto.Query{updates: []} ->
2634
+ query
2635
+
2051
2636
_ ->
2052
- error! query, "`#{operation}` does not allow `update` expressions"
2637
+ error!(query, "`#{operation}` does not allow `update` expressions")
2053
2638
end
2054
2639
end
2055
2640
2056
2641
defp assert_only_filter_expressions!(query, operation) do
2057
2642
case query do
2058
- %Ecto.Query{order_bys: [], limit: nil, offset: nil, group_bys: [],
2059
- havings: [], preloads: [], assocs: [], distinct: nil, lock: nil,
2060
- windows: [], combinations: []} ->
2643
+ %Ecto.Query{
2644
+ order_bys: [],
2645
+ limit: nil,
2646
+ offset: nil,
2647
+ group_bys: [],
2648
+ havings: [],
2649
+ preloads: [],
2650
+ assocs: [],
2651
+ distinct: nil,
2652
+ lock: nil,
2653
+ windows: [],
2654
+ combinations: []
2655
+ } ->
2061
2656
query
2657
+
2062
2658
_ when operation == :delete_all ->
2063
- error! query, "`#{operation}` allows only `with_cte`, `where`, `select`, and `join` expressions. " <>
2064
- "You can exclude unwanted expressions from a query by using " <>
2065
- "Ecto.Query.exclude/2. Error found"
2659
+ error!(
2660
+ query,
2661
+ "`#{operation}` allows only `with_cte`, `where`, `select`, and `join` expressions. " <>
2662
+ "You can exclude unwanted expressions from a query by using " <>
2663
+ "Ecto.Query.exclude/2. Error found"
2664
+ )
2665
+
2066
2666
_ ->
2067
- error! query, "`#{operation}` allows only `with_cte`, `where` and `join` expressions. " <>
2068
- "You can exclude unwanted expressions from a query by using " <>
2069
- "Ecto.Query.exclude/2. Error found"
2667
+ error!(
2668
+ query,
2669
+ "`#{operation}` allows only `with_cte`, `where` and `join` expressions. " <>
2670
+ "You can exclude unwanted expressions from a query by using " <>
2671
+ "Ecto.Query.exclude/2. Error found"
2672
+ )
2673
+ end
2674
+ end
2675
+
2676
+ defp dumper_for_update(query) do
2677
+ case get_source!(:updates, query, 0) do
2678
+ {source, schema, _} when is_binary(source) and schema != nil ->
2679
+ schema.__schema__(:dump)
2680
+
2681
+ _ ->
2682
+ nil
2070
2683
end
2071
2684
end
2072
2685
 
@@ -2083,6 +2696,11 @@ defmodule Ecto.Query.Planner do
2083
2696
end
2084
2697
2085
2698
defp error!(query, expr, message, hint) do
2086
- raise Ecto.QueryError, message: message, query: query, file: expr.file, line: expr.line, hint: hint
2699
+ raise Ecto.QueryError,
2700
+ message: message,
2701
+ query: query,
2702
+ file: expr.file,
2703
+ line: expr.line,
2704
+ hint: hint
2087
2705
end
2088
2706
end
changed lib/ecto/queryable.ex
 
@@ -1,6 +1,102 @@
1
1
defprotocol Ecto.Queryable do
2
2
@moduledoc """
3
3
Converts a data structure into an `Ecto.Query`.
4
+
5
+ This is used by `Ecto.Repo` and also `from` macro. For example, `Repo.all`
6
+ expects any queryable as argument, which is why you can do `Repo.all(MySchema)`
7
+ or `Repo.all(query)`. Furthermore, when you write `from ALIAS in QUERYABLE`,
8
+ `QUERYABLE` accepts any data structure that implements `Ecto.Queryable`.
9
+
10
+ This module defines a few default implementations so let us go over each and
11
+ how to use them.
12
+
13
+ ## Atom
14
+
15
+ The most common use case for this protocol is to convert atoms representing
16
+ an `Ecto.Schema` module into a query. This is what happens when you write:
17
+
18
+ query = from(p in Person)
19
+
20
+ Or when you directly pass a schema to a repository:
21
+
22
+ Repo.all(Person)
23
+
24
+ In case you did not know, Elixir modules are just atoms. This implementation
25
+ takes the provided module name and then tries to load the associated schema.
26
+ If no schema exists, it will raise `Protocol.UndefinedError`.
27
+
28
+ ## BitString
29
+
30
+ This implementation allows you to directly specify a table that you would like
31
+ to query from:
32
+
33
+ from(
34
+ p in "people",
35
+ select: {p.first_name, p.last_name}
36
+ )
37
+
38
+ Or:
39
+
40
+ Repo.delete_all("people")
41
+
42
+ While this is quite simple to use, some repository operations, such as
43
+ `Repo.all`, require a `select` clause. When you query a schema, the
44
+ select is automatically defined for you based on the schema fields,
45
+ but when you pass a table directly, you need to explicitly list them.
46
+ This limitation now brings us to our next implementation!
47
+
48
+ ## Tuple
49
+
50
+ Similar to the `BitString` implementation, this allows you to specify the
51
+ underlying table that you would like to query; however, this additionally
52
+ allows you to specify the schema you would like to use:
53
+
54
+ from(p in {"filtered_people", Person})
55
+
56
+ This can be particularly useful if you have database views that filter or
57
+ aggregate the underlying data of a table but share the same schema. This means
58
+ that you can reuse the same schema while specifying a separate "source" for
59
+ the data.
60
+
61
+ ## Ecto.Query
62
+
63
+ This is a simple pass through. After all, all `Ecto.Query` instances
64
+ can be converted into `Ecto.Query`:
65
+
66
+ Repo.all(from u in User, where: u.active)
67
+
68
+ This also enables Ecto queries to compose, since we can pass one query
69
+ as the source of another:
70
+
71
+ active_users = from u in User, where: u.active
72
+ ordered_active_users = from u in active_users, order_by: u.created_at
73
+
74
+ ## Ecto.SubQuery
75
+
76
+ Ecto also allows you to compose queries using subqueries. Imagine you
77
+ have a table of "people". Now imagine that you want to do something with
78
+ people with the most common last names. To get that list, you could write
79
+ something like:
80
+
81
+ sub = from(
82
+ p in Person,
83
+ group_by: p.last_name,
84
+ having: count(p.last_name) > 1,
85
+ select: %{last_name: p.last_name, count: count(p.last_name)}
86
+ )
87
+
88
+ Now if you want to do something else with this data, perhaps join on
89
+ additional tables and perform some calculations, you can do that as so:
90
+
91
+ from(
92
+ p in subquery(sub),
93
+ # other filtering etc here
94
+ )
95
+
96
+ Please note that the `Ecto.Query.subquery/2` is needed here to convert the
97
+ `Ecto.Query` into an instance of `Ecto.SubQuery`. This protocol then wraps
98
+ it into an `Ecto.Query`, but using the provided subquery in the FROM clause.
99
+ Please see `Ecto.Query.subquery/2` for more information.
4
100
"""
5
101
6
102
@doc """
 
@@ -39,6 +135,9 @@ defimpl Ecto.Queryable, for: Atom do
39
135
end
40
136
41
137
raise Protocol.UndefinedError, protocol: @protocol, value: module, description: message
138
+
139
+ FunctionClauseError ->
140
+ raise Protocol.UndefinedError, protocol: @protocol, value: module, description: "the given module is an embedded schema"
42
141
end
43
142
end
44
143
end
changed lib/ecto/repo.ex
 
@@ -50,7 +50,7 @@ defmodule Ecto.Repo do
50
50
using the [Telemetry](`:telemetry`) library. By default, the telemetry prefix
51
51
is based on the module name, so if your module is called
52
52
`MyApp.Repo`, the prefix will be `[:my_app, :repo]`. See the
53
- "Telemetry Events" section to see which events we recommend
53
+ ["Telemetry Events"](#module-telemetry-events) section to see which events we recommend
54
54
adapters to publish. Note that if you have multiple databases, you
55
55
should keep the `:telemetry_prefix` consistent for each repo and
56
56
use the `:repo` property in the event metadata for distinguishing
 
@@ -79,14 +79,6 @@ defmodule Ecto.Repo do
79
79
config :my_app, Repo,
80
80
url: "ecto:https://postgres:postgres@localhost/ecto_simple?ssl=true&pool_size=10"
81
81
82
- In case the URL needs to be dynamically configured, for example by
83
- reading a system environment variable, such can be done via the
84
- `c:init/2` repository callback:
85
-
86
- def init(_type, config) do
87
- {:ok, Keyword.put(config, :url, System.get_env("DATABASE_URL"))}
88
- end
89
-
90
82
## Shared options
91
83
92
84
Almost all of the repository functions outlined in this module accept the following
 
@@ -217,7 +209,7 @@ defmodule Ecto.Repo do
217
209
@aggregates [:count, :avg, :max, :min, :sum]
218
210
219
211
def config do
220
- {:ok, config} = Ecto.Repo.Supervisor.runtime_config(:runtime, __MODULE__, @otp_app, [])
212
+ {:ok, config} = Ecto.Repo.Supervisor.init_config(:runtime, __MODULE__, @otp_app, [])
221
213
config
222
214
end
223
215
 
@@ -608,6 +600,9 @@ defmodule Ecto.Repo do
608
600
@doc """
609
601
A callback executed when the repo starts or when configuration is read.
610
602
603
+ This callback is available for backwards compatibility purposes. Most
604
+ runtime configuration in Elixir today can be done via config/runtime.exs.
605
+
611
606
The first argument is the context the callback is being invoked. If it
612
607
is called because the Repo supervisor is starting, it will be `:supervisor`.
613
608
It will be `:runtime` if it is called for reading configuration without
 
@@ -626,7 +621,7 @@ defmodule Ecto.Repo do
626
621
@doc """
627
622
Returns the adapter tied to the repository.
628
623
"""
629
- @doc group: "Runtime API"
624
+ @doc group: "Config API"
630
625
@callback __adapter__ :: Ecto.Adapter.t()
631
626
632
627
@doc """
 
@@ -634,13 +629,13 @@ defmodule Ecto.Repo do
634
629
635
630
If the `c:init/2` callback is implemented in the repository,
636
631
it will be invoked with the first argument set to `:runtime`.
632
+ It does not consider the options given on `c:start_link/1`.
637
633
"""
638
- @doc group: "Runtime API"
634
+ @doc group: "Config API"
639
635
@callback config() :: Keyword.t()
640
636
641
637
@doc """
642
- Starts any connection pooling or supervision and return `{:ok, pid}`
643
- or just `:ok` if nothing needs to be done.
638
+ Starts the Repo supervision tree.
644
639
645
640
Returns `{:error, {:already_started, pid}}` if the repo is already
646
641
started or `{:error, term}` in case anything else goes wrong.
 
@@ -650,7 +645,7 @@ defmodule Ecto.Repo do
650
645
See the configuration in the moduledoc for options shared between adapters,
651
646
for adapter-specific configuration see the adapter's documentation.
652
647
"""
653
- @doc group: "Runtime API"
648
+ @doc group: "Process API"
654
649
@callback start_link(opts :: Keyword.t()) ::
655
650
{:ok, pid}
656
651
| {:error, {:already_started, pid}}
 
@@ -659,7 +654,7 @@ defmodule Ecto.Repo do
659
654
@doc """
660
655
Shuts down the repository.
661
656
"""
662
- @doc group: "Runtime API"
657
+ @doc group: "Process API"
663
658
@callback stop(timeout) :: :ok
664
659
665
660
@doc """
 
@@ -709,7 +704,7 @@ defmodule Ecto.Repo do
709
704
@doc """
710
705
Loads `data` into a schema or a map.
711
706
712
- The first argument can be a a schema module or a map (of types).
707
+ The first argument can be a schema module or a map (of types).
713
708
The first argument determines the return value: a struct or a map,
714
709
respectively.
715
710
 
@@ -759,7 +754,7 @@ defmodule Ecto.Repo do
759
754
760
755
See `c:put_dynamic_repo/1` for more information.
761
756
"""
762
- @doc group: "Runtime API"
757
+ @doc group: "Process API"
763
758
@callback get_dynamic_repo() :: atom() | pid()
764
759
765
760
@doc """
 
@@ -792,7 +787,7 @@ defmodule Ecto.Repo do
792
787
From this moment on, all future queries done by the current process will
793
788
run on `:tenant_foo`.
794
789
"""
795
- @doc group: "Runtime API"
790
+ @doc group: "Process API"
796
791
@callback put_dynamic_repo(name_or_pid :: atom() | pid()) :: atom() | pid()
797
792
798
793
## Ecto.Adapter.Queryable
 
@@ -827,7 +822,7 @@ defmodule Ecto.Repo do
827
822
in Postgres or the database in MySQL). This will be applied to all `from`
828
823
and `join`s in the query that did not have a prefix previously given
829
824
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
830
- in the schema. For more information see the "Query Prefix" section of the
825
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
831
826
`Ecto.Query` documentation.
832
827
833
828
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -853,7 +848,7 @@ defmodule Ecto.Repo do
853
848
in Postgres or the database in MySQL). This will be applied to all `from`
854
849
and `join`s in the query that did not have a prefix previously given
855
850
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
856
- in the schema. For more information see the "Query Prefix" section of the
851
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
857
852
`Ecto.Query` documentation.
858
853
859
854
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -881,7 +876,7 @@ defmodule Ecto.Repo do
881
876
in Postgres or the database in MySQL). This will be applied to all `from`
882
877
and `join`s in the query that did not have a prefix previously given
883
878
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
884
- in the schema. For more information see the "Query Prefix" section of the
879
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
885
880
`Ecto.Query` documentation.
886
881
887
882
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -912,7 +907,7 @@ defmodule Ecto.Repo do
912
907
in Postgres or the database in MySQL). This will be applied to all `from`
913
908
and `join`s in the query that did not have a prefix previously given
914
909
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
915
- in the schema. For more information see the "Query Prefix" section of the
910
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
916
911
`Ecto.Query` documentation.
917
912
918
913
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -991,7 +986,7 @@ defmodule Ecto.Repo do
991
986
in Postgres or the database in MySQL). This will be applied to all `from`
992
987
and `join`s in the query that did not have a prefix previously given
993
988
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
994
- in the schema. For more information see the "Query Prefix" section of the
989
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
995
990
`Ecto.Query` documentation.
996
991
997
992
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -1051,7 +1046,7 @@ defmodule Ecto.Repo do
1051
1046
in Postgres or the database in MySQL). This will be applied to all `from`
1052
1047
and `join`s in the query that did not have a prefix previously given
1053
1048
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
1054
- in the schema. For more information see the "Query Prefix" section of the
1049
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
1055
1050
`Ecto.Query` documentation.
1056
1051
1057
1052
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -1084,7 +1079,7 @@ defmodule Ecto.Repo do
1084
1079
in Postgres or the database in MySQL). This will be applied to all `from`
1085
1080
and `join`s in the query that did not have a prefix previously given
1086
1081
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
1087
- in the schema. For more information see the "Query Prefix" section of the
1082
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
1088
1083
`Ecto.Query` documentation.
1089
1084
1090
1085
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -1112,7 +1107,7 @@ defmodule Ecto.Repo do
1112
1107
in Postgres or the database in MySQL). This will be applied to all `from`
1113
1108
and `join`s in the query that did not have a prefix previously given
1114
1109
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
1115
- in the schema. For more information see the "Query Prefix" section of the
1110
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
1116
1111
`Ecto.Query` documentation.
1117
1112
1118
1113
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -1130,7 +1125,17 @@ defmodule Ecto.Repo do
1130
1125
database.
1131
1126
1132
1127
In case the association was already loaded, preload won't attempt
1133
- to reload it.
1128
+ to reload it. Preload assumes each association has the same nested
1129
+ associations already loaded. If this is not the case, it is
1130
+ possible to lose information. For example:
1131
+
1132
+ comment1 = TestRepo.preload(comment1, [author: [:permalink]])
1133
+ TestRepo.preload([comment1, comment2], :author)
1134
+
1135
+ If both comments are associated to the same author, the first comment
1136
+ will lose its nested `:permalink` association because the second comment
1137
+ does not have it preloaded. To avoid this, you must preload the nested
1138
+ associations as well.
1134
1139
1135
1140
If you want to reset the loaded fields, see `Ecto.reset_fields/2`.
1136
1141
 
@@ -1238,16 +1243,17 @@ defmodule Ecto.Repo do
1238
1243
@callback default_options(operation) :: Keyword.t()
1239
1244
when operation:
1240
1245
:all
1241
- | :insert_all
1242
- | :update_all
1246
+ | :delete
1243
1247
| :delete_all
1248
+ | :insert
1249
+ | :insert_all
1250
+ | :insert_or_update
1251
+ | :preload
1252
+ | :reload
1244
1253
| :stream
1245
1254
| :transaction
1246
- | :insert
1247
1255
| :update
1248
- | :delete
1249
- | :insert_or_update
1250
-
1256
+ | :update_all
1251
1257
@doc """
1252
1258
Fetches all entries from the data store matching the given query.
1253
1259
 
@@ -1259,7 +1265,7 @@ defmodule Ecto.Repo do
1259
1265
in Postgres or the database in MySQL). This will be applied to all `from`
1260
1266
and `join`s in the query that did not have a prefix previously given
1261
1267
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
1262
- in the schema. For more information see the "Query Prefix" section of the
1268
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
1263
1269
`Ecto.Query` documentation.
1264
1270
1265
1271
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -1290,7 +1296,7 @@ defmodule Ecto.Repo do
1290
1296
in Postgres or the database in MySQL). This will be applied to all `from`
1291
1297
and `join`s in the query that did not have a prefix previously given
1292
1298
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
1293
- in the schema. For more information see the "Query Prefix" section of the
1299
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
1294
1300
`Ecto.Query` documentation.
1295
1301
1296
1302
* `:max_rows` - The number of rows to load from the database as we stream.
 
@@ -1332,7 +1338,7 @@ defmodule Ecto.Repo do
1332
1338
in Postgres or the database in MySQL). This will be applied to all `from`
1333
1339
and `join`s in the query that did not have a prefix previously given
1334
1340
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
1335
- in the schema. For more information see the "Query Prefix" section of the
1341
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
1336
1342
`Ecto.Query` documentation.
1337
1343
1338
1344
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -1381,7 +1387,7 @@ defmodule Ecto.Repo do
1381
1387
in Postgres or the database in MySQL). This will be applied to all `from`
1382
1388
and `join`s in the query that did not have a prefix previously given
1383
1389
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
1384
- in the schema. For more information see the "Query Prefix" section of the
1390
+ in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
1385
1391
`Ecto.Query` documentation.
1386
1392
1387
1393
See the ["Shared options"](#module-shared-options) section at the module
 
@@ -1421,9 +1427,11 @@ defmodule Ecto.Repo do
1421
1427
both (`{"users", MyApp.User}`) as the first argument. The second
1422
1428
argument is a list of entries to be inserted, either as keyword
1423
1429
lists or as maps. The keys of the entries are the field names as
1424
- atoms and the value should be the respective value for the field
1425
- type or, optionally, an `Ecto.Query` that returns a single entry
1426
- with a single value.
1430
+ atoms, when a schema module is specified in the first argument.
1431
+ Otherwise, the keys can be either atoms or strings representing
1432
+ the names of the columns in the underlying datastore. The value
1433
+ should be the respective value for the field type or, optionally,
1434
+ an `Ecto.Query` that returns a single entry with a single value.
1427
1435
1428
1436
It returns a tuple containing the number of entries
1429
1437
and any returned result as second element. If the database
 
@@ -1462,7 +1470,7 @@ defmodule Ecto.Repo do
1462
1470
* `:on_conflict` - It may be one of `:raise` (the default), `:nothing`,
1463
1471
`:replace_all`, `{:replace_all_except, fields}`, `{:replace, fields}`,
1464
1472
a keyword list of update instructions or an `Ecto.Query`
1465
- query for updates. See the "Upserts" section for more information.
1473
+ query for updates. See the "[Upserts](#c:insert_all/3-upserts)" section for more information.
1466
1474
1467
1475
* `:conflict_target` - A list of column names to verify for conflicts.
1468
1476
It is expected those columns to have unique indexes on them that may conflict.
 
@@ -1470,8 +1478,8 @@ defmodule Ecto.Repo do
1470
1478
It may also be `{:unsafe_fragment, binary_fragment}` to pass any
1471
1479
expression to the database without any sanitization, this is useful
1472
1480
for partial index or index with expressions, such as
1473
- `{:unsafe_fragment, "(coalesce(firstname, ""), coalesce(lastname, "")) WHERE middlename IS NULL"}` for
1474
- `ON CONFLICT (coalesce(firstname, ""), coalesce(lastname, "")) WHERE middlename IS NULL` SQL query.
1481
+ `{:unsafe_fragment, "(coalesce(firstname, ''), coalesce(lastname, '')) WHERE middlename IS NULL"}` for
1482
+ `ON CONFLICT (coalesce(firstname, ''), coalesce(lastname, '')) WHERE middlename IS NULL` SQL query.
1475
1483
1476
1484
* `:placeholders` - A map with placeholders. This feature is not supported
1477
1485
by all databases. See the "Placeholders" section for more information.
 
@@ -1483,7 +1491,8 @@ defmodule Ecto.Repo do
1483
1491
1484
1492
A query can be given instead of a list with entries. This query needs to select
1485
1493
into a map containing only keys that are available as writeable columns in the
1486
- schema.
1494
+ schema. This will query and insert the values all inside one query, without
1495
+ another round trip to the application.
1487
1496
1488
1497
## Examples
1489
1498
 
@@ -1581,11 +1590,11 @@ defmodule Ecto.Repo do
1581
1590
"""
1582
1591
@doc group: "Schema API"
1583
1592
@callback insert_all(
1584
- schema_or_source :: binary | {binary, module} | module,
1585
- entries_or_query :: [%{atom => value} | Keyword.t(value)] | Ecto.Query.t(),
1593
+ schema_or_source :: binary() | {binary(), module()} | module(),
1594
+ entries_or_query :: [%{(atom() | String.t()) => value} | Keyword.t(value)] | Ecto.Query.t(),
1586
1595
opts :: Keyword.t()
1587
- ) :: {non_neg_integer, nil | [term]}
1588
- when value: term | Ecto.Query.t()
1596
+ ) :: {non_neg_integer(), nil | [term()]}
1597
+ when value: term() | Ecto.Query.t()
1589
1598
1590
1599
@doc """
1591
1600
Inserts a struct defined via `Ecto.Schema` or a changeset.
 
@@ -1612,8 +1621,8 @@ defmodule Ecto.Repo do
1612
1621
aware that the fields returned from the database overwrite what was
1613
1622
supplied by the user. Any field not returned by the database will be
1614
1623
present with the original value supplied by the user. Not all databases
1615
- support this option and it may not be available during upserts. See
1616
- the "Upserts" section for more information.
1624
+ support this option and it may not be available during upserts.
1625
+ See the ["Upserts"](`c:insert/2#upserts`) section for more information.
1617
1626
1618
1627
* `:prefix` - The prefix to run the query on (such as the schema path
1619
1628
in Postgres or the database in MySQL). This overrides the prefix set
 
@@ -1624,7 +1633,7 @@ defmodule Ecto.Repo do
1624
1633
* `:on_conflict` - It may be one of `:raise` (the default), `:nothing`,
1625
1634
`:replace_all`, `{:replace_all_except, fields}`, `{:replace, fields}`,
1626
1635
a keyword list of update instructions or an `Ecto.Query` query for updates.
1627
- See the "Upserts" section for more information.
1636
+ See the ["Upserts"](`c:insert/2#upserts`) section for more information.
1628
1637
1629
1638
* `:conflict_target` - A list of column names to verify for conflicts.
1630
1639
It is expected those columns to have unique indexes on them that may conflict.
 
@@ -1642,6 +1651,10 @@ defmodule Ecto.Repo do
1642
1651
* `:stale_error_message` - The message to add to the configured
1643
1652
`:stale_error_field` when stale errors happen, defaults to "is stale".
1644
1653
1654
+ * `:allow_stale` - Doesn't error if insert is stale. Defaults to `false`.
1655
+ This may happen if there are rules or triggers in the database that
1656
+ rejects the insert operation.
1657
+
1645
1658
See the ["Shared options"](#module-shared-options) section at the module
1646
1659
documentation for more options.
1647
1660
 
@@ -1758,6 +1771,32 @@ defmodule Ecto.Repo do
1758
1771
inserting a struct with associations and using the `:on_conflict` option
1759
1772
at the same time is not recommended, as Ecto will be unable to actually
1760
1773
track the proper status of the association.
1774
+
1775
+ ## Advanced Upserts
1776
+
1777
+ Using an `Ecto.Query` for `:on_conflict` can allow us to use more advanced
1778
+ database features. For example, PostgreSQL supports conditional upserts like
1779
+ `DO UPDATE SET title = EXCLUDED.title, version = EXCLUDED.version
1780
+ WHERE EXCLUDED.version > post.version`.
1781
+ This means that the title and version will be updated only if the proposed
1782
+ row has a greater version value than the existing row.
1783
+
1784
+ Ecto can support this as follows:
1785
+
1786
+ conflict_query =
1787
+ from(p in Post,
1788
+ update: [set: [
1789
+ title: fragment("EXCLUDED.title"),
1790
+ version: fragment("EXCLUDED.version")
1791
+ ]],
1792
+ where: fragment("EXCLUDED.version > ?", p.version)
1793
+ )
1794
+
1795
+ MyRepo.insert(
1796
+ %Post{id: 1, title: "Ecto Upserts (Dance Remix)", version: 2},
1797
+ conflict_target: [:id],
1798
+ on_conflict: conflict_query
1799
+ )
1761
1800
"""
1762
1801
@doc group: "Schema API"
1763
1802
@callback insert(
 
@@ -1812,6 +1851,11 @@ defmodule Ecto.Repo do
1812
1851
* `:stale_error_message` - The message to add to the configured
1813
1852
`:stale_error_field` when stale errors happen, defaults to "is stale".
1814
1853
1854
+ * `:allow_stale` - Doesn't error if update is stale. Defaults to `false`.
1855
+ This may happen if the struct has been deleted from the database before
1856
+ the update or if there is a rule or a trigger on the database that rejects
1857
+ the update operation.
1858
+
1815
1859
See the ["Shared options"](#module-shared-options) section at the module
1816
1860
documentation for more options.
1817
1861
 
@@ -1856,6 +1900,8 @@ defmodule Ecto.Repo do
1856
1900
* `:stale_error_message` - The message to add to the configured
1857
1901
`:stale_error_field` when stale errors happen, defaults to "is stale".
1858
1902
Only applies to updates.
1903
+ * `:allow_stale` - Doesn't error if delete is stale. Defaults to `false`.
1904
+ Only applies to updates.
1859
1905
1860
1906
See the ["Shared options"](#module-shared-options) section at the module
1861
1907
documentation for more options.
 
@@ -1916,6 +1962,11 @@ defmodule Ecto.Repo do
1916
1962
* `:stale_error_message` - The message to add to the configured
1917
1963
`:stale_error_field` when stale errors happen, defaults to "is stale".
1918
1964
1965
+ * `:allow_stale` - Doesn't error if delete is stale. Defaults to `false`.
1966
+ This may happen if the struct has been deleted from the database before
1967
+ this deletion or if there is a rule or a trigger on the database that rejects
1968
+ the delete operation.
1969
+
1919
1970
See the ["Shared options"](#module-shared-options) section at the module
1920
1971
documentation for more options.
1921
1972
 
@@ -2034,7 +2085,7 @@ defmodule Ecto.Repo do
2034
2085
# operation will raise an exception.
2035
2086
end)
2036
2087
2037
- See the "Aborted transactions" section for more examples of aborted
2088
+ See the ["Aborted transactions"](`c:transaction/2#aborted-transactions`) section for more examples of aborted
2038
2089
transactions and how to handle them.
2039
2090
2040
2091
In practice, managing nested transactions can become complex quickly.
 
@@ -2079,14 +2130,14 @@ defmodule Ecto.Repo do
2079
2130
end)
2080
2131
2081
2132
If the changeset is valid, but the insert operation fails due to a database constraint,
2082
- the subsequent `repo.insert(%Failure{})` operation will raise an exception because the
2083
- database has already aborted the transaction and thus making the operation invalid.
2133
+ the subsequent `repo.insert(%Status{value: "failure"})` operation will raise an exception
2134
+ because the database has already aborted the transaction and thus making the operation invalid.
2084
2135
In Postgres, the exception would look like this:
2085
2136
2086
2137
** (Postgrex.Error) ERROR 25P02 (in_failed_sql_transaction) current transaction is aborted, commands ignored until end of transaction block
2087
2138
2088
2139
If the changeset is invalid before it reaches the database due to a validation error,
2089
- no statement is sent to the database, an `:error` tuple is returned, and `repo.insert(%Failure{})`
2140
+ no statement is sent to the database, an `:error` tuple is returned, and `repo.insert(%Status{value: "failure"})`
2090
2141
operation will execute as usual.
2091
2142
2092
2143
We have two options to deal with such scenarios:
 
@@ -2098,7 +2149,7 @@ defmodule Ecto.Repo do
2098
2149
2099
2150
Another alternative is to handle this operation outside of the transaction.
2100
2151
For example, you can choose to perform an explicit `repo.rollback` call in the
2101
- `{:error, changeset}` clause and then perform the `repo.insert(%Failure{})` outside
2152
+ `{:error, changeset}` clause and then perform the `repo.insert(%Status{value: "failure"})` outside
2102
2153
of the transaction. You might also consider using `Ecto.Multi`, as they automatically
2103
2154
rollback whenever an operation fails.
changed lib/ecto/repo/assoc.ex
 
@@ -96,12 +96,13 @@ defmodule Ecto.Repo.Assoc do
96
96
defp maybe_first(list, _), do: list
97
97
98
98
defp create_refls(idx, fields, dicts, sources) do
99
- {_source, schema, _prefix} = elem(sources, idx)
99
+ schema = get_assoc_schema(sources, idx)
100
100
101
101
Enum.map(:lists.zip(dicts, fields), fn
102
102
{{_primary_keys, _cache, dict, sub_dicts}, {field, {child_idx, child_fields}}} ->
103
+ refl = schema.__schema__(:association, field)
103
104
sub_refls = create_refls(child_idx, child_fields, sub_dicts, sources)
104
- {dict, schema.__schema__(:association, field), sub_refls}
105
+ {dict, refl, sub_refls}
105
106
end)
106
107
end
107
108
 
@@ -110,11 +111,21 @@ defmodule Ecto.Repo.Assoc do
110
111
create_accs(child_idx, child_fields, sources, %{})
111
112
end)
112
113
113
- {_source, schema, _prefix} = elem(sources, idx)
114
+ schema = get_assoc_schema(sources, idx)
114
115
115
116
case schema.__schema__(:primary_key) do
116
117
[] -> raise Ecto.NoPrimaryKeyFieldError, schema: schema
117
118
pk -> {pk, %{}, initial_dict, acc}
118
119
end
119
120
end
121
+
122
+ defp get_assoc_schema(sources, idx) do
123
+ case elem(sources, idx) do
124
+ {_, schema, _} ->
125
+ schema
126
+
127
+ %Ecto.SubQuery{select: {:source, {_, schema}, _, _}} ->
128
+ schema
129
+ end
130
+ end
120
131
end
changed lib/ecto/repo/preloader.ex
 
@@ -12,14 +12,16 @@ defmodule Ecto.Repo.Preloader do
12
12
Transforms a result set based on query preloads, loading
13
13
the associations onto their parent schema.
14
14
"""
15
- @spec query([list], Ecto.Repo.t, list, Access.t, fun, {adapter_meta :: map, opts :: Keyword.t}) :: [list]
16
- def query([], _repo_name, _preloads, _take, _fun, _tuplet), do: []
17
- def query(rows, _repo_name, [], _take, fun, _tuplet), do: Enum.map(rows, fun)
15
+ @spec query([list], Ecto.Repo.t, list, Access.t, list, fun, {adapter_meta :: map, opts :: Keyword.t}) :: [list]
16
+ def query([], _repo_name, _preloads, _take, _assocs, _fun, _tuplet), do: []
17
+ def query(rows, _repo_name, [], _take, _assocs, fun, _tuplet), do: Enum.map(rows, fun)
18
+
19
+ def query(rows, repo_name, preloads, take, assocs, fun, tuplet) do
20
+ assocs = normalize_query_assocs(assocs)
18
21
19
- def query(rows, repo_name, preloads, take, fun, tuplet) do
20
22
rows
21
23
|> extract()
22
- |> normalize_and_preload_each(repo_name, preloads, take, tuplet)
24
+ |> normalize_and_preload_each(repo_name, preloads, take, assocs, tuplet)
23
25
|> unextract(rows, fun)
24
26
end
25
27
 
@@ -41,16 +43,16 @@ defmodule Ecto.Repo.Preloader do
41
43
end
42
44
43
45
def preload(structs, repo_name, preloads, {_adapter_meta, opts} = tuplet) when is_list(structs) do
44
- normalize_and_preload_each(structs, repo_name, preloads, opts[:take], tuplet)
46
+ normalize_and_preload_each(structs, repo_name, preloads, opts[:take], %{}, tuplet)
45
47
end
46
48
47
49
def preload(struct, repo_name, preloads, {_adapter_meta, opts} = tuplet) when is_map(struct) do
48
- normalize_and_preload_each([struct], repo_name, preloads, opts[:take], tuplet) |> hd()
50
+ normalize_and_preload_each([struct], repo_name, preloads, opts[:take], %{}, tuplet) |> hd()
49
51
end
50
52
51
- defp normalize_and_preload_each(structs, repo_name, preloads, take, tuplet) do
53
+ defp normalize_and_preload_each(structs, repo_name, preloads, take, query_assocs, tuplet) do
52
54
preloads = normalize(preloads, take, preloads)
53
- preload_each(structs, repo_name, preloads, tuplet)
55
+ preload_each(structs, repo_name, preloads, query_assocs, tuplet)
54
56
rescue
55
57
e ->
56
58
# Reraise errors so we ignore the preload inner stacktrace
 
@@ -59,21 +61,21 @@ defmodule Ecto.Repo.Preloader do
59
61
60
62
## Preloading
61
63
62
- defp preload_each(structs, _repo_name, [], _tuplet), do: structs
63
- defp preload_each([], _repo_name, _preloads, _tuplet), do: []
64
- defp preload_each(structs, repo_name, preloads, tuplet) do
64
+ defp preload_each(structs, _repo_name, [], _query_assocs, _tuplet), do: structs
65
+ defp preload_each([], _repo_name, _preloads, _query_assocs, _tuplet), do: []
66
+ defp preload_each(structs, repo_name, preloads, query_assocs, tuplet) do
65
67
if sample = Enum.find(structs, & &1) do
66
68
module = sample.__struct__
67
69
prefix = preload_prefix(tuplet, sample)
68
- {assocs, throughs, embeds} = expand(module, preloads, {%{}, %{}, []})
70
+ {assocs, throughs, embeds} = expand(module, preloads, query_assocs, {%{}, [], []})
69
71
structs = preload_embeds(structs, embeds, repo_name, tuplet)
72
+ structs = preload_throughs(structs, throughs, repo_name, query_assocs, tuplet)
70
73
71
74
{fetched_assocs, to_fetch_queries} =
72
75
prepare_queries(structs, module, assocs, prefix, repo_name, tuplet)
73
76
74
77
fetched_queries = maybe_pmap(to_fetch_queries, repo_name, tuplet)
75
- assocs = preload_assocs(fetched_assocs, fetched_queries, repo_name, tuplet)
76
- throughs = Map.values(throughs)
78
+ assocs = preload_assocs(fetched_assocs, fetched_queries, repo_name, query_assocs, tuplet)
77
79
78
80
for struct <- structs do
79
81
struct = Enum.reduce assocs, struct, &load_assoc/2
 
@@ -125,7 +127,7 @@ defmodule Ecto.Repo.Preloader do
125
127
126
128
# Then we execute queries in parallel
127
129
defp maybe_pmap(preloaders, _repo_name, {adapter_meta, opts}) do
128
- if match?([_,_|_], preloaders) and not adapter_meta.adapter.checked_out?(adapter_meta) and
130
+ if match?([_, _ | _] , preloaders) and not adapter_meta.adapter.checked_out?(adapter_meta) and
129
131
Keyword.get(opts, :in_parallel, true) do
130
132
# We pass caller: self() so the ownership pool knows where
131
133
# to fetch the connection from and set the proper timeouts.
 
@@ -151,23 +153,24 @@ defmodule Ecto.Repo.Preloader do
151
153
152
154
# Then we unpack the query results, merge them, and preload recursively
153
155
defp preload_assocs(
154
- [{assoc, query?, loaded_ids, loaded_structs, preloads} | assocs],
156
+ [{assoc, query?, loaded_ids, loaded_structs, sub_preloads} | assocs],
155
157
queries,
156
158
repo_name,
159
+ query_assocs,
157
160
tuplet
158
161
) do
159
162
{fetch_ids, fetch_structs, queries} = maybe_unpack_query(query?, queries)
160
- all = preload_each(Enum.reverse(loaded_structs, fetch_structs), repo_name, preloads, tuplet)
163
+ sub_query_assocs = Map.get(query_assocs, assoc.field, %{})
164
+ all = preload_each(Enum.reverse(loaded_structs, fetch_structs), repo_name, sub_preloads, sub_query_assocs, tuplet)
161
165
entry = {:assoc, assoc, assoc_map(assoc.cardinality, Enum.reverse(loaded_ids, fetch_ids), all)}
162
- [entry | preload_assocs(assocs, queries, repo_name, tuplet)]
166
+ [entry | preload_assocs(assocs, queries, repo_name, query_assocs, tuplet)]
163
167
end
164
168
165
- defp preload_assocs([], [], _repo_name, _tuplet), do: []
169
+ defp preload_assocs([] = _assocs, [] = _queries, _, _, _), do: []
166
170
167
- defp preload_embeds(structs, [], _repo_name, _tuplet), do: structs
171
+ defp preload_embeds(structs, [] = _embeds, _, _), do: structs
168
172
169
173
defp preload_embeds(structs, [embed | embeds], repo_name, tuplet) do
170
-
171
174
{%{field: field, cardinality: card}, sub_preloads} = embed
172
175
173
176
{embed_structs, counts} =
 
@@ -179,23 +182,59 @@ defmodule Ecto.Repo.Preloader do
179
182
struct, _counts -> raise ArgumentError, "expected #{inspect(struct)} to contain embed `#{field}`"
180
183
end)
181
184
182
- embed_structs = preload_each(embed_structs, repo_name, sub_preloads, tuplet)
183
- structs = load_embeds(card, field, structs, embed_structs, Enum.reverse(counts), [])
185
+ # It is not possible for an embed to be preloaded through Ecto.Query.preload
186
+ # Therefore, we don't consider associations coming from queries
187
+ embed_structs = preload_each(embed_structs, repo_name, sub_preloads, %{}, tuplet)
188
+ structs = put_through_or_embed(card, field, structs, embed_structs, Enum.reverse(counts), [])
184
189
preload_embeds(structs, embeds, repo_name, tuplet)
185
190
end
186
191
187
- defp load_embeds(_card, _field, [], [], [], acc), do: Enum.reverse(acc)
192
+ defp preload_throughs(structs, [] = _throughs, _, _, _), do: structs
188
193
189
- defp load_embeds(card, field, [struct | structs], embed_structs, [0 | counts], acc),
190
- do: load_embeds(card, field, structs, embed_structs, counts, [struct | acc])
194
+ defp preload_throughs(
195
+ structs,
196
+ [{_, _, false = _from_query?} | throughs],
197
+ repo_name,
198
+ query_assocs,
199
+ tuplet
200
+ ) do
201
+ # Through associations will not be preloaded directly unless they were
202
+ # loaded through a join using Ecto.Query.preload. When using Ecto.Repo.preload
203
+ # or Ecto.Query.preload where the through association is not part of a join,
204
+ # the chain of associations making up the through association are preloaded instead.
205
+ preload_throughs(structs, throughs, repo_name, query_assocs, tuplet)
206
+ end
191
207
192
- defp load_embeds(:one, field, [struct | structs], [embed_struct | embed_structs], [1 | counts], acc),
193
- do: load_embeds(:one, field, structs, embed_structs, counts, [Map.put(struct, field, embed_struct) | acc])
208
+ defp preload_throughs(structs, [through | throughs], repo_name, query_assocs, tuplet) do
209
+ {{_, %{field: field, cardinality: card}, _}, sub_preloads, true} = through
210
+ sub_query_assocs = Map.get(query_assocs, field, %{})
194
211
195
- defp load_embeds(:many, field, [struct | structs], embed_structs, [count | counts], acc) do
196
- {current_embeds, rest_embeds} = split_n(embed_structs, count, [])
197
- acc = [Map.put(struct, field, Enum.reverse(current_embeds)) | acc]
198
- load_embeds(:many, field, structs, rest_embeds, counts, acc)
212
+ {through_structs, counts} =
213
+ Enum.flat_map_reduce(structs, [], fn
214
+ %{^field => throughs}, counts when is_list(throughs) -> {throughs, [length(throughs) | counts]}
215
+ %{^field => nil}, counts -> {[], [0 | counts]}
216
+ %{^field => through}, counts -> {[through], [1 | counts]}
217
+ nil, counts -> {[], [0 | counts]}
218
+ struct, _counts -> raise ArgumentError, "expected #{inspect(struct)} to contain through association `#{field}`"
219
+ end)
220
+
221
+ through_structs = preload_each(through_structs, repo_name, sub_preloads, sub_query_assocs, tuplet)
222
+ structs = put_through_or_embed(card, field, structs, through_structs, Enum.reverse(counts), [])
223
+ preload_throughs(structs, throughs, repo_name, query_assocs, tuplet)
224
+ end
225
+
226
+ defp put_through_or_embed(_card, _field, [], [], [], acc), do: Enum.reverse(acc)
227
+
228
+ defp put_through_or_embed(card, field, [struct | structs], loaded_structs, [0 | counts], acc),
229
+ do: put_through_or_embed(card, field, structs, loaded_structs, counts, [struct | acc])
230
+
231
+ defp put_through_or_embed(:one, field, [struct | structs], [loaded | loaded_structs], [1 | counts], acc),
232
+ do: put_through_or_embed(:one, field, structs, loaded_structs, counts, [Map.put(struct, field, loaded) | acc])
233
+
234
+ defp put_through_or_embed(:many, field, [struct | structs], loaded_structs, [count | counts], acc) do
235
+ {current_loaded, rest_loaded} = split_n(loaded_structs, count, [])
236
+ acc = [Map.put(struct, field, Enum.reverse(current_loaded)) | acc]
237
+ put_through_or_embed(:many, field, structs, rest_loaded, counts, acc)
199
238
end
200
239
201
240
defp maybe_unpack_query(false, queries), do: {[], [], queries}
 
@@ -238,12 +277,13 @@ defmodule Ecto.Repo.Preloader do
238
277
end
239
278
end
240
279
241
- defp fetch_query(ids, assoc, _repo_name, query, _prefix, related_key, _take, _tuplet) when is_function(query, 1) do
280
+ defp fetch_query(ids, assoc, _repo_name, query, _prefix, related_key, _take, _tuplet)
281
+ when is_function(query, 1) or is_function(query, 2) do
242
282
# Note we use an explicit sort because we don't want
243
283
# to reorder based on the struct. Only the ID.
244
284
ids
245
- |> Enum.uniq
246
- |> query.()
285
+ |> Enum.uniq()
286
+ |> preload_function(assoc, query)
247
287
|> fetched_records_to_tuple_ids(assoc, related_key)
248
288
|> Enum.sort(fn {id1, _}, {id2, _} -> id1 <= id2 end)
249
289
|> unzip_ids([], [])
 
@@ -273,7 +313,7 @@ defmodule Ecto.Repo.Preloader do
273
313
query = add_preload_order(assoc.preload_order, query)
274
314
275
315
update_in query.order_bys, fn order_bys ->
276
- [%Ecto.Query.QueryExpr{expr: [asc: related_field_ast], params: [],
316
+ [%Ecto.Query.ByExpr{expr: [asc: related_field_ast], params: [],
277
317
file: __ENV__.file, line: __ENV__.line}|order_bys]
278
318
end
279
319
 
@@ -284,6 +324,9 @@ defmodule Ecto.Repo.Preloader do
284
324
unzip_ids Ecto.Repo.Queryable.all(repo_name, query, tuplet), [], []
285
325
end
286
326
327
+ defp preload_function(ids, _assoc, query) when is_function(query, 1), do: query.(ids)
328
+ defp preload_function(ids, assoc, query) when is_function(query, 2), do: query.(ids, assoc)
329
+
287
330
defp fetched_records_to_tuple_ids([], _assoc, _related_key),
288
331
do: []
289
332
 
@@ -443,11 +486,10 @@ defmodule Ecto.Repo.Preloader do
443
486
Map.put(struct, field, loaded)
444
487
end
445
488
446
- defp load_through({:through, _assoc, _throughs}, nil) do
447
- nil
448
- end
489
+ defp load_through({_, _, _}, nil), do: nil
490
+ defp load_through({_, _, true = _from_query?}, struct), do: struct
449
491
450
- defp load_through({:through, assoc, throughs}, struct) do
492
+ defp load_through({{:through, assoc, throughs}, _, false = _from_query?}, struct) do
451
493
%{cardinality: cardinality, field: field, owner: owner} = assoc
452
494
{loaded, _} = Enum.reduce(throughs, {[struct], owner}, &recur_through/2)
453
495
Map.put(struct, field, maybe_first(loaded, cardinality))
 
@@ -535,13 +577,13 @@ defmodule Ecto.Repo.Preloader do
535
577
end
536
578
537
579
defp normalize_each({atom, {query, list}}, acc, take, original)
538
- when is_atom(atom) and (is_map(query) or is_function(query, 1)) do
580
+ when is_atom(atom) and (is_map(query) or is_function(query, 1) or is_function(query, 2)) do
539
581
fields = take(take, atom)
540
582
[{atom, {fields, query!(query), normalize_each(wrap(list, original), [], fields, original)}}|acc]
541
583
end
542
584
543
585
defp normalize_each({atom, query}, acc, take, _original)
544
- when is_atom(atom) and (is_map(query) or is_function(query, 1)) do
586
+ when is_atom(atom) and (is_map(query) or is_function(query, 1) or is_function(query, 2)) do
545
587
[{atom, {take(take, atom), query!(query), []}}|acc]
546
588
end
547
589
 
@@ -559,6 +601,7 @@ defmodule Ecto.Repo.Preloader do
559
601
end
560
602
561
603
defp query!(query) when is_function(query, 1), do: query
604
+ defp query!(query) when is_function(query, 2), do: query
562
605
defp query!(%Ecto.Query{} = query), do: query
563
606
564
607
defp take(take, field) do
 
@@ -577,9 +620,19 @@ defmodule Ecto.Repo.Preloader do
577
620
"preload expects an atom, a (nested) keyword or a (nested) list of atoms"
578
621
end
579
622
623
+ defp normalize_query_assocs([]), do: %{}
624
+
625
+ defp normalize_query_assocs(assocs) when is_list(assocs) do
626
+ Enum.reduce(assocs, %{}, &normalize_each_query_assoc(&1, &2))
627
+ end
628
+
629
+ defp normalize_each_query_assoc({field, {_idx, sub_assocs}}, acc) do
630
+ Map.put(acc, field, normalize_query_assocs(sub_assocs))
631
+ end
632
+
580
633
## Expand
581
634
582
- def expand(schema, preloads, acc) do
635
+ def expand(schema, preloads, query_assocs, acc) do
583
636
Enum.reduce(preloads, acc, fn {preload, {fields, query, sub_preloads}},
584
637
{assocs, throughs, embeds} ->
585
638
assoc_or_embed = association_or_embed!(schema, preload)
 
@@ -593,13 +646,18 @@ defmodule Ecto.Repo.Preloader do
593
646
{assocs, throughs, embeds}
594
647
595
648
{:through, _, through} ->
596
- through =
597
- through
598
- |> Enum.reverse()
599
- |> Enum.reduce({fields, query, sub_preloads}, &{nil, nil, [{&1, &2}]})
600
- |> elem(2)
649
+ case query_assocs do
650
+ %{^preload => _} ->
651
+ {assocs, [{info, sub_preloads, true} | throughs], embeds}
601
652
602
- expand(schema, through, {assocs, Map.put(throughs, preload, info), embeds})
653
+ _ ->
654
+ {_, _, through} =
655
+ through
656
+ |> Enum.reverse()
657
+ |> Enum.reduce({fields, query, sub_preloads}, &{nil, nil, [{&1, &2}]})
658
+
659
+ expand(schema, through, query_assocs, {assocs, [{info, sub_preloads, false} | throughs], embeds})
660
+ end
603
661
604
662
:embed ->
605
663
if sub_preloads == [] do
changed lib/ecto/repo/queryable.ex
 
@@ -235,7 +235,7 @@ defmodule Ecto.Repo.Queryable do
235
235
{count,
236
236
rows
237
237
|> Ecto.Repo.Assoc.query(assocs, sources, preprocessor)
238
- |> Ecto.Repo.Preloader.query(name, preloads, take, postprocessor, tuplet)}
238
+ |> Ecto.Repo.Preloader.query(name, preloads, take, assocs, postprocessor, tuplet)}
239
239
end
240
240
end
241
241
 
@@ -576,7 +576,7 @@ defmodule Ecto.Repo.Queryable do
576
576
end
577
577
578
578
unless Enum.all?(structs, &(&1.__struct__ == head.__struct__)) do
579
- raise ArgumentError, "expected an homogenous list, received different struct types"
579
+ raise ArgumentError, "expected an homogeneous list, received different struct types"
580
580
end
581
581
582
582
:ok
changed lib/ecto/repo/schema.ex
 
@@ -52,7 +52,7 @@ defmodule Ecto.Repo.Schema do
52
52
on_conflict = Keyword.get(opts, :on_conflict, :raise)
53
53
conflict_target = Keyword.get(opts, :conflict_target, [])
54
54
conflict_target = conflict_target(conflict_target, dumper)
55
- {on_conflict, conflict_cast_params} = on_conflict(on_conflict, conflict_target, schema_meta, counter, adapter)
55
+ {on_conflict, conflict_cast_params} = on_conflict(on_conflict, conflict_target, schema_meta, counter, dumper, adapter)
56
56
opts = Keyword.put(opts, :cast_params, placeholder_cast_params ++ row_cast_params ++ conflict_cast_params)
57
57
58
58
{count, rows_or_query} =
 
@@ -104,43 +104,64 @@ defmodule Ecto.Repo.Schema do
104
104
{rows, header, row_cast_params, placeholder_cast_params, placeholder_dump_params, fn -> counter end}
105
105
end
106
106
107
- defp extract_header_and_fields(repo, %Ecto.Query{} = query, _schema, _dumper, _autogen_id, _placeholder_map, adapter, opts) do
107
+ defp extract_header_and_fields(repo, %Ecto.Query{} = query, _schema, dumper, _autogen_id, _placeholder_map, adapter, opts) do
108
108
{query, opts} = repo.prepare_query(:insert_all, query, opts)
109
109
query = attach_prefix(query, opts)
110
110
111
- {query, cast_params, dump_params} = Ecto.Adapter.Queryable.plan_query(:insert_all, adapter, query)
111
+ {query, cast_params, dump_params} =
112
+ Ecto.Adapter.Queryable.plan_query(:insert_all, adapter, query)
112
113
113
- ix = case query.select do
114
- %Ecto.Query.SelectExpr{expr: {:&, _, [ix]}} -> ix
115
- _ -> nil
116
- end
114
+ ix =
115
+ case query.select do
116
+ %Ecto.Query.SelectExpr{expr: {:&, _, [ix]}} -> ix
117
+ _ -> nil
118
+ end
117
119
118
- header = case query.select do
119
- %Ecto.Query.SelectExpr{expr: {:%{}, _ctx, args}} ->
120
- Enum.map(args, &elem(&1, 0))
120
+ header =
121
+ case query.select do
122
+ %Ecto.Query.SelectExpr{expr: {:%{}, [], [{:|, _, [{:&, _, [ix]}, args]}]}, fields: fields} ->
123
+ {updated_fields, updated_set} =
124
+ Enum.map_reduce(args, MapSet.new(), fn {field, _}, set ->
125
+ dumped_field = insert_all_select_dump!(field, dumper)
126
+ {dumped_field, MapSet.put(set, dumped_field)}
127
+ end)
121
128
122
- %Ecto.Query.SelectExpr{take: %{^ix => {_fun, fields}}} ->
123
- fields
129
+ unchanged_fields =
130
+ for {{:., _, [{:&, _, [^ix]}, field]}, [], []} = expr <- fields,
131
+ not MapSet.member?(updated_set, field),
132
+ do: insert_all_select_dump!(expr)
124
133
125
- _ ->
126
- raise ArgumentError, """
127
- cannot generate a fields list for insert_all from the given source query
128
- because it does not have a select clause that uses a map:
134
+ unchanged_fields ++ updated_fields
129
135
130
- #{inspect query}
136
+ %Ecto.Query.SelectExpr{expr: {:%{}, _ctx, args}} ->
137
+ Enum.map(args, fn {field, _} -> insert_all_select_dump!(field, dumper) end)
131
138
132
- Please add a select clause that selects into a map, like this:
139
+ %Ecto.Query.SelectExpr{take: %{^ix => {_fun, fields}}} ->
140
+ Enum.map(fields, &insert_all_select_dump!(&1, dumper))
133
141
134
- from x in Source,
135
- ...,
136
- select: %{
137
- field_a: x.bar,
138
- field_b: x.foo
139
- }
142
+ %Ecto.Query.SelectExpr{expr: {:&, _, [_ix]}, fields: fields} ->
143
+ Enum.map(fields, &insert_all_select_dump!(&1))
140
144
141
- All keys must exist in the schema that is being inserted into
142
- """
143
- end
145
+ _ ->
146
+ raise ArgumentError, """
147
+ cannot generate a fields list for insert_all from the given source query:
148
+
149
+ #{inspect(query)}
150
+
151
+ The select clause must be one of the following:
152
+
153
+ * A single `map/2` or several `map/2` expressions combined with `select_merge`
154
+ * A single `struct/2` or several `struct/2` expressions combined with `select_merge`
155
+ * A source such as `p` in the query `from p in Post`
156
+ * A single literal map or several literal maps combined with `select_merge`. If
157
+ combining several literal maps, there cannot be any query interpolations
158
+ except in the last `select_merge`. Consider using `Ecto.Query.exclude/2`
159
+ to rebuild the select expression from scratch if you need multiple `select_merge`
160
+ statements with interpolations
161
+
162
+ All keys must exist in the schema that is being inserted into
163
+ """
164
+ end
144
165
145
166
counter = fn -> length(dump_params) end
146
167
 
@@ -160,7 +181,7 @@ defmodule Ecto.Repo.Schema do
160
181
defp init_mapper(schema, dumper, adapter, placeholder_map) do
161
182
fn {field, value}, acc ->
162
183
case dumper do
163
- %{^field => {source, type}} ->
184
+ %{^field => {source, type, writable}} when writable != :never ->
164
185
extract_value(source, value, type, placeholder_map, acc, fn val ->
165
186
dump_field!(:insert_all, schema, field, type, val, adapter)
166
187
end)
 
@@ -168,7 +189,8 @@ defmodule Ecto.Repo.Schema do
168
189
%{} ->
169
190
raise ArgumentError,
170
191
"unknown field `#{inspect(field)}` in schema #{inspect(schema)} given to " <>
171
- "insert_all. Note virtual fields and associations are not supported"
192
+ "insert_all. Unwritable fields, such as virtual and read only fields " <>
193
+ "are not supported. Associations are also not supported"
172
194
end
173
195
end
174
196
end
 
@@ -247,6 +269,22 @@ defmodule Ecto.Repo.Schema do
247
269
{rows, Enum.reverse(cast_params), counter}
248
270
end
249
271
272
+ defp insert_all_select_dump!({{:., dot_meta, [{:&, _, [_]}, field]}, [], []}) do
273
+ if dot_meta[:writable] == :never do
274
+ raise ArgumentError, "cannot select unwritable field `#{inspect(field)}` for insert_all"
275
+ else
276
+ field
277
+ end
278
+ end
279
+
280
+ defp insert_all_select_dump!(field, dumper) when is_atom(field) do
281
+ case dumper do
282
+ %{^field => {source, _, writable}} when writable != :never -> source
283
+ %{} -> raise ArgumentError, "cannot select unwritable field `#{inspect(field)}` for insert_all"
284
+ nil -> field
285
+ end
286
+ end
287
+
250
288
defp autogenerate_id(nil, fields, header, _adapter) do
251
289
{fields, header}
252
290
end
 
@@ -324,7 +362,7 @@ defmodule Ecto.Repo.Schema do
324
362
struct = struct_from_changeset!(:insert, changeset)
325
363
schema = struct.__struct__
326
364
dumper = schema.__schema__(:dump)
327
- fields = schema.__schema__(:fields)
365
+ insertable_fields = schema.__schema__(:insertable_fields)
328
366
assocs = schema.__schema__(:associations)
329
367
embeds = schema.__schema__(:embeds)
330
368
 
@@ -341,7 +379,7 @@ defmodule Ecto.Repo.Schema do
341
379
# On insert, we always merge the whole struct into the
342
380
# changeset as changes, except the primary key if it is nil.
343
381
changeset = put_repo_and_action(changeset, :insert, repo, tuplet)
344
- changeset = Relation.surface_changes(changeset, struct, fields ++ assocs)
382
+ changeset = Relation.surface_changes(changeset, struct, insertable_fields ++ assocs)
345
383
346
384
wrap_in_transaction(adapter, adapter_meta, opts, changeset, assocs, embeds, prepare, fn ->
347
385
assoc_opts = assoc_opts(assocs, opts)
 
@@ -360,14 +398,14 @@ defmodule Ecto.Repo.Schema do
360
398
{changes, cast_extra, dump_extra, return_types, return_sources} =
361
399
autogenerate_id(autogen_id, changes, return_types, return_sources, adapter)
362
400
363
- changes = Map.take(changes, fields)
401
+ changes = Map.take(changes, insertable_fields)
364
402
autogen = autogenerate_changes(schema, :insert, changes)
365
403
366
404
dump_changes =
367
405
dump_changes!(:insert, changes, autogen, schema, dump_extra, dumper, adapter)
368
406
369
407
{on_conflict, conflict_cast_params} =
370
- on_conflict(on_conflict, conflict_target, schema_meta, fn -> length(dump_changes) end, adapter)
408
+ on_conflict(on_conflict, conflict_target, schema_meta, fn -> length(dump_changes) end, dumper, adapter)
371
409
372
410
change_values = Enum.map(changes, &elem(&1, 1))
373
411
autogen_values = Enum.map(autogen, &elem(&1, 1))
 
@@ -416,7 +454,7 @@ defmodule Ecto.Repo.Schema do
416
454
struct = struct_from_changeset!(:update, changeset)
417
455
schema = struct.__struct__
418
456
dumper = schema.__schema__(:dump)
419
- fields = schema.__schema__(:fields)
457
+ updatable_fields = schema.__schema__(:updatable_fields)
420
458
assocs = schema.__schema__(:associations)
421
459
embeds = schema.__schema__(:embeds)
422
460
 
@@ -445,7 +483,7 @@ defmodule Ecto.Repo.Schema do
445
483
if changeset.valid? do
446
484
embeds = Ecto.Embedded.prepare(changeset, embeds, adapter, :update)
447
485
448
- changes = changeset.changes |> Map.merge(embeds) |> Map.take(fields)
486
+ changes = changeset.changes |> Map.merge(embeds) |> Map.take(updatable_fields)
449
487
autogen = autogenerate_changes(schema, :update, changes)
450
488
dump_changes = dump_changes!(:update, changes, autogen, schema, [], dumper, adapter)
451
489
 
@@ -627,7 +665,7 @@ defmodule Ecto.Repo.Schema do
627
665
end
628
666
defp fields_to_sources(fields, dumper) do
629
667
Enum.reduce(fields, {[], []}, fn field, {types, sources} ->
630
- {source, type} = Map.fetch!(dumper, field)
668
+ {source, type, _writable} = Map.fetch!(dumper, field)
631
669
{[{field, type} | types], [source | sources]}
632
670
end)
633
671
end
 
@@ -687,7 +725,7 @@ defmodule Ecto.Repo.Schema do
687
725
defp conflict_target(conflict_target, dumper) do
688
726
for target <- List.wrap(conflict_target) do
689
727
case dumper do
690
- %{^target => {alias, _}} ->
728
+ %{^target => {alias, _, _}} ->
691
729
alias
692
730
%{} when is_atom(target) ->
693
731
raise ArgumentError, "unknown field `#{inspect(target)}` in conflict_target"
 
@@ -697,7 +735,7 @@ defmodule Ecto.Repo.Schema do
697
735
end
698
736
end
699
737
700
- defp on_conflict(on_conflict, conflict_target, schema_meta, counter_fun, adapter) do
738
+ defp on_conflict(on_conflict, conflict_target, schema_meta, counter_fun, dumper, adapter) do
701
739
%{source: source, schema: schema, prefix: prefix} = schema_meta
702
740
703
741
case on_conflict do
 
@@ -710,12 +748,18 @@ defmodule Ecto.Repo.Schema do
710
748
:nothing ->
711
749
{{:nothing, [], conflict_target}, []}
712
750
751
+ {:replace, []} ->
752
+ raise ArgumentError, ":on_conflict option with `{:replace, fields}` requires a non-empty list of fields"
753
+
713
754
{:replace, keys} when is_list(keys) ->
714
- fields = Enum.map(keys, &field_source!(schema, &1))
715
- {{fields, [], conflict_target}, []}
755
+ {{replace_fields!(dumper, keys), [], conflict_target}, []}
716
756
717
757
:replace_all ->
718
- {{replace_all_fields!(:replace_all, schema, []), [], conflict_target}, []}
758
+ # Remove the conflict targets from the replacing fields
759
+ # since the values don't change and this allows postgres to
760
+ # possibly perform a HOT optimization: https://www.postgresql.org/docs/current/storage-hot.html
761
+ to_remove = List.wrap(conflict_target)
762
+ {{replace_all_fields!(:replace_all, schema, to_remove), [], conflict_target}, []}
719
763
720
764
{:replace_all_except, fields} ->
721
765
{{replace_all_fields!(:replace_all_except, schema, fields), [], conflict_target}, []}
 
@@ -733,12 +777,27 @@ defmodule Ecto.Repo.Schema do
733
777
end
734
778
end
735
779
780
+ defp replace_fields!(nil, fields), do: fields
781
+
782
+ defp replace_fields!(dumper, fields) do
783
+ Enum.map(fields, fn field ->
784
+ case dumper do
785
+ %{^field => {source, _type, :always}} ->
786
+ source
787
+
788
+ _ ->
789
+ raise ArgumentError,
790
+ "cannot replace non-updatable field `#{inspect(field)}` in :on_conflict option"
791
+ end
792
+ end)
793
+ end
794
+
736
795
defp replace_all_fields!(kind, nil, _to_remove) do
737
796
raise ArgumentError, "cannot use #{inspect(kind)} on operations without a schema"
738
797
end
739
798
740
799
defp replace_all_fields!(_kind, schema, to_remove) do
741
- Enum.map(schema.__schema__(:fields) -- to_remove, &field_source!(schema, &1))
800
+ Enum.map(schema.__schema__(:updatable_fields) -- to_remove, &field_source!(schema, &1))
742
801
end
743
802
744
803
defp field_source!(nil, field) do
 
@@ -782,14 +841,18 @@ defmodule Ecto.Repo.Schema do
782
841
{:error, :stale} ->
783
842
opts = List.last(args)
784
843
785
- case Keyword.fetch(opts, :stale_error_field) do
786
- {:ok, stale_error_field} when is_atom(stale_error_field) ->
787
- stale_message = Keyword.get(opts, :stale_error_message, "is stale")
788
- user_changeset = Changeset.add_error(user_changeset, stale_error_field, stale_message, [stale: true])
789
- {:error, user_changeset}
844
+ if Keyword.get(opts, :allow_stale, false) do
845
+ {:ok, []}
846
+ else
847
+ case Keyword.fetch(opts, :stale_error_field) do
848
+ {:ok, stale_error_field} when is_atom(stale_error_field) ->
849
+ stale_message = Keyword.get(opts, :stale_error_message, "is stale")
850
+ user_changeset = Changeset.add_error(user_changeset, stale_error_field, stale_message, [stale: true])
851
+ {:error, user_changeset}
790
852
791
- _other ->
792
- raise Ecto.StaleEntryError, changeset: user_changeset, action: action
853
+ _other ->
854
+ raise Ecto.StaleEntryError, changeset: user_changeset, action: action
855
+ end
793
856
end
794
857
end
795
858
end
 
@@ -1054,7 +1117,7 @@ defmodule Ecto.Repo.Schema do
1054
1117
1055
1118
defp dump_fields!(action, schema, kw, dumper, adapter) do
1056
1119
for {field, value} <- kw do
1057
- {alias, type} = Map.fetch!(dumper, field)
1120
+ {alias, type, _writable} = Map.fetch!(dumper, field)
1058
1121
{alias, dump_field!(action, schema, field, type, value, adapter)}
1059
1122
end
1060
1123
end
changed lib/ecto/repo/supervisor.ex
 
@@ -17,7 +17,7 @@ defmodule Ecto.Repo.Supervisor do
17
17
@doc """
18
18
Retrieves the runtime configuration.
19
19
"""
20
- def runtime_config(type, repo, otp_app, opts) do
20
+ def init_config(type, repo, otp_app, opts) do
21
21
config = Application.get_env(otp_app, repo, [])
22
22
config = [otp_app: otp_app] ++ (@defaults |> Keyword.merge(config) |> Keyword.merge(opts))
23
23
config = Keyword.put_new_lazy(config, :telemetry_prefix, fn -> telemetry_prefix(repo) end)
 
@@ -35,7 +35,7 @@ defmodule Ecto.Repo.Supervisor do
35
35
defp telemetry_prefix(repo) do
36
36
repo
37
37
|> Module.split()
38
- |> Enum.map(& &1 |> Macro.underscore() |> String.to_atom())
38
+ |> Enum.map(&(&1 |> Macro.underscore() |> String.to_atom()))
39
39
end
40
40
41
41
defp repo_init(type, repo, config) do
 
@@ -58,8 +58,9 @@ defmodule Ecto.Repo.Supervisor do
58
58
end
59
59
60
60
if Code.ensure_compiled(adapter) != {:module, adapter} do
61
- raise ArgumentError, "adapter #{inspect adapter} was not compiled, " <>
62
- "ensure it is correct and it is included as a project dependency"
61
+ raise ArgumentError,
62
+ "adapter #{inspect(adapter)} was not compiled, " <>
63
+ "ensure it is correct and it is included as a project dependency"
63
64
end
64
65
65
66
behaviours =
 
@@ -125,6 +126,7 @@ defmodule Ecto.Repo.Supervisor do
125
126
126
127
defp parse_uri_query(%URI{query: nil}),
127
128
do: []
129
+
128
130
defp parse_uri_query(%URI{query: query} = url) do
129
131
query
130
132
|> URI.query_decoder()
 
@@ -150,8 +152,8 @@ defmodule Ecto.Repo.Supervisor do
150
152
151
153
_ ->
152
154
raise Ecto.InvalidURLError,
153
- url: url,
154
- message: "can not parse value `#{value}` for parameter `#{key}` as an integer"
155
+ url: url,
156
+ message: "cannot parse value `#{value}` for parameter `#{key}` as an integer"
155
157
end
156
158
end
157
159
 
@@ -174,7 +176,7 @@ defmodule Ecto.Repo.Supervisor do
174
176
# Normalize name to atom, ignore via/global names
175
177
name = if is_atom(name), do: name, else: nil
176
178
177
- case runtime_config(:supervisor, repo, otp_app, opts) do
179
+ case init_config(:supervisor, repo, otp_app, opts) do
178
180
{:ok, opts} ->
179
181
:telemetry.execute(
180
182
[:ecto, :repo, :init],
changed lib/ecto/schema.ex
 
@@ -47,9 +47,10 @@ defmodule Ecto.Schema do
47
47
However, most commonly, structs are cast, validated and manipulated with the
48
48
`Ecto.Changeset` module.
49
49
50
- Note that the name of the database table does not need to correlate to your
51
- module name. For example, if you are working with a legacy database, you can
52
- reference the table name when you define your schema:
50
+ The first argument of `schema/2` is the name of database's table, which does
51
+ not need to correlate to your module name (commonly referred to as the schema/schema name).
52
+ For example, if you are working with a legacy database, you can reference the table name
53
+ (`legacy_users`) when you define your schema (`User`):
53
54
54
55
defmodule User do
55
56
use Ecto.Schema
 
@@ -255,6 +256,7 @@ defmodule Ecto.Schema do
255
256
`:boolean` | `boolean` | true, false
256
257
`:string` | UTF-8 encoded `string` | "hello"
257
258
`:binary` | `binary` | `<<int, int, int, ...>>`
259
+ `:bitstring` | `bitstring` | `<<_::size>>
258
260
`{:array, inner_type}` | `list` | `[value, value, value, ...]`
259
261
`:map` | `map` |
260
262
`{:map, inner_type}` | `map` |
 
@@ -266,6 +268,7 @@ defmodule Ecto.Schema do
266
268
`:naive_datetime_usec` | `NaiveDateTime` |
267
269
`:utc_datetime` | `DateTime` |
268
270
`:utc_datetime_usec` | `DateTime` |
271
+ `:duration` | `Duration` |
269
272
270
273
**Notes:**
271
274
 
@@ -292,6 +295,10 @@ defmodule Ecto.Schema do
292
295
where casting has to be done explicitly and is never performed
293
296
implicitly when loading from or dumping to the database.
294
297
298
+ * For `the `:duration` type, you may need to enable `Duration` support in
299
+ your adapter. For information on how to enable it in Postgrex, see their
300
+ [HexDocs page](https://hexdocs.pm/postgrex/readme.html#data-representation).
301
+
295
302
### Custom types
296
303
297
304
Besides providing primitive types, Ecto allows custom types to be
 
@@ -437,7 +444,7 @@ defmodule Ecto.Schema do
437
444
* `__schema__(:embed, embed)` - Returns the embedding reflection of the given embed;
438
445
439
446
* `__schema__(:read_after_writes)` - Non-virtual fields that must be read back
440
- from the database after every write (insert or update);
447
+ from the database after every write (insert, update, and delete);
441
448
442
449
* `__schema__(:autogenerate_id)` - Primary key that is auto generated on insert;
443
450
* `__schema__(:autogenerate_fields)` - Returns a list of fields names that are auto
 
@@ -525,7 +532,8 @@ defmodule Ecto.Schema do
525
532
:type,
526
533
:where,
527
534
:references,
528
- :skip_default_validation
535
+ :skip_default_validation,
536
+ :writable
529
537
]
530
538
531
539
@doc """
 
@@ -539,6 +547,16 @@ defmodule Ecto.Schema do
539
547
Embedded schemas by default set the primary key type
540
548
to `:binary_id` but such can be configured with the
541
549
`@primary_key` attribute.
550
+
551
+ `belongs_to/3` associations may be defined inside of
552
+ embedded schemas. However, they are essentially read-only.
553
+ This means you may preload the associations but you may
554
+ not modify them by using `Ecto.Changeset.cast_assoc/3`
555
+ or `Ecto.Changeset.put_assoc/4`. If you would like to
556
+ modify the associations of an embedded schema, you must
557
+ change them independently. Associations nested inside of
558
+ embedded schemas will also not be persisted to the database
559
+ when calling `c:Ecto.Repo.insert/2` or `c:Ecto.Repo.update/2`.
542
560
"""
543
561
defmacro embedded_schema(do: block) do
544
562
schema(__CALLER__, nil, false, :binary_id, block)
 
@@ -658,23 +676,39 @@ defmodule Ecto.Schema do
658
676
def __schema__(:redact_fields), do: unquote(redacted_fields)
659
677
def __schema__(:virtual_fields), do: unquote(Enum.map(virtual_fields, &elem(&1, 0)))
660
678
679
+ def __schema__(:updatable_fields),
680
+ do: unquote(for {name, {_, :always}} <- fields, do: name)
681
+
682
+ def __schema__(:insertable_fields),
683
+ do: unquote(for {name, {_, writable}} when writable != :never <- fields, do: name)
684
+
661
685
def __schema__(:autogenerate_fields),
662
686
do: unquote(Enum.flat_map(autogenerate, &elem(&1, 0)))
663
687
664
- def __schema__(:query) do
665
- %Ecto.Query{
666
- from: %Ecto.Query.FromExpr{
667
- source: {unquote(source), __MODULE__},
668
- prefix: unquote(prefix)
688
+ if meta? do
689
+ def __schema__(:query) do
690
+ %Ecto.Query{
691
+ from: %Ecto.Query.FromExpr{
692
+ source: {unquote(source), __MODULE__},
693
+ prefix: unquote(prefix)
694
+ }
669
695
}
670
- }
696
+ end
671
697
end
672
698
673
699
for clauses <-
674
- Ecto.Schema.__schema__(fields, field_sources, assocs, embeds, virtual_fields),
700
+ Ecto.Schema.__schema__(
701
+ fields,
702
+ field_sources,
703
+ assocs,
704
+ embeds,
705
+ virtual_fields
706
+ ),
675
707
{args, body} <- clauses do
676
708
def __schema__(unquote_splicing(args)), do: unquote(body)
677
709
end
710
+
711
+ :ok
678
712
end
679
713
680
714
quote do
 
@@ -703,7 +737,7 @@ defmodule Ecto.Schema do
703
737
704
738
The default value is validated against the field's type at compilation time
705
739
and it will raise an ArgumentError if there is a type mismatch. If you cannot
706
- infer the field's type at compilation time, you can use the
740
+ infer the field's type at compilation time, you can use the
707
741
`:skip_default_validation` option on the field to skip validations.
708
742
709
743
Once a default value is set, if you send changes to the changeset that
 
@@ -721,7 +755,7 @@ defmodule Ecto.Schema do
721
755
A shorthand value of `true` is equivalent to `{type, :autogenerate, []}`.
722
756
723
757
* `:read_after_writes` - When true, the field is always read back
724
- from the database after insert and updates.
758
+ from the database after inserts, updates, and deletes.
725
759
726
760
For relational databases, this means the RETURNING option of those
727
761
statements is used. For this reason, MySQL does not support this
 
@@ -746,6 +780,12 @@ defmodule Ecto.Schema do
746
780
* `:skip_default_validation` - When true, it will skip the type validation
747
781
step at compile time.
748
782
783
+ * `:writable` - Defines when a field is allowed to be modified. Must be one of
784
+ `:always`, `:insert`, or `:never`. If set to `:always`, the field can be modified
785
+ by any repo operation. If set to `:insert`, the field can be inserted but cannot
786
+ be further modified, even in an upsert. If set to `:never`, the field becomes
787
+ read only. Defaults to `:always`.
788
+
749
789
"""
750
790
defmacro field(name, type \\ :string, opts \\ []) do
751
791
quote do
 
@@ -793,7 +833,8 @@ defmodule Ecto.Schema do
793
833
association, defaults to the primary key on the schema
794
834
795
835
* `:through` - Allow this association to be defined in terms of existing
796
- associations. Read the section on `:through` associations for more info
836
+ associations. Read the [section on `:through` associations](#has_many/3-has_many-has_one-through)
837
+ for more info
797
838
798
839
* `:on_delete` - The action taken on associations when parent record
799
840
is deleted. May be `:nothing` (default), `:nilify_all` and `:delete_all`.
 
@@ -826,12 +867,13 @@ defmodule Ecto.Schema do
826
867
* `:where` - A filter for the association. See "Filtering associations" below.
827
868
It does not apply to `:through` associations.
828
869
829
- * `:preload_order` - Sets the default `order_by` of the association.
830
- It is used when the association is preloaded.
870
+ * `:preload_order` - Sets the default `order_by` when preloading the association.
871
+ It may be a keyword list/list of fields or an MFA tuple, such as `{Mod, fun, []}`.
872
+ Both cases must resolve to a valid `order_by` expression.
831
873
For example, if you set `Post.has_many :comments, preload_order: [asc: :content]`,
832
874
whenever the `:comments` associations is preloaded,
833
- the comments will be order by the `:content` field.
834
- See `Ecto.Query.order_by/3` for more examples.
875
+ the comments will be ordered by the `:content` field.
876
+ See `Ecto.Query.order_by/3` to learn more about ordering expressions.
835
877
836
878
## Examples
837
879
 
@@ -1003,11 +1045,12 @@ defmodule Ecto.Schema do
1003
1045
Note `:through` associations are read-only. For example, you cannot use
1004
1046
`Ecto.Changeset.cast_assoc/3` to modify through associations.
1005
1047
"""
1006
- defmacro has_many(name, queryable, opts \\ []) do
1007
- queryable = expand_literals(queryable, __CALLER__)
1048
+ defmacro has_many(name, schema, opts \\ []) do
1049
+ schema = expand_literals(schema, __CALLER__)
1050
+ opts = expand_literals(opts, __CALLER__)
1008
1051
1009
1052
quote do
1010
- Ecto.Schema.__has_many__(__MODULE__, unquote(name), unquote(queryable), unquote(opts))
1053
+ Ecto.Schema.__has_many__(__MODULE__, unquote(name), unquote(schema), unquote(opts))
1011
1054
end
1012
1055
end
1013
1056
 
@@ -1079,11 +1122,11 @@ defmodule Ecto.Schema do
1079
1122
[post] = Repo.all(from(p in Post, where: p.id == 42, preload: :permalink))
1080
1123
post.permalink #=> %Permalink{...}
1081
1124
"""
1082
- defmacro has_one(name, queryable, opts \\ []) do
1083
- queryable = expand_literals(queryable, __CALLER__)
1125
+ defmacro has_one(name, schema, opts \\ []) do
1126
+ schema = expand_literals(schema, __CALLER__)
1084
1127
1085
1128
quote do
1086
- Ecto.Schema.__has_one__(__MODULE__, unquote(name), unquote(queryable), unquote(opts))
1129
+ Ecto.Schema.__has_one__(__MODULE__, unquote(name), unquote(schema), unquote(opts))
1087
1130
end
1088
1131
end
1089
1132
 
@@ -1286,11 +1329,11 @@ defmodule Ecto.Schema do
1286
1329
1287
1330
See `many_to_many/3` for more information on this particular approach.
1288
1331
"""
1289
- defmacro belongs_to(name, queryable, opts \\ []) do
1290
- queryable = expand_literals(queryable, __CALLER__)
1332
+ defmacro belongs_to(name, schema, opts \\ []) do
1333
+ schema = expand_literals(schema, __CALLER__)
1291
1334
1292
1335
quote do
1293
- Ecto.Schema.__belongs_to__(__MODULE__, unquote(name), unquote(queryable), unquote(opts))
1336
+ Ecto.Schema.__belongs_to__(__MODULE__, unquote(name), unquote(schema), unquote(opts))
1294
1337
end
1295
1338
end
1296
1339
 
@@ -1375,7 +1418,7 @@ defmodule Ecto.Schema do
1375
1418
* `:preload_order` - Sets the default `order_by` when preloading the association.
1376
1419
It may be a keyword list/list of fields or an MFA tuple, such as `{Mod, fun, []}`.
1377
1420
Both cases must resolve to a valid `order_by` expression. See `Ecto.Query.order_by/3`
1378
- to learn more about about ordering expressions.
1421
+ to learn more about ordering expressions.
1379
1422
See the [preload order](#many_to_many/3-preload-order) section below to learn how
1380
1423
this option can be utilized
1381
1424
 
@@ -1582,12 +1625,12 @@ defmodule Ecto.Schema do
1582
1625
{:error, changeset} -> # Handle the error
1583
1626
end
1584
1627
"""
1585
- defmacro many_to_many(name, queryable, opts \\ []) do
1586
- queryable = expand_literals(queryable, __CALLER__)
1628
+ defmacro many_to_many(name, schema, opts \\ []) do
1629
+ schema = expand_literals(schema, __CALLER__)
1587
1630
opts = expand_literals(opts, __CALLER__)
1588
1631
1589
1632
quote do
1590
- Ecto.Schema.__many_to_many__(__MODULE__, unquote(name), unquote(queryable), unquote(opts))
1633
+ Ecto.Schema.__many_to_many__(__MODULE__, unquote(name), unquote(schema), unquote(opts))
1591
1634
end
1592
1635
end
1593
1636
 
@@ -1612,8 +1655,8 @@ defmodule Ecto.Schema do
1612
1655
1613
1656
* `:primary_key` - The `:primary_key` option can be used with the same arguments
1614
1657
as `@primary_key` (see the [Schema attributes](https://hexdocs.pm/ecto/Ecto.Schema.html#module-schema-attributes)
1615
- section for more info). Primary keys are automatically set up for embedded schemas as well,
1616
- defaulting to `{:id, :binary_id, autogenerate: true}`.
1658
+ section for more info). Primary keys are automatically set up for embedded schemas as well,
1659
+ defaulting to `{:id, :binary_id, autogenerate: true}`.
1617
1660
1618
1661
* `:on_replace` - The action taken on associations when the embed is
1619
1662
replaced when casting or manipulating parent changeset. May be
 
@@ -1628,6 +1671,12 @@ defmodule Ecto.Schema do
1628
1671
selecting the whole struct in a query, such as `from p in Post, select: p`.
1629
1672
Defaults to `true`.
1630
1673
1674
+ * `:defaults_to_struct` - When true, the field will default to the initialized
1675
+ struct instead of nil, the same you would get from something like `%Order.Item{}`.
1676
+ One important thing is that if the underlying data is explicitly nil when loading
1677
+ the schema, it will still be loaded as nil, similar to how `:default` works in fields.
1678
+ Defaults to `false`.
1679
+
1631
1680
## Examples
1632
1681
1633
1682
defmodule Order do
 
@@ -1725,7 +1774,7 @@ defmodule Ecto.Schema do
1725
1774
Ecto provides this guarantee for all built-in types.
1726
1775
1727
1776
When decoding, if a key exists in the database not defined in the
1728
- schema, it'll be ignored. If a field exists in the schema thats not
1777
+ schema, it'll be ignored. If a field exists in the schema that's not
1729
1778
in the database, it's value will be `nil`.
1730
1779
"""
1731
1780
defmacro embeds_one(name, schema, opts \\ [])
 
@@ -1777,10 +1826,10 @@ defmodule Ecto.Schema do
1777
1826
maps are represented as JSON which allows Ecto to choose what works best).
1778
1827
1779
1828
The embedded may or may not have a primary key. Ecto uses the primary keys
1780
- to detect if an embed is being updated or not. If a primary is not present
1781
- and you still want the list of embeds to be updated, `:on_replace` must be
1782
- set to `:delete`, forcing all current embeds to be deleted and replaced by
1783
- new ones whenever a new list of embeds is set.
1829
+ to detect if an embed is being updated or not. If a primary key is not
1830
+ present and you still want the list of embeds to be updated, `:on_replace`
1831
+ must be set to `:delete`, forcing all current embeds to be deleted and
1832
+ replaced by new ones whenever a new list of embeds is set.
1784
1833
1785
1834
For encoding and decoding of embeds, please read the docs for
1786
1835
`embeds_one/3`.
 
@@ -1885,8 +1934,8 @@ defmodule Ecto.Schema do
1885
1934
end
1886
1935
end
1887
1936
1888
- Primary keys are automatically set up for embedded schemas as well,
1889
- defaulting to `{:id, :binary_id, autogenerate: true}`. You can
1937
+ Primary keys are automatically set up for embedded schemas as well,
1938
+ defaulting to `{:id, :binary_id, autogenerate: true}`. You can
1890
1939
customize it by passing a `:primary_key` option with the same arguments
1891
1940
as `@primary_key` (see the [Schema attributes](https://hexdocs.pm/ecto/Ecto.Schema.html#module-schema-attributes)
1892
1941
section for more info).
 
@@ -2023,6 +2072,7 @@ defmodule Ecto.Schema do
2023
2072
defp define_field(mod, name, type, opts) do
2024
2073
virtual? = opts[:virtual] || false
2025
2074
pk? = opts[:primary_key] || false
2075
+ writable = opts[:writable] || :always
2026
2076
put_struct_field(mod, name, Keyword.get(opts, :default))
2027
2077
2028
2078
if Keyword.get(opts, :redact, false) do
 
@@ -2062,6 +2112,10 @@ defmodule Ecto.Schema do
2062
2112
raise ArgumentError, "cannot mark the same field as autogenerate and read_after_writes"
2063
2113
end
2064
2114
2115
+ if writable != :always && gen do
2116
+ raise ArgumentError, "autogenerated fields must always be writable"
2117
+ end
2118
+
2065
2119
if pk? do
2066
2120
Module.put_attribute(mod, :ecto_primary_keys, name)
2067
2121
end
 
@@ -2070,7 +2124,7 @@ defmodule Ecto.Schema do
2070
2124
Module.put_attribute(mod, :ecto_query_fields, {name, type})
2071
2125
end
2072
2126
2073
- Module.put_attribute(mod, :ecto_fields, {name, type})
2127
+ Module.put_attribute(mod, :ecto_fields, {name, {type, writable}})
2074
2128
end
2075
2129
end
2076
2130
 
@@ -2197,11 +2251,19 @@ defmodule Ecto.Schema do
2197
2251
Module.put_attribute(mod, :ecto_changeset_fields, {name, {:assoc, struct}})
2198
2252
end
2199
2253
2200
- @valid_embeds_one_options [:on_replace, :source, :load_in_query]
2254
+ @valid_embeds_one_options [:on_replace, :source, :load_in_query, :defaults_to_struct]
2201
2255
2202
2256
@doc false
2203
2257
def __embeds_one__(mod, name, schema, opts) when is_atom(schema) do
2204
2258
check_options!(opts, @valid_embeds_one_options, "embeds_one/3")
2259
+
2260
+ opts =
2261
+ if Keyword.get(opts, :defaults_to_struct) do
2262
+ Keyword.put(opts, :default, schema.__schema__(:loaded))
2263
+ else
2264
+ opts
2265
+ end
2266
+
2205
2267
embed(mod, :one, name, schema, opts)
2206
2268
end
2207
2269
 
@@ -2271,7 +2333,7 @@ defmodule Ecto.Schema do
2271
2333
@doc false
2272
2334
def __schema__(fields, field_sources, assocs, embeds, virtual_fields) do
2273
2335
load =
2274
- for {name, type} <- fields do
2336
+ for {name, {type, _writable}} <- fields do
2275
2337
if alias = field_sources[name] do
2276
2338
{name, {:source, alias, type}}
2277
2339
else
 
@@ -2280,17 +2342,17 @@ defmodule Ecto.Schema do
2280
2342
end
2281
2343
2282
2344
dump =
2283
- for {name, type} <- fields do
2284
- {name, {field_sources[name] || name, type}}
2345
+ for {name, {type, writable}} <- fields do
2346
+ {name, {field_sources[name] || name, type, writable}}
2285
2347
end
2286
2348
2287
2349
field_sources_quoted =
2288
- for {name, _type} <- fields do
2350
+ for {name, {_type, _writable}} <- fields do
2289
2351
{[:field_source, name], field_sources[name] || name}
2290
2352
end
2291
2353
2292
2354
types_quoted =
2293
- for {name, type} <- fields do
2355
+ for {name, {type, _writable}} <- fields do
2294
2356
{[:type, name], Macro.escape(type)}
2295
2357
end
2296
2358
 
@@ -2347,7 +2409,7 @@ defmodule Ecto.Schema do
2347
2409
2348
2410
Module.put_attribute(mod, :ecto_changeset_fields, {name, {:embed, struct}})
2349
2411
Module.put_attribute(mod, :ecto_embeds, {name, struct})
2350
- define_field(mod, name, {:parameterized, Ecto.Embedded, struct}, opts)
2412
+ define_field(mod, name, {:parameterized, {Ecto.Embedded, struct}}, opts)
2351
2413
end
2352
2414
2353
2415
defp put_struct_field(mod, name, assoc) do
 
@@ -2381,7 +2443,7 @@ defmodule Ecto.Schema do
2381
2443
end
2382
2444
end
2383
2445
2384
- defp check_options!({:parameterized, _, _}, _opts, _valid, _fun_arity) do
2446
+ defp check_options!({:parameterized, _}, _opts, _valid, _fun_arity) do
2385
2447
:ok
2386
2448
end
2387
2449
 
@@ -2455,7 +2517,9 @@ defmodule Ecto.Schema do
2455
2517
Module.put_attribute(mod, :ecto_autogenerate, {[name], mfa})
2456
2518
end
2457
2519
2458
- defp store_type_autogenerate!(mod, name, source, {:parameterized, typemod, params} = type, pk?) do
2520
+ defp store_type_autogenerate!(mod, name, source, {:parameterized, typemod_params} = type, pk?) do
2521
+ {typemod, params} = typemod_params
2522
+
2459
2523
cond do
2460
2524
store_autogenerate_id!(mod, name, source, type, pk?) ->
2461
2525
:ok
changed lib/ecto/schema/loader.ex
 
@@ -24,7 +24,7 @@ defmodule Ecto.Schema.Loader do
24
24
@doc """
25
25
Loads data coming from the user/embeds into schema.
26
26
27
- Assumes data does not all belongs to schema/struct
27
+ Assumes data does not all belong to schema/struct
28
28
and that it may also require source-based renaming.
29
29
"""
30
30
def unsafe_load(schema, data, loader) do
 
@@ -36,7 +36,7 @@ defmodule Ecto.Schema.Loader do
36
36
@doc """
37
37
Loads data coming from the user/embeds into struct and types.
38
38
39
- Assumes data does not all belongs to schema/struct
39
+ Assumes data does not all belong to schema/struct
40
40
and that it may also require source-based renaming.
41
41
"""
42
42
def unsafe_load(struct, types, map, loader) when is_map(map) do
 
@@ -91,7 +91,7 @@ defmodule Ecto.Schema.Loader do
91
91
Dumps the given data.
92
92
"""
93
93
def safe_dump(struct, types, dumper) do
94
- Enum.reduce(types, %{}, fn {field, {source, type}}, acc ->
94
+ Enum.reduce(types, %{}, fn {field, {source, type, _writable}}, acc ->
95
95
value = Map.get(struct, field)
96
96
97
97
case dumper.(type, value) do
changed lib/ecto/type.ex
 
@@ -15,6 +15,27 @@ defmodule Ecto.Type do
15
15
basic custom types and rely on parameterized types if you need
16
16
the extra functionality.
17
17
18
+ ## External vs internal vs database representation
19
+
20
+ The core functionality of a custom type is the mapping between
21
+ external, internal and database representations of a value belonging
22
+ to the type.
23
+
24
+ For a definition of external and internal data take a look at the
25
+ [related section](`Ecto.Changeset#module-external-vs-internal-data`)
26
+ in the changeset documentation.
27
+
28
+ ```mermaid
29
+ stateDiagram-v2
30
+ external: External Data
31
+ internal: Internal Data
32
+ database: Database Data
33
+ external --> internal: cast/1
34
+ external --> database: dump/1
35
+ internal --> database: dump/1
36
+ database --> internal: load/1
37
+ ```
38
+
18
39
## Example
19
40
20
41
Imagine you want to store a URI struct as part of a schema in a
 
@@ -180,13 +201,14 @@ defmodule Ecto.Type do
180
201
@type primitive :: base | composite
181
202
182
203
@typedoc "Custom types are represented by user-defined modules."
183
- @type custom :: module | {:parameterized, module, term}
204
+ @type custom :: module | {:parameterized, {module, term}}
184
205
185
206
@type base ::
186
207
:integer
187
208
| :float
188
209
| :boolean
189
210
| :string
211
+ | :bitstring
190
212
| :map
191
213
| :binary
192
214
| :decimal
 
@@ -200,17 +222,19 @@ defmodule Ecto.Type do
200
222
| :utc_datetime_usec
201
223
| :naive_datetime_usec
202
224
| :time_usec
225
+ | :duration
203
226
204
227
@type composite :: {:array, t} | {:map, t} | private_composite
205
228
206
- @typep private_composite :: {:maybe, t} | {:in, t} | {:param, :any_datetime}
229
+ @typep private_composite :: {:try, t} | {:in, t} | {:supertype, :datetime}
207
230
208
231
@base ~w(
209
- integer float decimal boolean string map binary id binary_id any
232
+ integer float decimal boolean string bitstring map binary id binary_id any
210
233
utc_datetime naive_datetime date time
211
234
utc_datetime_usec naive_datetime_usec time_usec
235
+ duration
212
236
)a
213
- @composite ~w(array map maybe in param)a
237
+ @composite ~w(array map try in param)a
214
238
@variadic ~w(in splice)a
215
239
216
240
@doc """
 
@@ -314,7 +338,7 @@ defmodule Ecto.Type do
314
338
315
339
"""
316
340
@spec primitive?(t) :: boolean
317
- def primitive?({:parameterized, _, _}), do: true
341
+ def primitive?({:parameterized, _}), do: true
318
342
def primitive?({composite, _}) when composite in @composite, do: true
319
343
def primitive?(base) when base in @base, do: true
320
344
def primitive?(_), do: false
 
@@ -350,7 +374,7 @@ defmodule Ecto.Type do
350
374
351
375
See `c:embed_as/1`.
352
376
"""
353
- def embed_as({:parameterized, module, params}, format), do: module.embed_as(format, params)
377
+ def embed_as({:parameterized, {module, params}}, format), do: module.embed_as(format, params)
354
378
def embed_as({composite, type}, format) when composite in @composite, do: embed_as(type, format)
355
379
def embed_as(base, _format) when base in @base, do: :self
356
380
def embed_as(mod, format), do: mod.embed_as(format)
 
@@ -412,10 +436,10 @@ defmodule Ecto.Type do
412
436
"""
413
437
@spec type(t) :: t
414
438
def type(type)
415
- def type({:parameterized, type, params}), do: type.type(params)
439
+ def type({:parameterized, {type, params}}), do: type.type(params)
416
440
def type({:array, type}), do: {:array, type(type)}
417
441
def type({:map, type}), do: {:map, type(type)}
418
- def type({:maybe, type}), do: type(type)
442
+ def type({:try, type}), do: type(type)
419
443
def type(type) when type in @base, do: type
420
444
def type(type) when is_atom(type), do: type.type()
421
445
def type(type), do: type
 
@@ -456,10 +480,10 @@ defmodule Ecto.Type do
456
480
defp do_match?(:binary_id, :binary), do: true
457
481
defp do_match?(:id, :integer), do: true
458
482
defp do_match?(type, type), do: true
459
- defp do_match?(:naive_datetime, {:param, :any_datetime}), do: true
460
- defp do_match?(:naive_datetime_usec, {:param, :any_datetime}), do: true
461
- defp do_match?(:utc_datetime, {:param, :any_datetime}), do: true
462
- defp do_match?(:utc_datetime_usec, {:param, :any_datetime}), do: true
483
+ defp do_match?(:naive_datetime, {:supertype, :datetime}), do: true
484
+ defp do_match?(:naive_datetime_usec, {:supertype, :datetime}), do: true
485
+ defp do_match?(:utc_datetime, {:supertype, :datetime}), do: true
486
+ defp do_match?(:utc_datetime_usec, {:supertype, :datetime}), do: true
463
487
defp do_match?(_, _), do: false
464
488
465
489
@doc """
 
@@ -496,7 +520,7 @@ defmodule Ecto.Type do
496
520
@spec dump(t, term, (t, term -> {:ok, term} | :error)) :: {:ok, term} | :error
497
521
def dump(type, value, dumper \\ &dump/2)
498
522
499
- def dump({:parameterized, module, params}, value, dumper) do
523
+ def dump({:parameterized, {module, params}}, value, dumper) do
500
524
module.dump(value, dumper, params)
501
525
end
502
526
 
@@ -504,7 +528,7 @@ defmodule Ecto.Type do
504
528
{:ok, nil}
505
529
end
506
530
507
- def dump({:maybe, type}, value, dumper) do
531
+ def dump({:try, type}, value, dumper) do
508
532
case dump(type, value, dumper) do
509
533
{:ok, _} = ok -> ok
510
534
:error -> {:ok, value}
 
@@ -518,8 +542,10 @@ defmodule Ecto.Type do
518
542
end
519
543
end
520
544
521
- def dump({:array, {_, _, _} = type}, value, dumper), do: array(value, type, dumper, false, [])
522
- def dump({:array, type}, value, dumper), do: array(value, type, dumper, true, [])
545
+ def dump({:array, {:parameterized, _} = type}, value, dumper),
546
+ do: array_with_type(value, type, dumper, false, [])
547
+
548
+ def dump({:array, type}, value, dumper), do: array_with_type(value, type, dumper, true, [])
523
549
def dump({:map, type}, value, dumper), do: map(value, type, dumper, false, %{})
524
550
525
551
def dump(:any, value, _dumper), do: {:ok, value}
 
@@ -529,6 +555,7 @@ defmodule Ecto.Type do
529
555
def dump(:map, value, _dumper), do: same_map(value)
530
556
def dump(:string, value, _dumper), do: same_binary(value)
531
557
def dump(:binary, value, _dumper), do: same_binary(value)
558
+ def dump(:bitstring, value, _dumper), do: same_bitstring(value)
532
559
def dump(:id, value, _dumper), do: same_integer(value)
533
560
def dump(:binary_id, value, _dumper), do: same_binary(value)
534
561
def dump(:decimal, value, _dumper), do: same_decimal(value)
 
@@ -539,7 +566,8 @@ defmodule Ecto.Type do
539
566
def dump(:naive_datetime_usec, value, _dumper), do: dump_naive_datetime_usec(value)
540
567
def dump(:utc_datetime, value, _dumper), do: dump_utc_datetime(value)
541
568
def dump(:utc_datetime_usec, value, _dumper), do: dump_utc_datetime_usec(value)
542
- def dump({:param, :any_datetime}, value, _dumper), do: dump_any_datetime(value)
569
+ def dump(:duration, value, _dumper), do: same_duration(value)
570
+ def dump({:supertype, :datetime}, value, _dumper), do: dump_any_datetime(value)
543
571
def dump(mod, value, _dumper) when is_atom(mod), do: mod.dump(value)
544
572
545
573
defp dump_float(term) when is_float(term), do: {:ok, term}
 
@@ -597,7 +625,7 @@ defmodule Ecto.Type do
597
625
@spec load(t, term, (t, term -> {:ok, term} | :error)) :: {:ok, term} | :error
598
626
def load(type, value, loader \\ &load/2)
599
627
600
- def load({:parameterized, module, params}, value, loader) do
628
+ def load({:parameterized, {module, params}}, value, loader) do
601
629
module.load(value, loader, params)
602
630
end
603
631
 
@@ -605,15 +633,17 @@ defmodule Ecto.Type do
605
633
{:ok, nil}
606
634
end
607
635
608
- def load({:maybe, type}, value, loader) do
636
+ def load({:try, type}, value, loader) do
609
637
case load(type, value, loader) do
610
638
{:ok, _} = ok -> ok
611
639
:error -> {:ok, value}
612
640
end
613
641
end
614
642
615
- def load({:array, {_, _, _} = type}, value, loader), do: array(value, type, loader, false, [])
616
- def load({:array, type}, value, loader), do: array(value, type, loader, true, [])
643
+ def load({:array, {:parameterized, _} = type}, value, loader),
644
+ do: array_with_type(value, type, loader, false, [])
645
+
646
+ def load({:array, type}, value, loader), do: array_with_type(value, type, loader, true, [])
617
647
def load({:map, type}, value, loader), do: map(value, type, loader, false, %{})
618
648
619
649
def load(:any, value, _loader), do: {:ok, value}
 
@@ -623,6 +653,7 @@ defmodule Ecto.Type do
623
653
def load(:map, value, _loader), do: same_map(value)
624
654
def load(:string, value, _loader), do: same_binary(value)
625
655
def load(:binary, value, _loader), do: same_binary(value)
656
+ def load(:bitstring, value, _loader), do: same_bitstring(value)
626
657
def load(:id, value, _loader), do: same_integer(value)
627
658
def load(:binary_id, value, _loader), do: same_binary(value)
628
659
def load(:decimal, value, _loader), do: same_decimal(value)
 
@@ -633,6 +664,7 @@ defmodule Ecto.Type do
633
664
def load(:naive_datetime_usec, value, _loader), do: load_naive_datetime_usec(value)
634
665
def load(:utc_datetime, value, _loader), do: load_utc_datetime(value)
635
666
def load(:utc_datetime_usec, value, _loader), do: load_utc_datetime_usec(value)
667
+ def load(:duration, value, _loader), do: same_duration(value)
636
668
def load(mod, value, _loader), do: mod.load(value)
637
669
638
670
defp load_float(term) when is_float(term), do: {:ok, term}
 
@@ -772,11 +804,11 @@ defmodule Ecto.Type do
772
804
773
805
"""
774
806
@spec cast(t, term) :: {:ok, term} | {:error, keyword()} | :error
775
- def cast({:parameterized, type, params}, value), do: type.cast(value, params)
807
+ def cast({:parameterized, {type, params}}, value), do: type.cast(value, params)
776
808
def cast({:in, _type}, nil), do: :error
777
809
def cast(_type, nil), do: {:ok, nil}
778
810
779
- def cast({:maybe, type}, value) do
811
+ def cast({:try, type}, value) do
780
812
case cast(type, value) do
781
813
{:ok, _} = ok -> ok
782
814
_ -> {:ok, value}
 
@@ -793,6 +825,7 @@ defmodule Ecto.Type do
793
825
defp cast_fun(:map), do: &cast_map/1
794
826
defp cast_fun(:string), do: &cast_binary/1
795
827
defp cast_fun(:binary), do: &cast_binary/1
828
+ defp cast_fun(:bitstring), do: &cast_bitstring/1
796
829
defp cast_fun(:id), do: &cast_integer/1
797
830
defp cast_fun(:binary_id), do: &cast_binary/1
798
831
defp cast_fun(:any), do: &{:ok, &1}
 
@@ -804,21 +837,22 @@ defmodule Ecto.Type do
804
837
defp cast_fun(:naive_datetime_usec), do: &maybe_pad_usec(cast_naive_datetime(&1))
805
838
defp cast_fun(:utc_datetime), do: &maybe_truncate_usec(cast_utc_datetime(&1))
806
839
defp cast_fun(:utc_datetime_usec), do: &maybe_pad_usec(cast_utc_datetime(&1))
807
- defp cast_fun({:param, :any_datetime}), do: &cast_any_datetime(&1)
808
- defp cast_fun({:parameterized, mod, params}), do: &mod.cast(&1, params)
840
+ defp cast_fun(:duration), do: &cast_duration/1
841
+ defp cast_fun({:supertype, :datetime}), do: &cast_any_datetime(&1)
842
+ defp cast_fun({:parameterized, {mod, params}}), do: &mod.cast(&1, params)
809
843
defp cast_fun({qual, type}) when qual in @variadic, do: cast_fun({:array, type})
810
844
811
- defp cast_fun({:array, {:parameterized, _, _} = type}) do
845
+ defp cast_fun({:array, {:parameterized, _} = type}) do
812
846
fun = cast_fun(type)
813
- &array(&1, fun, false, [])
847
+ &array_with_index(&1, fun, false, 0, [])
814
848
end
815
849
816
850
defp cast_fun({:array, type}) do
817
851
fun = cast_fun(type)
818
- &array(&1, fun, true, [])
852
+ &array_with_index(&1, fun, true, 0, [])
819
853
end
820
854
821
- defp cast_fun({:map, {:parameterized, _, _} = type}) do
855
+ defp cast_fun({:map, {:parameterized, _} = type}) do
822
856
fun = cast_fun(type)
823
857
&map(&1, fun, false, %{})
824
858
end
 
@@ -876,9 +910,48 @@ defmodule Ecto.Type do
876
910
defp cast_binary(term) when is_binary(term), do: {:ok, term}
877
911
defp cast_binary(_), do: :error
878
912
913
+ defp cast_bitstring(term) when is_bitstring(term), do: {:ok, term}
914
+ defp cast_bitstring(_), do: :error
915
+
879
916
defp cast_map(term) when is_map(term), do: {:ok, term}
880
917
defp cast_map(_), do: :error
881
918
919
+ if Code.ensure_loaded?(Duration) do
920
+ defp cast_duration(%Duration{} = term), do: {:ok, term}
921
+ end
922
+
923
+ defp cast_duration(_), do: :error
924
+
925
+ @doc """
926
+ Casts a value to the given type or raises an error.
927
+
928
+ See `cast/2` for more information.
929
+
930
+ ## Examples
931
+
932
+ iex> Ecto.Type.cast!(:integer, "1")
933
+ 1
934
+ iex> Ecto.Type.cast!(:integer, 1)
935
+ 1
936
+ iex> Ecto.Type.cast!(:integer, nil)
937
+ nil
938
+
939
+ iex> Ecto.Type.cast!(:integer, 1.0)
940
+ ** (Ecto.CastError) cannot cast 1.0 to :integer
941
+ """
942
+ def cast!(type, value) do
943
+ case Ecto.Type.cast(type, value) do
944
+ {:ok, value} ->
945
+ value
946
+
947
+ :error ->
948
+ raise Ecto.CastError, type: type, value: value
949
+
950
+ {:error, metadata} ->
951
+ raise Ecto.CastError, [type: type, value: value] ++ Keyword.take(metadata, [:message])
952
+ end
953
+ end
954
+
882
955
## Shared helpers
883
956
884
957
@compile {:inline, same_integer: 1, same_boolean: 1, same_map: 1, same_decimal: 1, same_date: 1}
 
@@ -891,6 +964,9 @@ defmodule Ecto.Type do
891
964
defp same_binary(term) when is_binary(term), do: {:ok, term}
892
965
defp same_binary(_), do: :error
893
966
967
+ defp same_bitstring(term) when is_bitstring(term), do: {:ok, term}
968
+ defp same_bitstring(_), do: :error
969
+
894
970
defp same_map(term) when is_map(term), do: {:ok, term}
895
971
defp same_map(_), do: :error
896
972
 
@@ -902,6 +978,12 @@ defmodule Ecto.Type do
902
978
defp same_date(%Date{} = term), do: {:ok, term}
903
979
defp same_date(_), do: :error
904
980
981
+ if Code.ensure_loaded?(Duration) do
982
+ defp same_duration(%Duration{} = term), do: {:ok, term}
983
+ end
984
+
985
+ defp same_duration(_), do: :error
986
+
905
987
@doc false
906
988
def empty_trimmed_string?(value) do
907
989
is_binary(value) and String.trim_leading(value) == ""
 
@@ -1243,7 +1325,7 @@ defmodule Ecto.Type do
1243
1325
end
1244
1326
end
1245
1327
1246
- defp equal_fun({:parameterized, mod, params}) do
1328
+ defp equal_fun({:parameterized, {mod, params}}) do
1247
1329
&mod.equal?(&1, &2, params)
1248
1330
end
1249
1331
 
@@ -1302,11 +1384,7 @@ defmodule Ecto.Type do
1302
1384
@doc """
1303
1385
Format type for error messaging and logs.
1304
1386
"""
1305
- def format({composite, type}) when composite in [:array, :map] do
1306
- "{#{inspect(composite)}, #{format(type)}}"
1307
- end
1308
-
1309
- def format({:parameterized, type, params}) do
1387
+ def format({:parameterized, {type, params}}) do
1310
1388
if function_exported?(type, :format, 1) do
1311
1389
apply(type, :format, [params])
1312
1390
else
 
@@ -1314,6 +1392,10 @@ defmodule Ecto.Type do
1314
1392
end
1315
1393
end
1316
1394
1395
+ def format({composite, type}) when composite in [:array, :map] do
1396
+ "{#{inspect(composite)}, #{format(type)}}"
1397
+ end
1398
+
1317
1399
def format(type), do: inspect(type)
1318
1400
1319
1401
## Helpers
 
@@ -1331,23 +1413,35 @@ defmodule Ecto.Type do
1331
1413
defp of_base_type?(:date, value), do: Kernel.match?(%Date{}, value)
1332
1414
defp of_base_type?(_, _), do: false
1333
1415
1334
- defp array([nil | t], fun, true, acc) do
1335
- array(t, fun, true, [nil | acc])
1416
+ defp array_with_index([nil | t], fun, true, index, acc) do
1417
+ array_with_index(t, fun, true, index + 1, [nil | acc])
1336
1418
end
1337
1419
1338
- defp array([h | t], fun, skip_nil?, acc) do
1420
+ defp array_with_index([h | t], fun, skip_nil?, index, acc) do
1339
1421
case fun.(h) do
1340
- {:ok, h} -> array(t, fun, skip_nil?, [h | acc])
1341
- :error -> :error
1342
- {:error, _custom_errors} -> :error
1422
+ {:ok, h} ->
1423
+ array_with_index(t, fun, skip_nil?, index + 1, [h | acc])
1424
+
1425
+ :error ->
1426
+ :error
1427
+
1428
+ {:error, custom_errors} ->
1429
+ {:error, Keyword.update(custom_errors, :source, [index], &[index | &1])}
1343
1430
end
1344
1431
end
1345
1432
1346
- defp array([], _fun, _skip_nil?, acc) do
1433
+ defp array_with_index([], _fun, _skip_nil?, _index, acc) do
1347
1434
{:ok, Enum.reverse(acc)}
1348
1435
end
1349
1436
1350
- defp array(_, _, _, _) do
1437
+ defp array_with_index(%_{} = struct, fun, skip_nil?, index, acc) do
1438
+ case Enumerable.impl_for(struct) do
1439
+ nil -> :error
1440
+ _ -> struct |> Enum.to_list() |> array_with_index(fun, skip_nil?, index, acc)
1441
+ end
1442
+ end
1443
+
1444
+ defp array_with_index(_, _, _, _, _) do
1351
1445
:error
1352
1446
end
1353
1447
 
@@ -1365,9 +1459,14 @@ defmodule Ecto.Type do
1365
1459
1366
1460
defp map_each([{key, value} | t], fun, skip_nil?, acc) do
1367
1461
case fun.(value) do
1368
- {:ok, value} -> map_each(t, fun, skip_nil?, Map.put(acc, key, value))
1369
- :error -> :error
1370
- {:error, _custom_errors} -> :error
1462
+ {:ok, value} ->
1463
+ map_each(t, fun, skip_nil?, Map.put(acc, key, value))
1464
+
1465
+ :error ->
1466
+ :error
1467
+
1468
+ {:error, custom_errors} ->
1469
+ {:error, Keyword.update(custom_errors, :source, [key], &[key | &1])}
1371
1470
end
1372
1471
end
1373
1472
 
@@ -1375,22 +1474,22 @@ defmodule Ecto.Type do
1375
1474
{:ok, acc}
1376
1475
end
1377
1476
1378
- defp array([nil | t], type, fun, true, acc) do
1379
- array(t, type, fun, true, [nil | acc])
1477
+ defp array_with_type([nil | t], type, fun, true, acc) do
1478
+ array_with_type(t, type, fun, true, [nil | acc])
1380
1479
end
1381
1480
1382
- defp array([h | t], type, fun, skip_nil?, acc) do
1481
+ defp array_with_type([h | t], type, fun, skip_nil?, acc) do
1383
1482
case fun.(type, h) do
1384
- {:ok, h} -> array(t, type, fun, skip_nil?, [h | acc])
1483
+ {:ok, h} -> array_with_type(t, type, fun, skip_nil?, [h | acc])
1385
1484
:error -> :error
1386
1485
end
1387
1486
end
1388
1487
1389
- defp array([], _type, _fun, _skip_nil?, acc) do
1488
+ defp array_with_type([], _type, _fun, _skip_nil?, acc) do
1390
1489
{:ok, Enum.reverse(acc)}
1391
1490
end
1392
1491
1393
- defp array(_, _, _, _, _) do
1492
+ defp array_with_type(_, _, _, _, _) do
1394
1493
:error
1395
1494
end
changed lib/ecto/uuid.ex
 
@@ -24,7 +24,7 @@ defmodule Ecto.UUID do
24
24
25
25
If `uuid` is neither of these, `:error` will be returned.
26
26
27
- Since both binaries and strings are represent as binaries, this means some
27
+ Since both binaries and strings are represented as binaries, this means some
28
28
strings you may not expect are actually also valid UUIDs in their binary form
29
29
and so will be casted into their string form.
changed mix.exs
 
@@ -2,7 +2,7 @@ defmodule Ecto.MixProject do
2
2
use Mix.Project
3
3
4
4
@source_url "https://github.com/elixir-ecto/ecto"
5
- @version "3.11.2"
5
+ @version "3.12.0"
6
6
7
7
def project do
8
8
[
 
@@ -60,11 +60,12 @@ defmodule Ecto.MixProject do
60
60
skip_undefined_reference_warnings_on: ["CHANGELOG.md"],
61
61
extras: extras(),
62
62
groups_for_extras: groups_for_extras(),
63
- groups_for_functions: [
63
+ groups_for_docs: [
64
64
group_for_function("Query API"),
65
65
group_for_function("Schema API"),
66
66
group_for_function("Transaction API"),
67
- group_for_function("Runtime API"),
67
+ group_for_function("Process API"),
68
+ group_for_function("Config API"),
68
69
group_for_function("User callbacks")
69
70
],
70
71
groups_for_modules: [
 
@@ -104,7 +105,37 @@ defmodule Ecto.MixProject do
104
105
Ecto.Association.NotLoaded,
105
106
Ecto.Embedded
106
107
]
107
- ]
108
+ ],
109
+ before_closing_body_tag: fn
110
+ :html ->
111
+ """
112
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.min.js"></script>
113
+ <script>
114
+ document.addEventListener("DOMContentLoaded", function () {
115
+ mermaid.initialize({
116
+ startOnLoad: false,
117
+ theme: document.body.className.includes("dark") ? "dark" : "default"
118
+ });
119
+ let id = 0;
120
+ for (const codeEl of document.querySelectorAll("pre code.mermaid")) {
121
+ const preEl = codeEl.parentElement;
122
+ const graphDefinition = codeEl.textContent;
123
+ const graphEl = document.createElement("div");
124
+ const graphId = "mermaid-graph-" + id++;
125
+ mermaid.render(graphId, graphDefinition).then(({svg, bindFunctions}) => {
126
+ graphEl.innerHTML = svg;
127
+ bindFunctions?.(graphEl);
128
+ preEl.insertAdjacentElement("afterend", graphEl);
129
+ preEl.remove();
130
+ });
131
+ }
132
+ });
133
+ </script>
134
+ """
135
+
136
+ _ ->
137
+ ""
138
+ end
108
139
]
109
140
end