use std::borrow::Cow;
use crate::writer::{Expression, SqlWriter};
use super::convert::{IntoExpr, IntoExprList};
use super::node::Expr;
#[derive(Debug, Clone)]
pub struct FuncExpr {
name: Cow<'static, str>,
args: Vec<Expr>,
}
impl FuncExpr {
pub fn new(name: impl Into<Cow<'static, str>>, args: impl IntoExprList) -> FuncExpr {
FuncExpr {
name: name.into(),
args: args.into_expr_list(),
}
}
#[must_use]
pub fn over(self, window: impl IntoExpr) -> Expr {
Expr::Func {
name: self.name,
args: self.args,
over: Some(Box::new(window.into_expr())),
}
}
}
impl IntoExpr for FuncExpr {
fn into_expr(self) -> Expr {
Expr::Func {
name: self.name,
args: self.args,
over: None,
}
}
}
impl IntoExprList for FuncExpr {
fn into_expr_list(self) -> Vec<Expr> {
vec![self.into_expr()]
}
}
impl Expression for FuncExpr {
fn write_sql(&self, w: &mut SqlWriter<'_>) {
w.write_expr(&self.clone().into_expr());
}
}
#[cfg(test)]
mod tests {
use keelson_sqlcheck::testing::assert_frag_sql;
use super::super::{f, quote};
use super::*;
use crate::dialect::testing::Numbered;
use crate::expr::Chain;
use crate::writer::build;
const VALUE: &str = "SELECT {} FROM posts";
const OVER_NOTHING: &str = "SELECT {} OVER () FROM posts";
fn sql(e: Expr) -> String {
build(&Numbered, &e).expect("render").0
}
#[test]
fn a_call_with_no_arguments_still_has_its_parentheses() {
assert_frag_sql(VALUE, &sql(f("NOW", ()).into_expr()), "NOW()");
}
#[test]
fn arguments_are_comma_separated_and_may_be_anything() {
assert_frag_sql(
OVER_NOTHING,
&sql(f("LEAD", ("published_at", 1, f("NOW", ()))).into_expr()),
"LEAD(published_at, 1, NOW())",
);
}
#[test]
fn a_window_may_be_a_definition_a_name_or_empty() {
assert_frag_sql(
"SELECT {} FROM posts WINDOW w AS ()",
&sql(f("avg", "views").over("w")),
"avg(views) OVER (w)",
);
assert_frag_sql(
VALUE,
&sql(f("row_number", ()).over("")),
"row_number() OVER ()",
);
assert_frag_sql(
VALUE,
&sql(f("LEAD", ("published_at", 1)).over("PARTITION BY user_id")),
"LEAD(published_at, 1) OVER (PARTITION BY user_id)",
);
}
#[test]
fn a_windowed_call_continues_into_the_operator_chain() {
let e = f("LEAD", ("published_at", 1))
.over("PARTITION BY user_id")
.minus(quote("published_at"))
.as_("difference");
assert_frag_sql(
VALUE,
&sql(e),
concat!(
r#"(LEAD(published_at, 1) OVER (PARTITION BY user_id)"#,
r#" - "published_at") AS "difference""#
),
);
}
#[test]
fn a_call_can_be_used_as_an_expression_directly() {
let (s, args) = build(&Numbered, &f("count", "*")).unwrap();
assert_frag_sql(VALUE, &s, "count(*)");
assert!(args.is_empty());
}
#[test]
fn arguments_inside_a_call_are_numbered_in_order() {
let e = f("coalesce", (Expr::arg(1i32), Expr::arg(2i32))).into_expr();
let (s, args) = build(&Numbered, &e).unwrap();
assert_frag_sql(
r#"SELECT "id" FROM posts WHERE "views" = CAST({} AS integer)"#,
&s,
"coalesce($1, $2)",
);
assert_eq!(args.len(), 2);
}
}