useSupamonitorIndexAdvisor.ts 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import { useQueries } from '@tanstack/react-query'
  2. import { useIndexAdvisorStatus } from '../../QueryPerformance/hooks/useIsIndexAdvisorStatus'
  3. import type { QueryPerformanceRow } from '../../QueryPerformance/QueryPerformance.types'
  4. import { databaseKeys } from '@/data/database/keys'
  5. import { getIndexAdvisorResult } from '@/data/database/retrieve-index-advisor-result-query'
  6. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  7. function isEligibleQuery(query: string): boolean {
  8. const lower = query.trim().toLowerCase()
  9. if (!lower.startsWith('select') && !lower.startsWith('with')) return false
  10. // Dollar-quoted string literals (e.g. $$...$$ or $tag$...$tag$) break the single-quoted
  11. // SQL embedding in getIndexAdvisorResult. Plain parameter markers ($1, $2, …) are fine.
  12. if (/\$([A-Za-z_][A-Za-z0-9_]*)?\$/.test(query)) return false
  13. return true
  14. }
  15. export function useSupamonitorIndexAdvisor(rows: QueryPerformanceRow[]): QueryPerformanceRow[] {
  16. const { data: project } = useSelectedProjectQuery()
  17. const { isIndexAdvisorEnabled } = useIndexAdvisorStatus()
  18. const eligibleQueries = rows.map((r) => r.query).filter(isEligibleQuery)
  19. const results = useQueries({
  20. queries: eligibleQueries.map((query) => ({
  21. queryKey: databaseKeys.indexAdvisorFromQuery(
  22. project?.ref,
  23. query,
  24. project?.connectionString ?? undefined
  25. ),
  26. queryFn: () =>
  27. getIndexAdvisorResult({
  28. projectRef: project?.ref,
  29. connectionString: project?.connectionString,
  30. query,
  31. }),
  32. enabled: isIndexAdvisorEnabled && !!project?.ref,
  33. retry: false,
  34. staleTime: Infinity,
  35. })),
  36. })
  37. if (!isIndexAdvisorEnabled) return rows
  38. const resultByQuery = new Map(
  39. eligibleQueries.map((query, i) => {
  40. const result = results[i]
  41. // Only treat as ready when status is definitively success or error.
  42. // undefined/pending means data hasn't arrived yet — store undefined so
  43. // classifyQuery can defer slow classification and avoid flicker.
  44. const isReady = result?.status === 'success' || result?.status === 'error'
  45. return [query, isReady ? (result.data ?? null) : undefined]
  46. })
  47. )
  48. return rows.map((row) => ({
  49. ...row,
  50. index_advisor_result: resultByQuery.has(row.query)
  51. ? resultByQuery.get(row.query)
  52. : (row.index_advisor_result ?? null),
  53. }))
  54. }