Files
supabase/apps/studio/hooks/analytics/useDbQuery.tsx
Charis 2d4e87f579 studio: SafeSql for reports, query performance, privileges (4/7) (#45998)
## Summary

Part 4 of the SafeSql migration stack
([#45897](https://github.com/supabase/supabase/pull/45897),
[#45903](https://github.com/supabase/supabase/pull/45903),
[#45990](https://github.com/supabase/supabase/pull/45990), this PR, …).

Converts the remaining reports, query performance, observability, index
advisor, and privileges call sites of `executeSql` to produce
`SafeSqlFragment` values. The `ReportQuery.sql` field flips from
`string` to `SafeSqlFragment`, which cascades into every consumer —
landed here atomically so each branch typechecks cleanly.

Touched areas:

- `interfaces/Reports/*` — `ReportQuery.sql: SafeSqlFragment`, plus all
report definitions/utilities updated
- `interfaces/QueryPerformance/useQueryPerformanceQuery.ts`
- `interfaces/Database/IndexAdvisor/*` and
`data/database/{table-index-advisor,retrieve-index-advisor-result}-query.ts`
-
`data/privileges/{table-api-access,update-exposed-entities}-mutation.ts`
- `interfaces/Storage/StoragePolicies/StoragePolicies.tsx`
- `hooks/analytics/useDbQuery.tsx`
- `Observability/useSlowQueriesCount.ts` +
`useQueryInsightsIssues.utils.test.ts`

## Test plan

- [x] `pnpm typecheck` passes
- [x] `useQueryInsightsIssues.utils.test.ts` passes
- [x] Dev-server smoke test: reports pages, query performance, index
advisor, storage policies

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Reworked SQL construction and typings across reporting, query
performance, index advisor, and privilege features to use safer SQL
fragments, improving reliability and preventing query composition
issues.
* **Types**
* Reporting query types were split to distinguish database vs. logs
queries, enabling correct handling and validation.
* **Docs/Utils**
  * Added a helper to consistently generate logs SQL for report hooks.
* **Tests**
  * Updated tests to exercise the new SQL-building API.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45998)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-15 14:50:38 -04:00

94 lines
2.4 KiB
TypeScript

import { type SafeSqlFragment } from '@supabase/pg-meta/src/pg-format'
import { useQuery } from '@tanstack/react-query'
import { DEFAULT_QUERY_PARAMS } from '@/components/interfaces/Reports/Reports.constants'
import {
BaseReportParams,
MetaQueryResponse,
ReportQueryDb,
} from '@/components/interfaces/Reports/Reports.types'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
import { executeSql } from '@/data/sql/execute-sql-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
export interface DbQueryHook<T = any> {
isLoading: boolean
isRefetching: boolean
error: string
data: T[]
params: BaseReportParams
logData?: never
runQuery: () => void
setParams?: never
changeQuery?: never
resolvedSql: string
}
// [Joshen] Atm this is being used only in query performance
const useDbQuery = ({
sql,
params = DEFAULT_QUERY_PARAMS,
where,
orderBy,
}: {
sql: ReportQueryDb['safeSql'] | SafeSqlFragment
params?: BaseReportParams
where?: string
orderBy?: string
}): DbQueryHook => {
const { data: project } = useSelectedProjectQuery()
const state = useDatabaseSelectorStateSnapshot()
const { data: databases } = useReadReplicasQuery({ projectRef: project?.ref })
const connectionString = (databases || []).find(
(db) => db.identifier === state.selectedDatabaseId
)?.connectionString
const identifier = state.selectedDatabaseId
const resolvedSql = typeof sql === 'function' ? sql([]) : sql
const {
data,
error: rqError,
isPending,
isRefetching,
refetch,
} = useQuery({
queryKey: [
'projects',
project?.ref,
'db',
{ ...params, sql: resolvedSql, identifier },
where,
orderBy,
],
queryFn: ({ signal }) => {
return executeSql(
{
projectRef: project?.ref,
connectionString: connectionString || project?.connectionString,
sql: resolvedSql,
},
signal
).then((res) => res.result) as Promise<MetaQueryResponse>
},
enabled: Boolean(resolvedSql),
refetchOnWindowFocus: false,
refetchOnReconnect: false,
})
const error = rqError || (typeof data === 'object' ? data?.error : '')
return {
error,
data,
isLoading: isPending,
isRefetching,
params,
runQuery: refetch,
resolvedSql,
}
}
export default useDbQuery