Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Add :prepare option for Postgres Adapter #372

Merged
merged 11 commits into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
21 changes: 21 additions & 0 deletions lib/ecto/adapters/postgres.ex
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ defmodule Ecto.Adapters.Postgres do
@behaviour Ecto.Adapter.Structure

@default_maintenance_database "postgres"
@default_prepare_opt :named

@doc """
All Ecto extensions for Postgrex.
Expand All @@ -121,6 +122,26 @@ 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(adapter_meta, query_meta, query, params, put_use_cache(opts, prepare))
end

defp valid_prepare?(prepare) when prepare in [:named, :unnamed], do: true
defp valid_prepare?(_), do: false

defp put_use_cache(opts, :named), do: Keyword.put(opts, :use_cache, true)
defp put_use_cache(opts, :unnamed), do: Keyword.put(opts, :use_cache, false)

## Storage API

@impl true
Expand Down
2 changes: 2 additions & 0 deletions lib/ecto/adapters/postgres/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ if Code.ensure_loaded?(Postgrex) do

@impl true
def prepare_execute(conn, name, sql, params, opts) do
name = if opts[:use_cache], do: name, else: ""

case Postgrex.prepare_execute(conn, name, sql, params, opts) do
{:error, %Postgrex.Error{postgres: %{pg_code: "22P02", message: message}} = error} ->
context = """
Expand Down
31 changes: 25 additions & 6 deletions lib/ecto/adapters/sql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -690,25 +690,39 @@ defmodule Ecto.Adapters.SQL do

@doc false
def execute(adapter_meta, query_meta, prepared, params, opts) do
use_cache? = Keyword.get(opts, :use_cache, true)

%{num_rows: num, rows: rows} =
execute!(adapter_meta, prepared, params, put_source(opts, query_meta))
execute!(use_cache?, 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!(use_cache?, adapter_meta, {:cache, update, {id, prepared}}, params, opts) do
name = prepare_name(id)

case sql_call(adapter_meta, :prepare_execute, [name, prepared], params, opts) do
{:ok, query, result} ->
update.({id, query})
maybe_update_cache(use_cache?, update, {id, query})
result
{:error, err} ->
raise_sql_call_error err
end
end

defp execute!(false = _use_cache?, adapter_meta, {:cached, _update, _reset, {id, cached}}, params, opts) do
name = prepare_name(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!(adapter_meta, {:cached, update, reset, {id, cached}}, params, opts) do
defp execute!(true = _use_cache?, 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})
Expand All @@ -723,13 +737,18 @@ defmodule Ecto.Adapters.SQL do
end
end

defp execute!(adapter_meta, {:nocache, {_id, prepared}}, params, opts) do
defp execute!(_use_cache?, 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(id), do: "ecto_" <> Integer.to_string(id)

defp maybe_update_cache(true = _use_cache?, update, value), do: update.(value)
defp maybe_update_cache(false = _use_cache?, _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))
Expand Down