From 230d3582f243b03a3297bdfc2be0dd0824b259dd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Jan 2026 13:37:12 +0000 Subject: [PATCH 1/4] Log accessible tables at startup Add an info log message during Electric startup that lists all tables accessible to the database user (tables with SELECT permission). This helps users verify their database connection is working correctly and see what tables are available for syncing. The log appears right after the "Connected to Postgres" message and before the shapes supervisor starts, showing output like: "5 tables are accessible to Electric: [\"public.items\", ...]" https://claude.ai/code/session_012BtysQooC3gKQrgpj4KbGk --- .../lib/electric/connection/manager.ex | 25 ++++++++++++++++++ .../lib/electric/postgres/configuration.ex | 26 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/packages/sync-service/lib/electric/connection/manager.ex b/packages/sync-service/lib/electric/connection/manager.ex index 0bad7e00e3..94f562a0b7 100644 --- a/packages/sync-service/lib/electric/connection/manager.ex +++ b/packages/sync-service/lib/electric/connection/manager.ex @@ -118,6 +118,7 @@ defmodule Electric.Connection.Manager do end use GenServer, shutdown: :infinity + alias Electric.Postgres.Configuration alias Electric.Postgres.LockBreakerConnection alias Electric.Connection.Manager.ConnectionBackoff alias Electric.Connection.Manager.ConnectionResolver @@ -483,6 +484,9 @@ defmodule Electric.Connection.Manager do ) end + # Log all tables accessible to Electric + log_accessible_tables(state) + repl_sup_opts = [ stack_id: state.stack_id, shape_cache_opts: state.shape_cache_opts, @@ -1234,6 +1238,27 @@ defmodule Electric.Connection.Manager do ) end + defp log_accessible_tables(state) do + pool = pool_name(state.stack_id, :admin) + + case Configuration.run_handling_db_connection_errors(fn -> + Configuration.list_accessible_tables!(pool) + end) do + {:error, reason} -> + Logger.warning("Failed to list accessible tables: #{inspect(reason)}") + + tables when is_list(tables) -> + table_names = + tables + |> Enum.map(fn {schema, table} -> Electric.Utils.relation_to_sql({schema, table}) end) + |> Enum.sort() + + Logger.info( + "#{length(table_names)} tables are accessible to Electric: #{inspect(table_names)}" + ) + end + end + defp should_retry_connection?( %State{ current_phase: :connection_setup, diff --git a/packages/sync-service/lib/electric/postgres/configuration.ex b/packages/sync-service/lib/electric/postgres/configuration.ex index a25121fcb9..69f8d92705 100644 --- a/packages/sync-service/lib/electric/postgres/configuration.ex +++ b/packages/sync-service/lib/electric/postgres/configuration.ex @@ -382,4 +382,30 @@ defmodule Electric.Postgres.Configuration do @spec trim_relation_with_replica(relation_with_replica()) :: Electric.oid_relation() defp trim_relation_with_replica({oid, relation, _replident}), do: {oid, relation} + + @doc """ + List all tables accessible to Electric (tables the connected user has SELECT permission on). + + Returns a list of `{schema, table}` tuples for ordinary and partitioned tables, + excluding system schemas (pg_catalog, information_schema). + """ + @spec list_accessible_tables!(Postgrex.conn()) :: list(Electric.relation()) + def list_accessible_tables!(conn) do + %Postgrex.Result{rows: rows} = + Postgrex.query!( + conn, + """ + SELECT n.nspname, c.relname + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind IN ('r', 'p') + AND n.nspname NOT IN ('pg_catalog', 'information_schema') + AND has_table_privilege(c.oid, 'SELECT') + ORDER BY n.nspname, c.relname + """, + [] + ) + + Enum.map(rows, fn [schema, table] -> {schema, table} end) + end end From 429de1c879e706850bd77f75c6e69b759f928cee Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 30 Jan 2026 07:15:01 -0700 Subject: [PATCH 2/4] Ensure accessible tables are logged only once at startup - Add logged_accessible_tables? flag to State struct to track if logging has already occurred - Only log accessible tables on first startup, not during reconnections - Add stack_id to error log message for better debugging context - Remove redundant inline comment Co-Authored-By: Claude Opus 4.5 --- .../lib/electric/connection/manager.ex | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/sync-service/lib/electric/connection/manager.ex b/packages/sync-service/lib/electric/connection/manager.ex index 94f562a0b7..690c8984b8 100644 --- a/packages/sync-service/lib/electric/connection/manager.ex +++ b/packages/sync-service/lib/electric/connection/manager.ex @@ -110,6 +110,7 @@ defmodule Electric.Connection.Manager do :max_shapes, :persistent_kv, purge_all_shapes?: false, + logged_accessible_tables?: false, # PIDs of the database connection pools pool_pids: %{admin: nil, snapshot: nil}, validated_connection_opts: %{replication: nil, pool: nil}, @@ -484,8 +485,13 @@ defmodule Electric.Connection.Manager do ) end - # Log all tables accessible to Electric - log_accessible_tables(state) + state = + if state.logged_accessible_tables? do + state + else + log_accessible_tables(state) + %{state | logged_accessible_tables?: true} + end repl_sup_opts = [ stack_id: state.stack_id, @@ -1245,13 +1251,10 @@ defmodule Electric.Connection.Manager do Configuration.list_accessible_tables!(pool) end) do {:error, reason} -> - Logger.warning("Failed to list accessible tables: #{inspect(reason)}") + Logger.warning("Failed to list accessible tables for stack #{state.stack_id}: #{inspect(reason)}") tables when is_list(tables) -> - table_names = - tables - |> Enum.map(fn {schema, table} -> Electric.Utils.relation_to_sql({schema, table}) end) - |> Enum.sort() + table_names = Enum.map(tables, &Electric.Utils.relation_to_sql/1) Logger.info( "#{length(table_names)} tables are accessible to Electric: #{inspect(table_names)}" From a100aaad9d550dc638cb6282a3eea13a336fc3fb Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 30 Jan 2026 07:15:56 -0700 Subject: [PATCH 3/4] Add changeset for accessible tables logging Co-Authored-By: Claude Opus 4.5 --- .changeset/log-accessible-tables.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/log-accessible-tables.md diff --git a/.changeset/log-accessible-tables.md b/.changeset/log-accessible-tables.md new file mode 100644 index 0000000000..aa86ec164e --- /dev/null +++ b/.changeset/log-accessible-tables.md @@ -0,0 +1,5 @@ +--- +'@core/sync-service': patch +--- + +Log all tables accessible to Electric at startup. This helps operators understand which tables the connected database user has SELECT permission on and aids in debugging permission issues. From 2e275f742b1ea24f9a08a7589c727ac465c2b7ea Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Fri, 30 Jan 2026 07:26:03 -0700 Subject: [PATCH 4/4] Fix Elixir formatting Co-Authored-By: Claude Opus 4.5 --- packages/sync-service/lib/electric/connection/manager.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/sync-service/lib/electric/connection/manager.ex b/packages/sync-service/lib/electric/connection/manager.ex index 690c8984b8..356ea9ba2e 100644 --- a/packages/sync-service/lib/electric/connection/manager.ex +++ b/packages/sync-service/lib/electric/connection/manager.ex @@ -1251,7 +1251,9 @@ defmodule Electric.Connection.Manager do Configuration.list_accessible_tables!(pool) end) do {:error, reason} -> - Logger.warning("Failed to list accessible tables for stack #{state.stack_id}: #{inspect(reason)}") + Logger.warning( + "Failed to list accessible tables for stack #{state.stack_id}: #{inspect(reason)}" + ) tables when is_list(tables) -> table_names = Enum.map(tables, &Electric.Utils.relation_to_sql/1)