Skip to main content

lutra_sql/
dml.rs

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