1use anyhow::Result;
2use serde::Deserialize;
3use sqlx::{postgres::PgPoolOptions, Executor, PgPool};
4use std::{sync::OnceLock, time::Duration};
5
6#[derive(Debug, Deserialize)]
7pub struct PostgresConfig {
8 pub url: String,
9 pub max_connections: u32,
10 pub min_connections: u32,
11 pub acquire_timeout: u64,
12 pub idle_timeout: u64,
13 pub max_lifetime: u64,
14}
15
16static PG_POOL: OnceLock<PgPool> = OnceLock::new();
17static PG_TIMEZONE: OnceLock<String> = OnceLock::new();
18
19pub async fn init(config: &PostgresConfig) -> Result<()> {
20 let tz = iana_time_zone::get_timezone().unwrap_or_else(|_| "UTC".to_string());
21 PG_TIMEZONE
22 .set(tz)
23 .map_err(|_| anyhow::anyhow!("Failed to set PG_TIMEZONE"))?;
24 let pool = PgPoolOptions::new()
25 .after_connect(|conn, _meta| {
26 Box::pin(async move {
27 conn.execute(format!("SET TIME ZONE '{}';", pg_session_timezone()).as_str())
28 .await?;
29 Ok(())
30 })
31 })
32 .max_connections(config.max_connections)
33 .min_connections(config.min_connections)
34 .acquire_timeout(Duration::from_secs(config.acquire_timeout))
35 .idle_timeout(Some(Duration::from_secs(config.idle_timeout)))
36 .max_lifetime(Some(Duration::from_secs(config.max_lifetime)))
37 .connect(config.url.as_str())
38 .await?;
39 PG_POOL
40 .set(pool)
41 .map_err(|_| anyhow::anyhow!("Failed to set OnceLock<PgPool>"))
42}
43
44pub fn conn() -> &'static PgPool {
45 PG_POOL.get().expect("OnceLock<PgPool> not initialized")
46}
47
48pub fn pg_session_timezone() -> &'static str {
49 PG_TIMEZONE.get().expect("PG_TIMEZONE not initialized")
50}