Skip to main content

apache_datasketches/tuple/generic/
sketch.rs

1use super::builder::resize_factor_multiplier;
2use super::summary::{erase, refill, TupleSummary};
3use crate::error::SketchError;
4use crate::tuple::ResizeFactor;
5use apache_datasketches_sys::tuple_generic::ffi as sys;
6use apache_datasketches_sys::tuple_generic::RustSummary;
7use cxx::UniquePtr;
8use std::marker::PhantomData;
9
10/// A mutable, update-only Tuple sketch carrying a user-defined summary `S`
11/// per distinct key. Build one with
12/// [`TupleSketchBuilder`](super::TupleSketchBuilder).
13pub struct TupleSketch<S: TupleSummary> {
14    pub(crate) inner: UniquePtr<sys::TupleGenericSketchShim>,
15    /// A single erased summary, reused as the value argument of every update.
16    ///
17    /// Every `update_*` has to hand C++ a `RustSummary`, and building a fresh
18    /// one per call heap-allocated a `Box<dyn RawSummaryOps>` every time —
19    /// before the FFI crossing, so before upstream's theta screen, meaning even
20    /// a key C++ immediately discarded paid for it. Holding one box and
21    /// overwriting its contents (see `summary::refill`) makes the update path
22    /// allocation-free.
23    ///
24    /// `None` until the first update, because constructing one needs an `S` and
25    /// there is no `S` to be had before a caller supplies an update value.
26    ///
27    /// Not part of the sketch's logical state: it is scratch space, holds
28    /// whatever the last update left behind, and is never read except by the
29    /// update that just wrote it.
30    ///
31    /// One visible consequence: this keeps one `S` alive for the sketch's
32    /// lifetime, and [`Self::reset`] does not release it — reset clears the C++
33    /// table but leaves the scratch box allocated, deliberately, since the
34    /// sketch is likely to be updated again. It is freed when the sketch drops.
35    scratch: Option<RustSummary>,
36    pub(crate) _marker: PhantomData<fn() -> S>,
37}
38
39// Sound because `S: Send` is a supertrait of `TupleSummary` and the sys-crate
40// box is `Box<dyn RawSummaryOps + Send>`. Deliberately not `Sync`.
41unsafe impl<S: TupleSummary> Send for TupleSketch<S> {}
42
43impl<S: TupleSummary> TupleSketch<S> {
44    pub(crate) fn from_parts(lg_k: u8, rf: ResizeFactor, p: f32) -> Result<Self, SketchError> {
45        let inner = sys::new_tuple_generic_sketch(lg_k, resize_factor_multiplier(rf), p)
46            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))?;
47        Ok(Self {
48            inner,
49            scratch: None,
50            _marker: PhantomData,
51        })
52    }
53
54    /// Loads `value` into the reused scratch summary, allocating only on the
55    /// first call for this sketch.
56    ///
57    /// Returns nothing, and callers read `self.scratch` themselves, so that the
58    /// immutable borrow of `scratch` and the mutable borrow of `inner` are two
59    /// disjoint field borrows. A helper returning `&RustSummary` from
60    /// `&mut self` would borrow the whole sketch and conflict with the
61    /// `inner.pin_mut()` that has to follow.
62    fn load_scratch(&mut self, value: S) {
63        if let Some(existing) = self.scratch.as_mut() {
64            refill(existing, value);
65        } else {
66            self.scratch = Some(erase(value));
67        }
68    }
69
70    /// The scratch summary `load_scratch` just filled.
71    ///
72    /// Takes the field, not `&self`: a `&self` method borrows the whole sketch
73    /// and so cannot coexist with the `self.inner.pin_mut()` that follows.
74    /// Passing `&self.scratch` keeps it to one field, which leaves `inner` free
75    /// to be borrowed mutably.
76    ///
77    /// The `expect` is unreachable — every caller runs `load_scratch` first, and
78    /// that leaves `scratch` engaged on both paths.
79    fn filled(scratch: &Option<RustSummary>) -> &RustSummary {
80        scratch
81            .as_ref()
82            .expect("load_scratch always leaves the scratch summary engaged")
83    }
84
85    /// Adds a `u64` key. `S::create` runs first, entirely in Rust; the
86    /// resulting summary is combined into an existing entry or cloned into a
87    /// new one.
88    pub fn update_u64(&mut self, key: u64, value: &S::Update) {
89        self.load_scratch(S::create(value));
90        let summary = Self::filled(&self.scratch);
91        self.inner.pin_mut().update_u64(key, summary);
92    }
93
94    /// Adds an `i64` key. See [`Self::update_u64`].
95    pub fn update_i64(&mut self, key: i64, value: &S::Update) {
96        self.load_scratch(S::create(value));
97        let summary = Self::filled(&self.scratch);
98        self.inner.pin_mut().update_i64(key, summary);
99    }
100
101    /// Adds a `u32` key. See [`Self::update_u64`].
102    pub fn update_u32(&mut self, key: u32, value: &S::Update) {
103        self.load_scratch(S::create(value));
104        let summary = Self::filled(&self.scratch);
105        self.inner.pin_mut().update_u32(key, summary);
106    }
107
108    /// Adds an `i32` key. See [`Self::update_u64`].
109    pub fn update_i32(&mut self, key: i32, value: &S::Update) {
110        self.load_scratch(S::create(value));
111        let summary = Self::filled(&self.scratch);
112        self.inner.pin_mut().update_i32(key, summary);
113    }
114
115    /// Adds a `u16` key. See [`Self::update_u64`].
116    pub fn update_u16(&mut self, key: u16, value: &S::Update) {
117        self.load_scratch(S::create(value));
118        let summary = Self::filled(&self.scratch);
119        self.inner.pin_mut().update_u16(key, summary);
120    }
121
122    /// Adds an `i16` key. See [`Self::update_u64`].
123    pub fn update_i16(&mut self, key: i16, value: &S::Update) {
124        self.load_scratch(S::create(value));
125        let summary = Self::filled(&self.scratch);
126        self.inner.pin_mut().update_i16(key, summary);
127    }
128
129    /// Adds a `u8` key. See [`Self::update_u64`].
130    pub fn update_u8(&mut self, key: u8, value: &S::Update) {
131        self.load_scratch(S::create(value));
132        let summary = Self::filled(&self.scratch);
133        self.inner.pin_mut().update_u8(key, summary);
134    }
135
136    /// Adds an `i8` key. See [`Self::update_u64`].
137    pub fn update_i8(&mut self, key: i8, value: &S::Update) {
138        self.load_scratch(S::create(value));
139        let summary = Self::filled(&self.scratch);
140        self.inner.pin_mut().update_i8(key, summary);
141    }
142
143    /// Adds an `f64` key. See [`Self::update_u64`].
144    pub fn update_f64(&mut self, key: f64, value: &S::Update) {
145        self.load_scratch(S::create(value));
146        let summary = Self::filled(&self.scratch);
147        self.inner.pin_mut().update_f64(key, summary);
148    }
149
150    /// Adds a string key. See [`Self::update_u64`].
151    pub fn update_str(&mut self, key: &str, value: &S::Update) {
152        self.load_scratch(S::create(value));
153        let summary = Self::filled(&self.scratch);
154        self.inner.pin_mut().update_str(key, summary);
155    }
156
157    /// Adds an arbitrary byte-slice key. See [`Self::update_u64`].
158    pub fn update_bytes(&mut self, key: &[u8], value: &S::Update) {
159        self.load_scratch(S::create(value));
160        let summary = Self::filled(&self.scratch);
161        self.inner.pin_mut().update_bytes(key, summary);
162    }
163
164    /// Removes retained entries in excess of the nominal size `k`, lowering
165    /// theta to do so. Note this shifts [`Self::get_estimate`].
166    pub fn trim(&mut self) {
167        self.inner.pin_mut().trim();
168    }
169
170    /// Resets this sketch to its initial, empty state.
171    pub fn reset(&mut self) {
172        self.inner.pin_mut().reset();
173    }
174
175    /// Returns the current estimate of the number of distinct keys added.
176    pub fn get_estimate(&self) -> f64 {
177        self.inner.get_estimate()
178    }
179
180    /// Returns the lower bound of the confidence interval around
181    /// [`Self::get_estimate`], for `num_std_dev` of `1`, `2`, or `3`.
182    pub fn get_lower_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
183        self.inner
184            .get_lower_bound(num_std_dev)
185            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
186    }
187
188    /// Returns the upper bound of the confidence interval around
189    /// [`Self::get_estimate`]. See [`Self::get_lower_bound`].
190    pub fn get_upper_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
191        self.inner
192            .get_upper_bound(num_std_dev)
193            .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
194    }
195
196    /// Returns `true` if no keys have been added.
197    pub fn is_empty(&self) -> bool {
198        self.inner.is_empty()
199    }
200
201    /// Returns `true` if the sketch has begun sampling.
202    pub fn is_estimation_mode(&self) -> bool {
203        self.inner.is_estimation_mode()
204    }
205
206    /// Returns `true` if retained entries are sorted by hash value.
207    pub fn is_ordered(&self) -> bool {
208        self.inner.is_ordered()
209    }
210
211    /// Returns the current theta threshold.
212    pub fn get_theta(&self) -> f64 {
213        self.inner.get_theta()
214    }
215
216    /// Returns the number of retained entries.
217    pub fn get_num_retained(&self) -> u32 {
218        self.inner.get_num_retained()
219    }
220
221    /// Produces an immutable
222    /// [`CompactTupleSketch`](super::CompactTupleSketch) snapshot. If
223    /// `ordered` is `true`, its entries are sorted by hash value.
224    pub fn compact(&self, ordered: bool) -> super::CompactTupleSketch<S> {
225        super::CompactTupleSketch::from_shim(self.inner.compact(ordered))
226    }
227}