use std::env;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use tracing::{debug, info};
use super::pool::DbPool;
type DbResult<T> = Result<T, sqlx::Error>;
fn is_marked_ready() -> bool {
matches!(
env::var("AGENTIC_API_SCHEMA_READY").as_deref(),
Ok("1" | "true" | "t" | "yes" | "y" | "on")
)
}
pub struct PoolWithSchema {
pool: Arc<DbPool>,
schema_ready: AtomicBool,
}
impl PoolWithSchema {
#[must_use]
pub fn new(pool: Arc<DbPool>) -> Self {
Self {
pool,
schema_ready: AtomicBool::new(false),
}
}
pub fn pool(&self) -> &Arc<DbPool> {
&self.pool
}
pub async fn ensure_schema_ready(&self) -> DbResult<()> {
if self.schema_ready.load(Ordering::SeqCst) {
return Ok(());
}
if is_marked_ready() {
debug!("[schema] DDL skipped — marked ready by supervisor.");
self.schema_ready.store(true, Ordering::SeqCst);
return Ok(());
}
debug!("[schema] Running migrations...");
sqlx::migrate!("./migrations")
.run(self.pool.as_ref())
.await
.map_err(|e| sqlx::Error::Configuration(e.to_string().into()))?;
info!("[schema] DB schema ready.");
self.schema_ready.store(true, Ordering::SeqCst);
Ok(())
}
}
pub struct SchemaManager<'a> {
pool: &'a DbPool,
}
impl<'a> SchemaManager<'a> {
#[must_use]
pub fn new(pool: &'a DbPool) -> Self {
Self { pool }
}
pub async fn run_migrations(&self) -> DbResult<()> {
debug!("[schema] Running migrations...");
sqlx::migrate!("./migrations")
.run(self.pool)
.await
.map_err(|e| sqlx::Error::Configuration(e.to_string().into()))?;
info!("[schema] DB schema ready.");
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_env_var_pattern() {
let test_values = vec![
("1", true),
("true", true),
("t", true),
("yes", true),
("y", true),
("on", true),
("0", false),
("false", false),
("f", false),
("no", false),
("n", false),
("off", false),
("", false),
];
for (val, expected) in test_values {
let matches = matches!(
Ok::<&str, String>(val).as_deref(),
Ok("1" | "true" | "t" | "yes" | "y" | "on")
);
assert_eq!(matches, expected, "Mismatch for value '{val}'");
}
}
#[tokio::test]
async fn test_pool_with_schema_ready() {
let pool = crate::storage::pool::create_pool(Some("sqlite://?mode=memory"))
.await
.expect("failed to create pool");
let pool_with_schema = PoolWithSchema::new(pool);
let result = pool_with_schema.ensure_schema_ready().await;
assert!(result.is_ok(), "ensure_schema_ready failed: {result:?}");
assert!(pool_with_schema.schema_ready.load(Ordering::SeqCst));
let result = pool_with_schema.ensure_schema_ready().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_multiple_pools_independent() {
let pool1 = crate::storage::pool::create_pool(Some("sqlite://?mode=memory"))
.await
.expect("failed to create pool1");
let pool2 = crate::storage::pool::create_pool(Some("sqlite://?mode=memory"))
.await
.expect("failed to create pool2");
let pwc1 = PoolWithSchema::new(pool1);
let pwc2 = PoolWithSchema::new(pool2);
pwc1.ensure_schema_ready().await.expect("pool1 failed");
pwc2.ensure_schema_ready().await.expect("pool2 failed");
assert!(pwc1.schema_ready.load(Ordering::SeqCst));
assert!(pwc2.schema_ready.load(Ordering::SeqCst));
pwc1.ensure_schema_ready().await.expect("pool1 repeat failed");
pwc2.ensure_schema_ready().await.expect("pool2 repeat failed");
}
}