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;
use crate::sort::compare;
type Sortable = (Vec<Value>, Vec<Value>);
#[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)?;
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 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)
}