use std::sync::Arc;
use durability::{Directory, PersistenceResult};
use segstore::{SegmentedStore, Store};
use crate::{BlockingConfig, MinHashTextLSH};
struct TextBacking;
impl Store for TextBacking {
type Id = u32;
type Item = String;
type Segment = Vec<(u32, String)>;
fn build_segment(&self, batch: &[(u32, String)]) -> Vec<(u32, String)> {
batch.to_vec()
}
fn merge_segments(
&self,
segs: &[Vec<(u32, String)>],
live: &dyn Fn(&u32) -> bool,
) -> Vec<(u32, String)> {
segs.iter()
.flatten()
.filter(|(id, _)| live(id))
.cloned()
.collect()
}
}
pub struct UpdatableIndex {
inner: SegmentedStore<TextBacking>,
config: BlockingConfig,
}
impl UpdatableIndex {
pub fn open(
dir: Arc<dyn Directory>,
flush_threshold: usize,
config: BlockingConfig,
) -> PersistenceResult<Self> {
Ok(Self {
inner: SegmentedStore::open(dir, TextBacking, flush_threshold)?,
config,
})
}
pub fn add(&mut self, id: u32, text: impl Into<String>) -> PersistenceResult<()> {
self.inner.add(id, text.into())
}
pub fn delete(&mut self, id: u32) -> PersistenceResult<()> {
self.inner.delete(id)
}
pub fn compact(&mut self) -> PersistenceResult<()> {
self.inner.compact()
}
pub fn checkpoint(&mut self) -> PersistenceResult<()> {
self.inner.checkpoint()
}
pub fn near_duplicates(&self, text: &str) -> Vec<u32> {
let mut out: Vec<u32> = Vec::new();
for seg in self.inner.segments() {
out.extend(self.candidates_in(seg, text));
}
let buffered = self.inner.buffer().to_vec();
out.extend(self.candidates_in(&buffered, text));
out.sort_unstable();
out.dedup();
out
}
fn candidates_in(&self, batch: &[(u32, String)], text: &str) -> Vec<u32> {
let mut lsh = match MinHashTextLSH::new(self.config.clone()) {
Ok(l) => l,
Err(_) => return Vec::new(),
};
let mut ids: Vec<u32> = Vec::new();
for (id, doc) in batch {
if self.inner.is_live(id) {
lsh.insert_text(id.to_string(), doc);
ids.push(*id);
}
}
if ids.is_empty() {
return Vec::new();
}
lsh.query(text)
.into_iter()
.filter_map(|i| ids.get(i).copied())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use durability::MemoryDirectory;
const A: &str = "the quick brown fox jumps over the lazy dog";
const B: &str = "lorem ipsum dolor sit amet consectetur adipiscing elit";
#[test]
fn add_delete_compact_recover_through_real_lsh() {
let dir = MemoryDirectory::arc();
{
let mut store =
UpdatableIndex::open(dir.clone(), 2, BlockingConfig::default()).unwrap();
store.add(1, A).unwrap();
store.add(2, A).unwrap(); store.add(3, B).unwrap();
let dups = store.near_duplicates(A);
assert!(
dups.contains(&1) && dups.contains(&2),
"identical docs are near-duplicates"
);
assert!(!dups.contains(&3), "unrelated doc is not");
store.delete(2).unwrap();
assert!(
!store.near_duplicates(A).contains(&2),
"deleted doc drops out"
);
store.compact().unwrap();
let dups = store.near_duplicates(A);
assert!(
dups.contains(&1) && !dups.contains(&2),
"compaction preserves the result"
);
}
let store = UpdatableIndex::open(dir, 2, BlockingConfig::default()).unwrap();
let dups = store.near_duplicates(A);
assert!(
dups.contains(&1) && !dups.contains(&2),
"recovery preserves the result"
);
}
}