import { useParams } from 'common' import { sortBy } from 'lodash' import { AlertCircle, Search, Trash } from 'lucide-react' import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' import { useEffect, useRef, useState } from 'react' import { toast } from 'sonner' import { Button, Card, SidePanel, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal' import { GenericSkeletonLoader, ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning' import { CreateIndexSidePanel } from './CreateIndexSidePanel' import AlertError from '@/components/ui/AlertError' import CodeEditor from '@/components/ui/CodeEditor/CodeEditor' import SchemaSelector from '@/components/ui/SchemaSelector' import { Shortcut } from '@/components/ui/Shortcut' import { useDatabaseIndexDeleteMutation } from '@/data/database-indexes/index-delete-mutation' import { useIndexesQuery, type DatabaseIndex } from '@/data/database-indexes/indexes-query' import { useSchemasQuery } from '@/data/database/schemas-query' import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' import { onSearchInputEscape } from '@/lib/keyboard' import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import { useShortcut } from '@/state/shortcuts/useShortcut' export const Indexes = () => { const { data: project } = useSelectedProjectQuery() const { schema: urlSchema, table } = useParams() const [search, setSearch] = useQueryState('search', parseAsString.withDefault('')) const [schemaSelectorOpen, setSchemaSelectorOpen] = useState(false) const searchInputRef = useRef(null) const { selectedSchema, setSelectedSchema } = useQuerySchemaState() const { data: allIndexes, error: indexesError, isPending: isLoadingIndexes, isSuccess: isSuccessIndexes, isError: isErrorIndexes, } = useIndexesQuery({ schema: selectedSchema, projectRef: project?.ref, connectionString: project?.connectionString, }) const [showCreateIndex, setShowCreateIndex] = useQueryState( 'new', parseAsBoolean.withDefault(false) ) const [editIndexId, setEditIndexId] = useQueryState('edit', parseAsString) const selectedIndex = allIndexes?.find((idx) => idx.name === editIndexId) const [deleteIndexId, setDeleteIndexId] = useQueryState('delete', parseAsString) const selectedIndexToDelete = allIndexes?.find((idx) => idx.name === deleteIndexId) const { data: schemas, isPending: isLoadingSchemas, isSuccess: isSuccessSchemas, isError: isErrorSchemas, } = useSchemasQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const { mutate: deleteIndex, isPending: isExecuting, isSuccess: isSuccessDelete, } = useDatabaseIndexDeleteMutation({ onSuccess: async () => { setDeleteIndexId(null) toast.success('Successfully deleted index') }, }) const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedSchema }) useShortcut( SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH, () => { searchInputRef.current?.focus() searchInputRef.current?.select() }, { label: 'Search indexes' } ) useShortcut(SHORTCUT_IDS.LIST_PAGE_RESET_FILTERS, () => { setSearch('') }) const sortedIndexes = sortBy(allIndexes ?? [], (index) => index.name.toLocaleLowerCase()) const indexes = search.length > 0 ? sortedIndexes.filter((index) => index.name.includes(search) || index.table.includes(search)) : sortedIndexes const onConfirmDeleteIndex = (index: DatabaseIndex) => { if (!project) return console.error('Project is required') deleteIndex({ projectRef: project.ref, connectionString: project.connectionString, name: index.name, schema: selectedSchema, }) } useEffect(() => { if (urlSchema !== undefined) { const schema = schemas?.find((s) => s.name === urlSchema) if (schema !== undefined) setSelectedSchema(schema.name) } }, [urlSchema, isSuccessSchemas]) useEffect(() => { if (table !== undefined) setSearch(table) }, [table]) useEffect(() => { if (isSuccessIndexes && !!editIndexId && !selectedIndex) { toast('Index not found') setEditIndexId(null) } }, [isSuccessIndexes, editIndexId, selectedIndex, setEditIndexId]) useEffect(() => { if (isSuccessIndexes && !!deleteIndexId && !selectedIndexToDelete && !isSuccessDelete) { toast('Index not found') setDeleteIndexId(null) } }, [isSuccessIndexes, deleteIndexId, selectedIndexToDelete, isSuccessDelete, setDeleteIndexId]) return ( <>
{isLoadingSchemas && } {isErrorSchemas && (

Failed to load schemas

)} {isSuccessSchemas && ( setSchemaSelectorOpen(true)} side="bottom" tooltipOpen={schemaSelectorOpen ? false : undefined} > )} setSearch(e.target.value)} onKeyDown={onSearchInputEscape(search, setSearch)} placeholder="Search for an index" icon={} /> {!isSchemaLocked && ( setShowCreateIndex(true)} options={{ enabled: isSuccessSchemas }} side="bottom" > )}
{isSchemaLocked && } {isLoadingIndexes && } {isErrorIndexes && ( )} {isSuccessIndexes && (
Table Columns Name {indexes.length === 0 && search.length === 0 && (

No indexes created yet

There are no indexes found in the schema "{selectedSchema}"

)} {indexes.length === 0 && search.length > 0 && (

No results found

Your search for "{search}" did not return any results

)} {indexes.length > 0 && indexes.map((index) => (

{index.table}

{index.columns}

{index.name}

{!isSchemaLocked && (
))}
)}
Index: {selectedIndex?.name} } onCancel={() => setEditIndexId(null)} >
setShowCreateIndex(false)} /> Confirm to delete index{' '} {selectedIndexToDelete?.name} } confirmLabel="Confirm delete" confirmLabelLoading="Deleting..." onConfirm={() => selectedIndexToDelete !== undefined ? onConfirmDeleteIndex(selectedIndexToDelete) : {} } onCancel={() => setDeleteIndexId(null)} alert={{ title: 'This action cannot be undone', description: 'Deleting an index that is still in use will cause queries to slow down, and in some cases causing significant performance issues.', }} className="pt-0" >
  • Before deleting this index, consider:
    • This index is no longer in use
    • The table which the index is on is not currently in use, as dropping an index requires a short exclusive access lock on the table.
) }