use sqlx::{AnyPool, any::install_default_drivers};
use std::sync::OnceLock;
use std::sync::atomic::{AtomicUsize, Ordering};
pub use rust_eloquent_macros::*;
pub use sqlx;
pub use futures;
pub use serde;
pub use serde_json;
#[cfg(feature = "redis")]
pub use redis;
pub mod schema;
pub mod collection;
pub mod types;
pub use types::Json;
pub use collection::EloquentCollection;
pub use async_trait::async_trait;
pub use sqlx::FromRow;
static DB_POOL: OnceLock<AnyPool> = OnceLock::new();
static DB_DRIVER: OnceLock<String> = OnceLock::new();
static REPLICA_POOLS: OnceLock<Vec<AnyPool>> = OnceLock::new();
static REPLICA_INDEX: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "redis")]
static REDIS_CLIENT: OnceLock<redis::Client> = OnceLock::new();
#[cfg(feature = "redis")]
static REDIS_MANAGER: OnceLock<redis::aio::ConnectionManager> = OnceLock::new();
#[derive(Clone, Debug)]
pub enum EloquentValue {
String(String),
Int(i32),
Float(f64),
Bool(bool),
}
impl From<&str> for EloquentValue {
fn from(s: &str) -> Self { EloquentValue::String(s.to_string()) }
}
impl From<String> for EloquentValue {
fn from(s: String) -> Self { EloquentValue::String(s) }
}
impl From<i32> for EloquentValue {
fn from(i: i32) -> Self { EloquentValue::Int(i) }
}
impl From<f64> for EloquentValue {
fn from(f: f64) -> Self { EloquentValue::Float(f) }
}
impl From<bool> for EloquentValue {
fn from(b: bool) -> Self { EloquentValue::Bool(b) }
}
pub struct Eloquent;
impl Eloquent {
pub async fn init(database_url: &str) -> Result<(), sqlx::Error> {
install_default_drivers();
let pool = AnyPool::connect(database_url).await?;
if DB_POOL.set(pool).is_err() {
panic!("Eloquent has already been initialized");
}
let driver = if database_url.starts_with("postgres") {
"postgres"
} else if database_url.starts_with("mysql") {
"mysql"
} else {
"sqlite"
};
let _ = DB_DRIVER.set(driver.to_string());
let _ = REPLICA_POOLS.set(vec![]);
Ok(())
}
pub async fn init_with_replicas(primary_url: &str, replica_urls: Vec<&str>) -> Result<(), sqlx::Error> {
install_default_drivers();
let pool = AnyPool::connect(primary_url).await?;
if DB_POOL.set(pool).is_err() {
panic!("Eloquent has already been initialized");
}
let driver = if primary_url.starts_with("postgres") {
"postgres"
} else if primary_url.starts_with("mysql") {
"mysql"
} else {
"sqlite"
};
let _ = DB_DRIVER.set(driver.to_string());
let mut replicas = vec![];
for url in replica_urls {
let p = AnyPool::connect(url).await?;
replicas.push(p);
}
let _ = REPLICA_POOLS.set(replicas);
Ok(())
}
pub fn pool() -> &'static AnyPool {
DB_POOL.get().expect("Eloquent must be initialized before querying")
}
pub fn read_pool() -> &'static AnyPool {
if let Some(replicas) = REPLICA_POOLS.get() {
if !replicas.is_empty() {
let idx = REPLICA_INDEX.fetch_add(1, Ordering::Relaxed);
return &replicas[idx % replicas.len()];
}
}
Self::pool()
}
pub fn driver() -> &'static str {
DB_DRIVER.get().expect("Eloquent must be initialized before querying").as_str()
}
pub async fn begin_transaction() -> Result<sqlx::Transaction<'static, sqlx::Any>, sqlx::Error> {
let pool = Self::pool();
pool.begin().await
}
pub async fn seed(seeders: Vec<Box<dyn Seeder>>) -> Result<(), sqlx::Error> {
for seeder in seeders {
seeder.run().await?;
}
Ok(())
}
pub fn enable_query_log() {
crate::schema::enable_query_log();
}
pub fn disable_query_log() {
crate::schema::disable_query_log();
}
#[cfg(feature = "redis")]
pub async fn init_redis(redis_url: &str) -> Result<(), redis::RedisError> {
let client = redis::Client::open(redis_url)?;
let manager = redis::aio::ConnectionManager::new(client.clone()).await?;
let _ = REDIS_CLIENT.set(client);
let _ = REDIS_MANAGER.set(manager);
Ok(())
}
#[cfg(feature = "redis")]
pub fn redis_client() -> &'static redis::Client {
REDIS_CLIENT.get().expect("Redis must be initialized before using cache features")
}
#[cfg(feature = "redis")]
pub fn redis_manager() -> redis::aio::ConnectionManager {
REDIS_MANAGER.get().expect("Redis must be initialized before using cache features").clone()
}
}
#[async_trait]
pub trait Seeder: Send + Sync {
async fn run(&self) -> Result<(), sqlx::Error>;
}
#[async_trait]
pub trait EloquentModel {
fn table_name() -> &'static str;
}
#[derive(Debug, Clone)]
pub struct PaginationResult<T> {
pub data: Vec<T>,
pub total: i64,
pub per_page: usize,
pub current_page: usize,
pub last_page: usize,
}