Files
supabase/apps/studio/components/interfaces/ConnectSheet/DatabaseSettings.utils.ts

280 lines
9.1 KiB
TypeScript

import type { ConnectionStringPooler, DeploymentMode } from './Connect.types'
type ConnectionStrings = {
psql: string
uri: string
golang: string
jdbc: string
dotnet: string
nodejs: string
php: string
python: string
sqlalchemy: string
}
/**
* Self-hosted Supavisor pooler strings. User/password are placeholders that
* the operator fills in — `POOLER_TENANT_ID` and the postgres password are
* defined in the docker-compose env.
*/
export const getSelfHostedPoolerStrings = (
dbHost: string,
port: number | string,
dbName: string = 'postgres'
): ConnectionStrings => {
const user = 'postgres.[POOLER_TENANT_ID]'
const password = '[YOUR-PASSWORD]'
const uri = `postgresql://${user}:${password}@${dbHost}:${port}/${dbName}`
const psql = `psql 'postgresql://${user}:${password}@${dbHost}:${port}/${dbName}'`
const golang = `user=${user}\npassword=${password}\nhost=${dbHost}\nport=${port}\ndbname=${dbName}`
const jdbc = `jdbc:postgresql://${dbHost}:${port}/${dbName}?user=${user}&password=${password}`
const dotnet = `{
"ConnectionStrings": {
"DefaultConnection": "User Id=${user};Password=${password};Server=${dbHost};Port=${port};Database=${dbName}"
}
}`
const nodejs = `DATABASE_URL=${uri}`
return {
psql,
uri,
golang,
jdbc,
dotnet,
nodejs,
php: golang,
python: golang,
sqlalchemy: golang,
}
}
/**
* Self-hosted direct postgres connection strings. Requires the operator to
* have exposed postgres on the host — by default docker-compose does not.
*/
export const getSelfHostedDirectStrings = (
dbHost: string,
port: number | string,
dbName: string = 'postgres'
): ConnectionStrings => {
const user = 'postgres'
const password = '[YOUR-PASSWORD]'
const uri = `postgresql://${user}:${password}@${dbHost}:${port}/${dbName}`
const psql = `psql 'postgresql://${user}:${password}@${dbHost}:${port}/${dbName}'`
const golang = `user=${user}\npassword=${password}\nhost=${dbHost}\nport=${port}\ndbname=${dbName}`
const jdbc = `jdbc:postgresql://${dbHost}:${port}/${dbName}?user=${user}&password=${password}`
const dotnet = `{
"ConnectionStrings": {
"DefaultConnection": "User Id=${user};Password=${password};Server=${dbHost};Port=${port};Database=${dbName}"
}
}`
const nodejs = `DATABASE_URL=${uri}`
return {
psql,
uri,
golang,
jdbc,
dotnet,
nodejs,
php: golang,
python: golang,
sqlalchemy: golang,
}
}
/**
* Returns `{ direct, pooler }`. `.direct` depends only on `connectionInfo`, so
* when callers invoke this twice (once per pooler flavor) as
* `connectionStringsShared` / `connectionStringsDedicated`, both `.direct`
* fields are identical — the `Shared`/`Dedicated` suffix only describes which
* pooler URI you get from `.pooler`.
*/
export const getConnectionStrings = ({
connectionInfo,
poolingInfo,
metadata,
}: {
connectionInfo: {
db_user: string
db_port: number
db_host: string
db_name: string
}
poolingInfo?: {
connectionString: string
db_user: string
db_port: number
db_host: string
db_name: string
}
metadata: {
projectRef?: string
pgVersion?: string
}
}): {
direct: ConnectionStrings
pooler: ConnectionStrings
} => {
const isMd5 = poolingInfo?.connectionString.includes('options=reference')
const { projectRef } = metadata
const password = '[YOUR-PASSWORD]'
// Direct connection variables
const directUser = connectionInfo.db_user
const directPort = connectionInfo.db_port
const directHost = connectionInfo.db_host
const directName = connectionInfo.db_name
// Pooler connection variables
const poolerUser = poolingInfo?.db_user
const poolerPort = poolingInfo?.db_port
const poolerHost = poolingInfo?.db_host
const poolerName = poolingInfo?.db_name
// Direct connection strings
const directPsqlString = isMd5
? `psql "postgresql://${directUser}:${password}@${directHost}:${directPort}/${directName}"`
: `psql -h ${directHost} -p ${directPort} -d ${directName} -U ${directUser}`
const directUriString = `postgresql://${directUser}:${password}@${directHost}:${directPort}/${directName}`
const directGolangString = `DATABASE_URL=${directUriString}`
const directJdbcString = `jdbc:postgresql://${directHost}:${directPort}/${directName}?user=${directUser}&password=${password}`
// User Id=${directUser};Password=${password};Server=${directHost};Port=${directPort};Database=${directName}`
const directDotNetString = `{
"ConnectionStrings": {
"DefaultConnection": "Host=${directHost};Database=${directName};Username=${directUser};Password=${password};SSL Mode=Require;Trust Server Certificate=true"
}
}`
// `User Id=${poolerUser};Password=${password};Server=${poolerHost};Port=${poolerPort};Database=${poolerName}${isMd5 ? `;Options='reference=${projectRef}'` : ''}`
const poolerDotNetString = `{
"ConnectionStrings": {
"DefaultConnection": "User Id=${poolerUser};Password=${password};Server=${poolerHost};Port=${poolerPort};Database=${poolerName}${isMd5 ? `;Options='reference=${projectRef}'` : ''}"
}
}`
const directNodejsString = `DATABASE_URL=${directUriString}`
// Pooler connection strings
const poolerPsqlString = isMd5
? `psql "postgresql://${poolerUser}:${password}@${poolerHost}:${poolerPort}/${poolerName}?options=reference%3D${projectRef}"`
: `psql -h ${poolerHost} -p ${poolerPort} -d ${poolerName} -U ${poolerUser}`
const poolerUriString = poolingInfo?.connectionString ?? ''
const nodejsPoolerUriString = `DATABASE_URL=${poolingInfo?.connectionString ?? ''}`
const poolerGolangString = `user=${poolerUser}
password=${password}
host=${poolerHost}
port=${poolerPort}
dbname=${poolerName}${isMd5 ? `options=reference=${projectRef}` : ''}`
const poolerJdbcString = `jdbc:postgresql://${poolerHost}:${poolerPort}/${poolerName}?user=${poolerUser}${isMd5 ? `&options=reference%3D${projectRef}` : ''}&password=${password}`
const sqlalchemyString = `user=${directUser}
password=${password}
host=${directHost}
port=${directPort}
dbname=${directName}`
const poolerSqlalchemyString = `user=${poolerUser}
password=${password}
host=${poolerHost}
port=${poolerPort}
dbname=${poolerName}`
return {
direct: {
psql: directPsqlString,
uri: directUriString,
golang: directGolangString,
jdbc: directJdbcString,
dotnet: directDotNetString,
nodejs: directNodejsString,
php: directGolangString,
python: directGolangString,
sqlalchemy: sqlalchemyString,
},
pooler: {
psql: poolerPsqlString,
uri: poolerUriString,
golang: poolerGolangString,
jdbc: poolerJdbcString,
dotnet: poolerDotNetString,
nodejs: nodejsPoolerUriString,
php: poolerGolangString,
python: poolerGolangString,
sqlalchemy: poolerSqlalchemyString,
},
}
}
/**
* Shapes the ConnectionStringPooler "bag" consumed by every connection-string
* step. On platform we keep the existing shared/dedicated pooler layout; on
* self-hosted we substitute Supavisor placeholder strings on the standard
* ports; on CLI we collapse to direct since no pooler is exposed.
*/
export const buildConnectionStringPooler = ({
deploymentMode,
connectionInfo,
connectionStringsShared,
connectionStringsDedicated,
ipv4Addon,
}: {
deploymentMode: DeploymentMode
connectionInfo: { db_host: string; db_port: number | string }
connectionStringsShared: { direct: ConnectionStrings; pooler: ConnectionStrings }
connectionStringsDedicated?: { direct: ConnectionStrings; pooler: ConnectionStrings }
ipv4Addon: boolean
}): ConnectionStringPooler => {
if (deploymentMode.isSelfHosted) {
const dbHost = connectionInfo.db_host
const dbPort = connectionInfo.db_port || 5432
const sessionPool = getSelfHostedPoolerStrings(dbHost, dbPort)
const transactionPool = getSelfHostedPoolerStrings(dbHost, 6543)
const directConn = getSelfHostedDirectStrings(dbHost, dbPort)
return {
transactionShared: transactionPool.uri,
sessionShared: sessionPool.uri,
transactionDedicated: undefined,
sessionDedicated: undefined,
ipv4SupportedForDedicatedPooler: false,
direct: directConn.uri,
}
}
if (deploymentMode.isCli) {
// CLI exposes postgres directly; no pooler is available, so any code path
// that reaches for a pooler URI falls back to the direct connection.
const directUri = connectionStringsShared.direct.uri
return {
transactionShared: directUri,
sessionShared: directUri,
transactionDedicated: undefined,
sessionDedicated: undefined,
ipv4SupportedForDedicatedPooler: false,
direct: directUri,
}
}
// Port-swap 6543→5432 derives session from transaction. For shared this is a
// real Supavisor session connection; for dedicated it lands on direct Postgres
// (PgBouncer has no session mode).
return {
transactionShared: connectionStringsShared.pooler.uri,
sessionShared: connectionStringsShared.pooler.uri.replace('6543', '5432'),
transactionDedicated: connectionStringsDedicated?.pooler.uri,
sessionDedicated: connectionStringsDedicated?.pooler.uri.replace('6543', '5432'),
ipv4SupportedForDedicatedPooler: ipv4Addon,
direct: connectionStringsShared.direct.uri,
}
}