Skip to main content

tank_core/writer/
sql_writer.rs

1use crate::*;
2use std::fmt::Write;
3
4/// SQL dialect printer: the user-facing API.
5///
6/// Everything a caller needs lives here. [`SqlWriter`] produces complete
7/// statements (queries and definitions), and is the only trait a user has to
8/// bring into scope. The overridable pieces those statements are built from are
9/// exposed, split by concern, through the fragment traits ([`SqlCoreWriter`],
10/// [`SqlValueWriter`], [`SqlExpressionWriter`], [`SqlFragmentWriter`]) which
11/// driver authors implement.
12///
13/// [`SqlCoreWriter::as_dyn`] upcasts to this view so a fragment can reach any
14/// other fragment.
15pub trait SqlWriter:
16    SqlCoreWriter + SqlValueWriter + SqlExpressionWriter + SqlFragmentWriter
17{
18    /// Write SELECT statement.
19    fn write_select<'a, Data>(&self, out: &mut DynQuery, query: &impl SelectQuery<Data>)
20    where
21        Self: Sized,
22        Data: Dataset + 'a,
23    {
24        let Some(from) = query.get_from() else {
25            log::error!("The query does not have the FROM clause");
26            return;
27        };
28        let columns = query.get_select();
29        let columns_count = columns.clone().into_iter().count();
30        out.buffer().reserve(128 + columns_count * 32);
31        if !out.is_empty() {
32            out.push('\n');
33        }
34        out.push_str("SELECT ");
35        let mut context = Context::new(Fragment::SqlSelect, Data::qualified_columns());
36        if columns_count != 0 {
37            separated_by(
38                out,
39                columns.clone(),
40                |out, col| {
41                    col.write_query(self, &mut context, out);
42                },
43                ", ",
44            );
45        } else {
46            out.push('*');
47        }
48        out.push_str("\nFROM ");
49        from.write_table_name(
50            self,
51            &mut context.switch_fragment(Fragment::SqlSelectFrom).current,
52            out,
53        );
54        if let Some(condition) = query.get_where()
55            && !condition.accept_visitor(&mut IsTrue, self, &mut context, out)
56        {
57            out.push_str("\nWHERE ");
58            condition.write_query(
59                self,
60                &mut context.switch_fragment(Fragment::SqlSelectWhere).current,
61                out,
62            );
63        }
64        let mut group_by = query.get_group_by().peekable();
65        if group_by.peek().is_some() {
66            out.push_str("\nGROUP BY ");
67            let mut context = context.switch_fragment(Fragment::SqlSelectGroupBy);
68            separated_by(
69                out,
70                group_by,
71                |out, col| {
72                    col.write_query(self, &mut context.current, out);
73                },
74                ", ",
75            );
76        }
77        if let Some(having) = query.get_having() {
78            out.push_str("\nHAVING ");
79            having.write_query(
80                self,
81                &mut context.switch_fragment(Fragment::SqlSelectHaving).current,
82                out,
83            );
84        }
85        let mut order_by = query.get_order_by().peekable();
86        if order_by.peek().is_some() {
87            out.push_str("\nORDER BY ");
88            let mut context = context.switch_fragment(Fragment::SqlSelectOrderBy);
89            separated_by(
90                out,
91                order_by,
92                |out, col| {
93                    col.write_query(self, &mut context.current, out);
94                },
95                ", ",
96            );
97        }
98        if let Some(limit) = query.get_limit() {
99            let _ = write!(out, "\nLIMIT {limit}");
100        }
101        out.push(';');
102    }
103
104    /// Write INSERT statement.
105    fn write_insert<It>(&self, out: &mut DynQuery, entities: It, update: bool)
106    where
107        Self: Sized,
108        It: IntoIterator,
109        It::Item: AsEntity,
110    {
111        type E<It> = <<It as IntoIterator>::Item as AsEntity>::Entity;
112        let table = E::<It>::table();
113        let mut entities = entities.into_iter().peekable();
114        if entities.peek().is_none() {
115            return;
116        };
117        let cols = E::<It>::columns().len();
118        out.buffer().reserve(128 + cols * 32);
119        if !out.is_empty() {
120            out.push('\n');
121        }
122        out.push_str("INSERT INTO ");
123        let mut context = Context::new(Fragment::SqlInsertInto, E::<It>::qualified_columns());
124        self.write_table_ref(&mut context, out, table);
125        out.push_str(" (");
126        separated_by(
127            out,
128            E::<It>::columns().iter(),
129            |out, col| {
130                self.write_identifier(&mut context, out, col.name(), true);
131            },
132            ", ",
133        );
134        out.push_str(") VALUES");
135        let mut context = context.switch_fragment(Fragment::SqlInsertIntoValues);
136        separated_by(
137            out,
138            entities,
139            |out, entity| {
140                out.push_str("\n(");
141                entity
142                    .as_entity()
143                    .write_query(self.as_dyn(), &mut context.current, out);
144                out.push(')');
145            },
146            ",",
147        );
148        if update {
149            self.write_insert_update_fragment::<E<It>>(
150                &mut context.current,
151                out,
152                E::<It>::columns().iter(),
153            );
154        }
155        out.push(';');
156    }
157
158    /// Write DELETE statement.
159    fn write_delete<E>(&self, out: &mut DynQuery, condition: impl Expression)
160    where
161        Self: Sized,
162        E: Entity,
163    {
164        let table = E::table();
165        out.buffer().reserve(128);
166        if !out.is_empty() {
167            out.push('\n');
168        }
169        out.push_str("DELETE FROM ");
170        let mut context = Context::new(Fragment::SqlDeleteFrom, E::qualified_columns());
171        self.write_table_ref(&mut context, out, table);
172        out.push_str("\nWHERE ");
173        condition.write_query(
174            self,
175            &mut context
176                .switch_fragment(Fragment::SqlDeleteFromWhere)
177                .current,
178            out,
179        );
180        out.push(';');
181    }
182
183    /// Emit CREATE SCHEMA.
184    fn write_create_schema<E>(&self, out: &mut DynQuery, if_not_exists: bool)
185    where
186        Self: Sized,
187        E: Entity,
188    {
189        let table = E::table();
190        out.buffer().reserve(32 + table.schema.len());
191        if !out.is_empty() {
192            out.push('\n');
193        }
194        out.push_str("CREATE SCHEMA ");
195        let mut context = Context::new(Fragment::SqlCreateSchema, E::qualified_columns());
196        if if_not_exists {
197            out.push_str("IF NOT EXISTS ");
198        }
199        self.write_identifier(&mut context, out, &table.schema, true);
200        out.push(';');
201    }
202
203    /// Emit DROP SCHEMA.
204    fn write_drop_schema<E>(&self, out: &mut DynQuery, if_exists: bool)
205    where
206        Self: Sized,
207        E: Entity,
208    {
209        let mut context = Context::new(Fragment::SqlDropSchema, E::qualified_columns());
210        let table = E::table();
211        out.buffer().reserve(32 + table.schema.len());
212        if !out.is_empty() {
213            out.push('\n');
214        }
215        out.push_str("DROP SCHEMA ");
216        if if_exists {
217            out.push_str("IF EXISTS ");
218        }
219        self.write_identifier(&mut context, out, &table.schema, true);
220        out.push(';');
221    }
222
223    /// Emit CREATE TABLE with columns, constraints & comments.
224    fn write_create_table<E>(&self, out: &mut DynQuery, if_not_exists: bool)
225    where
226        Self: Sized,
227        E: Entity,
228    {
229        let mut context = Context::new(Fragment::SqlCreateTable, E::qualified_columns());
230        let table = E::table();
231        let estimated = 128 + E::columns().len() * 64 + E::primary_key_def().len() * 24;
232        out.buffer().reserve(estimated);
233        if !out.is_empty() {
234            out.push('\n');
235        }
236        out.push_str("CREATE TABLE ");
237        if if_not_exists {
238            out.push_str("IF NOT EXISTS ");
239        }
240        self.write_table_ref(&mut context, out, table);
241        out.push_str(" (\n");
242        separated_by(
243            out,
244            E::columns(),
245            |out, col| {
246                self.write_create_table_column_fragment(&mut context, out, col);
247            },
248            ",\n",
249        );
250        let pk = E::primary_key_def();
251        if pk.len() > 1 {
252            self.write_create_table_primary_key_fragment(&mut context, out, pk.iter().map(|v| *v));
253        }
254        for unique in E::unique_defs() {
255            if unique.len() > 0 {
256                out.push_str(",\nUNIQUE (");
257                separated_by(
258                    out,
259                    unique,
260                    |out, col| {
261                        self.write_identifier(
262                            &mut context
263                                .switch_fragment(Fragment::SqlCreateTableUnique)
264                                .current,
265                            out,
266                            col.name(),
267                            true,
268                        );
269                    },
270                    ", ",
271                );
272                out.push(')');
273            }
274        }
275        let foreign_keys = E::columns().iter().filter(|c| c.references.is_some());
276        separated_by(
277            out,
278            foreign_keys,
279            |out, column| {
280                let references = column.references.as_ref().unwrap();
281                out.push_str(",\nFOREIGN KEY (");
282                self.write_identifier(&mut context, out, &column.name(), true);
283                out.push_str(") REFERENCES ");
284                self.write_table_ref(&mut context, out, &references.table());
285                out.push('(');
286                self.write_column_ref(&mut context, out, references);
287                out.push(')');
288                if let Some(on_delete) = &column.on_delete {
289                    out.push_str(" ON DELETE ");
290                    self.write_create_table_references_action_fragment(
291                        &mut context,
292                        out,
293                        on_delete,
294                    );
295                }
296                if let Some(on_update) = &column.on_update {
297                    out.push_str(" ON UPDATE ");
298                    self.write_create_table_references_action_fragment(
299                        &mut context,
300                        out,
301                        on_update,
302                    );
303                }
304            },
305            "",
306        );
307        out.push_str(");");
308        self.write_column_comments_statements_fragment::<E>(&mut context, out);
309    }
310
311    /// Write DROP TABLE statement.
312    fn write_drop_table<E>(&self, out: &mut DynQuery, if_exists: bool)
313    where
314        Self: Sized,
315        E: Entity,
316    {
317        let table = E::table();
318        out.buffer()
319            .reserve(24 + table.schema.len() + table.name.len());
320        if !out.is_empty() {
321            out.push('\n');
322        }
323        out.push_str("DROP TABLE ");
324        let mut context = Context::new(Fragment::SqlDropTable, E::qualified_columns());
325        if if_exists {
326            out.push_str("IF EXISTS ");
327        }
328        self.write_table_ref(&mut context, out, table);
329        out.push(';');
330    }
331
332    /// Emit BEGIN statement.
333    fn write_transaction_begin(&self, out: &mut DynQuery) {
334        out.push_str("BEGIN;");
335    }
336
337    /// Emit COMMIT statement.
338    fn write_transaction_commit(&self, out: &mut DynQuery) {
339        out.push_str("COMMIT;");
340    }
341
342    /// Emit ROLLBACK statement.
343    fn write_transaction_rollback(&self, out: &mut DynQuery) {
344        out.push_str("ROLLBACK;");
345    }
346}
347
348/// Generic SQL writer.
349pub struct GenericSqlWriter;
350impl GenericSqlWriter {
351    /// New generic writer.
352    pub fn new() -> Self {
353        Self {}
354    }
355}
356impl SqlCoreWriter for GenericSqlWriter {
357    fn as_dyn(&self) -> &dyn SqlWriter {
358        self
359    }
360}
361impl SqlValueWriter for GenericSqlWriter {}
362impl SqlExpressionWriter for GenericSqlWriter {}
363impl SqlFragmentWriter for GenericSqlWriter {}
364impl SqlWriter for GenericSqlWriter {}