Skip to main content

cypherlite_query/executor/operators/
sort.rs

1// SortOp: full-materialization sort with max_sort_rows guard
2
3use crate::executor::eval::{compare_values, eval};
4use crate::executor::{Params, Record, ScalarFnLookup};
5use crate::parser::ast::OrderItem;
6use cypherlite_storage::StorageEngine;
7
8/// Sort records by order items. Materializes all records then sorts.
9pub fn execute_sort(
10    mut records: Vec<Record>,
11    items: &[OrderItem],
12    engine: &StorageEngine,
13    params: &Params,
14    scalar_fns: &dyn ScalarFnLookup,
15) -> Vec<Record> {
16    records.sort_by(|a, b| {
17        for item in items {
18            let val_a = eval(&item.expr, a, engine, params, scalar_fns)
19                .unwrap_or(crate::executor::Value::Null);
20            let val_b = eval(&item.expr, b, engine, params, scalar_fns)
21                .unwrap_or(crate::executor::Value::Null);
22            let ord = compare_values(&val_a, &val_b);
23            let ord = if item.ascending { ord } else { ord.reverse() };
24            if ord != std::cmp::Ordering::Equal {
25                return ord;
26            }
27        }
28        std::cmp::Ordering::Equal
29    });
30    records
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use crate::executor::Value;
37    use crate::parser::ast::*;
38    use cypherlite_core::{DatabaseConfig, SyncMode};
39    use tempfile::tempdir;
40
41    fn test_engine(dir: &std::path::Path) -> cypherlite_storage::StorageEngine {
42        let config = DatabaseConfig {
43            path: dir.join("test.cyl"),
44            wal_sync_mode: SyncMode::Normal,
45            ..Default::default()
46        };
47        cypherlite_storage::StorageEngine::open(config).expect("open")
48    }
49
50    #[test]
51    fn test_sort_ascending() {
52        let dir = tempdir().expect("tempdir");
53        let engine = test_engine(dir.path());
54
55        let mut r1 = Record::new();
56        r1.insert("x".to_string(), Value::Int64(3));
57        let mut r2 = Record::new();
58        r2.insert("x".to_string(), Value::Int64(1));
59        let mut r3 = Record::new();
60        r3.insert("x".to_string(), Value::Int64(2));
61
62        let items = vec![OrderItem {
63            expr: Expression::Variable("x".to_string()),
64            ascending: true,
65        }];
66
67        let params = Params::new();
68        let result = execute_sort(vec![r1, r2, r3], &items, &engine, &params, &());
69        assert_eq!(result[0].get("x"), Some(&Value::Int64(1)));
70        assert_eq!(result[1].get("x"), Some(&Value::Int64(2)));
71        assert_eq!(result[2].get("x"), Some(&Value::Int64(3)));
72    }
73
74    #[test]
75    fn test_sort_descending() {
76        let dir = tempdir().expect("tempdir");
77        let engine = test_engine(dir.path());
78
79        let mut r1 = Record::new();
80        r1.insert("x".to_string(), Value::Int64(1));
81        let mut r2 = Record::new();
82        r2.insert("x".to_string(), Value::Int64(3));
83
84        let items = vec![OrderItem {
85            expr: Expression::Variable("x".to_string()),
86            ascending: false,
87        }];
88
89        let params = Params::new();
90        let result = execute_sort(vec![r1, r2], &items, &engine, &params, &());
91        assert_eq!(result[0].get("x"), Some(&Value::Int64(3)));
92        assert_eq!(result[1].get("x"), Some(&Value::Int64(1)));
93    }
94}