use std::sync::{Arc, Mutex};
use rudb_common::{Error, Memory, Reservation, Result, Value};
use rudb_pipeline::{Progress, Sink};
use rudb_vector::Chunk;
use crate::buffer::Buffered;
use crate::rows;
#[derive(Debug, Default, Clone)]
pub(crate) struct Rows {
held: Arc<Mutex<Vec<Vec<Value>>>>,
}
impl Rows {
pub(crate) fn take(&self) -> Result<Vec<Vec<Value>>> {
Ok(std::mem::take(&mut *self.held.lock().map_err(poisoned)?))
}
}
#[derive(Debug)]
pub(crate) struct Gather {
memory: Memory,
out: Rows,
charged: Mutex<Vec<Reservation>>,
}
#[derive(Debug)]
pub(crate) struct Gathering {
rows: Vec<Vec<Value>>,
charged: Reservation,
counted: u64,
}
impl Gather {
pub(crate) fn new(memory: &Memory) -> (Self, Rows) {
let out = Rows::default();
let gather =
Self { memory: memory.clone(), out: out.clone(), charged: Mutex::new(Vec::new()) };
(gather, out)
}
}
impl Sink for Gather {
type Local = Gathering;
fn local(&self) -> Gathering {
Gathering { rows: Vec::new(), charged: self.memory.reservation(), counted: 0 }
}
fn sink(&self, chunk: &Chunk, local: &mut Gathering) -> Result<Progress> {
take(chunk, local)?;
Ok(Progress::More)
}
fn combine(&self, local: Gathering) -> Result<()> {
self.out.held.lock().map_err(poisoned)?.extend(local.rows);
self.charged.lock().map_err(poisoned)?.push(local.charged);
Ok(())
}
fn finalize(&self) -> Result<()> {
Ok(())
}
}
pub(crate) fn take(chunk: &Chunk, local: &mut Gathering) -> Result<()> {
let mut taken = 0;
for row in 0..chunk.len() {
let values: Vec<Value> = chunk.row(row).collect();
taken += rows::heap(&values);
local.rows.push(values);
}
local.charged.grow(taken)?;
let slots = u64::try_from(local.rows.capacity() * size_of::<Vec<Value>>()).unwrap_or(u64::MAX);
rows::capacity(slots, &mut local.counted, &mut local.charged)
}
#[derive(Debug)]
pub(crate) struct Keep {
memory: Memory,
chunks: Mutex<Vec<Chunk>>,
charged: Mutex<Vec<Reservation>>,
out: Buffered,
}
#[derive(Debug)]
pub(crate) struct Kept {
chunks: Vec<Chunk>,
charged: Reservation,
}
impl Keep {
pub(crate) fn new(memory: &Memory) -> (Self, Buffered) {
let out = Buffered::new();
let keep = Self {
memory: memory.clone(),
chunks: Mutex::new(Vec::new()),
charged: Mutex::new(Vec::new()),
out: out.clone(),
};
(keep, out)
}
}
impl Sink for Keep {
type Local = Kept;
fn local(&self) -> Kept {
Kept { chunks: Vec::new(), charged: self.memory.reservation() }
}
fn sink(&self, chunk: &Chunk, local: &mut Kept) -> Result<Progress> {
if !chunk.is_empty() {
local.charged.grow(u64::try_from(chunk.footprint()).unwrap_or(u64::MAX))?;
local.chunks.push(chunk.clone());
}
Ok(Progress::More)
}
fn combine(&self, local: Kept) -> Result<()> {
self.chunks.lock().map_err(poisoned)?.extend(local.chunks);
self.charged.lock().map_err(poisoned)?.push(local.charged);
Ok(())
}
fn finalize(&self) -> Result<()> {
let chunks = std::mem::take(&mut *self.chunks.lock().map_err(poisoned)?);
self.out.fill(chunks)
}
}
pub(crate) fn gathering(memory: &Memory) -> Gathering {
Gathering { rows: Vec::new(), charged: memory.reservation(), counted: 0 }
}
pub(crate) fn into_parts(local: Gathering) -> (Vec<Vec<Value>>, Reservation) {
(local.rows, local.charged)
}
fn poisoned<T>(_: T) -> Error {
Error::internal("a thread panicked while holding the rows an operator gathered")
}
#[cfg(test)]
mod tests {
use rudb_common::{LogicalType, Memory, Value};
use rudb_vector::{Data, Vector};
use super::{Chunk, Gather, Keep, Sink};
fn chunk(values: &[i32]) -> Chunk {
let column = Vector::flat(LogicalType::Integer, Data::Int32(values.to_vec().into()))
.expect("integers are an i32 layout");
Chunk::new(vec![column]).expect("one column is one length")
}
fn first(rows: &[Vec<Value>]) -> Vec<Value> {
rows.iter().map(|row| row[0].clone()).collect()
}
#[test]
fn what_goes_in_comes_out_in_the_order_it_was_combined() {
let memory = Memory::unlimited();
let (gather, rows) = Gather::new(&memory);
let mut local = gather.local();
gather.sink(&chunk(&[1, 2]), &mut local).expect("two rows");
gather.sink(&chunk(&[3]), &mut local).expect("one more");
gather.combine(local).expect("the one instance");
gather.finalize().expect("nothing to do");
assert_eq!(
first(&rows.take().expect("readable")),
[Value::Integer(1), Value::Integer(2), Value::Integer(3)]
);
}
#[test]
fn two_instances_both_end_up_in_the_one_list() {
let memory = Memory::unlimited();
let (gather, rows) = Gather::new(&memory);
let mut left = gather.local();
let mut right = gather.local();
gather.sink(&chunk(&[1]), &mut left).expect("one row");
gather.sink(&chunk(&[2]), &mut right).expect("one row");
gather.combine(left).expect("the first instance");
gather.combine(right).expect("the second instance");
assert_eq!(first(&rows.take().expect("readable")), [Value::Integer(1), Value::Integer(2)]);
}
#[test]
fn taking_the_rows_empties_them() {
let memory = Memory::unlimited();
let (gather, rows) = Gather::new(&memory);
gather.combine(gather.local()).expect("an instance that saw nothing");
assert!(rows.take().expect("readable").is_empty());
assert!(rows.take().expect("readable").is_empty());
}
#[test]
fn a_keep_holds_the_chunks_it_was_given_and_drops_the_empty_ones() {
let memory = Memory::unlimited();
let (keep, out) = Keep::new(&memory);
let mut local = keep.local();
keep.sink(&chunk(&[1, 2]), &mut local).expect("two rows");
keep.sink(&Chunk::empty(&[LogicalType::Integer]), &mut local).expect("no rows");
keep.sink(&chunk(&[3]), &mut local).expect("one row");
keep.combine(local).expect("the one instance");
keep.finalize().expect("the chunks");
assert_eq!(out.len().expect("readable"), 2);
assert_eq!(out.at(0).expect("readable").expect("the first").len(), 2);
assert_eq!(out.at(1).expect("readable").expect("the second").len(), 1);
}
}