use crate::config::{DatabaseBackend, SqlxConfig, SslMode};
use crate::error::{SqlxError, SqlxResult};
#[derive(Clone)]
pub enum SqlxPool {
#[cfg(feature = "postgres")]
Postgres(sqlx::PgPool),
#[cfg(feature = "mysql")]
MySql(sqlx::MySqlPool),
#[cfg(feature = "sqlite")]
Sqlite(sqlx::SqlitePool),
}
#[cfg(feature = "postgres")]
fn pg_ssl_mode(mode: SslMode) -> sqlx::postgres::PgSslMode {
use sqlx::postgres::PgSslMode;
match mode {
SslMode::Disable => PgSslMode::Disable,
SslMode::Prefer => PgSslMode::Prefer,
SslMode::Require => PgSslMode::Require,
SslMode::VerifyCa => PgSslMode::VerifyCa,
SslMode::VerifyFull => PgSslMode::VerifyFull,
}
}
#[cfg(feature = "mysql")]
fn mysql_ssl_mode(mode: SslMode) -> sqlx::mysql::MySqlSslMode {
use sqlx::mysql::MySqlSslMode;
match mode {
SslMode::Disable => MySqlSslMode::Disabled,
SslMode::Prefer => MySqlSslMode::Preferred,
SslMode::Require => MySqlSslMode::Required,
SslMode::VerifyCa => MySqlSslMode::VerifyCa,
SslMode::VerifyFull => MySqlSslMode::VerifyIdentity,
}
}
impl SqlxPool {
pub async fn connect(config: &SqlxConfig) -> SqlxResult<Self> {
match config.backend {
#[cfg(feature = "postgres")]
DatabaseBackend::Postgres => {
use std::str::FromStr;
use sqlx::postgres::PgConnectOptions;
let mut options = PgConnectOptions::from_str(&config.url)?
.statement_cache_capacity(config.statement_cache_capacity)
.ssl_mode(pg_ssl_mode(config.ssl_mode));
if let Some(name) = &config.application_name {
options = options.application_name(name);
}
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(config.max_connections)
.min_connections(config.min_connections)
.acquire_timeout(config.connect_timeout)
.idle_timeout(config.idle_timeout)
.max_lifetime(config.max_lifetime)
.connect_with(options)
.await?;
Ok(Self::Postgres(pool))
}
#[cfg(feature = "mysql")]
DatabaseBackend::MySql => {
use std::str::FromStr;
use sqlx::mysql::MySqlConnectOptions;
let options = MySqlConnectOptions::from_str(&config.url)?
.statement_cache_capacity(config.statement_cache_capacity)
.ssl_mode(mysql_ssl_mode(config.ssl_mode));
if config.application_name.is_some() {
tracing::warn!(
"application_name is not supported by the sqlx MySQL backend; ignoring"
);
}
let pool = sqlx::mysql::MySqlPoolOptions::new()
.max_connections(config.max_connections)
.min_connections(config.min_connections)
.acquire_timeout(config.connect_timeout)
.idle_timeout(config.idle_timeout)
.max_lifetime(config.max_lifetime)
.connect_with(options)
.await?;
Ok(Self::MySql(pool))
}
#[cfg(feature = "sqlite")]
DatabaseBackend::Sqlite => {
use std::str::FromStr;
use sqlx::sqlite::SqliteConnectOptions;
let options = SqliteConnectOptions::from_str(&config.url)?
.statement_cache_capacity(config.statement_cache_capacity);
if config.ssl_mode != SslMode::default() {
tracing::warn!("ssl_mode is not applicable to SQLite connections; ignoring");
}
if config.application_name.is_some() {
tracing::warn!(
"application_name is not supported by the sqlx SQLite backend; ignoring"
);
}
let pool = sqlx::sqlite::SqlitePoolOptions::new()
.max_connections(config.max_connections)
.min_connections(config.min_connections)
.acquire_timeout(config.connect_timeout)
.idle_timeout(config.idle_timeout)
.max_lifetime(config.max_lifetime)
.connect_with(options)
.await?;
Ok(Self::Sqlite(pool))
}
#[allow(unreachable_patterns)]
_ => Err(SqlxError::config(format!(
"Database backend {:?} not enabled. Enable the corresponding feature.",
config.backend
))),
}
}
pub fn backend(&self) -> DatabaseBackend {
match self {
#[cfg(feature = "postgres")]
Self::Postgres(_) => DatabaseBackend::Postgres,
#[cfg(feature = "mysql")]
Self::MySql(_) => DatabaseBackend::MySql,
#[cfg(feature = "sqlite")]
Self::Sqlite(_) => DatabaseBackend::Sqlite,
}
}
pub async fn close(&self) {
match self {
#[cfg(feature = "postgres")]
Self::Postgres(pool) => pool.close().await,
#[cfg(feature = "mysql")]
Self::MySql(pool) => pool.close().await,
#[cfg(feature = "sqlite")]
Self::Sqlite(pool) => pool.close().await,
}
}
pub fn is_closed(&self) -> bool {
match self {
#[cfg(feature = "postgres")]
Self::Postgres(pool) => pool.is_closed(),
#[cfg(feature = "mysql")]
Self::MySql(pool) => pool.is_closed(),
#[cfg(feature = "sqlite")]
Self::Sqlite(pool) => pool.is_closed(),
}
}
pub fn size(&self) -> u32 {
match self {
#[cfg(feature = "postgres")]
Self::Postgres(pool) => pool.size(),
#[cfg(feature = "mysql")]
Self::MySql(pool) => pool.size(),
#[cfg(feature = "sqlite")]
Self::Sqlite(pool) => pool.size(),
}
}
pub fn num_idle(&self) -> usize {
match self {
#[cfg(feature = "postgres")]
Self::Postgres(pool) => pool.num_idle(),
#[cfg(feature = "mysql")]
Self::MySql(pool) => pool.num_idle(),
#[cfg(feature = "sqlite")]
Self::Sqlite(pool) => pool.num_idle(),
}
}
#[cfg(feature = "postgres")]
pub fn as_postgres(&self) -> Option<&sqlx::PgPool> {
match self {
Self::Postgres(pool) => Some(pool),
#[allow(unreachable_patterns)]
_ => None,
}
}
#[cfg(feature = "mysql")]
pub fn as_mysql(&self) -> Option<&sqlx::MySqlPool> {
match self {
Self::MySql(pool) => Some(pool),
#[allow(unreachable_patterns)]
_ => None,
}
}
#[cfg(feature = "sqlite")]
pub fn as_sqlite(&self) -> Option<&sqlx::SqlitePool> {
match self {
Self::Sqlite(pool) => Some(pool),
#[allow(unreachable_patterns)]
_ => None,
}
}
}
pub struct SqlxPoolBuilder {
config: SqlxConfig,
}
impl SqlxPoolBuilder {
pub fn new(config: SqlxConfig) -> Self {
Self { config }
}
pub fn from_url(url: impl Into<String>) -> SqlxResult<Self> {
let config = SqlxConfig::from_url(url)?;
Ok(Self { config })
}
pub fn max_connections(mut self, max: u32) -> Self {
self.config.max_connections = max;
self
}
pub fn min_connections(mut self, min: u32) -> Self {
self.config.min_connections = min;
self
}
pub async fn build(self) -> SqlxResult<SqlxPool> {
SqlxPool::connect(&self.config).await
}
}
#[derive(Debug, Clone)]
pub struct PoolStatus {
pub size: u32,
pub idle: usize,
pub is_closed: bool,
pub backend: DatabaseBackend,
}
impl SqlxPool {
pub fn status(&self) -> PoolStatus {
PoolStatus {
size: self.size(),
idle: self.num_idle(),
is_closed: self.is_closed(),
backend: self.backend(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_builder() {
let builder = SqlxPoolBuilder::from_url("postgres://localhost/test").unwrap();
let builder = builder.max_connections(20).min_connections(5);
assert_eq!(builder.config.max_connections, 20);
assert_eq!(builder.config.min_connections, 5);
}
#[cfg(feature = "postgres")]
#[test]
fn test_pg_ssl_mode_mapping() {
use sqlx::postgres::PgSslMode;
assert!(matches!(pg_ssl_mode(SslMode::Disable), PgSslMode::Disable));
assert!(matches!(pg_ssl_mode(SslMode::Prefer), PgSslMode::Prefer));
assert!(matches!(pg_ssl_mode(SslMode::Require), PgSslMode::Require));
assert!(matches!(
pg_ssl_mode(SslMode::VerifyCa),
PgSslMode::VerifyCa
));
assert!(matches!(
pg_ssl_mode(SslMode::VerifyFull),
PgSslMode::VerifyFull
));
}
#[cfg(feature = "mysql")]
#[test]
fn test_mysql_ssl_mode_mapping() {
use sqlx::mysql::MySqlSslMode;
assert!(matches!(
mysql_ssl_mode(SslMode::Disable),
MySqlSslMode::Disabled
));
assert!(matches!(
mysql_ssl_mode(SslMode::Prefer),
MySqlSslMode::Preferred
));
assert!(matches!(
mysql_ssl_mode(SslMode::Require),
MySqlSslMode::Required
));
assert!(matches!(
mysql_ssl_mode(SslMode::VerifyCa),
MySqlSslMode::VerifyCa
));
assert!(matches!(
mysql_ssl_mode(SslMode::VerifyFull),
MySqlSslMode::VerifyIdentity
));
}
}