keelson_core/clause/
values.rs1use crate::expr::{Expr, IntoExpr, IntoExprList};
2use crate::writer::{Expression, SqlWriter};
3
4use super::{MaybeAbsent, write_present};
5
6#[derive(Debug, Clone, Default)]
21pub struct Values {
22 pub query: Option<Expr>,
27 pub rows: Vec<ValuesRow>,
29}
30
31impl Values {
32 pub fn from_query(query: impl IntoExpr) -> Self {
34 Values {
35 query: Some(query.into_expr()),
36 rows: Vec::new(),
37 }
38 }
39
40 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 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
69pub trait HasValues {
71 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#[derive(Debug, Clone, Default)]
86pub struct ValuesRow(
87 pub Vec<Expr>,
89);
90
91impl ValuesRow {
92 pub fn new(cells: impl IntoExprList) -> Self {
94 ValuesRow(cells.into_expr_list())
95 }
96
97 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 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 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 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}