mirror of
https://github.com/supabase/supabase.git
synced 2026-09-10 03:51:51 +08:00
## 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 -->
102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
import { safeSql } from '@supabase/pg-meta'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
import { hasIndexRecommendations } from '../../QueryPerformance/IndexAdvisor/index-advisor.utils'
|
|
import type { QueryPerformanceRow } from '../../QueryPerformance/QueryPerformance.types'
|
|
import { classifyQuery } from './useQueryInsightsIssues.utils'
|
|
|
|
vi.mock('../../QueryPerformance/IndexAdvisor/index-advisor.utils', () => ({
|
|
hasIndexRecommendations: vi.fn(),
|
|
}))
|
|
|
|
const baseRow: QueryPerformanceRow = {
|
|
query: safeSql`SELECT * FROM users`,
|
|
calls: 10,
|
|
mean_time: 50,
|
|
min_time: 10,
|
|
max_time: 200,
|
|
total_time: 500,
|
|
prop_total_time: 5,
|
|
rows_read: 100,
|
|
cache_hit_rate: 1,
|
|
rolname: 'postgres',
|
|
application_name: 'test',
|
|
index_advisor_result: null,
|
|
_total_cache_hits: 0,
|
|
_total_cache_misses: 0,
|
|
}
|
|
|
|
describe('classifyQuery', () => {
|
|
it('returns error when index_advisor_result has errors', () => {
|
|
const row = {
|
|
...baseRow,
|
|
index_advisor_result: {
|
|
errors: ['some error'],
|
|
index_statements: [],
|
|
startup_cost_before: 0,
|
|
startup_cost_after: 0,
|
|
total_cost_before: 0,
|
|
total_cost_after: 0,
|
|
},
|
|
}
|
|
const result = classifyQuery(row)
|
|
expect(result.issueType).toBe('error')
|
|
expect(result.hint).toBe('some error')
|
|
})
|
|
|
|
it('returns index when hasIndexRecommendations is true', () => {
|
|
vi.mocked(hasIndexRecommendations).mockReturnValue(true)
|
|
const row = {
|
|
...baseRow,
|
|
index_advisor_result: {
|
|
errors: [],
|
|
index_statements: [safeSql`CREATE INDEX ...`],
|
|
startup_cost_before: 0,
|
|
startup_cost_after: 0,
|
|
total_cost_before: 0,
|
|
total_cost_after: 0,
|
|
},
|
|
}
|
|
const result = classifyQuery(row)
|
|
expect(result.issueType).toBe('index')
|
|
expect(result.hint).toContain('Missing index')
|
|
vi.mocked(hasIndexRecommendations).mockReset()
|
|
})
|
|
|
|
it('returns slow when mean_time exceeds threshold', () => {
|
|
vi.mocked(hasIndexRecommendations).mockReturnValue(false)
|
|
const row = { ...baseRow, mean_time: 300 }
|
|
const result = classifyQuery(row)
|
|
expect(result.issueType).toBe('slow')
|
|
expect(result.hint).toBe('Abnormally slow query detected')
|
|
vi.mocked(hasIndexRecommendations).mockReset()
|
|
})
|
|
|
|
it('returns null issue for healthy queries', () => {
|
|
vi.mocked(hasIndexRecommendations).mockReturnValue(false)
|
|
const row = { ...baseRow, mean_time: 50 }
|
|
const result = classifyQuery(row)
|
|
expect(result.issueType).toBeNull()
|
|
expect(result.hint).toBe('')
|
|
vi.mocked(hasIndexRecommendations).mockReset()
|
|
})
|
|
|
|
it('errors take priority over index recommendations', () => {
|
|
vi.mocked(hasIndexRecommendations).mockReturnValue(true)
|
|
const row = {
|
|
...baseRow,
|
|
index_advisor_result: {
|
|
errors: ['critical error'],
|
|
index_statements: [safeSql`CREATE INDEX ...`],
|
|
startup_cost_before: 0,
|
|
startup_cost_after: 0,
|
|
total_cost_before: 0,
|
|
total_cost_after: 0,
|
|
},
|
|
}
|
|
const result = classifyQuery(row)
|
|
expect(result.issueType).toBe('error')
|
|
vi.mocked(hasIndexRecommendations).mockReset()
|
|
})
|
|
})
|