lume/
lib.rs

1#![warn(missing_docs)]
2
3//! # Lume
4//!
5//! A type-safe, ergonomic schema builder and ORM for SQL databases, inspired by Drizzle ORM.
6//!
7//! ## Features
8//!
9//! - 🚀 **Type-safe**: Compile-time type checking for all database operations
10//! - 🎯 **Ergonomic**: Clean, intuitive API inspired by modern ORMs
11//! - ⚡ **Performance**: Zero-cost abstractions with minimal runtime overhead
12//! - 🔧 **Flexible**: Support for various column constraints and SQL types
13//! - 🛡️ **Safe**: Prevents SQL injection and runtime type errors
14//! - 📦 **Lightweight**: Minimal dependencies, maximum functionality
15//!
16//! ## Quick Start
17//!
18//! ```no_run,ignore
19//! use lume::define_schema;
20//! use lume::schema::{Schema, ColumnInfo, Value};
21//! use lume::database::Database;
22//!
23//! // Define your database schema
24//! define_schema! {
25//!     Users {
26//!         id: i32 [primary_key().not_null()],
27//!         username: String [not_null()],
28//!         email: String,
29//!         age: i32,
30//!         is_active: bool [default_value(true)],
31//!     }
32//! }
33//!
34//! #[tokio::main]
35//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
36//!     // Connect to your MySQL database
37//!     let db = Database::connect("mysql://user:password@localhost/database").await?;
38//!     
39//!     // Type-safe queries
40//!     let users = db
41//!         .query::<Users, SelectUsers>()
42//!         .filter(lume::filter::Filter::eq_value("username", Value::String("john_doe".to_string())))
43//!         .execute()
44//!         .await?;
45//!     
46//!     for user in users {
47//!         let username: Option<String> = user.get(Users::username());
48//!         println!("User: {}", username.unwrap_or_default());
49//!     }
50//!     
51//!     Ok(())
52//! }
53//! ```
54//!
55//! ## Supported Database Types
56//!
57//! - `String` → `VARCHAR(255)`
58//! - `i32` → `INTEGER`
59//! - `i64` → `BIGINT`
60//! - `f32` → `FLOAT`
61//! - `f64` → `DOUBLE`
62//! - `bool` → `BOOLEAN`
63//!
64//! ## Column Constraints
65//!
66//! - `primary_key()` - Sets the column as primary key
67//! - `not_null()` - Makes the column NOT NULL
68//! - `unique()` - Adds a UNIQUE constraint
69//! - `indexed()` - Creates an index on the column
70//! - `default_value(value)` - Sets a default value
71
72use crate::{filter::Filtered, schema::Value};
73
74/// Database connection and management functionality
75pub mod database;
76
77/// Query filtering and condition building
78pub mod filter;
79
80/// Database operations (queries, inserts, etc.)
81pub mod operations;
82
83/// Row abstraction for type-safe data access
84pub mod row;
85
86/// Schema definition and column management
87pub mod schema;
88
89/// Table registry and definition management
90pub mod table;
91
92mod tests;
93
94#[derive(PartialEq, Debug)]
95pub(crate) enum StartingSql {
96    Select,
97    Insert,
98    Delete,
99    Update,
100}
101
102pub(crate) fn get_starting_sql(starting_sql: StartingSql, table_name: &str) -> String {
103    match starting_sql {
104        StartingSql::Select => "SELECT ".to_string(),
105        StartingSql::Insert => format!("INSERT INTO `{}` (", table_name),
106        StartingSql::Delete => format!("DELETE FROM `{}` ", table_name),
107        StartingSql::Update => format!("UPDATE `{}` SET ", table_name),
108    }
109}
110
111#[cfg(not(feature = "mysql"))]
112pub(crate) fn returning_sql(mut sql: String, returning: &Vec<&'static str>) -> String {
113    if returning.is_empty() {
114        return sql;
115    }
116
117    sql.push_str(" RETURNING ");
118    for (i, col) in returning.iter().enumerate() {
119        if i > 0 {
120            sql.push_str(", ");
121        }
122        sql.push_str(col);
123    }
124    sql.push_str(";");
125    sql
126}
127
128#[cfg(feature = "mysql")]
129pub(crate) fn returning_sql(mut sql: String, returning: &Vec<&'static str>) -> String {
130    if returning.is_empty() {
131        return sql;
132    }
133    sql.push_str(&returning.join(", "));
134
135    sql
136}
137
138pub(crate) fn build_filter_expr(filter: &dyn Filtered, params: &mut Vec<Value>) -> String {
139    if filter.is_or_filter() || filter.is_and_filter() {
140        let op = if filter.is_or_filter() { "OR" } else { "AND" };
141        let Some(f1) = filter.filter1() else {
142            eprintln!("Warning: Composite filter missing filter1, using tautology");
143            return "1=1".to_string();
144        };
145        let Some(f2) = filter.filter2() else {
146            eprintln!("Warning: Composite filter missing filter2, using tautology");
147            return "1=1".to_string();
148        };
149        let left = build_filter_expr(f1, params);
150        let right = build_filter_expr(f2, params);
151        return format!("({} {} {})", left, op, right);
152    }
153
154    if filter.is_not().unwrap_or(false) {
155        let Some(f) = filter.filter1() else {
156            eprintln!("Warning: Not filter missing filter1, using tautology");
157            return "1=1".to_string();
158        };
159        return format!("NOT ({})", build_filter_expr(f, params));
160    }
161
162    let Some(col1) = filter.column_one() else {
163        eprintln!("Warning: Simple filter missing column_one, using tautology");
164        return "1=1".to_string();
165    };
166    // Handle IN / NOT IN array filters
167    if let Some(in_array) = filter.is_in_array() {
168        let values = filter.array_values().unwrap_or(&[]);
169        if values.is_empty() {
170            return if in_array {
171                "1=0".to_string()
172            } else {
173                "1=1".to_string()
174            };
175        }
176        let mut placeholders: Vec<&'static str> = Vec::with_capacity(values.len());
177        for v in values.iter().cloned() {
178            params.push(v);
179            placeholders.push("?");
180        }
181        let op = if in_array { "IN" } else { "NOT IN" };
182        return format!("{}.{} {} ({})", col1.0, col1.1, op, placeholders.join(", "));
183    }
184    if let Some(value) = filter.value() {
185        match value {
186            Value::Null => {
187                // Special handling for NULL comparisons
188                let op = filter.filter_type();
189                let null_sql = match op {
190                    crate::filter::FilterType::Eq => "IS NULL",
191                    crate::filter::FilterType::Neq => "IS NOT NULL",
192                    _ => {
193                        // Unsupported operator with NULL; force false to avoid surprising results
194                        return "1=0".to_string();
195                    }
196                };
197                format!("{}.{} {}", col1.0, col1.1, null_sql)
198            }
199            Value::Between(min, max) => {
200                params.push((**min).clone());
201                params.push((**max).clone());
202                format!("{}.{} BETWEEN ? AND ?", col1.0, col1.1)
203            }
204            _ => {
205                params.push(value.clone());
206                format!("{}.{} {} ?", col1.0, col1.1, filter.filter_type().to_sql())
207            }
208        }
209    } else if let Some(col2) = filter.column_two() {
210        format!(
211            "{}.{} {} {}.{}",
212            col1.0,
213            col1.1,
214            filter.filter_type().to_sql(),
215            col2.0,
216            col2.1
217        )
218    } else {
219        return "1=1".to_string();
220    }
221}