1use sqlx::{AnyPool, any::install_default_drivers};
2use std::sync::OnceLock;
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5pub use rust_eloquent_macros::*;
7pub use sqlx;
8pub use futures;
9pub use serde;
10pub use serde_json;
11
12#[cfg(feature = "redis")]
13pub use redis;
14pub mod schema;
15pub mod collection;
16pub mod types;
17
18pub use types::Json;
19pub use collection::EloquentCollection;
20
21pub use async_trait::async_trait;
23
24pub use sqlx::FromRow;
26
27static DB_POOL: OnceLock<AnyPool> = OnceLock::new();
29
30static DB_DRIVER: OnceLock<String> = OnceLock::new();
32
33static REPLICA_POOLS: OnceLock<Vec<AnyPool>> = OnceLock::new();
35
36static REPLICA_INDEX: AtomicUsize = AtomicUsize::new(0);
38
39#[cfg(feature = "redis")]
40static REDIS_CLIENT: OnceLock<redis::Client> = OnceLock::new();
41
42#[cfg(feature = "redis")]
43static REDIS_MANAGER: OnceLock<redis::aio::ConnectionManager> = OnceLock::new();
44
45#[derive(Clone, Debug)]
47pub enum EloquentValue {
48 String(String),
49 Int(i32),
50 Float(f64),
51 Bool(bool),
52}
53
54impl From<&str> for EloquentValue {
55 fn from(s: &str) -> Self { EloquentValue::String(s.to_string()) }
56}
57impl From<String> for EloquentValue {
58 fn from(s: String) -> Self { EloquentValue::String(s) }
59}
60impl From<i32> for EloquentValue {
61 fn from(i: i32) -> Self { EloquentValue::Int(i) }
62}
63impl From<f64> for EloquentValue {
64 fn from(f: f64) -> Self { EloquentValue::Float(f) }
65}
66impl From<bool> for EloquentValue {
67 fn from(b: bool) -> Self { EloquentValue::Bool(b) }
68}
69
70pub struct Eloquent;
72
73impl Eloquent {
74 pub async fn init(database_url: &str) -> Result<(), sqlx::Error> {
76 install_default_drivers();
77 let pool = AnyPool::connect(database_url).await?;
78
79 if DB_POOL.set(pool).is_err() {
80 panic!("Eloquent has already been initialized");
81 }
82
83 let driver = if database_url.starts_with("postgres") {
84 "postgres"
85 } else if database_url.starts_with("mysql") {
86 "mysql"
87 } else {
88 "sqlite"
89 };
90
91 let _ = DB_DRIVER.set(driver.to_string());
92 let _ = REPLICA_POOLS.set(vec![]);
93
94 Ok(())
95 }
96
97 pub async fn init_with_replicas(primary_url: &str, replica_urls: Vec<&str>) -> Result<(), sqlx::Error> {
99 install_default_drivers();
100 let pool = AnyPool::connect(primary_url).await?;
101
102 if DB_POOL.set(pool).is_err() {
103 panic!("Eloquent has already been initialized");
104 }
105
106 let driver = if primary_url.starts_with("postgres") {
107 "postgres"
108 } else if primary_url.starts_with("mysql") {
109 "mysql"
110 } else {
111 "sqlite"
112 };
113
114 let _ = DB_DRIVER.set(driver.to_string());
115
116 let mut replicas = vec![];
117 for url in replica_urls {
118 let p = AnyPool::connect(url).await?;
119 replicas.push(p);
120 }
121 let _ = REPLICA_POOLS.set(replicas);
122
123 Ok(())
124 }
125
126 pub fn pool() -> &'static AnyPool {
128 DB_POOL.get().expect("Eloquent must be initialized before querying")
129 }
130
131 pub fn read_pool() -> &'static AnyPool {
134 if let Some(replicas) = REPLICA_POOLS.get() {
135 if !replicas.is_empty() {
136 let idx = REPLICA_INDEX.fetch_add(1, Ordering::Relaxed);
137 return &replicas[idx % replicas.len()];
138 }
139 }
140 Self::pool()
141 }
142
143 pub fn driver() -> &'static str {
145 DB_DRIVER.get().expect("Eloquent must be initialized before querying").as_str()
146 }
147
148 pub async fn begin_transaction() -> Result<sqlx::Transaction<'static, sqlx::Any>, sqlx::Error> {
150 let pool = Self::pool();
151 pool.begin().await
152 }
153
154 pub async fn seed(seeders: Vec<Box<dyn Seeder>>) -> Result<(), sqlx::Error> {
156 for seeder in seeders {
157 seeder.run().await?;
158 }
159 Ok(())
160 }
161
162 pub fn enable_query_log() {
164 crate::schema::enable_query_log();
165 }
166
167 pub fn disable_query_log() {
169 crate::schema::disable_query_log();
170 }
171
172 #[cfg(feature = "redis")]
174 pub async fn init_redis(redis_url: &str) -> Result<(), redis::RedisError> {
175 let client = redis::Client::open(redis_url)?;
176 let manager = redis::aio::ConnectionManager::new(client.clone()).await?;
177 let _ = REDIS_CLIENT.set(client);
178 let _ = REDIS_MANAGER.set(manager);
179 Ok(())
180 }
181
182 #[cfg(feature = "redis")]
184 pub fn redis_client() -> &'static redis::Client {
185 REDIS_CLIENT.get().expect("Redis must be initialized before using cache features")
186 }
187
188 #[cfg(feature = "redis")]
190 pub fn redis_manager() -> redis::aio::ConnectionManager {
191 REDIS_MANAGER.get().expect("Redis must be initialized before using cache features").clone()
192 }
193}
194
195#[async_trait]
197pub trait Seeder: Send + Sync {
198 async fn run(&self) -> Result<(), sqlx::Error>;
199}
200
201#[async_trait]
203pub trait EloquentModel {
204 fn table_name() -> &'static str;
205}
206
207#[derive(Debug, Clone)]
209pub struct PaginationResult<T> {
210 pub data: Vec<T>,
211 pub total: i64,
212 pub per_page: usize,
213 pub current_page: usize,
214 pub last_page: usize,
215}
216