From 963ea4d32ad5eb2ec998bfab7ae5115aa1bcde58 Mon Sep 17 00:00:00 2001 From: Mayel de Borniol Date: Fri, 10 Jul 2026 14:20:48 +0100 Subject: [PATCH] support for `hostname` config field, and added ready status to fix issues on initial connection --- lib/adapters/postgres.ex | 24 ++++++++++++++++++------ lib/pool.ex | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/adapters/postgres.ex b/lib/adapters/postgres.ex index 8763d92..ac3fd79 100644 --- a/lib/adapters/postgres.ex +++ b/lib/adapters/postgres.ex @@ -55,6 +55,8 @@ defmodule SQL.Adapters.Postgres do end defp setup(%{domain: domain, type: type, protocol: protocol, username: username, database: database}=state, buffer, inflight, prepared, current, queue, owner, rows, count) do + # honor the documented `hostname` field: resolve it to an `addr` tuple at connect time (the driver otherwise only uses `addr`/`port`, silently ignoring `hostname` and defaulting to 127.0.0.1) + state = resolve_addr(state) handle = make_ref() {:ok, socket} = :socket.open(domain, type, protocol, Map.take(state, [:netns, :use_registry, :debug])) :socket.monitor(socket) @@ -65,6 +67,8 @@ defmodule SQL.Adapters.Postgres do :socket.connect(socket) :socket.recv(socket, 0, [], handle) state + # not ready for queries until the startup/auth handshake completes (ReadyForQuery), so gate query sends on this to avoid pipelining a Sync mid-SASL (server aborts with "expected SASL response, got message type 83") + |> Map.put(:ready, false) |> Map.put(:sock, socket) |> Map.put(:handle, handle) |> case do @@ -78,12 +82,20 @@ defmodule SQL.Adapters.Postgres do end end - defp loop(%{sock: socket, handle: handle, timeout: timeout} = state, buffer, inflight, prepared, current, queue, owner, rows, count) do + defp resolve_addr(%{hostname: hostname, family: family} = state) when is_binary(hostname) and hostname != "" do + case :inet.getaddr(String.to_charlist(hostname), family) do + {:ok, addr} -> %{state | addr: addr} + {:error, _} -> state + end + end + defp resolve_addr(state), do: state + + defp loop(%{sock: socket, handle: handle, timeout: timeout, ready: ready} = state, buffer, inflight, prepared, current, queue, owner, rows, count) do receive do {:EXIT, _from, _reason} -> :socket.shutdown(socket, :write) drain(state, buffer, inflight, prepared, current, queue, owner, rows, count) - {_ref, _caller, _timestamp, %{tokens: [{tag, _, _} |_]}}=entry when tag in ~w[drop create]a and is_nil(owner) -> + {_ref, _caller, _timestamp, %{tokens: [{tag, _, _} |_]}}=entry when tag in ~w[drop create]a and is_nil(owner) and ready -> send_data(state, @sync) prepare_execute(entry, state, buffer, inflight, prepared, current, queue, owner, rows, count) {ref, :cancel} -> @@ -93,7 +105,7 @@ defmodule SQL.Adapters.Postgres do _ -> loop(state, buffer, inflight, prepared, current, :queue.filter(fn {r, _caller, _timestamp, _sql} -> r != ref end, queue), owner, rows, count) end - {ref, caller, _timestamp, %{tokens: [{:begin, _, _}]}}=entry when is_nil(owner) -> + {ref, caller, _timestamp, %{tokens: [{:begin, _, _}]}}=entry when is_nil(owner) and ready -> send caller, {ref, :begin} prepare(entry, state, buffer, inflight, prepared, current, queue, caller, rows, count) {_ref, ^owner, _timestamp, %{tokens: [{:commit, _, _}]}}=entry -> @@ -110,9 +122,9 @@ defmodule SQL.Adapters.Postgres do prepare(entry, state, buffer, inflight, prepared, current, queue, owner, rows, count) {^owner, entry} -> prepare_execute(entry, state, buffer, inflight, prepared, current, queue, owner, rows, count) - {_ref, _caller, _timestamp, %SQL{}} = entry when is_nil(owner) -> + {_ref, _caller, _timestamp, %SQL{}} = entry when is_nil(owner) and ready -> prepare_execute(entry, state, buffer, inflight, prepared, current, queue, owner, rows, count) - {_ref, _caller, _timestamp, %SQL{}} = entry -> + {_ref, _caller, _timestamp, %SQL{}} = entry when not is_nil(owner) -> loop(state, buffer, inflight, prepared, current, :queue.in(entry, queue), owner, rows, count) {:"$socket", ^socket, :select, ^handle} -> drain(state, buffer, inflight, prepared, current, queue, owner, rows, count) @@ -313,7 +325,7 @@ defmodule SQL.Adapters.Postgres do # handle_data(state, rest, inflight, Enum.reduce(queries, prepared, &Map.put(&2, &1.name, true)), current, queue, owner, rows, count) # end <> -> - parse_messages(state, rest, inflight, prepared, current, queue, owner, rows, count) + parse_messages(%{state | ready: true}, rest, inflight, prepared, current, queue, owner, rows, count) <> -> [k, v, _] = :binary.split(payload, <<0>>, [:global]) parse_messages(%{state | parameter: Map.put(parameter, k, v)}, rest, inflight, prepared, current, queue, owner, rows, count) diff --git a/lib/pool.ex b/lib/pool.ex index 837c9cc..b8ce3d8 100644 --- a/lib/pool.ex +++ b/lib/pool.ex @@ -6,7 +6,7 @@ defmodule SQL.Pool do use Supervisor @derive {Inspect, only: []} - defstruct [ssl: false, timeout: 15_000, parameter: %{}, name: :default, domain: :inet, type: :stream, protocol: :tcp, use_registry: false, debug: false, otp: %{rcvbuf: {64_000, 128_000}}, socket: %{keepalive: true, linger: %{onoff: true, linger: 5}}, family: :inet, port: 5432, addr: {127, 0, 0, 1}, scheduler_id: nil, adapter: nil, username: nil, password: nil, hostname: nil, database: nil, timezone: nil] + defstruct [ssl: false, timeout: 15_000, parameter: %{}, name: :default, domain: :inet, type: :stream, protocol: :tcp, use_registry: false, debug: false, otp: %{rcvbuf: {64_000, 128_000}}, socket: %{keepalive: true, linger: %{onoff: true, linger: 5}}, family: :inet, port: 5432, addr: {127, 0, 0, 1}, scheduler_id: nil, adapter: nil, username: nil, password: nil, hostname: nil, database: nil, timezone: nil, ready: false] def start_link(opts) do state = struct(__MODULE__, opts)