Skip to main content

lutra_sql/
dml.rs

1#[cfg(not(feature = "std"))]
2use alloc::{
3    boxed::Box,
4    format,
5    string::{String, ToString},
6    vec::Vec,
7};
8
9use core::fmt::{self, Display};
10
11use crate::{RelNamed, SelectInto, TableAlias, Values};
12
13use super::display_utils::{Indent, SpaceOrNewline, indented_list};
14use super::{Expr, Ident, ObjectName, OrderByExpr, Query, SelectItem, display_comma_separated};
15
16/// INSERT statement.
17#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
18pub struct Insert {
19    /// TABLE
20    pub table: ObjectName,
21    /// COLUMNS
22    pub columns: Vec<Ident>,
23    /// A SQL query that specifies what to insert
24    pub source: Box<Query>,
25}
26
27impl Display for Insert {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "INSERT INTO {} ", self.table)?;
30
31        if !self.columns.is_empty() {
32            write!(f, "({})", display_comma_separated(&self.columns))?;
33            SpaceOrNewline.fmt(f)?;
34        }
35        self.source.fmt(f)?;
36        Ok(())
37    }
38}
39
40/// DELETE statement.
41#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
42pub struct Delete {
43    /// Multi tables delete are supported in mysql
44    pub tables: Vec<ObjectName>,
45    /// FROM
46    pub from: FromTable,
47    /// USING (Snowflake, Postgres, MySQL)
48    pub using: Option<Vec<RelNamed>>,
49    /// WHERE
50    pub selection: Option<Expr>,
51    /// RETURNING
52    pub returning: Option<Vec<SelectItem>>,
53    /// ORDER BY (MySQL)
54    pub order_by: Vec<OrderByExpr>,
55    /// LIMIT (MySQL)
56    pub limit: Option<Expr>,
57}
58
59impl Display for Delete {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.write_str("DELETE")?;
62        if !self.tables.is_empty() {
63            indented_list(f, &self.tables)?;
64        }
65        match &self.from {
66            FromTable::WithFromKeyword(from) => {
67                f.write_str(" FROM")?;
68                indented_list(f, from)?;
69            }
70            FromTable::WithoutKeyword(from) => {
71                indented_list(f, from)?;
72            }
73        }
74        if let Some(using) = &self.using {
75            SpaceOrNewline.fmt(f)?;
76            f.write_str("USING")?;
77            indented_list(f, using)?;
78        }
79        if let Some(selection) = &self.selection {
80            SpaceOrNewline.fmt(f)?;
81            f.write_str("WHERE")?;
82            SpaceOrNewline.fmt(f)?;
83            Indent(selection).fmt(f)?;
84        }
85        if let Some(returning) = &self.returning {
86            SpaceOrNewline.fmt(f)?;
87            f.write_str("RETURNING")?;
88            indented_list(f, returning)?;
89        }
90        if !self.order_by.is_empty() {
91            SpaceOrNewline.fmt(f)?;
92            f.write_str("ORDER BY")?;
93            indented_list(f, &self.order_by)?;
94        }
95        if let Some(limit) = &self.limit {
96            SpaceOrNewline.fmt(f)?;
97            f.write_str("LIMIT")?;
98            SpaceOrNewline.fmt(f)?;
99            Indent(limit).fmt(f)?;
100        }
101        Ok(())
102    }
103}
104/// A `FROM` clause within a `DELETE` statement.
105///
106/// Syntax
107/// ```sql
108/// [FROM] table
109/// ```
110#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
111pub enum FromTable {
112    /// An explicit `FROM` keyword was specified.
113    WithFromKeyword(Vec<RelNamed>),
114    /// BigQuery: `FROM` keyword was omitted.
115    /// <https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#delete_statement>
116    WithoutKeyword(Vec<RelNamed>),
117}
118impl Display for FromTable {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            FromTable::WithFromKeyword(tables) => {
122                write!(f, "FROM {}", display_comma_separated(tables))
123            }
124            FromTable::WithoutKeyword(tables) => {
125                write!(f, "{}", display_comma_separated(tables))
126            }
127        }
128    }
129}
130
131#[allow(clippy::large_enum_variant)]
132#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
133pub struct Update {
134    /// TABLE
135    pub table: ObjectName,
136    /// AS
137    pub alias: Option<TableAlias>,
138    /// Column assignments
139    pub assignments: Vec<Assignment>,
140    /// Relations which provide value to be set
141    pub from: Vec<RelNamed>,
142    /// WHERE
143    pub selection: Option<Expr>,
144    /// RETURNING
145    pub returning: Option<Vec<SelectItem>>,
146    /// LIMIT
147    pub limit: Option<Expr>,
148}
149
150impl fmt::Display for Update {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        f.write_str("UPDATE ")?;
153        self.table.fmt(f)?;
154        if let Some(alias) = &self.alias {
155            f.write_str(" AS ")?;
156            alias.fmt(f)?;
157        }
158        if !self.assignments.is_empty() {
159            SpaceOrNewline.fmt(f)?;
160            f.write_str("SET")?;
161            indented_list(f, &self.assignments)?;
162        }
163        if !self.from.is_empty() {
164            SpaceOrNewline.fmt(f)?;
165            f.write_str("FROM")?;
166            indented_list(f, &self.from)?;
167        }
168        if let Some(selection) = &self.selection {
169            SpaceOrNewline.fmt(f)?;
170            f.write_str("WHERE")?;
171            SpaceOrNewline.fmt(f)?;
172            Indent(selection).fmt(f)?;
173        }
174        if let Some(returning) = &self.returning {
175            SpaceOrNewline.fmt(f)?;
176            f.write_str("RETURNING")?;
177            indented_list(f, returning)?;
178        }
179        if let Some(limit) = &self.limit {
180            SpaceOrNewline.fmt(f)?;
181            write!(f, "LIMIT {limit}")?;
182        }
183        Ok(())
184    }
185}
186
187/// SQL assignment `foo = expr` as used in Update
188#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
189pub struct Assignment {
190    pub target: AssignmentTarget,
191    pub value: Expr,
192}
193
194impl fmt::Display for Assignment {
195    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
196        write!(f, "{} = {}", self.target, self.value)
197    }
198}
199
200/// Left-hand side of an assignment in an UPDATE statement,
201/// e.g. `foo` in `foo = 5` (ColumnName assignment) or
202/// `(a, b)` in `(a, b) = (1, 2)` (Tuple assignment).
203#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
204pub enum AssignmentTarget {
205    /// A single column
206    ColumnName(ObjectName),
207    /// A tuple of columns
208    Tuple(Vec<ObjectName>),
209}
210
211impl fmt::Display for AssignmentTarget {
212    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213        match self {
214            AssignmentTarget::ColumnName(column) => write!(f, "{column}"),
215            AssignmentTarget::Tuple(columns) => write!(f, "({})", display_comma_separated(columns)),
216        }
217    }
218}
219
220/// Variant of `WHEN` clause used within a `MERGE` Statement.
221///
222/// Example:
223/// ```sql
224/// MERGE INTO T USING U ON FALSE WHEN MATCHED THEN DELETE
225/// ```
226/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
227/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
228#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
229pub enum MergeClauseKind {
230    /// `WHEN MATCHED`
231    Matched,
232    /// `WHEN NOT MATCHED`
233    NotMatched,
234    /// `WHEN MATCHED BY TARGET`
235    ///
236    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
237    NotMatchedByTarget,
238    /// `WHEN MATCHED BY SOURCE`
239    ///
240    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
241    NotMatchedBySource,
242}
243
244impl Display for MergeClauseKind {
245    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246        match self {
247            MergeClauseKind::Matched => write!(f, "MATCHED"),
248            MergeClauseKind::NotMatched => write!(f, "NOT MATCHED"),
249            MergeClauseKind::NotMatchedByTarget => write!(f, "NOT MATCHED BY TARGET"),
250            MergeClauseKind::NotMatchedBySource => write!(f, "NOT MATCHED BY SOURCE"),
251        }
252    }
253}
254
255/// The type of expression used to insert rows within a `MERGE` statement.
256///
257/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
258/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
259#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
260pub enum MergeInsertKind {
261    /// The insert expression is defined from an explicit `VALUES` clause
262    ///
263    /// Example:
264    /// ```sql
265    /// INSERT VALUES(product, quantity)
266    /// ```
267    Values(Values),
268    /// The insert expression is defined using only the `ROW` keyword.
269    ///
270    /// Example:
271    /// ```sql
272    /// INSERT ROW
273    /// ```
274    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
275    Row,
276}
277
278impl Display for MergeInsertKind {
279    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
280        match self {
281            MergeInsertKind::Values(values) => {
282                write!(f, "{values}")
283            }
284            MergeInsertKind::Row => {
285                write!(f, "ROW")
286            }
287        }
288    }
289}
290
291/// The expression used to insert rows within a `MERGE` statement.
292///
293/// Examples
294/// ```sql
295/// INSERT (product, quantity) VALUES(product, quantity)
296/// INSERT ROW
297/// ```
298///
299/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
300/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
301#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
302pub struct MergeInsertExpr {
303    /// Columns (if any) specified by the insert.
304    ///
305    /// Example:
306    /// ```sql
307    /// INSERT (product, quantity) VALUES(product, quantity)
308    /// INSERT (product, quantity) ROW
309    /// ```
310    pub columns: Vec<Ident>,
311    /// The insert type used by the statement.
312    pub kind: MergeInsertKind,
313}
314
315impl Display for MergeInsertExpr {
316    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
317        if !self.columns.is_empty() {
318            write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?;
319        }
320        write!(f, "{}", self.kind)
321    }
322}
323
324/// Underlying statement of a when clause within a `MERGE` Statement
325///
326/// Example
327/// ```sql
328/// INSERT (product, quantity) VALUES(product, quantity)
329/// ```
330///
331/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
332/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
333#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
334pub enum MergeAction {
335    /// An `INSERT` clause
336    ///
337    /// Example:
338    /// ```sql
339    /// INSERT (product, quantity) VALUES(product, quantity)
340    /// ```
341    Insert(MergeInsertExpr),
342    /// An `UPDATE` clause
343    ///
344    /// Example:
345    /// ```sql
346    /// UPDATE SET quantity = T.quantity + S.quantity
347    /// ```
348    Update { assignments: Vec<Assignment> },
349    /// A plain `DELETE` clause
350    Delete,
351}
352
353impl Display for MergeAction {
354    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
355        match self {
356            MergeAction::Insert(insert) => {
357                write!(f, "INSERT {insert}")
358            }
359            MergeAction::Update { assignments } => {
360                write!(f, "UPDATE SET {}", display_comma_separated(assignments))
361            }
362            MergeAction::Delete => {
363                write!(f, "DELETE")
364            }
365        }
366    }
367}
368
369/// A when clause within a `MERGE` Statement
370///
371/// Example:
372/// ```sql
373/// WHEN NOT MATCHED BY SOURCE AND product LIKE '%washer%' THEN DELETE
374/// ```
375/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
376/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
377#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
378pub struct MergeClause {
379    pub clause_kind: MergeClauseKind,
380    pub predicate: Option<Expr>,
381    pub action: MergeAction,
382}
383
384impl Display for MergeClause {
385    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
386        let MergeClause {
387            clause_kind,
388            predicate,
389            action,
390        } = self;
391
392        write!(f, "WHEN {clause_kind}")?;
393        if let Some(pred) = predicate {
394            write!(f, " AND {pred}")?;
395        }
396        write!(f, " THEN {action}")
397    }
398}
399
400/// A Output Clause in the end of a 'MERGE' Statement
401///
402/// Example:
403/// OUTPUT $action, deleted.* INTO dbo.temp_products;
404/// [mssql](https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql)
405#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
406pub enum OutputClause {
407    Output {
408        select_items: Vec<SelectItem>,
409        into_table: Option<SelectInto>,
410    },
411    Returning {
412        select_items: Vec<SelectItem>,
413    },
414}
415
416impl fmt::Display for OutputClause {
417    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
418        match self {
419            OutputClause::Output {
420                select_items,
421                into_table,
422            } => {
423                f.write_str("OUTPUT ")?;
424                display_comma_separated(select_items).fmt(f)?;
425                if let Some(into_table) = into_table {
426                    f.write_str(" ")?;
427                    into_table.fmt(f)?;
428                }
429                Ok(())
430            }
431            OutputClause::Returning { select_items } => {
432                f.write_str("RETURNING ")?;
433                display_comma_separated(select_items).fmt(f)
434            }
435        }
436    }
437}