diff --git a/integration_test/pg/prepare_test.exs b/integration_test/pg/prepare_test.exs new file mode 100644 index 00000000..ea502053 --- /dev/null +++ b/integration_test/pg/prepare_test.exs @@ -0,0 +1,19 @@ +defmodule Ecto.Integration.PrepareTest do + use Ecto.Integration.Case, async: true + + alias Ecto.Integration.TestRepo + alias Ecto.Integration.Post + + test "prepare option" do + one = TestRepo.insert!(%Post{title: "one"}) + two = TestRepo.insert!(%Post{title: "two"}) + + # Uncached + assert TestRepo.all(Post, prepare: :unnamed) == [one, two] + assert TestRepo.all(Post, prepare: :named) == [one, two] + + # Cached + assert TestRepo.all(Post, prepare: :unnamed) == [one, two] + assert TestRepo.all(Post, prepare: :named) == [one, two] + end +end diff --git a/lib/ecto/adapters/postgres.ex b/lib/ecto/adapters/postgres.ex index 7555e374..d0665606 100644 --- a/lib/ecto/adapters/postgres.ex +++ b/lib/ecto/adapters/postgres.ex @@ -21,6 +21,10 @@ defmodule Ecto.Adapters.Postgres do config :your_app, YourApp.Repo, ... + The `:prepare` option may be specified per operation: + + YourApp.Repo.all(Queryable, prepare: :unnamed) + ### Connection options * `:hostname` - Server hostname @@ -106,6 +110,7 @@ defmodule Ecto.Adapters.Postgres do @behaviour Ecto.Adapter.Structure @default_maintenance_database "postgres" + @default_prepare_opt :named @doc """ All Ecto extensions for Postgrex. @@ -121,6 +126,23 @@ defmodule Ecto.Adapters.Postgres do def dumpers(:binary_id, type), do: [type, Ecto.UUID] def dumpers(_, type), do: [type] + ## Query API + + @impl Ecto.Adapter.Queryable + def execute(adapter_meta, query_meta, query, params, opts) do + prepare = Keyword.get(opts, :prepare, @default_prepare_opt) + + unless valid_prepare?(prepare) do + raise ArgumentError, + "expected option `:prepare` to be either `:named` or `:unnamed`, got: #{inspect(prepare)}" + end + + Ecto.Adapters.SQL.execute(prepare, adapter_meta, query_meta, query, params, opts) + end + + defp valid_prepare?(prepare) when prepare in [:named, :unnamed], do: true + defp valid_prepare?(_), do: false + ## Storage API @impl true diff --git a/lib/ecto/adapters/sql.ex b/lib/ecto/adapters/sql.ex index 3d9c039b..a6a396ac 100644 --- a/lib/ecto/adapters/sql.ex +++ b/lib/ecto/adapters/sql.ex @@ -129,7 +129,7 @@ defmodule Ecto.Adapters.SQL do @impl true def execute(adapter_meta, query_meta, query, params, opts) do - Ecto.Adapters.SQL.execute(adapter_meta, query_meta, query, params, opts) + Ecto.Adapters.SQL.execute(:named, adapter_meta, query_meta, query, params, opts) end @impl true @@ -689,26 +689,38 @@ defmodule Ecto.Adapters.SQL do end @doc false - def execute(adapter_meta, query_meta, prepared, params, opts) do + def execute(prepare, adapter_meta, query_meta, prepared, params, opts) do %{num_rows: num, rows: rows} = - execute!(adapter_meta, prepared, params, put_source(opts, query_meta)) + execute!(prepare, adapter_meta, prepared, params, put_source(opts, query_meta)) {num, rows} end - defp execute!(adapter_meta, {:cache, update, {id, prepared}}, params, opts) do - name = "ecto_" <> Integer.to_string(id) + defp execute!(prepare, adapter_meta, {:cache, update, {id, prepared}}, params, opts) do + name = prepare_name(prepare, id) case sql_call(adapter_meta, :prepare_execute, [name, prepared], params, opts) do {:ok, query, result} -> - update.({id, query}) + maybe_update_cache(prepare, update, {id, query}) result {:error, err} -> raise_sql_call_error err end end - defp execute!(adapter_meta, {:cached, update, reset, {id, cached}}, params, opts) do + defp execute!(:unnamed = prepare, adapter_meta, {:cached, _update, _reset, {id, cached}}, params, opts) do + name = prepare_name(prepare, id) + prepared = String.Chars.to_string(cached) + + case sql_call(adapter_meta, :prepare_execute, [name, prepared], params, opts) do + {:ok, _query, result} -> + result + {:error, err} -> + raise_sql_call_error err + end + end + + defp execute!(:named = _prepare, adapter_meta, {:cached, update, reset, {id, cached}}, params, opts) do case sql_call(adapter_meta, :execute, [cached], params, opts) do {:ok, query, result} -> update.({id, query}) @@ -723,13 +735,19 @@ defmodule Ecto.Adapters.SQL do end end - defp execute!(adapter_meta, {:nocache, {_id, prepared}}, params, opts) do + defp execute!(_prepare, adapter_meta, {:nocache, {_id, prepared}}, params, opts) do case sql_call(adapter_meta, :query, [prepared], params, opts) do {:ok, res} -> res {:error, err} -> raise_sql_call_error err end end + defp prepare_name(:named, id), do: "ecto_" <> Integer.to_string(id) + defp prepare_name(:unnamed, _id), do: "" + + defp maybe_update_cache(:named = _prepare, update, value), do: update.(value) + defp maybe_update_cache(:unnamed = _prepare, _update, _value), do: :noop + @doc false def stream(adapter_meta, query_meta, prepared, params, opts) do do_stream(adapter_meta, prepared, params, put_source(opts, query_meta))