use crate::config::PoolParams;
use ecat_data::Dialect;
use sqlx::mysql::{MySqlPool, MySqlPoolOptions};
use sqlx::postgres::{PgPool, PgPoolOptions};
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};
#[derive(Debug, Clone)]
pub enum Pool {
Pg(PgPool),
My(MySqlPool),
Sq(SqlitePool),
}
pub enum PoolGuard {
Pg(sqlx::pool::PoolConnection<sqlx::Postgres>),
My(sqlx::pool::PoolConnection<sqlx::MySql>),
Sq(sqlx::pool::PoolConnection<sqlx::Sqlite>),
}
impl Pool {
pub async fn connect(url: &str, params: &PoolParams) -> Result<Self, sqlx::Error> {
match Dialect::from_url(url) {
Dialect::Postgres => Ok(Self::Pg(
common_pg(PgPoolOptions::new(), params).connect(url).await?,
)),
Dialect::MySql => Ok(Self::My(
common_mysql(MySqlPoolOptions::new(), params)
.connect(url)
.await?,
)),
Dialect::Sqlite => Ok(Self::Sq(
common_sqlite(SqlitePoolOptions::new(), params)
.connect(url)
.await?,
)),
Dialect::Standard => Err(unsupported(url)),
Dialect::Mssql => Err(sqlx::Error::Configuration(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"mssql:// is served by ecat-data-mssql, not the sqlx backend",
)))),
}
}
pub fn dialect(&self) -> Dialect {
match self {
Self::Pg(_) => Dialect::Postgres,
Self::My(_) => Dialect::MySql,
Self::Sq(_) => Dialect::Sqlite,
}
}
pub fn size(&self) -> u32 {
match self {
Self::Pg(p) => p.size(),
Self::My(p) => p.size(),
Self::Sq(p) => p.size(),
}
}
pub fn idle(&self) -> u32 {
match self {
Self::Pg(p) => p.num_idle() as u32,
Self::My(p) => p.num_idle() as u32,
Self::Sq(p) => p.num_idle() as u32,
}
}
pub async fn acquire(&self) -> Result<PoolGuard, sqlx::Error> {
Ok(match self {
Self::Pg(p) => PoolGuard::Pg(p.acquire().await?),
Self::My(p) => PoolGuard::My(p.acquire().await?),
Self::Sq(p) => PoolGuard::Sq(p.acquire().await?),
})
}
}
fn unsupported(url: &str) -> sqlx::Error {
let scheme = url
.split("://")
.next()
.unwrap_or(url)
.split(':')
.next()
.unwrap_or(url);
sqlx::Error::Configuration(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("unsupported database scheme for sqlx backend: {scheme}"),
)))
}
fn common_pg(opts: PgPoolOptions, p: &PoolParams) -> PgPoolOptions {
let opts = opts
.max_connections(p.max_connections)
.min_connections(p.min_connections)
.acquire_timeout(p.acquire_timeout)
.idle_timeout(Some(p.idle_timeout))
.max_lifetime(Some(p.max_lifetime))
.test_before_acquire(p.test_before_acquire);
if p.session_init.is_empty() {
return opts;
}
let stmts = p.session_init.clone();
opts.after_connect(move |conn, _meta| {
let stmts = stmts.clone();
Box::pin(async move {
for s in &stmts {
sqlx::query(s).execute(&mut *conn).await?;
}
Ok(())
})
})
}
fn common_mysql(opts: MySqlPoolOptions, p: &PoolParams) -> MySqlPoolOptions {
let opts = opts
.max_connections(p.max_connections)
.min_connections(p.min_connections)
.acquire_timeout(p.acquire_timeout)
.idle_timeout(Some(p.idle_timeout))
.max_lifetime(Some(p.max_lifetime))
.test_before_acquire(p.test_before_acquire);
if p.session_init.is_empty() {
return opts;
}
let stmts = p.session_init.clone();
opts.after_connect(move |conn, _meta| {
let stmts = stmts.clone();
Box::pin(async move {
for s in &stmts {
sqlx::query(s).execute(&mut *conn).await?;
}
Ok(())
})
})
}
fn common_sqlite(opts: SqlitePoolOptions, p: &PoolParams) -> SqlitePoolOptions {
let opts = opts
.max_connections(p.max_connections)
.min_connections(p.min_connections)
.acquire_timeout(p.acquire_timeout)
.idle_timeout(Some(p.idle_timeout))
.max_lifetime(Some(p.max_lifetime))
.test_before_acquire(p.test_before_acquire);
if p.session_init.is_empty() {
return opts;
}
let stmts = p.session_init.clone();
opts.after_connect(move |conn, _meta| {
let stmts = stmts.clone();
Box::pin(async move {
for s in &stmts {
sqlx::query(s).execute(&mut *conn).await?;
}
Ok(())
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use ecat_data::Dialect;
#[tokio::test]
async fn sqlite_url_builds_sqlite_pool() {
let pool = Pool::connect("sqlite::memory:", &PoolParams::for_url("sqlite::memory:"))
.await
.unwrap();
assert_eq!(pool.dialect(), Dialect::Sqlite);
assert!(matches!(pool, Pool::Sq(_)));
}
#[test]
fn unsupported_scheme_is_rejected_and_names_the_scheme() {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let err = rt
.block_on(Pool::connect("oracle://h/db", &PoolParams::default()))
.unwrap_err();
let msg = err.to_string();
assert!(msg.contains("unsupported"), "got: {msg}");
assert!(
msg.contains("oracle"),
"错误信息必须点名 scheme,got: {msg}"
);
}
#[test]
fn mssql_scheme_is_rejected_by_sqlx_backend() {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let err = rt
.block_on(Pool::connect("mssql://h:1433/db", &PoolParams::default()))
.unwrap_err();
assert!(err.to_string().contains("ecat-data-mssql"), "got: {err}");
}
}