minco_sqlx_postgres/
lib.rs1#![forbid(unsafe_code)]
3
4use serde::{Deserialize, Serialize};
5pub use sqlx::PgPool;
6use sqlx::postgres::PgPoolOptions;
7use std::{path::Path, time::Duration};
8use thiserror::Error;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct PostgresPoolConfig {
12 pub url: String,
13 pub max_connections: u32,
14 pub acquire_timeout_seconds: u64,
15 pub idle_timeout_seconds: u64,
16}
17
18impl PostgresPoolConfig {
19 pub fn serverless(url: impl Into<String>) -> Self {
20 Self {
21 url: url.into(),
22 max_connections: 2,
23 acquire_timeout_seconds: 5,
24 idle_timeout_seconds: 60,
25 }
26 }
27 pub fn validate(&self) -> Result<(), PostgresError> {
28 if self.url.trim().is_empty() {
29 return Err(PostgresError::InvalidConfig("database URL is empty".into()));
30 }
31 if self.max_connections == 0 {
32 return Err(PostgresError::InvalidConfig(
33 "max_connections must be at least 1".into(),
34 ));
35 }
36 Ok(())
37 }
38}
39
40pub async fn connect(config: &PostgresPoolConfig) -> Result<PgPool, PostgresError> {
41 config.validate()?;
42 Ok(PgPoolOptions::new()
43 .min_connections(0)
44 .max_connections(config.max_connections)
45 .acquire_timeout(Duration::from_secs(config.acquire_timeout_seconds))
46 .idle_timeout(Some(Duration::from_secs(config.idle_timeout_seconds)))
47 .connect(&config.url)
48 .await?)
49}
50
51pub fn connect_lazy(config: &PostgresPoolConfig) -> Result<PgPool, PostgresError> {
52 config.validate()?;
53 Ok(PgPoolOptions::new()
54 .min_connections(0)
55 .max_connections(config.max_connections)
56 .acquire_timeout(Duration::from_secs(config.acquire_timeout_seconds))
57 .idle_timeout(Some(Duration::from_secs(config.idle_timeout_seconds)))
58 .connect_lazy(&config.url)?)
59}
60
61pub async fn migrate(pool: &PgPool, path: impl AsRef<Path>) -> Result<(), PostgresError> {
62 let migrator = sqlx::migrate::Migrator::new(path.as_ref()).await?;
63 migrator.run(pool).await?;
64 Ok(())
65}
66
67pub async fn ready(pool: &PgPool) -> bool {
68 matches!(
69 sqlx::query_scalar::<_, i32>("SELECT 1")
70 .fetch_one(pool)
71 .await,
72 Ok(1)
73 )
74}
75
76#[derive(Debug, Error)]
77pub enum PostgresError {
78 #[error("invalid PostgreSQL configuration: {0}")]
79 InvalidConfig(String),
80 #[error("PostgreSQL error: {0}")]
81 Sqlx(#[from] sqlx::Error),
82 #[error("PostgreSQL migration error: {0}")]
83 Migration(#[from] sqlx::migrate::MigrateError),
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 #[test]
90 fn serverless_defaults_bound_connection_pressure() {
91 let config = PostgresPoolConfig::serverless("postgres://example.invalid/db");
92 assert_eq!(config.max_connections, 2);
93 assert_eq!(config.acquire_timeout_seconds, 5);
94 }
95}