Skip to main content

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}