#[cfg(feature = "permission")]
use crate::access::permission::RolePolicy;
use async_trait::async_trait;
#[cfg(feature = "permission")]
use oxcache::Cache;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::time::Duration;
#[cfg(feature = "metrics")]
use std::time::Instant;
use tokio::sync::{Mutex as AsyncMutex, Notify, Semaphore};
#[cfg(feature = "pool-health-check")]
use tokio::time::interval;
use tokio::time::timeout;
use super::Session;
#[cfg(feature = "permission")]
use crate::access::permission::PermissionConfig;
use crate::foundation::config::{ConfigError, DbConfig};
use crate::foundation::error::{DbError, DbResult};
#[cfg(feature = "metrics")]
use crate::observability::metrics::MetricsCollector;
use sea_orm::ConnectionTrait;
pub type DatabaseConnection = sea_orm::DatabaseConnection;
#[derive(Clone)]
pub enum DbConnection {
SeaOrm(DatabaseConnection),
#[cfg(feature = "duckdb")]
DuckDb(crate::database::pool::DuckDbConnection),
}
impl DbConnection {
pub fn as_sea_orm(&self) -> DbResult<&DatabaseConnection> {
match self {
DbConnection::SeaOrm(conn) => Ok(conn),
#[cfg(feature = "duckdb")]
DbConnection::DuckDb(_) => Err(DbError::Connection(sea_orm::DbErr::Custom(
"Operation requires SeaORM connection but got DuckDb".to_string(),
))),
}
}
#[cfg(feature = "duckdb")]
pub fn as_duckdb(&self) -> DbResult<&crate::database::pool::DuckDbConnection> {
match self {
DbConnection::DuckDb(conn) => Ok(conn),
DbConnection::SeaOrm(_) => Err(DbError::Connection(sea_orm::DbErr::Custom(
"Operation requires DuckDb connection but got SeaOrm".to_string(),
))),
}
}
pub fn is_duckdb(&self) -> bool {
#[cfg(feature = "duckdb")]
{
matches!(self, DbConnection::DuckDb(_))
}
#[cfg(not(feature = "duckdb"))]
{
false
}
}
}
impl std::fmt::Debug for DbConnection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DbConnection::SeaOrm(_) => write!(f, "DbConnection::SeaOrm(..)"),
#[cfg(feature = "duckdb")]
DbConnection::DuckDb(conn) => write!(f, "DbConnection::DuckDb({conn:?})"),
}
}
}
#[derive(Clone)]
pub struct DbPool {
inner: Arc<DbPoolInner>,
}
pub(crate) struct DbPoolInner {
pub(crate) config: DbConfig,
connection_semaphore: Arc<Semaphore>,
idle_connections: AsyncMutex<Vec<DbConnection>>,
connection_available: Notify,
pub(super) active_count: AtomicU32,
pub(super) total_count: AtomicU32,
#[cfg(feature = "permission")]
pub(crate) policy_cache: Arc<Cache<String, RolePolicy>>,
#[cfg(feature = "permission")]
permission_config: Arc<AsyncMutex<Option<PermissionConfig>>>,
health_check_shutdown: Arc<Notify>,
pub(super) admin_role: String,
#[cfg(feature = "metrics")]
pub(crate) metrics_collector: Option<Arc<MetricsCollector>>,
pub(super) wait_count: AtomicU32,
pub(super) max_waiters: AtomicU32,
pub(super) borrow_count: AtomicU64,
pub(super) max_active: AtomicU32,
}
impl DbPool {
fn update_max_active(&self, active: u32) {
let mut current = self.inner.max_active.load(Ordering::Acquire);
while active > current {
match self
.inner
.max_active
.compare_exchange(current, active, Ordering::SeqCst, Ordering::Acquire)
{
Ok(_) => return,
Err(observed) => {
current = observed;
}
}
}
}
pub async fn new(url: &str) -> DbResult<Self> {
let config = DbConfig {
url: url.to_string(),
..Default::default()
};
Self::with_config(config).await
}
pub async fn with_config(config: DbConfig) -> DbResult<Self> {
let _connection = Self::create_connection(&config).await?;
#[cfg(feature = "permission")]
let (policy_cache, permission_config) = Self::setup_permission_cache(&config).await?;
let pool = Self {
inner: Arc::new(DbPoolInner {
config: config.clone(),
connection_semaphore: Arc::new(Semaphore::new(config.max_connections as usize)),
idle_connections: AsyncMutex::new(Vec::new()),
connection_available: Notify::new(),
active_count: AtomicU32::new(0),
total_count: AtomicU32::new(0),
#[cfg(feature = "permission")]
policy_cache,
#[cfg(feature = "permission")]
permission_config: Arc::new(AsyncMutex::new(permission_config)),
health_check_shutdown: Arc::new(Notify::new()),
admin_role: config.admin_role.clone(),
#[cfg(feature = "metrics")]
metrics_collector: None,
wait_count: AtomicU32::new(0),
max_waiters: AtomicU32::new(0),
borrow_count: AtomicU64::new(0),
max_active: AtomicU32::new(0),
}),
};
#[cfg(feature = "pool-health-check")]
pool.start_background_health_check();
#[cfg(feature = "pool-warmup")]
pool.warmup_connections().await?;
#[cfg(feature = "auto-migrate")]
if config.auto_migrate {
if let Some(ref migrations_dir) = config.migrations_dir {
if migrations_dir.exists() {
let _applied = pool.run_migrations(migrations_dir).await?;
} else {
}
}
}
Ok(pool)
}
#[cfg_attr(
feature = "sqlite",
doc = r###"
/// ```rust
/// use dbnexus::DbPool;
/// use dbnexus::config::DbConfig;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let config = DbConfig {
/// url: "sqlite::memory:".to_string(),
/// max_connections: 10,
/// min_connections: 2,
/// ..Default::default()
/// };
///
/// let pool = DbPool::try_from_config(config).await?;
/// Ok(())
/// }
/// ```
"###
)]
#[cfg_attr(
not(feature = "sqlite"),
doc = r###"
/// ```rust,ignore
/// // 此文档测试需要 sqlite 特性
/// // 在使用其他数据库时,请参考相应的文档和示例
/// ```
"###
)]
pub async fn try_from_config(config: DbConfig) -> DbResult<Self> {
Self::with_config(config).await
}
#[cfg(not(feature = "permission"))]
pub fn try_from(config: &DbConfig) -> Result<Self, ConfigError> {
Ok(Self {
inner: Arc::new(DbPoolInner {
config: config.clone(),
connection_semaphore: Arc::new(Semaphore::new(config.max_connections as usize)),
idle_connections: AsyncMutex::new(Vec::new()),
connection_available: Notify::new(),
active_count: AtomicU32::new(0),
total_count: AtomicU32::new(0),
health_check_shutdown: Arc::new(Notify::new()),
admin_role: config.admin_role.clone(),
#[cfg(feature = "metrics")]
metrics_collector: None,
wait_count: AtomicU32::new(0),
max_waiters: AtomicU32::new(0),
borrow_count: AtomicU64::new(0),
max_active: AtomicU32::new(0),
}),
})
}
#[cfg(feature = "permission")]
pub fn try_from(config: &DbConfig) -> Result<Self, ConfigError> {
let _ = config;
Err(ConfigError::InvalidValue {
key: "permission".to_string(),
message: "DbPool::try_from cannot be used with `permission` feature enabled; \
use `DbPool::with_config(config).await` instead (async constructor required for cache initialization)"
.to_string(),
})
}
#[cfg(feature = "permission")]
async fn setup_permission_cache(
config: &DbConfig,
) -> DbResult<(Arc<Cache<String, RolePolicy>>, Option<PermissionConfig>)> {
let policy_cache = Arc::new(
Cache::builder()
.capacity(config.cache_config.policy_cache_capacity)
.build()
.await
.map_err(|_e| {
DbError::Connection(sea_orm::DbErr::ConnectionAcquire(sea_orm::ConnAcquireErr::Timeout))
})?,
);
let permission_config = Self::load_permission_config(config).await;
if let Some(ref perm_config) = permission_config {
for (role_name, policy) in &perm_config.roles {
let _ = policy_cache.set(role_name, policy).await;
}
}
Ok((policy_cache, permission_config))
}
#[cfg(feature = "permission")]
async fn load_permission_config(config: &DbConfig) -> Option<PermissionConfig> {
if let Some(ref path) = config.permissions_path {
match tokio::fs::read_to_string(path).await {
Ok(content) => match Self::parse_permission_yaml(&content, path) {
Ok(perm_config) => {
return Some(perm_config);
}
Err(_e) => {
return None;
}
},
Err(_e) => {
return None;
}
}
}
None
}
#[cfg(feature = "json")]
fn parse_permission_json(content: &str, source: &str) -> Result<PermissionConfig, String> {
serde_json::from_str(content).map_err(|e| format!("JSON parse error in '{}': {}", source, e))
}
fn parse_permission_yaml(content: &str, source: &str) -> Result<PermissionConfig, String> {
#[cfg(feature = "json")]
{
Self::parse_permission_json(content, source)
}
#[cfg(not(feature = "json"))]
{
#[cfg(feature = "yaml")]
{
serde_yaml_ng::from_str(content).map_err(|e| format!("YAML parse error in '{}': {}", source, e))
}
#[cfg(not(feature = "yaml"))]
{
Err(format!(
"Cannot parse permission config from '{}': neither JSON nor YAML support available",
source
))
}
}
}
#[cfg(feature = "metrics")]
pub fn metrics(&self) -> Option<&Arc<MetricsCollector>> {
self.inner.metrics_collector.as_ref()
}
pub fn get_actual_config(&self) -> &DbConfig {
&self.inner.config
}
pub async fn get_session(&self, role: &str) -> DbResult<Session> {
#[cfg(feature = "permission")]
self.validate_role_name(role).await?;
let connection = self.acquire_connection().await?;
let pool_ref = Arc::new(self.clone());
let session = Session::new(connection, pool_ref, self.inner.clone(), role.to_string());
Ok(session)
}
#[cfg(feature = "permission")]
async fn validate_role_name(&self, role: &str) -> DbResult<()> {
let permission_config = self.inner.permission_config.lock().await;
if permission_config.is_none() {
#[cfg(feature = "tracing")]
tracing::warn!(
role = %role,
"dbnexus::pool: no permission config loaded, using safe default policy (admin/system only)"
);
#[cfg(not(feature = "tracing"))]
eprintln!(
"[warn] dbnexus::pool: no permission config loaded, using safe default policy (admin/system only) for role '{}'",
role
);
let safe_roles = ["admin", "system"];
if !safe_roles.contains(&role) {
return Err(DbError::Permission(format!(
"Role '{}' is not allowed without explicit permission configuration. Allowed roles: {}",
role,
safe_roles.join(", ")
)));
}
return Ok(());
}
if permission_config
.as_ref()
.is_some_and(|c| c.get_role_policy(role).is_none())
{
return Err(DbError::Permission(format!(
"Role '{}' is not defined in permission configuration",
role
)));
}
Ok(())
}
async fn create_connection(config: &DbConfig) -> DbResult<DbConnection> {
let db_type = config
.database_type()
.map_err(|e| DbError::Connection(sea_orm::DbErr::Custom(format!("Invalid database URL: {e}"))))?;
match db_type {
crate::foundation::config::DatabaseType::DuckDb => {
#[cfg(feature = "duckdb")]
{
let conn = crate::database::pool::DuckDbConnection::new(&config.url)?;
Ok(DbConnection::DuckDb(conn))
}
#[cfg(not(feature = "duckdb"))]
{
Err(DbError::Connection(sea_orm::DbErr::Custom(
"DuckDB feature is not enabled".to_string(),
)))
}
}
_ => {
let conn = sea_orm::Database::connect(&config.url).await?;
Ok(DbConnection::SeaOrm(conn))
}
}
}
#[cfg(feature = "pool-warmup")]
async fn warmup_connections(&self) -> DbResult<()> {
let initial_connections = self.inner.config.min_connections;
let warmup_timeout = Duration::from_secs(self.inner.config.warmup_timeout);
let warmup_retries = self.inner.config.warmup_retries;
let mut connection_tasks = Vec::new();
for _ in 0..initial_connections {
let config = self.inner.config.clone();
connection_tasks.push(async move {
let mut retries = 0;
let mut last_error = None;
while retries <= warmup_retries {
match timeout(warmup_timeout, Self::create_connection(&config)).await {
Ok(Ok(conn)) => return Ok(conn),
Ok(Err(e)) => {
last_error = Some(e);
retries += 1;
if retries <= warmup_retries {
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
Err(_) => {
last_error = Some(DbError::Connection(sea_orm::DbErr::ConnectionAcquire(
sea_orm::ConnAcquireErr::Timeout,
)));
break;
}
}
}
Err(last_error.unwrap_or_else(|| {
DbError::Connection(sea_orm::DbErr::ConnectionAcquire(sea_orm::ConnAcquireErr::Timeout))
}))
});
}
let results = futures::future::join_all(connection_tasks).await;
let mut success_count = 0usize;
let mut errors: Vec<DbError> = Vec::new();
for result in results {
match result {
Ok(conn) => {
self.inner.idle_connections.lock().await.push(conn);
self.inner.total_count.fetch_add(1, Ordering::SeqCst);
success_count += 1;
}
Err(e) => errors.push(e),
}
}
if success_count == 0 && initial_connections > 0 {
return Err(errors.into_iter().next().unwrap_or_else(|| {
DbError::Connection(sea_orm::DbErr::ConnectionAcquire(sea_orm::ConnAcquireErr::Timeout))
}));
}
if !errors.is_empty() {
#[cfg(feature = "tracing")]
tracing::warn!(
success = success_count,
total = initial_connections,
errors = errors.len(),
"dbnexus::pool::warmup: warmup_connections partially failed"
);
#[cfg(not(feature = "tracing"))]
eprintln!(
"[warn] dbnexus::pool::warmup: warmup_connections partially failed: {}/{} connections created, {} errors",
success_count,
initial_connections,
errors.len()
);
}
Ok(())
}
pub async fn check_connection_health(&self, conn: &DbConnection) -> bool {
match conn {
DbConnection::SeaOrm(sea_conn) => {
let backend = Self::get_database_backend(&self.inner.config.url);
let result = timeout(
Duration::from_secs(5),
sea_conn.execute_raw(sea_orm::Statement::from_string(backend, "SELECT 1".to_string())),
)
.await;
matches!(result, Ok(Ok(_)))
}
#[cfg(feature = "duckdb")]
DbConnection::DuckDb(duck_conn) => {
let result = timeout(Duration::from_secs(5), duck_conn.health_check()).await;
matches!(result, Ok(Ok(_)))
}
}
}
fn get_database_backend(url: &str) -> sea_orm::DatabaseBackend {
if url.starts_with("sqlite:") {
sea_orm::DatabaseBackend::Sqlite
} else if url.starts_with("postgres:") || url.starts_with("postgresql:") {
sea_orm::DatabaseBackend::Postgres
} else if url.starts_with("mysql:") {
sea_orm::DatabaseBackend::MySql
} else if url.starts_with("duckdb:") {
sea_orm::DatabaseBackend::Sqlite
} else {
sea_orm::DatabaseBackend::Sqlite
}
}
#[cfg(feature = "pool-health-check")]
async fn validate_idle_connections(idle: &mut Vec<DbConnection>, config: &DbConfig) -> (Vec<DbConnection>, usize) {
let backend = Self::get_database_backend(&config.url);
let connections: Vec<DbConnection> = std::mem::take(idle);
let check_futures: Vec<_> = connections
.into_iter()
.map(|conn| async {
let is_valid = match &conn {
DbConnection::SeaOrm(sea_conn) => timeout(
Duration::from_secs(2),
sea_conn.execute_raw(sea_orm::Statement::from_string(backend, "SELECT 1".to_string())),
)
.await
.is_ok_and(|result| result.is_ok()),
#[cfg(feature = "duckdb")]
DbConnection::DuckDb(duck_conn) => timeout(Duration::from_secs(2), duck_conn.health_check())
.await
.is_ok_and(|result| result.is_ok()),
};
(conn, is_valid)
})
.collect();
let results: Vec<(DbConnection, bool)> = futures::future::join_all(check_futures).await;
let invalid_count = results.iter().filter(|(_, is_valid)| !*is_valid).count();
let valid_connections: Vec<DbConnection> = results
.into_iter()
.filter_map(|(conn, is_valid)| if is_valid { Some(conn) } else { None })
.collect();
(valid_connections, invalid_count)
}
#[cfg(feature = "pool-health-check")]
pub async fn clean_invalid_connections(&self) -> u32 {
let mut idle = self.inner.idle_connections.lock().await;
let config = &self.inner.config;
let (valid_connections, removed_count) = Self::validate_idle_connections(&mut idle, config).await;
idle.extend(valid_connections);
if removed_count > 0 {
self.inner.total_count.fetch_sub(removed_count as u32, Ordering::SeqCst);
}
removed_count as u32
}
#[cfg(feature = "pool-health-check")]
pub async fn validate_and_recreate_connections(&self) -> Result<u32, sea_orm::DbErr> {
let mut idle = self.inner.idle_connections.lock().await;
let config = &self.inner.config;
let (valid_connections, invalid_count) = Self::validate_idle_connections(&mut idle, config).await;
let mut recreated_count = 0;
if invalid_count > 0 {
self.inner.total_count.fetch_sub(invalid_count as u32, Ordering::SeqCst);
idle.extend(valid_connections);
let current_idle = idle.len();
let needed = config.min_connections.saturating_sub(current_idle as u32) as usize;
for _ in 0..needed {
match Self::create_connection(config).await {
Ok(new_conn) => {
idle.push(new_conn);
self.inner.total_count.fetch_add(1, Ordering::SeqCst);
recreated_count += 1;
}
Err(e) => {
return Err(sea_orm::DbErr::Custom(format!("Failed to recreate connections: {}", e)));
}
}
}
} else {
idle.extend(valid_connections);
}
Ok(recreated_count as u32)
}
#[cfg(feature = "pool-health-check")]
pub fn parse_health_check_interval(value: &str) -> u64 {
value.parse::<u64>().ok().map(|v| v.clamp(5, 300)).unwrap_or(30)
}
#[cfg(feature = "pool-health-check")]
fn start_background_health_check(&self) {
let pool = self.clone();
let shutdown = self.inner.health_check_shutdown.clone();
let env_value = std::env::var("DB_HEALTH_CHECK_INTERVAL").unwrap_or_default();
let interval_secs = Self::parse_health_check_interval(&env_value);
tokio::spawn(async move {
let mut interval = interval(Duration::from_secs(interval_secs));
loop {
tokio::select! {
_ = interval.tick() => {
let _ = pool.validate_and_recreate_connections().await;
}
_ = shutdown.notified() => {
break;
}
}
}
});
}
async fn acquire_connection(&self) -> DbResult<DbConnection> {
let waiters = self.inner.wait_count.fetch_add(1, Ordering::SeqCst) + 1;
self.update_max_waiters(waiters);
let timeout_duration = self.inner.config.acquire_timeout_duration();
#[cfg(feature = "metrics")]
let start = Instant::now();
let acquire_result = timeout(timeout_duration, self.inner.connection_semaphore.acquire()).await;
self.inner.wait_count.fetch_sub(1, Ordering::SeqCst);
let permit = match acquire_result {
Ok(Ok(p)) => {
#[cfg(feature = "metrics")]
if let Some(ref collector) = self.inner.metrics_collector {
collector.record_connection_acquire_duration(start.elapsed());
}
p
}
Ok(Err(_)) => {
return Err(DbError::Connection(sea_orm::DbErr::ConnectionAcquire(
sea_orm::ConnAcquireErr::Timeout,
)));
}
Err(_) => {
#[cfg(feature = "metrics")]
{
let elapsed_ms = start.elapsed().as_millis() as u64;
if let Some(ref collector) = self.inner.metrics_collector {
collector.record_connection_timeout_level(elapsed_ms);
}
}
return Err(DbError::Connection(sea_orm::DbErr::ConnectionAcquire(
sea_orm::ConnAcquireErr::Timeout,
)));
}
};
{
let mut idle = self.inner.idle_connections.lock().await;
if let Some(conn) = idle.pop() {
let active = self.inner.active_count.fetch_add(1, Ordering::SeqCst) + 1;
self.update_max_active(active);
self.inner.borrow_count.fetch_add(1, Ordering::SeqCst);
permit.forget();
return Ok(conn);
}
}
self.inner.total_count.fetch_add(1, Ordering::SeqCst);
let active = self.inner.active_count.fetch_add(1, Ordering::SeqCst) + 1;
self.update_max_active(active);
match Self::create_connection(&self.inner.config).await {
Ok(conn) => {
self.inner.borrow_count.fetch_add(1, Ordering::SeqCst);
permit.forget();
Ok(conn)
}
Err(e) => {
self.inner.total_count.fetch_sub(1, Ordering::SeqCst);
self.inner.active_count.fetch_sub(1, Ordering::SeqCst);
drop(permit);
Err(e)
}
}
}
fn update_max_waiters(&self, current_waiters: u32) {
let mut current = self.inner.max_waiters.load(Ordering::Acquire);
while current_waiters > current {
match self
.inner
.max_waiters
.compare_exchange(current, current_waiters, Ordering::SeqCst, Ordering::Acquire)
{
Ok(_) => return,
Err(observed) => {
current = observed;
}
}
}
}
pub(crate) fn release_connection(&self, conn: DbConnection) {
self.inner.active_count.fetch_sub(1, Ordering::SeqCst);
let inner = self.inner.clone();
if let Ok(mut idle) = inner.idle_connections.try_lock() {
if idle.len() < inner.config.max_connections as usize {
idle.push(conn);
inner.connection_available.notify_one();
inner.connection_semaphore.add_permits(1);
} else {
inner.total_count.fetch_sub(1, Ordering::SeqCst);
inner.connection_semaphore.add_permits(1);
}
return;
}
if tokio::runtime::Handle::try_current().is_ok() {
tokio::spawn(async move {
let mut idle = inner.idle_connections.lock().await;
if idle.len() < inner.config.max_connections as usize {
idle.push(conn);
inner.connection_available.notify_one();
} else {
inner.total_count.fetch_sub(1, Ordering::SeqCst);
}
inner.connection_semaphore.add_permits(1);
});
} else {
inner.total_count.fetch_sub(1, Ordering::SeqCst);
inner.connection_semaphore.add_permits(1);
}
}
pub fn status(&self) -> PoolStatus {
let total = self.inner.total_count.load(Ordering::SeqCst);
let active = self.inner.active_count.load(Ordering::SeqCst);
let wait_count = self.inner.wait_count.load(Ordering::SeqCst);
let max_waiters = self.inner.max_waiters.load(Ordering::SeqCst);
let borrow_count = self.inner.borrow_count.load(Ordering::SeqCst);
let max_active = self.inner.max_active.load(Ordering::SeqCst);
PoolStatus {
total,
active,
idle: total.saturating_sub(active),
wait_count,
max_waiters,
borrow_count,
max_active,
}
}
#[cfg(feature = "metrics")]
pub fn pool_metrics(&self) -> PoolMetrics {
let wait_count = self.inner.wait_count.load(Ordering::SeqCst);
let max_waiters = self.inner.max_waiters.load(Ordering::SeqCst);
if let Some(ref collector) = self.inner.metrics_collector {
let stats = collector.connection_acquire_stats();
PoolMetrics {
slow_acquires: stats.slow_acquires,
timeout_errors: stats.timeout_warn + stats.timeout_error + stats.timeout_critical,
critical_timeouts: stats.timeout_critical,
wait_count,
max_waiters,
}
} else {
PoolMetrics {
slow_acquires: 0,
timeout_errors: 0,
critical_timeouts: 0,
wait_count,
max_waiters,
}
}
}
pub fn config(&self) -> &DbConfig {
&self.inner.config
}
#[cfg(feature = "auto-migrate")]
pub async fn run_auto_migrate(&self) -> Result<u32, DbError> {
if let Some(ref migrations_dir) = self.inner.config.migrations_dir {
self.run_migrations(migrations_dir).await
} else {
Ok(0)
}
}
#[cfg(feature = "auto-migrate")]
pub async fn run_migrations(&self, migrations_dir: &std::path::Path) -> Result<u32, DbError> {
use crate::database::migration::MigrationExecutor;
let db_type = self
.inner
.config
.database_type()
.map_err(|e| DbError::Config(e.to_string()))?;
let connection = self.acquire_connection().await?;
let connection_for_migration = connection.as_sea_orm()?.clone();
let mut executor = MigrationExecutor::new(connection_for_migration, db_type);
let applied = executor.run_migrations(migrations_dir).await?;
self.release_connection(connection);
Ok(applied)
}
}
impl Drop for DbPool {
fn drop(&mut self) {
self.inner.health_check_shutdown.notify_one();
}
}
#[cfg(feature = "metrics")]
#[derive(Debug, Clone)]
pub struct PoolMetrics {
pub slow_acquires: u64,
pub timeout_errors: u64,
pub critical_timeouts: u64,
pub wait_count: u32,
pub max_waiters: u32,
}
#[derive(Debug, Clone)]
pub struct PoolStatus {
pub total: u32,
pub active: u32,
pub idle: u32,
pub wait_count: u32,
pub max_waiters: u32,
pub borrow_count: u64,
pub max_active: u32,
}
#[async_trait]
impl super::ConnectionPool for DbPool {
async fn get_session(&self, role: &str) -> DbResult<Session> {
self.get_session(role).await
}
fn status(&self) -> PoolStatus {
self.status()
}
fn config(&self) -> &DbConfig {
self.config()
}
}