import { AccordionTrigger } from '@ui/components/shadcn/ui/accordion' import { Check, Lightbulb, Table2 } from 'lucide-react' import { useEffect, useState } from 'react' import { Accordion_Shadcn_, AccordionContent_Shadcn_, AccordionItem_Shadcn_, Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Button, cn, Collapsible_Shadcn_, CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_, } from 'ui' import { Admonition } from 'ui-patterns' import { CodeBlock } from 'ui-patterns/CodeBlock' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { useIndexInvalidation } from './hooks/useIndexInvalidation' import { EnableIndexAdvisorButton } from './IndexAdvisor/EnableIndexAdvisorButton' import { calculateImprovement, createIndexes, hasIndexRecommendations, } from './IndexAdvisor/index-advisor.utils' import { IndexAdvisorDisabledState } from './IndexAdvisor/IndexAdvisorDisabledState' import { IndexImprovementText } from './IndexAdvisor/IndexImprovementText' import { QueryPanelContainer, QueryPanelScoreSection, QueryPanelSection } from './QueryPanel' import { QueryPerformanceRow } from './QueryPerformance.types' import { useIndexAdvisorStatus } from '@/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus' import AlertError from '@/components/ui/AlertError' import { DocsButton } from '@/components/ui/DocsButton' import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query' import { GetIndexAdvisorResultResponse, useGetIndexAdvisorResult, } from '@/data/database/retrieve-index-advisor-result-query' import { useGetIndexesFromSelectQuery } from '@/data/database/retrieve-index-from-select-query' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' import { useTrack } from '@/lib/telemetry/track' interface QueryIndexesProps { selectedRow: Pick columnName?: string suggestedSelectQuery?: string prefetchedIndexAdvisorResult?: GetIndexAdvisorResultResponse | null onClose?: () => void } // [Joshen] There's several more UX things we can do to help ease the learning curve of indexes I think // e.g understanding "costs", what numbers of "costs" are actually considered insignificant export const QueryIndexes = ({ selectedRow, columnName, suggestedSelectQuery, prefetchedIndexAdvisorResult, onClose, }: QueryIndexesProps) => { // [Joshen] TODO implement this logic once the linter rules are in const isLinterWarning = false const { data: project } = useSelectedProjectQuery() const [showStartupCosts, setShowStartupCosts] = useState(false) const [isExecuting, setIsExecuting] = useState(false) const track = useTrack() const [hasTrackedTabView, setHasTrackedTabView] = useState(false) const { data: usedIndexes, isSuccess, isPending: isLoading, isError, error, } = useGetIndexesFromSelectQuery({ projectRef: project?.ref, connectionString: project?.connectionString, query: selectedRow?.['query'], }) const { isPending: isLoadingExtensions } = useDatabaseExtensionsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const { isIndexAdvisorEnabled } = useIndexAdvisorStatus() const hasPrefetchedResult = prefetchedIndexAdvisorResult !== undefined const { data: fetchedIndexAdvisorResult, error: indexAdvisorError, refetch, isError: isErrorIndexAdvisorResult, isSuccess: isFetchSuccessIndexAdvisorResult, isLoading: isFetchLoadingIndexAdvisorResult, } = useGetIndexAdvisorResult( { projectRef: project?.ref, connectionString: project?.connectionString, query: selectedRow?.['query'], }, { enabled: isIndexAdvisorEnabled && !hasPrefetchedResult } ) const indexAdvisorResult = hasPrefetchedResult ? prefetchedIndexAdvisorResult : fetchedIndexAdvisorResult const isSuccessIndexAdvisorResult = hasPrefetchedResult || isFetchSuccessIndexAdvisorResult const isLoadingIndexAdvisorResult = hasPrefetchedResult ? false : isFetchLoadingIndexAdvisorResult const { index_statements, startup_cost_after, startup_cost_before, total_cost_after, total_cost_before, } = indexAdvisorResult ?? { index_statements: [], total_cost_after: 0, total_cost_before: 0 } const hasIndexRecommendation = hasIndexRecommendations( indexAdvisorResult, isSuccessIndexAdvisorResult ) const totalImprovement = calculateImprovement(total_cost_before, total_cost_after) const invalidateQueries = useIndexInvalidation() useEffect(() => { if (!isLoadingIndexAdvisorResult && !hasTrackedTabView) { track('index_advisor_tab_clicked', { hasRecommendations: hasIndexRecommendation, isIndexAdvisorEnabled: isIndexAdvisorEnabled, }) setHasTrackedTabView(true) } }, [ isLoadingIndexAdvisorResult, hasIndexRecommendation, hasTrackedTabView, track, isIndexAdvisorEnabled, ]) const createIndex = async () => { if (index_statements.length === 0) return setIsExecuting(true) track('index_advisor_create_indexes_button_clicked') try { await createIndexes({ projectRef: project?.ref, connectionString: project?.connectionString, indexStatements: index_statements, onSuccess: () => refetch(), }) // Only invalidate queries if index creation was successful invalidateQueries() } catch (error) { // Error is already handled by createIndexes with a toast notification // But we could add component-specific error handling here if needed console.error('Failed to create index:', error) setIsExecuting(false) } finally { setIsExecuting(false) onClose?.() } } if (!isLoadingExtensions && !isIndexAdvisorEnabled) { return (

