diff --git a/apps/studio/components/interfaces/QueryInsights/QueryMetricExplorer.tsx b/apps/studio/components/interfaces/QueryInsights/QueryMetricExplorer.tsx
index 85fe286e5c9..6ba3d6434b8 100644
--- a/apps/studio/components/interfaces/QueryInsights/QueryMetricExplorer.tsx
+++ b/apps/studio/components/interfaces/QueryInsights/QueryMetricExplorer.tsx
@@ -9,7 +9,10 @@ import {
CardHeader,
} from 'ui'
import { QueryRowExplorer } from './QueryRowExplorer'
-import { useQueryInsightsMetricsQuery } from 'data/query-insights/query-metrics-query'
+import {
+ useQueryInsightsMetrics,
+ usePreFetchQueryInsightsData,
+} from 'data/query-insights/query-metrics-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import ComposedChart from 'components/ui/Charts/ComposedChart'
import { MultiAttribute } from 'components/ui/Charts/ComposedChart.utils'
@@ -55,6 +58,9 @@ export const QueryMetricExplorer = ({ startTime, endTime }: QueryMetricExplorerP
return { startTime: fallbackStartTime, endTime: fallbackEndTime }
}, [startTime, endTime])
+ // Pre-fetch query data for the QueryRowExplorer
+ usePreFetchQueryInsightsData(project?.ref, effectiveStartTime, effectiveEndTime)
+
// Debug logging
console.log('QueryMetricExplorer Debug:', {
props: { startTime, endTime },
@@ -67,13 +73,7 @@ export const QueryMetricExplorer = ({ startTime, endTime }: QueryMetricExplorerP
data: metricsData,
isLoading,
error,
- } = useQueryInsightsMetricsQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- metric: selectedMetric,
- startTime: effectiveStartTime,
- endTime: effectiveEndTime,
- })
+ } = useQueryInsightsMetrics(project?.ref, selectedMetric, effectiveStartTime, effectiveEndTime)
// Debug logging for query results
console.log('QueryMetricExplorer Query Results:', {
diff --git a/apps/studio/components/interfaces/QueryInsights/QueryQuickGlance.tsx b/apps/studio/components/interfaces/QueryInsights/QueryQuickGlance.tsx
index 387fbc262a3..aa9b189cb1b 100644
--- a/apps/studio/components/interfaces/QueryInsights/QueryQuickGlance.tsx
+++ b/apps/studio/components/interfaces/QueryInsights/QueryQuickGlance.tsx
@@ -1,5 +1,5 @@
import { useMemo } from 'react'
-import { useQueryInsightsGlanceQuery } from 'data/query-insights/query-glance-query'
+import { useQueryInsightsGlance } from 'data/query-insights/query-glance-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Card, CardContent } from 'ui'
import { Database, Clock, AlertCircle } from 'lucide-react'
@@ -29,25 +29,20 @@ export const QueryQuickGlance = ({ startTime, endTime }: QueryQuickGlanceProps)
console.log('QueryQuickGlance Debug:', {
props: { startTime, endTime },
effective: { startTime: effectiveStartTime, endTime: effectiveEndTime },
- project: { ref: project?.ref, hasConnectionString: !!project?.connectionString }
+ project: { ref: project?.ref, hasConnectionString: !!project?.connectionString },
})
const {
data: glanceData,
isLoading,
error,
- } = useQueryInsightsGlanceQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- startTime: effectiveStartTime,
- endTime: effectiveEndTime,
- })
+ } = useQueryInsightsGlance(project?.ref, effectiveStartTime, effectiveEndTime)
// Debug logging for glance results
console.log('QueryQuickGlance Results:', {
glanceData,
isLoading,
- error
+ error,
})
const cards = useMemo(() => {
@@ -110,7 +105,7 @@ export const QueryQuickGlance = ({ startTime, endTime }: QueryQuickGlanceProps)
}
if (error) {
- return
Error loading query insights: {error.message}
+ return Error loading query insights: {String(error)}
}
return (
diff --git a/apps/studio/components/interfaces/QueryInsights/QueryRowExplorer.tsx b/apps/studio/components/interfaces/QueryInsights/QueryRowExplorer.tsx
index e18ae80db17..2445590e49d 100644
--- a/apps/studio/components/interfaces/QueryInsights/QueryRowExplorer.tsx
+++ b/apps/studio/components/interfaces/QueryInsights/QueryRowExplorer.tsx
@@ -1,4 +1,4 @@
-import { ArrowDown, ArrowUp, TextSearch, X } from 'lucide-react'
+import { ArrowDown, ArrowUp, Search, Settings2, TextSearch, X } from 'lucide-react'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
@@ -17,7 +17,18 @@ import {
cn,
} from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
-import { useQueryInsightsQueries, type QueryInsightsQuery } from 'data/query-insights/query-metrics-query'
+import { Input } from 'ui-patterns/DataInputs/Input'
+import { ButtonTooltip } from 'components/ui/ButtonTooltip'
+import {
+ useQueryInsightsQueries,
+ type QueryInsightsQuery,
+} from 'data/query-insights/query-metrics-query'
+import {
+ useSingleQueryLatency,
+ useSingleQueryCalls,
+ useSingleQueryRows,
+ useSingleQueryRowsWritten,
+} from 'data/query-insights/single-query-latency-query'
interface QueryRowExplorerProps {
startTime: string
@@ -40,7 +51,7 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
queries,
isLoading,
error,
- queriesLength: queries?.length
+ queriesLength: queries?.length,
})
const [sort, setSort] = useState<{ column: string; order: 'asc' | 'desc' } | undefined>({
@@ -48,7 +59,7 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
order: 'desc',
})
const [selectedRow, setSelectedRow] = useState()
- const [view, setView] = useState<'details' | 'indexes'>('details')
+ const [view, setView] = useState<'details' | 'indexes' | 'metrics'>('details')
// Define columns similar to QueryPerformance
const columns: Column[] = [
@@ -171,9 +182,7 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
return (
{formattedValue}
- {value && (
-
{(value / 1000).toFixed(2)}s
- )}
+ {value &&
{(value / 1000).toFixed(2)}s
}
)
},
@@ -205,9 +214,7 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
return (
{formattedValue}
- {value && (
-
{(value / 1000).toFixed(2)}s
- )}
+ {value &&
{(value / 1000).toFixed(2)}s
}
)
},
@@ -277,29 +284,66 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
]
// For debugging - show test data if no queries found
- const reportData = queries?.length ? queries : [
- {
- query_id: 1,
- query: 'SELECT * FROM test_table WHERE id = 1',
- total_time: 150.5,
- calls: 10,
- rows_read: 1000,
- rows_insert: 0,
- rows_update: 0,
- rows_delete: 0,
- shared_blks_read: 50,
- shared_blks_hit: 950,
- mean_exec_time: 15.05,
- database: 'postgres',
- timestamp: new Date().toISOString(),
- cmd_type_text: 'SELECT',
- application_name: 'Test App',
- badness_score: 25.5,
- error_count: 0
- } as QueryInsightsQuery
- ]
+ const reportData = queries?.length
+ ? queries
+ : [
+ {
+ query_id: 1,
+ query: 'SELECT * FROM users WHERE id = 1',
+ total_time: 150.5,
+ calls: 10,
+ rows_read: 1000,
+ rows_insert: 0,
+ rows_update: 0,
+ rows_delete: 0,
+ shared_blks_read: 50,
+ shared_blks_hit: 950,
+ mean_exec_time: 15.05,
+ database: 'postgres',
+ timestamp: new Date().toISOString(),
+ cmd_type_text: 'SELECT',
+ application_name: 'Test App',
+ badness_score: 25.5,
+ error_count: 0,
+ startup_cost_before: 0,
+ startup_cost_after: 0,
+ total_cost_before: 0,
+ total_cost_after: 0,
+ index_statements: [],
+ errors: [],
+ } as QueryInsightsQuery,
+ ]
const selectedQuery = selectedRow !== undefined ? reportData[selectedRow] : undefined
+ // Single query metrics hooks
+ const { data: singleQueryLatency } = useSingleQueryLatency(
+ ref,
+ startTime,
+ endTime,
+ selectedQuery?.query_id?.toString()
+ )
+
+ const { data: singleQueryCalls } = useSingleQueryCalls(
+ ref,
+ startTime,
+ endTime,
+ selectedQuery?.query_id?.toString()
+ )
+
+ const { data: singleQueryRows } = useSingleQueryRows(
+ ref,
+ startTime,
+ endTime,
+ selectedQuery?.query_id?.toString()
+ )
+
+ const { data: singleQueryRowsWritten } = useSingleQueryRowsWritten(
+ ref,
+ startTime,
+ endTime,
+ selectedQuery?.query_id?.toString()
+ )
+
const onSortChange = (column: string) => {
let updatedSort: { column: string; order: 'asc' | 'desc' } | undefined = undefined
@@ -326,9 +370,7 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
if (aValue === undefined || bValue === undefined) return 0
if (typeof aValue === 'string' && typeof bValue === 'string') {
- return sort.order === 'asc'
- ? aValue.localeCompare(bValue)
- : bValue.localeCompare(aValue)
+ return sort.order === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue)
}
if (typeof aValue === 'number' && typeof bValue === 'number') {
@@ -338,17 +380,39 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
return 0
})
+ // Debug logging for table data
+ console.log('QueryRowExplorer Table Debug:', {
+ queries,
+ reportData,
+ reportDataLength: reportData.length,
+ sortedDataLength: sortedData.length,
+ isLoading,
+ error,
+ firstRow: sortedData[0],
+ })
+
useEffect(() => {
setSelectedRow(undefined)
}, [startTime, endTime])
return (
-
+
-
+
Database
{selectedQuery.database}
-
Command Type
+
+ Command Type
+
{selectedQuery.cmd_type_text}
-
Application
+
+ Application
+
{selectedQuery.application_name}
-
Timestamp
-
{new Date(selectedQuery.timestamp).toLocaleString()}
+
+ Timestamp
+
+
+ {new Date(selectedQuery.timestamp).toLocaleString()}
+
-
Performance
+
+ Performance
+
-
Total time: {selectedQuery.total_time?.toFixed(2)}ms
-
Mean time: {selectedQuery.mean_exec_time?.toFixed(2)}ms
+
+ Total time: {selectedQuery.total_time?.toFixed(2)}ms
+
+
+ Mean time: {selectedQuery.mean_exec_time?.toFixed(2)}ms
+
Calls: {selectedQuery.calls?.toLocaleString()}
Rows
-
Read: {selectedQuery.rows_read?.toLocaleString()}
-
Insert: {selectedQuery.rows_insert?.toLocaleString()}
-
Update: {selectedQuery.rows_update?.toLocaleString()}
-
Delete: {selectedQuery.rows_delete?.toLocaleString()}
+
+ Read: {selectedQuery.rows_read?.toLocaleString()}
+
+
+ Insert: {selectedQuery.rows_insert?.toLocaleString()}
+
+
+ Update: {selectedQuery.rows_update?.toLocaleString()}
+
+
+ Delete: {selectedQuery.rows_delete?.toLocaleString()}
+
-
Cache Performance
+
+ Cache Performance
+
-
Blocks read: {selectedQuery.shared_blks_read?.toLocaleString()}
-
Blocks hit: {selectedQuery.shared_blks_hit?.toLocaleString()}
-
Hit ratio: {selectedQuery.shared_blks_hit && selectedQuery.shared_blks_read
- ? ((selectedQuery.shared_blks_hit / (selectedQuery.shared_blks_hit + selectedQuery.shared_blks_read)) * 100).toFixed(1) + '%'
- : 'N/A'}
+
+ Blocks read: {selectedQuery.shared_blks_read?.toLocaleString()}
+
+
+ Blocks hit: {selectedQuery.shared_blks_hit?.toLocaleString()}
+
+
+ Hit ratio:{' '}
+ {selectedQuery.shared_blks_hit && selectedQuery.shared_blks_read
+ ? (
+ (selectedQuery.shared_blks_hit /
+ (selectedQuery.shared_blks_hit +
+ selectedQuery.shared_blks_read)) *
+ 100
+ ).toFixed(1) + '%'
+ : 'N/A'}
+
{selectedQuery.error_count && selectedQuery.error_count > 0 && (
Errors
-
{selectedQuery.error_count} slow queries detected
+
+ {selectedQuery.error_count} slow queries detected
+
)}
+
+
+
+
Query Performance Over Time
+
+ {singleQueryLatency && singleQueryLatency.length > 0 && (
+
+
+ Latency
+
+
+ {singleQueryLatency.slice(-5).map((point, index) => (
+
+ {new Date(point.timestamp).toLocaleTimeString()}
+ {point.value?.toFixed(2)}ms
+
+ ))}
+
+
+ )}
+
+ {singleQueryCalls && singleQueryCalls.length > 0 && (
+
+
Calls
+
+ {singleQueryCalls.slice(-5).map((point, index) => (
+
+ {new Date(point.timestamp).toLocaleTimeString()}
+ {point.value?.toLocaleString()}
+
+ ))}
+
+
+ )}
+
+ {singleQueryRows && singleQueryRows.length > 0 && (
+
+
+ Rows Read
+
+
+ {singleQueryRows.slice(-5).map((point, index) => (
+
+ {new Date(point.timestamp).toLocaleTimeString()}
+ {point.value?.toLocaleString()}
+
+ ))}
+
+
+ )}
+
+ {singleQueryRowsWritten && singleQueryRowsWritten.length > 0 && (
+
+
+ Rows Written
+
+
+ {singleQueryRowsWritten.slice(-5).map((point, index) => (
+
+ {new Date(point.timestamp).toLocaleTimeString()}
+ {point.value?.toLocaleString()}
+
+ ))}
+
+
+ )}
+
+ {(!singleQueryLatency || singleQueryLatency.length === 0) &&
+ (!singleQueryCalls || singleQueryCalls.length === 0) &&
+ (!singleQueryRows || singleQueryRows.length === 0) &&
+ (!singleQueryRowsWritten || singleQueryRowsWritten.length === 0) && (
+
+ No metrics data available for this query
+
+ )}
+
+
+
Index Recommendations
- {selectedQuery.index_statements && selectedQuery.index_statements.length > 0 ? (
+ {selectedQuery.index_statements &&
+ selectedQuery.index_statements.length > 0 ? (
- {selectedQuery.index_statements.map((statement: string, index: number) => (
-
- ))}
+ {selectedQuery.index_statements.map(
+ (statement: string, index: number) => (
+
+ )
+ )}
) : (
-
No index recommendations available
+
+ No index recommendations available
+
)}
{selectedQuery.errors && selectedQuery.errors.length > 0 && (
-
Index Advisor Errors
+
+ Index Advisor Errors
+
- {selectedQuery.errors.map((error: string, index: number) => (
-
- {error}
-
- ))}
+ {selectedQuery.errors.map((error: string, index: number) => (
+
+ {error}
+
+ ))}
)}
diff --git a/apps/studio/data/query-insights/query-glance-query.ts b/apps/studio/data/query-insights/query-glance-query.ts
index bdb301b0bd6..b18b22fb570 100644
--- a/apps/studio/data/query-insights/query-glance-query.ts
+++ b/apps/studio/data/query-insights/query-glance-query.ts
@@ -11,7 +11,8 @@ export type QueryInsightsGlance = {
avg_error_rate: number
}
-export const getQueriesGlanceMetrics = (startTime: string, endTime: string) => /* SQL */ `
+export const getQueriesGlanceMetrics = (startTime: string, endTime: string) =>
+ /* SQL */ `
SELECT
COUNT(DISTINCT query) as unique_queries,
SUM(calls) as total_queries,
@@ -43,7 +44,12 @@ export async function getQueryInsightsGlance(
const sql = getQueriesGlanceMetrics(startTime, endTime)
const { result } = await executeSql(
- { projectRef, connectionString, sql, queryKey: queryInsightsKeys.glance(projectRef, startTime, endTime) },
+ {
+ projectRef,
+ connectionString,
+ sql,
+ queryKey: queryInsightsKeys.glance(projectRef, startTime, endTime),
+ },
signal
)
@@ -53,21 +59,29 @@ export async function getQueryInsightsGlance(
export type QueryInsightsGlanceData = Awaited>
export type QueryInsightsGlanceError = ExecuteSqlError
-export const useQueryInsightsGlanceQuery = (
- { projectRef, connectionString, startTime, endTime }: QueryInsightsGlanceVariables,
- { enabled = true, ...options }: UseQueryOptions = {}
-) => {
- return useQuery(
- queryInsightsKeys.glance(projectRef, startTime, endTime),
- ({ signal }) => getQueryInsightsGlance({ projectRef, connectionString, startTime, endTime }, signal),
- {
- enabled: enabled && typeof projectRef !== 'undefined',
- staleTime: 5 * 60 * 1000, // 5 minutes
- cacheTime: 10 * 60 * 1000, // 10 minutes
- refetchOnWindowFocus: false,
- refetchOnMount: false,
- refetchOnReconnect: false,
- ...options
- }
- )
+export function useQueryInsightsGlance(
+ projectRef: string | undefined,
+ startTime: string,
+ endTime: string,
+ options?: UseQueryOptions
+) {
+ return useQuery({
+ queryKey: queryInsightsKeys.glance(projectRef, startTime, endTime),
+ queryFn: async () => {
+ if (!projectRef) throw new Error('Project ref is required')
+
+ const sql = getQueriesGlanceMetrics(startTime, endTime)
+ console.log(`[useQueryInsightsGlance] Executing SQL:`, sql)
+
+ const { result } = await executeSql({
+ projectRef,
+ sql,
+ })
+
+ console.log(`[useQueryInsightsGlance] Result:`, result)
+ return result[0] as QueryInsightsGlance
+ },
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes
+ ...options,
+ })
}
diff --git a/apps/studio/data/query-insights/query-metrics-query.ts b/apps/studio/data/query-insights/query-metrics-query.ts
index 6e39ed38b71..1141493d068 100644
--- a/apps/studio/data/query-insights/query-metrics-query.ts
+++ b/apps/studio/data/query-insights/query-metrics-query.ts
@@ -1,17 +1,9 @@
-import { useQuery, UseQueryOptions } from '@tanstack/react-query'
-import { executeSql, ExecuteSqlError } from '../sql/execute-sql-query'
+import { useQuery, UseQueryOptions, useQueryClient } from '@tanstack/react-query'
+import { executeSql } from '../sql/execute-sql-query'
import { queryInsightsKeys } from './keys'
+import { useEffect, useCallback } from 'react'
export type QueryInsightsMetric = {
- timestamp: string
- value: number
- p50?: number
- p95?: number
- p99?: number
- p99_9?: number
-}
-
-export type QueryInsightsMetricDetail = {
timestamp: string
value?: number
rows?: number
@@ -57,27 +49,37 @@ export type QueryInsightsQuery = {
error_count?: number
}
-export type QueryInsightsMetricsVariables = {
- projectRef?: string
- connectionString?: string | null
- metric: 'rows_read' | 'query_latency' | 'queries_per_second' | 'calls' | 'cache_hits'
- startTime: string
- endTime: string
+export type QueryInsightsMetrics = {
+ queries: {
+ total: number
+ total_time: number
+ avg_time: number
+ total_rows: number
+ }
+ cache: {
+ hit_ratio: number
+ hits: number
+ misses: number
+ }
+ errors: {
+ total: number
+ }
}
-const getQueryInsightsMetricsSQL = (metric: string, startTime: string, endTime: string) => {
+const getMetricsSql = (metric: string, startTime: string, endTime: string) => {
switch (metric) {
case 'rows_read':
return /* SQL */ `
SELECT
bucket_start_time as timestamp,
- SUM(rows) as value
+ SUM(rows) as value,
+ datname as database
FROM pg_stat_monitor
WHERE bucket_start_time >= '${startTime}'::timestamptz
AND bucket_start_time <= '${endTime}'::timestamptz
AND bucket_done = true
AND cmd_type = 1
- GROUP BY bucket_start_time
+ GROUP BY bucket_start_time, datname
ORDER BY timestamp ASC
`
@@ -89,205 +91,261 @@ const getQueryInsightsMetricsSQL = (metric: string, startTime: string, endTime:
percentile_cont(0.95) WITHIN GROUP (ORDER BY mean_exec_time) as p95,
percentile_cont(0.99) WITHIN GROUP (ORDER BY mean_exec_time) as p99,
percentile_cont(0.999) WITHIN GROUP (ORDER BY mean_exec_time) as p99_9,
- AVG(mean_exec_time) as value
+ datname as database
FROM pg_stat_monitor
WHERE bucket_start_time >= '${startTime}'::timestamptz
AND bucket_start_time <= '${endTime}'::timestamptz
- AND bucket_done = true
- GROUP BY bucket_start_time
+ AND bucket_done = true -- Only include completed buckets
+ GROUP BY bucket_start_time, datname
ORDER BY timestamp ASC
`
-
case 'queries_per_second':
return /* SQL */ `
SELECT
bucket_start_time as timestamp,
- COUNT(*) / 300.0 as value
+ COUNT(*) / 300.0 as value, -- Convert to per-second rate (5 mins = 300 seconds)
+ datname as database
FROM pg_stat_monitor
WHERE bucket_start_time >= '${startTime}'::timestamptz
AND bucket_start_time <= '${endTime}'::timestamptz
- AND bucket_done = true
- GROUP BY bucket_start_time
+ AND bucket_done = true -- Only include completed buckets
+ GROUP BY bucket_start_time, datname
ORDER BY timestamp ASC
`
-
case 'calls':
return /* SQL */ `
SELECT
bucket_start_time as timestamp,
- SUM(calls) as value
+ SUM(calls) as value,
+ datname as database
FROM pg_stat_monitor
WHERE bucket_start_time >= '${startTime}'::timestamptz
AND bucket_start_time <= '${endTime}'::timestamptz
- AND bucket_done = true
- GROUP BY bucket_start_time
+ AND bucket_done = true -- Only include completed buckets
+ GROUP BY bucket_start_time, datname
ORDER BY timestamp ASC
`
-
case 'cache_hits':
return /* SQL */ `
SELECT
bucket_start_time as timestamp,
- CASE
- WHEN SUM(shared_blks_read) > 0 THEN
- (SUM(shared_blks_hit)::float / (SUM(shared_blks_hit) + SUM(shared_blks_read))::float) * 100
- ELSE 0
- END as value
+ SUM(shared_blks_hit) as shared_blks_hit,
+ SUM(shared_blks_read) as shared_blks_read,
+ SUM(shared_blks_dirtied) as shared_blks_dirtied,
+ SUM(shared_blks_written) as shared_blks_written,
+ datname as database
FROM pg_stat_monitor
WHERE bucket_start_time >= '${startTime}'::timestamptz
AND bucket_start_time <= '${endTime}'::timestamptz
- AND bucket_done = true
- GROUP BY bucket_start_time
+ AND bucket_done = true -- Only include completed buckets
+ GROUP BY bucket_start_time, datname
+ ORDER BY timestamp ASC
+ `
+ case 'issues':
+ return /* SQL */ `
+ SELECT
+ bucket_start_time as timestamp,
+ COUNT(CASE WHEN mean_exec_time > 1000 AND calls > 1 THEN 1 END) as value,
+ datname as database
+ FROM pg_stat_monitor
+ WHERE bucket_start_time >= '${startTime}'::timestamptz
+ AND bucket_start_time <= '${endTime}'::timestamptz
+ AND bucket_done = true -- Only include completed buckets
+ GROUP BY bucket_start_time, datname
ORDER BY timestamp ASC
`
-
default:
return /* SQL */ `
SELECT
bucket_start_time as timestamp,
- COUNT(*) as value
+ SUM(rows) / 300 as value, -- Convert to per-second rate (5 mins = 300 seconds)
+ datname as database
FROM pg_stat_monitor
WHERE bucket_start_time >= '${startTime}'::timestamptz
AND bucket_start_time <= '${endTime}'::timestamptz
- AND bucket_done = true
- GROUP BY bucket_start_time
+ AND bucket_done = true -- Only include completed buckets
+ GROUP BY bucket_start_time, datname
ORDER BY timestamp ASC
`
}
}
-export async function getQueryInsightsMetrics(
- { projectRef, connectionString, metric, startTime, endTime }: QueryInsightsMetricsVariables,
- signal?: AbortSignal
-) {
- const sql = getQueryInsightsMetricsSQL(metric, startTime, endTime)
-
- console.log('getQueryInsightsMetrics Debug:', {
- projectRef,
- hasConnectionString: !!connectionString,
- metric,
- startTime,
- endTime,
- sql
- })
-
- const { result } = await executeSql(
- {
- projectRef,
- connectionString,
- sql,
- queryKey: queryInsightsKeys.metrics(projectRef, metric, startTime, endTime)
- },
- signal
+const getQueriesSql = (startTime: string, endTime: string) => /* SQL */ `
+ WITH base_queries AS (
+ SELECT
+ queryid AS query_id,
+ -- Prefer top-level query if available, else fallback to normalized form
+ COALESCE(MAX(top_query), MAX(query)) AS query,
+ -- Total time spent executing this query across all calls
+ SUM(total_exec_time) AS total_time,
+ -- Number of times this query was executed
+ SUM(calls) AS calls,
+ -- Total rows affected or returned depending on command type
+ SUM(CASE WHEN cmd_type = 1 THEN rows ELSE 0 END) AS rows_read, -- SELECT
+ SUM(CASE WHEN cmd_type = 2 THEN rows ELSE 0 END) AS rows_insert, -- INSERT
+ SUM(CASE WHEN cmd_type = 3 THEN rows ELSE 0 END) AS rows_update, -- UPDATE
+ SUM(CASE WHEN cmd_type = 4 THEN rows ELSE 0 END) AS rows_delete, -- DELETE
+ -- Buffer activity: disk reads vs cache hits
+ SUM(shared_blks_read) AS shared_blks_read,
+ SUM(shared_blks_hit) AS shared_blks_hit,
+ -- Mean execution time per call
+ SUM(total_exec_time) / NULLIF(SUM(calls), 0) AS mean_exec_time,
+ -- Metadata
+ STRING_AGG(DISTINCT datname, ', ') AS database,
+ MAX(bucket_start_time) AS timestamp,
+ MAX(get_cmd_type(cmd_type)) AS cmd_type_text,
+ STRING_AGG(DISTINCT COALESCE(application_name, 'Unknown'), ', ') AS application_name,
+ -- Total number of rows affected or returned across all executions
+ (
+ SUM(CASE WHEN cmd_type = 1 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 2 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 3 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 4 THEN rows ELSE 0 END)
+ ) AS rows_total,
+ -- Error information (using available columns)
+ COUNT(CASE WHEN mean_exec_time > 1000 AND calls > 1 THEN 1 END) AS error_count,
+ -- Heuristic "badness score":
+ -- Penalizes queries that are slow, frequent, and touch few rows
+ (
+ (SUM(total_exec_time) / NULLIF(SUM(calls), 1)) * -- mean execution time
+ LOG(GREATEST(SUM(calls), 1)) / -- scaled by frequency
+ GREATEST( -- penalize low-row queries
+ SUM(CASE WHEN cmd_type = 1 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 2 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 3 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 4 THEN rows ELSE 0 END),
+ 1)
+ ) AS badness_score
+ FROM pg_stat_monitor
+ WHERE bucket_start_time >= '${startTime}'
+ AND bucket_start_time <= '${endTime}'
+ AND bucket_done = true
+ -- AND cmd_type IN (1, 2, 3, 4) -- Only consider SELECT, INSERT, UPDATE, DELETE
+ GROUP BY queryid
)
+ SELECT
+ q.*,
+ ia.*
+ FROM
+ base_queries q
+ LEFT JOIN LATERAL (
+ SELECT *
+ FROM index_advisor(q.query)
+ ) ia ON q.query ILIKE 'SELECT%' OR q.query ILIKE 'WITH%'
+ ORDER BY badness_score DESC
+ LIMIT 9999;
+`
- console.log('getQueryInsightsMetrics Result:', {
- result,
- resultLength: result?.length,
- firstRow: result?.[0]
- })
-
- return result as QueryInsightsMetric[]
-}
-
-export type QueryInsightsMetricsData = Awaited>
-export type QueryInsightsMetricsError = ExecuteSqlError
-
-export const useQueryInsightsMetricsQuery = (
- { projectRef, connectionString, metric, startTime, endTime }: QueryInsightsMetricsVariables,
- { enabled = true, ...options }: UseQueryOptions = {}
-) => {
- return useQuery(
- queryInsightsKeys.metrics(projectRef, metric, startTime, endTime),
- ({ signal }) => getQueryInsightsMetrics({ projectRef, connectionString, metric, startTime, endTime }, signal),
- {
- enabled: enabled && typeof projectRef !== 'undefined',
- staleTime: 5 * 60 * 1000, // 5 minutes
- cacheTime: 10 * 60 * 1000, // 10 minutes
- refetchOnWindowFocus: false,
- refetchOnMount: false,
- refetchOnReconnect: false,
- ...options
- }
+const getQueriesWithErrorsSql = (startTime: string, endTime: string) => /* SQL */ `
+ WITH base_queries AS (
+ SELECT
+ queryid AS query_id,
+ -- Prefer top-level query if available, else fallback to normalized form
+ COALESCE(MAX(top_query), MAX(query)) AS query,
+ -- Total time spent executing this query across all calls
+ SUM(total_exec_time) AS total_time,
+ -- Number of times this query was executed
+ SUM(calls) AS calls,
+ -- Total rows affected or returned depending on command type
+ SUM(CASE WHEN cmd_type = 1 THEN rows ELSE 0 END) AS rows_read, -- SELECT
+ SUM(CASE WHEN cmd_type = 2 THEN rows ELSE 0 END) AS rows_insert, -- INSERT
+ SUM(CASE WHEN cmd_type = 3 THEN rows ELSE 0 END) AS rows_update, -- UPDATE
+ SUM(CASE WHEN cmd_type = 4 THEN rows ELSE 0 END) AS rows_delete, -- DELETE
+ -- Buffer activity: disk reads vs cache hits
+ SUM(shared_blks_read) AS shared_blks_read,
+ SUM(shared_blks_hit) AS shared_blks_hit,
+ -- Mean execution time per call
+ SUM(total_exec_time) / NULLIF(SUM(calls), 0) AS mean_exec_time,
+ -- Metadata
+ STRING_AGG(DISTINCT datname, ', ') AS database,
+ MAX(bucket_start_time) AS timestamp,
+ MAX(get_cmd_type(cmd_type)) AS cmd_type_text,
+ STRING_AGG(DISTINCT COALESCE(application_name, 'Unknown'), ', ') AS application_name,
+ -- Total number of rows affected or returned across all executions
+ (
+ SUM(CASE WHEN cmd_type = 1 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 2 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 3 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 4 THEN rows ELSE 0 END)
+ ) AS rows_total,
+ -- Error information (using available columns)
+ COUNT(CASE WHEN mean_exec_time > 1000 AND calls > 1 THEN 1 END) AS error_count,
+ -- Heuristic "badness score":
+ -- Penalizes queries that are slow, frequent, and touch few rows
+ (
+ (SUM(total_exec_time) / NULLIF(SUM(calls), 1)) * -- mean execution time
+ LOG(GREATEST(SUM(calls), 1)) / -- scaled by frequency
+ GREATEST( -- penalize low-row queries
+ SUM(CASE WHEN cmd_type = 1 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 2 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 3 THEN rows ELSE 0 END) +
+ SUM(CASE WHEN cmd_type = 4 THEN rows ELSE 0 END),
+ 1)
+ ) AS badness_score
+ FROM pg_stat_monitor
+ WHERE bucket_start_time >= '${startTime}'
+ AND bucket_start_time <= '${endTime}'
+ AND bucket_done = true
+ AND mean_exec_time > 1000
+ AND calls > 1 -- Only include queries that are slow and called multiple times
+ GROUP BY queryid
)
-}
+ SELECT
+ q.*,
+ ia.*
+ FROM
+ base_queries q
+ LEFT JOIN LATERAL (
+ SELECT *
+ FROM index_advisor(q.query)
+ ) ia ON q.query ILIKE 'SELECT%' OR q.query ILIKE 'WITH%'
+ ORDER BY error_count DESC, badness_score DESC
+ LIMIT 9999;
+`
-// Hook to fetch all queries
-export const useQueryInsightsQueries = (
+export function useQueryInsightsMetrics(
projectRef: string | undefined,
+ metric: string,
startTime: string,
endTime: string,
- options?: UseQueryOptions
-) => {
+ options?: UseQueryOptions
+) {
return useQuery({
- queryKey: queryInsightsKeys.queries(projectRef, startTime, endTime),
+ queryKey: queryInsightsKeys.metrics(projectRef, metric, startTime, endTime),
queryFn: async () => {
if (!projectRef) throw new Error('Project ref is required')
- const sql = /* SQL */ `
- WITH base_queries AS (
- SELECT
- queryid AS query_id,
- COALESCE(MAX(top_query), MAX(query)) AS query,
- SUM(total_exec_time) AS total_time,
- SUM(calls) AS calls,
- SUM(CASE WHEN cmd_type = 1 THEN rows ELSE 0 END) AS rows_read,
- SUM(CASE WHEN cmd_type = 2 THEN rows ELSE 0 END) AS rows_insert,
- SUM(CASE WHEN cmd_type = 3 THEN rows ELSE 0 END) AS rows_update,
- SUM(CASE WHEN cmd_type = 4 THEN rows ELSE 0 END) AS rows_delete,
- SUM(shared_blks_read) AS shared_blks_read,
- SUM(shared_blks_hit) AS shared_blks_hit,
- SUM(total_exec_time) / NULLIF(SUM(calls), 0) AS mean_exec_time,
- STRING_AGG(DISTINCT datname, ', ') AS database,
- MAX(bucket_start_time) AS timestamp,
- MAX(get_cmd_type(cmd_type)) AS cmd_type_text,
- STRING_AGG(DISTINCT COALESCE(application_name, 'Unknown'), ', ') AS application_name,
- COUNT(CASE WHEN mean_exec_time > 1000 AND calls > 1 THEN 1 END) AS error_count,
- (
- (SUM(total_exec_time) / NULLIF(SUM(calls), 1)) *
- LOG(GREATEST(SUM(calls), 1)) /
- GREATEST(
- SUM(CASE WHEN cmd_type = 1 THEN rows ELSE 0 END) +
- SUM(CASE WHEN cmd_type = 2 THEN rows ELSE 0 END) +
- SUM(CASE WHEN cmd_type = 3 THEN rows ELSE 0 END) +
- SUM(CASE WHEN cmd_type = 4 THEN rows ELSE 0 END),
- 1)
- ) AS badness_score
- FROM pg_stat_monitor
- WHERE bucket_start_time >= '${startTime}'
- AND bucket_start_time <= '${endTime}'
- AND bucket_done = true
- GROUP BY queryid
- )
- SELECT
- q.*,
- ia.*
- FROM
- base_queries q
- LEFT JOIN LATERAL (
- SELECT *
- FROM index_advisor(q.query)
- ) ia ON q.query ILIKE 'SELECT%' OR q.query ILIKE 'WITH%'
- ORDER BY badness_score DESC
- LIMIT 9999;
- `
-
- console.log('useQueryInsightsQueries Debug:', {
- projectRef,
- startTime,
- endTime,
- sql
- })
+ const sql = getMetricsSql(metric, startTime, endTime)
+ console.log(`[useQueryInsightsMetrics] Executing SQL for ${metric}:`, sql)
const { result } = await executeSql({
projectRef,
sql,
})
- console.log('useQueryInsightsQueries Result:', {
- result,
- resultLength: result?.length,
- firstRow: result?.[0]
+ console.log(`[useQueryInsightsMetrics] Result for ${metric}:`, result)
+
+ return result as QueryInsightsMetric[]
+ },
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes
+ ...options,
+ })
+}
+
+export function useQueryInsightsQueries(
+ projectRef: string | undefined,
+ startTime: string,
+ endTime: string,
+ options?: UseQueryOptions
+) {
+ return useQuery({
+ queryKey: queryInsightsKeys.queries(projectRef, startTime, endTime),
+ queryFn: async () => {
+ if (!projectRef) throw new Error('Project ref is required')
+
+ const { result } = await executeSql({
+ projectRef,
+ sql: getQueriesSql(startTime, endTime),
})
return result as QueryInsightsQuery[]
@@ -295,4 +353,148 @@ export const useQueryInsightsQueries = (
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
...options,
})
-}
\ No newline at end of file
+}
+
+export function useQueryInsightsQueriesWithErrors(
+ projectRef: string | undefined,
+ startTime: string,
+ endTime: string,
+ options?: UseQueryOptions
+) {
+ return useQuery({
+ queryKey: queryInsightsKeys.queriesWithErrors(projectRef, startTime, endTime),
+ queryFn: async () => {
+ if (!projectRef) throw new Error('Project ref is required')
+
+ const { result } = await executeSql({
+ projectRef,
+ sql: getQueriesWithErrorsSql(startTime, endTime),
+ })
+
+ return result as QueryInsightsQuery[]
+ },
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes
+ ...options,
+ })
+}
+
+// Hook to pre-fetch all metrics data for the current time range
+export function usePreFetchQueryInsightsData(
+ projectRef: string | undefined,
+ startTime: string,
+ endTime: string
+) {
+ const queryClient = useQueryClient()
+
+ useEffect(() => {
+ if (!projectRef) return
+
+ // Pre-fetch all metric types
+ const metricTypes: Array<{ id: string; label: string }> = [
+ { id: 'query_latency', label: 'Query latency' },
+ { id: 'rows_read', label: 'Rows read' },
+
+ { id: 'calls', label: 'Calls' },
+ { id: 'cache_hits', label: 'Cache hits' },
+ { id: 'issues', label: 'Issues' },
+ ]
+
+ // Pre-fetch metrics data
+ metricTypes.forEach((metric) => {
+ queryClient.prefetchQuery({
+ queryKey: queryInsightsKeys.metrics(projectRef, metric.id, startTime, endTime),
+ queryFn: async () => {
+ const sql = getMetricsSql(metric.id, startTime, endTime)
+ console.log(`[usePreFetchQueryInsightsData] Executing SQL for ${metric.id}:`, sql)
+
+ const { result } = await executeSql({
+ projectRef,
+ sql,
+ })
+
+ console.log(`[usePreFetchQueryInsightsData] Result for ${metric.id}:`, result)
+ return result as QueryInsightsMetric[]
+ },
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes
+ })
+ })
+
+ // Pre-fetch queries data
+ queryClient.prefetchQuery({
+ queryKey: queryInsightsKeys.queries(projectRef, startTime, endTime),
+ queryFn: async () => {
+ const { result } = await executeSql({
+ projectRef,
+ sql: getQueriesSql(startTime, endTime),
+ })
+ return result as QueryInsightsQuery[]
+ },
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes
+ })
+
+ // Pre-fetch queries with errors data
+ queryClient.prefetchQuery({
+ queryKey: queryInsightsKeys.queriesWithErrors(projectRef, startTime, endTime),
+ queryFn: async () => {
+ const { result } = await executeSql({
+ projectRef,
+ sql: getQueriesWithErrorsSql(startTime, endTime),
+ })
+ return result as QueryInsightsQuery[]
+ },
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes
+ })
+ }, [projectRef, startTime, endTime, queryClient])
+}
+
+// Hook for better cache management and manual refresh control
+export function useQueryInsightsCacheManager(
+ projectRef: string | undefined,
+ startTime: string,
+ endTime: string
+) {
+ const queryClient = useQueryClient()
+
+ const refreshAllData = useCallback(async () => {
+ if (!projectRef) return
+
+ const metricTypes = ['query_latency', 'rows_read', 'calls', 'cache_hits', 'issues']
+
+ // Invalidate and refetch all metrics
+ await Promise.all([
+ ...metricTypes.map((metric) =>
+ queryClient.invalidateQueries({
+ queryKey: queryInsightsKeys.metrics(projectRef, metric, startTime, endTime),
+ })
+ ),
+ queryClient.invalidateQueries({
+ queryKey: queryInsightsKeys.queries(projectRef, startTime, endTime),
+ }),
+ queryClient.invalidateQueries({
+ queryKey: queryInsightsKeys.queriesWithErrors(projectRef, startTime, endTime),
+ }),
+ ])
+ }, [projectRef, startTime, endTime, queryClient])
+
+ const isAnyDataStale = useCallback(() => {
+ if (!projectRef) return false
+
+ const metricTypes = ['query_latency', 'rows_read', 'calls', 'cache_hits', 'issues']
+
+ return (
+ metricTypes.some((metric) => {
+ const query = queryClient.getQueryData(
+ queryInsightsKeys.metrics(projectRef, metric, startTime, endTime)
+ )
+ return !query
+ }) ||
+ !queryClient.getQueryData(queryInsightsKeys.queries(projectRef, startTime, endTime)) ||
+ !queryClient.getQueryData(queryInsightsKeys.queriesWithErrors(projectRef, startTime, endTime))
+ )
+ }, [projectRef, startTime, endTime, queryClient])
+
+ return {
+ refreshAllData,
+ isAnyDataStale,
+ }
+}
diff --git a/apps/studio/data/query-insights/single-query-latency-query.ts b/apps/studio/data/query-insights/single-query-latency-query.ts
new file mode 100644
index 00000000000..859f53df833
--- /dev/null
+++ b/apps/studio/data/query-insights/single-query-latency-query.ts
@@ -0,0 +1,193 @@
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+import { executeSql } from '../sql/execute-sql-query'
+import { QueryInsightsMetric } from './query-metrics-query'
+
+const singleQueryLatencyKeys = {
+ base: ['single-query-latency'] as const,
+ latency: (
+ projectRef: string | undefined,
+ queryId: string | undefined,
+ startTime: string,
+ endTime: string
+ ) => [...singleQueryLatencyKeys.base, projectRef, queryId, startTime, endTime] as const,
+}
+
+const singleQueryCallsKeys = {
+ base: ['single-query-calls'] as const,
+ calls: (
+ projectRef: string | undefined,
+ queryId: string | undefined,
+ startTime: string,
+ endTime: string
+ ) => [...singleQueryCallsKeys.base, projectRef, queryId, startTime, endTime] as const,
+}
+
+const getSingleQueryLatencySql = (
+ queryId: string,
+ startTime: string,
+ endTime: string
+) => /* SQL */ `
+ SELECT
+ bucket_start_time as timestamp,
+ mean_exec_time as value,
+ calls,
+ datname as database
+ FROM pg_stat_monitor
+ WHERE bucket_start_time >= '${startTime}'::timestamptz
+ AND bucket_start_time <= '${endTime}'::timestamptz
+ AND bucket_done = true -- Only include completed buckets
+ AND queryid = ${queryId}::bigint
+ ORDER BY timestamp ASC
+`
+
+const getSingleQueryRowsSql = (queryId: string, startTime: string, endTime: string) => /* SQL */ `
+ SELECT
+ bucket_start_time as timestamp,
+ SUM(rows) as value,
+ datname as database
+ FROM pg_stat_monitor
+ WHERE bucket_start_time >= '${startTime}'::timestamptz
+ AND bucket_start_time <= '${endTime}'::timestamptz
+ AND bucket_done = true
+ AND queryid = ${queryId}::bigint
+ AND cmd_type = 1
+ GROUP BY bucket_start_time, datname
+ ORDER BY timestamp ASC
+`
+
+const getSingleQueryRowsWrittenSql = (queryId: string, startTime: string, endTime: string) => /* SQL */ `
+ SELECT
+ bucket_start_time as timestamp,
+ SUM(CASE WHEN cmd_type IN (2, 3, 4) THEN rows ELSE 0 END) as value,
+ datname as database
+ FROM pg_stat_monitor
+ WHERE bucket_start_time >= '${startTime}'::timestamptz
+ AND bucket_start_time <= '${endTime}'::timestamptz
+ AND bucket_done = true
+ AND queryid = ${queryId}::bigint
+ GROUP BY bucket_start_time, datname
+ ORDER BY timestamp ASC
+`
+
+const getSingleQueryCallsSql = (queryId: string, startTime: string, endTime: string) => /* SQL */ `
+ SELECT
+ bucket_start_time as timestamp,
+ calls as value,
+ datname as database
+ FROM pg_stat_monitor
+ WHERE bucket_start_time >= '${startTime}'::timestamptz
+ AND bucket_start_time <= '${endTime}'::timestamptz
+ AND bucket_done = true
+ AND queryid = ${queryId}::bigint
+ GROUP BY bucket_start_time, calls, datname
+ ORDER BY timestamp ASC
+`
+
+export function useSingleQueryLatency(
+ projectRef: string | undefined,
+ startTime: string,
+ endTime: string,
+ queryId: string | undefined,
+ options?: UseQueryOptions
+) {
+ return useQuery({
+ queryKey: singleQueryLatencyKeys.latency(projectRef, queryId, startTime, endTime),
+ queryFn: async () => {
+ if (!projectRef) throw new Error('Project ref is required')
+ if (!queryId) return []
+
+ const { result } = await executeSql({
+ projectRef,
+ sql: getSingleQueryLatencySql(queryId, startTime, endTime),
+ })
+
+ console.log('Chart data points:', result)
+ return result as QueryInsightsMetric[]
+ },
+ enabled: !!queryId && !!projectRef,
+ ...options,
+ })
+}
+
+export function useSingleQueryRows(
+ projectRef: string | undefined,
+ startTime: string,
+ endTime: string,
+ queryId: string | undefined,
+ options?: UseQueryOptions
+) {
+ return useQuery({
+ queryKey: [...singleQueryLatencyKeys.base, 'rows', projectRef, queryId, startTime, endTime],
+ queryFn: async () => {
+ if (!projectRef) throw new Error('Project ref is required')
+ if (!queryId) return []
+
+ const sql = getSingleQueryRowsSql(queryId, startTime, endTime)
+ console.log('[useSingleQueryRows] Executing SQL:', sql)
+
+ const { result } = await executeSql({
+ projectRef,
+ sql,
+ })
+
+ console.log('[useSingleQueryRows] Result:', result)
+ return result as QueryInsightsMetric[]
+ },
+ enabled: !!queryId && !!projectRef,
+ ...options,
+ })
+}
+
+export function useSingleQueryRowsWritten(
+ projectRef: string | undefined,
+ startTime: string,
+ endTime: string,
+ queryId: string | undefined,
+ options?: UseQueryOptions
+) {
+ return useQuery({
+ queryKey: [...singleQueryLatencyKeys.base, 'rows-written', projectRef, queryId, startTime, endTime],
+ queryFn: async () => {
+ if (!projectRef) throw new Error('Project ref is required')
+ if (!queryId) return []
+
+ const sql = getSingleQueryRowsWrittenSql(queryId, startTime, endTime)
+ console.log('[useSingleQueryRowsWritten] Executing SQL:', sql)
+
+ const { result } = await executeSql({
+ projectRef,
+ sql,
+ })
+
+ console.log('[useSingleQueryRowsWritten] Result:', result)
+ return result as QueryInsightsMetric[]
+ },
+ enabled: !!queryId && !!projectRef,
+ ...options,
+ })
+}
+
+export function useSingleQueryCalls(
+ projectRef: string | undefined,
+ startTime: string,
+ endTime: string,
+ queryId: string | undefined,
+ options?: UseQueryOptions
+) {
+ return useQuery({
+ queryKey: singleQueryCallsKeys.calls(projectRef, queryId, startTime, endTime),
+ queryFn: async () => {
+ if (!projectRef) throw new Error('Project ref is required')
+ if (!queryId) return []
+
+ const { result } = await executeSql({
+ projectRef,
+ sql: getSingleQueryCallsSql(queryId, startTime, endTime),
+ })
+
+ return result as QueryInsightsMetric[]
+ },
+ enabled: !!queryId && !!projectRef,
+ ...options,
+ })
+}
\ No newline at end of file