keelson_core/clause/
order_by.rs1use std::borrow::Cow;
2
3use crate::expr::{Expr, IntoExpr};
4use crate::writer::{Expression, SqlWriter};
5
6#[derive(Debug, Clone, Default)]
8pub struct OrderBy {
9 pub expressions: Vec<Expr>,
12}
13
14impl OrderBy {
15 pub fn append_order(&mut self, order: impl IntoExpr) {
17 self.expressions.push(order.into_expr());
18 }
19
20 pub fn clear_order_by(&mut self) {
23 self.expressions.clear();
24 }
25
26 pub fn is_empty(&self) -> bool {
28 self.expressions.is_empty()
29 }
30}
31
32impl Expression for OrderBy {
33 fn write_sql(&self, w: &mut SqlWriter<'_>) {
34 w.write_slice(&self.expressions, "ORDER BY ", ", ", "");
35 }
36}
37
38pub trait HasOrderBy {
42 fn order_by_mut(&mut self) -> &mut OrderBy;
44}
45
46impl HasOrderBy for OrderBy {
47 fn order_by_mut(&mut self) -> &mut OrderBy {
48 self
49 }
50}
51
52#[derive(Debug, Clone, Default)]
63pub struct OrderDef {
64 pub expression: Option<Expr>,
66 pub collation: Option<Cow<'static, str>>,
68 pub direction: Option<OrderDirection>,
70 pub nulls: Option<NullsPosition>,
73}
74
75impl OrderDef {
76 pub fn new(expression: impl IntoExpr) -> Self {
78 OrderDef {
79 expression: Some(expression.into_expr()),
80 ..OrderDef::default()
81 }
82 }
83
84 pub fn is_empty(&self) -> bool {
86 self.expression.is_none()
87 }
88}
89
90impl Expression for OrderDef {
91 fn write_sql(&self, w: &mut SqlWriter<'_>) {
92 let Some(expression) = &self.expression else {
93 return;
95 };
96 w.write_expr(expression);
97
98 if let Some(collation) = &self.collation {
99 w.push_str(" COLLATE ");
100 w.push_quoted(&[collation]);
101 }
102
103 match &self.direction {
104 None => {}
105 Some(OrderDirection::Asc) => w.push_str(" ASC"),
106 Some(OrderDirection::Desc) => w.push_str(" DESC"),
107 Some(OrderDirection::Using(op)) => {
108 w.push_str(" USING ");
109 w.push_str(op);
110 }
111 }
112
113 if let Some(nulls) = &self.nulls {
114 w.push_str(" NULLS ");
115 w.push_str(nulls.as_str());
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum OrderDirection {
126 Asc,
128 Desc,
130 Using(Cow<'static, str>),
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum NullsPosition {
137 First,
139 Last,
141}
142
143impl NullsPosition {
144 pub fn as_str(self) -> &'static str {
146 match self {
147 NullsPosition::First => "FIRST",
148 NullsPosition::Last => "LAST",
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use keelson_sqlcheck::testing::assert_frag_sql;
156
157 use super::*;
158 use crate::dialect::testing::Numbered;
159 use crate::expr::{arg, quote};
160 use crate::value::Value;
161 use crate::writer::build;
162
163 const KEY_FRAME: &str = r#"SELECT "id" FROM users ORDER BY {}"#;
165 const CLAUSE_FRAME: &str = r#"SELECT "id", "name", "age" FROM users {}"#;
166
167 fn sql(e: &impl Expression) -> String {
168 build(&Numbered, e).expect("render").0
169 }
170
171 #[test]
172 fn an_empty_order_by_writes_nothing() {
173 assert_frag_sql(CLAUSE_FRAME, &sql(&OrderBy::default()), "");
174 assert!(OrderBy::default().is_empty());
175 }
176
177 #[test]
178 fn an_order_def_with_no_expression_writes_nothing() {
179 let o = OrderDef {
180 direction: Some(OrderDirection::Desc),
181 ..OrderDef::default()
182 };
183 assert_eq!(build(&Numbered, &o).unwrap().0, "");
187 assert!(o.is_empty());
188 }
189
190 #[test]
191 fn a_bare_order_def_is_just_its_expression() {
192 assert_frag_sql(KEY_FRAME, &sql(&OrderDef::new(quote("name"))), r#""name""#);
193 }
194
195 #[test]
196 fn collation_precedes_the_direction_and_nulls_comes_last() {
197 let o = OrderDef {
201 collation: Some("bg-BG-x-icu".into()),
202 direction: Some(OrderDirection::Asc),
203 nulls: Some(NullsPosition::Last),
204 ..OrderDef::new(quote("name"))
205 };
206 assert_frag_sql(
207 KEY_FRAME,
208 &sql(&o),
209 r#""name" COLLATE "bg-BG-x-icu" ASC NULLS LAST"#,
210 );
211 }
212
213 #[test]
214 fn a_direction_can_be_an_operator() {
215 let o = OrderDef {
218 direction: Some(OrderDirection::Using(">".into())),
219 nulls: Some(NullsPosition::First),
220 ..OrderDef::new(quote("name"))
221 };
222 assert_frag_sql(KEY_FRAME, &sql(&o), r#""name" USING > NULLS FIRST"#);
223 }
224
225 #[test]
226 fn keys_are_comma_separated_and_can_be_cleared() {
227 let mut ob = OrderBy::default();
228 ob.append_order(Expr::custom(OrderDef::new(quote("name"))));
229 ob.append_order(Expr::custom(OrderDef {
230 direction: Some(OrderDirection::Desc),
231 ..OrderDef::new(quote("age"))
232 }));
233 ob.append_order("3");
236
237 assert_frag_sql(CLAUSE_FRAME, &sql(&ob), r#"ORDER BY "name", "age" DESC, 3"#);
238
239 ob.clear_order_by();
240 assert_frag_sql(CLAUSE_FRAME, &sql(&ob), "");
241 }
242
243 #[test]
244 fn a_sort_key_may_bind_an_argument() {
245 let mut ob = OrderBy::default();
246 ob.append_order(Expr::custom(OrderDef::new(Expr::func(
247 "coalesce",
248 (quote("age"), arg(0i32)),
249 ))));
250 let (rendered, args) = build(&Numbered, &ob).unwrap();
251 assert_frag_sql(CLAUSE_FRAME, &rendered, r#"ORDER BY coalesce("age", $1)"#);
252 assert_eq!(args, vec![Value::I32(0)]);
253 }
254}