Skip to content
Closed
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
54 changes: 54 additions & 0 deletions integration_test/cases/repo.exs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,60 @@ defmodule Ecto.Integration.RepoTest do
assert TestRepo.all_by(Post, title: "b") |> Enum.sort() == [post3]
end

test "select_all" do
post1 = TestRepo.insert!(%Post{title: "a"})
post2 = TestRepo.insert!(%Post{title: "b"})
post3 = TestRepo.insert!(%Post{title: "c"})

# Test with full schema
{count, posts} = TestRepo.select_all(Post)
assert count == 3
assert Enum.sort(posts) == [post1, post2, post3]

# Test with query and select
query = from p in Post, where: p.title in ["a", "b"], select: p.title
{count, titles} = TestRepo.select_all(query)
assert count == 2
assert Enum.sort(titles) == ["a", "b"]

# Test with empty result
query = from p in Post, where: p.title == "nonexistent"
{count, posts} = TestRepo.select_all(query)
assert count == 0
assert posts == []

# Test without schema
{count, titles} =
TestRepo.select_all(from(p in "posts", order_by: p.title, select: p.title))
assert count == 3
assert titles == ["a", "b", "c"]
end

test "select_all_by" do
post1 = TestRepo.insert!(%Post{title: "a"})
post2 = TestRepo.insert!(%Post{title: "a"})
post3 = TestRepo.insert!(%Post{title: "b"})

{count, posts} = TestRepo.select_all_by(Post, title: "a")
assert count == 2
assert Enum.sort(posts) == [post1, post2]

{count, posts} = TestRepo.select_all_by(Post, title: "b")
assert count == 1
assert posts == [post3]

# Test with query
query = from p in Post
{count, posts} = TestRepo.select_all_by(query, title: "a")
assert count == 2
assert Enum.sort(posts) == [post1, post2]

# Test with empty result
{count, posts} = TestRepo.select_all_by(Post, title: "nonexistent")
assert count == 0
assert posts == []
end

test "insert, update and delete" do
post = %Post{title: "insert, update, delete", visits: 1}
meta = post.__meta__
Expand Down
95 changes: 95 additions & 0 deletions lib/ecto/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,31 @@ defmodule Ecto.Repo do
)
end

def select_all(queryable, opts \\ []) do
validate_query_opts!(opts, :select_all)

repo = get_dynamic_repo()

Ecto.Repo.Queryable.select_all(
repo,
queryable,
Ecto.Repo.Supervisor.tuplet(repo, prepare_opts(:all, opts))
)
end

def select_all_by(queryable, clauses, opts \\ []) do
validate_query_opts!(opts, :select_all_by)

repo = get_dynamic_repo()

Ecto.Repo.Queryable.select_all_by(
repo,
queryable,
clauses,
Ecto.Repo.Supervisor.tuplet(repo, prepare_opts(:all, opts))
)
end

def all_by(queryable, clauses, opts \\ []) do
validate_query_opts!(opts, :all_by)

Expand Down Expand Up @@ -1485,6 +1510,76 @@ defmodule Ecto.Repo do
@doc group: "Query API"
@callback all(queryable :: Ecto.Queryable.t(), opts :: Keyword.t()) :: [Ecto.Schema.t() | term]

@doc """
Fetches all entries from the data store matching the given query.

Similar to `c:all/2`, but returns a tuple with the count of rows and the list of results.

May raise `Ecto.QueryError` if query validation fails.

## Options

* `:prefix` - The prefix to run the query on (such as the schema path
in Postgres or the database in MySQL). This will be applied to all `from`
and `join`s in the query that did not have a prefix previously given
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
`Ecto.Query` documentation.

See the ["Shared options"](#module-shared-options) section at the module
documentation for more options.

## Example

# Fetch all post titles with count
query = from p in Post,
select: p.title
{count, titles} = MyRepo.select_all(query)

# With returning the full struct
{count, posts} = from(p in Post, select: p) |> MyRepo.select_all()
"""
@doc group: "Query API"
@callback select_all(queryable :: Ecto.Queryable.t(), opts :: Keyword.t()) ::
{non_neg_integer, [Ecto.Schema.t() | term]}

