use std::num::NonZeroU64;
use std::sync::Arc;
use async_trait::async_trait;
use futures::StreamExt;
use futures::stream;
use parking_lot::Mutex;
use roaring::RoaringBitmap;
use tracing::trace;
use vortex_array::ArrayRef;
use vortex_array::ExecutionCtx;
use vortex_array::VortexSessionExecute;
use vortex_array::dtype::DType;
use vortex_array::stream::ArrayStreamAdapter;
use vortex_array::stream::ArrayStreamExt;
use vortex_array::stream::SendableArrayStream;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_ensure;
use vortex_session::VortexSession;
use crate::BufferedBytesReservation;
use crate::BufferedBytesTracker;
use crate::LayoutRef;
use crate::LayoutStrategy;
use crate::LayoutWriterContext;
use crate::layouts::indexed::IndexPartitioning;
use crate::layouts::indexed::IndexSpec;
use crate::layouts::indexed::IndexedLayout;
use crate::layouts::indexed::index::IndexBuilder;
use crate::layouts::indexed::index::IndexVTable;
use crate::layouts::indexed::index::IndexVTableRef;
use crate::segments::SegmentSinkRef;
use crate::sequence::SendableSequentialStream;
use crate::sequence::SequencePointer;
use crate::sequence::SequentialArrayStreamExt;
use crate::sequence::SequentialStreamAdapter;
use crate::sequence::SequentialStreamExt;
#[derive(Clone, Debug)]
pub struct IndexConfig {
vtable: IndexVTableRef,
options: Vec<u8>,
partition_len: Option<NonZeroU64>,
}
impl IndexConfig {
pub fn new(vtable: IndexVTableRef, options: Vec<u8>) -> Self {
Self {
vtable,
options,
partition_len: None,
}
}
pub fn with_defaults(vtable: IndexVTableRef) -> Self {
Self::new(vtable, Vec::new())
}
pub fn with_partition_len(mut self, partition_len: NonZeroU64) -> Self {
self.partition_len = Some(partition_len);
self
}
}
pub struct IndexedStrategy {
data: Arc<dyn LayoutStrategy>,
index: Arc<dyn LayoutStrategy>,
configs: Arc<[IndexConfig]>,
data_block_len: Option<u64>,
}
impl IndexedStrategy {
pub fn new<D: LayoutStrategy, I: LayoutStrategy>(
data: D,
index: I,
configs: Vec<IndexConfig>,
) -> Self {
Self {
data: Arc::new(data),
index: Arc::new(index),
configs: configs.into(),
data_block_len: None,
}
}
pub fn with_data_block_len(mut self, data_block_len: u64) -> Self {
self.data_block_len = Some(data_block_len);
self
}
}
pub(crate) trait IndexWriter: Send + Sync {
fn index_dtype(&self, dtype: &DType) -> Option<DType>;
fn builder(
&self,
dtype: &DType,
index_dtype: &DType,
data_block_len: Option<u64>,
session: &VortexSession,
) -> VortexResult<Box<dyn PartitionBuilder>>;
}
pub(crate) struct TypedIndexWriter<V: IndexVTable> {
vtable: Arc<V>,
options: V::Options,
}
pub(crate) trait PartitionBuilder: Send {
fn push(
&mut self,
chunk: &ArrayRef,
row_offset: u64,
ctx: &mut ExecutionCtx,
) -> VortexResult<()>;
fn finish(self: Box<Self>) -> VortexResult<PartitionOutput>;
fn buffered_bytes(&self) -> u64;
}
struct TypedPartitionBuilder<V: IndexVTable> {
vtable: Arc<V>,
builder: V::Builder,
index_dtype: DType,
session: VortexSession,
}
impl<V: IndexVTable> PartitionBuilder for TypedPartitionBuilder<V> {
fn push(
&mut self,
chunk: &ArrayRef,
row_offset: u64,
ctx: &mut ExecutionCtx,
) -> VortexResult<()> {
self.builder.push(chunk, row_offset, ctx)
}
fn finish(self: Box<Self>) -> VortexResult<PartitionOutput> {
let Self {
vtable,
builder,
index_dtype,
session,
} = *self;
let Some((chunks, options)) = builder.finish()? else {
return Ok(None);
};
let mut ctx = session.create_execution_ctx();
let arrays = chunks
.iter()
.map(|chunk| {
let array = vtable.encode(chunk, &options, &mut ctx)?;
vortex_ensure!(
array.dtype() == &index_dtype,
"Index {} encoded a chunk of dtype {}, but the index kind declared {index_dtype}",
vtable.id(),
array.dtype()
);
Ok(array)
})
.collect::<VortexResult<Vec<_>>>()?;
let content =
ArrayStreamAdapter::new(index_dtype, stream::iter(arrays.into_iter().map(Ok)));
Ok(Some((
ArrayStreamExt::boxed(content),
vtable.serialize_options(&options),
)))
}
fn buffered_bytes(&self) -> u64 {
self.builder.buffered_bytes()
}
}
impl<V: IndexVTable> TypedIndexWriter<V> {
pub(crate) fn try_new(vtable: Arc<V>, options: &[u8]) -> VortexResult<Self> {
let options = vtable.deserialize_options(options)?;
Ok(Self { vtable, options })
}
}
impl<V: IndexVTable> IndexWriter for TypedIndexWriter<V> {
fn index_dtype(&self, dtype: &DType) -> Option<DType> {
self.vtable.index_dtype(dtype, &self.options)
}
fn builder(
&self,
dtype: &DType,
index_dtype: &DType,
data_block_len: Option<u64>,
session: &VortexSession,
) -> VortexResult<Box<dyn PartitionBuilder>> {
Ok(Box::new(TypedPartitionBuilder {
vtable: Arc::clone(&self.vtable),
builder: self
.vtable
.builder(dtype, &self.options, data_block_len, session)?,
index_dtype: index_dtype.clone(),
session: session.clone(),
}))
}
}
struct BuilderFactory {
dtype: DType,
data_block_len: Option<u64>,
session: VortexSession,
}
impl BuilderFactory {
fn builder(
&self,
writer: &dyn IndexWriter,
index_dtype: &DType,
) -> VortexResult<Box<dyn PartitionBuilder>> {
writer.builder(&self.dtype, index_dtype, self.data_block_len, &self.session)
}
}
type PartitionOutput = Option<(SendableArrayStream, Vec<u8>)>;
struct IndexState {
vtable: IndexVTableRef,
writer: Arc<dyn IndexWriter>,
index_dtype: DType,
partition_len: Option<u64>,
builder: Box<dyn PartitionBuilder>,
partition_rows: u64,
finished: Vec<PartitionOutput>,
finished_bytes: u64,
}
impl IndexState {
fn push(
&mut self,
chunk: &ArrayRef,
factory: &BuilderFactory,
ctx: &mut ExecutionCtx,
) -> VortexResult<()> {
let Some(partition_len) = self.partition_len else {
return self
.builder
.push(chunk, self.partition_rows, ctx)
.map(|()| {
self.partition_rows += chunk.len() as u64;
});
};
let mut start = 0;
while start < chunk.len() {
let room = usize::try_from(partition_len - self.partition_rows)?;
let end = chunk.len().min(start + room);
let piece = if start == 0 && end == chunk.len() {
chunk.clone()
} else {
chunk.slice(start..end)?
};
self.builder.push(&piece, self.partition_rows, ctx)?;
self.partition_rows += (end - start) as u64;
start = end;
if self.partition_rows == partition_len {
let next = factory.builder(self.writer.as_ref(), &self.index_dtype)?;
self.finish_partition(next)?;
}
}
Ok(())
}
fn finish_partition(&mut self, next: Box<dyn PartitionBuilder>) -> VortexResult<()> {
let done = std::mem::replace(&mut self.builder, next);
self.finished_bytes += done.buffered_bytes();
self.finished.push(done.finish()?);
self.partition_rows = 0;
Ok(())
}
fn buffered_bytes(&self) -> u64 {
self.builder.buffered_bytes() + self.finished_bytes
}
fn finish(mut self) -> VortexResult<Vec<PartitionOutput>> {
if self.partition_len.is_none() || self.partition_rows > 0 {
self.finished.push(self.builder.finish()?);
}
Ok(self.finished)
}
}
struct BuilderState {
factory: BuilderFactory,
indexes: Vec<IndexState>,
buffered: Option<BufferedBytesReservation>,
}
impl BuilderState {
fn push(&mut self, chunk: &ArrayRef, tracker: &BufferedBytesTracker) -> VortexResult<()> {
let mut ctx = self.factory.session.create_execution_ctx();
for index in &mut self.indexes {
index.push(chunk, &self.factory, &mut ctx)?;
}
let total: u64 = self.indexes.iter().map(IndexState::buffered_bytes).sum();
self.buffered = Some(tracker.reserve(total));
Ok(())
}
}
struct IndexContent {
stream: SendableArrayStream,
options: Vec<u8>,
dtype: DType,
partition_rows: Arc<Mutex<Vec<u64>>>,
declined: RoaringBitmap,
}
impl IndexContent {
fn assemble(
id: impl std::fmt::Display,
dtype: DType,
partitions: Vec<PartitionOutput>,
) -> VortexResult<Option<Self>> {
let mut declined = RoaringBitmap::new();
let mut built = Vec::with_capacity(partitions.len());
let mut options: Option<Vec<u8>> = None;
let partition_count = partitions.len();
for (partition, output) in partitions.into_iter().enumerate() {
let Some((content, partition_options)) = output else {
declined.insert(u32::try_from(partition)?);
continue;
};
match &options {
None => options = Some(partition_options),
Some(first) => vortex_ensure!(
*first == partition_options,
"Index {id} partition {partition} finished with different options than earlier partitions"
),
}
built.push((partition, content));
}
let Some(options) = options else {
return Ok(None);
};
let partition_rows = Arc::new(Mutex::new(vec![0u64; partition_count]));
let counts = Arc::clone(&partition_rows);
let chunks = stream::iter(built).flat_map(move |(partition, content)| {
let counts = Arc::clone(&counts);
content.map(move |chunk| {
if let Ok(chunk) = &chunk {
counts.lock()[partition] += chunk.len() as u64;
}
chunk
})
});
Ok(Some(Self {
stream: ArrayStreamExt::boxed(ArrayStreamAdapter::new(dtype.clone(), chunks)),
options,
dtype,
partition_rows,
declined,
}))
}
}
fn partitioning(
partition_len: u64,
partition_rows: &[u64],
declined: RoaringBitmap,
) -> IndexPartitioning {
let ends = partition_rows
.iter()
.scan(0u64, |end, rows| {
*end += rows;
Some(*end)
})
.collect();
IndexPartitioning::new(partition_len, ends, declined)
}
#[async_trait]
impl LayoutStrategy for IndexedStrategy {
async fn write_stream(
&self,
ctx: LayoutWriterContext,
segment_sink: SegmentSinkRef,
stream: SendableSequentialStream,
mut eof: SequencePointer,
session: &VortexSession,
) -> VortexResult<LayoutRef> {
let dtype = stream.dtype().clone();
for config in self.configs.iter() {
let Some(partition_len) = config.partition_len else {
continue;
};
let Some(block_len) = self.data_block_len.filter(|len| *len > 0) else {
vortex_bail!(
"Index {} is partitioned every {partition_len} rows, which requires a non-zero \
data block length (IndexedStrategy::with_data_block_len)",
config.vtable.id()
);
};
vortex_ensure!(
partition_len.get() % block_len == 0,
"Index {} partition length {partition_len} must be a multiple of the data block \
length {block_len}",
config.vtable.id()
);
}
let factory = BuilderFactory {
dtype: dtype.clone(),
data_block_len: self.data_block_len,
session: session.clone(),
};
let mut indexes = Vec::with_capacity(self.configs.len());
for config in self.configs.iter() {
let writer = config.vtable.open_writer(&config.options)?;
let Some(index_dtype) = writer.index_dtype(&dtype) else {
continue;
};
indexes.push(IndexState {
builder: factory.builder(writer.as_ref(), &index_dtype)?,
writer,
index_dtype,
vtable: config.vtable.clone(),
partition_len: config.partition_len.map(NonZeroU64::get),
partition_rows: 0,
finished: Vec::new(),
finished_bytes: 0,
});
}
if indexes.is_empty() {
return self
.data
.write_stream(ctx, segment_sink, stream, eof, session)
.await;
}
let state = Arc::new(Mutex::new(BuilderState {
factory,
indexes,
buffered: None,
}));
let feed_state = Arc::clone(&state);
let feed_tracker = ctx.buffered_bytes_tracker().clone();
let stream = SequentialStreamAdapter::new(
dtype,
stream.map(move |item| {
let (sequence_id, chunk) = item?;
feed_state.lock().push(&chunk, &feed_tracker)?;
Ok((sequence_id, chunk))
}),
)
.sendable();
let data_eof = eof.split_off();
let data_layout = self
.data
.write_stream(
ctx.clone(),
Arc::clone(&segment_sink),
stream,
data_eof,
session,
)
.await?;
let indexes = {
let mut state = state.lock();
state.buffered.take();
std::mem::take(&mut state.indexes)
};
let mut index_layouts = Vec::with_capacity(indexes.len());
let mut specs = Vec::with_capacity(indexes.len());
for index in indexes {
let vtable = index.vtable.clone();
let partition_len = index.partition_len;
let declared = index.index_dtype.clone();
let Some(IndexContent {
stream: content,
options,
dtype: index_dtype,
partition_rows,
declined,
}) = IndexContent::assemble(vtable.id(), declared, index.finish()?)?
else {
trace!(index = %vtable.id(), "index builder declined, writing no child");
continue;
};
let content_ptr = eof.split_off();
let child_eof = eof.split_off();
let layout = self
.index
.write_stream(
ctx.clone(),
Arc::clone(&segment_sink),
content.sequenced(content_ptr),
child_eof,
session,
)
.await?;
let partitioning =
partition_len.map(|len| partitioning(len, &partition_rows.lock(), declined));
specs.push(IndexSpec::new(vtable, options, index_dtype, partitioning));
index_layouts.push(layout);
}
if index_layouts.is_empty() {
return Ok(data_layout);
}
Ok(IndexedLayout::try_new(data_layout, index_layouts, specs)?.into_layout())
}
}