use std::cmp::Ordering;
use std::sync::Mutex;
use rudb_common::{Error, LogicalType, Memory, Reservation, Result, Value};
use rudb_pipeline::{Progress, Sink};
use rudb_plan::{Plan, Slice, SortKey};
use rudb_vector::Chunk;
use crate::buffer::Buffered;
use crate::prepared::{Prepared, Scratch};
use crate::rows;
use crate::schema::Schema;
type Sortable = (Vec<Value>, Vec<Value>);
#[derive(Debug)]
pub(crate) struct Sort {
keys: Vec<SortKey>,
exprs: Prepared,
types: Vec<LogicalType>,
memory: Memory,
rows: Mutex<Vec<Sortable>>,
charged: Mutex<Vec<Reservation>>,
held: Mutex<Reservation>,
out: Buffered,
}
#[derive(Debug)]
pub(crate) struct Gathered {
rows: Vec<Sortable>,
scratch: Scratch,
charged: Reservation,
}
impl Sort {
pub(crate) fn new(
plan: &Plan,
input: &Schema,
keys: Slice,
memory: &Memory,
) -> Result<(Self, Buffered)> {
let keys = plan.sort_key_list(keys).to_vec();
let exprs: Vec<_> = keys.iter().map(|key| key.expr).collect();
let out = Buffered::new();
let sort = Self {
exprs: Prepared::new(plan, &exprs, input)?,
keys,
types: input.types(),
memory: memory.clone(),
rows: Mutex::new(Vec::new()),
charged: Mutex::new(Vec::new()),
held: Mutex::new(memory.reservation()),
out: out.clone(),
};
Ok((sort, out))
}
}
impl Sink for Sort {
type Local = Gathered;
fn local(&self) -> Gathered {
Gathered {
rows: Vec::new(),
scratch: self.exprs.scratch(),
charged: self.memory.reservation(),
}
}
fn sink(&self, chunk: &Chunk, local: &mut Gathered) -> Result<Progress> {
let mut keys = Vec::with_capacity(self.keys.len());
self.exprs.evaluate(chunk, &mut local.scratch, &mut keys)?;
let mut taken = 0;
for row in 0..chunk.len() {
let key: Vec<Value> = keys.iter().map(|column| column.value_at(row)).collect();
let values: Vec<Value> = chunk.row(row).collect();
taken += rows::footprint(&key) + rows::footprint(&values);
local.rows.push((key, values));
}
local.charged.grow(taken)?;
Ok(Progress::More)
}
fn combine(&self, local: Gathered) -> Result<()> {
let mut rows = self.rows.lock().map_err(poisoned)?;
rows.extend(local.rows);
self.charged.lock().map_err(poisoned)?.push(local.charged);
Ok(())
}
fn finalize(&self) -> Result<()> {
let mut sortable = std::mem::take(&mut *self.rows.lock().map_err(poisoned)?);
let mut failure: Option<Error> = None;
sortable.sort_by(|left, right| compare(&self.keys, &left.0, &right.0, &mut failure));
if let Some(error) = failure {
return Err(error);
}
let ordered: Vec<Vec<Value>> = sortable.into_iter().map(|(_, row)| row).collect();
let mut held = self.held.lock().map_err(poisoned)?;
let chunks = rows::chunks(&self.types, &ordered, &mut held)?;
self.out.fill(chunks)?;
self.charged.lock().map_err(poisoned)?.clear();
Ok(())
}
}
pub(crate) fn compare(
keys: &[SortKey],
left: &[Value],
right: &[Value],
failure: &mut Option<Error>,
) -> Ordering {
for (at, key) in keys.iter().enumerate() {
let ordering = match rank(&left[at], &right[at], *key) {
Ok(ordering) => ordering,
Err(error) => {
failure.get_or_insert(error);
Ordering::Equal
}
};
if ordering != Ordering::Equal {
return ordering;
}
}
Ordering::Equal
}
fn rank(left: &Value, right: &Value, key: SortKey) -> Result<Ordering> {
match (left.is_null(), right.is_null()) {
(true, true) => Ok(Ordering::Equal),
(true, false) => Ok(if key.nulls_first { Ordering::Less } else { Ordering::Greater }),
(false, true) => Ok(if key.nulls_first { Ordering::Greater } else { Ordering::Less }),
(false, false) => {
let ordering = rudb_kernels::order(left, right)?;
Ok(if key.descending { ordering.reverse() } else { ordering })
}
}
}
fn poisoned<T>(_: T) -> Error {
Error::internal("a thread panicked while holding the rows a sort is gathering")
}