1use crate::builder::{
4 CompoundFilter, Filter, FilterExpr, LogicalOp, Operator, SortDir, SortField, Value,
5};
6
7use super::cursor::Cursor;
8
9#[derive(Debug, Clone, PartialEq)]
14#[non_exhaustive]
15pub struct KeysetCondition {
16 pub sort_fields: Vec<SortField>,
18 pub cursor_values: Vec<Value>,
20 pub forward: bool,
22}
23
24impl KeysetCondition {
25 #[must_use]
27 pub fn after(sorts: &[SortField], cursor: &Cursor) -> Option<Self> {
28 Self::new(sorts, cursor, true)
29 }
30
31 #[must_use]
33 pub fn before(sorts: &[SortField], cursor: &Cursor) -> Option<Self> {
34 Self::new(sorts, cursor, false)
35 }
36
37 fn new(sorts: &[SortField], cursor: &Cursor, forward: bool) -> Option<Self> {
38 if sorts.is_empty() {
39 return None;
40 }
41
42 let mut cursor_values = Vec::new();
44 for sort in sorts {
45 let value = cursor
46 .fields
47 .iter()
48 .find(|(name, _)| name == &sort.field)
49 .map(|(_, v)| v.clone())?;
50 cursor_values.push(value);
51 }
52
53 Some(Self {
54 sort_fields: sorts.to_vec(),
55 cursor_values,
56 forward,
57 })
58 }
59
60 #[must_use]
72 pub fn to_filter_expr(&self) -> FilterExpr {
73 if self.sort_fields.is_empty() || self.cursor_values.is_empty() {
74 return FilterExpr::Simple(Filter {
76 field: "1".to_string(),
77 op: Operator::Eq,
78 value: Value::Int(1),
79 });
80 }
81
82 if self.sort_fields.len() == 1 {
83 let sort = &self.sort_fields[0];
85 let value = &self.cursor_values[0];
86 let op = self.get_operator(sort.dir);
87
88 return FilterExpr::Simple(Filter {
89 field: sort.field.clone(),
90 op,
91 value: value.clone(),
92 });
93 }
94
95 let mut or_conditions: Vec<FilterExpr> = Vec::new();
101
102 for i in 0..self.sort_fields.len() {
103 let mut and_conditions: Vec<FilterExpr> = Vec::new();
105
106 for j in 0..i {
108 and_conditions.push(FilterExpr::Simple(Filter {
109 field: self.sort_fields[j].field.clone(),
110 op: Operator::Eq,
111 value: self.cursor_values[j].clone(),
112 }));
113 }
114
115 let sort = &self.sort_fields[i];
117 let value = &self.cursor_values[i];
118 let op = self.get_operator(sort.dir);
119 and_conditions.push(FilterExpr::Simple(Filter {
120 field: sort.field.clone(),
121 op,
122 value: value.clone(),
123 }));
124
125 let condition = if and_conditions.len() == 1 {
127 and_conditions.into_iter().next().unwrap()
128 } else {
129 FilterExpr::Compound(CompoundFilter {
130 op: LogicalOp::And,
131 filters: and_conditions,
132 })
133 };
134
135 or_conditions.push(condition);
136 }
137
138 if or_conditions.len() == 1 {
140 or_conditions.into_iter().next().unwrap()
141 } else {
142 FilterExpr::Compound(CompoundFilter {
143 op: LogicalOp::Or,
144 filters: or_conditions,
145 })
146 }
147 }
148
149 const fn get_operator(&self, dir: SortDir) -> Operator {
150 match (self.forward, dir) {
151 (true, SortDir::Asc) => Operator::Gt,
152 (true, SortDir::Desc) => Operator::Lt,
153 (false, SortDir::Asc) => Operator::Lt,
154 (false, SortDir::Desc) => Operator::Gt,
155 }
156 }
157}
158
159#[cfg(test)]
160#[allow(clippy::match_wildcard_for_single_variants)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_keyset_condition_asc() {
166 let sorts = vec![SortField::new("id", SortDir::Asc)];
167 let cursor = Cursor::new().int("id", 100);
168
169 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
170 let expr = condition.to_filter_expr();
171
172 match expr {
173 FilterExpr::Simple(f) => {
174 assert_eq!(f.field, "id");
175 assert_eq!(f.op, Operator::Gt);
176 },
177 _ => panic!("Expected simple filter"),
178 }
179 }
180
181 #[test]
182 fn test_keyset_condition_desc() {
183 let sorts = vec![SortField::new("created_at", SortDir::Desc)];
184 let cursor = Cursor::new().string("created_at", "2024-01-01");
185
186 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
187 let expr = condition.to_filter_expr();
188
189 match expr {
190 FilterExpr::Simple(f) => {
191 assert_eq!(f.op, Operator::Lt);
192 },
193 _ => panic!("Expected simple filter"),
194 }
195 }
196
197 #[test]
198 fn test_keyset_condition_before() {
199 let sorts = vec![SortField::new("id", SortDir::Asc)];
200 let cursor = Cursor::new().int("id", 100);
201
202 let condition = KeysetCondition::before(&sorts, &cursor).unwrap();
203 let expr = condition.to_filter_expr();
204
205 match expr {
206 FilterExpr::Simple(f) => {
207 assert_eq!(f.op, Operator::Lt);
208 },
209 _ => panic!("Expected simple filter"),
210 }
211 }
212
213 #[test]
214 fn test_keyset_condition_multi_field_asc_asc() {
215 let sorts = vec![
218 SortField::new("created_at", SortDir::Asc),
219 SortField::new("id", SortDir::Asc),
220 ];
221 let cursor = Cursor::new()
222 .string("created_at", "2024-01-01")
223 .int("id", 100);
224
225 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
226 let expr = condition.to_filter_expr();
227
228 match expr {
230 FilterExpr::Compound(compound) => {
231 assert_eq!(compound.op, LogicalOp::Or);
232 assert_eq!(compound.filters.len(), 2);
233
234 match &compound.filters[0] {
236 FilterExpr::Simple(f) => {
237 assert_eq!(f.field, "created_at");
238 assert_eq!(f.op, Operator::Gt);
239 },
240 _ => panic!("Expected simple filter for first condition"),
241 }
242
243 match &compound.filters[1] {
245 FilterExpr::Compound(and_compound) => {
246 assert_eq!(and_compound.op, LogicalOp::And);
247 assert_eq!(and_compound.filters.len(), 2);
248 },
249 _ => panic!("Expected compound AND filter for second condition"),
250 }
251 },
252 _ => panic!("Expected compound OR filter for multi-field keyset"),
253 }
254 }
255
256 #[test]
257 fn test_keyset_condition_multi_field_desc_asc() {
258 let sorts = vec![
260 SortField::new("created_at", SortDir::Desc),
261 SortField::new("id", SortDir::Asc),
262 ];
263 let cursor = Cursor::new()
264 .string("created_at", "2024-01-01")
265 .int("id", 100);
266
267 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
268 let expr = condition.to_filter_expr();
269
270 match expr {
271 FilterExpr::Compound(compound) => {
272 assert_eq!(compound.op, LogicalOp::Or);
273
274 match &compound.filters[0] {
276 FilterExpr::Simple(f) => {
277 assert_eq!(f.field, "created_at");
278 assert_eq!(f.op, Operator::Lt); },
280 _ => panic!("Expected simple filter"),
281 }
282 },
283 _ => panic!("Expected compound filter"),
284 }
285 }
286
287 #[test]
288 fn test_keyset_condition_three_fields() {
289 let sorts = vec![
294 SortField::new("a", SortDir::Asc),
295 SortField::new("b", SortDir::Asc),
296 SortField::new("c", SortDir::Asc),
297 ];
298 let cursor = Cursor::new().int("a", 1).int("b", 2).int("c", 3);
299
300 let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
301 let expr = condition.to_filter_expr();
302
303 match expr {
304 FilterExpr::Compound(compound) => {
305 assert_eq!(compound.op, LogicalOp::Or);
306 assert_eq!(compound.filters.len(), 3);
307
308 match &compound.filters[0] {
310 FilterExpr::Simple(f) => {
311 assert_eq!(f.field, "a");
312 assert_eq!(f.op, Operator::Gt);
313 },
314 _ => panic!("Expected simple filter"),
315 }
316
317 match &compound.filters[1] {
319 FilterExpr::Compound(and_compound) => {
320 assert_eq!(and_compound.filters.len(), 2);
321 },
322 _ => panic!("Expected compound filter"),
323 }
324
325 match &compound.filters[2] {
327 FilterExpr::Compound(and_compound) => {
328 assert_eq!(and_compound.filters.len(), 3);
329 },
330 _ => panic!("Expected compound filter"),
331 }
332 },
333 _ => panic!("Expected compound filter"),
334 }
335 }
336
337 #[test]
338 fn test_keyset_with_missing_cursor_field() {
339 let sorts = vec![SortField::new("missing_field", SortDir::Asc)];
341 let cursor = Cursor::new().int("id", 100);
342
343 let condition = KeysetCondition::after(&sorts, &cursor);
344 assert!(
345 condition.is_none(),
346 "Should return None when cursor missing required field"
347 );
348 }
349
350 #[test]
351 fn test_keyset_with_empty_sorts() {
352 let cursor = Cursor::new().int("id", 100);
353 let condition = KeysetCondition::after(&[], &cursor);
354 assert!(
355 condition.is_none(),
356 "Should return None for empty sort list"
357 );
358 }
359}