keelson_core/clause/
fetch.rs1use super::offset::RowsKeyword;
2use crate::expr::{Expr, IntoExpr};
3use crate::writer::{Expression, SqlWriter};
4
5#[derive(Debug, Clone, Default)]
20pub struct Fetch {
21 pub count: Option<Expr>,
23 pub first_or_next: FirstOrNext,
25 pub rows: RowsKeyword,
27 pub with_ties: bool,
30}
31
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
36pub enum FirstOrNext {
37 First,
39 #[default]
41 Next,
42}
43
44impl FirstOrNext {
45 pub fn as_str(self) -> &'static str {
47 match self {
48 FirstOrNext::First => "FIRST",
49 FirstOrNext::Next => "NEXT",
50 }
51 }
52}
53
54impl Fetch {
55 pub fn new(count: impl IntoExpr) -> Self {
57 Fetch {
58 count: Some(count.into_expr()),
59 ..Fetch::default()
60 }
61 }
62
63 pub fn set_fetch(&mut self, count: impl IntoExpr) {
65 self.count = Some(count.into_expr());
66 }
67
68 pub fn is_empty(&self) -> bool {
70 self.count.is_none()
71 }
72}
73
74impl Expression for Fetch {
75 fn write_sql(&self, w: &mut SqlWriter<'_>) {
76 let Some(count) = &self.count else {
82 return;
83 };
84
85 w.push_str("FETCH ");
86 w.push_str(self.first_or_next.as_str());
87 w.push_str(" ");
88 w.write_expr(count);
89 w.push_str(" ");
90 w.push_str(self.rows.as_str());
91 w.push_str(if self.with_ties {
92 " WITH TIES"
93 } else {
94 " ONLY"
95 });
96 }
97}
98
99pub trait HasFetch {
101 fn fetch_mut(&mut self) -> &mut Fetch;
103}
104
105impl HasFetch for Fetch {
106 fn fetch_mut(&mut self) -> &mut Fetch {
107 self
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use keelson_sqlcheck::testing::assert_frag_sql;
114
115 use super::*;
116 use crate::dialect::testing::Numbered;
117 use crate::expr::arg;
118 use crate::value::Value;
119 use crate::writer::build;
120
121 const FRAME: &str = r#"SELECT "id" FROM users ORDER BY "id" {}"#;
124
125 fn sql(f: &Fetch) -> String {
126 build(&Numbered, f).expect("render").0
127 }
128
129 #[test]
130 fn an_unset_fetch_writes_nothing_even_with_spellings_asked_for() {
131 let f = Fetch {
132 count: None,
133 first_or_next: FirstOrNext::First,
134 rows: RowsKeyword::Row,
135 with_ties: true,
136 };
137 assert_frag_sql(FRAME, &sql(&f), "");
138 assert!(Fetch::default().is_empty());
139 }
140
141 #[test]
142 fn the_suffix_switches_on_with_ties() {
143 let mut f = Fetch::new(3i64);
144 assert_frag_sql(FRAME, &sql(&f), "FETCH NEXT 3 ROWS ONLY");
145
146 f.with_ties = true;
147 assert_frag_sql(FRAME, &sql(&f), "FETCH NEXT 3 ROWS WITH TIES");
148 }
149
150 #[test]
154 fn first_and_row_are_the_other_spellings() {
155 let mut f = Fetch::new(3i64);
156 f.first_or_next = FirstOrNext::First;
157 assert_frag_sql(FRAME, &sql(&f), "FETCH FIRST 3 ROWS ONLY");
158
159 f.rows = RowsKeyword::Row;
160 assert_frag_sql(FRAME, &sql(&f), "FETCH FIRST 3 ROW ONLY");
161
162 f.first_or_next = FirstOrNext::Next;
163 f.set_fetch(1i64);
164 assert_frag_sql(FRAME, &sql(&f), "FETCH NEXT 1 ROW ONLY");
165 }
166
167 #[test]
168 fn the_count_may_be_bound() {
169 let mut f = Fetch::default();
170 f.set_fetch(arg(3i64));
171 let (rendered, args) = build(&Numbered, &f).unwrap();
172 assert_frag_sql(FRAME, &rendered, "FETCH NEXT $1 ROWS ONLY");
173 assert_eq!(args, vec![Value::I64(3)]);
174 }
175
176 #[test]
179 fn first_composes_with_a_bound_count_and_ties() {
180 let mut f = Fetch::default();
181 f.set_fetch(arg(3i64));
182 f.first_or_next = FirstOrNext::First;
183 f.with_ties = true;
184 let (rendered, args) = build(&Numbered, &f).unwrap();
185 assert_frag_sql(FRAME, &rendered, "FETCH FIRST $1 ROWS WITH TIES");
186 assert_eq!(args, vec![Value::I64(3)]);
187 }
188}