use std::cmp::Ordering;
use crate::document::{Document, Value};
#[derive(Debug, Clone, PartialEq)]
pub enum SortDirection {
Asc,
Desc,
}
#[derive(Debug, Clone)]
pub struct SortSpec {
pub field: String,
pub direction: SortDirection,
}
impl SortSpec {
pub fn asc(field: impl Into<String>) -> Self {
Self {
field: field.into(),
direction: SortDirection::Asc,
}
}
pub fn desc(field: impl Into<String>) -> Self {
Self {
field: field.into(),
direction: SortDirection::Desc,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct FindOptions {
pub sort: Vec<SortSpec>,
pub skip: u64,
pub limit: Option<u64>,
pub fields: Option<Vec<String>>,
pub exclude: Option<Vec<String>>,
pub timeout: Option<std::time::Duration>,
}
pub(crate) fn cmp_values(a: &Value, b: &Value) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (a, b) {
(Value::Null, Value::Null) => Ordering::Equal,
(Value::Null, _) => Ordering::Less,
(_, Value::Null) => Ordering::Greater,
(Value::Bool(x), Value::Bool(y)) => x.cmp(y),
(Value::Bool(_), _) => Ordering::Less,
(_, Value::Bool(_)) => Ordering::Greater,
(Value::Int(x), Value::Int(y)) => x.cmp(y),
(Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
(Value::Int(x), Value::Float(y)) => (*x as f64).partial_cmp(y).unwrap_or(Ordering::Equal),
(Value::Float(x), Value::Int(y)) => x.partial_cmp(&(*y as f64)).unwrap_or(Ordering::Equal),
(Value::Int(_) | Value::Float(_), _) => Ordering::Less,
(_, Value::Int(_) | Value::Float(_)) => Ordering::Greater,
(Value::Str(x), Value::Str(y)) => x.cmp(y),
(Value::Str(_), _) => Ordering::Less,
(_, Value::Str(_)) => Ordering::Greater,
(Value::Bytes(x), Value::Bytes(y)) => x.cmp(y),
(Value::Bytes(_), _) => Ordering::Less,
(_, Value::Bytes(_)) => Ordering::Greater,
_ => Ordering::Equal,
}
}
struct KeyRow {
keys: Vec<Option<Value>>,
id: ulid::Ulid,
idx: usize,
}
fn key_rows(docs: &[Document], sort: &[SortSpec]) -> Vec<KeyRow> {
docs.iter()
.enumerate()
.map(|(idx, d)| KeyRow {
keys: sort.iter().map(|s| d.get(&s.field).cloned()).collect(),
id: d.id,
idx,
})
.collect()
}
fn cmp_rows(a: &KeyRow, b: &KeyRow, sort: &[SortSpec]) -> Ordering {
for (i, spec) in sort.iter().enumerate() {
let ord = match (&a.keys[i], &b.keys[i]) {
(Some(x), Some(y)) => cmp_values(x, y),
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(None, None) => Ordering::Equal,
};
let ord = if spec.direction == SortDirection::Desc {
ord.reverse()
} else {
ord
};
if ord != Ordering::Equal {
return ord;
}
}
a.id.cmp(&b.id)
}
fn gather(docs: &mut Vec<Document>, rows: &[KeyRow]) {
let mut slots: Vec<Option<Document>> = std::mem::take(docs).into_iter().map(Some).collect();
*docs = rows
.iter()
.map(|r| slots[r.idx].take().expect("each index is visited once"))
.collect();
}
pub fn sort_documents(docs: &mut Vec<Document>, sort: &[SortSpec]) {
if sort.is_empty() || docs.len() < 2 {
return;
}
let mut rows = key_rows(docs, sort);
rows.sort_by(|a, b| cmp_rows(a, b, sort));
gather(docs, &rows);
}
pub fn partial_sort_documents(docs: &mut Vec<Document>, sort: &[SortSpec], keep: usize) {
if keep == 0 {
docs.clear();
return;
}
if sort.is_empty() {
docs.truncate(keep);
return;
}
let mut rows = key_rows(docs, sort);
if keep < rows.len() {
rows.select_nth_unstable_by(keep - 1, |a, b| cmp_rows(a, b, sort));
rows.truncate(keep);
}
rows.sort_by(|a, b| cmp_rows(a, b, sort));
gather(docs, &rows);
}
pub fn project_document(mut doc: Document, fields: &[String]) -> Document {
doc.fields.retain(|(k, _)| fields.iter().any(|f| f == k));
doc
}
pub fn exclude_fields(mut doc: Document, exclude: &[String]) -> Document {
doc.fields.retain(|(k, _)| !exclude.iter().any(|f| f == k));
doc
}