Skip to content

Commit 2e44830

Browse files
Add :prepare option for Postgres Adapter (#372)
1 parent 6f1224e commit 2e44830

3 files changed

Lines changed: 67 additions & 8 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
defmodule Ecto.Integration.PrepareTest do
2+
use Ecto.Integration.Case, async: true
3+
4+
alias Ecto.Integration.TestRepo
5+
alias Ecto.Integration.Post
6+
7+
test "prepare option" do
8+
one = TestRepo.insert!(%Post{title: "one"})
9+
two = TestRepo.insert!(%Post{title: "two"})
10+
11+
# Uncached
12+
assert TestRepo.all(Post, prepare: :unnamed) == [one, two]
13+
assert TestRepo.all(Post, prepare: :named) == [one, two]
14+
15+
# Cached
16+
assert TestRepo.all(Post, prepare: :unnamed) == [one, two]
17+
assert TestRepo.all(Post, prepare: :named) == [one, two]
18+
end
19+
end

lib/ecto/adapters/postgres.ex

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ defmodule Ecto.Adapters.Postgres do
2121
config :your_app, YourApp.Repo,
2222
...
2323
24+
The `:prepare` option may be specified per operation:
25+
26+
YourApp.Repo.all(Queryable, prepare: :unnamed)
27+
2428
### Connection options
2529
2630
* `:hostname` - Server hostname
@@ -106,6 +110,7 @@ defmodule Ecto.Adapters.Postgres do
106110
@behaviour Ecto.Adapter.Structure
107111

108112
@default_maintenance_database "postgres"
113+
@default_prepare_opt :named
109114

110115
@doc """
111116
All Ecto extensions for Postgrex.
@@ -121,6 +126,23 @@ defmodule Ecto.Adapters.Postgres do
121126
def dumpers(:binary_id, type), do: [type, Ecto.UUID]
122127
def dumpers(_, type), do: [type]
123128

129+
## Query API
130+
131+
@impl Ecto.Adapter.Queryable
132+
def execute(adapter_meta, query_meta, query, params, opts) do
133+
prepare = Keyword.get(opts, :prepare, @default_prepare_opt)
134+
135+
unless valid_prepare?(prepare) do
136+
raise ArgumentError,
137+
"expected option `:prepare` to be either `:named` or `:unnamed`, got: #{inspect(prepare)}"
138+
end
139+
140+
Ecto.Adapters.SQL.execute(prepare, adapter_meta, query_meta, query, params, opts)
141+
end
142+
143+
defp valid_prepare?(prepare) when prepare in [:named, :unnamed], do: true
144+
defp valid_prepare?(_), do: false
145+
124146
## Storage API
125147

126148
@impl true

lib/ecto/adapters/sql.ex

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ defmodule Ecto.Adapters.SQL do
129129

130130
@impl true
131131
def execute(adapter_meta, query_meta, query, params, opts) do
132-
Ecto.Adapters.SQL.execute(adapter_meta, query_meta, query, params, opts)
132+
Ecto.Adapters.SQL.execute(:named, adapter_meta, query_meta, query, params, opts)
133133
end
134134

135135
@impl true
@@ -689,26 +689,38 @@ defmodule Ecto.Adapters.SQL do
689689
end
690690

691691
@doc false
692-
def execute(adapter_meta, query_meta, prepared, params, opts) do
692+
def execute(prepare, adapter_meta, query_meta, prepared, params, opts) do
693693
%{num_rows: num, rows: rows} =
694-
execute!(adapter_meta, prepared, params, put_source(opts, query_meta))
694+
execute!(prepare, adapter_meta, prepared, params, put_source(opts, query_meta))
695695

696696
{num, rows}
697697
end
698698

699-
defp execute!(adapter_meta, {:cache, update, {id, prepared}}, params, opts) do
700-
name = "ecto_" <> Integer.to_string(id)
699+
defp execute!(prepare, adapter_meta, {:cache, update, {id, prepared}}, params, opts) do
700+
name = prepare_name(prepare, id)
701701

702702
case sql_call(adapter_meta, :prepare_execute, [name, prepared], params, opts) do
703703
{:ok, query, result} ->
704-
update.({id, query})
704+
maybe_update_cache(prepare, update, {id, query})
705705
result
706706
{:error, err} ->
707707
raise_sql_call_error err
708708
end
709709
end
710710

711-
defp execute!(adapter_meta, {:cached, update, reset, {id, cached}}, params, opts) do
711+
defp execute!(:unnamed = prepare, adapter_meta, {:cached, _update, _reset, {id, cached}}, params, opts) do
712+
name = prepare_name(prepare, id)
713+
prepared = String.Chars.to_string(cached)
714+
715+
case sql_call(adapter_meta, :prepare_execute, [name, prepared], params, opts) do
716+
{:ok, _query, result} ->
717+
result
718+
{:error, err} ->
719+
raise_sql_call_error err
720+
end
721+
end
722+
723+
defp execute!(:named = _prepare, adapter_meta, {:cached, update, reset, {id, cached}}, params, opts) do
712724
case sql_call(adapter_meta, :execute, [cached], params, opts) do
713725
{:ok, query, result} ->
714726
update.({id, query})
@@ -723,13 +735,19 @@ defmodule Ecto.Adapters.SQL do
723735
end
724736
end
725737

726-
defp execute!(adapter_meta, {:nocache, {_id, prepared}}, params, opts) do
738+
defp execute!(_prepare, adapter_meta, {:nocache, {_id, prepared}}, params, opts) do
727739
case sql_call(adapter_meta, :query, [prepared], params, opts) do
728740
{:ok, res} -> res
729741
{:error, err} -> raise_sql_call_error err
730742
end
731743
end
732744

745+
defp prepare_name(:named, id), do: "ecto_" <> Integer.to_string(id)
746+
defp prepare_name(:unnamed, _id), do: ""
747+
748+
defp maybe_update_cache(:named = _prepare, update, value), do: update.(value)
749+
defp maybe_update_cache(:unnamed = _prepare, _update, _value), do: :noop
750+
733751
@doc false
734752
def stream(adapter_meta, query_meta, prepared, params, opts) do
735753
do_stream(adapter_meta, prepared, params, put_source(opts, query_meta))

0 commit comments

Comments
 (0)