keelson_core/clause/
offset.rs1use crate::expr::{Expr, IntoExpr};
2use crate::writer::{Expression, SqlWriter};
3
4#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
13pub enum RowsKeyword {
14 Row,
16 #[default]
18 Rows,
19}
20
21impl RowsKeyword {
22 pub fn as_str(self) -> &'static str {
24 match self {
25 RowsKeyword::Row => "ROW",
26 RowsKeyword::Rows => "ROWS",
27 }
28 }
29}
30
31#[derive(Debug, Clone, Default)]
39pub struct Offset {
40 pub count: Option<Expr>,
42 pub rows: Option<RowsKeyword>,
44}
45
46impl Offset {
47 pub fn set_offset(&mut self, count: impl IntoExpr) {
49 self.count = Some(count.into_expr());
50 }
51
52 pub fn is_empty(&self) -> bool {
55 self.count.is_none()
56 }
57}
58
59impl Expression for Offset {
60 fn write_sql(&self, w: &mut SqlWriter<'_>) {
61 let Some(count) = &self.count else {
62 return;
63 };
64
65 w.push_str("OFFSET ");
66 w.write_expr(count);
67 if let Some(rows) = self.rows {
68 w.push_str(" ");
69 w.push_str(rows.as_str());
70 }
71 }
72}
73
74pub trait HasOffset {
76 fn offset_mut(&mut self) -> &mut Offset;
78}
79
80impl HasOffset for Offset {
81 fn offset_mut(&mut self) -> &mut Offset {
82 self
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use keelson_sqlcheck::testing::assert_frag_sql;
89
90 use super::*;
91 use crate::dialect::testing::Numbered;
92 use crate::expr::arg;
93 use crate::value::Value;
94 use crate::writer::build;
95
96 const FRAME: &str = r#"SELECT "id" FROM users {}"#;
97
98 #[test]
99 fn an_unset_offset_writes_nothing() {
100 assert_frag_sql(FRAME, &build(&Numbered, &Offset::default()).unwrap().0, "");
101 assert!(Offset::default().is_empty());
102 }
103
104 #[test]
105 fn the_count_is_written_after_the_keyword() {
106 let mut o = Offset::default();
107 o.set_offset(5i64);
108 assert_frag_sql(FRAME, &build(&Numbered, &o).unwrap().0, "OFFSET 5");
109
110 o.set_offset(arg(5i64));
111 let (sql, args) = build(&Numbered, &o).unwrap();
112 assert_frag_sql(FRAME, &sql, "OFFSET $1");
113 assert_eq!(args, vec![Value::I64(5)]);
114 }
115
116 #[test]
119 fn the_rows_keyword_follows_the_count_in_either_number() {
120 let mut o = Offset::default();
121 o.set_offset(5i64);
122 o.rows = Some(RowsKeyword::Rows);
123 assert_frag_sql(FRAME, &build(&Numbered, &o).unwrap().0, "OFFSET 5 ROWS");
124
125 o.rows = Some(RowsKeyword::Row);
126 assert_frag_sql(FRAME, &build(&Numbered, &o).unwrap().0, "OFFSET 5 ROW");
127 }
128
129 #[test]
132 fn the_count_may_be_bound_with_the_keyword_present() {
133 let mut o = Offset::default();
134 o.set_offset(arg(5i64));
135 o.rows = Some(RowsKeyword::Rows);
136 let (sql, args) = build(&Numbered, &o).unwrap();
137 assert_frag_sql(FRAME, &sql, "OFFSET $1 ROWS");
138 assert_eq!(args, vec![Value::I64(5)]);
139 }
140
141 #[test]
144 fn the_keyword_alone_writes_nothing() {
145 let o = Offset {
146 count: None,
147 rows: Some(RowsKeyword::Rows),
148 };
149 assert_frag_sql(FRAME, &build(&Numbered, &o).unwrap().0, "");
150 assert!(o.is_empty());
151 }
152}