BREAKING CHANGE(core): replace the TypeScript database engine with a Rust-backed embedded server and bridge
This commit is contained in:
137
rust/crates/rustdb-query/src/sort.rs
Normal file
137
rust/crates/rustdb-query/src/sort.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use bson::{Bson, Document};
|
||||
|
||||
use crate::field_path::get_nested_value;
|
||||
|
||||
/// Sort documents according to a sort specification.
|
||||
/// Sort spec: `{ field1: 1, field2: -1 }` where 1 = ascending, -1 = descending.
|
||||
pub fn sort_documents(docs: &mut [Document], sort_spec: &Document) {
|
||||
if sort_spec.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
docs.sort_by(|a, b| {
|
||||
for (field, direction) in sort_spec {
|
||||
let ascending = match direction {
|
||||
Bson::Int32(n) => *n > 0,
|
||||
Bson::Int64(n) => *n > 0,
|
||||
Bson::String(s) => !s.eq_ignore_ascii_case("desc") && !s.eq_ignore_ascii_case("descending"),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
let a_val = get_value(a, field);
|
||||
let b_val = get_value(b, field);
|
||||
|
||||
let ord = compare_bson_values(&a_val, &b_val);
|
||||
let ord = if ascending { ord } else { ord.reverse() };
|
||||
|
||||
if ord != std::cmp::Ordering::Equal {
|
||||
return ord;
|
||||
}
|
||||
}
|
||||
std::cmp::Ordering::Equal
|
||||
});
|
||||
}
|
||||
|
||||
fn get_value(doc: &Document, field: &str) -> Option<Bson> {
|
||||
if field.contains('.') {
|
||||
get_nested_value(doc, field)
|
||||
} else {
|
||||
doc.get(field).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare two BSON values for sorting purposes.
|
||||
/// BSON type sort order: null < numbers < strings < objects < arrays < binData < ObjectId < bool < date
|
||||
fn compare_bson_values(a: &Option<Bson>, b: &Option<Bson>) -> std::cmp::Ordering {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
match (a, b) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(Bson::Null)) => Ordering::Equal,
|
||||
(Some(Bson::Null), None) => Ordering::Equal,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(Some(Bson::Null), Some(Bson::Null)) => Ordering::Equal,
|
||||
(Some(Bson::Null), Some(_)) => Ordering::Less,
|
||||
(Some(_), Some(Bson::Null)) => Ordering::Greater,
|
||||
(Some(av), Some(bv)) => compare_typed(av, bv),
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_typed(a: &Bson, b: &Bson) -> std::cmp::Ordering {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
// Cross-type numeric comparison
|
||||
let a_num = to_f64(a);
|
||||
let b_num = to_f64(b);
|
||||
if let (Some(an), Some(bn)) = (a_num, b_num) {
|
||||
return an.partial_cmp(&bn).unwrap_or(Ordering::Equal);
|
||||
}
|
||||
|
||||
match (a, b) {
|
||||
(Bson::String(x), Bson::String(y)) => x.cmp(y),
|
||||
(Bson::Boolean(x), Bson::Boolean(y)) => x.cmp(y),
|
||||
(Bson::DateTime(x), Bson::DateTime(y)) => x.cmp(y),
|
||||
(Bson::ObjectId(x), Bson::ObjectId(y)) => x.cmp(y),
|
||||
_ => {
|
||||
let ta = type_order(a);
|
||||
let tb = type_order(b);
|
||||
ta.cmp(&tb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_f64(v: &Bson) -> Option<f64> {
|
||||
match v {
|
||||
Bson::Int32(n) => Some(*n as f64),
|
||||
Bson::Int64(n) => Some(*n as f64),
|
||||
Bson::Double(n) => Some(*n),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn type_order(v: &Bson) -> u8 {
|
||||
match v {
|
||||
Bson::Null => 0,
|
||||
Bson::Int32(_) | Bson::Int64(_) | Bson::Double(_) | Bson::Decimal128(_) => 1,
|
||||
Bson::String(_) => 2,
|
||||
Bson::Document(_) => 3,
|
||||
Bson::Array(_) => 4,
|
||||
Bson::Binary(_) => 5,
|
||||
Bson::ObjectId(_) => 7,
|
||||
Bson::Boolean(_) => 8,
|
||||
Bson::DateTime(_) => 9,
|
||||
_ => 10,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_sort_ascending() {
|
||||
let mut docs = vec![
|
||||
bson::doc! { "x": 3 },
|
||||
bson::doc! { "x": 1 },
|
||||
bson::doc! { "x": 2 },
|
||||
];
|
||||
sort_documents(&mut docs, &bson::doc! { "x": 1 });
|
||||
assert_eq!(docs[0].get_i32("x").unwrap(), 1);
|
||||
assert_eq!(docs[1].get_i32("x").unwrap(), 2);
|
||||
assert_eq!(docs[2].get_i32("x").unwrap(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sort_descending() {
|
||||
let mut docs = vec![
|
||||
bson::doc! { "x": 1 },
|
||||
bson::doc! { "x": 3 },
|
||||
bson::doc! { "x": 2 },
|
||||
];
|
||||
sort_documents(&mut docs, &bson::doc! { "x": -1 });
|
||||
assert_eq!(docs[0].get_i32("x").unwrap(), 3);
|
||||
assert_eq!(docs[1].get_i32("x").unwrap(), 2);
|
||||
assert_eq!(docs[2].get_i32("x").unwrap(), 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user