mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 21:05:06 +03:00
## Context There's some changes for Vector Buckets with the wrappers extension on version 0.5.7 and above The "Query from Postgres" CTA (which runs an import foreign schema command) no longer accepts the bucket name as part of the schema options (ref [changelog](https://github.com/supabase/wrappers/releases/tag/v0.5.7)), and instead takes the bucket name as the remote schema name. So changes here are to update the SQL for importing foreign schema for vector buckets if wrappers extension is above 0.5.7 Also - the `::embd` type no longer seems to exist in 0.5.7 as well, so updated the example code too ## To test Can probably test on a new US East 1 project on the preview - [ ] Verify creating a vector bucket table + can query from postgres (via the Insert vectors CTA after creating a table) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Fixed breadcrumb navigation links for vector bucket pages. * **Improvements** * Unified loading state for schema workflows and disabled actions while loading. * Enhanced version compatibility checks for foreign schema imports to support newer extension syntax. * Adjusted generated SQL for vector data to use the appropriate embedding syntax based on extension version. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
286 lines
9.9 KiB
TypeScript
286 lines
9.9 KiB
TypeScript
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 { 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 { Database, ExternalLinkIcon, Plus } from 'lucide-react'
|
|
import { useEffect, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
AlertDescription_Shadcn_,
|
|
AlertTitle_Shadcn_,
|
|
Alert_Shadcn_,
|
|
Button,
|
|
Form,
|
|
Input,
|
|
Listbox,
|
|
Modal,
|
|
WarningIcon,
|
|
} from 'ui'
|
|
import { Admonition } from 'ui-patterns'
|
|
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
const orioleExtCallOuts = ['vector', 'postgis']
|
|
|
|
// Extensions that have recommended schemas (rather than required schemas)
|
|
const extensionsWithRecommendedSchemas: Record<string, string> = {
|
|
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, isPending: 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 isLoading = fetchingSchemaInfo || isSchemasLoading
|
|
|
|
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 (
|
|
<Modal
|
|
hideFooter
|
|
visible={visible}
|
|
onCancel={onCancel}
|
|
size="small"
|
|
header={
|
|
<div className="flex items-baseline gap-2">
|
|
<h5 className="text-foreground">Enable</h5>
|
|
<code className="text-code-inline">{extension.name}</code>
|
|
</div>
|
|
}
|
|
>
|
|
<Form
|
|
initialValues={{
|
|
name: extension.name, // Name of new schema, if creating new
|
|
schema: 'extensions',
|
|
}}
|
|
validate={validate}
|
|
onSubmit={onSubmit}
|
|
>
|
|
{({ values }: any) => {
|
|
return (
|
|
<>
|
|
<Modal.Content className="flex flex-col gap-y-2">
|
|
{isOrioleDb && orioleExtCallOuts.includes(extension.name) && (
|
|
<Admonition type="default" title="Extension is limited by OrioleDB">
|
|
<span className="block">
|
|
{extension.name} cannot be accelerated by indexes on tables that are using the
|
|
OrioleDB access method
|
|
</span>
|
|
<DocsButton abbrev={false} className="mt-2" href={`${DOCS_URL}`} />
|
|
</Admonition>
|
|
)}
|
|
|
|
{isLoading ? (
|
|
<div className="space-y-2">
|
|
<ShimmeringLoader />
|
|
<div className="w-3/4">
|
|
<ShimmeringLoader />
|
|
</div>
|
|
</div>
|
|
) : defaultSchema ? (
|
|
<Input
|
|
disabled
|
|
id="schema"
|
|
name="schema"
|
|
value={defaultSchema}
|
|
label="Select a schema to enable the extension for"
|
|
descriptionText={getSchemaDescriptionText(extension.name, defaultSchema)}
|
|
/>
|
|
) : (
|
|
<Listbox
|
|
size="small"
|
|
name="schema"
|
|
label="Select a schema to enable the extension for"
|
|
descriptionText={getSchemaDescriptionText(extension.name, null)}
|
|
>
|
|
<Listbox.Option
|
|
key="custom"
|
|
id="custom"
|
|
label={`Create a new schema "${extension.name}"`}
|
|
value="custom"
|
|
addOnBefore={() => <Plus size={16} strokeWidth={1.5} />}
|
|
>
|
|
Create a new schema "{extension.name}"
|
|
</Listbox.Option>
|
|
<Modal.Separator />
|
|
{schemas
|
|
?.filter(
|
|
(schema) =>
|
|
!protectedSchemas.some(
|
|
(protectedSchema) => protectedSchema.name === schema.name
|
|
)
|
|
)
|
|
.map((schema) => {
|
|
return (
|
|
<Listbox.Option
|
|
key={schema.id}
|
|
id={schema.name}
|
|
label={schema.name}
|
|
value={schema.name}
|
|
addOnBefore={() => <Database size={16} strokeWidth={1.5} />}
|
|
>
|
|
{schema.name}
|
|
</Listbox.Option>
|
|
)
|
|
})}
|
|
</Listbox>
|
|
)}
|
|
</Modal.Content>
|
|
|
|
{values.schema === 'custom' && (
|
|
<Modal.Content>
|
|
<Input id="name" name="name" label="Schema name" />
|
|
</Modal.Content>
|
|
)}
|
|
|
|
{extension.name === 'pg_cron' && project?.cloud_provider === 'FLY' && (
|
|
<Modal.Content>
|
|
<Alert_Shadcn_ variant="warning">
|
|
<WarningIcon />
|
|
<AlertTitle_Shadcn_>
|
|
The pg_cron extension is not fully supported for Fly projects
|
|
</AlertTitle_Shadcn_>
|
|
|
|
<AlertDescription_Shadcn_>
|
|
You can still enable the extension, but pg_cron jobs may not run due to the
|
|
behavior of Fly projects.
|
|
</AlertDescription_Shadcn_>
|
|
|
|
<AlertDescription_Shadcn_ className="mt-3">
|
|
<Button
|
|
asChild
|
|
type="default"
|
|
iconRight={<ExternalLinkIcon width={12} height={12} />}
|
|
>
|
|
<a
|
|
href="/docs/guides/platform/fly-postgres#limitations"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<span>Learn more</span>
|
|
</a>
|
|
</Button>
|
|
</AlertDescription_Shadcn_>
|
|
</Alert_Shadcn_>
|
|
</Modal.Content>
|
|
)}
|
|
<Modal.Separator />
|
|
<Modal.Content className="flex items-center justify-end space-x-2">
|
|
<Button type="default" disabled={isEnabling} onClick={() => onCancel()}>
|
|
Cancel
|
|
</Button>
|
|
<Button htmlType="submit" disabled={isEnabling || isLoading} loading={isEnabling}>
|
|
Enable extension
|
|
</Button>
|
|
</Modal.Content>
|
|
</>
|
|
)
|
|
}}
|
|
</Form>
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
export default EnableExtensionModal
|