Skip to content

Multiple connections

Every query, explain, compare, report, schema and stats call can target a specific analytical data source.

The default connection

Out of the box there is one connection, default, built from DATABASE_HOST, DATABASE_PORT, DATABASE_NAME and the readonly credentials. Its id is whatever DATABASE_DEFAULT_CONNECTION_ID names (default default); if that id isn't among the configured connections, the app falls back to the first one instead of erroring, since this is a startup configuration issue, not a per-request one.

Additional connections come from DATABASE_CONNECTIONS_JSON (a static list) or, for organizations, per-organization connection secrets managed via the admin API. See Configuration – multiple database connections.

Resolving connection_id

Request value Behavior
Omitted, or empty string The configured default connection
A known id That connection
An unknown, non-empty id 400 CONNECTION_NOT_FOUND

There is no silent fallback to a different database for a request that names an unknown connection: it fails closed, so a typo in connection_id cannot accidentally run against the wrong data source.

Connection permissions

Each connection grants a subset of actions to a given organization: query, explain, analyze, schema, report, schedule, stats, ask. If an organization has no rows for a connection at all, every action is allowed by default, unless SECURITY_CONNECTION_ALLOWLIST_REQUIRED is set (its default is on under production StrictMode), in which case an unassigned connection is denied outright. If the organization has any assignment for the connection, only the actions explicitly granted apply; platform/tenant admins bypass the check. A request missing the required action fails with 400 CONNECTION_FORBIDDEN.

Action Needed for
query Run query; verify_results on a compare
explain EXPLAIN, compare (plan-only)
analyze EXPLAIN ANALYZE, compare with analyze: true
schema Schema browsing
report Workbench report generation
schedule Scheduled report runs
stats pg_stat_statements reads, regression polling. On a read-only role shared by several organizations only a platform admin may read statistics, and the poller skips it (why)
ask Natural-language Ask

Readiness per connection

GET /ready/connections reports each configured pool's readiness independently: name, role, whether it's initialized (per-organization pools are created lazily on first use), and any connection error. It always returns 200; it's a diagnostic view, not a liveness gate. GET /ready itself only checks the app metadata pool and the schema migration version.

Cross-database identity

A queryid from pg_stat_statements is only meaningful within one connection: the same SQL text on two different databases gets different queryid values, and the regression poller, snapshots, baselines and alerts are all scoped by (organization, connection, queryid). Comparing regression history across connections means comparing by SQL shape, not by id.

See also

Configuration reference · Regressions and applied fixes · Organizations and tenancy