apache_datasketches/tuple/generic/intersection.rs
1use super::{CompactTupleSketch, TupleInput, TupleSummary};
2use crate::error::SketchError;
3use apache_datasketches_sys::tuple_generic_input::TupleGenericInputRef;
4use apache_datasketches_sys::tuple_generic_intersection::ffi as sys;
5use cxx::UniquePtr;
6use std::marker::PhantomData;
7
8/// Computes the intersection of generic Tuple sketches fed via
9/// [`Self::update`]. Summaries of keys present in every input are merged with
10/// [`TupleSummary::intersection_combine`](super::TupleSummary::intersection_combine);
11/// a key present in only some of the inputs is dropped and its summary never
12/// reaches the callback.
13///
14/// No builder: upstream's type has a plain constructor, matching
15/// [`ArrayOfDoublesIntersection`](crate::tuple::ArrayOfDoublesIntersection).
16///
17/// A fresh intersection is the infinite "universe", not the empty set — see
18/// [`Self::get_result`].
19pub struct TupleIntersection<S: TupleSummary> {
20 inner: UniquePtr<sys::TupleGenericIntersectionShim>,
21 _marker: PhantomData<fn() -> S>,
22}
23
24// Sound because `S: Send` is a supertrait of `TupleSummary` and every summary
25// the intersection owns is a `Box<dyn RawSummaryOps + Send>`. Deliberately not
26// `Sync`, matching `TupleSketch<S>`, `CompactTupleSketch<S>` and
27// `TupleUnion<S>`.
28unsafe impl<S: TupleSummary> Send for TupleIntersection<S> {}
29
30impl<S: TupleSummary> Default for TupleIntersection<S> {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl<S: TupleSummary> TupleIntersection<S> {
37 /// Creates an intersection with no result yet — call [`Self::update`] at
38 /// least once before [`Self::get_result`].
39 pub fn new() -> Self {
40 Self {
41 inner: sys::new_tuple_generic_intersection(),
42 _marker: PhantomData,
43 }
44 }
45
46 /// Narrows the running result to also require membership in `input`.
47 ///
48 /// Infallible: unlike ArrayOfDoubles there is no `num_values` to agree
49 /// on — the type system already guarantees both operands carry `S`.
50 pub fn update(&mut self, input: &impl TupleInput<S>) {
51 match input.as_input() {
52 TupleGenericInputRef::Sketch(s) => self.inner.pin_mut().update_with_sketch(s),
53 TupleGenericInputRef::Compact(c) => self.inner.pin_mut().update_with_compact(c),
54 }
55 }
56
57 /// Returns the current result, or [`SketchError::EmptyIntersection`] if
58 /// [`Self::update`] has never been called. If `ordered` is `true`,
59 /// entries are sorted by hash value.
60 ///
61 /// The no-operand case is an error rather than an empty sketch because
62 /// upstream defines it as the infinite "universe" and throws
63 /// (`theta_intersection_base::get_result`). That is a genuinely different
64 /// state from an intersection of disjoint operands, which succeeds and
65 /// returns an empty sketch.
66 pub fn get_result(&self, ordered: bool) -> Result<CompactTupleSketch<S>, SketchError> {
67 if !self.inner.has_result() {
68 return Err(SketchError::EmptyIntersection);
69 }
70 let inner = self
71 .inner
72 .get_result(ordered)
73 .map_err(|e| SketchError::Cpp(e.what().to_string()))?;
74 Ok(CompactTupleSketch::from_shim(inner))
75 }
76
77 /// Returns `true` if [`Self::update`] has been called at least once.
78 pub fn has_result(&self) -> bool {
79 self.inner.has_result()
80 }
81}