apache_datasketches/tuple/sketch.rs
1use crate::error::SketchError;
2use crate::tuple::builder::ResizeFactor;
3use apache_datasketches_sys::array_of_doubles_sketch::ffi as sys;
4use cxx::UniquePtr;
5
6/// A mutable, update-only ArrayOfDoubles Tuple sketch: estimates the number
7/// of distinct keys added via `update_*`, and carries a fixed-width array of
8/// `f64` values per retained key, summed on collision. Build one with
9/// [`ArrayOfDoublesSketchBuilder`](super::ArrayOfDoublesSketchBuilder).
10///
11/// Call [`Self::compact`] to produce an immutable, serializable
12/// [`super::CompactArrayOfDoublesSketch`] snapshot for storage, transmission,
13/// or use as input to a set operation.
14pub struct ArrayOfDoublesSketch {
15 pub(crate) inner: UniquePtr<sys::ArrayOfDoublesSketchShim>,
16 /// Cached copy of the shim's `get_num_values()`.
17 ///
18 /// Every `update_*` has to validate the caller's slice length (see
19 /// [`Self::check_values`]), and reading `num_values` back from C++ to do
20 /// so cost a full FFI crossing per update — on the order of the update
21 /// itself.
22 ///
23 /// Caching is sound because the value is fixed for the sketch's lifetime:
24 /// it lives in the C++ update policy, is set once when the builder
25 /// constructs the sketch, has no setter, and `reset()` clears the hash
26 /// table without touching the policy.
27 ///
28 /// **This invariant is what makes the cache correct, and the compiler will
29 /// not enforce it.** `from_parts` is currently the only constructor. Any
30 /// new one — a `deserialize`, or a `from_shim` wrapping a sketch built
31 /// elsewhere — must populate this field from the shim rather than assume a
32 /// value, or the cache silently disagrees with C++ and `check_values`
33 /// starts rejecting valid slices (or admitting short ones).
34 num_values: u8,
35}
36
37unsafe impl Send for ArrayOfDoublesSketch {}
38
39impl ArrayOfDoublesSketch {
40 pub(crate) fn from_parts(
41 lg_k: u8,
42 rf: ResizeFactor,
43 p: f32,
44 num_values: u8,
45 ) -> Result<Self, SketchError> {
46 if num_values == 0 {
47 return Err(SketchError::InvalidConfig(
48 "num_values must be at least 1".to_string(),
49 ));
50 }
51 let inner = sys::new_array_of_doubles_sketch(lg_k, rf.into(), p, num_values)
52 .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))?;
53 Ok(Self { inner, num_values })
54 }
55
56 /// Validates that `values` has exactly [`Self::get_num_values`] elements.
57 ///
58 /// This check cannot be delegated to the C++ layer's exceptions the way
59 /// `lg_k`/`num_std_dev` validation is: upstream's update policy indexes
60 /// the supplied values blindly for `i in 0..num_values`, so a short slice
61 /// would be an out-of-bounds read rather than a graceful failure.
62 ///
63 /// Reads the cached [`Self::num_values`] rather than calling into C++, so
64 /// this costs no FFI crossing.
65 fn check_values(&self, values: &[f64]) -> Result<(), SketchError> {
66 let expected = self.num_values as usize;
67 if values.len() != expected {
68 return Err(SketchError::InvalidConfig(format!(
69 "expected {expected} values, got {}",
70 values.len()
71 )));
72 }
73 Ok(())
74 }
75
76 /// Adds a `u64` key with its associated values. Returns
77 /// [`SketchError::InvalidConfig`] unless
78 /// `values.len() == self.get_num_values()`.
79 pub fn update_u64(&mut self, key: u64, values: &[f64]) -> Result<(), SketchError> {
80 self.check_values(values)?;
81 self.inner.pin_mut().update_u64(key, values);
82 Ok(())
83 }
84
85 /// Adds an `i64` key with its associated values. See [`Self::update_u64`].
86 pub fn update_i64(&mut self, key: i64, values: &[f64]) -> Result<(), SketchError> {
87 self.check_values(values)?;
88 self.inner.pin_mut().update_i64(key, values);
89 Ok(())
90 }
91
92 /// Adds a `u32` key with its associated values. See [`Self::update_u64`].
93 pub fn update_u32(&mut self, key: u32, values: &[f64]) -> Result<(), SketchError> {
94 self.check_values(values)?;
95 self.inner.pin_mut().update_u32(key, values);
96 Ok(())
97 }
98
99 /// Adds an `i32` key with its associated values. See [`Self::update_u64`].
100 pub fn update_i32(&mut self, key: i32, values: &[f64]) -> Result<(), SketchError> {
101 self.check_values(values)?;
102 self.inner.pin_mut().update_i32(key, values);
103 Ok(())
104 }
105
106 /// Adds a `u16` key with its associated values. See [`Self::update_u64`].
107 pub fn update_u16(&mut self, key: u16, values: &[f64]) -> Result<(), SketchError> {
108 self.check_values(values)?;
109 self.inner.pin_mut().update_u16(key, values);
110 Ok(())
111 }
112
113 /// Adds an `i16` key with its associated values. See [`Self::update_u64`].
114 pub fn update_i16(&mut self, key: i16, values: &[f64]) -> Result<(), SketchError> {
115 self.check_values(values)?;
116 self.inner.pin_mut().update_i16(key, values);
117 Ok(())
118 }
119
120 /// Adds a `u8` key with its associated values. See [`Self::update_u64`].
121 pub fn update_u8(&mut self, key: u8, values: &[f64]) -> Result<(), SketchError> {
122 self.check_values(values)?;
123 self.inner.pin_mut().update_u8(key, values);
124 Ok(())
125 }
126
127 /// Adds an `i8` key with its associated values. See [`Self::update_u64`].
128 pub fn update_i8(&mut self, key: i8, values: &[f64]) -> Result<(), SketchError> {
129 self.check_values(values)?;
130 self.inner.pin_mut().update_i8(key, values);
131 Ok(())
132 }
133
134 /// Adds an `f64` key with its associated values. See [`Self::update_u64`].
135 pub fn update_f64(&mut self, key: f64, values: &[f64]) -> Result<(), SketchError> {
136 self.check_values(values)?;
137 self.inner.pin_mut().update_f64(key, values);
138 Ok(())
139 }
140
141 /// Adds a string key with its associated values. See [`Self::update_u64`].
142 pub fn update_str(&mut self, key: &str, values: &[f64]) -> Result<(), SketchError> {
143 self.check_values(values)?;
144 self.inner.pin_mut().update_str(key, values);
145 Ok(())
146 }
147
148 /// Adds an arbitrary byte-slice key with its associated values. See
149 /// [`Self::update_u64`].
150 pub fn update_bytes(&mut self, key: &[u8], values: &[f64]) -> Result<(), SketchError> {
151 self.check_values(values)?;
152 self.inner.pin_mut().update_bytes(key, values);
153 Ok(())
154 }
155
156 /// Removes retained entries in excess of the nominal size `k`, lowering
157 /// the theta threshold to do so.
158 ///
159 /// Note that this *does* shift [`Self::get_estimate`] — trimming lowers
160 /// theta, and the estimate is derived from the retained count and theta
161 /// together. Upstream only guarantees the excess entries are dropped.
162 pub fn trim(&mut self) {
163 self.inner.pin_mut().trim();
164 }
165
166 /// Resets this sketch to its initial, empty state. `num_values` is
167 /// preserved.
168 pub fn reset(&mut self) {
169 self.inner.pin_mut().reset();
170 }
171
172 /// Returns the current estimate of the number of distinct keys added.
173 pub fn get_estimate(&self) -> f64 {
174 self.inner.get_estimate()
175 }
176
177 /// Returns the lower bound of the confidence interval around
178 /// [`Self::get_estimate`], for the given number of standard deviations
179 /// (`1`, `2`, or `3`, corresponding to roughly 67%, 95%, and 99%
180 /// confidence). Returns [`SketchError::InvalidConfig`] for any other
181 /// value.
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`] for the meaning
190 /// of `num_std_dev`.
191 pub fn get_upper_bound(&self, num_std_dev: u8) -> Result<f64, SketchError> {
192 self.inner
193 .get_upper_bound(num_std_dev)
194 .map_err(|e| SketchError::InvalidConfig(e.what().to_string()))
195 }
196
197 /// Returns `true` if no keys have been added to this sketch.
198 pub fn is_empty(&self) -> bool {
199 self.inner.is_empty()
200 }
201
202 /// Returns `true` if this sketch's theta threshold is below `1.0`
203 /// (i.e. it has begun sampling and [`Self::get_estimate`] is a
204 /// statistical estimate rather than an exact count).
205 pub fn is_estimation_mode(&self) -> bool {
206 self.inner.is_estimation_mode()
207 }
208
209 /// Returns `true` if this sketch's retained entries are sorted by hash
210 /// value.
211 pub fn is_ordered(&self) -> bool {
212 self.inner.is_ordered()
213 }
214
215 /// Returns the current theta threshold (`1.0` until sampling begins).
216 pub fn get_theta(&self) -> f64 {
217 self.inner.get_theta()
218 }
219
220 /// Returns the number of entries currently retained by this sketch.
221 pub fn get_num_retained(&self) -> u32 {
222 self.inner.get_num_retained()
223 }
224
225 /// Returns the fixed number of `f64` values each retained entry carries,
226 /// as configured at build time.
227 pub fn get_num_values(&self) -> u8 {
228 self.num_values
229 }
230
231 /// Iterates the retained entries as `(hash, values)` pairs, where
232 /// `values.len() == self.get_num_values()`.
233 ///
234 /// The entries are copied out of C++ in two FFI calls up front (cxx
235 /// cannot hand back a live C++ iterator), so each item owns its `Vec`
236 /// rather than borrowing from the sketch. Iteration order is unspecified
237 /// for an update sketch; compact it with `ordered = true` for
238 /// hash-ordered iteration.
239 pub fn entries(&self) -> impl Iterator<Item = (u64, Vec<f64>)> {
240 let num_values = self.num_values as usize;
241 let hashes: Vec<u64> = self.inner.entry_hashes().into_iter().collect();
242 let values: Vec<f64> = self.inner.entry_values().into_iter().collect();
243 let grouped: Vec<Vec<f64>> = if num_values == 0 {
244 Vec::new()
245 } else {
246 values.chunks(num_values).map(|c| c.to_vec()).collect()
247 };
248 hashes.into_iter().zip(grouped)
249 }
250
251 /// Produces an immutable, serializable
252 /// [`super::CompactArrayOfDoublesSketch`] snapshot of this sketch's
253 /// current state. If `ordered` is `true`, the snapshot's entries are
254 /// sorted by hash value.
255 pub fn compact(&self, ordered: bool) -> super::CompactArrayOfDoublesSketch {
256 super::CompactArrayOfDoublesSketch::from_shim(self.inner.compact(ordered))
257 }
258}