mirror of
https://github.com/supabase/supabase.git
synced 2026-06-20 15:26:07 +08:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Spotted by @kostasb, index advisor was recommending slightly different column names. Index advisor was running on mismatched queries thus recommending for the wrong table.
157 lines
5.2 KiB
TypeScript
157 lines
5.2 KiB
TypeScript
import { useQueryClient } from '@tanstack/react-query'
|
|
import { useIndexAdvisorStatus } from 'components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus'
|
|
import { QueryIndexes } from 'components/interfaces/QueryPerformance/QueryIndexes'
|
|
import { databaseKeys } from 'data/database/keys'
|
|
import {
|
|
cleanIndexColumnName,
|
|
IndexAdvisorSuggestion,
|
|
TableIndexAdvisorData,
|
|
useTableIndexAdvisorQuery,
|
|
} from 'data/database/table-index-advisor-query'
|
|
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
|
import { createContext, PropsWithChildren, useCallback, useContext, useState } from 'react'
|
|
import { Sheet, SheetContent, SheetHeader, SheetTitle } from 'ui'
|
|
|
|
interface TableIndexAdvisorContextValue {
|
|
isLoading: boolean
|
|
isAvailable: boolean
|
|
isEnabled: boolean
|
|
columnsWithSuggestions: string[]
|
|
suggestions: TableIndexAdvisorData['suggestions']
|
|
openSheet: (columnName: string) => void
|
|
getSuggestionsForColumn: (columnName: string) => IndexAdvisorSuggestion[]
|
|
invalidate: () => Promise<void>
|
|
}
|
|
|
|
const TableIndexAdvisorContext = createContext<TableIndexAdvisorContextValue>({
|
|
isLoading: false,
|
|
isAvailable: false,
|
|
isEnabled: false,
|
|
columnsWithSuggestions: [],
|
|
suggestions: [],
|
|
openSheet: () => {},
|
|
getSuggestionsForColumn: () => [],
|
|
invalidate: async () => {},
|
|
})
|
|
|
|
interface TableIndexAdvisorProviderProps {
|
|
schema: string
|
|
table: string
|
|
}
|
|
|
|
export function TableIndexAdvisorProvider({
|
|
children,
|
|
schema,
|
|
table,
|
|
}: PropsWithChildren<TableIndexAdvisorProviderProps>) {
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const { isIndexAdvisorAvailable, isIndexAdvisorEnabled } = useIndexAdvisorStatus()
|
|
const queryClient = useQueryClient()
|
|
const [isSheetOpen, setIsSheetOpen] = useState(false)
|
|
const [selectedColumn, setSelectedColumn] = useState<string | undefined>(undefined)
|
|
|
|
const { data, isLoading } = useTableIndexAdvisorQuery(
|
|
{
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
schema,
|
|
table,
|
|
},
|
|
{
|
|
enabled: isIndexAdvisorEnabled && !!schema && !!table,
|
|
}
|
|
)
|
|
|
|
const openSheet = useCallback((columnName: string) => {
|
|
setSelectedColumn(columnName)
|
|
setIsSheetOpen(true)
|
|
}, [])
|
|
|
|
const closeSheet = useCallback(() => {
|
|
setIsSheetOpen(false)
|
|
setSelectedColumn(undefined)
|
|
}, [])
|
|
|
|
const getSuggestionsForColumn = useCallback(
|
|
(columnName: string): IndexAdvisorSuggestion[] => {
|
|
if (!data?.suggestions) return []
|
|
// Filter suggestions that include this column in their index statements
|
|
return data.suggestions.filter((suggestion) =>
|
|
suggestion.index_statements.some((stmt) => {
|
|
const match = stmt.match(/USING\s+\w+\s*\(([^)]+)\)/i)
|
|
if (match) {
|
|
const columns = match[1].split(',').map((c) => cleanIndexColumnName(c))
|
|
return columns.includes(columnName)
|
|
}
|
|
return false
|
|
})
|
|
)
|
|
},
|
|
[data?.suggestions]
|
|
)
|
|
|
|
const invalidate = useCallback(async () => {
|
|
if (project?.ref && schema && table) {
|
|
await queryClient.invalidateQueries({
|
|
queryKey: databaseKeys.tableIndexAdvisor(project.ref, schema, table),
|
|
})
|
|
}
|
|
}, [queryClient, project?.ref, schema, table])
|
|
|
|
// Get the first suggestion for the selected column to pass to QueryIndexes
|
|
const selectedSuggestion = selectedColumn ? getSuggestionsForColumn(selectedColumn)[0] : null
|
|
|
|
const prefetchedIndexAdvisorResult = selectedSuggestion
|
|
? {
|
|
errors: [],
|
|
index_statements: selectedSuggestion.index_statements,
|
|
startup_cost_before: selectedSuggestion.startup_cost_before,
|
|
startup_cost_after: selectedSuggestion.startup_cost_after,
|
|
total_cost_before: selectedSuggestion.total_cost_before,
|
|
total_cost_after: selectedSuggestion.total_cost_after,
|
|
}
|
|
: null
|
|
|
|
const value: TableIndexAdvisorContextValue = {
|
|
isLoading,
|
|
isAvailable: isIndexAdvisorAvailable,
|
|
isEnabled: isIndexAdvisorEnabled,
|
|
columnsWithSuggestions: data?.columnsWithSuggestions ?? [],
|
|
suggestions: data?.suggestions ?? [],
|
|
openSheet,
|
|
getSuggestionsForColumn,
|
|
invalidate,
|
|
}
|
|
|
|
return (
|
|
<TableIndexAdvisorContext.Provider value={value}>
|
|
{children}
|
|
<Sheet open={isSheetOpen} onOpenChange={(open) => !open && closeSheet()}>
|
|
<SheetContent className="flex flex-col gap-0 p-0 lg:!w-[calc(100vw-802px)] max-w-[700px]">
|
|
<SheetHeader className="border-b px-5 py-3">
|
|
<SheetTitle>Index Recommendation</SheetTitle>
|
|
</SheetHeader>
|
|
{selectedSuggestion && (
|
|
<QueryIndexes
|
|
selectedRow={{ query: selectedSuggestion.query }}
|
|
columnName={selectedColumn}
|
|
suggestedSelectQuery={selectedSuggestion.query}
|
|
prefetchedIndexAdvisorResult={prefetchedIndexAdvisorResult}
|
|
onClose={closeSheet}
|
|
/>
|
|
)}
|
|
</SheetContent>
|
|
</Sheet>
|
|
</TableIndexAdvisorContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useTableIndexAdvisor() {
|
|
return useContext(TableIndexAdvisorContext)
|
|
}
|
|
|
|
export function useColumnHasIndexSuggestion(columnName: string): boolean {
|
|
const { columnsWithSuggestions } = useTableIndexAdvisor()
|
|
return columnsWithSuggestions.includes(columnName)
|
|
}
|