@doc """
Fetches all entries from the data store matching the given query and conditions.

Similar to `c:all_by/3`, but returns a tuple with the count of rows and the list of results.

May raise `Ecto.QueryError` if query validation fails.

This function is a shortcut for `c:select_all/2` when adjusting the given query with simple conditions.

See also `c:select_all/2`, `c:all_by/3`, and `c:get_by/3`.

## Options

* `:prefix` - The prefix to run the query on (such as the schema path
in Postgres or the database in MySQL). This will be applied to all `from`
and `join`s in the query that did not have a prefix previously given
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
`Ecto.Query` documentation.

See the ["Shared options"](#module-shared-options) section at the module
documentation for more options.

## Example

{count, posts} = MyRepo.select_all_by(Post, author_id: 1)

query = from p in Post
{count, posts} = MyRepo.select_all_by(query, author_id: 1)
"""
@doc group: "Query API"
@callback select_all_by(
queryable :: Ecto.Queryable.t(),
clauses :: Keyword.t() | map,
opts :: Keyword.t()
) :: {non_neg_integer, [Ecto.Schema.t() | term]}

@doc """
Fetches all entries from the data store matching the given query and conditions.

Expand Down
16 changes: 12 additions & 4 deletions lib/ecto/repo/queryable.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,30 @@ defmodule Ecto.Repo.Queryable do

require Ecto.Query

def all(name, queryable, tuplet) do
def select_all(name, queryable, tuplet) do
query =
queryable
|> Ecto.Queryable.to_query()
|> Ecto.Query.Planner.ensure_select(true)

execute(:all, name, query, tuplet) |> elem(1)
execute(:all, name, query, tuplet)
end

def all_by(name, queryable, clauses, tuplet) do
def select_all_by(name, queryable, clauses, tuplet) do
query =
queryable
|> Ecto.Query.where([], ^Enum.to_list(clauses))
|> Ecto.Query.Planner.ensure_select(true)

execute(:all, name, query, tuplet) |> elem(1)
execute(:all, name, query, tuplet)
end

def all(name, queryable, tuplet) do
select_all(name, queryable, tuplet) |> elem(1)
end

def all_by(name, queryable, clauses, tuplet) do
select_all_by(name, queryable, clauses, tuplet) |> elem(1)
end

def stream(_name, queryable, {adapter_meta, opts}) do
Expand Down
14 changes: 14 additions & 0 deletions test/ecto/repo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2212,7 +2212,7 @@
def ensure_all_started(_, _), do: raise("not implemented")
end

defmodule NoTransactionRepo do

Check warning on line 2215 in test/ecto/repo_test.exs

View workflow job for this annotation

GitHub Actions / unit test (1.14.5, 24.3.4.17)

function select_all_by/3 required by behaviour Ecto.Repo is not implemented (in module Ecto.RepoTest.NoTransactionRepo)

Check warning on line 2215 in test/ecto/repo_test.exs

View workflow job for this annotation

GitHub Actions / unit test (1.14.5, 24.3.4.17)

function select_all/2 required by behaviour Ecto.Repo is not implemented (in module Ecto.RepoTest.NoTransactionRepo)
use Ecto.Repo, otp_app: :ecto, adapter: NoTransactionAdapter
end

Expand Down Expand Up @@ -2318,6 +2318,20 @@
assert_received {:all, %{prefix: "rewritten"}}
end

test "select_all" do
query = from p in MyParent, select: p

PrepareRepo.select_all(query, hello: :world)
assert_received {:all, ^query, [hello: :world]}
assert_received {:all, %{prefix: "rewritten"}}
end

test "select_all_by" do
PrepareRepo.select_all_by(MyParent, [id: 1], hello: :world)
assert_received {:all, _query, [hello: :world]}
assert_received {:all, %{prefix: "rewritten"}}
end

test "update_all" do
query = from p in MyParent, update: [set: [n: 1]]
PrepareRepo.update_all(query, [], hello: :world)
Expand Down
Loading