mirror of
https://github.com/supabase/supabase.git
synced 2026-09-23 18:19:17 +08:00
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 --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45998) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
import { ident, literal } from '@supabase/pg-meta/src/pg-format'
|
||||
import {
|
||||
ident,
|
||||
joinSqlFragments,
|
||||
keyword,
|
||||
literal,
|
||||
safeSql,
|
||||
type SafeSqlFragment,
|
||||
} from '@supabase/pg-meta'
|
||||
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import { PRESET_CONFIG } from '../Reports/Reports.constants'
|
||||
@@ -54,36 +61,47 @@ export function generateQueryPerformanceSql({
|
||||
(orderBy.order === 'asc' || orderBy.order === 'desc')
|
||||
|
||||
const orderBySql = isValidOrderBy
|
||||
? `ORDER BY ${ident(orderBy!.column)} ${orderBy!.order}`
|
||||
? safeSql`ORDER BY ${ident(orderBy!.column)} ${keyword(orderBy!.order)}`
|
||||
: undefined
|
||||
|
||||
const whereConditions = []
|
||||
const whereConditions: SafeSqlFragment[] = []
|
||||
if (roles.length > 0) {
|
||||
whereConditions.push(`auth.rolname in (${roles.map((r) => `${literal(r)}`).join(', ')})`)
|
||||
whereConditions.push(
|
||||
safeSql`auth.rolname in (${joinSqlFragments(
|
||||
roles.map((r) => literal(r)),
|
||||
', '
|
||||
)})`
|
||||
)
|
||||
}
|
||||
if (searchQuery.length > 0) {
|
||||
whereConditions.push(`statements.query ~* ${literal(searchQuery)}`)
|
||||
whereConditions.push(safeSql`statements.query ~* ${literal(searchQuery)}`)
|
||||
}
|
||||
if (sources.includes('dashboard') && !sources.includes('non-dashboard')) {
|
||||
whereConditions.push(`statements.query ~* 'source: dashboard'`)
|
||||
whereConditions.push(safeSql`statements.query ~* 'source: dashboard'`)
|
||||
}
|
||||
if (sources.includes('non-dashboard') && !sources.includes('dashboard')) {
|
||||
whereConditions.push(`statements.query !~* 'source: dashboard'`)
|
||||
whereConditions.push(safeSql`statements.query !~* 'source: dashboard'`)
|
||||
}
|
||||
if (Number.isFinite(minCalls) && minCalls > 0) {
|
||||
whereConditions.push(`statements.calls >= ${minCalls}`)
|
||||
whereConditions.push(safeSql`statements.calls >= ${literal(minCalls)}`)
|
||||
}
|
||||
if (Number.isFinite(minTotalTime) && minTotalTime > 0) {
|
||||
whereConditions.push(
|
||||
`(statements.total_exec_time + statements.total_plan_time) >= ${minTotalTime}`
|
||||
safeSql`(statements.total_exec_time + statements.total_plan_time) >= ${literal(minTotalTime)}`
|
||||
)
|
||||
}
|
||||
|
||||
const whereSql = whereConditions.join(' AND ')
|
||||
const whereSql = joinSqlFragments(whereConditions, ' AND ')
|
||||
|
||||
const sql = baseSQL.sql(
|
||||
if (baseSQL.queryType !== 'db') {
|
||||
throw new Error(
|
||||
`Query performance presets must be db queries; got ${baseSQL.queryType} for preset ${preset}`
|
||||
)
|
||||
}
|
||||
|
||||
const sql = baseSQL.safeSql(
|
||||
[],
|
||||
whereSql.length > 0 ? `WHERE ${whereSql}` : undefined,
|
||||
whereSql.length > 0 ? safeSql`WHERE ${whereSql}` : undefined,
|
||||
orderBySql,
|
||||
runIndexAdvisor,
|
||||
filterIndexAdvisor,
|
||||
|
||||
Reference in New Issue
Block a user