use crate::builder::{
CompoundFilter, Filter, FilterExpr, LogicalOp, Operator, SortDir, SortField, Value,
};
use super::cursor::Cursor;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct KeysetCondition {
pub sort_fields: Vec<SortField>,
pub cursor_values: Vec<Value>,
pub forward: bool,
}
impl KeysetCondition {
#[must_use]
pub fn after(sorts: &[SortField], cursor: &Cursor) -> Option<Self> {
Self::new(sorts, cursor, true)
}
#[must_use]
pub fn before(sorts: &[SortField], cursor: &Cursor) -> Option<Self> {
Self::new(sorts, cursor, false)
}
fn new(sorts: &[SortField], cursor: &Cursor, forward: bool) -> Option<Self> {
if sorts.is_empty() {
return None;
}
let mut cursor_values = Vec::new();
for sort in sorts {
let value = cursor
.fields
.iter()
.find(|(name, _)| name == &sort.field)
.map(|(_, v)| v.clone())?;
cursor_values.push(value);
}
Some(Self {
sort_fields: sorts.to_vec(),
cursor_values,
forward,
})
}
#[must_use]
pub fn to_filter_expr(&self) -> FilterExpr {
if self.sort_fields.is_empty() || self.cursor_values.is_empty() {
return FilterExpr::Simple(Filter {
field: "1".to_string(),
op: Operator::Eq,
value: Value::Int(1),
});
}
if self.sort_fields.len() == 1 {
let sort = &self.sort_fields[0];
let value = &self.cursor_values[0];
let op = self.get_operator(sort.dir);
return FilterExpr::Simple(Filter {
field: sort.field.clone(),
op,
value: value.clone(),
});
}
let mut or_conditions: Vec<FilterExpr> = Vec::new();
for i in 0..self.sort_fields.len() {
let mut and_conditions: Vec<FilterExpr> = Vec::new();
for j in 0..i {
and_conditions.push(FilterExpr::Simple(Filter {
field: self.sort_fields[j].field.clone(),
op: Operator::Eq,
value: self.cursor_values[j].clone(),
}));
}
let sort = &self.sort_fields[i];
let value = &self.cursor_values[i];
let op = self.get_operator(sort.dir);
and_conditions.push(FilterExpr::Simple(Filter {
field: sort.field.clone(),
op,
value: value.clone(),
}));
let condition = if and_conditions.len() == 1 {
and_conditions.into_iter().next().unwrap()
} else {
FilterExpr::Compound(CompoundFilter {
op: LogicalOp::And,
filters: and_conditions,
})
};
or_conditions.push(condition);
}
if or_conditions.len() == 1 {
or_conditions.into_iter().next().unwrap()
} else {
FilterExpr::Compound(CompoundFilter {
op: LogicalOp::Or,
filters: or_conditions,
})
}
}
const fn get_operator(&self, dir: SortDir) -> Operator {
match (self.forward, dir) {
(true, SortDir::Asc) => Operator::Gt,
(true, SortDir::Desc) => Operator::Lt,
(false, SortDir::Asc) => Operator::Lt,
(false, SortDir::Desc) => Operator::Gt,
}
}
}
#[cfg(test)]
#[allow(clippy::match_wildcard_for_single_variants)]
mod tests {
use super::*;
#[test]
fn test_keyset_condition_asc() {
let sorts = vec![SortField::new("id", SortDir::Asc)];
let cursor = Cursor::new().int("id", 100);
let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
let expr = condition.to_filter_expr();
match expr {
FilterExpr::Simple(f) => {
assert_eq!(f.field, "id");
assert_eq!(f.op, Operator::Gt);
},
_ => panic!("Expected simple filter"),
}
}
#[test]
fn test_keyset_condition_desc() {
let sorts = vec![SortField::new("created_at", SortDir::Desc)];
let cursor = Cursor::new().string("created_at", "2024-01-01");
let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
let expr = condition.to_filter_expr();
match expr {
FilterExpr::Simple(f) => {
assert_eq!(f.op, Operator::Lt);
},
_ => panic!("Expected simple filter"),
}
}
#[test]
fn test_keyset_condition_before() {
let sorts = vec![SortField::new("id", SortDir::Asc)];
let cursor = Cursor::new().int("id", 100);
let condition = KeysetCondition::before(&sorts, &cursor).unwrap();
let expr = condition.to_filter_expr();
match expr {
FilterExpr::Simple(f) => {
assert_eq!(f.op, Operator::Lt);
},
_ => panic!("Expected simple filter"),
}
}
#[test]
fn test_keyset_condition_multi_field_asc_asc() {
let sorts = vec![
SortField::new("created_at", SortDir::Asc),
SortField::new("id", SortDir::Asc),
];
let cursor = Cursor::new()
.string("created_at", "2024-01-01")
.int("id", 100);
let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
let expr = condition.to_filter_expr();
match expr {
FilterExpr::Compound(compound) => {
assert_eq!(compound.op, LogicalOp::Or);
assert_eq!(compound.filters.len(), 2);
match &compound.filters[0] {
FilterExpr::Simple(f) => {
assert_eq!(f.field, "created_at");
assert_eq!(f.op, Operator::Gt);
},
_ => panic!("Expected simple filter for first condition"),
}
match &compound.filters[1] {
FilterExpr::Compound(and_compound) => {
assert_eq!(and_compound.op, LogicalOp::And);
assert_eq!(and_compound.filters.len(), 2);
},
_ => panic!("Expected compound AND filter for second condition"),
}
},
_ => panic!("Expected compound OR filter for multi-field keyset"),
}
}
#[test]
fn test_keyset_condition_multi_field_desc_asc() {
let sorts = vec![
SortField::new("created_at", SortDir::Desc),
SortField::new("id", SortDir::Asc),
];
let cursor = Cursor::new()
.string("created_at", "2024-01-01")
.int("id", 100);
let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
let expr = condition.to_filter_expr();
match expr {
FilterExpr::Compound(compound) => {
assert_eq!(compound.op, LogicalOp::Or);
match &compound.filters[0] {
FilterExpr::Simple(f) => {
assert_eq!(f.field, "created_at");
assert_eq!(f.op, Operator::Lt); },
_ => panic!("Expected simple filter"),
}
},
_ => panic!("Expected compound filter"),
}
}
#[test]
fn test_keyset_condition_three_fields() {
let sorts = vec![
SortField::new("a", SortDir::Asc),
SortField::new("b", SortDir::Asc),
SortField::new("c", SortDir::Asc),
];
let cursor = Cursor::new().int("a", 1).int("b", 2).int("c", 3);
let condition = KeysetCondition::after(&sorts, &cursor).unwrap();
let expr = condition.to_filter_expr();
match expr {
FilterExpr::Compound(compound) => {
assert_eq!(compound.op, LogicalOp::Or);
assert_eq!(compound.filters.len(), 3);
match &compound.filters[0] {
FilterExpr::Simple(f) => {
assert_eq!(f.field, "a");
assert_eq!(f.op, Operator::Gt);
},
_ => panic!("Expected simple filter"),
}
match &compound.filters[1] {
FilterExpr::Compound(and_compound) => {
assert_eq!(and_compound.filters.len(), 2);
},
_ => panic!("Expected compound filter"),
}
match &compound.filters[2] {
FilterExpr::Compound(and_compound) => {
assert_eq!(and_compound.filters.len(), 3);
},
_ => panic!("Expected compound filter"),
}
},
_ => panic!("Expected compound filter"),
}
}
#[test]
fn test_keyset_with_missing_cursor_field() {
let sorts = vec![SortField::new("missing_field", SortDir::Asc)];
let cursor = Cursor::new().int("id", 100);
let condition = KeysetCondition::after(&sorts, &cursor);
assert!(
condition.is_none(),
"Should return None when cursor missing required field"
);
}
#[test]
fn test_keyset_with_empty_sorts() {
let cursor = Cursor::new().int("id", 100);
let condition = KeysetCondition::after(&[], &cursor);
assert!(
condition.is_none(),
"Should return None for empty sort list"
);
}
}