Skip to main content

keelson_core/clause/
values.rs

1use crate::expr::{Expr, IntoExpr, IntoExprList};
2use crate::writer::{Expression, SqlWriter};
3
4use super::{MaybeAbsent, write_present};
5
6/// Where the rows an `INSERT` adds come from.
7///
8/// Two shapes, in priority order — a query to insert from, or a list of rows:
9///
10/// ```text
11/// INSERT INTO t (cols) VALUES ( expr [, ...] ) [, ...]
12/// INSERT INTO t (cols) query
13/// ```
14///
15/// bob has a third: with neither it writes `DEFAULT VALUES`. That is not done here,
16/// because an absent clause has to render nothing, and because the spelling is not
17/// shared — PostgreSQL and SQLite say `DEFAULT VALUES`, MySQL says `VALUES ()` or
18/// `() VALUES ()`. An `INSERT` query type checks [`is_empty`](Self::is_empty) and
19/// writes its own dialect's spelling.
20#[derive(Debug, Clone, Default)]
21pub struct Values {
22    /// A query to insert the results of. Takes priority over
23    /// [`rows`](Self::rows), because `INSERT … VALUES … SELECT …` is not a thing:
24    /// the two are alternatives, and a query having been set is the more
25    /// deliberate act.
26    pub query: Option<Expr>,
27    /// One entry per row.
28    pub rows: Vec<ValuesRow>,
29}
30
31impl Values {
32    /// Insert the results of `query`.
33    pub fn from_query(query: impl IntoExpr) -> Self {
34        Values {
35            query: Some(query.into_expr()),
36            rows: Vec::new(),
37        }
38    }
39
40    /// Append one row.
41    ///
42    /// An empty row is ignored: `VALUES ()` is not valid in PostgreSQL or SQLite,
43    /// and an insert with no values wants its dialect's "default row" spelling
44    /// instead.
45    pub fn append_values(&mut self, values: impl IntoExprList) {
46        let row = values.into_expr_list();
47        if row.is_empty() {
48            return;
49        }
50        self.rows.push(ValuesRow(row));
51    }
52
53    /// Whether there is no row source at all.
54    pub fn is_empty(&self) -> bool {
55        self.query.is_none() && self.rows.is_empty()
56    }
57}
58
59impl Expression for Values {
60    fn write_sql(&self, w: &mut SqlWriter<'_>) {
61        if let Some(query) = &self.query {
62            w.write_expr(query);
63            return;
64        }
65        write_present(w, &self.rows, "VALUES ", ", ", "");
66    }
67}
68
69/// An `INSERT` with a row source.
70pub trait HasValues {
71    /// The row source to modify.
72    fn values_mut(&mut self) -> &mut Values;
73}
74
75impl HasValues for Values {
76    fn values_mut(&mut self) -> &mut Values {
77        self
78    }
79}
80
81/// One parenthesised row of a `VALUES` list.
82///
83/// Named `ValuesRow` rather than bob's `Value` so that it cannot be confused with
84/// [`Value`](crate::Value), the bound-argument enum.
85#[derive(Debug, Clone, Default)]
86pub struct ValuesRow(
87    /// The cells, in column order.
88    pub Vec<Expr>,
89);
90
91impl ValuesRow {
92    /// A row from any expression list.
93    pub fn new(cells: impl IntoExprList) -> Self {
94        ValuesRow(cells.into_expr_list())
95    }
96
97    /// Whether the row has no cells.
98    pub fn is_empty(&self) -> bool {
99        self.0.is_empty()
100    }
101}
102
103impl Expression for ValuesRow {
104    fn write_sql(&self, w: &mut SqlWriter<'_>) {
105        w.write_slice(&self.0, "(", ", ", ")");
106    }
107}
108
109impl MaybeAbsent for ValuesRow {
110    fn is_absent(&self) -> bool {
111        self.is_empty()
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use keelson_sqlcheck::testing::assert_frag_sql;
118
119    use super::*;
120    use crate::dialect::testing::Numbered;
121    use crate::expr::arg;
122    use crate::value::Value;
123    use crate::writer::build;
124
125    /// A `VALUES` list is the tail of an `INSERT`, and its width has to match the
126    /// column list — a mismatch parses and is caught by the engine.
127    const TWO_COL_FRAME: &str = r#"INSERT INTO tags ("id", "name") {}"#;
128    const THREE_COL_FRAME: &str = r#"INSERT INTO users ("id", "name", "age") {}"#;
129    const ONE_COL_FRAME: &str = r#"INSERT INTO tags ("id") {}"#;
130
131    fn sql(v: &Values) -> String {
132        build(&Numbered, v).expect("render").0
133    }
134
135    #[test]
136    fn no_query_and_no_rows_writes_nothing() {
137        // Not framed: an `INSERT` with neither VALUES nor a source query is not a
138        // statement, so there is nothing to judge. That the clause contributes
139        // nothing is the whole assertion.
140        assert_eq!(build(&Numbered, &Values::default()).unwrap().0, "");
141        assert!(Values::default().is_empty());
142    }
143
144    #[test]
145    fn rows_are_parenthesised_and_numbered_across_the_whole_list() {
146        let mut v = Values::default();
147        v.append_values((arg(1i32), arg("a")));
148        v.append_values((arg(2i32), arg("b")));
149
150        let (rendered, args) = build(&Numbered, &v).unwrap();
151        assert_frag_sql(TWO_COL_FRAME, &rendered, "VALUES ($1, $2), ($3, $4)");
152        assert_eq!(
153            args,
154            vec![
155                Value::I32(1),
156                Value::Text("a".into()),
157                Value::I32(2),
158                Value::Text("b".into())
159            ]
160        );
161    }
162
163    #[test]
164    fn a_cell_may_be_a_literal_a_keyword_or_a_sub_select() {
165        let mut v = Values::default();
166        v.append_values((
167            arg(1i32),
168            "DEFAULT",
169            Expr::group(Expr::raw("SELECT max(id) FROM users")),
170        ));
171        assert_frag_sql(
172            THREE_COL_FRAME,
173            &sql(&v),
174            "VALUES ($1, DEFAULT, (SELECT max(id) FROM users))",
175        );
176    }
177
178    #[test]
179    fn an_empty_row_is_dropped_rather_than_written_as_empty_parentheses() {
180        // Not framed, for the same reason as the no-rows case above.
181        let mut v = Values::default();
182        v.append_values(());
183        assert!(v.rows.is_empty());
184        assert_eq!(build(&Numbered, &v).unwrap().0, "");
185        assert!(ValuesRow::default().is_empty());
186    }
187
188    #[test]
189    fn a_query_wins_over_recorded_rows_and_writes_no_values_keyword() {
190        let mut v = Values::default();
191        v.append_values(arg(1i32));
192        v.query = Some(Expr::raw(r#"SELECT "id" FROM posts"#));
193
194        let (rendered, args) = build(&Numbered, &v).unwrap();
195        assert_frag_sql(ONE_COL_FRAME, &rendered, r#"SELECT "id" FROM posts"#);
196        assert!(
197            args.is_empty(),
198            "the dropped rows must not leave their arguments behind"
199        );
200
201        assert_frag_sql(
202            ONE_COL_FRAME,
203            &sql(&Values::from_query(Expr::raw("SELECT 1"))),
204            "SELECT 1",
205        );
206    }
207}