use std::borrow::Cow;
use crate::error::Error;
use crate::value::{ToValue, Value};
use crate::writer::SqlWriter;
use super::convert::IntoExpr;
use super::node::Expr;
#[derive(Debug, Clone)]
pub enum RawArg {
Value(Value),
Expr(Expr),
Named(Cow<'static, str>),
}
impl RawArg {
pub fn value(v: impl ToValue) -> RawArg {
RawArg::Value(v.to_value())
}
pub fn expr(e: impl IntoExpr) -> RawArg {
RawArg::Expr(e.into_expr())
}
pub fn named(name: impl Into<Cow<'static, str>>) -> RawArg {
RawArg::Named(name.into())
}
}
pub(super) fn write_template(w: &mut SqlWriter<'_>, sql: &str, args: &[RawArg]) {
let mut rest = sql;
let mut placeholders = 0usize;
loop {
let Some(mark) = rest.find('?') else {
w.push_str(rest);
break;
};
if let Some(escape) = rest.find("\\?").filter(|escape| mark > *escape) {
w.push_str(&rest[..escape]);
w.push_str("?");
rest = &rest[escape + 2..];
continue;
}
w.push_str(&rest[..mark]);
match args.get(placeholders) {
Some(RawArg::Value(v)) => w.push_arg(v.clone()),
Some(RawArg::Expr(e)) => w.write_expr(e),
Some(RawArg::Named(name)) => w.push_named_arg(name),
None => w.push_arg(Value::Null),
}
placeholders += 1;
rest = &rest[mark + 1..];
}
if placeholders != args.len() {
w.record_error(Error::raw_arg_count(placeholders, args.len(), sql));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dialect::testing::{Numbered, TestDialect};
use crate::writer::build;
mod bob_raw_test {
use super::*;
#[test]
fn plain() {
let (sql, args) = build(
&TestDialect,
&Expr::template("SELECT a, b FROM alphabet", []),
)
.unwrap();
assert_eq!(sql, "SELECT a, b FROM alphabet");
assert!(args.is_empty());
}
#[test]
fn escaped_args() {
let e = Expr::template(
r#"SELECT a, b FROM "alphabet\?" WHERE c = ? AND d <= ?"#,
[RawArg::value(1i32), RawArg::value(2i32)],
);
let (sql, args) = build(&TestDialect, &e).unwrap();
assert_eq!(
sql,
r#"SELECT a, b FROM "alphabet?" WHERE c = ?1 AND d <= ?2"#
);
assert_eq!(args, vec![Value::I32(1), Value::I32(2)]);
}
#[test]
fn mismatched_args_and_placeholders() {
let e = Expr::template("SELECT a, b FROM alphabet WHERE c = ? AND d <= ?", []);
let mut w = SqlWriter::new(&TestDialect);
w.write_expr(&e);
assert_eq!(
w.sql(),
"SELECT a, b FROM alphabet WHERE c = ?1 AND d <= ?2"
);
let err = w.finish().unwrap_err();
assert_eq!(
err.to_string(),
"Bad Statement: has 2 placeholders but 0 args: \
SELECT a, b FROM alphabet WHERE c = ? AND d <= ?"
);
}
#[test]
fn numbered_args() {
let e = Expr::template(
"SELECT a, b FROM alphabet WHERE c = ? AND d <= ?",
[RawArg::value(1i32), RawArg::value(2i32)],
);
let (sql, args) = build(&TestDialect, &e).unwrap();
assert_eq!(sql, "SELECT a, b FROM alphabet WHERE c = ?1 AND d <= ?2");
assert_eq!(args, vec![Value::I32(1), Value::I32(2)]);
}
#[test]
fn expr_args() {
let e = Expr::template(
"SELECT a, b FROM alphabet WHERE c IN (?) AND d <= ?",
[RawArg::expr(Expr::args([5i32, 6, 7])), RawArg::value(2i32)],
);
let (sql, args) = build(&TestDialect, &e).unwrap();
assert_eq!(
sql,
"SELECT a, b FROM alphabet WHERE c IN (?1, ?2, ?3) AND d <= ?4"
);
assert_eq!(
args,
vec![Value::I32(5), Value::I32(6), Value::I32(7), Value::I32(2)]
);
}
#[test]
fn expr_args_group() {
let e = Expr::template(
"SELECT a, b FROM alphabet WHERE c IN ? AND d <= ?",
[
RawArg::expr(crate::expr::arg_group([5i32, 6, 7])),
RawArg::value(2i32),
],
);
let (sql, args) = build(&TestDialect, &e).unwrap();
assert_eq!(
sql,
"SELECT a, b FROM alphabet WHERE c IN (?1, ?2, ?3) AND d <= ?4"
);
assert_eq!(
args,
vec![Value::I32(5), Value::I32(6), Value::I32(7), Value::I32(2)]
);
}
#[test]
fn expr_args_quote() {
let e = Expr::template(
"SELECT a, b FROM alphabet WHERE c = ? AND d <= ?",
[RawArg::expr(Expr::ident("AA")), RawArg::value(2i32)],
);
let (sql, args) = build(&TestDialect, &e).unwrap();
assert_eq!(
sql,
r#"SELECT a, b FROM alphabet WHERE c = "AA" AND d <= ?1"#
);
assert_eq!(args, vec![Value::I32(2)]);
}
}
#[test]
fn a_lone_escape_before_a_real_placeholder() {
let e = Expr::template(r"a\?b?c", [RawArg::value(1i32)]);
let (sql, _) = build(&Numbered, &e).unwrap();
assert_eq!(sql, "a?b$1c");
}
#[test]
fn an_escape_after_a_real_placeholder_is_still_honoured() {
let e = Expr::template(r"a?b\?c", [RawArg::value(1i32)]);
let (sql, _) = build(&Numbered, &e).unwrap();
assert_eq!(sql, "a$1b?c");
}
#[test]
fn consecutive_escapes_stay_literal() {
let e = Expr::template(r"\?\?", []);
let (sql, args) = build(&Numbered, &e).unwrap();
assert_eq!(sql, "??");
assert!(args.is_empty());
}
#[test]
fn a_trailing_placeholder_ends_the_scan() {
let e = Expr::template("a = ?", [RawArg::value(9i32)]);
assert_eq!(build(&Numbered, &e).unwrap().0, "a = $1");
}
#[test]
fn too_many_args_is_also_a_mismatch() {
let e = Expr::template("a = ?", [RawArg::value(1i32), RawArg::value(2i32)]);
let err = build(&Numbered, &e).unwrap_err();
assert_eq!(
err.to_string(),
"Bad Statement: has 1 placeholders but 2 args: a = ?"
);
}
#[test]
fn a_named_replacement_consumes_a_placeholder_but_no_position() {
let e = Expr::template(
"a = ? AND b = ? AND c = ?",
[RawArg::value(1i32), RawArg::named("b"), RawArg::value(3i32)],
);
let (sql, args) = build(&TestDialect, &e).unwrap();
assert_eq!(sql, "a = ?1 AND b = :b AND c = ?2");
assert_eq!(args, vec![Value::I32(1), Value::I32(3)]);
}
#[test]
fn a_named_replacement_fails_on_a_dialect_without_named_arguments() {
let e = Expr::template("a = ?", [RawArg::named("a")]);
assert!(matches!(build(&Numbered, &e), Err(Error::NoNamedArgs)));
}
#[test]
fn multibyte_text_around_a_placeholder_is_not_sliced_mid_character() {
let e = Expr::template(
"名前 = ? AND 年齢 > ?",
[RawArg::value("さくら"), RawArg::value(20i32)],
);
let (sql, _) = build(&Numbered, &e).unwrap();
assert_eq!(sql, "名前 = $1 AND 年齢 > $2");
}
#[test]
fn a_template_continues_the_surrounding_numbering() {
let outer = Expr::join([
Expr::arg(1i32),
Expr::template("f(?, ?)", [RawArg::value(2i32), RawArg::value(3i32)]),
Expr::arg(4i32),
]);
let (sql, args) = build(&Numbered, &outer).unwrap();
assert_eq!(sql, "$1 f($2, $3) $4");
assert_eq!(args.len(), 4);
}
}