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::normal::{self, Normal};
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);
pub(crate) type Normalized = (Normal, Arrival, Source);
const BESIDE: u64 = (size_of::<Sortable>() - size_of::<Vec<Value>>()) as u64;
const NORMALIZED: u64 = size_of::<Normalized>() as u64;
#[derive(Debug)]
pub(crate) struct Sort {
keys: Vec<SortKey>,
widths: Option<Vec<usize>>,
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: Keyed,
}
#[derive(Debug)]
enum Keyed {
Normal(Vec<Normalized>),
Valued(Vec<Sortable>),
}
impl Default for Keyed {
fn default() -> Self {
Self::Valued(Vec::new())
}
}
impl Keyed {
fn empty(&self) -> Self {
match self {
Self::Normal(_) => Self::Normal(Vec::new()),
Self::Valued(_) => Self::Valued(Vec::new()),
}
}
fn absorb(&mut self, other: Self, base: u32) -> Result<()> {
match (self, other) {
(Self::Normal(into), Self::Normal(from)) => {
into.extend(from.into_iter().map(|(key, arrival, (chunk, row))| {
(key, arrival, (chunk.saturating_add(base), row))
}));
Ok(())
}
(Self::Valued(into), Self::Valued(from)) => {
into.extend(from.into_iter().map(|(key, arrival, (chunk, row))| {
(key, arrival, (chunk.saturating_add(base), row))
}));
Ok(())
}
_ => Err(Error::internal("two instances of one sort holding their keys differently")),
}
}
fn sort(&mut self, keys: &[SortKey]) -> Result<()> {
match self {
Self::Normal(rows) => {
rows.sort_unstable_by(|left, right| {
left.0.cmp(&right.0).then_with(|| left.1.cmp(&right.1))
});
Ok(())
}
Self::Valued(rows) => {
let mut failure: Option<Error> = None;
rows.sort_by(|left, right| settled(keys, left, right, &mut failure));
failure.map_or(Ok(()), Err)
}
}
}
fn sources(&self) -> Box<dyn ExactSizeIterator<Item = Source> + '_> {
match self {
Self::Normal(rows) => Box::new(rows.iter().map(|row| row.2)),
Self::Valued(rows) => Box::new(rows.iter().map(|row| row.2)),
}
}
fn len(&self) -> usize {
match self {
Self::Normal(rows) => rows.len(),
Self::Valued(rows) => rows.len(),
}
}
fn footprint(&self) -> u64 {
match self {
Self::Normal(rows) => {
u64::try_from(rows.len()).unwrap_or(u64::MAX).saturating_mul(NORMALIZED)
}
Self::Valued(rows) => rows.iter().map(|row| rows::footprint(&row.0) + BESIDE).sum(),
}
}
}
#[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 types: Vec<_> = exprs.iter().map(|&expr| plan.expr_type(expr).clone()).collect();
let out = Buffered::new();
let widths = normal::layout(&types);
let rows =
if widths.is_some() { Keyed::Normal(Vec::new()) } else { Keyed::Valued(Vec::new()) };
let sort = Self {
widths,
exprs: Prepared::new(plan, &exprs, input)?,
keys,
types: input.types(),
memory: memory.clone(),
gathered: Mutex::new(Combined { chunks: Vec::new(), rows }),
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 {
let rows = if self.widths.is_some() {
Keyed::Normal(Vec::new())
} else {
Keyed::Valued(Vec::new())
};
Gathered {
held: Combined { chunks: Vec::new(), rows },
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);
match (&self.widths, &mut local.held.rows) {
(Some(widths), Keyed::Normal(rows)) => {
for row in 0..chunk.len() {
let mut key: Normal = [0; normal::WIDTH];
let mut written = 0;
for (at, column) in keys.iter().enumerate() {
let wide = *widths.get(at).ok_or_else(mismatched)?;
let value = column.try_value_at(row)?;
normal::write(&mut key, written, wide, &value, self.keys[at])?;
written += wide;
}
taken += NORMALIZED;
let row = u32::try_from(row).map_err(|_| too_many())?;
rows.push((key, local.place.of(row as usize), (at, row)));
}
}
(None, Keyed::Valued(rows)) => {
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())?;
rows.push((key, local.place.of(row as usize), (at, row)));
}
}
_ => return Err(mismatched()),
}
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.absorb(local.held.rows, base)?;
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 } = {
let mut gathered = self.gathered.lock().map_err(poisoned)?;
let empty = Combined { chunks: Vec::new(), rows: gathered.rows.empty() };
std::mem::replace(&mut *gathered, empty)
};
rows.sort(&self.keys)?;
let mut charged = std::mem::take(&mut *self.charged.lock().map_err(poisoned)?);
let total = rows.len();
if u32::try_from(total).is_err() {
return Err(too_many());
}
let at = places(&chunks, &rows)?;
let taken = rows.footprint();
drop(rows);
give(&mut charged, taken);
let mut held = self.held.lock().map_err(poisoned)?;
let out = gathered(&self.types, chunks, &at, total, &mut held, &mut charged)?;
self.out.fill(out)?;
Ok(())
}
}
fn places(chunks: &[Chunk], rows: &Keyed) -> Result<Vec<Vec<u32>>> {
let mut at: Vec<Vec<u32>> = chunks.iter().map(|chunk| vec![0; chunk.len()]).collect();
for (rank, (chunk, row)) in rows.sources().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;
}
Ok(at)
}
fn give(charged: &mut Vec<Reservation>, mut bytes: u64) {
while bytes > 0 {
let Some(last) = charged.last_mut() else { return };
let held = last.bytes();
if held > bytes {
last.shrink(bytes);
return;
}
bytes -= held;
charged.pop();
}
}
fn gathered(
types: &[LogicalType],
chunks: Vec<Chunk>,
at: &[Vec<u32>],
rows: usize,
held: &mut Reservation,
charged: &mut Vec<Reservation>,
) -> Result<Vec<Chunk>> {
if rows == 0 {
return Ok(Vec::new());
}
let mut pieces: Vec<Vec<Vector>> = vec![Vec::with_capacity(chunks.len()); types.len()];
for chunk in chunks {
for (position, column) in chunk.into_columns().into_iter().enumerate() {
let Some(into) = pieces.get_mut(position) else {
return Err(Error::internal("a sorted chunk wider than the schema it came from"));
};
into.push(column);
}
}
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)?;
let laid = pieces.get_mut(position).map(std::mem::take).unwrap_or_default();
for (piece, places) in laid.iter().zip(at) {
assembly.place(places, piece)?;
}
let given = laid.iter().map(Vector::footprint).sum::<usize>();
drop(laid);
give(charged, u64::try_from(given).unwrap_or(u64::MAX));
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")
}
fn mismatched() -> Error {
Error::internal("a sort holding its keys two ways at once")
}
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")
}