Files
supabase/apps/studio/hooks/useProtectedSchemas.ts
T
Ivan VasilovandJoshen Lim b3c6992e56 feat: Make the protected schemas dynamic, namespace schemas are now protected (#37290)
* Add hooks for async protected schemas.

* Migrate the ProtectedSchemaWarning to support the new implementation.

* sq

* Migrate all uses of protected schemas to the new approach.

* Delete extra file.

* Refactor the import foreign schema dialog to forbid protected and exposed schemas.

* Add the type to the protected schema.

* Revert ImportForeignSchemaDialog, it'll be addressed in another PR.

* Update apps/studio/hooks/useProtectedSchemas.ts

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>

* Fix a bad commit.

* Minor fixes.

* Fix the FDW delete mutation to handle names with numbers.

* Simplify the logic to skip a fetch.

* Minor fixes.

* Make the useIcebergFdwSchemasQuery work for all iceberg FDWs.

* Fix the tab schemas to always show in the Table Editor.

* Apply suggestion from @joshenlim

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>

* Fix a minor typo.

* Refactor ProtectedSchemaWarning to use Admonition, and standardise input field for target schema iceberg

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2025-07-30 11:38:50 +02:00

113 lines
3.3 KiB
TypeScript

import { uniq } from 'lodash'
import { useMemo } from 'react'
import { WRAPPER_HANDLERS } from 'components/interfaces/Integrations/Wrappers/Wrappers.constants'
import {
convertKVStringArrayToJson,
wrapperMetaComparator,
} from 'components/interfaces/Integrations/Wrappers/Wrappers.utils'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { QUEUES_SCHEMA } from 'data/database-queues/database-queues-toggle-postgrest-mutation'
import { useFDWsQuery } from 'data/fdw/fdws-query'
/**
* A list of system schemas that users should not interact with
*/
export const INTERNAL_SCHEMAS = [
'auth',
'cron',
'extensions',
'information_schema',
'net',
'pgsodium',
'pgsodium_masks',
'pgbouncer',
'pgtle',
'realtime',
'storage',
'supabase_functions',
'supabase_migrations',
'vault',
'graphql',
'graphql_public',
QUEUES_SCHEMA,
]
/**
* Get the list of schemas used by IcebergFDWs
*/
const useIcebergFdwSchemasQuery = () => {
const { project } = useProjectContext()
const result = useFDWsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const schemas = useMemo(() => {
const icebergFDWs = result.data?.filter((wrapper) =>
wrapperMetaComparator(
{ handlerName: WRAPPER_HANDLERS.ICEBERG, server: { options: [] } },
wrapper
)
)
const fdwSchemas = icebergFDWs
?.map((fdw) => convertKVStringArrayToJson(fdw.server_options))
.map((options) => options['supabase_target_schema'])
.flatMap((s) => s?.split(','))
.filter(Boolean)
return uniq(fdwSchemas)
}, [result.data])
return { ...result, data: schemas }
}
/**
* Returns a list of schemas that are protected by Supabase (internal schemas or schemas used by Iceberg FDWs).
*/
export const useProtectedSchemas = ({
excludeSchemas = [],
}: { excludeSchemas?: string[] } = {}) => {
// Stabilize the excludeSchemas array to prevent unnecessary re-computations
// eslint-disable-next-line react-hooks/exhaustive-deps
const stableExcludeSchemas = useMemo(() => excludeSchemas, [JSON.stringify(excludeSchemas)])
const result = useIcebergFdwSchemasQuery()
const schemas = useMemo<{ name: string; type: 'fdw' | 'internal' }[]>(() => {
const internalSchemas = INTERNAL_SCHEMAS.map((s) => ({ name: s, type: 'internal' as const }))
const icebergFdwSchemas = result.data?.map((s) => ({ name: s, type: 'fdw' as const }))
const schemas = uniq([...internalSchemas, ...icebergFdwSchemas])
return schemas.filter((schema) => !stableExcludeSchemas.includes(schema.name))
}, [result.data, stableExcludeSchemas])
return { ...result, data: schemas }
}
/**
* Returns whether a given schema is protected by Supabase (internal schema or schema used by Iceberg FDWs).
*/
export const useIsProtectedSchema = ({
schema,
excludedSchemas = [],
}: {
schema: string
excludedSchemas?: string[]
}):
| { isSchemaLocked: false; reason: undefined }
| { isSchemaLocked: true; reason: 'fdw' | 'internal' } => {
const { data: schemas } = useProtectedSchemas({ excludeSchemas: excludedSchemas })
const foundSchema = schemas.find((s) => s.name === schema)
if (foundSchema) {
return {
isSchemaLocked: true,
reason: foundSchema.type,
}
}
return { isSchemaLocked: false, reason: undefined }
}