import { sortBy } from 'lodash' import { AlertCircle, Search, Trash } from 'lucide-react' import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' import { useEffect, useRef } from 'react' import { toast } from 'sonner' import { useParams } from 'common' import AlertError from 'components/ui/AlertError' import CodeEditor from 'components/ui/CodeEditor/CodeEditor' import SchemaSelector from 'components/ui/SchemaSelector' 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 { handleErrorOnDelete, useQueryStateWithSelect } from 'hooks/misc/useQueryStateWithSelect' import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' 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' const Indexes = () => { const { data: project } = useSelectedProjectQuery() const { schema: urlSchema, table } = useParams() const [search, setSearch] = useQueryState('search', parseAsString.withDefault('')) const { selectedSchema, setSelectedSchema } = useQuerySchemaState() const deletingIndexNameRef = useRef(null) 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).withOptions({ history: 'push', clearOnDefault: true }) ) const { setValue: setSelectedIndexName, value: selectedIndex } = useQueryStateWithSelect({ urlKey: 'edit', select: (id) => (id ? allIndexes?.find((idx) => idx.name === id) : undefined), enabled: !!allIndexes, onError: () => toast.error(`Index not found`), }) const { setValue: setSelectedIndexNameToDelete, value: selectedIndexToDelete } = useQueryStateWithSelect({ urlKey: 'delete', select: (id) => (id ? allIndexes?.find((idx) => idx.name === id) : undefined), enabled: !!allIndexes, onError: (_error, selectedId) => handleErrorOnDelete(deletingIndexNameRef, selectedId, `Index not found`), }) const { data: schemas, isPending: isLoadingSchemas, isSuccess: isSuccessSchemas, isError: isErrorSchemas, } = useSchemasQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const { mutate: deleteIndex, isPending: isExecuting } = useDatabaseIndexDeleteMutation({ onSuccess: async () => { setSelectedIndexNameToDelete(null) toast.success('Successfully deleted index') }, }) const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedSchema }) 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') deletingIndexNameRef.current = index.name 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]) return ( <>
{isLoadingSchemas && } {isErrorSchemas && (

Failed to load schemas

)} {isSuccessSchemas && ( )} setSearch(e.target.value)} placeholder="Search for an index" icon={} /> {!isSchemaLocked && ( )}
{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={() => setSelectedIndexName(null)} >
setShowCreateIndex(false)} /> Confirm to delete index {selectedIndexToDelete?.name} } confirmLabel="Confirm delete" confirmLabelLoading="Deleting..." onConfirm={() => selectedIndexToDelete !== undefined ? onConfirmDeleteIndex(selectedIndexToDelete) : {} } onCancel={() => setSelectedIndexNameToDelete(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.', }} >
  • 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.
) } export default Indexes