fix: use old sql queries as they were working

This commit is contained in:
kemal committed 2025-08-27 20:06:39 +01:00
1 parent d54cb2ea61
commit 8ba76455f1
6 files changed
+875 -267

No files matched your search

@@ -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:', {
@@ -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 <div className="text-red-500">Error loading query insights: {error.message}</div>
return <div className="text-red-500">Error loading query insights: {String(error)}</div>
}
return (
@@ -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<number>()
const [view, setView] = useState<'details' | 'indexes'>('details')
const [view, setView] = useState<'details' | 'indexes' | 'metrics'>('details')
// Define columns similar to QueryPerformance
const columns: Column<QueryInsightsQuery>[] = [
@@ -171,9 +182,7 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
return (
<div className="w-full flex flex-col justify-center font-mono text-xs text-right">
<p>{formattedValue}</p>
{value && (
<p className="text-foreground-lighter">{(value / 1000).toFixed(2)}s</p>
)}
{value && <p className="text-foreground-lighter">{(value / 1000).toFixed(2)}s</p>}
</div>
)
},
@@ -205,9 +214,7 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
return (
<div className="w-full flex flex-col justify-center font-mono text-xs text-right">
<p>{formattedValue}</p>
{value && (
<p className="text-foreground-lighter">{(value / 1000).toFixed(2)}s</p>
)}
{value && <p className="text-foreground-lighter">{(value / 1000).toFixed(2)}s</p>}
</div>
)
},
@@ -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 (
<div className="border-t bg-surface-100 flex flex-col h-full">
<div className="border-t bg-surface-100 flex flex-col h-96 overflow-auto">
<div className="px-4 py-2 border-b flex justify-between items-center">
<span>Search</span>
<span>Filters</span>
<Input
size="tiny"
placeholder="Filter by query name..."
icon={<Search size={14} />}
className="w-64"
/>
<ButtonTooltip
type="default"
size="tiny"
icon={<Settings2 size={14} />}
tooltip={{ content: { side: 'bottom', text: 'Toggle columns' } }}
className="!px-1.5"
/>
</div>
<ResizablePanelGroup
direction="horizontal"
className="relative flex flex-grow bg-alternative min-h-0"
@@ -363,6 +427,9 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
headerRowHeight={36}
columns={columns}
rows={sortedData}
onRowsChange={(newRows) => {
console.log('DataGrid rows changed:', newRows.length)
}}
rowClass={(_, idx) => {
const isSelected = idx === selectedRow
return [
@@ -408,7 +475,12 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
{selectedRow !== undefined && selectedQuery && (
<>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={30} maxSize={45} minSize={30} className="bg-studio border-t">
<ResizablePanel
defaultSize={30}
maxSize={45}
minSize={30}
className="bg-studio border-t"
>
<Button
type="text"
className="absolute top-3 right-3 px-1"
@@ -427,6 +499,12 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
>
Query details
</TabsTrigger_Shadcn_>
<TabsTrigger_Shadcn_
value="metrics"
className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent !shadow-none"
>
Metrics
</TabsTrigger_Shadcn_>
{selectedQuery.index_statements && selectedQuery.index_statements.length > 0 && (
<TabsTrigger_Shadcn_
value="indexes"
@@ -447,65 +525,184 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
{selectedQuery.query}
</pre>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<h5 className="text-xs font-medium text-foreground-light mb-1">Database</h5>
<p className="text-sm">{selectedQuery.database}</p>
</div>
<div>
<h5 className="text-xs font-medium text-foreground-light mb-1">Command Type</h5>
<h5 className="text-xs font-medium text-foreground-light mb-1">
Command Type
</h5>
<p className="text-sm">{selectedQuery.cmd_type_text}</p>
</div>
<div>
<h5 className="text-xs font-medium text-foreground-light mb-1">Application</h5>
<h5 className="text-xs font-medium text-foreground-light mb-1">
Application
</h5>
<p className="text-sm">{selectedQuery.application_name}</p>
</div>
<div>
<h5 className="text-xs font-medium text-foreground-light mb-1">Timestamp</h5>
<p className="text-sm">{new Date(selectedQuery.timestamp).toLocaleString()}</p>
<h5 className="text-xs font-medium text-foreground-light mb-1">
Timestamp
</h5>
<p className="text-sm">
{new Date(selectedQuery.timestamp).toLocaleString()}
</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<h5 className="text-xs font-medium text-foreground-light mb-1">Performance</h5>
<h5 className="text-xs font-medium text-foreground-light mb-1">
Performance
</h5>
<div className="space-y-1">
<p className="text-xs">Total time: {selectedQuery.total_time?.toFixed(2)}ms</p>
<p className="text-xs">Mean time: {selectedQuery.mean_exec_time?.toFixed(2)}ms</p>
<p className="text-xs">
Total time: {selectedQuery.total_time?.toFixed(2)}ms
</p>
<p className="text-xs">
Mean time: {selectedQuery.mean_exec_time?.toFixed(2)}ms
</p>
<p className="text-xs">Calls: {selectedQuery.calls?.toLocaleString()}</p>
</div>
</div>
<div>
<h5 className="text-xs font-medium text-foreground-light mb-1">Rows</h5>
<div className="space-y-1">
<p className="text-xs">Read: {selectedQuery.rows_read?.toLocaleString()}</p>
<p className="text-xs">Insert: {selectedQuery.rows_insert?.toLocaleString()}</p>
<p className="text-xs">Update: {selectedQuery.rows_update?.toLocaleString()}</p>
<p className="text-xs">Delete: {selectedQuery.rows_delete?.toLocaleString()}</p>
<p className="text-xs">
Read: {selectedQuery.rows_read?.toLocaleString()}
</p>
<p className="text-xs">
Insert: {selectedQuery.rows_insert?.toLocaleString()}
</p>
<p className="text-xs">
Update: {selectedQuery.rows_update?.toLocaleString()}
</p>
<p className="text-xs">
Delete: {selectedQuery.rows_delete?.toLocaleString()}
</p>
</div>
</div>
</div>
<div>
<h5 className="text-xs font-medium text-foreground-light mb-1">Cache Performance</h5>
<h5 className="text-xs font-medium text-foreground-light mb-1">
Cache Performance
</h5>
<div className="space-y-1">
<p className="text-xs">Blocks read: {selectedQuery.shared_blks_read?.toLocaleString()}</p>
<p className="text-xs">Blocks hit: {selectedQuery.shared_blks_hit?.toLocaleString()}</p>
<p className="text-xs">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'}</p>
<p className="text-xs">
Blocks read: {selectedQuery.shared_blks_read?.toLocaleString()}
</p>
<p className="text-xs">
Blocks hit: {selectedQuery.shared_blks_hit?.toLocaleString()}
</p>
<p className="text-xs">
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'}
</p>
</div>
</div>
{selectedQuery.error_count && selectedQuery.error_count > 0 && (
<div>
<h5 className="text-xs font-medium text-red-500 mb-1">Errors</h5>
<p className="text-xs text-red-500">{selectedQuery.error_count} slow queries detected</p>
<p className="text-xs text-red-500">
{selectedQuery.error_count} slow queries detected
</p>
</div>
)}
</div>
</TabsContent_Shadcn_>
<TabsContent_Shadcn_
value="metrics"
className="mt-0 flex-grow min-h-0 overflow-y-auto"
>
<div className="p-5 space-y-4">
<div>
<h4 className="text-sm font-medium mb-2">Query Performance Over Time</h4>
{singleQueryLatency && singleQueryLatency.length > 0 && (
<div className="mb-4">
<h5 className="text-xs font-medium text-foreground-light mb-2">
Latency
</h5>
<div className="space-y-1">
{singleQueryLatency.slice(-5).map((point, index) => (
<div key={index} className="flex justify-between text-xs">
<span>{new Date(point.timestamp).toLocaleTimeString()}</span>
<span>{point.value?.toFixed(2)}ms</span>
</div>
))}
</div>
</div>
)}
{singleQueryCalls && singleQueryCalls.length > 0 && (
<div className="mb-4">
<h5 className="text-xs font-medium text-foreground-light mb-2">Calls</h5>
<div className="space-y-1">
{singleQueryCalls.slice(-5).map((point, index) => (
<div key={index} className="flex justify-between text-xs">
<span>{new Date(point.timestamp).toLocaleTimeString()}</span>
<span>{point.value?.toLocaleString()}</span>
</div>
))}
</div>
</div>
)}
{singleQueryRows && singleQueryRows.length > 0 && (
<div className="mb-4">
<h5 className="text-xs font-medium text-foreground-light mb-2">
Rows Read
</h5>
<div className="space-y-1">
{singleQueryRows.slice(-5).map((point, index) => (
<div key={index} className="flex justify-between text-xs">
<span>{new Date(point.timestamp).toLocaleTimeString()}</span>
<span>{point.value?.toLocaleString()}</span>
</div>
))}
</div>
</div>
)}
{singleQueryRowsWritten && singleQueryRowsWritten.length > 0 && (
<div className="mb-4">
<h5 className="text-xs font-medium text-foreground-light mb-2">
Rows Written
</h5>
<div className="space-y-1">
{singleQueryRowsWritten.slice(-5).map((point, index) => (
<div key={index} className="flex justify-between text-xs">
<span>{new Date(point.timestamp).toLocaleTimeString()}</span>
<span>{point.value?.toLocaleString()}</span>
</div>
))}
</div>
</div>
)}
{(!singleQueryLatency || singleQueryLatency.length === 0) &&
(!singleQueryCalls || singleQueryCalls.length === 0) &&
(!singleQueryRows || singleQueryRows.length === 0) &&
(!singleQueryRowsWritten || singleQueryRowsWritten.length === 0) && (
<p className="text-sm text-foreground-light">
No metrics data available for this query
</p>
)}
</div>
</div>
</TabsContent_Shadcn_>
<TabsContent_Shadcn_
value="indexes"
className="mt-0 flex-grow min-h-0 overflow-y-auto"
@@ -513,28 +710,35 @@ export const QueryRowExplorer = ({ startTime, endTime }: QueryRowExplorerProps)
<div className="p-5 space-y-4">
<div>
<h4 className="text-sm font-medium mb-2">Index Recommendations</h4>
{selectedQuery.index_statements && selectedQuery.index_statements.length > 0 ? (
{selectedQuery.index_statements &&
selectedQuery.index_statements.length > 0 ? (
<div className="space-y-3">
{selectedQuery.index_statements.map((statement: string, index: number) => (
<div key={index} className="bg-surface-200 p-3 rounded">
<pre className="text-xs overflow-x-auto">{statement}</pre>
</div>
))}
{selectedQuery.index_statements.map(
(statement: string, index: number) => (
<div key={index} className="bg-surface-200 p-3 rounded">
<pre className="text-xs overflow-x-auto">{statement}</pre>
</div>
)
)}
</div>
) : (
<p className="text-sm text-foreground-light">No index recommendations available</p>
<p className="text-sm text-foreground-light">
No index recommendations available
</p>
)}
</div>
{selectedQuery.errors && selectedQuery.errors.length > 0 && (
<div>
<h4 className="text-sm font-medium text-red-500 mb-2">Index Advisor Errors</h4>
<h4 className="text-sm font-medium text-red-500 mb-2">
Index Advisor Errors
</h4>
<div className="space-y-2">
{selectedQuery.errors.map((error: string, index: number) => (
<div key={index} className="text-xs text-red-500 bg-red-50 p-2 rounded">
{error}
</div>
))}
{selectedQuery.errors.map((error: string, index: number) => (
<div key={index} className="text-xs text-red-500 bg-red-50 p-2 rounded">
{error}
</div>
))}
</div>
</div>
)}
@@ -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<ReturnType<typeof getQueryInsightsGlance>>
export type QueryInsightsGlanceError = ExecuteSqlError
export const useQueryInsightsGlanceQuery = <TData = QueryInsightsGlanceData>(
{ projectRef, connectionString, startTime, endTime }: QueryInsightsGlanceVariables,
{ enabled = true, ...options }: UseQueryOptions<QueryInsightsGlanceData, QueryInsightsGlanceError, TData> = {}
) => {
return useQuery<QueryInsightsGlanceData, QueryInsightsGlanceError, TData>(
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<QueryInsightsGlanceData>
) {
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,
})
}
@@ -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<ReturnType<typeof getQueryInsightsMetrics>>
export type QueryInsightsMetricsError = ExecuteSqlError
export const useQueryInsightsMetricsQuery = <TData = QueryInsightsMetricsData>(
{ projectRef, connectionString, metric, startTime, endTime }: QueryInsightsMetricsVariables,
{ enabled = true, ...options }: UseQueryOptions<QueryInsightsMetricsData, QueryInsightsMetricsError, TData> = {}
) => {
return useQuery<QueryInsightsMetricsData, QueryInsightsMetricsError, TData>(
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<QueryInsightsQuery[]>
) => {
options?: UseQueryOptions<QueryInsightsMetric[]>
) {
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<QueryInsightsQuery[]>
) {
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,
})
}
}
export function useQueryInsightsQueriesWithErrors(
projectRef: string | undefined,
startTime: string,
endTime: string,
options?: UseQueryOptions<QueryInsightsQuery[]>
) {
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,
}
}
@@ -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<QueryInsightsMetric[]>
) {
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<QueryInsightsMetric[]>
) {
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<QueryInsightsMetric[]>
) {
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<QueryInsightsMetric[]>
) {
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,
})
}