use std::{fmt, sync::OnceLock, time::Duration}; use std::fmt::Display; use chrono::{DateTime, Utc}; use redis::{aio::MultiplexedConnection as RedisConnection, Client as RedisClient, RedisResult}; use sqlx::{postgres::PgPoolOptions, Pool, Postgres}; use crate::error::SirenResult; pub mod condition; pub mod events; mod executable_query; pub mod guilds; pub mod insert; pub mod messages; pub mod query; pub mod update; pub use executable_query::ExecutableQuery; static POOL: OnceLock> = OnceLock::new(); static REDIS: OnceLock = OnceLock::new(); pub async fn initialize() -> SirenResult<()> { log::info!("Initializing database..."); let db_user = std::env::var("DATABASE_USER").unwrap_or("siren".to_string()); let db_password = std::env::var("DATABASE_PASSWORD").expect("DATABASE_PASSWORD must be set"); let db_host: String = std::env::var("DATABASE_HOST").expect("DATABASE_HOST must be set"); let db_port = std::env::var("DATABASE_PORT").unwrap_or("5432".to_string()); let db_name = std::env::var("DATABASE_NAME").unwrap_or("siren".to_string()); // Setup Postgres pool connection let pool = PgPoolOptions::new() .max_connections(5) .acquire_timeout(Duration::from_secs(30)) .connect(&format!( "postgres://{}:{}@{}:{}/{}", db_user, db_password, db_host, db_port, db_name )) .await?; match POOL.set(pool) { Ok(_) => {} Err(_) => { log::warn!("Database pool already initialized"); } } // Setup Redis connection let redis = { let host = std::env::var("REDIS_HOST").unwrap_or("localhost".to_string()); let port = std::env::var("REDIS_PORT").unwrap_or("6379".to_string()); let url = format!("redis://{}:{}", host, port); RedisClient::open(url).expect("Failed to create redis client") }; match REDIS.set(redis) { Ok(_) => {} Err(_) => { log::warn!("Redis client already initialized"); } } // Run migrations match run_migrations().await { Ok(_) => log::debug!("Successfully ran migrations"), Err(e) => log::error!("Failed to run migrations: {}", e), } log::info!("Database initialized"); Ok(()) } pub fn pool() -> &'static Pool { POOL.get().unwrap() } fn redis() -> &'static RedisClient { REDIS.get().unwrap() } pub fn redis_connection() -> RedisResult { let conn = redis().get_connection()?; Ok(conn) } pub async fn redis_async_connection() -> RedisResult { let conn = redis().get_multiplexed_async_connection().await?; Ok(conn) } async fn run_migrations() -> SirenResult<()> { log::debug!("Running migrations"); let pool = pool(); sqlx::migrate!().run(pool).await?; Ok(()) } #[derive(Debug, Clone)] pub enum Value { Int(i32), OptionalInt(Option), BigInt(i64), OptionalBigInt(Option), Float(f32), OptionalFloat(Option), Double(f64), OptionalDouble(Option), Bool(bool), OptionalBool(Option), Text(String), OptionalText(Option), DateTime(DateTime), OptionalDateTime(Option>), } impl Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Value::Int(n) => write!(f, "{}", n), Value::OptionalInt(Some(n)) => write!(f, "{}", n), Value::OptionalInt(None) => write!(f, "NULL"), Value::BigInt(n) => write!(f, "{}", n), Value::OptionalBigInt(Some(n)) => write!(f, "{}", n), Value::OptionalBigInt(None) => write!(f, "NULL"), Value::Float(n) => write!(f, "{}", n), Value::OptionalFloat(Some(n)) => write!(f, "{}", n), Value::OptionalFloat(None) => write!(f, "NULL"), Value::Double(n) => write!(f, "{}", n), Value::OptionalDouble(Some(n)) => write!(f, "{}", n), Value::OptionalDouble(None) => write!(f, "NULL"), Value::Bool(n) => write!(f, "{}", n), Value::OptionalBool(Some(n)) => write!(f, "{}", n), Value::OptionalBool(None) => write!(f, "NULL"), Value::Text(s) => write!(f, "'{}'", s.replace("'", "''")), Value::OptionalText(Some(s)) => write!(f, "'{}'", s.replace("'", "''")), Value::OptionalText(None) => write!(f, "NULL"), Value::DateTime(n) => write!(f, "{}", n), Value::OptionalDateTime(Some(n)) => write!(f, "{}", n), Value::OptionalDateTime(None) => write!(f, "NULL"), } } }