real_time_sqlx/
database.rs

1//! Query utilities and particularized database implementations
2//! Some implementations need to be particularized because of trait generics hell.
3
4use serde::Serialize;
5use sqlx::FromRow;
6
7use crate::{
8    queries::serialize::{
9        Condition, Constraint, ConstraintValue, FinalType, OrderBy, PaginateOptions, QueryData,
10        QueryTree,
11    },
12    utils::{placeholders, sanitize_identifier},
13};
14
15#[cfg(feature = "mysql")]
16pub mod mysql;
17
18#[cfg(feature = "postgres")]
19pub mod postgres;
20
21#[cfg(feature = "sqlite")]
22pub mod sqlite;
23
24/// Produce a prepared SQL string and a list of argument values for binding
25/// from a deserialized query, and for use in a SQLx query
26fn prepare_sqlx_query(query: &QueryTree) -> (String, Vec<FinalType>) {
27    let mut string_query = "SELECT * FROM ".to_string();
28    let mut values = vec![];
29    string_query.push_str(&sanitize_identifier(&query.table));
30
31    if let Some(condition) = &query.condition {
32        string_query.push_str(" WHERE ");
33        let (placeholders, args) = condition.traverse();
34        string_query.push_str(&placeholders);
35        values.extend(args);
36    }
37
38    if let Some(paginate) = &query.paginate {
39        string_query.push_str(" ");
40        let pagination = paginate.traverse();
41        string_query.push_str(&pagination.0);
42        values.extend(pagination.1);
43    }
44
45    (string_query, values)
46}
47
48/// Serialize SQL rows to json by mapping them to an intermediate data model structure
49pub fn serialize_rows<T, R>(data: &QueryData<R>) -> serde_json::Value
50where
51    T: for<'r> FromRow<'r, R> + Serialize,
52    R: sqlx::Row,
53{
54    match data {
55        QueryData::Single(row) => match row {
56            Some(row) => serde_json::json!(QueryData::Single(Some(T::from_row(row).unwrap()))),
57            None => serde_json::json!(QueryData::Single(None::<T>)),
58        },
59        QueryData::Many(rows) => serde_json::json!(QueryData::Many(
60            rows.iter()
61                .map(|row| T::from_row(row).unwrap())
62                .collect::<Vec<T>>()
63        )),
64    }
65}
66
67// ********************************************************************************************* //
68//                                     Query Traversal Functions                                 //
69// ********************************************************************************************* //
70
71/// Trait to normalize the traversal of query constraints and conditions
72trait Traversable {
73    fn traverse(&self) -> (String, Vec<FinalType>);
74}
75
76impl Traversable for FinalType {
77    /// Traverse a final constraint value
78    fn traverse(&self) -> (String, Vec<FinalType>) {
79        ("?".to_string(), vec![self.clone()])
80    }
81}
82
83impl Traversable for ConstraintValue {
84    /// Traverse a query constraint value
85    fn traverse(&self) -> (String, Vec<FinalType>) {
86        match self {
87            ConstraintValue::List(list) => (placeholders(list.len()), list.clone()),
88            ConstraintValue::Final(value) => value.traverse(),
89        }
90    }
91}
92
93impl Traversable for Constraint {
94    /// Traverse a query constraint
95    fn traverse(&self) -> (String, Vec<FinalType>) {
96        let (values_string_query, values) = self.value.traverse();
97
98        (
99            format!(
100                "\"{}\" {} {}",
101                self.column, self.operator, values_string_query
102            ),
103            values,
104        )
105    }
106}
107
108impl Traversable for Condition {
109    /// Traverse a query condition
110    fn traverse(&self) -> (String, Vec<FinalType>) {
111        match self {
112            Condition::Single { constraint } => constraint.traverse(),
113            Condition::Or { conditions } => reduce_constraints_list(conditions, " OR "),
114            Condition::And { conditions } => reduce_constraints_list(conditions, " AND "),
115        }
116    }
117}
118
119impl Traversable for PaginateOptions {
120    /// Traverse a query pagination options
121    fn traverse(&self) -> (String, Vec<FinalType>) {
122        let mut query_string = "".to_string();
123        let mut values: Vec<FinalType> = vec![];
124
125        if let Some(order) = &self.order_by {
126            query_string.push_str(
127                match order {
128                    OrderBy::Asc(col) => format!("ORDER BY {} ASC ", sanitize_identifier(col)),
129                    OrderBy::Desc(col) => format!("ORDER BY {} DESC ", sanitize_identifier(col)),
130                }
131                .as_str(),
132            );
133        } else {
134            // By default, if paginate options are present, order by ID descending
135            query_string.push_str("ORDER BY id DESC ");
136        }
137
138        query_string.push_str("LIMIT ? ");
139        values.push(FinalType::Number(self.per_page.into()));
140
141        if let Some(offset) = self.offset {
142            query_string.push_str("OFFSET ? ");
143            values.push(FinalType::Number(offset.into()));
144        }
145
146        (query_string, values)
147    }
148}
149
150/// Create a list of string queries and constraint values vectors from a list of
151/// conditions
152fn reduce_constraints_list(conditions: &[Condition], sep: &str) -> (String, Vec<FinalType>) {
153    let mut placeholder_strings: Vec<String> = vec![];
154    let mut total_values: Vec<FinalType> = vec![];
155
156    conditions.iter().for_each(|condition| {
157        let (string_query, values) = condition.traverse();
158        placeholder_strings.push(string_query);
159        total_values.extend(values);
160    });
161
162    (format!("({})", placeholder_strings.join(sep)), total_values)
163}