use apache_datasketches::tuple::generic::{
CompactTupleSketch, TupleAnotB, TupleIntersection, TupleSketch, TupleSketchBuilder,
TupleSummary, TupleUnion, TupleUnionBuilder,
};
#[derive(Clone, Debug, PartialEq)]
struct Tally {
hits: u64,
label: String,
}
impl TupleSummary for Tally {
type Update = str;
fn create(update: &str) -> Self {
Tally {
hits: 1,
label: update.to_string(),
}
}
fn union_combine(&mut self, other: &Self) {
self.hits += other.hits;
}
fn intersection_combine(&mut self, other: &Self) {
self.hits = self.hits.min(other.hits);
}
}
struct SyncProbe<T>(std::marker::PhantomData<T>);
trait ProbeViaSync {
fn is_sync(&self) -> bool;
}
impl<T: Sync> ProbeViaSync for &SyncProbe<T> {
fn is_sync(&self) -> bool {
true
}
}
trait ProbeViaFallback {
fn is_sync(&self) -> bool;
}
impl<T> ProbeViaFallback for SyncProbe<T> {
fn is_sync(&self) -> bool {
false
}
}
macro_rules! probe_is_sync {
($ty:ty) => {{
let probe = SyncProbe::<$ty>(std::marker::PhantomData);
#[allow(clippy::needless_borrow)]
(&&probe).is_sync()
}};
}
#[test]
fn all_types_are_send_and_not_sync() {
fn assert_send<T: Send>() {}
assert_send::<TupleSketch<Tally>>();
assert_send::<CompactTupleSketch<Tally>>();
assert_send::<TupleUnion<Tally>>();
assert_send::<TupleIntersection<Tally>>();
assert_send::<TupleAnotB<Tally>>();
assert!(
!probe_is_sync!(TupleSketch<Tally>),
"TupleSketch<S> must not be Sync"
);
assert!(
!probe_is_sync!(CompactTupleSketch<Tally>),
"CompactTupleSketch<S> must not be Sync"
);
assert!(
!probe_is_sync!(TupleUnion<Tally>),
"TupleUnion<S> must not be Sync"
);
assert!(
!probe_is_sync!(TupleIntersection<Tally>),
"TupleIntersection<S> must not be Sync"
);
assert!(
!probe_is_sync!(TupleAnotB<Tally>),
"TupleAnotB<S> must not be Sync"
);
assert!(
probe_is_sync!(u64),
"probe is broken: it does not detect Sync at all"
);
}
#[test]
fn a_sketch_built_on_one_thread_is_usable_on_another() {
let handle = std::thread::spawn(|| {
let mut sketch: TupleSketch<Tally> = TupleSketchBuilder::new().build().unwrap();
for i in 0..1_000u64 {
sketch.update_u64(i, "worker");
}
sketch.compact(true)
});
let compact = handle.join().unwrap();
assert_eq!(compact.entries().count(), 1_000);
let bad: Vec<(u64, Tally)> = compact
.entries()
.filter(|(_, t)| {
*t != Tally {
hits: 1,
label: "worker".to_string(),
}
})
.collect();
assert!(
bad.is_empty(),
"found entries that did not survive the cross-thread move intact: {bad:?}"
);
}
#[test]
fn per_thread_sketches_merge_correctly() {
let handles: Vec<_> = (0..4u64)
.map(|t| {
std::thread::spawn(move || {
let mut sketch: TupleSketch<Tally> = TupleSketchBuilder::new().build().unwrap();
for i in (t * 2_500)..((t + 1) * 2_500) {
sketch.update_u64(i, "shard");
}
sketch.compact(true)
})
})
.collect();
let mut union = TupleUnionBuilder::<Tally>::new().build().unwrap();
for handle in handles {
union.update(&handle.join().unwrap());
}
let result = union.get_result(true);
let estimate = result.get_estimate();
assert!(
(estimate - 10_000.0).abs() < 10_000.0 * 0.03,
"union estimate out of tolerance: {estimate}"
);
let entries: Vec<(u64, Tally)> = result.entries().collect();
let bad: Vec<(u64, Tally)> = entries
.into_iter()
.filter(|(_, t)| {
*t != Tally {
hits: 1,
label: "shard".to_string(),
}
})
.collect();
assert!(
bad.is_empty(),
"found union result entries with the wrong hits/label: {bad:?}"
);
}