pub mod compressed;
pub mod id_tracker_base;
pub mod immutable_id_tracker;
pub mod in_memory_id_tracker;
mod memory_reporter;
pub mod mutable_id_tracker;
pub mod point_mappings;
use crate::common::types::PointOffsetType;
pub use id_tracker_base::*;
use itertools::Itertools as _;
use crate::segment::types::{ExtendedPointId, PointIdType};
#[derive(Debug, Clone, Copy)]
pub struct MergedPointId {
pub external_id: ExtendedPointId,
pub tracker_index: usize,
pub internal_id: PointOffsetType,
pub version: u64,
pub is_deleted: bool,
}
pub fn for_each_unique_point<'a>(
id_trackers: impl Iterator<Item = &'a (impl IdTracker + ?Sized + 'a)>,
mut f: impl FnMut(MergedPointId),
) {
let mut iter = id_trackers
.enumerate()
.map(|(segment_index, id_tracker)| {
id_tracker.point_mappings().iter_from(None).filter_map(
move |(external_id, internal_id)| {
let version = id_tracker.internal_version(internal_id);
let is_deleted = id_tracker.is_deleted_point(internal_id);
version.map(|version| MergedPointId {
external_id,
tracker_index: segment_index,
internal_id,
version,
is_deleted,
})
},
)
})
.kmerge_by(|a, b| a.external_id < b.external_id);
let Some(mut best_item) = iter.next() else {
return;
};
for item in iter {
if best_item.external_id == item.external_id {
if best_item.version < item.version {
best_item = item;
}
} else {
if !best_item.is_deleted {
f(best_item);
}
best_item = item;
}
}
if !best_item.is_deleted {
f(best_item);
}
}
impl From<&ExtendedPointId> for PointIdType {
fn from(point_id: &ExtendedPointId) -> Self {
match point_id {
ExtendedPointId::NumId(idx) => PointIdType::NumId(*idx),
ExtendedPointId::Uuid(uuid) => PointIdType::Uuid(*uuid),
}
}
}
#[cfg(test)]
mod tests {
use std::collections::{HashMap, hash_map};
use in_memory_id_tracker::InMemoryIdTracker;
use rand::SeedableRng as _;
use rand::rngs::StdRng;
use rstest::rstest;
use super::*;
#[rstest]
fn test_for_each_unique_point(#[values(0, 1, 5)] tracker_count: usize) {
let mut rand = StdRng::seed_from_u64(42);
let id_trackers = (0..tracker_count)
.map(|_| InMemoryIdTracker::random(&mut rand, 1000, 500, 10))
.collect_vec();
let mut collisions = 0;
let mut expected = HashMap::<ExtendedPointId, MergedPointId>::new();
for (tracker_index, id_tracker) in id_trackers.iter().enumerate() {
for (external_id, internal_id) in id_tracker.point_mappings().iter_from(None) {
let version = id_tracker.internal_version(internal_id).unwrap();
let is_deleted = id_tracker.is_deleted_point(internal_id);
let merged_point_id = MergedPointId {
external_id,
tracker_index,
internal_id,
version,
is_deleted,
};
match expected.entry(external_id) {
hash_map::Entry::Occupied(mut entry) => {
collisions += 1;
if entry.get().version < version {
entry.insert(merged_point_id);
}
}
hash_map::Entry::Vacant(entry) => {
entry.insert(merged_point_id);
}
}
}
}
if tracker_count > 1 {
assert!(collisions > 500);
} else {
assert_eq!(collisions, 0);
}
if tracker_count == 0 {
assert!(expected.is_empty());
}
expected.retain(|_, v| !v.is_deleted);
for_each_unique_point(id_trackers.iter(), |merged_point_id| {
let v = expected.remove(&merged_point_id.external_id).unwrap();
assert_eq!(merged_point_id.tracker_index, v.tracker_index);
assert_eq!(merged_point_id.internal_id, v.internal_id);
assert_eq!(merged_point_id.version, v.version);
assert!(!merged_point_id.is_deleted);
});
assert!(expected.is_empty());
}
}