atmosphere_core/runtime/
sql.rs

1//! # SQL Code Generation
2//!
3//! This submodule provides essential constructors for SQL queries tailored to the operations
4//! performed within the Atmosphere framework. It includes functionalities for dynamically building
5//! queries for CRUD (Create, Read, Update, Delete) operations, and managing bindings between SQL
6//! queries and table entities.
7//!
8//! ## Key features:
9//!
10//! - Query Builders: Functions like `select`, `insert`, `update`, `delete`, and `upsert`, which create SQL
11//!   queries for their respective operations. These builders ensure that queries are correctly formatted and
12//!   aligned with the structure and constraints of the target table.
13//!
14//! - Binding Management: The `Bindings` struct and its implementations, which manage the relationship between
15//!   table columns and the SQL queries they are bound to. This ensures that queries are executed with the correct
16//!   parameters and their values.
17
18use std::fmt;
19
20use sqlx::QueryBuilder;
21
22use crate::{
23    Bind, Column,
24    query::{self, Query},
25};
26
27/// Struct representing bindings for SQL queries.
28///
29/// `Bindings` is responsible for holding a collection of columns that are bound to a specific SQL query.
30/// It encapsulates the necessary details for each column, such as field names and SQL representations,
31/// ensuring accurate and efficient binding of data to the query.
32pub struct Bindings<T: Bind>(Vec<Column<T>>);
33
34impl<T: Bind> PartialEq for Bindings<T> {
35    fn eq(&self, other: &Self) -> bool {
36        if self.0.len() != other.0.len() {
37            return false;
38        }
39
40        for (i, a) in self.0.iter().enumerate() {
41            let Some(b) = other.0.get(i) else {
42                return false;
43            };
44
45            if a.field() != b.field() {
46                return false;
47            }
48
49            if a.sql() != b.sql() {
50                return false;
51            }
52        }
53
54        true
55    }
56}
57
58impl<T: Bind> Eq for Bindings<T> {}
59
60impl<T: Bind> fmt::Debug for Bindings<T> {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        let mut f = f.debug_tuple("Bindings");
63
64        for c in &self.0 {
65            f.field(&c.field());
66        }
67
68        f.finish()
69    }
70}
71
72impl<T: Bind> Bindings<T> {
73    pub fn columns(&self) -> &[Column<T>] {
74        &self.0
75    }
76
77    pub fn empty() -> Self {
78        Self(vec![])
79    }
80}
81
82fn table<T: Bind>() -> String {
83    #[cfg(not(feature = "sqlite"))]
84    return format!("\"{}\".\"{}\"", T::SCHEMA, T::TABLE);
85
86    #[cfg(feature = "sqlite")]
87    return format!("\"{}\"", T::TABLE);
88}
89
90/// Generates a `SELECT` query to retrieve a single row from the table based on its primary key.
91///
92/// SQL: `SELECT * FROM .. WHERE .. = $1`
93pub fn select<T: Bind>() -> Query<T> {
94    select_by(Column::PrimaryKey(&T::PRIMARY_KEY))
95}
96
97/// Creates a `SELECT` query to retrieve rows from the table based on a specific column.
98///
99/// SQL: `SELECT * FROM .. WHERE .. = $1`
100pub fn select_by<T: Bind>(c: Column<T>) -> Query<T> {
101    let mut query = QueryBuilder::new("SELECT\n  ");
102
103    let mut separated = query.separated(",\n  ");
104
105    separated.push(T::PRIMARY_KEY.sql);
106
107    for fk in T::FOREIGN_KEYS {
108        separated.push(fk.sql);
109    }
110
111    for data in T::DATA_COLUMNS {
112        separated.push(data.sql);
113    }
114
115    for meta in T::TIMESTAMP_COLUMNS {
116        separated.push(meta.sql);
117    }
118
119    query.push(format!("\nFROM\n  {}\n", table::<T>()));
120    query.push(format!("WHERE {} = $1", c.sql()));
121
122    Query::new(
123        query::Operation::Select,
124        query::Cardinality::One,
125        query,
126        Bindings(vec![c]),
127    )
128}
129
130/// Constructs a `SELECT` query to fetch all rows from the table.
131///
132/// SQL: `SELECT * FROM ..`
133pub fn select_all<T: Bind>() -> Query<T> {
134    let mut query = QueryBuilder::new("SELECT\n  ");
135
136    let mut separated = query.separated(",\n  ");
137
138    separated.push(T::PRIMARY_KEY.sql);
139
140    for fk in T::FOREIGN_KEYS {
141        separated.push(fk.sql);
142    }
143
144    for data in T::DATA_COLUMNS {
145        separated.push(data.sql);
146    }
147
148    for meta in T::TIMESTAMP_COLUMNS {
149        separated.push(meta.sql);
150    }
151
152    query.push(format!("\nFROM\n  {}\n", table::<T>()));
153
154    Query::new(
155        query::Operation::Select,
156        query::Cardinality::Many,
157        query,
158        Bindings::empty(),
159    )
160}
161
162/// Generates an `INSERT` query to add a new row to the table.
163///
164/// SQL: `INSERT INTO .. VALUES ..`
165pub fn insert<T: Bind>() -> Query<T> {
166    let mut builder = QueryBuilder::new(format!("INSERT INTO {}\n  (", table::<T>()));
167
168    let mut bindings = vec![];
169
170    let mut separated = builder.separated(", ");
171
172    separated.push(T::PRIMARY_KEY.sql.to_string());
173    bindings.push(Column::PrimaryKey(&T::PRIMARY_KEY));
174
175    for fk in T::FOREIGN_KEYS {
176        separated.push(fk.sql.to_string());
177        bindings.push(Column::ForeignKey(fk));
178    }
179
180    for data in T::DATA_COLUMNS {
181        separated.push(data.sql.to_string());
182        bindings.push(Column::Data(data));
183    }
184
185    for meta in T::TIMESTAMP_COLUMNS {
186        separated.push(meta.sql.to_string());
187        bindings.push(Column::Timestamp(meta));
188    }
189
190    separated.push_unseparated(")\nVALUES\n  (");
191
192    separated.push_unseparated("$1");
193
194    let columns = 1 + T::FOREIGN_KEYS.len() + T::DATA_COLUMNS.len() + T::TIMESTAMP_COLUMNS.len();
195
196    for c in 2..=columns {
197        separated.push(format!("${c}"));
198    }
199
200    builder.push(")");
201
202    Query::new(
203        query::Operation::Insert,
204        query::Cardinality::One,
205        builder,
206        Bindings(bindings),
207    )
208}
209
210/// Creates an `UPDATE` query to modify an existing row in the table.
211///
212/// SQL: `UPDATE .. SET .. WHERE ..`
213pub fn update<T: Bind>() -> Query<T> {
214    let mut builder = QueryBuilder::new(format!("UPDATE {} SET\n  ", table::<T>()));
215    let mut bindings = vec![];
216
217    let mut separated = builder.separated(",\n  ");
218
219    separated.push(format!("{} = $1", T::PRIMARY_KEY.sql));
220    bindings.push(Column::PrimaryKey(&T::PRIMARY_KEY));
221
222    let mut col = 2;
223
224    for fk in T::FOREIGN_KEYS {
225        separated.push(format!("{} = ${col}", fk.sql));
226        bindings.push(Column::ForeignKey(fk));
227        col += 1;
228    }
229
230    for data in T::DATA_COLUMNS {
231        separated.push(format!("{} = ${col}", data.sql));
232        bindings.push(Column::Data(data));
233        col += 1;
234    }
235
236    for meta in T::TIMESTAMP_COLUMNS {
237        separated.push(format!("{} = ${col}", meta.sql));
238        bindings.push(Column::Timestamp(meta));
239        col += 1;
240    }
241
242    builder.push(format!("\nWHERE\n  {} = $1", T::PRIMARY_KEY.sql));
243
244    Query::new(
245        query::Operation::Update,
246        query::Cardinality::One,
247        builder,
248        Bindings(bindings),
249    )
250}
251
252/// Constructs an `UPSERT` query (update or insert) for a row in the table.
253///
254/// SQL: `UPDATE .. SET .. WHERE .. ON CONFLICT .. DO UPDATE SET`
255pub fn upsert<T: Bind>() -> Query<T> {
256    let Query {
257        mut builder,
258        bindings,
259        ..
260    } = insert::<T>();
261
262    builder.push("\nON CONFLICT(");
263    builder.push(T::PRIMARY_KEY.sql);
264    builder.push(")\nDO UPDATE SET\n  ");
265
266    let mut separated = builder.separated(",\n  ");
267
268    for fk in T::FOREIGN_KEYS {
269        separated.push(format!("{} = EXCLUDED.{}", fk.sql, fk.sql));
270    }
271
272    for data in T::DATA_COLUMNS {
273        separated.push(format!("{} = EXCLUDED.{}", data.sql, data.sql));
274    }
275
276    for meta in T::TIMESTAMP_COLUMNS {
277        separated.push(format!("{} = EXCLUDED.{}", meta.sql, meta.sql));
278    }
279
280    Query::new(
281        query::Operation::Upsert,
282        query::Cardinality::One,
283        builder,
284        bindings,
285    )
286}
287
288/// Generates a `DELETE` query to remove a row from the table based on its primary key.
289///
290/// SQL: `DELETE FROM .. WHERE ..`
291pub fn delete<T: Bind>() -> Query<T> {
292    delete_by(T::PRIMARY_KEY.as_col())
293}
294
295/// Creates a `DELETE` query to remove rows from the table based on a specific column.
296///
297/// SQL: `DELETE FROM .. WHERE ..`
298pub fn delete_by<T: Bind>(c: Column<T>) -> Query<T> {
299    let mut builder = QueryBuilder::new(format!("DELETE FROM {} WHERE ", table::<T>()));
300
301    builder.push(c.sql());
302    builder.push(" = $1");
303
304    Query::new(
305        query::Operation::Delete,
306        query::Cardinality::One,
307        builder,
308        Bindings(vec![Column::PrimaryKey(&T::PRIMARY_KEY)]),
309    )
310}
311
312#[cfg(test)]
313mod tests {
314    use crate::{
315        Bind, Bindable, Column, DataColumn, ForeignKey, PrimaryKey, Table, TimestampColumn,
316        runtime::sql::{self, Bindings},
317    };
318
319    #[derive(sqlx::FromRow)]
320    #[allow(unused)]
321    struct TestTable {
322        id: i32,
323        fk: i32,
324        data: bool,
325    }
326
327    impl Table for TestTable {
328        type PrimaryKey = i32;
329
330        const SCHEMA: &'static str = "public";
331        const TABLE: &'static str = "test";
332
333        const PRIMARY_KEY: PrimaryKey<Self> = PrimaryKey::new("id", "id_sql_col");
334        const FOREIGN_KEYS: &'static [ForeignKey<Self>] = &[ForeignKey::new("fk", "fk_sql_col")];
335        const DATA_COLUMNS: &'static [DataColumn<Self>] =
336            &[DataColumn::new("data", "data_sql_col")];
337        const TIMESTAMP_COLUMNS: &'static [TimestampColumn<Self>] = &[];
338
339        fn pk(&self) -> &Self::PrimaryKey {
340            &self.id
341        }
342    }
343
344    impl Bind for TestTable {
345        fn bind<'q, Q: Bindable<'q>>(&'q self, c: &'q Column<Self>, query: Q) -> crate::Result<Q> {
346            match c.field() {
347                "id" => Ok(query.dyn_bind(self.id)),
348                "fk" => Ok(query.dyn_bind(self.fk)),
349                "data" => Ok(query.dyn_bind(self.data)),
350                _ => unimplemented!(),
351            }
352        }
353    }
354
355    #[test]
356    fn select() {
357        let sql::Query {
358            builder, bindings, ..
359        } = sql::select::<TestTable>();
360
361        assert_eq!(
362            builder.sql(),
363            "SELECT\n  id_sql_col,\n  fk_sql_col,\n  data_sql_col\nFROM\n  \"public\".\"test\"\nWHERE id_sql_col = $1"
364        );
365
366        assert_eq!(
367            bindings,
368            Bindings(vec![Column::PrimaryKey(&TestTable::PRIMARY_KEY),])
369        );
370    }
371
372    #[test]
373    fn insert() {
374        let sql::Query {
375            builder, bindings, ..
376        } = sql::insert::<TestTable>();
377
378        assert_eq!(
379            builder.sql(),
380            "INSERT INTO \"public\".\"test\"\n  (id_sql_col, fk_sql_col, data_sql_col)\nVALUES\n  ($1, $2, $3)"
381        );
382
383        assert_eq!(
384            bindings,
385            Bindings(vec![
386                Column::PrimaryKey(&TestTable::PRIMARY_KEY),
387                Column::ForeignKey(&TestTable::FOREIGN_KEYS[0]),
388                Column::Data(&TestTable::DATA_COLUMNS[0]),
389            ])
390        );
391    }
392
393    #[test]
394    fn update() {
395        let sql::Query {
396            builder, bindings, ..
397        } = sql::update::<TestTable>();
398
399        assert_eq!(
400            builder.sql(),
401            "UPDATE \"public\".\"test\" SET\n  id_sql_col = $1,\n  fk_sql_col = $2,\n  data_sql_col = $3\nWHERE\n  id_sql_col = $1"
402        );
403
404        assert_eq!(
405            bindings,
406            Bindings(vec![
407                Column::PrimaryKey(&TestTable::PRIMARY_KEY),
408                Column::ForeignKey(&TestTable::FOREIGN_KEYS[0]),
409                Column::Data(&TestTable::DATA_COLUMNS[0]),
410            ])
411        );
412    }
413
414    #[test]
415    fn upsert() {
416        let sql::Query {
417            builder, bindings, ..
418        } = sql::upsert::<TestTable>();
419
420        assert_eq!(
421            builder.sql(),
422            "INSERT INTO \"public\".\"test\"\n  (id_sql_col, fk_sql_col, data_sql_col)\nVALUES\n  ($1, $2, $3)\nON CONFLICT(id_sql_col)\nDO UPDATE SET\n  fk_sql_col = EXCLUDED.fk_sql_col,\n  data_sql_col = EXCLUDED.data_sql_col"
423        );
424
425        assert_eq!(
426            bindings,
427            Bindings(vec![
428                Column::PrimaryKey(&TestTable::PRIMARY_KEY),
429                Column::ForeignKey(&TestTable::FOREIGN_KEYS[0]),
430                Column::Data(&TestTable::DATA_COLUMNS[0]),
431            ])
432        );
433    }
434
435    #[test]
436    fn delete() {
437        let sql::Query {
438            builder, bindings, ..
439        } = sql::delete::<TestTable>();
440
441        assert_eq!(
442            builder.sql(),
443            "DELETE FROM \"public\".\"test\" WHERE id_sql_col = $1"
444        );
445        assert_eq!(
446            bindings,
447            Bindings(vec![Column::PrimaryKey(&TestTable::PRIMARY_KEY),])
448        );
449    }
450}