use crate::cursor::IndexScanPlan;
use crate::index::edge_index_manager::{compute_edge_index_scan_range, EdgeIndexCursor};
use crate::index::key_codec::key_types::SecondaryIndexKey;
use crate::index::manifest::{ManifestCatalog, ManifestHandle};
use crate::index::shard_runtime::GenerationRuntime;
use crate::index::types::IndexRecord;
use crate::index::types::StaleChecker;
use crate::index::vertex_index_manager::{compute_vertex_index_scan_range, VertexIndexCursor};
use crate::index::IndexDataManagerImpl;
use linkrs_core::types::Index;
use linkrs_core::{StorageError, StorageResult};
use std::collections::HashSet;
use std::sync::Arc;
pub(crate) struct ChainForwardIterator {
chain: Vec<Arc<GenerationRuntime>>,
shard_id: u32,
read_ts: u64,
range_start: Vec<u8>,
range_end: Vec<u8>,
gen_idx: usize,
pos: usize,
current_entries: Vec<(SecondaryIndexKey, IndexRecord)>,
seen: HashSet<SecondaryIndexKey>,
tombstoned: HashSet<SecondaryIndexKey>,
}
impl ChainForwardIterator {
pub(crate) fn new(
chain: Vec<Arc<GenerationRuntime>>,
shard_id: u32,
read_ts: u64,
range_start: Vec<u8>,
range_end: Vec<u8>,
) -> Self {
Self {
chain,
shard_id,
read_ts,
range_start,
range_end,
gen_idx: 0,
pos: 0,
current_entries: Vec::new(),
seen: HashSet::new(),
tombstoned: HashSet::new(),
}
}
fn load_next_gen(&mut self) {
while self.gen_idx < self.chain.len() {
if let Some(shard) = self.chain[self.gen_idx].shard(self.shard_id) {
if shard.forward_may_have_range(&self.range_start, &self.range_end) {
self.current_entries = shard
.forward_range(&self.range_start, &self.range_end)
.collect();
}
self.gen_idx += 1;
if !self.current_entries.is_empty() {
self.pos = 0;
return;
}
} else {
self.gen_idx += 1;
}
}
self.current_entries = Vec::new();
}
}
impl Iterator for ChainForwardIterator {
type Item = (SecondaryIndexKey, IndexRecord);
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.pos >= self.current_entries.len() {
self.load_next_gen();
if self.current_entries.is_empty() {
return None;
}
}
let (key, entry) = &self.current_entries[self.pos];
self.pos += 1;
let key = key.clone();
let entry = entry.clone();
if entry.created_ts > self.read_ts {
continue;
}
if entry.deleted_ts.is_some_and(|d| d <= self.read_ts) {
self.tombstoned.insert(key);
continue;
}
if self.tombstoned.contains(&key) {
continue;
}
if !self.seen.insert(key.clone()) {
continue;
}
return Some((key, entry));
}
}
}
impl IndexDataManagerImpl {
#[cfg(test)]
pub fn open_edge_index_cursor(
&self,
space_id: u64,
index: &Index,
plan: &IndexScanPlan,
) -> StorageResult<EdgeIndexCursor> {
self.open_edge_index_cursor_full(space_id, index, plan, None, None)
}
pub fn open_tag_index_cursor_full(
&self,
space_id: u64,
index: &Index,
plan: &IndexScanPlan,
stale_checker: Option<StaleChecker>,
catalog: Option<&ManifestCatalog>,
) -> StorageResult<VertexIndexCursor> {
let identity = crate::index::types::IndexIdentity {
space_id,
index_id: plan.index_id,
};
self.publish_pending_delta(identity)?;
let owned_catalog = if catalog.is_none() {
self.register_native_index(space_id, index)?;
self.manifest_catalog(space_id, plan.index_id)
} else {
None
};
let catalog = catalog
.or(owned_catalog.as_deref())
.ok_or_else(|| StorageError::not_found("Index manifest is unavailable"))?;
let runtime = self.runtime(space_id, plan.index_id)?;
let handle = catalog.acquire();
let manifest = handle.manifest();
let chain = runtime.generation_chain_until(manifest.generation)?;
let chain_handles = pin_chain_manifests(catalog, &chain);
let (start, end) = compute_vertex_index_scan_range(space_id, index, plan)?;
let shard_iterators: Vec<ChainForwardIterator> = manifest
.scan_ranges_with_shard(&plan.partition, &start, &end)
.into_iter()
.filter_map(|(shard_id, lower, upper)| {
let has_data = chain.iter().any(|gen| gen.shard(shard_id).is_some());
has_data.then(|| {
ChainForwardIterator::new(
chain.clone(),
shard_id,
plan.read_timestamp,
lower,
upper,
)
})
})
.collect();
Ok(VertexIndexCursor::new(
shard_iterators,
plan,
stale_checker,
chain_handles,
))
}
pub fn open_edge_index_cursor_full(
&self,
space_id: u64,
index: &Index,
plan: &IndexScanPlan,
stale_checker: Option<StaleChecker>,
catalog: Option<&ManifestCatalog>,
) -> StorageResult<EdgeIndexCursor> {
let identity = crate::index::types::IndexIdentity {
space_id,
index_id: plan.index_id,
};
self.publish_pending_delta(identity)?;
let owned_catalog = if catalog.is_none() {
self.register_native_index(space_id, index)?;
self.manifest_catalog(space_id, plan.index_id)
} else {
None
};
let catalog = catalog
.or(owned_catalog.as_deref())
.ok_or_else(|| StorageError::not_found("Index manifest is unavailable"))?;
let runtime = self.runtime(space_id, plan.index_id)?;
let handle = catalog.acquire();
let manifest = handle.manifest();
let chain = runtime.generation_chain_until(manifest.generation)?;
let chain_handles = pin_chain_manifests(catalog, &chain);
let (start, end) = compute_edge_index_scan_range(space_id, index, plan)?;
let shard_iterators: Vec<ChainForwardIterator> = manifest
.scan_ranges_with_shard(&plan.partition, &start, &end)
.into_iter()
.filter_map(|(shard_id, lower, upper)| {
let has_data = chain.iter().any(|gen| gen.shard(shard_id).is_some());
has_data.then(|| {
ChainForwardIterator::new(
chain.clone(),
shard_id,
plan.read_timestamp,
lower,
upper,
)
})
})
.collect();
Ok(EdgeIndexCursor::new(
shard_iterators,
plan,
stale_checker,
chain_handles,
))
}
}
fn pin_chain_manifests(
catalog: &ManifestCatalog,
chain: &[Arc<GenerationRuntime>],
) -> Vec<ManifestHandle> {
chain
.iter()
.filter_map(|gen| catalog.acquire_generation(gen.generation))
.collect()
}