Enable Index Advisor

Recommends indexes to improve query performance.

) } return ( {(columnName || suggestedSelectQuery) && (

Recommendation reason

{columnName && (

Recommendation for column: {columnName}

)}
{suggestedSelectQuery && (

Based on the following query:

code]:m-0 [&>code>span]:flex [&>code>span]:flex-wrap' )} />
)}
)}

Indexes in use

This query is using the following index{(usedIndexes ?? []).length > 1 ? 's' : ''}:

{isLoading && } {isError && ( )} {isSuccess && (
{usedIndexes.length === 0 && (

No indexes are involved in this query

Indexes may not necessarily be used if they incur a higher cost when executing the query

)} {usedIndexes.map((index) => { return (
{index.schema}.{index.table}
{index.name}
) })}
)}
{(!isSuccessIndexAdvisorResult || indexAdvisorResult !== null) && (

New index recommendations

)} {isLoadingExtensions ? ( ) : !isIndexAdvisorEnabled ? ( ) : ( <> {isLoadingIndexAdvisorResult && } {isErrorIndexAdvisorResult && ( )} {isSuccessIndexAdvisorResult && ( <> {indexAdvisorResult === null ? ( ) : (index_statements ?? []).length === 0 ? ( This query is optimized Recommendations for indexes will show here ) : ( <> {isLinterWarning ? ( We have {index_statements.length} index recommendation {index_statements.length > 1 ? 's' : ''} You can improve this query's performance by{' '} {totalImprovement.toFixed(2)}% by adding the following suggested{' '} {index_statements.length > 1 ? 'indexes' : 'index'} ) : ( )} code]:m-0 [&>code>span]:flex [&>code>span]:flex-wrap' )} />

This recommendation serves to prevent your queries from slowing down as your application grows, and hence the index may not be used immediately after it's created (e.g If your table is still small at this time).

)} )} )}
{isIndexAdvisorEnabled && hasIndexRecommendation && ( <>

Query costs

View {showStartupCosts ? 'less' : 'more'}

FAQ

What units are cost in? Costs are in an arbitrary unit, and do not represent a unit of time. The units are anchored (by default) to a single sequential page read costing 1.0 units. They do, however, serve as a predictor of higher execution times. How should I prioritize start up and total cost?

This depends on the expected size of the result set from the query.

For queries that return a small number or rows, the startup cost is more critical and minimizing startup cost can lead to faster response times, especially in interactive applications.

For queries that return a large number of rows, the total cost becomes more important, and optimizing it will help in efficiently using resources and reducing overall query execution time.

)} {isIndexAdvisorEnabled && hasIndexRecommendation && (
Apply index to database This will run the SQL that is shown above
)}
) }