import type { PostgresExtension } from '@supabase/postgres-meta' import { Database, ExternalLinkIcon, Plus } from 'lucide-react' import { useEffect, useState } from 'react' import { toast } from 'sonner' import { DocsButton } from 'components/ui/DocsButton' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useDatabaseExtensionEnableMutation } from 'data/database-extensions/database-extension-enable-mutation' import { useSchemasQuery } from 'data/database/schemas-query' import { executeSql } from 'data/sql/execute-sql-query' import { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useProtectedSchemas } from 'hooks/useProtectedSchemas' import { DOCS_URL } from 'lib/constants' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, Form, Input, Listbox, Modal, WarningIcon, } from 'ui' import { Admonition } from 'ui-patterns' const orioleExtCallOuts = ['vector', 'postgis'] // Extensions that have recommended schemas (rather than required schemas) const extensionsWithRecommendedSchemas: Record = { wrappers: 'extensions', } interface EnableExtensionModalProps { visible: boolean extension: PostgresExtension onCancel: () => void } const EnableExtensionModal = ({ visible, extension, onCancel }: EnableExtensionModalProps) => { const { data: project } = useSelectedProjectQuery() const isOrioleDb = useIsOrioleDb() const [defaultSchema, setDefaultSchema] = useState() const [fetchingSchemaInfo, setFetchingSchemaInfo] = useState(false) const { data: schemas, isLoading: isSchemasLoading } = useSchemasQuery( { projectRef: project?.ref, connectionString: project?.connectionString, }, { enabled: visible } ) const { data: protectedSchemas } = useProtectedSchemas({ excludeSchemas: ['extensions'] }) const { mutate: enableExtension, isPending: isEnabling } = useDatabaseExtensionEnableMutation({ onSuccess: () => { toast.success(`Extension "${extension.name}" is now enabled`) onCancel() }, onError: (error) => { toast.error(`Failed to enable ${extension.name}: ${error.message}`) }, }) // [Joshen] Worth checking in with users - whether having this schema selection // might be confusing, and if we should have a tooltip to explain that schemas // are just concepts of namespace, you can use that extension no matter where it's // installed in useEffect(() => { let cancel = false if (visible) { const checkExtensionSchema = async () => { if (!cancel) { setFetchingSchemaInfo(true) setDefaultSchema(undefined) } try { const res = await executeSql({ projectRef: project?.ref, connectionString: project?.connectionString, sql: `select * from pg_available_extension_versions where name = '${extension.name}'`, }) if (!cancel) setDefaultSchema(res.result[0].schema) } catch (error) {} setFetchingSchemaInfo(false) } checkExtensionSchema() } return () => { cancel = true } }, [visible, extension.name]) const getSchemaDescriptionText = (extensionName: string, schema: string | null | undefined) => { // Prioritize defaultSchema (required/forced) over recommended schema if (schema) { return `Extension must be installed in the “${schema}” schema.` } const recommendedSchema = extensionsWithRecommendedSchemas[extensionName] if (recommendedSchema) { return `Use the “${recommendedSchema}” schema for full compatibility with related features.` } return undefined } const validate = (values: any) => { const errors: any = {} if (values.schema === 'custom' && !values.name) errors.name = 'Required field' return errors } const onSubmit = async (values: any) => { if (project === undefined) return console.error('Project is required') const schema = defaultSchema !== undefined && defaultSchema !== null ? defaultSchema : values.schema === 'custom' ? values.name : values.schema enableExtension({ projectRef: project.ref, connectionString: project?.connectionString, schema, name: extension.name, version: extension.default_version, cascade: true, createSchema: !schema.startsWith('pg_'), }) } return (
Enable
{extension.name} } >
{({ values }: any) => { return ( <> {isOrioleDb && orioleExtCallOuts.includes(extension.name) && ( {extension.name} cannot be accelerated by indexes on tables that are using the OrioleDB access method )} {fetchingSchemaInfo || isSchemasLoading ? (
) : defaultSchema ? ( ) : ( } > Create a new schema "{extension.name}" {schemas ?.filter( (schema) => !protectedSchemas.some( (protectedSchema) => protectedSchema.name === schema.name ) ) .map((schema) => { return ( } > {schema.name} ) })} )}
{values.schema === 'custom' && ( )} {extension.name === 'pg_cron' && project?.cloud_provider === 'FLY' && ( The pg_cron extension is not fully supported for Fly projects You can still enable the extension, but pg_cron jobs may not run due to the behavior of Fly projects. )} ) }}
) } export default EnableExtensionModal