diff --git a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx b/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx
index 37f9a2865b..3c6a46adff 100644
--- a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx
@@ -1,5 +1,4 @@
import { zodResolver } from '@hookform/resolvers/zod'
-import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { Fragment, useEffect } from 'react'
@@ -32,7 +31,6 @@ import { usePoolingConfigurationUpdateMutation } from 'data/database/pooling-con
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useCheckPermissions, useStore } from 'hooks'
import { POOLING_OPTIMIZATIONS } from './ConnectionPooling.constants'
-import { constructConnStringSyntax, getPoolerTld } from './ConnectionPooling.utils'
const formId = 'connection-pooling-form'
@@ -73,21 +71,9 @@ export const ConnectionPooling = () => {
isSuccess,
} = usePoolingConfigurationQuery({ projectRef: projectRef })
- const poolerTld = isSuccess ? getPoolerTld(poolingInfo.connectionString) : 'com'
-
const connectionPoolingUnavailable =
!poolingInfo?.pgbouncer_enabled && poolingInfo?.pool_mode === null
- const poolerConnStringSyntax = isSuccess
- ? constructConnStringSyntax(poolingInfo?.connectionString, {
- ref: projectRef as string,
- cloudProvider: projectIsLoading ? '' : project?.cloud_provider || '',
- region: projectIsLoading ? '' : project?.region || '',
- tld: poolerTld,
- portNumber: poolingInfo.db_port.toString(),
- })
- : []
-
// [Joshen] TODO this needs to be obtained from BE as 26th Jan is when we'll start - projects will be affected at different rates
const resolvesToIpV6 = !poolingInfo?.supavisor_enabled && false // Number(new Date()) > Number(dayjs.utc('01-26-2024', 'MM-DD-YYYY').toDate())
@@ -166,21 +152,22 @@ export const ConnectionPooling = () => {
{connectionPoolingUnavailable
? 'Connection Pooling is not available for this project'
- : 'Connect to your database via connection pooling'}
+ : 'Connection pooling configuration'}
{isSuccess && (
-
- With {poolingInfo?.supavisor_enabled ? 'Supavisor' : 'PGBouncer'}
-
-
- {resolvesToIpV6 ? 'Resolves to IPv6' : 'Resolves to IPv4'}
+
+ {poolingInfo?.supavisor_enabled ? 'Supavisor' : 'PGBouncer'}
)}
}>
-
+
Documentation
@@ -256,10 +243,20 @@ export const ConnectionPooling = () => {
label="Transaction"
value="transaction"
>
- Transaction
+ Transaction mode
+
+ Connection is assigned to the client for the duration of a
+ transaction. Some session-based Postgres features such as prepared
+ statements are not available with this option.
+
- Session
+ Session mode
+
+ When a new client connects, a connection is assigned to the client
+ until it disconnects. All Postgres features can be used with this
+ option.
+
@@ -406,70 +403,6 @@ export const ConnectionPooling = () => {
>
)}
-
-
-
-
-
-
- You may also connect to another database or with another user via Supavisor
- with the following URI format:
-
-
- {poolerConnStringSyntax.length > 0 && (
-
- {poolerConnStringSyntax.map((x, idx) => {
- if (x.tooltip) {
- return (
-
-
- {x.value}
-
-
-
-
-
-
- {x.tooltip}
-
-
-
-
-
- )
- } else {
- return x.value
- }
- })}
-
- )}
-
- )
- }
- />
>
)}
diff --git a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.utils.ts b/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.utils.ts
deleted file mode 100644
index a4975c39fb..0000000000
--- a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.utils.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-// [Joshen] This is to the best of interpreting the syntax from the API response
-// // There's different format for PG13 (depending on authentication method being md5) and PG14
-export const constructConnStringSyntax = (
- connString: string,
- {
- ref,
- cloudProvider,
- region,
- tld,
- portNumber,
- }: { ref: string; cloudProvider: string; region: string; tld: string; portNumber: string }
-) => {
- if (connString.includes('postgres:[YOUR-PASSWORD]')) {
- // PG 13 + Authentication MD5
- return [
- { value: 'postgres://', tooltip: undefined },
- { value: '[user]', tooltip: 'Database user (e.g postgres)' },
- { value: ':', tooltip: undefined },
- { value: '[password]', tooltip: 'Database password' },
- { value: '@', tooltip: undefined },
- { value: cloudProvider.toLocaleLowerCase(), tooltip: 'Cloud provider' },
- { value: '-0-', tooltip: undefined },
- { value: region, tooltip: "Project's region" },
- { value: `.pooler.supabase.${tld}:`, tooltip: undefined },
- { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
- { value: '/', tooltip: undefined },
- { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
- { value: `?options=reference%3D`, tooltip: undefined },
- { value: ref, tooltip: "Project's reference ID" },
- ]
- } else {
- return [
- { value: 'postgres://', tooltip: undefined },
- { value: '[user]', tooltip: 'Database user (e.g postgres)' },
- { value: '.', tooltip: undefined },
- { value: ref, tooltip: "Project's reference ID" },
- { value: ':', tooltip: undefined },
- { value: '[password]', tooltip: 'Database password' },
- { value: '@', tooltip: undefined },
- { value: cloudProvider.toLocaleLowerCase(), tooltip: 'Cloud provider' },
- { value: '-0-', tooltip: undefined },
- { value: region, tooltip: "Project's region" },
- { value: `.pooler.supabase.${tld}:`, tooltip: undefined },
- { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
- { value: '/', tooltip: undefined },
- { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
- ]
- }
-}
-
-export const getPoolerTld = (connString: string) => {
- try {
- const segment = connString.split('pooler.supabase.')[1]
- const tld = segment.split(':6543')[0]
- return tld
- } catch {
- return 'com'
- }
-}
diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx
new file mode 100644
index 0000000000..78bf375cf4
--- /dev/null
+++ b/apps/studio/components/interfaces/Settings/Database/DatabaseReadOnlyAlert.tsx
@@ -0,0 +1,85 @@
+import { useParams } from 'common'
+import Link from 'next/link'
+import {
+ Alert_Shadcn_,
+ IconAlertTriangle,
+ AlertTitle_Shadcn_,
+ AlertDescription_Shadcn_,
+ Button,
+ IconExternalLink,
+} from 'ui'
+
+import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
+import { useSelectedOrganization } from 'hooks'
+import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
+import { useState } from 'react'
+import ConfirmDisableReadOnlyModeModal from './DatabaseSettings/ConfirmDisableReadOnlyModal'
+
+export const DatabaseReadOnlyAlert = () => {
+ const { ref: projectRef } = useParams()
+ const organization = useSelectedOrganization()
+ const [showConfirmationModal, setShowConfirmationModal] = useState(false)
+
+ const { data: resourceWarnings } = useResourceWarningsQuery()
+ const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
+
+ const isReadOnlyMode =
+ (resourceWarnings ?? [])?.find((warning) => warning.project === projectRef)
+ ?.is_readonly_mode_enabled ?? false
+
+ return (
+ <>
+ {isReadOnlyMode && (
+
+
+
+ Project is in read-only mode and database is no longer accepting write requests
+
+
+ You have reached 95% of your project's disk space, and read-only mode has been enabled
+ to preserve your database's stability and prevent your project from exceeding its
+ current billing plan. To resolve this, you may:
+
+
+ Temporarily disable read-only mode to free up space and reduce your database size
+
+ {subscription?.plan.id === 'free' ? (
+
+
+ Upgrade to the Pro plan
+ {' '}
+ to increase your database size limit to 8GB.
+
+ ) : subscription?.plan.id === 'pro' && subscription?.usage_billing_enabled ? (
+
+
+ Disable your Spend Cap
+ {' '}
+ to allow your project to auto-scale and expand beyond the 8GB database size limit
+
+ ) : null}
+
+
+
+
setShowConfirmationModal(true)}>
+ Disable read-only mode
+
+
}>
+
+ Learn more
+
+
+
+
+ )}
+ setShowConfirmationModal(false)}
+ />
+ >
+ )
+}
diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx
index a4f406f1e5..d728f2e229 100644
--- a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx
@@ -1,23 +1,32 @@
+import * as Tooltip from '@radix-ui/react-tooltip'
import { useParams, useTelemetryProps } from 'common'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
-import { Input, Separator, Tabs } from 'ui'
+import { Button, IconExternalLink, Input, Separator, Tabs } from 'ui'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import AlertError from 'components/ui/AlertError'
import DatabaseSelector from 'components/ui/DatabaseSelector'
+import Panel from 'components/ui/Panel'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
+import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
import { useFlag } from 'hooks'
import { pluckObjectFields } from 'lib/helpers'
import Telemetry from 'lib/telemetry'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
-import { getConnectionStrings } from './DatabaseSettings.utils'
+import { IPv4DeprecationNotice } from '../IPv4DeprecationNotice'
+import { UsePoolerCheckbox } from '../UsePoolerCheckbox'
+import {
+ constructConnStringSyntax,
+ getConnectionStrings,
+ getPoolerTld,
+} from './DatabaseSettings.utils'
const CONNECTION_TYPES = [
- { id: 'psql', label: 'PSQL' },
{ id: 'uri', label: 'URI' },
+ { id: 'psql', label: 'PSQL' },
{ id: 'golang', label: 'Golang' },
{ id: 'jdbc', label: 'JDBC' },
{ id: 'dotnet', label: '.NET' },
@@ -28,16 +37,22 @@ const CONNECTION_TYPES = [
export const DatabaseConnectionString = () => {
const router = useRouter()
- const { project: projectDetails } = useProjectContext()
+ const { project: projectDetails, isLoading: isProjectLoading } = useProjectContext()
const { ref: projectRef, connectionString } = useParams()
const telemetryProps = useTelemetryProps()
+ const readReplicasEnabled = useFlag('readReplicas') && projectDetails?.is_read_replicas_enabled
+
const state = useDatabaseSelectorStateSnapshot()
- const readReplicasEnabled = useFlag('readReplicas') && projectDetails?.is_read_replicas_enabled
const connectionStringsRef = useRef(null)
+ const [usePoolerConnection, setUsePoolerConnection] = useState(true)
const [selectedTab, setSelectedTab] = useState<
- 'psql' | 'uri' | 'golang' | 'jdbc' | 'dotnet' | 'nodejs' | 'php' | 'python'
- >('psql')
+ 'uri' | 'psql' | 'golang' | 'jdbc' | 'dotnet' | 'nodejs' | 'php' | 'python'
+ >('uri')
+
+ const { data: poolingInfo, isSuccess: isSuccessPoolingInfo } = usePoolingConfigurationQuery({
+ projectRef,
+ })
const {
data,
@@ -70,6 +85,10 @@ export const DatabaseConnectionString = () => {
const connectionInfo = readReplicasEnabled
? pluckObjectFields(selectedDatabase || emptyState, DB_FIELDS)
: pluckObjectFields(project || 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
@@ -84,7 +103,26 @@ export const DatabaseConnectionString = () => {
)
}
- const connectionStrings = getConnectionStrings(connectionInfo)
+ const connectionStrings = isSuccessPoolingInfo
+ ? getConnectionStrings(connectionInfo, poolingInfo, {
+ projectRef,
+ usePoolerConnection,
+ })
+ : { uri: '', psql: '', golang: '', jdbc: '', dotnet: '', nodejs: '', php: '', python: '' }
+ const poolerTld = isSuccessPoolingInfo ? getPoolerTld(poolingInfo.connectionString) : 'com'
+ const poolerConnStringSyntax = isSuccessPoolingInfo
+ ? constructConnStringSyntax(poolingInfo.connectionString, {
+ selectedTab,
+ usePoolerConnection,
+ ref: projectRef as string,
+ cloudProvider: isProjectLoading ? '' : project?.cloud_provider || '',
+ region: isProjectLoading ? '' : project?.region || '',
+ tld: usePoolerConnection ? poolerTld : connectionTld,
+ portNumber: usePoolerConnection
+ ? poolingInfo.db_port.toString()
+ : connectionInfo.db_port.toString(),
+ })
+ : []
useEffect(() => {
if (
@@ -99,40 +137,103 @@ export const DatabaseConnectionString = () => {
return (
-
-
-
- Connection string
-
- {readReplicasEnabled && }
-
-
- {CONNECTION_TYPES.map((type) => (
-
- ))}
-
-
-
-
-
- {isLoading &&
}
- {isError &&
}
- {isSuccess && (
-
handleCopy(selectedTab)}
- />
- )}
-
+
+
+
+ Connection string
+
+
+
+
+ {CONNECTION_TYPES.map((type) => (
+
+ ))}
+
+
+
+ }
+ >
+
+ {isLoading && }
+ {isError && }
+ {isSuccess && (
+
+
+ {!usePoolerConnection &&
}
+
handleCopy(selectedTab)}
+ />
+ {poolerConnStringSyntax.length > 0 && poolingInfo?.supavisor_enabled && (
+
+
+ You can use the following URI format to switch to a different database or user
+ when using connection pooling.
+
+
+ {poolerConnStringSyntax.map((x, idx) => {
+ if (x.tooltip) {
+ return (
+
+
+ {x.value}
+
+
+
+
+
+
+ {x.tooltip}
+
+
+
+
+
+ )
+ } else {
+ return (
+
+ {x.value}
+
+ )
+ }
+ })}
+
+
+ )}
+
+ )}
+
+
)
}
diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx
index 0f37ea9d5b..fffecfaa92 100644
--- a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx
@@ -1,5 +1,4 @@
import { useParams, useTelemetryProps } from 'common'
-import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
@@ -9,9 +8,7 @@ import Panel from 'components/ui/Panel'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
-import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
-import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
-import { useFlag, useSelectedOrganization, useSelectedProject } from 'hooks'
+import { useFlag, useSelectedProject } from 'hooks'
import { pluckObjectFields } from 'lib/helpers'
import Telemetry from 'lib/telemetry'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
@@ -19,16 +16,14 @@ import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
- Badge,
- Button,
- IconAlertTriangle,
- IconExternalLink,
+ IconAlertCircle,
Input,
- Separator,
} from 'ui'
-import ConfirmDisableReadOnlyModeModal from './ConfirmDisableReadOnlyModal'
+import { IPv4DeprecationNotice } from '../IPv4DeprecationNotice'
+import { UsePoolerCheckbox } from '../UsePoolerCheckbox'
import ResetDbPassword from './ResetDbPassword'
-import { DatabaseConnectionString } from './DatabaseConnectionString'
+import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
+import { getHostFromConnectionString } from './DatabaseSettings.utils'
const DatabaseSettings = () => {
const router = useRouter()
@@ -36,17 +31,21 @@ const DatabaseSettings = () => {
const telemetryProps = useTelemetryProps()
const state = useDatabaseSelectorStateSnapshot()
const selectedProject = useSelectedProject()
- const organization = useSelectedOrganization()
const readReplicasEnabled = useFlag('readReplicas')
const showReadReplicasUI = readReplicasEnabled && selectedProject?.is_read_replicas_enabled
const connectionStringsRef = useRef(null)
- const [showConfirmationModal, setShowConfirmationModal] = useState(false)
+ const [usePoolerConnection, setUsePoolerConnection] = useState(true)
- // [Joshen] TODO this needs to be obtained from BE as 26th Jan is when we'll start - projects will be affected at different rates
- const resolvesToIpV6 = false // Number(new Date()) > Number(dayjs.utc('01-26-2024', 'MM-DD-YYYY').toDate())
-
- const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
+ const {
+ data: poolingInfo,
+ error: poolingInfoError,
+ isLoading: isLoadingPoolingInfo,
+ isError: isErrorPoolingInfo,
+ isSuccess: isSuccessPoolingInfo,
+ } = usePoolingConfigurationQuery({
+ projectRef,
+ })
const {
data,
error: projectSettingsError,
@@ -54,7 +53,6 @@ const DatabaseSettings = () => {
isError: isErrorProjectSettings,
isSuccess: isSuccessProjectSettings,
} = useProjectSettingsQuery({ projectRef })
- const { data: resourceWarnings } = useResourceWarningsQuery()
const {
data: databases,
error: readReplicasError,
@@ -62,26 +60,40 @@ const DatabaseSettings = () => {
isError: isErrorReadReplicas,
isSuccess: isSuccessReadReplicas,
} = useReadReplicasQuery({ projectRef })
- const error = showReadReplicasUI ? readReplicasError : projectSettingsError
- const isLoading = showReadReplicasUI ? isLoadingReadReplicas : isLoadingProjectSettings
- const isError = showReadReplicasUI ? isErrorReadReplicas : isErrorProjectSettings
- const isSuccess = showReadReplicasUI ? isSuccessReadReplicas : isSuccessProjectSettings
+ const error = showReadReplicasUI ? readReplicasError : projectSettingsError || poolingInfoError
+ const isLoading = showReadReplicasUI
+ ? isLoadingReadReplicas
+ : isLoadingProjectSettings || isLoadingPoolingInfo
+ const isError = showReadReplicasUI
+ ? isErrorReadReplicas
+ : isErrorProjectSettings || isErrorPoolingInfo
+ const isSuccess = showReadReplicasUI
+ ? isSuccessReadReplicas
+ : isSuccessProjectSettings || isSuccessPoolingInfo
const selectedDatabase = (databases ?? []).find(
(db) => db.identifier === state.selectedDatabaseId
)
-
- const isReadOnlyMode =
- (resourceWarnings ?? [])?.find((warning) => warning.project === projectRef)
- ?.is_readonly_mode_enabled ?? false
+ const isMd5 = poolingInfo?.connectionString.includes('?options=reference')
const { project } = data ?? {}
- const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at']
+ const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user']
const emptyState = { db_user: '', db_host: '', db_port: '', db_name: '' }
- const connectionInfo = showReadReplicasUI
+ const dbConnectionInfo = showReadReplicasUI
? pluckObjectFields(selectedDatabase || emptyState, DB_FIELDS)
: pluckObjectFields(project || emptyState, DB_FIELDS)
+ const connectionInfo = usePoolerConnection
+ ? {
+ db_host: isSuccessPoolingInfo
+ ? getHostFromConnectionString(poolingInfo?.connectionString)
+ : '',
+ db_name: poolingInfo?.db_name,
+ db_port: poolingInfo?.db_port,
+ db_user: `postgres.${projectRef}`,
+ }
+ : dbConnectionInfo
+
const handleCopy = (labelValue?: string) =>
Telemetry.sendEvent(
{
@@ -103,68 +115,16 @@ const DatabaseSettings = () => {
return (
<>
-
- setShowConfirmationModal(false)}
- />
>
)
}
diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.utils.ts b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.utils.ts
index 50223293c6..96ce0f50dc 100644
--- a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.utils.ts
+++ b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.utils.ts
@@ -1,32 +1,50 @@
-export const getConnectionStrings = (connectionInfo: {
- db_user: string
- db_port: number
- db_host: string
- db_name: string
-}) => {
- const uriConnString =
- `postgresql://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
- `${connectionInfo.db_host}:${connectionInfo.db_port.toString()}` +
- `/${connectionInfo.db_name}`
+import { PoolingConfiguration } from 'data/database/pooling-configuration-query'
+
+export const getHostFromConnectionString = (str: string) => {
+ const segment = str.split('[YOUR-PASSWORD]@')
+ const [output] = segment[1].split(':')
+ return output
+}
+
+export const getConnectionStrings = (
+ connectionInfo: {
+ db_user: string
+ db_port: number
+ db_host: string
+ db_name: string
+ },
+ poolingInfo: PoolingConfiguration,
+ metadata: {
+ usePoolerConnection: boolean
+ projectRef?: string
+ pgVersion?: string
+ }
+) => {
+ const { usePoolerConnection, projectRef } = metadata
+
+ // Pooler: user, host port
+ const user = usePoolerConnection ? `postgres.${projectRef}` : connectionInfo.db_user
+ const port = usePoolerConnection ? poolingInfo?.db_port : connectionInfo.db_port
+ // [Joshen] Temp FE: extract host from pooler connection string
+ const host = usePoolerConnection
+ ? getHostFromConnectionString(poolingInfo.connectionString)
+ : connectionInfo.db_host
+ const name = usePoolerConnection ? poolingInfo?.db_name : connectionInfo.db_name
+
+ const uriConnString = usePoolerConnection
+ ? poolingInfo?.connectionString
+ : `postgresql://${user}:[YOUR-PASSWORD]@` + `${host}:${port}` + `/${name}`
const golangConnString =
- `user=${connectionInfo.db_user} password=[YOUR-PASSWORD] ` +
- `host=${connectionInfo.db_host} port=${connectionInfo.db_port.toString()}` +
- ` dbname=${connectionInfo.db_name}`
- const psqlConnString =
- `psql -h ${connectionInfo.db_host} -p ` +
- `${connectionInfo.db_port.toString()} -d ${connectionInfo.db_name} ` +
- `-U ${connectionInfo.db_user}`
+ `user=${user} password=[YOUR-PASSWORD] ` + `host=${host} port=${port}` + ` dbname=${name}`
+ const psqlConnString = `psql -h ${host} -p ` + `${port} -d ${name} ` + `-U ${user}`
const jdbcConnString =
- `jdbc:postgresql://${connectionInfo.db_host}:${connectionInfo.db_port.toString()}` +
- `/${connectionInfo.db_name}?user=${connectionInfo.db_user}&password=[YOUR-PASSWORD]`
+ `jdbc:postgresql://${host}:${port}` + `/${name}?user=${user}&password=[YOUR-PASSWORD]`
const dotNetConnString =
- `User Id=${connectionInfo.db_user};Password=[YOUR-PASSWORD];` +
- `Server=${connectionInfo.db_host};Port=${connectionInfo.db_port.toString()};` +
- `Database=${connectionInfo.db_name}`
+ `User Id=${user};Password=[YOUR-PASSWORD];` +
+ `Server=${host};Port=${port};` +
+ `Database=${name}`
const pythonConnString =
- `user=${connectionInfo.db_user} password=[YOUR-PASSWORD]` +
- ` host=${connectionInfo.db_host} port=${connectionInfo.db_port.toString()}` +
- ` database=${connectionInfo.db_name}`
+ `user=${user} password=[YOUR-PASSWORD]` + ` host=${host} port=${port}` + ` database=${name}`
return {
psql: psqlConnString,
@@ -39,3 +57,302 @@ export const getConnectionStrings = (connectionInfo: {
python: pythonConnString,
}
}
+
+// [Joshen] This is to the best of interpreting the syntax from the API response
+// // There's different format for PG13 (depending on authentication method being md5) and PG14
+export const constructConnStringSyntax = (
+ connString: string,
+ {
+ selectedTab,
+ usePoolerConnection,
+ ref,
+ cloudProvider,
+ region,
+ tld,
+ portNumber,
+ }: {
+ selectedTab: 'uri' | 'psql' | 'golang' | 'jdbc' | 'dotnet' | 'nodejs' | 'php' | 'python'
+ usePoolerConnection: boolean
+ ref: string
+ cloudProvider: string
+ region: string
+ tld: string
+ portNumber: string
+ }
+) => {
+ const isMd5 = connString.includes('?options=reference')
+ const poolerHostDetails = [
+ { value: cloudProvider.toLocaleLowerCase(), tooltip: 'Cloud provider' },
+ { value: '-0-', tooltip: undefined },
+ { value: region, tooltip: "Project's region" },
+ { value: `.pooler.supabase.${tld}`, tooltip: undefined },
+ ]
+ const dbHostDetails = [
+ { value: 'db.', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ { value: `.supabase.${tld}`, tooltip: undefined },
+ ]
+
+ if (selectedTab === 'uri' || selectedTab === 'nodejs') {
+ if (isMd5) {
+ return [
+ { value: 'postgres://', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ { value: ':', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ { value: '@', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ':', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: `?options=reference%3D`, tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'postgres://', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ { value: ':', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ { value: '@', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ':', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ ]
+ }
+ }
+
+ if (selectedTab === 'psql') {
+ if (isMd5) {
+ return [
+ { value: 'psql "postgresql://', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ { value: ':', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ { value: '@', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ':', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: '?options=reference%3D', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'psql -h ', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ' -p ', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: ' -d ', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ { value: ' -U ', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ ]
+ }
+ }
+
+ if (selectedTab === 'golang' || selectedTab === 'php') {
+ if (isMd5) {
+ return [
+ { value: 'user=', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ { value: ' password=', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ { value: ' host=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ' port=', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: ' dbname=', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: ' options=reference=', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'user=', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ { value: ' password=', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ { value: ' host=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ' port=', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: ' dbname=', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ ]
+ }
+ }
+
+ if (selectedTab === 'jdbc') {
+ if (isMd5) {
+ return [
+ { value: 'jdbc:postgresql://', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ':', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ { value: '?user=', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ { value: '&password=', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ ...(usePoolerConnection
+ ? [
+ { value: '&options=reference%3D', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'jdbc:postgresql://', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: `:`, tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ { value: '?user=', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ { value: '&password=', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ ]
+ }
+ }
+
+ if (selectedTab === 'dotnet') {
+ if (isMd5) {
+ return [
+ { value: 'User Id=', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ { value: ';Password=', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ { value: ';Server=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ';Port=', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: ';Database=', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: ";Options='reference=", tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ { value: "'", tooltip: undefined },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'User Id=', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ { value: ';Password=', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ { value: ';Server=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ';Port=', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: ';Database=', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ ]
+ }
+ }
+
+ if ('python') {
+ if (isMd5) {
+ return [
+ { value: 'user=', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ { value: ' password=', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ { value: ' host=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ' port=', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: ' database=', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: ' options=reference=', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'user=', tooltip: undefined },
+ { value: '[user]', tooltip: 'Database user (e.g postgres)' },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: "Project's reference ID" },
+ ]
+ : []),
+ { value: ' password=', tooltip: undefined },
+ { value: '[password]', tooltip: 'Database password' },
+ { value: ' host=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ' port=', tooltip: undefined },
+ { value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
+ { value: ' database=', tooltip: undefined },
+ { value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
+ ]
+ }
+ }
+
+ return []
+}
+
+export const getPoolerTld = (connString: string) => {
+ try {
+ const segment = connString.split('pooler.supabase.')[1]
+ const tld = segment.split(':6543')[0]
+ return tld
+ } catch {
+ return 'com'
+ }
+}
diff --git a/apps/studio/components/interfaces/Settings/Database/IPv4DeprecationNotice.tsx b/apps/studio/components/interfaces/Settings/Database/IPv4DeprecationNotice.tsx
new file mode 100644
index 0000000000..d0eb82e14b
--- /dev/null
+++ b/apps/studio/components/interfaces/Settings/Database/IPv4DeprecationNotice.tsx
@@ -0,0 +1,35 @@
+import {
+ Alert_Shadcn_,
+ IconAlertTriangle,
+ AlertTitle_Shadcn_,
+ AlertDescription_Shadcn_,
+ Button,
+ IconExternalLink,
+} from 'ui'
+
+export const IPv4DeprecationNotice = () => {
+ return (
+
+
+
+ Direct database access via IPv4 and pgBouncer will be removed from January 26th 2024
+
+
+
+ We strongly recommend using connection pooling to
+ connect to your database. You'll only need to change the connection string that you're
+ using in your application to the pooler's connection string.
+
+ }>
+
+ Learn more
+
+
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Settings/Database/UsePoolerCheckbox.tsx b/apps/studio/components/interfaces/Settings/Database/UsePoolerCheckbox.tsx
new file mode 100644
index 0000000000..466b3c7189
--- /dev/null
+++ b/apps/studio/components/interfaces/Settings/Database/UsePoolerCheckbox.tsx
@@ -0,0 +1,53 @@
+import { useParams } from 'common'
+import { Badge, Checkbox_Shadcn_ } from 'ui'
+
+import { Markdown } from 'components/interfaces/Markdown'
+import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
+
+interface UsePoolerCheckboxInterface {
+ id: string
+ checked: boolean
+ onCheckedChange: (value: boolean) => void
+}
+
+export const UsePoolerCheckbox = ({ id, checked, onCheckedChange }: UsePoolerCheckboxInterface) => {
+ const { ref: projectRef } = useParams()
+ const { data, isSuccess } = usePoolingConfigurationQuery({ projectRef })
+
+ // [Joshen] TODO this needs to be obtained from BE as 26th Jan is when we'll start - projects will be affected at different rates
+ const resolvesToIpV6 = !data?.supavisor_enabled && false // Number(new Date()) > Number(dayjs.utc('01-26-2024', 'MM-DD-YYYY').toDate())
+
+ return (
+
+
onCheckedChange(!checked)}
+ />
+
+
+ Use connection pooling
+ {isSuccess && checked && data.supavisor_enabled && (
+
+ Supavisor
+
+ )}
+
+ {checked
+ ? 'Resolves to IPv4'
+ : resolvesToIpV6
+ ? 'Resolves to IPv6'
+ : 'Resolves to IPv4'}
+
+
+
+
+
+ )
+}
diff --git a/apps/studio/data/database/pooling-configuration-query.ts b/apps/studio/data/database/pooling-configuration-query.ts
index 4959c1ecd2..1c2ffa8191 100644
--- a/apps/studio/data/database/pooling-configuration-query.ts
+++ b/apps/studio/data/database/pooling-configuration-query.ts
@@ -2,11 +2,14 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'data/fetchers'
import { ResponseError } from 'types'
import { databaseKeys } from './keys'
+import { components } from 'data/api'
export type PoolingConfigurationVariables = {
projectRef?: string
}
+export type PoolingConfiguration = components['schemas']['PgbouncerConfigResponse']
+
export async function getPoolingConfiguration(
{ projectRef }: PoolingConfigurationVariables,
signal?: AbortSignal
diff --git a/apps/studio/pages/project/[ref]/settings/database.tsx b/apps/studio/pages/project/[ref]/settings/database.tsx
index 3401c8184b..4e7dbfc4e3 100644
--- a/apps/studio/pages/project/[ref]/settings/database.tsx
+++ b/apps/studio/pages/project/[ref]/settings/database.tsx
@@ -1,29 +1,36 @@
-import { observer } from 'mobx-react-lite'
-import { NextPageWithLayout } from 'types'
-import { SettingsLayout } from 'components/layouts'
import {
ConnectionPooling,
DatabaseSettings,
NetworkRestrictions,
} from 'components/interfaces/Settings/Database'
+import { SettingsLayout } from 'components/layouts'
+import { observer } from 'mobx-react-lite'
+import { NextPageWithLayout } from 'types'
-import SSLConfiguration from 'components/interfaces/Settings/Database/SSLConfiguration'
-import DiskSizeConfiguration from 'components/interfaces/Settings/Database/DiskSizeConfiguration'
import BannedIPs from 'components/interfaces/Settings/Database/BannedIPs'
+import { DatabaseConnectionString } from 'components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString'
+import DiskSizeConfiguration from 'components/interfaces/Settings/Database/DiskSizeConfiguration'
+import SSLConfiguration from 'components/interfaces/Settings/Database/SSLConfiguration'
+import { DatabaseReadOnlyAlert } from 'components/interfaces/Settings/Database/DatabaseReadOnlyAlert'
const ProjectSettings: NextPageWithLayout = () => {
return (