import { zodResolver } from '@hookform/resolvers/zod' import type { PostgresExtension } from '@supabase/postgres-meta' import { DocsButton } from 'components/ui/DocsButton' import { useDatabaseExtensionEnableMutation } from 'data/database-extensions/database-extension-enable-mutation' import { useSchemasQuery } from 'data/database/schemas-query' import { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useProtectedSchemas } from 'hooks/useProtectedSchemas' import { DOCS_URL } from 'lib/constants' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { Badge, Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogSection, DialogSectionSeparator, DialogTitle, FormControl_Shadcn_, FormField_Shadcn_, Form_Shadcn_, Input_Shadcn_, SelectContent_Shadcn_, SelectItem_Shadcn_, SelectSeparator_Shadcn_, SelectTrigger_Shadcn_, SelectValue_Shadcn_, Select_Shadcn_, } from 'ui' import { Admonition } from 'ui-patterns' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import * as z from 'zod' import { extensionsWithRecommendedSchemas } from './Extensions.constants' import { useDatabaseExtensionDefaultSchemaQuery } from '@/data/database-extensions/database-extension-schema-query' const orioleExtCallOuts = ['vector', 'postgis'] const FormSchema = z.object({ name: z.string(), schema: z.string() }).superRefine((val, ctx) => { if (val.schema === 'custom' && val.name.length === 0) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['name'], message: 'Please provide a name for the schema', }) } }) interface EnableExtensionModalProps { visible: boolean extension: PostgresExtension onCancel: () => void } export const EnableExtensionModal = ({ visible, extension, onCancel, }: EnableExtensionModalProps) => { const isOrioleDb = useIsOrioleDb() const { data: project } = useSelectedProjectQuery() const { data: protectedSchemas } = useProtectedSchemas({ excludeSchemas: ['extensions'] }) const recommendedSchema = extensionsWithRecommendedSchemas[extension.name] const { data: schemas = [], isPending: isSchemasLoading } = useSchemasQuery( { projectRef: project?.ref, connectionString: project?.connectionString, }, { enabled: visible } ) const availableSchemas = schemas.filter( (schema) => schema.name === recommendedSchema || !protectedSchemas.some((protectedSchema) => protectedSchema.name === schema.name) ) const { data: extensionMeta, isPending: fetchingSchemaInfo } = useDatabaseExtensionDefaultSchemaQuery( { projectRef: project?.ref, connectionString: project?.connectionString, extension: extension.name, }, { enabled: visible } ) // [Joshen] Hard-coding pg_cron here as this is enforced on our end (Not via pg_available_extension_versions) const defaultSchema = extension.name === 'pg_cron' ? 'pg_catalog' : extensionMeta?.schema const isLoading = fetchingSchemaInfo || isSchemasLoading 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}`) }, }) const defaultValues = { name: extension.name, schema: recommendedSchema ?? 'extensions' } const form = useForm>({ mode: 'onBlur', reValidateMode: 'onBlur', resolver: zodResolver(FormSchema), defaultValues, }) const { schema } = form.watch() const onSubmit = async (values: z.infer) => { 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 ( { if (!open) onCancel() }} > Enable {extension.name} {isOrioleDb && orioleExtCallOuts.includes(extension.name) && ( {extension.name} cannot be accelerated by indexes on tables that are using the OrioleDB access method )} {extension.name === 'pg_cron' && project?.cloud_provider === 'FLY' && (

You can still enable the extension, but pg_cron jobs may not run due to the behavior of Fly projects.

)}
{isLoading ? (
) : !!defaultSchema ? (

Extension must be installed in the “{defaultSchema}” schema.

) : (
( Create a new schema{' '} {extension.name} {availableSchemas.map((schema) => { return ( {schema.name} {schema.name === recommendedSchema ? ( Recommended ) : !defaultSchema && schema.name === 'extensions' ? ( Default ) : null} ) })} )} /> {!!recommendedSchema && (

Use the "{recommendedSchema}" schema for full compatibility with related features.

)} {schema === 'custom' && ( ( )} /> )}
)}
) }