pub mod index;
pub(crate) mod reader;
pub mod session;
#[cfg(any(test, feature = "_test-harness"))]
pub mod test_harness;
pub mod writer;
use std::sync::Arc;
use prost::Message;
use roaring::RoaringBitmap;
use vortex_array::DeserializeMetadata;
use vortex_array::SerializeMetadata;
use vortex_array::dtype::DType;
use vortex_array::dtype::proto::dtype as pb;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_ensure;
use vortex_error::vortex_err;
use vortex_error::vortex_panic;
use vortex_session::VortexSession;
use vortex_session::registry::CachedId;
pub use self::index::BuiltIndex;
pub use self::index::IndexBuilder;
pub use self::index::IndexExactness;
pub use self::index::IndexId;
pub use self::index::IndexQueryPlan;
pub use self::index::IndexVTable;
pub use self::index::IndexVTableRef;
pub use self::index::RowLocator;
pub use self::session::IndexSession;
pub use self::session::IndexSessionExt;
pub use self::writer::IndexConfig;
pub use self::writer::IndexedStrategy;
use crate::Layout;
use crate::LayoutChildType;
use crate::LayoutDeserializeArgs;
use crate::LayoutId;
use crate::LayoutParts;
use crate::LayoutReaderContext;
use crate::LayoutReaderRef;
use crate::LayoutRef;
use crate::VTable;
use crate::children::layout_children;
use crate::layouts::indexed::reader::IndexedReader;
use crate::segments::SegmentSource;
pub const INDEXED_LAYOUT_ID: &str = "vortex.indexed";
const INDEXED_METADATA_VERSION: u8 = 1;
#[derive(Clone, Debug)]
pub struct Indexed;
#[derive(Clone, Debug)]
pub struct IndexPartitioning {
partition_len: u64,
index_ends: Arc<[u64]>,
declined: RoaringBitmap,
}
impl IndexPartitioning {
pub fn new(partition_len: u64, index_ends: Vec<u64>, declined: RoaringBitmap) -> Self {
Self {
partition_len,
index_ends: index_ends.into(),
declined,
}
}
pub fn partition_len(&self) -> u64 {
self.partition_len
}
pub fn index_ends(&self) -> &[u64] {
&self.index_ends
}
pub fn declined(&self) -> &RoaringBitmap {
&self.declined
}
fn validate(&self, id: IndexId, data_row_count: u64, index_row_count: u64) -> VortexResult<()> {
vortex_ensure!(
self.partition_len > 0,
"Index {id} has a zero partition length"
);
let expected = data_row_count.div_ceil(self.partition_len);
vortex_ensure!(
self.index_ends.len() as u64 == expected,
"Index {id} has {} partitions, expected {expected} for {data_row_count} rows at {} rows each",
self.index_ends.len(),
self.partition_len
);
vortex_ensure!(
self.index_ends.is_sorted(),
"Index {id} partition ends must be non-decreasing"
);
vortex_ensure!(
self.index_ends.last().copied().unwrap_or(0) == index_row_count,
"Index {id} partition ends do not cover its {index_row_count}-row child"
);
vortex_ensure!(
self.declined.max().is_none_or(|p| u64::from(p) < expected),
"Index {id} declines a partition past its last"
);
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct IndexSpec {
id: IndexId,
options: Arc<[u8]>,
index_dtype: DType,
partitioning: Option<IndexPartitioning>,
vtable: Option<IndexVTableRef>,
}
impl IndexSpec {
pub fn new(
vtable: IndexVTableRef,
options: Vec<u8>,
index_dtype: DType,
partitioning: Option<IndexPartitioning>,
) -> Self {
Self {
id: vtable.id(),
options: options.into(),
index_dtype,
partitioning,
vtable: Some(vtable),
}
}
pub fn partitioning(&self) -> Option<&IndexPartitioning> {
self.partitioning.as_ref()
}
pub fn id(&self) -> IndexId {
self.id
}
pub fn options(&self) -> &[u8] {
&self.options
}
pub fn index_dtype(&self) -> &DType {
&self.index_dtype
}
pub fn vtable(&self) -> Option<&IndexVTableRef> {
self.vtable.as_ref()
}
}
#[derive(Clone, Debug)]
pub struct IndexedData {
indexes: Arc<[IndexSpec]>,
}
pub type IndexedLayout = Layout<Indexed>;
impl IndexedLayout {
pub fn try_new(
data: LayoutRef,
index_layouts: Vec<LayoutRef>,
indexes: Vec<IndexSpec>,
) -> VortexResult<Self> {
vortex_ensure!(
index_layouts.len() == indexes.len(),
"IndexedLayout got {} index children for {} specs",
index_layouts.len(),
indexes.len()
);
for (layout, spec) in index_layouts.iter().zip(&indexes) {
vortex_ensure!(
layout.dtype() == &spec.index_dtype,
"Index child dtype {} does not match spec dtype {} for {}",
layout.dtype(),
spec.index_dtype,
spec.id
);
if let Some(partitioning) = &spec.partitioning {
partitioning.validate(spec.id, data.row_count(), layout.row_count())?;
}
}
let dtype = data.dtype().clone();
let row_count = data.row_count();
let mut children = Vec::with_capacity(1 + index_layouts.len());
children.push(data);
children.extend(index_layouts);
Ok(LayoutParts::new(
Indexed,
dtype,
row_count,
Vec::new(),
layout_children(children),
IndexedData {
indexes: indexes.into(),
},
)
.into_typed())
}
pub fn indexes(&self) -> &Arc<[IndexSpec]> {
&self.indexes
}
}
impl VTable for Indexed {
type LayoutData = IndexedData;
type Metadata = IndexedMetadata;
fn id(&self) -> LayoutId {
static ID: CachedId = CachedId::new(INDEXED_LAYOUT_ID);
*ID
}
fn metadata(layout: &Layout<Self>) -> Self::Metadata {
IndexedMetadata {
indexes: layout
.indexes
.iter()
.map(|spec| IndexSpecProto {
id: spec.id.to_string(),
options: spec.options.to_vec(),
index_dtype: Some(
pb::DType::try_from(&spec.index_dtype)
.vortex_expect("index child dtype should be serializable"),
),
partition_len: spec.partitioning.as_ref().map(|p| p.partition_len),
partition_index_ends: spec
.partitioning
.as_ref()
.map(|p| p.index_ends.to_vec())
.unwrap_or_default(),
declined_partitions: spec
.partitioning
.as_ref()
.map(|p| p.declined.iter().collect())
.unwrap_or_default(),
})
.collect::<Vec<_>>()
.into(),
}
}
fn deserialize(
&self,
args: &LayoutDeserializeArgs<'_>,
metadata: &IndexedMetadata,
) -> VortexResult<Self::LayoutData> {
vortex_ensure!(
args.children.nchildren() == 1 + metadata.indexes.len(),
"IndexedLayout expects {} children (data + {} indexes), got {}",
1 + metadata.indexes.len(),
metadata.indexes.len(),
args.children.nchildren()
);
let registry = args.session.indexes();
let indexes = metadata
.indexes
.iter()
.enumerate()
.map(|(idx, spec)| {
let index_dtype = spec
.index_dtype
.as_ref()
.map(|dtype| DType::from_proto(dtype, args.session))
.transpose()?
.ok_or_else(|| vortex_err!("Index spec {} is missing its dtype", spec.id))?;
let id = IndexId::from(spec.id.as_str());
let partitioning = spec
.partition_len
.map(|partition_len| {
let partitioning = IndexPartitioning::new(
partition_len,
spec.partition_index_ends.clone(),
spec.declined_partitions.iter().copied().collect(),
);
partitioning.validate(
id,
args.row_count,
args.children.child_row_count(idx + 1),
)?;
Ok::<_, vortex_error::VortexError>(partitioning)
})
.transpose()?;
Ok(IndexSpec {
id,
options: spec.options.as_slice().into(),
index_dtype,
partitioning,
vtable: registry.find(&id),
})
})
.collect::<VortexResult<Vec<_>>>()?;
args.children.child(0, args.dtype)?;
for (idx, spec) in indexes.iter().enumerate() {
args.children.child(idx + 1, &spec.index_dtype)?;
}
Ok(IndexedData {
indexes: indexes.into(),
})
}
fn child_dtype(layout: &Layout<Self>, slot: usize) -> VortexResult<DType> {
match slot {
0 => Ok(layout.dtype().clone()),
_ => {
let Some(spec) = layout.indexes.get(slot - 1) else {
vortex_bail!("Invalid child index: {}", slot);
};
Ok(spec.index_dtype.clone())
}
}
}
fn child_type(layout: &Layout<Self>, slot: usize) -> LayoutChildType {
match slot {
0 => LayoutChildType::Transparent("data".into()),
_ => match layout.indexes.get(slot - 1) {
Some(spec) => LayoutChildType::Auxiliary(format!("index:{}", spec.id).into()),
None => vortex_panic!("Invalid child index: {}", slot),
},
}
}
fn new_reader(
layout: &Layout<Self>,
name: Arc<str>,
segment_source: Arc<dyn SegmentSource>,
session: &VortexSession,
ctx: &LayoutReaderContext,
) -> VortexResult<LayoutReaderRef> {
Ok(Arc::new(IndexedReader::try_new(
layout.clone(),
name,
segment_source,
session.clone(),
ctx.clone(),
)?))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IndexedMetadata {
indexes: Arc<[IndexSpecProto]>,
}
#[derive(Clone, PartialEq, Message)]
struct IndexedMetadataProto {
#[prost(message, repeated, tag = "1")]
indexes: Vec<IndexSpecProto>,
}
#[derive(Clone, PartialEq, Message)]
struct IndexSpecProto {
#[prost(string, tag = "1")]
id: String,
#[prost(bytes = "vec", tag = "2")]
options: Vec<u8>,
#[prost(message, optional, tag = "3")]
index_dtype: Option<pb::DType>,
#[prost(uint64, optional, tag = "4")]
partition_len: Option<u64>,
#[prost(uint64, repeated, tag = "5")]
partition_index_ends: Vec<u64>,
#[prost(uint32, repeated, tag = "6")]
declined_partitions: Vec<u32>,
}
impl SerializeMetadata for IndexedMetadata {
fn serialize(self) -> Vec<u8> {
let proto = IndexedMetadataProto {
indexes: self.indexes.to_vec(),
};
let mut metadata = vec![INDEXED_METADATA_VERSION];
metadata.extend(proto.encode_to_vec());
metadata
}
}
impl DeserializeMetadata for IndexedMetadata {
type Output = Self;
fn deserialize(metadata: &[u8]) -> VortexResult<Self::Output> {
let Some((&version, proto_bytes)) = metadata.split_first() else {
vortex_bail!("Indexed metadata missing protobuf version");
};
vortex_ensure!(
version == INDEXED_METADATA_VERSION,
"Unsupported indexed metadata version: {}",
version
);
let proto = IndexedMetadataProto::decode(proto_bytes)
.map_err(|err| vortex_err!("Failed to decode indexed metadata: {err}"))?;
Ok(Self {
indexes: proto.indexes.into(),
})
}
}
#[cfg(test)]
mod tests;