use crate::expr::{Expr, IntoExpr, IntoExprList};
use crate::writer::{Expression, SqlWriter};
use super::{MaybeAbsent, write_present};
#[derive(Debug, Clone, Default)]
pub struct Values {
pub query: Option<Expr>,
pub rows: Vec<ValuesRow>,
}
impl Values {
pub fn from_query(query: impl IntoExpr) -> Self {
Values {
query: Some(query.into_expr()),
rows: Vec::new(),
}
}
pub fn append_values(&mut self, values: impl IntoExprList) {
let row = values.into_expr_list();
if row.is_empty() {
return;
}
self.rows.push(ValuesRow(row));
}
pub fn is_empty(&self) -> bool {
self.query.is_none() && self.rows.is_empty()
}
}
impl Expression for Values {
fn write_sql(&self, w: &mut SqlWriter<'_>) {
if let Some(query) = &self.query {
w.write_expr(query);
return;
}
write_present(w, &self.rows, "VALUES ", ", ", "");
}
}
pub trait HasValues {
fn values_mut(&mut self) -> &mut Values;
}
impl HasValues for Values {
fn values_mut(&mut self) -> &mut Values {
self
}
}
#[derive(Debug, Clone, Default)]
pub struct ValuesRow(
pub Vec<Expr>,
);
impl ValuesRow {
pub fn new(cells: impl IntoExprList) -> Self {
ValuesRow(cells.into_expr_list())
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl Expression for ValuesRow {
fn write_sql(&self, w: &mut SqlWriter<'_>) {
w.write_slice(&self.0, "(", ", ", ")");
}
}
impl MaybeAbsent for ValuesRow {
fn is_absent(&self) -> bool {
self.is_empty()
}
}
#[cfg(test)]
mod tests {
use keelson_sqlcheck::testing::assert_frag_sql;
use super::*;
use crate::dialect::testing::Numbered;
use crate::expr::arg;
use crate::value::Value;
use crate::writer::build;
const TWO_COL_FRAME: &str = r#"INSERT INTO tags ("id", "name") {}"#;
const THREE_COL_FRAME: &str = r#"INSERT INTO users ("id", "name", "age") {}"#;
const ONE_COL_FRAME: &str = r#"INSERT INTO tags ("id") {}"#;
fn sql(v: &Values) -> String {
build(&Numbered, v).expect("render").0
}
#[test]
fn no_query_and_no_rows_writes_nothing() {
assert_eq!(build(&Numbered, &Values::default()).unwrap().0, "");
assert!(Values::default().is_empty());
}
#[test]
fn rows_are_parenthesised_and_numbered_across_the_whole_list() {
let mut v = Values::default();
v.append_values((arg(1i32), arg("a")));
v.append_values((arg(2i32), arg("b")));
let (rendered, args) = build(&Numbered, &v).unwrap();
assert_frag_sql(TWO_COL_FRAME, &rendered, "VALUES ($1, $2), ($3, $4)");
assert_eq!(
args,
vec![
Value::I32(1),
Value::Text("a".into()),
Value::I32(2),
Value::Text("b".into())
]
);
}
#[test]
fn a_cell_may_be_a_literal_a_keyword_or_a_sub_select() {
let mut v = Values::default();
v.append_values((
arg(1i32),
"DEFAULT",
Expr::group(Expr::raw("SELECT max(id) FROM users")),
));
assert_frag_sql(
THREE_COL_FRAME,
&sql(&v),
"VALUES ($1, DEFAULT, (SELECT max(id) FROM users))",
);
}
#[test]
fn an_empty_row_is_dropped_rather_than_written_as_empty_parentheses() {
let mut v = Values::default();
v.append_values(());
assert!(v.rows.is_empty());
assert_eq!(build(&Numbered, &v).unwrap().0, "");
assert!(ValuesRow::default().is_empty());
}
#[test]
fn a_query_wins_over_recorded_rows_and_writes_no_values_keyword() {
let mut v = Values::default();
v.append_values(arg(1i32));
v.query = Some(Expr::raw(r#"SELECT "id" FROM posts"#));
let (rendered, args) = build(&Numbered, &v).unwrap();
assert_frag_sql(ONE_COL_FRAME, &rendered, r#"SELECT "id" FROM posts"#);
assert!(
args.is_empty(),
"the dropped rows must not leave their arguments behind"
);
assert_frag_sql(
ONE_COL_FRAME,
&sql(&Values::from_query(Expr::raw("SELECT 1"))),
"SELECT 1",
);
}
}