Skip to main content

apache_datasketches/tuple/generic/
compact.rs

1use super::summary::{unerase, TupleSummary};
2use crate::error::SketchError;
3use apache_datasketches_sys::tuple_generic::ffi as sys;
4use cxx::UniquePtr;
5use std::marker::PhantomData;
6
7/// An immutable snapshot of a generic Tuple sketch, produced by
8/// [`TupleSketch::compact`](super::TupleSketch::compact) or by any set
9/// operation's result.
10///
11/// Serialization is not part of this version; it is the subject of a
12/// follow-up design.
13pub struct CompactTupleSketch<S: TupleSummary> {
14    pub(crate) inner: UniquePtr<sys::CompactTupleGenericSketchShim>,
15    pub(crate) _marker: PhantomData<fn() -> S>,
16}
17
18// Sound because `S: Send` is a supertrait of `TupleSummary` and the erased
19// box is `Box<dyn RawSummaryOps + Send>`. Deliberately NOT `Sync`: the C++
20// shim lazily populates a `mutable` entry cache (`entries_`/`entries_built_`
21// in `tuple_generic_compact_shim.h`) from otherwise-`const` methods, which is
22// only safe without concurrent `&`-access to the same instance. Do not add a
23// `Sync` impl, and do not wrap the shim in a `Sync` newtype.
24unsafe impl<S: TupleSummary> Send for CompactTupleSketch<S> {}
25
26impl<S: TupleSummary> CompactTupleSketch<S> {
27    /// Wraps a shim produced by [`TupleSketch::compact`](super::TupleSketch::compact)
28    /// or a set operation.
29    ///
30    /// `inner` should have every summary reachable from it be an `Adapter<S>`
31    /// for this same `S` — that is, it should have originated from a sketch
32    /// or operation typed over `S`.
33    ///
34    /// # Panics
35    ///
36    /// [`Self::entries`] calls `unerase` on every summary it reads back.
37    /// `unerase`'s invariant panic is otherwise unreachable, but passing a
38    /// shim whose summaries were erased for a different `S` makes it
39    /// reachable, and the panic fires there instead of here.
40    pub(crate) fn from_shim(inner: UniquePtr<sys::CompactTupleGenericSketchShim>) -> Self {
41        Self {
42            inner,
43            _marker: PhantomData,
44        }
45    }
46
47    /// Returns the current estimate of the number of distinct keys.
48    pub fn get_estimate(&self) -> f64 {
49        self.inner.get_estimate()
50    }
51
52    /// Returns the lower bound of the confidence interval around
53    /// [`Self::get_estimate`], for `num_std_dev` of `1`, `2`, or `3`.
54    pub fn get_lower_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
55        self.inner
56            .get_lower_bound(num_std_dev)
57            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
58    }
59
60    /// Returns the upper bound of the confidence interval around
61    /// [`Self::get_estimate`]. See [`Self::get_lower_bound`].
62    pub fn get_upper_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
63        self.inner
64            .get_upper_bound(num_std_dev)
65            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
66    }
67
68    /// Returns `true` if this sketch represents an empty set.
69    pub fn is_empty(&self) -> bool {
70        self.inner.is_empty()
71    }
72
73    /// Returns `true` if the estimate is a statistical estimate rather than
74    /// an exact count.
75    pub fn is_estimation_mode(&self) -> bool {
76        self.inner.is_estimation_mode()
77    }
78
79    /// Returns `true` if retained entries are sorted by hash value.
80    pub fn is_ordered(&self) -> bool {
81        self.inner.is_ordered()
82    }
83
84    /// Returns the current theta threshold.
85    pub fn get_theta(&self) -> f64 {
86        self.inner.get_theta()
87    }
88
89    /// Returns the number of retained entries.
90    pub fn get_num_retained(&self) -> u32 {
91        self.inner.get_num_retained()
92    }
93
94    /// Iterates the retained entries as `(hash, summary)` pairs.
95    ///
96    /// Each summary is cloned out of C++, so the items are owned. Ordered by
97    /// hash if [`Self::is_ordered`] is `true`.
98    pub fn entries(&self) -> impl Iterator<Item = (u64, S)> + '_ {
99        (0..self.inner.entry_count()).map(move |i| {
100            let hash = self
101                .inner
102                .entry_hash(i)
103                .expect("index derived from entry_count is always in range");
104            let summary = self
105                .inner
106                .entry_summary(i)
107                .expect("index derived from entry_count is always in range");
108            (hash, unerase::<S>(&summary))
109        })
110    }
111}