eloquent_core/
lib.rs

1//! # Eloquent Core
2//!
3//! The core library for building SQL queries. This library is used by the [Eloquent](https://crates.io/crates/eloquent) library to build SQL queries.
4
5use compiler::{build_statement, build_substatement};
6use error::EloquentError;
7use std::fmt::Display;
8
9mod builders;
10mod checks;
11mod compiler;
12mod compilers;
13/// The error module that contains all the possible errors that can occur while building a query.
14pub mod error;
15mod queries;
16mod query_builder;
17mod subqueries;
18mod subquery_builder;
19mod validator;
20
21/// The main builder struct that holds all the query building information.
22pub struct QueryBuilder {
23    table: Option<String>,
24    selects: Vec<Select>,
25    inserts: Vec<Insert>,
26    updates: Vec<Update>,
27    delete: bool,
28    conditions: Vec<Condition>,
29    closures: Vec<(Logic, Vec<Condition>)>,
30    joins: Vec<Join>,
31    havings: Vec<Having>,
32    group_by: Vec<String>,
33    order_by: Vec<OrderColumn>,
34    limit: Option<u64>,
35    offset: Option<u64>,
36    enable_checks: bool,
37    paginate: Option<Paginate>,
38}
39
40/// The subquery builder struct that holds all the subquery building information.
41pub struct SubqueryBuilder {
42    table: Option<String>,
43    selects: Vec<Select>,
44    conditions: Vec<Condition>,
45    joins: Vec<Join>,
46    havings: Vec<Having>,
47    group_by: Vec<String>,
48    order_by: Vec<OrderColumn>,
49    limit: Option<u64>,
50    offset: Option<u64>,
51}
52
53pub trait ToSql {
54    fn to_sql(&self) -> Result<String, EloquentError>;
55
56    fn is_subquery(&self) -> bool {
57        false
58    }
59}
60
61pub trait Columnable {
62    fn to_columns(&self) -> Vec<String>;
63}
64
65pub trait Selectable {
66    fn to_select_column(&self) -> String;
67}
68
69pub(crate) trait PerformChecks {
70    fn check(builder: &QueryBuilder) -> Result<(), EloquentError>;
71}
72
73#[allow(clippy::borrowed_box)]
74pub(crate) trait SqlBuilder {
75    fn build<'a>(
76        builder: &'a QueryBuilder,
77        sql: &mut String,
78        params: &mut Vec<&'a Box<(dyn ToSql + 'static)>>,
79    ) -> Result<String, EloquentError>;
80}
81
82#[derive(Debug, PartialEq)]
83pub(crate) enum Action {
84    Select,
85    Insert,
86    Update,
87    Delete,
88}
89
90struct Condition {
91    field: String,
92    operator: Operator,
93    logic: Logic,
94    values: Vec<Box<dyn ToSql>>,
95}
96
97struct Select {
98    column: String,
99    function: Option<Function>,
100    alias: Option<String>,
101}
102
103struct Insert {
104    column: String,
105    values: Vec<Box<dyn ToSql>>,
106}
107
108struct Update {
109    column: String,
110    value: Box<dyn ToSql>,
111}
112
113#[derive(PartialEq)]
114struct OrderColumn {
115    column: String,
116    order: Order,
117}
118
119struct Having {
120    conditions: Vec<Condition>,
121}
122
123#[derive(Debug, PartialEq)]
124enum Operator {
125    Equal,
126    NotEqual,
127    GreaterThan,
128    GreaterThanOrEqual,
129    LessThan,
130    LessThanOrEqual,
131    Between,
132    Like,
133    In,
134    NotIn,
135    IsNull,
136    IsNotNull,
137    Date,
138    Year,
139    Month,
140    Day,
141}
142
143#[derive(Debug, PartialEq)]
144enum Logic {
145    And,
146    Or,
147}
148
149#[derive(Debug, PartialEq, Eq, Hash)]
150enum Function {
151    Count,
152    Sum,
153    Avg,
154    Min,
155    Max,
156    Distinct,
157}
158
159struct Join {
160    table: String,
161    left_hand: String,
162    join_type: JoinType,
163    right_hand: String,
164}
165
166enum JoinType {
167    Inner,
168    Left,
169    Right,
170    Full,
171}
172
173#[derive(Debug, PartialEq)]
174pub(crate) enum Order {
175    Asc,
176    Desc,
177}
178
179pub(crate) struct Paginate {
180    column: String,
181    last_id: Option<Box<dyn ToSql>>,
182    per_page: u64,
183}
184
185impl Select {
186    fn format_column_name(&self) -> String {
187        let column = match &self.function {
188            Some(function) => match function {
189                Function::Distinct => format!("{} {}", function, self.column),
190                _ => format!("{}({})", function, self.column),
191            },
192            None => self.column.clone(),
193        };
194
195        if let Some(alias) = &self.alias {
196            format!("{} AS {}", column, alias)
197        } else {
198            column
199        }
200    }
201
202    fn format_column_name_without_alias(&self) -> String {
203        match &self.function {
204            Some(function) => match function {
205                Function::Distinct => format!("{} {}", function, self.column),
206                _ => format!("{}({})", function, self.column),
207            },
208            None => self.column.clone(),
209        }
210    }
211}
212
213impl Selectable for &str {
214    fn to_select_column(&self) -> String {
215        self.to_string()
216    }
217}
218
219impl Selectable for SubqueryBuilder {
220    fn to_select_column(&self) -> String {
221        self.to_sql().unwrap()
222    }
223}
224
225impl Condition {
226    fn new(field: &str, operator: Operator, logic: Logic, values: Vec<Box<dyn ToSql>>) -> Self {
227        Condition {
228            field: field.to_string(),
229            operator,
230            logic,
231            values,
232        }
233    }
234}
235
236impl ToSql for &str {
237    fn to_sql(&self) -> Result<String, EloquentError> {
238        Ok(format!("'{}'", self.replace('\'', "''")))
239    }
240}
241
242impl ToSql for String {
243    fn to_sql(&self) -> Result<String, EloquentError> {
244        Ok(format!("'{}'", self.replace('\'', "''")))
245    }
246}
247
248impl ToSql for &String {
249    fn to_sql(&self) -> Result<String, EloquentError> {
250        Ok(format!("'{}'", self.replace('\'', "''")))
251    }
252}
253
254impl ToSql for i32 {
255    fn to_sql(&self) -> Result<String, EloquentError> {
256        Ok(self.to_string())
257    }
258}
259
260impl ToSql for i64 {
261    fn to_sql(&self) -> Result<String, EloquentError> {
262        Ok(self.to_string())
263    }
264}
265
266impl ToSql for u32 {
267    fn to_sql(&self) -> Result<String, EloquentError> {
268        Ok(self.to_string())
269    }
270}
271
272impl ToSql for u64 {
273    fn to_sql(&self) -> Result<String, EloquentError> {
274        Ok(self.to_string())
275    }
276}
277
278impl ToSql for f32 {
279    fn to_sql(&self) -> Result<String, EloquentError> {
280        Ok(self.to_string())
281    }
282}
283
284impl ToSql for f64 {
285    fn to_sql(&self) -> Result<String, EloquentError> {
286        Ok(self.to_string())
287    }
288}
289
290impl ToSql for bool {
291    fn to_sql(&self) -> Result<String, EloquentError> {
292        Ok(self.to_string())
293    }
294}
295
296impl ToSql for QueryBuilder {
297    fn to_sql(&self) -> Result<String, EloquentError> {
298        build_statement(self)
299    }
300}
301
302impl ToSql for SubqueryBuilder {
303    fn to_sql(&self) -> Result<String, EloquentError> {
304        build_substatement(self)
305    }
306
307    fn is_subquery(&self) -> bool {
308        true
309    }
310}
311
312impl Columnable for &str {
313    fn to_columns(&self) -> Vec<String> {
314        vec![self.to_string()]
315    }
316}
317
318impl Columnable for Vec<&str> {
319    fn to_columns(&self) -> Vec<String> {
320        self.iter().map(|&s| s.to_string()).collect()
321    }
322}
323
324impl Display for Operator {
325    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
326        let operator = match self {
327            Operator::Equal => "=",
328            Operator::NotEqual => "!=",
329            Operator::GreaterThan => ">",
330            Operator::GreaterThanOrEqual => ">=",
331            Operator::LessThan => "<",
332            Operator::LessThanOrEqual => "<=",
333            Operator::Between => "BETWEEN",
334            Operator::Like => "LIKE",
335            Operator::In => "IN",
336            Operator::NotIn => "NOT IN",
337            Operator::IsNull => "IS NULL",
338            Operator::IsNotNull => "IS NOT NULL",
339            Operator::Date => "DATE",
340            Operator::Year => "YEAR",
341            Operator::Month => "MONTH",
342            Operator::Day => "DAY",
343        };
344
345        write!(f, "{}", operator)
346    }
347}
348
349impl Display for Order {
350    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351        let order = match self {
352            Order::Asc => "ASC",
353            Order::Desc => "DESC",
354        };
355
356        write!(f, "{}", order)
357    }
358}
359
360impl Display for Function {
361    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362        let function = match self {
363            Function::Count => "COUNT",
364            Function::Sum => "SUM",
365            Function::Avg => "AVG",
366            Function::Min => "MIN",
367            Function::Max => "MAX",
368            Function::Distinct => "DISTINCT",
369        };
370
371        write!(f, "{}", function)
372    }
373}
374
375impl Display for JoinType {
376    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377        let join_type = match self {
378            JoinType::Inner => "JOIN",
379            JoinType::Left => "LEFT JOIN",
380            JoinType::Right => "RIGHT JOIN",
381            JoinType::Full => "FULL JOIN",
382        };
383
384        write!(f, "{}", join_type)
385    }
386}
387
388impl Condition {
389    fn format_sql(&self) -> String {
390        let values = self
391            .values
392            .iter()
393            .map(|v| v.to_sql().unwrap())
394            .collect::<Vec<String>>()
395            .join(", ");
396
397        match self.operator {
398            Operator::Between => format!(
399                "{} {} {} AND {}",
400                self.field,
401                self.operator,
402                values.split(", ").next().unwrap(),
403                values.split(", ").last().unwrap()
404            ),
405            Operator::In | Operator::NotIn => {
406                if self.values.iter().any(|v| v.is_subquery()) {
407                    // subquery already contains parentheses so we don't need to add them
408                    format!("{} {} {}", self.field, self.operator, values)
409                } else {
410                    format!("{} {} ({})", self.field, self.operator, values)
411                }
412            }
413            Operator::IsNull | Operator::IsNotNull => format!("{} {}", self.field, self.operator),
414            Operator::Date | Operator::Year | Operator::Month | Operator::Day => {
415                format!("{}({}) = {}", self.operator, self.field, values)
416            }
417            _ => format!("{} {} {}", self.field, self.operator, values),
418        }
419    }
420}
421
422#[macro_export]
423macro_rules! eloquent_sql_row {
424    ($($key:expr => $value:expr),* $(,)?) => {
425        vec![
426            $(($key, Box::new($value) as Box<dyn $crate::ToSql>)),*
427        ]
428    };
429}