use std::cmp::Ordering;
use std::sync::Mutex;
use rudb_common::{Error, LogicalType, Memory, Reservation, Result, Value};
use rudb_kernels::{Comparison, refine};
use rudb_pipeline::{Progress, Sink};
use rudb_plan::{Plan, Slice, SortKey};
use rudb_vector::{Chunk, Selection, Vector};
use crate::buffer::Buffered;
use crate::prepared::{Prepared, Scratch};
use crate::rows;
use crate::schema::Schema;
use crate::sort::{compare, rank};
type Sortable = (Vec<Value>, Vec<Value>);
const SORTED_BOUND: usize = 64;
#[derive(Debug)]
pub(crate) struct TopN {
keys: Vec<SortKey>,
exprs: Prepared,
types: Vec<LogicalType>,
count: usize,
offset: usize,
bound: usize,
memory: Memory,
rows: Mutex<Vec<Sortable>>,
charged: Mutex<Vec<Reservation>>,
held: Mutex<Reservation>,
out: Buffered,
}
#[derive(Debug)]
pub(crate) struct Running {
kept: Vec<Sortable>,
scratch: Scratch,
charged: Reservation,
failure: Option<Error>,
}
impl TopN {
pub(crate) fn new(
plan: &Plan,
input: &Schema,
keys: Slice,
count: u64,
offset: u64,
memory: &Memory,
) -> Result<(Self, Buffered)> {
let count = usize::try_from(count).unwrap_or(usize::MAX);
let offset = usize::try_from(offset).unwrap_or(usize::MAX);
let keys = plan.sort_key_list(keys).to_vec();
let exprs: Vec<_> = keys.iter().map(|key| key.expr).collect();
let out = Buffered::new();
let top = Self {
exprs: Prepared::new(plan, &exprs, input)?,
keys,
types: input.types(),
count,
offset,
bound: count.saturating_add(offset),
memory: memory.clone(),
rows: Mutex::new(Vec::new()),
charged: Mutex::new(Vec::new()),
held: Mutex::new(memory.reservation()),
out: out.clone(),
};
Ok((top, out))
}
}
impl Sink for TopN {
type Local = Running;
fn local(&self) -> Running {
Running {
kept: Vec::new(),
scratch: self.exprs.scratch(),
charged: self.memory.reservation(),
failure: None,
}
}
fn sink(&self, chunk: &Chunk, local: &mut Running) -> Result<Progress> {
let mut keys = Vec::with_capacity(self.keys.len());
self.exprs.evaluate(chunk, &mut local.scratch, &mut keys)?;
if self.bound <= SORTED_BOUND {
let full = self.bound > 0 && local.kept.len() == self.bound;
let narrowed = full
.then(|| {
worth_looking_at(&self.keys, &keys, &local.kept[self.bound - 1].0, chunk.len())
})
.flatten();
let failure = &mut local.failure;
match narrowed {
Some(rows) => {
for row in rows.iter() {
keep(&self.keys, &mut local.kept, &keys, chunk, row, self.bound, failure);
}
}
None => {
for row in 0..chunk.len() {
keep(&self.keys, &mut local.kept, &keys, chunk, row, self.bound, failure);
}
}
}
recharge(&local.kept, &mut local.charged)?;
return Ok(Progress::More);
}
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.kept.push((key, values));
}
local.charged.grow(taken)?;
if local.kept.len() > self.bound.saturating_mul(2) {
trim(&self.keys, &mut local.kept, self.bound, &mut local.failure);
recharge(&local.kept, &mut local.charged)?;
}
Ok(Progress::More)
}
fn combine(&self, mut local: Running) -> Result<()> {
if let Some(error) = local.failure {
return Err(error);
}
let mut rows = self.rows.lock().map_err(poisoned)?;
rows.extend(local.kept);
let mut failure = None;
trim(&self.keys, &mut rows, self.bound, &mut failure);
if let Some(error) = failure {
return Err(error);
}
recharge(&rows, &mut local.charged)?;
self.charged.lock().map_err(poisoned)?.push(local.charged);
Ok(())
}
fn finalize(&self) -> Result<()> {
let kept = std::mem::take(&mut *self.rows.lock().map_err(poisoned)?);
let wanted = kept.into_iter().skip(self.offset).take(self.count);
let ordered: Vec<Vec<Value>> = wanted.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(())
}
}
fn poisoned<T>(_: T) -> Error {
Error::internal("a thread panicked while holding the rows a top N is keeping")
}
fn trim(keys: &[SortKey], kept: &mut Vec<Sortable>, bound: usize, failure: &mut Option<Error>) {
kept.sort_by(|left, right| compare(keys, &left.0, &right.0, failure));
kept.truncate(bound);
}
fn keep(
keys: &[SortKey],
kept: &mut Vec<Sortable>,
columns: &[Vector],
chunk: &Chunk,
row: usize,
bound: usize,
failure: &mut Option<Error>,
) {
if bound == 0 {
return;
}
if kept.len() == bound
&& against(keys, columns, row, &kept[bound - 1].0, failure) != Ordering::Less
{
return;
}
let key: Vec<Value> = columns.iter().map(|column| column.value_at(row)).collect();
let at = kept.partition_point(|candidate| {
compare(keys, &candidate.0, &key, failure) != Ordering::Greater
});
kept.insert(at, (key, chunk.row(row).collect()));
kept.truncate(bound);
}
fn against(
keys: &[SortKey],
columns: &[Vector],
row: usize,
held: &[Value],
failure: &mut Option<Error>,
) -> Ordering {
for (at, key) in keys.iter().enumerate() {
let ordering = match rank(&columns[at].value_at(row), &held[at], *key) {
Ok(ordering) => ordering,
Err(error) => {
failure.get_or_insert(error);
Ordering::Equal
}
};
if ordering != Ordering::Equal {
return ordering;
}
}
Ordering::Equal
}
fn worth_looking_at(
keys: &[SortKey],
columns: &[Vector],
worst: &[Value],
rows: usize,
) -> Option<Selection> {
let key = *keys.first()?;
let bound = worst.first()?;
let column = columns.first()?;
if bound.is_null() || (key.nulls_first && column.validity().has_nulls(rows)) {
return None;
}
let op = match (key.descending, keys.len() == 1) {
(false, true) => Comparison::Less,
(false, false) => Comparison::LessOrEqual,
(true, true) => Comparison::Greater,
(true, false) => Comparison::GreaterOrEqual,
};
let against = Vector::constant(column.logical_type().clone(), bound.clone(), rows);
refine(op, column, &against, &Selection::identity(rows)).ok()
}
fn recharge(kept: &[Sortable], scratch: &mut Reservation) -> Result<()> {
let footprint =
kept.iter().map(|(key, values)| rows::footprint(key) + rows::footprint(values)).sum();
scratch.release();
scratch.grow(footprint)
}