keelson_core/expr/
func.rs1use std::borrow::Cow;
2
3use crate::writer::{Expression, SqlWriter};
4
5use super::convert::{IntoExpr, IntoExprList};
6use super::node::Expr;
7
8#[derive(Debug, Clone)]
25pub struct FuncExpr {
26 name: Cow<'static, str>,
27 args: Vec<Expr>,
28}
29
30impl FuncExpr {
31 pub fn new(name: impl Into<Cow<'static, str>>, args: impl IntoExprList) -> FuncExpr {
33 FuncExpr {
34 name: name.into(),
35 args: args.into_expr_list(),
36 }
37 }
38
39 #[must_use]
47 pub fn over(self, window: impl IntoExpr) -> Expr {
48 Expr::Func {
49 name: self.name,
50 args: self.args,
51 over: Some(Box::new(window.into_expr())),
52 }
53 }
54}
55
56impl IntoExpr for FuncExpr {
57 fn into_expr(self) -> Expr {
58 Expr::Func {
59 name: self.name,
60 args: self.args,
61 over: None,
62 }
63 }
64}
65
66impl IntoExprList for FuncExpr {
67 fn into_expr_list(self) -> Vec<Expr> {
68 vec![self.into_expr()]
69 }
70}
71
72impl Expression for FuncExpr {
73 fn write_sql(&self, w: &mut SqlWriter<'_>) {
74 w.write_expr(&self.clone().into_expr());
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use keelson_sqlcheck::testing::assert_frag_sql;
83
84 use super::super::{f, quote};
85 use super::*;
86 use crate::dialect::testing::Numbered;
87 use crate::expr::Chain;
88 use crate::writer::build;
89
90 const VALUE: &str = "SELECT {} FROM posts";
94 const OVER_NOTHING: &str = "SELECT {} OVER () FROM posts";
95
96 fn sql(e: Expr) -> String {
97 build(&Numbered, &e).expect("render").0
98 }
99
100 #[test]
101 fn a_call_with_no_arguments_still_has_its_parentheses() {
102 assert_frag_sql(VALUE, &sql(f("NOW", ()).into_expr()), "NOW()");
103 }
104
105 #[test]
106 fn arguments_are_comma_separated_and_may_be_anything() {
107 assert_frag_sql(
108 OVER_NOTHING,
109 &sql(f("LEAD", ("published_at", 1, f("NOW", ()))).into_expr()),
110 "LEAD(published_at, 1, NOW())",
111 );
112 }
113
114 #[test]
117 fn a_window_may_be_a_definition_a_name_or_empty() {
118 assert_frag_sql(
119 "SELECT {} FROM posts WINDOW w AS ()",
120 &sql(f("avg", "views").over("w")),
121 "avg(views) OVER (w)",
122 );
123 assert_frag_sql(
124 VALUE,
125 &sql(f("row_number", ()).over("")),
126 "row_number() OVER ()",
127 );
128 assert_frag_sql(
129 VALUE,
130 &sql(f("LEAD", ("published_at", 1)).over("PARTITION BY user_id")),
131 "LEAD(published_at, 1) OVER (PARTITION BY user_id)",
132 );
133 }
134
135 #[test]
136 fn a_windowed_call_continues_into_the_operator_chain() {
137 let e = f("LEAD", ("published_at", 1))
138 .over("PARTITION BY user_id")
139 .minus(quote("published_at"))
140 .as_("difference");
141 assert_frag_sql(
142 VALUE,
143 &sql(e),
144 concat!(
145 r#"(LEAD(published_at, 1) OVER (PARTITION BY user_id)"#,
146 r#" - "published_at") AS "difference""#
147 ),
148 );
149 }
150
151 #[test]
152 fn a_call_can_be_used_as_an_expression_directly() {
153 let (s, args) = build(&Numbered, &f("count", "*")).unwrap();
155 assert_frag_sql(VALUE, &s, "count(*)");
156 assert!(args.is_empty());
157 }
158
159 #[test]
160 fn arguments_inside_a_call_are_numbered_in_order() {
161 let e = f("coalesce", (Expr::arg(1i32), Expr::arg(2i32))).into_expr();
162 let (s, args) = build(&Numbered, &e).unwrap();
163 assert_frag_sql(
168 r#"SELECT "id" FROM posts WHERE "views" = CAST({} AS integer)"#,
169 &s,
170 "coalesce($1, $2)",
171 );
172 assert_eq!(args.len(), 2);
173 }
174}