From fa55a9c4bd7e99109351727a8cf79da231260ea8 Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Tue, 2 Jun 2026 17:03:22 +0200 Subject: [PATCH] chore: use ro connstring for observability (#44806) ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? function change ## What is the current behavior? defaults to the read-write connection string when doing observability report queries ## What is the new behavior? uses the read-only connection string instead ## Additional context these should only ever be read-only operations, reporting should not have side effects and this adds a guardrail to ensure that remains the case ## Summary by CodeRabbit **Bug Fixes** - Corrected database replica query handling by using read-only connection strings for replica database access. --- .../useQueryPerformanceQuery.ts | 2 +- apps/studio/hooks/analytics/useDbQuery.tsx | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/studio/components/interfaces/QueryPerformance/useQueryPerformanceQuery.ts b/apps/studio/components/interfaces/QueryPerformance/useQueryPerformanceQuery.ts index 317205ab773..706af70ce59 100644 --- a/apps/studio/components/interfaces/QueryPerformance/useQueryPerformanceQuery.ts +++ b/apps/studio/components/interfaces/QueryPerformance/useQueryPerformanceQuery.ts @@ -138,7 +138,7 @@ export const useQueryPerformanceInfiniteQuery = ( const { data: databases } = useReadReplicasQuery({ projectRef: project?.ref }) const connectionString = (databases || []).find( (db) => db.identifier === state.selectedDatabaseId - )?.connectionString + )?.connection_string_read_only // default to read_only connection string // Clamp pageSize the same way generateQueryPerformanceSql does so getNextPageParam // and the queryKey are always consistent with the SQL actually executed. diff --git a/apps/studio/hooks/analytics/useDbQuery.tsx b/apps/studio/hooks/analytics/useDbQuery.tsx index c080a115758..5d83ce266f5 100644 --- a/apps/studio/hooks/analytics/useDbQuery.tsx +++ b/apps/studio/hooks/analytics/useDbQuery.tsx @@ -10,6 +10,7 @@ import { import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query' import { executeSql } from '@/data/sql/execute-sql-query' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { IS_PLATFORM } from '@/lib/constants' import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector' export interface DbQueryHook { @@ -43,9 +44,16 @@ const useDbQuery = ({ const { data: databases } = useReadReplicasQuery({ projectRef: project?.ref }) const connectionString = (databases || []).find( (db) => db.identifier === state.selectedDatabaseId - )?.connectionString + )?.connection_string_read_only // default to using the read_only string const identifier = state.selectedDatabaseId + // When a read-replica is selected, require its connection string before fetching. + // Falling back to the primary's connection string would silently query the wrong database. + const isPrimarySelected = !state.selectedDatabaseId || state.selectedDatabaseId === project?.ref + const effectiveConnectionString = isPrimarySelected + ? (connectionString ?? project?.connectionString) + : connectionString + const resolvedSql = typeof sql === 'function' ? sql([]) : sql const { @@ -59,7 +67,7 @@ const useDbQuery = ({ 'projects', project?.ref, 'db', - { ...params, sql: resolvedSql, identifier }, + { ...params, sql: resolvedSql, identifier, connectionString: effectiveConnectionString }, where, orderBy, ], @@ -67,13 +75,17 @@ const useDbQuery = ({ return executeSql( { projectRef: project?.ref, - connectionString: connectionString || project?.connectionString, + connectionString: effectiveConnectionString, sql: resolvedSql, }, signal ).then((res) => res.result) as Promise }, - enabled: Boolean(resolvedSql), + // Don't run until we have a connection string for the selected database. + // For replicas this prevents a silent fallback to the primary before replicas load. + // In self-hosted mode (IS_PLATFORM=false) there is no real connection string, so we + // skip the check — executeSql works fine without one on self-hosted deployments. + enabled: Boolean(resolvedSql) && (!IS_PLATFORM || Boolean(effectiveConnectionString)), refetchOnWindowFocus: false, refetchOnReconnect: false, })