use std::sync::OnceLock;
use sqlx::{MySql, Pool, Postgres, Sqlite, sqlite::SqlitePoolOptions};
pub mod error;
pub mod repository;
mod schema;
mod utils;
pub use bindizr_core::model;
pub(crate) use bindizr_core::{config, log_error, log_info};
static DATABASE_POOL: OnceLock<DatabasePool> = OnceLock::new();
static INITIALIZE_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
#[derive(Debug)]
pub enum DatabasePool {
MySQL(Pool<MySql>),
PostgreSQL(Pool<Postgres>),
SQLite(Pool<Sqlite>),
}
#[derive(Debug, Clone)]
pub enum DatabaseType {
MySQL,
PostgreSQL,
SQLite,
}
pub async fn initialize() {
if is_initialized() {
return;
}
let _guard = INITIALIZE_LOCK.lock().await;
if is_initialized() {
return;
}
let bindizr_config = config::get_bindizr_config();
let database_type = match bindizr_config.database.database_type {
config::DatabaseType::Mysql => DatabaseType::MySQL,
config::DatabaseType::Postgresql => DatabaseType::PostgreSQL,
config::DatabaseType::Sqlite => DatabaseType::SQLite,
};
let database_url = match database_type {
DatabaseType::MySQL => bindizr_config.database.mysql.server_url.clone(),
DatabaseType::PostgreSQL => bindizr_config.database.postgresql.server_url.clone(),
DatabaseType::SQLite => utils::to_sqlite_url(&bindizr_config.database.sqlite.file_path)
.unwrap_or_else(|e| {
log_error!("{}", e);
std::process::exit(1);
}),
};
let pool = match database_type {
DatabaseType::MySQL => DatabasePool::new_mysql(&database_url).await,
DatabaseType::PostgreSQL => DatabasePool::new_postgres(&database_url).await,
DatabaseType::SQLite => DatabasePool::new_sqlite(&database_url).await,
};
if DATABASE_POOL.set(pool).is_err() {
return;
}
log_info!("Database pool initialized");
}
fn is_initialized() -> bool {
DATABASE_POOL.get().is_some()
}
pub fn get_pool() -> &'static DatabasePool {
DATABASE_POOL.get().expect("Database pool not initialized")
}
impl DatabasePool {
pub async fn new_mysql(url: &str) -> Self {
let pool = Pool::<MySql>::connect(url).await.unwrap_or_else(|e| {
log_error!("Failed to create MySQL database pool: {}", e);
std::process::exit(1);
});
let database_pool = DatabasePool::MySQL(pool);
if let Err(e) = database_pool.create_tables().await {
log_error!("Failed to create tables: {}", e);
std::process::exit(1);
}
database_pool
}
pub async fn new_postgres(url: &str) -> Self {
let pool = Pool::<Postgres>::connect(url).await.unwrap_or_else(|e| {
log_error!("Failed to create PostgreSQL database pool: {}", e);
std::process::exit(1);
});
let database_pool = DatabasePool::PostgreSQL(pool);
if let Err(e) = database_pool.create_tables().await {
log_error!("Failed to create tables: {}", e);
std::process::exit(1);
}
database_pool
}
pub async fn new_sqlite(url: &str) -> Self {
let pool = SqlitePoolOptions::new()
.after_connect(|conn, _meta| {
Box::pin(async move {
sqlx::query("PRAGMA foreign_keys = ON")
.execute(conn)
.await
.map(|_| ())
})
})
.connect(url)
.await
.unwrap_or_else(|e| {
log_error!("Failed to create SQLite database pool: {}", e);
std::process::exit(1);
});
let database_pool = DatabasePool::SQLite(pool);
if let Err(e) = database_pool.create_tables().await {
log_error!("Failed to create tables: {}", e);
std::process::exit(1);
}
database_pool
}
async fn create_tables(&self) -> Result<(), String> {
let queries = match self {
DatabasePool::MySQL(_) => schema::get_mysql_table_creation_queries(),
DatabasePool::PostgreSQL(_) => schema::get_postgres_table_creation_queries(),
DatabasePool::SQLite(_) => schema::get_sqlite_table_creation_queries(),
};
match self {
DatabasePool::MySQL(pool) => {
for query in queries {
let mut conn = pool.acquire().await.map_err(|e| {
log_error!("Failed to acquire MySQL connection: {}", e);
e.to_string()
})?;
sqlx::query(query).execute(&mut *conn).await.map_err(|e| {
log_error!("Failed to execute query '{}': {}", query, e);
e.to_string()
})?;
}
}
DatabasePool::PostgreSQL(pool) => {
for query in queries {
let mut conn = pool.acquire().await.map_err(|e| {
log_error!("Failed to acquire PostgreSQL connection: {}", e);
e.to_string()
})?;
sqlx::query(query).execute(&mut *conn).await.map_err(|e| {
log_error!("Failed to execute query '{}': {}", query, e);
e.to_string()
})?;
}
}
DatabasePool::SQLite(pool) => {
for query in queries {
let mut conn = pool.acquire().await.map_err(|e| {
log_error!("Failed to acquire SQLite connection: {}", e);
e.to_string()
})?;
sqlx::query(query).execute(&mut *conn).await.map_err(|e| {
log_error!("Failed to execute query '{}': {}", query, e);
e.to_string()
})?;
}
}
}
Ok(())
}
}
pub fn get_zone_repository() -> Box<dyn repository::ZoneRepository> {
let pool = get_pool();
repository::RepositoryFactory::create_zone_repository(pool)
}
pub fn get_record_repository() -> Box<dyn repository::RecordRepository> {
let pool = get_pool();
repository::RepositoryFactory::create_record_repository(pool)
}
pub fn get_api_token_repository() -> Box<dyn repository::ApiTokenRepository> {
let pool = get_pool();
repository::RepositoryFactory::create_api_token_repository(pool)
}
pub fn get_zone_change_repository() -> Box<dyn repository::ZoneChangeRepository> {
let pool = get_pool();
repository::RepositoryFactory::create_zone_change_repository(pool)
}
pub fn get_zone_snapshot_repository() -> Box<dyn repository::ZoneSnapshotRepository> {
let pool = get_pool();
repository::RepositoryFactory::create_zone_snapshot_repository(pool)
}
pub fn get_catalog_zone_state_repository() -> Box<dyn repository::CatalogZoneStateRepository> {
let pool = get_pool();
repository::RepositoryFactory::create_catalog_zone_state_repository(pool)
}