use std::sync::Arc;
use std::sync::atomic::{self, AtomicUsize};
use glaredb_error::Result;
use parking_lot::Mutex;
use super::segment::ColumnCollectionSegment;
use crate::arrays::batch::Batch;
use crate::arrays::datatype::DataType;
use crate::buffer::buffer_manager::DefaultBufferManager;
use crate::storage::projections::Projections;
#[derive(Debug)]
pub struct ColumnCollectionAppendState {
segment: ColumnCollectionSegment,
}
#[derive(Debug)]
pub struct ColumnCollectionScanState {
relative_scan_offset: usize,
next_segment_idx: usize,
segment: Option<Arc<ColumnCollectionSegment>>,
chunk_idx: usize,
}
impl ColumnCollectionScanState {
pub fn relative_scan_offset(&self) -> usize {
self.relative_scan_offset
}
}
#[derive(Debug)]
pub struct ParallelColumnCollectionScanState {
pub state: ColumnCollectionScanState,
next: Arc<AtomicUsize>,
}
#[derive(Debug)]
pub struct ConcurrentColumnCollection {
datatypes: Vec<DataType>,
segment_size: usize,
chunk_capacity: usize,
flushed: Mutex<FlushedSegments>,
}
#[derive(Debug)]
struct FlushedSegments {
segments: Vec<Arc<ColumnCollectionSegment>>,
flushed_row_count: usize,
}
impl ConcurrentColumnCollection {
pub fn new(
datatypes: impl IntoIterator<Item = DataType>,
segment_size: usize,
chunk_capacity: usize,
) -> Self {
ConcurrentColumnCollection {
datatypes: datatypes.into_iter().collect(),
segment_size,
chunk_capacity,
flushed: Mutex::new(FlushedSegments {
segments: Vec::new(),
flushed_row_count: 0,
}),
}
}
pub fn init_append_state(&self) -> ColumnCollectionAppendState {
ColumnCollectionAppendState {
segment: ColumnCollectionSegment::new(self.chunk_capacity),
}
}
pub fn init_parallel_scan_states(
&self,
num_parallel: usize,
) -> impl Iterator<Item = ParallelColumnCollectionScanState> + '_ {
CreateParallelStateIter::new(num_parallel)
}
pub fn init_scan_state(&self) -> ColumnCollectionScanState {
ColumnCollectionScanState {
relative_scan_offset: 0,
next_segment_idx: 0,
segment: None,
chunk_idx: 0,
}
}
pub fn flushed_rows(&self) -> usize {
self.flushed.lock().flushed_row_count
}
pub fn datatypes(&self) -> &[DataType] {
&self.datatypes
}
pub fn append_batch(
&self,
state: &mut ColumnCollectionAppendState,
batch: &Batch,
) -> Result<()> {
state
.segment
.append_batch(&DefaultBufferManager, batch, &self.datatypes)?;
if state.segment.num_chunks() >= self.segment_size {
self.flush(state)?;
}
Ok(())
}
pub fn flush(&self, state: &mut ColumnCollectionAppendState) -> Result<()> {
let mut segment = std::mem::replace(
&mut state.segment,
ColumnCollectionSegment::new(self.chunk_capacity),
);
segment.finish_append();
let num_rows = segment.num_rows();
if num_rows == 0 {
return Ok(());
}
let mut flushed = self.flushed.lock();
let relative_offset = flushed.flushed_row_count;
segment.set_relative_offsets(relative_offset);
flushed.segments.push(Arc::new(segment));
flushed.flushed_row_count += num_rows;
Ok(())
}
pub fn scan(
&self,
projections: &Projections,
state: &mut ColumnCollectionScanState,
output: &mut Batch,
) -> Result<usize> {
self.scan_inner(projections, state, output, |curr| curr + 1)
}
pub fn parallel_scan(
&self,
projections: &Projections,
state: &mut ParallelColumnCollectionScanState,
output: &mut Batch,
) -> Result<usize> {
self.scan_inner(projections, &mut state.state, output, |_curr| {
state.next.fetch_add(1, atomic::Ordering::Relaxed)
})
}
fn scan_inner(
&self,
projections: &Projections,
state: &mut ColumnCollectionScanState,
output: &mut Batch,
next_segment_fn: impl Fn(usize) -> usize,
) -> Result<usize> {
loop {
if state.segment.is_none() {
let flushed = self.flushed.lock();
let segment = match flushed.segments.get(state.next_segment_idx) {
Some(segment) => segment,
None => {
output.set_num_rows(0)?;
return Ok(0);
}
};
state.segment = Some(segment.clone());
state.relative_scan_offset = 0;
state.next_segment_idx = next_segment_fn(state.next_segment_idx);
state.chunk_idx = 0;
}
let segment = state.segment.as_ref().unwrap();
match segment.get_chunk(state.chunk_idx) {
Some(chunk) => {
let num_rows = chunk.scan(projections, output)?;
state.chunk_idx += 1;
state.relative_scan_offset = chunk.relative_offset;
return Ok(num_rows);
}
None => {
state.segment = None;
}
}
}
}
}
#[derive(Debug)]
struct CreateParallelStateIter {
next: Arc<AtomicUsize>,
idx: usize,
count: usize,
}
impl CreateParallelStateIter {
fn new(num_parallel: usize) -> Self {
CreateParallelStateIter {
next: Arc::new(AtomicUsize::new(num_parallel)),
idx: 0,
count: num_parallel,
}
}
}
impl Iterator for CreateParallelStateIter {
type Item = ParallelColumnCollectionScanState;
fn next(&mut self) -> Option<Self::Item> {
if self.idx >= self.count {
return None;
}
let state = ParallelColumnCollectionScanState {
next: self.next.clone(),
state: ColumnCollectionScanState {
relative_scan_offset: 0,
next_segment_idx: self.idx,
segment: None,
chunk_idx: 0,
},
};
self.idx += 1;
Some(state)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let rem = self.count - self.idx;
(rem, Some(rem))
}
}
impl ExactSizeIterator for CreateParallelStateIter {}
#[cfg(test)]
mod tests {
use super::*;
use crate::generate_batch;
use crate::testutil::arrays::assert_batches_eq;
#[test]
fn append_scan_simple() {
let collection =
ConcurrentColumnCollection::new([DataType::int32(), DataType::utf8()], 16, 16);
let projections = Projections::new([0, 1]);
let mut append_state = collection.init_append_state();
let mut scan_state = collection.init_scan_state();
let input = generate_batch!([4, 5, 6, 7], ["a", "b", "c", "d"]);
collection.append_batch(&mut append_state, &input).unwrap();
collection.flush(&mut append_state).unwrap();
let mut output = Batch::new([DataType::int32(), DataType::utf8()], 16).unwrap();
collection
.scan(&projections, &mut scan_state, &mut output)
.unwrap();
assert_batches_eq(&input, &output);
assert_eq!(0, scan_state.relative_scan_offset());
collection
.scan(&projections, &mut scan_state, &mut output)
.unwrap();
assert_eq!(0, output.num_rows());
assert_eq!(0, scan_state.relative_scan_offset());
let input = generate_batch!([1, 2, 3, 4], ["e", "f", "g", "h"]);
collection.append_batch(&mut append_state, &input).unwrap();
collection.flush(&mut append_state).unwrap();
collection
.scan(&projections, &mut scan_state, &mut output)
.unwrap();
assert_batches_eq(&input, &output);
assert_eq!(4, scan_state.relative_scan_offset());
}
#[test]
fn scan_from_many_chunks() {
let collection = ConcurrentColumnCollection::new([DataType::int32()], 2, 4);
let mut append_state = collection.init_append_state();
for idx in 0..16 {
let batch = generate_batch!(std::iter::repeat(idx).take(4));
collection.append_batch(&mut append_state, &batch).unwrap();
}
collection.flush(&mut append_state).unwrap();
let mut out = Batch::new([DataType::int32()], 4).unwrap();
let projections = Projections::new([0]);
let mut scan_state = collection.init_scan_state();
for idx in 0..16 {
let expected_offset = idx * 4;
let count = collection
.scan(&projections, &mut scan_state, &mut out)
.unwrap();
assert_eq!(4, count);
assert_eq!(expected_offset, scan_state.relative_scan_offset());
}
}
#[test]
fn scan_projected_column() {
let collection =
ConcurrentColumnCollection::new([DataType::int32(), DataType::utf8()], 16, 16);
let projections = Projections::new([1]);
let mut append_state = collection.init_append_state();
let mut scan_state = collection.init_scan_state();
let input = generate_batch!([4, 5, 6, 7], ["a", "b", "c", "d"]);
collection.append_batch(&mut append_state, &input).unwrap();
collection.flush(&mut append_state).unwrap();
let mut output = Batch::new([DataType::utf8()], 16).unwrap();
collection
.scan(&projections, &mut scan_state, &mut output)
.unwrap();
let expected = generate_batch!(["a", "b", "c", "d"]);
assert_batches_eq(&expected, &output);
}
#[test]
fn scan_parallel() {
let collection =
ConcurrentColumnCollection::new([DataType::int32(), DataType::utf8()], 1, 2);
let mut append_state = collection.init_append_state();
let input1 = generate_batch!([4, 5], ["a", "b"]);
collection.append_batch(&mut append_state, &input1).unwrap();
collection.flush(&mut append_state).unwrap();
let input2 = generate_batch!([6, 7], ["c", "d"]);
collection.append_batch(&mut append_state, &input2).unwrap();
collection.flush(&mut append_state).unwrap();
let projections = Projections::new([0, 1]);
let mut states: Vec<_> = collection.init_parallel_scan_states(2).collect();
assert_eq!(2, states.len());
let mut output1 = Batch::new([DataType::int32(), DataType::utf8()], 2).unwrap();
collection
.parallel_scan(&projections, &mut states[0], &mut output1)
.unwrap();
let expected1 = generate_batch!([4, 5], ["a", "b"]);
assert_batches_eq(&expected1, &output1);
assert_eq!(0, states[0].state.relative_scan_offset());
let mut output2 = Batch::new([DataType::int32(), DataType::utf8()], 2).unwrap();
collection
.parallel_scan(&projections, &mut states[1], &mut output2)
.unwrap();
let expected2 = generate_batch!([6, 7], ["c", "d"]);
assert_batches_eq(&expected2, &output2);
assert_eq!(2, states[1].state.relative_scan_offset());
collection
.parallel_scan(&projections, &mut states[0], &mut output1)
.unwrap();
assert_eq!(0, output1.num_rows());
assert_eq!(0, states[0].state.relative_scan_offset());
collection
.parallel_scan(&projections, &mut states[1], &mut output2)
.unwrap();
assert_eq!(0, output2.num_rows());
assert_eq!(2, states[1].state.relative_scan_offset());
}
#[test]
fn scan_parallel_exhaust_refill() {
let collection =
ConcurrentColumnCollection::new([DataType::int32(), DataType::utf8()], 1, 2);
let projections = Projections::new([0, 1]);
let mut append_state = collection.init_append_state();
let mut scan_states: Vec<_> = collection.init_parallel_scan_states(2).collect();
let input1 = generate_batch!([4, 5], ["a", "b"]);
collection.append_batch(&mut append_state, &input1).unwrap();
collection.flush(&mut append_state).unwrap();
let mut out1 = Batch::new([DataType::int32(), DataType::utf8()], 2).unwrap();
collection
.parallel_scan(&projections, &mut scan_states[0], &mut out1)
.unwrap();
let expected1 = generate_batch!([4, 5], ["a", "b"]);
assert_batches_eq(&expected1, &out1);
assert_eq!(0, scan_states[0].state.relative_scan_offset());
let mut out2 = Batch::new([DataType::int32(), DataType::utf8()], 2).unwrap();
collection
.parallel_scan(&projections, &mut scan_states[1], &mut out2)
.unwrap();
assert_eq!(0, out2.num_rows());
assert_eq!(0, scan_states[1].state.relative_scan_offset());
let input2 = generate_batch!([6, 7], ["c", "d"]);
collection.append_batch(&mut append_state, &input2).unwrap();
collection.flush(&mut append_state).unwrap();
collection
.parallel_scan(&projections, &mut scan_states[1], &mut out2)
.unwrap();
let expected2 = generate_batch!([6, 7], ["c", "d"]);
assert_batches_eq(&expected2, &out2);
assert_eq!(2, scan_states[1].state.relative_scan_offset());
}
}