import { useParams } from 'common' import { getAddons } from 'components/interfaces/Billing/Subscription/Subscription.utils' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import AlertError from 'components/ui/AlertError' import DatabaseSelector from 'components/ui/DatabaseSelector' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { pluckObjectFields } from 'lib/helpers' import { ChevronDown } from 'lucide-react' import { useState } from 'react' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { useDatabaseSettingsStateSnapshot } from 'state/database-settings' import { CodeBlock, CodeBlockLang, CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_, Collapsible_Shadcn_, DIALOG_PADDING_X, SelectContent_Shadcn_, SelectItem_Shadcn_, SelectTrigger_Shadcn_, SelectValue_Shadcn_, Select_Shadcn_, Separator, cn, } from 'ui' import { Admonition } from 'ui-patterns' import { CodeBlockFileHeader, ConnectionPanel } from './ConnectionPanel' import { getConnectionStrings, getPoolerTld } from './DatabaseSettings.utils' import examples, { Example } from './DirectConnectionExamples' const CONNECTION_TYPES: { id: ConnectionType label: string contentType: 'input' | 'code' lang: CodeBlockLang fileTitle: string | undefined }[] = [ { id: 'uri', label: 'URI', contentType: 'input', lang: 'bash', fileTitle: undefined }, { id: 'psql', label: 'PSQL', contentType: 'code', lang: 'bash', fileTitle: undefined }, { id: 'golang', label: 'Golang', contentType: 'code', lang: 'go', fileTitle: '.env' }, { id: 'jdbc', label: 'JDBC', contentType: 'input', lang: 'bash', fileTitle: undefined }, { id: 'dotnet', label: '.NET', contentType: 'code', lang: 'csharp', fileTitle: 'appsettings.json', }, { id: 'nodejs', label: 'Node.js', contentType: 'code', lang: 'js', fileTitle: '.env' }, { id: 'php', label: 'PHP', contentType: 'code', lang: 'php', fileTitle: '.env' }, { id: 'python', label: 'Python', contentType: 'code', lang: 'python', fileTitle: '.env' }, { id: 'sqlalchemy', label: 'SQLAlchemy', contentType: 'code', lang: 'python', fileTitle: '.env' }, ] type ConnectionType = | 'uri' | 'psql' | 'golang' | 'jdbc' | 'dotnet' | 'nodejs' | 'php' | 'python' | 'sqlalchemy' const CONNECTION_PARAMETERS = { host: { key: 'host', description: 'The hostname of your database', }, port: { key: 'port', description: 'Port number for the connection', }, database: { key: 'database', description: 'Default database name', }, user: { key: 'user', description: 'Database user', }, pool_mode: { key: 'pool_mode', description: 'Connection pooling behavior', }, } as const export const DatabaseConnectionString = () => { const project = useSelectedProject() const { ref: projectRef, connectionString } = useParams() const snap = useDatabaseSettingsStateSnapshot() const state = useDatabaseSelectorStateSnapshot() const { project: projectDetails, isLoading: isProjectLoading } = useProjectContext() const [poolingMode, setPoolingMode] = useState<'transaction' | 'session'>('transaction') const [selectedTab, setSelectedTab] = useState('uri') const { data: poolingInfo, isSuccess: isSuccessPoolingInfo } = usePoolingConfigurationQuery({ projectRef, }) const poolingConfiguration = poolingInfo?.find((x) => x.identifier === state.selectedDatabaseId) const { data: databases, error: readReplicasError, isLoading: isLoadingReadReplicas, isError: isErrorReadReplicas, isSuccess: isSuccessReadReplicas, } = useReadReplicasQuery({ projectRef }) const selectedDatabase = (databases ?? []).find( (db) => db.identifier === state.selectedDatabaseId ) const { data: addons, isSuccess: isSuccessAddons } = useProjectAddonsQuery({ projectRef }) const { ipv4: ipv4Addon } = getAddons(addons?.selected_addons ?? []) const { mutate: sendEvent } = useSendEventMutation() const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at'] const emptyState = { db_user: '', db_host: '', db_port: '', db_name: '' } const connectionInfo = pluckObjectFields(selectedDatabase || emptyState, DB_FIELDS) const connectionTld = projectDetails?.restUrl !== undefined ? new URL(projectDetails?.restUrl ?? '').hostname.split('.').pop() ?? 'co' : 'co' const handleCopy = (id: string) => { const labelValue = CONNECTION_TYPES.find((type) => type.id === id)?.label sendEvent({ category: 'settings', action: 'copy_connection_string', label: labelValue ? labelValue : '', }) } const connectionStrings = isSuccessPoolingInfo && poolingConfiguration !== undefined ? getConnectionStrings(connectionInfo, poolingConfiguration, { projectRef, }) : { direct: { uri: '', psql: '', golang: '', jdbc: '', dotnet: '', nodejs: '', php: '', python: '', sqlalchemy: '', }, pooler: { uri: '', psql: '', golang: '', jdbc: '', dotnet: '', nodejs: '', php: '', python: '', sqlalchemy: '', }, } const poolerTld = isSuccessPoolingInfo && poolingConfiguration !== undefined ? getPoolerTld(poolingConfiguration?.connectionString) : 'com' // @mildtomato - Possible reintroduce later // // const poolerConnStringSyntax = // isSuccessPoolingInfo && poolingConfiguration !== undefined // ? constructConnStringSyntax(poolingConfiguration?.connectionString, { // selectedTab, // usePoolerConnection: snap.usePoolerConnection, // ref: projectRef as string, // cloudProvider: isProjectLoading ? '' : project?.cloud_provider || '', // region: isProjectLoading ? '' : project?.region || '', // tld: snap.usePoolerConnection ? poolerTld : connectionTld, // portNumber: `[5432 or 6543]`, // }) // : [] // useEffect(() => { // // if (poolingConfiguration?.pool_mode === 'session') { // // setPoolingMode(poolingConfiguration.pool_mode) // // } // }, [poolingConfiguration?.pool_mode]) const lang = CONNECTION_TYPES.find((type) => type.id === selectedTab)?.lang ?? 'bash' const contentType = CONNECTION_TYPES.find((type) => type.id === selectedTab)?.contentType ?? 'input' const example: Example | undefined = examples[selectedTab as keyof typeof examples] const exampleFiles = example?.files const exampleInstallCommands = example?.installCommands const examplePostInstallCommands = example?.postInstallCommands const hasCodeExamples = exampleFiles || exampleInstallCommands const fileTitle = CONNECTION_TYPES.find((type) => type.id === selectedTab)?.fileTitle let stepNumber = 0 const StepLabel = ({ number, children, ...props }: { number: number; children: React.ReactNode } & React.HTMLAttributes) => (
{number}
{children}
) return (
Type setSelectedTab(connectionType)} > {CONNECTION_TYPES.map((type) => ( {type.label} ))}
{isLoadingReadReplicas && } {isErrorReadReplicas && ( )} {isSuccessReadReplicas && (
{/* // handle non terminal examples */} {hasCodeExamples && (
Install the following {exampleInstallCommands?.map((cmd, i) => ( {cmd} ))}
{exampleFiles && exampleFiles?.length > 0 && (
Add file to project {exampleFiles?.map((file, i) => (
))}
)}
)}
{hasCodeExamples && (
Choose type of connection
)}
handleCopy(selectedTab)} ipv4Status={{ type: !ipv4Addon ? 'error' : 'success', title: !ipv4Addon ? 'Not IPv4 compatible' : 'IPv4 compatible', description: ipv4Addon && 'Connections are IPv4 proxied with IPv4 addon.', link: !ipv4Addon ? { text: 'IPv4 addon', url: `/project/${projectRef}/settings/addons?panel=ipv4`, } : { text: 'IPv4 settings', url: `/project/${projectRef}/settings/addons?panel=ipv4`, }, }} parameters={[ // prettier-ignore { ...CONNECTION_PARAMETERS.host, value: connectionInfo.db_host }, // prettier-ignore { ...CONNECTION_PARAMETERS.port, value: connectionInfo.db_port }, // prettier-ignore { ...CONNECTION_PARAMETERS.database, value: connectionInfo.db_name }, // prettier-ignore { ...CONNECTION_PARAMETERS.user, value: connectionInfo.db_user }, ]} /> handleCopy(selectedTab)} ipv4Status={{ type: 'success', title: 'IPv4 compatible', description: 'Transaction pooler connections are IPv4 proxied for free.', }} notice={['Does not support PREPARE statements']} parameters={[ // prettier-ignore { ...CONNECTION_PARAMETERS.host, value: `${projectRef}.pooler.supabase.${poolerTld}` }, // prettier-ignore { ...CONNECTION_PARAMETERS.port, value: poolingConfiguration?.db_port.toString() ?? '6543', description: 'Port number for transaction pooler' }, // prettier-ignore { ...CONNECTION_PARAMETERS.database, value: connectionInfo.db_name }, // prettier-ignore { ...CONNECTION_PARAMETERS.user, value: connectionInfo.db_user }, // prettier-ignore { ...CONNECTION_PARAMETERS.pool_mode, value: 'transaction', description: 'Each transaction uses a different connection' }, ]} /> {ipv4Addon && (

If you are using Session Pooler, we recommend switching to Direct Connection.

)} handleCopy(selectedTab)} ipv4Status={{ type: 'success', title: 'IPv4 compatible', description: 'Session pooler connections are IPv4 proxied for free.', }} parameters={[ // prettier-ignore { ...CONNECTION_PARAMETERS.host, value: `${projectRef}.pooler.supabase.${poolerTld}` }, // prettier-ignore { ...CONNECTION_PARAMETERS.port, value: '5432', description: 'Port number for session pooler' }, // prettier-ignore { ...CONNECTION_PARAMETERS.database, value: connectionInfo.db_name }, // prettier-ignore { ...CONNECTION_PARAMETERS.user, value: connectionInfo.db_user }, // prettier-ignore { ...CONNECTION_PARAMETERS.pool_mode, value: 'session', description: 'Connection is reserved for the entire session' }, ]} />
{examplePostInstallCommands && (
Add the configuration package to read the settings {examplePostInstallCommands?.map((cmd, i) => ( {cmd} ))}
)}
)} {/* Possibly reintroduce later - @mildtomato */} {/*

How to connect to a different database or switch to another user

You can use the following URI format to switch to a different database or user {snap.usePoolerConnection ? ' when using connection pooling' : ''}.

{poolerConnStringSyntax.map((x, idx) => { if (x.tooltip) { return ( {x.value} {x.tooltip} ) } else { return ( {x.value} ) } })}

*/} {selectedTab === 'python' && ( <>

Connecting to SQL Alchemy

Please use postgresql:// instead of postgres:// as your dialect when connecting via SQLAlchemy.

Example: create_engine("postgresql+psycopg2://...")

)}
) }