Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog for v3.x

## Unreleased

### Enhancements

* [Ecto.Repo] Preload custom queries with `order_by` now take precedence over `:preload_order`. The `:preload_order` option is now only applied when no custom query with ordering is provided.

## v3.13.3 (2025-09-19)

### Enhancements
Expand Down
17 changes: 17 additions & 0 deletions integration_test/cases/preload.exs
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,23 @@ defmodule Ecto.Integration.PreloadTest do
assert [%{name: "foz"}, %{name: "baz"}] = post2.ordered_users_by_join_table
end

test "custom query order_by overrides preload_order" do
post = TestRepo.insert!(%Post{title: "1"})

TestRepo.insert!(%Comment{text: "2", post_id: post.id})
TestRepo.insert!(%Comment{text: "1", post_id: post.id})
TestRepo.insert!(%Comment{text: "3", post_id: post.id})

# Without custom query, preload_order (asc by text) should apply
post = TestRepo.preload(post, :ordered_comments)
assert [%{text: "1"}, %{text: "2"}, %{text: "3"}] = post.ordered_comments

# With custom query having order_by, it should override preload_order
query = from(c in Comment, order_by: [desc: c.text])
post = TestRepo.preload(post, [ordered_comments: query], force: true)
assert [%{text: "3"}, %{text: "2"}, %{text: "1"}] = post.ordered_comments
end

## Others

@tag :invalid_prefix
Expand Down
5 changes: 5 additions & 0 deletions lib/ecto/repo/preloader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,11 @@ defmodule Ecto.Repo.Preloader do

defp add_preload_order([], query), do: query

defp add_preload_order(_order, %{order_bys: [_|_]} = query) do
# Skip applying preload_order when query already has custom order_by clauses
query
end

defp add_preload_order(order, query) when is_list(order) do
Ecto.Query.prepend_order_by(query, [q], ^order)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/ecto/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,8 @@ defmodule Ecto.Schema do
For example, if you set `Post.has_many :comments, preload_order: [asc: :content]`,
whenever the `:comments` associations is preloaded,
the comments will be ordered by the `:content` field.
Note that if you provide a custom query with its own `order_by` clause,
the custom ordering will take precedence and the `:preload_order` will not be applied.
See `Ecto.Query.order_by/3` to learn more about ordering expressions.

## Examples
Expand Down Expand Up @@ -1352,6 +1354,8 @@ defmodule Ecto.Schema do
It may be a keyword list/list of fields or an MFA tuple, such as `{Mod, fun, []}`.
Both cases must resolve to a valid `order_by` expression. See `Ecto.Query.order_by/3`
to learn more about ordering expressions.
Note that if you provide a custom query with its own `order_by` clause,
the custom ordering will take precedence and the `:preload_order` will not be applied.
See the [preload order](#many_to_many/3-preload-order) section below to learn how
this option can be utilized

Expand Down
Loading