use std::cmp::Ordering;
use std::sync::Mutex;
use rudb_common::{Error, LogicalType, Memory, Reservation, Result, Session, Value};
use rudb_pipeline::{Lease, Progress, Sink};
use rudb_plan::{Plan, Slice, SortKey};
use rudb_vector::{Assembly, Chunk, VECTOR_SIZE, Vector};
use crate::buffer::Buffered;
use crate::prepared::{Prepared, Scratch};
use crate::rows;
use crate::schema::Schema;
pub(crate) type Sortable = (Vec<Value>, Arrival, Source);
pub(crate) type Arrival = (u64, u64);
pub(crate) type Source = (u32, u32);
const BESIDE: u64 = (size_of::<Sortable>() - size_of::<Vec<Value>>()) as u64;
#[derive(Debug)]
pub(crate) struct Sort {
keys: Vec<SortKey>,
exprs: Prepared,
types: Vec<LogicalType>,
memory: Memory,
gathered: Mutex<Combined>,
charged: Mutex<Vec<Reservation>>,
held: Mutex<Reservation>,
out: Buffered,
}
#[derive(Debug, Default)]
struct Combined {
chunks: Vec<Chunk>,
rows: Vec<Sortable>,
}
#[derive(Debug)]
pub(crate) struct Gathered {
held: Combined,
scratch: Scratch,
charged: Reservation,
place: Place,
}
#[derive(Debug, Default)]
pub(crate) struct Place {
pub(crate) morsel: u64,
pub(crate) at: u64,
}
impl Place {
pub(crate) fn of(&self, row: usize) -> Arrival {
(self.morsel, self.at.saturating_add(row as u64))
}
pub(crate) fn past(&mut self, rows: usize) {
self.at = self.at.saturating_add(rows as u64);
}
pub(crate) fn start(&mut self, morsel: u64) {
self.morsel = morsel;
self.at = 0;
}
}
impl Sort {
#[must_use]
pub(crate) fn in_session(mut self, session: &Session) -> Self {
self.exprs = self.exprs.in_session(session);
self
}
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(),
gathered: Mutex::new(Combined::default()),
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 {
held: Combined::default(),
scratch: self.exprs.scratch(),
charged: self.memory.reservation(),
place: Place::default(),
}
}
fn at(&self, morsel: &rudb_pipeline::Morsel, local: &mut Gathered) -> Result<()> {
local.place.start(morsel.index());
Ok(())
}
fn sink(&self, chunk: &Chunk, local: &mut Gathered) -> Result<Progress> {
if chunk.is_empty() {
return Ok(Progress::More);
}
let mut keys = Vec::with_capacity(self.keys.len());
self.exprs.evaluate(chunk, &mut local.scratch, &mut keys)?;
let at = u32::try_from(local.held.chunks.len()).map_err(|_| too_many())?;
let mut taken = u64::try_from(chunk.footprint()).unwrap_or(u64::MAX);
for row in 0..chunk.len() {
let key: Vec<Value> =
keys.iter().map(|column| column.try_value_at(row)).collect::<Result<_>>()?;
taken += rows::footprint(&key) + BESIDE;
let row = u32::try_from(row).map_err(|_| too_many())?;
local.held.rows.push((key, local.place.of(row as usize), (at, row)));
}
local.held.chunks.push(chunk.clone());
local.place.past(chunk.len());
local.charged.grow(taken)?;
Ok(Progress::More)
}
fn combine(&self, local: Gathered) -> Result<()> {
let mut gathered = self.gathered.lock().map_err(poisoned)?;
let base = u32::try_from(gathered.chunks.len()).map_err(|_| too_many())?;
gathered.rows.extend(
local.held.rows.into_iter().map(|(key, arrival, (chunk, row))| {
(key, arrival, (chunk.saturating_add(base), row))
}),
);
gathered.chunks.extend(local.held.chunks);
self.charged.lock().map_err(poisoned)?.push(local.charged);
Ok(())
}
fn finalize(&self, _threads: &Lease<'_>) -> Result<()> {
let Combined { chunks, mut rows } =
std::mem::take(&mut *self.gathered.lock().map_err(poisoned)?);
let mut failure: Option<Error> = None;
rows.sort_by(|left, right| settled(&self.keys, left, right, &mut failure));
if let Some(error) = failure {
return Err(error);
}
let mut held = self.held.lock().map_err(poisoned)?;
let out = gathered(&self.types, &chunks, &rows, &mut held)?;
self.out.fill(out)?;
self.charged.lock().map_err(poisoned)?.clear();
Ok(())
}
}
fn gathered(
types: &[LogicalType],
chunks: &[Chunk],
order: &[Sortable],
held: &mut Reservation,
) -> Result<Vec<Chunk>> {
let rows = order.len();
if rows == 0 {
return Ok(Vec::new());
}
if u32::try_from(rows).is_err() {
return Err(too_many());
}
let mut at: Vec<Vec<u32>> = chunks.iter().map(|chunk| vec![0; chunk.len()]).collect();
for (rank, &(_, _, (chunk, row))) in order.iter().enumerate() {
let Some(place) = at.get_mut(chunk as usize).and_then(|run| run.get_mut(row as usize))
else {
return Err(Error::internal("a sorted row pointing outside the chunks it came from"));
};
*place = rank as u32;
}
let blocks = rows.div_ceil(VECTOR_SIZE);
let mut columns: Vec<Vec<Vector>> = vec![Vec::with_capacity(types.len()); blocks];
for (position, ty) in types.iter().enumerate() {
let mut assembly = Assembly::new(ty.clone(), rows)?;
for (chunk, places) in chunks.iter().zip(&at) {
assembly.place(places, chunk.column(position)?)?;
}
let whole = assembly.finish()?.into_pages();
for (block, into) in columns.iter_mut().enumerate() {
let start = block * VECTOR_SIZE;
into.push(whole.slice(start, (rows - start).min(VECTOR_SIZE))?);
}
}
let mut built = Vec::with_capacity(blocks);
for (block, columns) in columns.into_iter().enumerate() {
let start = block * VECTOR_SIZE;
let chunk = Chunk::with_rows(columns, (rows - start).min(VECTOR_SIZE))?;
held.grow(u64::try_from(chunk.footprint()).unwrap_or(u64::MAX))?;
built.push(chunk);
}
Ok(built)
}
fn too_many() -> Error {
Error::internal("a sort of more than 4294967295 rows")
}
pub(crate) fn settled(
keys: &[SortKey],
left: &Sortable,
right: &Sortable,
failure: &mut Option<Error>,
) -> Ordering {
match compare(keys, &left.0, &right.0, failure) {
Ordering::Equal => left.1.cmp(&right.1),
ordering => ordering,
}
}
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
}
pub(crate) 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")
}