Skip to main content

rust_eloquent/
lib.rs

1use sqlx::{AnyPool, any::install_default_drivers};
2use std::sync::OnceLock;
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5// Re-export the procedural macro so users only need to import `rust-eloquent`
6pub 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
21// Re-export async_trait so the macro can use it implicitly
22pub use async_trait::async_trait;
23
24// Re-export sqlx and FromRow for database mapping
25pub use sqlx::FromRow;
26
27/// The global connection pool
28static DB_POOL: OnceLock<AnyPool> = OnceLock::new();
29
30/// The driver identifier (postgres, mysql, sqlite) to help macro syntax formatting
31static DB_DRIVER: OnceLock<String> = OnceLock::new();
32
33/// The replica connection pools for read operations
34static REPLICA_POOLS: OnceLock<Vec<AnyPool>> = OnceLock::new();
35
36/// Atomic index for replica round-robin selection
37static 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/// Enum dinĂ¢mico para encapsular qualquer tipo que possa ser associado ao banco de dados pelo Macro
46#[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
70/// Eloquent configuration structure
71pub struct Eloquent;
72
73impl Eloquent {
74    /// Initialize the global database connection pool using an agnostic URI
75    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    /// Initialize the global database connection pool and its read replicas
98    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    /// Retrieve the global database connection pool (strictly for writes)
127    pub fn pool() -> &'static AnyPool {
128        DB_POOL.get().expect("Eloquent must be initialized before querying")
129    }
130
131    /// Retrieve the connection pool for read operations.
132    /// Performs a round-robin load balancing over replicas if configured.
133    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    /// Retrieve the active driver string
144    pub fn driver() -> &'static str {
145        DB_DRIVER.get().expect("Eloquent must be initialized before querying").as_str()
146    }
147
148    /// Starts a new database transaction
149    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    /// Run an array of seeders sequentially
155    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    /// Enable query logging to print all queries to the terminal
163    pub fn enable_query_log() {
164        crate::schema::enable_query_log();
165    }
166
167    /// Disable query logging
168    pub fn disable_query_log() {
169        crate::schema::disable_query_log();
170    }
171
172    /// Initialize Redis connection and connection manager for caching and events
173    #[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    /// Get reference to the global Redis client
183    #[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    /// Get clone of the thread-safe connection manager for async Redis queries
189    #[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/// A database seeder trait for populating tables
196#[async_trait]
197pub trait Seeder: Send + Sync {
198    async fn run(&self) -> Result<(), sqlx::Error>;
199}
200
201/// The core trait that all Eloquent models will implement via #[derive(Eloquent)]
202#[async_trait]
203pub trait EloquentModel {
204    fn table_name() -> &'static str;
205}
206
207/// Represents a paginated result set
208#[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