use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use rudb_common::{Error, LogicalType, Result, Value};
use rudb_pipeline::{Morsel, Progress, Source};
use rudb_vector::{Assembly, Chunk, Data, StringColumn, VECTOR_SIZE, Vector};
use crate::runs::Runs;
pub(crate) const KEY: usize = crate::normal::WIDTH;
pub(crate) const ORDER: usize = KEY + 16;
#[derive(Debug, Clone)]
pub(crate) struct Sorted {
stage: Arc<Mutex<Stage>>,
handed: Arc<AtomicU64>,
}
#[derive(Debug)]
enum Stage {
Waiting,
Held(Vec<Chunk>),
Merging(Merge),
}
impl Default for Sorted {
fn default() -> Self {
Self::new()
}
}
impl Sorted {
pub(crate) fn new() -> Self {
Self { stage: Arc::new(Mutex::new(Stage::Waiting)), handed: Arc::new(AtomicU64::new(0)) }
}
pub(crate) fn hold(&self, chunks: Vec<Chunk>) -> Result<()> {
*self.stage.lock().map_err(poisoned)? = Stage::Held(chunks);
Ok(())
}
pub(crate) fn merge(&self, files: Vec<Runs>, types: Vec<LogicalType>) -> Result<()> {
let mut heads = Vec::with_capacity(files.len());
for file in files {
heads.push(Head::opening(file)?);
}
*self.stage.lock().map_err(poisoned)? = Stage::Merging(Merge { heads, types });
Ok(())
}
fn held(&self) -> Option<usize> {
match &*self.stage.lock().ok()? {
Stage::Held(chunks) => Some(chunks.len()),
Stage::Waiting | Stage::Merging(_) => None,
}
}
}
impl Source for Sorted {
fn morsel(&self) -> Option<Morsel> {
let index = self.handed.fetch_add(1, Ordering::Relaxed);
match self.held() {
Some(chunks) if index < chunks as u64 => Some(Morsel::new(index, index, index + 1)),
Some(_) => None,
None if index == 0 => Some(Morsel::new(0, 0, 1)),
None => None,
}
}
fn morsels(&self, threads: usize, _weight: usize) -> Option<usize> {
let stage = self.stage.lock().ok()?;
match &*stage {
Stage::Held(chunks) => {
let rows = chunks.iter().map(Chunk::len).sum::<usize>();
let useful = rows.div_ceil(50_000).clamp(1, 4);
Some(chunks.len().min(threads).min(useful))
}
Stage::Waiting => Some(0),
Stage::Merging(_) => Some(1),
}
}
fn read(&self, morsel: &mut Morsel, out: &mut Chunk) -> Result<Progress> {
let mut stage = self.stage.lock().map_err(poisoned)?;
match &mut *stage {
Stage::Held(chunks) => {
let index = morsel.cursor() as usize;
let Some(chunk) = chunks.get(index) else {
return Err(Error::internal(format!("{morsel} asks for a chunk nobody built")));
};
*out = chunk.clone();
morsel.advance(1);
Ok(Progress::Done)
}
Stage::Waiting => Err(Error::internal("a sort was read before it finished")),
Stage::Merging(merge) => match merge.next()? {
Some(chunk) => {
*out = chunk;
if merge.spent() {
morsel.advance(1);
return Ok(Progress::Done);
}
Ok(Progress::More)
}
None => {
*out = Chunk::empty(&merge.types);
morsel.advance(1);
Ok(Progress::Done)
}
},
}
}
}
#[derive(Debug)]
struct Merge {
heads: Vec<Head>,
types: Vec<LogicalType>,
}
impl Merge {
fn spent(&self) -> bool {
self.heads.iter().all(|head| head.at >= head.chunk.len())
}
fn best(&self) -> Option<usize> {
let mut best: Option<(usize, &[u8])> = None;
for (index, head) in self.heads.iter().enumerate() {
let Some(order) = head.order() else { continue };
match best {
Some((_, so_far)) if order >= so_far => {}
_ => best = Some((index, order)),
}
}
best.map(|(index, _)| index)
}
fn next(&mut self) -> Result<Option<Chunk>> {
let mut pieces: Vec<Chunk> = Vec::new();
let mut rows = 0usize;
while rows < VECTOR_SIZE {
let Some(winner) = self.best() else { break };
let from = self.heads[winner].at;
loop {
self.heads[winner].at += 1;
rows += 1;
if rows == VECTOR_SIZE || self.heads[winner].at >= self.heads[winner].chunk.len() {
break;
}
if self.best() != Some(winner) {
break;
}
}
let head = &self.heads[winner];
pieces.push(head.slice(from, head.at - from)?);
if self.heads[winner].at >= self.heads[winner].chunk.len() {
self.heads[winner].refill()?;
}
}
if rows == 0 {
return Ok(None);
}
Ok(Some(laid(&self.types, &pieces, rows)?))
}
}
fn laid(types: &[LogicalType], pieces: &[Chunk], rows: usize) -> Result<Chunk> {
let mut columns = Vec::with_capacity(types.len());
for (position, ty) in types.iter().enumerate() {
let mut assembly = Assembly::new(ty.clone(), rows)?;
let mut at = 0u32;
for piece in pieces {
let column = piece.column(position)?;
let places: Vec<u32> = (0..column.len() as u32).map(|row| at + row).collect();
assembly.place(&places, column)?;
at += column.len() as u32;
}
columns.push(assembly.finish()?);
}
Chunk::with_rows(columns, rows)
}
#[derive(Debug)]
struct Head {
file: Runs,
chunk: Chunk,
order: Vector,
at: usize,
}
impl Head {
fn opening(file: Runs) -> Result<Self> {
let empty = Vector::constant(LogicalType::Blob, Value::Null, 0);
let mut head = Self { file, chunk: Chunk::empty(&[]), order: empty, at: 0 };
head.refill()?;
Ok(head)
}
fn order(&self) -> Option<&[u8]> {
if self.at >= self.chunk.len() {
return None;
}
self.order.bytes_at(self.at)
}
fn slice(&self, from: usize, len: usize) -> Result<Chunk> {
let mut columns = Vec::with_capacity(self.chunk.width());
for column in self.chunk.columns() {
columns.push(column.slice(from, len)?);
}
Chunk::with_rows(columns, len)
}
fn refill(&mut self) -> Result<()> {
loop {
let Some(chunk) = self.file.next_chunk()? else {
self.chunk = Chunk::empty(&[]);
self.at = 0;
return Ok(());
};
if chunk.is_empty() {
continue;
}
let rows = chunk.len();
let mut columns = chunk.into_columns();
let Some(order) = columns.pop() else {
return Err(Error::internal("a sorted run with no ordering column in it"));
};
self.order = order;
self.chunk = Chunk::with_rows(columns, rows)?;
self.at = 0;
return Ok(());
}
}
}
pub(crate) fn order_of(key: &[u8; KEY], arrival: (u64, u64)) -> [u8; ORDER] {
let mut out = [0u8; ORDER];
out[..KEY].copy_from_slice(key);
out[KEY..KEY + 8].copy_from_slice(&arrival.0.to_be_bytes());
out[KEY + 8..].copy_from_slice(&arrival.1.to_be_bytes());
out
}
pub(crate) fn ordering(orders: &[[u8; ORDER]]) -> Result<Vector> {
let mut strings = StringColumn::with_capacity(orders.len());
for order in orders {
strings.push_bytes(order);
}
Vector::flat(LogicalType::Blob, Data::Varlen(strings))
}
fn poisoned<T>(_: T) -> Error {
Error::internal("a thread panicked while holding a sort's finished rows")
}
impl fmt::Display for Sorted {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.stage.lock() {
Ok(stage) => match &*stage {
Stage::Held(chunks) => write!(f, "{} sorted chunks", chunks.len()),
Stage::Waiting => write!(f, "a sort that has not finished"),
Stage::Merging(merge) => {
let rows: u64 = merge.heads.iter().map(|head| head.file.rows()).sum();
let bytes: u64 = merge.heads.iter().map(|head| head.file.bytes()).sum();
let chunks: u64 = merge.heads.iter().map(|head| head.file.chunks()).sum();
write!(
f,
"a merge of {} runs, {rows} rows in {chunks} chunks and {bytes} bytes",
merge.heads.len()
)
}
},
Err(_) => write!(f, "sorted rows nobody can read"),
}
}
}
#[cfg(test)]
mod tests {
use rudb_common::{LogicalType, Value};
use rudb_vector::{Data, Vector};
use super::{
Chunk, KEY, ORDER, Progress, Runs, Sorted, Source, VECTOR_SIZE, order_of, ordering,
};
fn run(number: u64, keys: &[i64]) -> Runs {
let types = vec![LogicalType::BigInt, LogicalType::Blob];
let mut file = Runs::new("test", types).expect("a run file");
file.begin(keys.len()).expect("the row count");
let payload = Vector::flat(LogicalType::BigInt, Data::Int64(keys.to_vec().into()))
.expect("bigints are an i64 layout");
file.column(&payload).expect("the payload column");
let orders: Vec<[u8; ORDER]> = keys
.iter()
.enumerate()
.map(|(at, key)| {
let mut bytes = [0u8; KEY];
bytes[..8].copy_from_slice(&(*key as u64 ^ (1 << 63)).to_be_bytes());
order_of(&bytes, (number, at as u64))
})
.collect();
for block in orders.chunks(VECTOR_SIZE) {
file.part(&ordering(block).expect("blobs")).expect("the ordering column");
}
file
}
fn merged(files: Vec<Runs>) -> Vec<i64> {
let sorted = Sorted::new();
sorted.merge(files, vec![LogicalType::BigInt]).expect("the runs open");
let mut out = Vec::new();
while let Some(mut morsel) = sorted.morsel() {
loop {
let mut chunk = Chunk::empty(&[]);
let progress = sorted.read(&mut morsel, &mut chunk).expect("a chunk");
for row in 0..chunk.len() {
match chunk.value_at(row, 0) {
Value::BigInt(value) => out.push(value),
other => panic!("a {other} came out of a BIGINT column"),
}
}
if progress == Progress::Done {
break;
}
}
}
out
}
#[test]
fn two_runs_come_out_in_one_order() {
let answer = merged(vec![run(0, &[1, 4, 7, 9]), run(1, &[2, 3, 8])]);
assert_eq!(answer, vec![1, 2, 3, 4, 7, 8, 9]);
}
#[test]
fn a_tie_goes_to_whichever_row_arrived_first() {
assert_eq!(merged(vec![run(1, &[5, 5]), run(0, &[5, 5])]).len(), 4);
assert!(order_of(&[0; KEY], (0, 0)) < order_of(&[0; KEY], (1, 0)));
assert!(order_of(&[0; KEY], (0, 0)) < order_of(&[0; KEY], (0, 1)));
}
#[test]
fn a_run_of_several_chunks_is_read_through() {
let rows = VECTOR_SIZE * 2 + 5;
let long: Vec<i64> = (0..rows as i64).map(|value| value * 2).collect();
let short: Vec<i64> = (0..rows as i64 - VECTOR_SIZE as i64).map(|v| v * 2 + 1).collect();
let mut want: Vec<i64> = long.iter().chain(short.iter()).copied().collect();
want.sort_unstable();
assert_eq!(merged(vec![run(0, &long), run(1, &short)]), want);
}
#[test]
fn one_run_comes_back_as_itself() {
assert_eq!(merged(vec![run(0, &[3, 4, 5])]), vec![3, 4, 5]);
}
#[test]
fn runs_with_nothing_in_them_merge_to_nothing() {
assert!(merged(vec![run(0, &[]), run(1, &[])]).is_empty());
}
#[test]
fn the_key_bytes_order_the_way_the_numbers_do() {
let answer = merged(vec![run(0, &[-9, -1, 3]), run(1, &[-5, 0, 7])]);
assert_eq!(answer, vec![-9, -5, -1, 0, 3, 7]);
}
#[test]
fn a_sort_that_fitted_reads_back_the_chunks_it_was_given() {
let column = Vector::flat(LogicalType::BigInt, Data::Int64(vec![1, 2].into()))
.expect("bigints are an i64 layout");
let sorted = Sorted::new();
sorted.hold(vec![Chunk::new(vec![column]).expect("one column")]).expect("held");
let mut morsel = sorted.morsel().expect("the one chunk");
let mut out = Chunk::empty(&[]);
assert_eq!(sorted.read(&mut morsel, &mut out).expect("readable"), Progress::Done);
assert_eq!(out.value_at(0, 0), Value::BigInt(1));
assert!(sorted.morsel().is_none(), "and no second chunk");
}
#[test]
fn a_sort_that_has_not_finished_says_so_rather_than_answering_nothing() {
let sorted = Sorted::new();
let mut morsel = rudb_pipeline::Morsel::new(0, 0, 1);
let mut out = Chunk::empty(&[]);
let why = sorted.read(&mut morsel, &mut out).expect_err("nothing was handed over");
assert!(why.to_string().contains("read before it finished"), "{why}");
}
}