Skip to content

Commit df84e75

Browse files
authored
fix: Database.from_settings/from_tenant can error out (#1841)
1 parent e036c01 commit df84e75

12 files changed

Lines changed: 117 additions & 105 deletions

File tree

lib/extensions/postgres_cdc_rls/replication_poller.ex

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ defmodule Extensions.PostgresCdcRls.ReplicationPoller do
6060

6161
@impl true
6262
def handle_continue({:connect, tenant}, state) do
63-
realtime_rls_settings = Database.from_tenant(tenant, "realtime_rls")
64-
65-
with {:ok, conn} <- Database.connect_db(realtime_rls_settings) do
63+
with {:ok, realtime_rls_settings} <- Database.from_tenant(tenant, "realtime_rls"),
64+
{:ok, conn} <- Database.connect_db(realtime_rls_settings) do
6665
{:noreply, Map.put(state, :conn, conn), {:continue, :prepare}}
6766
else
6867
{:error, reason} ->

lib/extensions/postgres_cdc_rls/subscription_manager.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ defmodule Extensions.PostgresCdcRls.SubscriptionManager do
7979
extension = Realtime.PostgresCdc.filter_settings("postgres_cdc_rls", tenant.extensions)
8080
extension = Map.merge(extension, %{"subs_pool_size" => Map.get(extension, "subcriber_pool_size", 4)})
8181

82-
subscription_manager_settings = Database.from_settings(extension, "realtime_subscription_manager")
83-
subscription_manager_pub_settings = Database.from_settings(extension, "realtime_subscription_manager_pub")
84-
85-
with {:ok, conn} <- Database.connect_db(subscription_manager_settings),
82+
with {:ok, subscription_manager_settings} <- Database.from_settings(extension, "realtime_subscription_manager"),
83+
{:ok, subscription_manager_pub_settings} <-
84+
Database.from_settings(extension, "realtime_subscription_manager_pub"),
85+
{:ok, conn} <- Database.connect_db(subscription_manager_settings),
8686
{:ok, conn_pub} <- Database.connect_db(subscription_manager_pub_settings) do
8787
Subscriptions.delete_all_if_table_exists(conn)
8888

lib/extensions/postgres_cdc_rls/subscriptions_checker.ex

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ defmodule Extensions.PostgresCdcRls.SubscriptionsChecker do
5656

5757
%Realtime.Api.Tenant{} = tenant = Realtime.Tenants.Cache.get_tenant_by_external_id(id)
5858

59-
realtime_subscription_checker_settings = Database.from_tenant(tenant, "realtime_subscription_checker")
60-
61-
with {:ok, conn} <- Database.connect_db(realtime_subscription_checker_settings) do
59+
with {:ok, realtime_subscription_checker_settings} <- Database.from_tenant(tenant, "realtime_subscription_checker"),
60+
{:ok, conn} <- Database.connect_db(realtime_subscription_checker_settings) do
6261
state = %State{
6362
id: id,
6463
conn: conn,

lib/realtime/database.ex

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ defmodule Realtime.Database do
4444
@doc """
4545
Creates a database connection struct from the given tenant.
4646
"""
47-
@spec from_tenant(Tenant.t(), binary(), :stop | :exp | :rand | :rand_exp) :: t()
47+
@spec from_tenant(Tenant.t(), binary(), :stop | :exp | :rand | :rand_exp) :: {:ok, t()} | {:error, :nxdomain}
4848
def from_tenant(%Tenant{} = tenant, application_name, backoff \\ :rand_exp) do
4949
tenant
5050
|> then(&Realtime.PostgresCdc.filter_settings(@cdc, &1.extensions))
@@ -54,7 +54,7 @@ defmodule Realtime.Database do
5454
@doc """
5555
Creates a database connection struct from the given settings.
5656
"""
57-
@spec from_settings(map(), binary(), :stop | :exp | :rand | :rand_exp) :: t()
57+
@spec from_settings(map(), binary(), :stop | :exp | :rand | :rand_exp) :: {:ok, t()} | {:error, :nxdomain}
5858
def from_settings(settings, application_name, backoff \\ :rand_exp) do
5959
pool = pool_size_by_application_name(application_name, settings)
6060

@@ -65,22 +65,24 @@ defmodule Realtime.Database do
6565
|> Map.new()
6666
|> then(&Map.merge(settings, &1))
6767

68-
{:ok, addrtype} = detect_ip_version(settings["db_host"])
69-
ssl = if default_ssl_param(settings), do: [verify: :verify_none], else: false
70-
71-
%__MODULE__{
72-
hostname: settings["db_host"],
73-
port: String.to_integer(settings["db_port"]),
74-
database: settings["db_name"],
75-
username: settings["db_user"],
76-
password: settings["db_password"],
77-
pool_size: pool,
78-
queue_target: settings["db_queue_target"] || 5_000,
79-
application_name: application_name,
80-
backoff_type: backoff,
81-
socket_options: [addrtype],
82-
ssl: ssl
83-
}
68+
with {:ok, addrtype} <- detect_ip_version(settings["db_host"]) do
69+
ssl = if default_ssl_param(settings), do: [verify: :verify_none], else: false
70+
71+
{:ok,
72+
%__MODULE__{
73+
hostname: settings["db_host"],
74+
port: String.to_integer(settings["db_port"]),
75+
database: settings["db_name"],
76+
username: settings["db_user"],
77+
password: settings["db_password"],
78+
pool_size: pool,
79+
queue_target: settings["db_queue_target"] || 5_000,
80+
application_name: application_name,
81+
backoff_type: backoff,
82+
socket_options: [addrtype],
83+
ssl: ssl
84+
}}
85+
end
8486
end
8587

8688
@available_connection_factor 0.95
@@ -97,10 +99,10 @@ defmodule Realtime.Database do
9799
|> then(&PostgresCdc.filter_settings(@cdc, &1.extensions))
98100
|> then(fn settings ->
99101
required_pool = tenant_pool_requirements(settings)
100-
check_settings = from_settings(settings, "realtime_connect", :stop)
101-
check_settings = Map.put(check_settings, :max_restarts, 0)
102102

103-
with {:ok, conn} <- connect_db(check_settings),
103+
with {:ok, base_settings} <- from_settings(settings, "realtime_connect", :stop),
104+
check_settings = %{base_settings | max_restarts: 0},
105+
{:ok, conn} <- connect_db(check_settings),
104106
{:ok, [available_connections, migrations_ran]} <- query_connection_info(conn) do
105107
requirement = ceil(required_pool * @available_connection_factor)
106108

@@ -154,9 +156,9 @@ defmodule Realtime.Database do
154156
@spec connect(Tenant.t(), binary(), :stop | :exp | :rand | :rand_exp) ::
155157
{:ok, pid()} | {:error, any()}
156158
def connect(tenant, application_name, backoff \\ :stop) do
157-
tenant
158-
|> from_tenant(application_name, backoff)
159-
|> connect_db()
159+
with {:ok, settings} <- from_tenant(tenant, application_name, backoff) do
160+
connect_db(settings)
161+
end
160162
end
161163

162164
@doc """

lib/realtime/tenants/migrations.ex

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -226,35 +226,35 @@ defmodule Realtime.Tenants.Migrations do
226226
end
227227

228228
defp migrate(settings) do
229-
settings = Database.from_settings(settings, "realtime_migrations", :stop)
229+
with {:ok, settings} <- Database.from_settings(settings, "realtime_migrations", :stop) do
230+
[
231+
hostname: settings.hostname,
232+
port: settings.port,
233+
database: settings.database,
234+
password: settings.password,
235+
username: settings.username,
236+
pool_size: settings.pool_size,
237+
backoff_type: settings.backoff_type,
238+
socket_options: settings.socket_options,
239+
parameters: [application_name: settings.application_name],
240+
ssl: settings.ssl
241+
]
242+
|> Repo.with_dynamic_repo(fn repo ->
243+
Logger.info("Applying migrations to #{settings.hostname}")
230244

231-
[
232-
hostname: settings.hostname,
233-
port: settings.port,
234-
database: settings.database,
235-
password: settings.password,
236-
username: settings.username,
237-
pool_size: settings.pool_size,
238-
backoff_type: settings.backoff_type,
239-
socket_options: settings.socket_options,
240-
parameters: [application_name: settings.application_name],
241-
ssl: settings.ssl
242-
]
243-
|> Repo.with_dynamic_repo(fn repo ->
244-
Logger.info("Applying migrations to #{settings.hostname}")
245+
try do
246+
opts = [all: true, prefix: "realtime", dynamic_repo: repo]
247+
{time, _} = :timer.tc(fn -> Ecto.Migrator.run(Repo, @migrations, :up, opts) end)
248+
Logger.info("Finished applying tenant migrations in #{div(time, 1000)}ms")
245249

246-
try do
247-
opts = [all: true, prefix: "realtime", dynamic_repo: repo]
248-
{time, _} = :timer.tc(fn -> Ecto.Migrator.run(Repo, @migrations, :up, opts) end)
249-
Logger.info("Finished applying tenant migrations in #{div(time, 1000)}ms")
250-
251-
:ok
252-
rescue
253-
error ->
254-
log_error("MigrationsFailedToRun", error, migration_error_metadata(error))
255-
{:error, error}
256-
end
257-
end)
250+
:ok
251+
rescue
252+
error ->
253+
log_error("MigrationsFailedToRun", error, migration_error_metadata(error))
254+
{:error, error}
255+
end
256+
end)
257+
end
258258
end
259259

260260
defp migration_error_metadata(%Postgrex.Error{postgres: postgres}) when is_map(postgres) do

lib/realtime/tenants/replication_connection.ex

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -126,28 +126,29 @@ defmodule Realtime.Tenants.ReplicationConnection do
126126

127127
def start_link(%__MODULE__{tenant_id: tenant_id} = attrs) do
128128
tenant = Cache.get_tenant_by_external_id(tenant_id)
129-
connection_opts = Database.from_tenant(tenant, "realtime_broadcast_changes", :stop)
130-
131-
connection_opts =
132-
[
133-
name: {:via, Registry, {Realtime.Registry.Unique, {__MODULE__, tenant_id}}},
134-
hostname: connection_opts.hostname,
135-
username: connection_opts.username,
136-
password: connection_opts.password,
137-
database: connection_opts.database,
138-
port: connection_opts.port,
139-
socket_options: connection_opts.socket_options,
140-
ssl: connection_opts.ssl,
141-
sync_connect: true,
142-
auto_reconnect: false,
143-
parameters: [application_name: "realtime_replication_connection"]
144-
]
145-
146-
case Postgrex.ReplicationConnection.start_link(__MODULE__, attrs, connection_opts) do
147-
{:ok, pid} -> {:ok, pid}
148-
{:error, {:already_started, pid}} -> {:ok, pid}
149-
{:error, {:bad_return_from_init, {:stop, error}}} -> {:error, error}
150-
{:error, error} -> {:error, error}
129+
130+
with {:ok, db_settings} <- Database.from_tenant(tenant, "realtime_broadcast_changes", :stop) do
131+
connection_opts =
132+
[
133+
name: {:via, Registry, {Realtime.Registry.Unique, {__MODULE__, tenant_id}}},
134+
hostname: db_settings.hostname,
135+
username: db_settings.username,
136+
password: db_settings.password,
137+
database: db_settings.database,
138+
port: db_settings.port,
139+
socket_options: db_settings.socket_options,
140+
ssl: db_settings.ssl,
141+
sync_connect: true,
142+
auto_reconnect: false,
143+
parameters: [application_name: "realtime_replication_connection"]
144+
]
145+
146+
case Postgrex.ReplicationConnection.start_link(__MODULE__, attrs, connection_opts) do
147+
{:ok, pid} -> {:ok, pid}
148+
{:error, {:already_started, pid}} -> {:ok, pid}
149+
{:error, {:bad_return_from_init, {:stop, error}}} -> {:error, error}
150+
{:error, error} -> {:error, error}
151+
end
151152
end
152153
end
153154

test/realtime/database_test.exs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,15 +265,15 @@ defmodule Realtime.DatabaseTest do
265265

266266
describe "from_tenant/3" do
267267
test "uses default backoff when not provided", %{tenant: tenant} do
268-
settings = Database.from_tenant(tenant, "realtime_test")
268+
{:ok, settings} = Database.from_tenant(tenant, "realtime_test")
269269
assert settings.backoff_type == :rand_exp
270270
end
271271
end
272272

273273
describe "from_settings/3" do
274274
test "uses default backoff when not provided", %{tenant: tenant} do
275275
settings = Realtime.PostgresCdc.filter_settings("postgres_cdc_rls", tenant.extensions)
276-
result = Database.from_settings(settings, "realtime_connect")
276+
{:ok, result} = Database.from_settings(settings, "realtime_connect")
277277
assert result.backoff_type == :rand_exp
278278
end
279279

@@ -283,7 +283,7 @@ defmodule Realtime.DatabaseTest do
283283
{:ok, ip_version} = Database.detect_ip_version("127.0.0.1")
284284
socket_options = [ip_version]
285285
settings = Realtime.PostgresCdc.filter_settings("postgres_cdc_rls", tenant.extensions)
286-
settings = Database.from_settings(settings, application_name, backoff)
286+
{:ok, settings} = Database.from_settings(settings, application_name, backoff)
287287
port = settings.port
288288

289289
assert %Realtime.Database{
@@ -313,12 +313,12 @@ defmodule Realtime.DatabaseTest do
313313

314314
settings = Realtime.PostgresCdc.filter_settings("postgres_cdc_rls", tenant.extensions)
315315
settings = Map.put(settings, "ssl_enforced", true)
316-
settings = Database.from_settings(settings, application_name, backoff)
316+
{:ok, settings} = Database.from_settings(settings, application_name, backoff)
317317
assert settings.ssl == [verify: :verify_none]
318318

319319
settings = Realtime.PostgresCdc.filter_settings("postgres_cdc_rls", tenant.extensions)
320320
settings = Map.put(settings, "ssl_enforced", false)
321-
settings = Database.from_settings(settings, application_name, backoff)
321+
{:ok, settings} = Database.from_settings(settings, application_name, backoff)
322322
assert settings.ssl == false
323323
end
324324
end

test/realtime/extensions/cdc_rls/subscriptions_test.exs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ defmodule Realtime.Extensions.PostgresCdcRls.SubscriptionsTest do
1111
setup do
1212
tenant = Containers.checkout_tenant(run_migrations: true)
1313

14+
{:ok, db_settings} = Database.from_tenant(tenant, "realtime_rls")
15+
1416
{:ok, conn} =
15-
tenant
16-
|> Database.from_tenant("realtime_rls")
17+
db_settings
1718
|> Map.from_struct()
1819
|> Keyword.new()
1920
|> Postgrex.start_link()
@@ -279,9 +280,10 @@ defmodule Realtime.Extensions.PostgresCdcRls.SubscriptionsTest do
279280
)
280281

281282
on_exit(fn ->
283+
{:ok, db_settings} = Database.from_tenant(tenant, "realtime_rls")
284+
282285
{:ok, cleanup_conn} =
283-
tenant
284-
|> Database.from_tenant("realtime_rls")
286+
db_settings
285287
|> Map.from_struct()
286288
|> Keyword.new()
287289
|> Postgrex.start_link()

test/realtime/tenants/connect_test.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,8 @@ defmodule Realtime.Tenants.ConnectTest do
569569
end
570570

571571
test "handles max_wal_senders by logging the correct operational code", %{tenant: tenant} do
572-
opts = tenant |> Database.from_tenant("realtime_test", :stop) |> Database.opts()
572+
{:ok, settings} = Database.from_tenant(tenant, "realtime_test", :stop)
573+
opts = Database.opts(settings)
573574
parent = self()
574575

575576
pids =

test/realtime/tenants/replication_connection_test.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,8 @@ defmodule Realtime.Tenants.ReplicationConnectionTest do
544544
end
545545

546546
test "handle standby connections exceeds max_wal_senders", %{tenant: tenant} do
547-
opts = Database.from_tenant(tenant, "realtime_test", :stop) |> Database.opts()
547+
{:ok, settings} = Database.from_tenant(tenant, "realtime_test", :stop)
548+
opts = Database.opts(settings)
548549
parent = self()
549550

550551
# This creates a loop of errors that occupies all WAL senders and lets us test the error handling

0 commit comments

Comments
 (0)