rudb_common/stat.rs
1//! What the statistics layer answers with, and how much of it is safe to believe.
2//!
3//! One type comes back from every statistics question, [`Stat`], and it is either a value with a
4//! class attached or it is [`Stat::Unknown`]. `Unknown` is an answer rather than a failure, and it
5//! is not zero, not one and not a default constant. The caller decides what to do without a number,
6//! which is a decision the caller can make well and this layer cannot. A subsystem that invents a
7//! number instead of saying `Unknown` produces plans that are confidently wrong, and the reason
8//! those plans are hard to fix later is that nothing in them records which number was made up.
9//!
10//! Here at rank 0 because the answers are produced at rank 5 by the storage layer, at rank 8 by the
11//! catalog, at rank 11 by the optimizer and at rank 12 by an operator that has just finished its
12//! build side, and consumed in most of the same places. See `spec/stats/04-in-memory.md` section
13//! 4.1, which this module is the implementation of.
14//!
15//! # The class is the point
16//!
17//! [`Class`] is what makes three optimizations legal rather than merely attractive.
18//! `spec/stats/05-every-query.md` section 5.10 states the rule that constant folding on a statistic
19//! is legal only when the statistic is [`Class::Exact`], and [`Stat::exact_value`] is the one way to
20//! ask for a number under that rule. Narrowing a `DECIMAL(15,2)` sum into an `i64` is correct
21//! because the bound is exact and wrong if it is a guess. Seeding a top-n threshold from a quantile
22//! is correct because the certificate says the seed cannot exclude a qualifying row, which is what
23//! [`Class::Certified`] carries and what an estimate does not have.
24//!
25//! # Provenance belongs to every answer, not only to the guesses
26//!
27//! [`Provenance`] used to hang off [`Class::Estimated`] alone, on the reasoning that a bad plan is
28//! diagnosed by asking which guess was wrong and where it came from. That is half the job. An exact
29//! number is also worth attributing, because an exact row count out of the catalog and an exact
30//! join cardinality out of a link header are different kinds of exact and a reader of `EXPLAIN` has
31//! to be able to tell them apart. So provenance is a field of [`Stat::Known`] and every constructor
32//! takes one, per `spec/stats/02-the-catalogue.md` section 2.1.1.
33//!
34//! [`Provenance::Default`] is how a hardcoded constant admits to being one, and it is the word to
35//! search an `EXPLAIN` for, because it means nobody had a number at that node at all.
36//! [`Provenance::Observed`] is a measurement from a previous execution and is kept distinguishable
37//! from a measurement of the file, so that a reader can tell a fact about the data from a fact about
38//! history.
39//!
40//! # The three uses
41//!
42//! A class on its own does not say what a caller may do with it. The missing column is the use, and
43//! `spec/stats/05-every-query.md` section 5.1.1 names three of them. [`Stat::answer`] is for a
44//! statistic that *is* the result and takes [`Class::Exact`], or a certificate the caller can
45//! discharge. [`Stat::enable`] is for a rewrite that would be wrong if the number were wrong and
46//! takes [`Class::Exact`] and nothing else, because a bound is not an equality. [`Stat::decide`] is
47//! for choosing between two plans that produce the same rows and takes anything, including nothing,
48//! because a decision made from `Unknown` is a decision made from a documented default.
49//!
50//! Those are three methods rather than three comments because the difference between them is the
51//! difference between a slow query and a wrong answer, and the strictest of the three is the one a
52//! future contributor is most likely to break in good faith.
53//!
54//! # What the histogram reads today
55//!
56//! The one producer wired up is the optimizer's row count estimator, and the [`Classes`] histogram
57//! it fills is the G0 baseline that the rest of the series moves. It does not read all `Unknown`.
58//! A base table scan gets its count from the catalog and is [`Class::Exact`], a `LIMIT` over an
59//! input nobody counted is [`Class::Certified`] because the limit is a real ceiling, a table
60//! function or a dependent join is `Unknown`, and everything above the first filter, group by or
61//! join is [`Class::Estimated`] from [`Provenance::Default`], which is the literal selectivity guess
62//! the estimator has always used. So the honest zero is the share of decisions resting on a
63//! constant rather than a hundred percent unknown, and that share is what the series drives down.
64//!
65//! Nothing reads the class back yet. It is written into `EXPLAIN` and into the metrics document so
66//! that the ablation of `spec/stats/09-measurement.md` section 9.3 has a number to compare against,
67//! and no plan choice turns on it until a later milestone puts real statistics behind it.
68
69use std::fmt;
70
71/// One statistic, or the honest absence of one.
72///
73/// `Known` carries the value and how much to trust it. `Unknown` is what a question has no answer
74/// to, and the whole design rests on callers treating it as a case rather than as a zero.
75#[derive(Debug, Clone, Copy, PartialEq)]
76pub enum Stat<T> {
77 /// A value, what kind of knowledge it is, and where it came from.
78 Known {
79 /// The number, bound, flag or set the question asked for.
80 value: T,
81 /// How much of it is known rather than guessed.
82 class: Class,
83 /// What produced it, which is what `EXPLAIN` prints next to the class.
84 provenance: Provenance,
85 },
86 /// No answer. Not a zero, not a one and not a default.
87 ///
88 /// Not written, not resident, or not applicable. This is the ordinary answer for a statistic
89 /// whose load has just been scheduled, because `spec/stats/04-in-memory.md` says no query ever
90 /// waits on one, and it is the honest one.
91 Unknown,
92}
93
94/// How much of an answer is knowledge.
95///
96/// Three cases, ordered by how much they permit. `Exact` permits anything, including changing an
97/// answer by folding a predicate away. `Certified` permits a decision whose fallback survives being
98/// wrong, which is the case a safe bound is for. `Estimated` permits choosing between two plans that
99/// produce the same rows, and nothing else.
100#[derive(Debug, Clone, Copy, PartialEq)]
101pub enum Class {
102 /// The value is the value. A count that was counted, a null count that was maintained, a
103 /// minimum that was compared.
104 Exact,
105 /// The value is wrong by no more than `bound`, as a fraction of itself, in the direction
106 /// `direction` says, and the structure that produced it can prove that.
107 ///
108 /// A quantile summary with an epsilon is the usual source. The number is what makes a threshold
109 /// safe to seed from, so a producer that cannot state one should say `Estimated` instead of
110 /// picking a bound that sounds about right.
111 Certified {
112 /// The relative error bound, where `0.01` is one percent.
113 bound: f64,
114 /// Which side of the value the bound is on.
115 direction: Direction,
116 },
117 /// The value is a guess. Where it came from is the provenance beside it.
118 Estimated,
119}
120
121/// Which side of a value a certificate bounds.
122///
123/// A certificate that does not say the direction is a certificate a consumer cannot use, because
124/// the whole point of the class is picking the end of the range whose failure you can afford, per
125/// `spec/stats/05-every-query.md` section 5.1. Under-reserving memory spills and over-reserving
126/// starves, and they are not the same cost.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
128pub enum Direction {
129 /// The truth is no larger than the value. A frequency synopsis `omitted_max` is this.
130 AtMost,
131 /// The truth is no smaller than the value. A lower bound on a distinct count out of a sketch is
132 /// this.
133 AtLeast,
134 /// The truth is within the bound on either side. A quantile boundary with an epsilon is this.
135 Within,
136}
137
138impl Direction {
139 /// The word `EXPLAIN` prints.
140 #[must_use]
141 pub const fn name(self) -> &'static str {
142 match self {
143 Self::AtMost => "at most",
144 Self::AtLeast => "at least",
145 Self::Within => "within",
146 }
147 }
148}
149
150impl fmt::Display for Direction {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 f.write_str(self.name())
153 }
154}
155
156/// What produced an answer.
157///
158/// Printed by `EXPLAIN` next to the class, so these are the words a person reads when a plan went
159/// wrong. It names the source rather than the value, and it is carried by exact answers as well as
160/// by guesses, because an exact count out of a catalog and an exact join cardinality out of a link
161/// header want different follow up questions when one of them turns out to be stale.
162///
163/// The list is `spec/stats/02-the-catalogue.md` section 2.1.1's fourteen plus [`Self::Propagation`],
164/// which the specification does not name because it is not a source of data. It is what a number
165/// derived from two others says about itself, and leaving it out would mean a combination inherited
166/// the provenance of whichever operand happened to be on the left.
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
168pub enum Provenance {
169 /// A maintained row count. Exact.
170 RowCount,
171 /// A minimum and a maximum, either answered from or interpolated between.
172 ZoneMap,
173 /// A maintained null count. Exact.
174 NullCount,
175 /// A distinct-count sketch, outside the regime where it is exact.
176 Sketch,
177 /// A frequency synopsis, with or without its certificate discharged.
178 FrequencySynopsis,
179 /// A quantile summary.
180 Quantiles,
181 /// A dictionary's size, standing in for or answering a distinct count.
182 Dictionary,
183 /// A persisted sortedness flag.
184 Sortedness,
185 /// A persisted distinctness flag.
186 Distinctness,
187 /// The four numbers in a relationship's link header, or a cardinality derived from them. Exact,
188 /// and the most valuable kind of exact there is, because join cardinality is where every cost
189 /// model in the literature goes wrong by orders of magnitude.
190 LinkHeader,
191 /// A relationship's degree distribution.
192 DegreeDistribution,
193 /// The stored sample.
194 Sample,
195 /// A constant in the source. The weakest answer that is not `Unknown`, and the one worth
196 /// searching an `EXPLAIN` for, because it means nobody had a number at that node at all.
197 Default,
198 /// Something a previous execution measured, out of the observation log. Kept apart from every
199 /// other variant here on purpose: the rest are facts about the file and this one is a fact about
200 /// history.
201 Observed,
202 /// A rule applied over two other answers. Not a source of data, and the honest thing to say
203 /// about a number that was derived rather than read.
204 Propagation,
205}
206
207impl Provenance {
208 /// The word `EXPLAIN` prints.
209 #[must_use]
210 pub const fn name(self) -> &'static str {
211 match self {
212 Self::RowCount => "row count",
213 Self::ZoneMap => "zone map",
214 Self::NullCount => "null count",
215 Self::Sketch => "sketch",
216 Self::FrequencySynopsis => "frequency synopsis",
217 Self::Quantiles => "quantiles",
218 Self::Dictionary => "dictionary",
219 Self::Sortedness => "sortedness",
220 Self::Distinctness => "distinctness",
221 Self::LinkHeader => "link header",
222 Self::DegreeDistribution => "degree distribution",
223 Self::Sample => "sample",
224 Self::Default => "default",
225 Self::Observed => "observed",
226 Self::Propagation => "propagation",
227 }
228 }
229
230 /// Whether this is a fact about a previous execution rather than about the file.
231 ///
232 /// The one question a consumer of `spec/stats/06-the-reward.md`'s tier 1 corrections has to be
233 /// able to ask, because an observation is `Exact` only for the generation it was taken on.
234 #[must_use]
235 pub const fn is_observed(self) -> bool {
236 matches!(self, Self::Observed)
237 }
238}
239
240impl fmt::Display for Provenance {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 f.write_str(self.name())
243 }
244}
245
246/// What a caller intends to do with an answer.
247///
248/// The three uses of `spec/stats/05-every-query.md` section 5.1.1. A consumer declares one, the
249/// class rule follows from it rather than from the consumer's judgement, and `EXPLAIN` prints which
250/// one happened. See [`Stat::answer`], [`Stat::enable`] and [`Stat::decide`].
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
252pub enum Use {
253 /// The statistic is the result. Entitled to `Exact`, and to `Certified` where the consumer can
254 /// discharge the proof obligation and has a fallback for when it cannot.
255 Answer,
256 /// The statistic licenses a rewrite that would be wrong if the statistic were wrong. Entitled to
257 /// `Exact` only.
258 Enable,
259 /// The statistic chooses between two plans that produce the same rows. Entitled to any class,
260 /// including none.
261 Decide,
262}
263
264impl Use {
265 /// The word `EXPLAIN` prints.
266 #[must_use]
267 pub const fn name(self) -> &'static str {
268 match self {
269 Self::Answer => "answer",
270 Self::Enable => "enable",
271 Self::Decide => "decide",
272 }
273 }
274
275 /// Whether a class is enough for this use.
276 ///
277 /// `Answer` says yes to a certificate here and the caller still has to discharge it, which is
278 /// what [`Stat::answer_certified`] is for. This function is the class rule and not the whole
279 /// obligation.
280 #[must_use]
281 pub const fn permits(self, class: Option<Class>) -> bool {
282 match self {
283 // Any class, including none, because a decision made from nothing is a decision made
284 // from a documented default and the default is printed as one.
285 Self::Decide => true,
286 // Exact always, and a certificate only where the caller discharges it.
287 Self::Answer => matches!(class, Some(Class::Exact | Class::Certified { .. })),
288 // Exact and nothing else, because a bound is not an equality and these rewrites need
289 // an equality.
290 Self::Enable => matches!(class, Some(Class::Exact)),
291 }
292 }
293}
294
295impl fmt::Display for Use {
296 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297 f.write_str(self.name())
298 }
299}
300
301impl Class {
302 /// Whether this is knowledge rather than a guess, which is the test the folding rule applies.
303 #[must_use]
304 pub const fn is_exact(self) -> bool {
305 matches!(self, Self::Exact)
306 }
307
308 /// The class of an answer computed from two others.
309 ///
310 /// Exact combined with anything else is the anything else, which is the honest direction and the
311 /// easy one to get backwards. Two certified bounds add, because a combination of two bounded
312 /// errors is bounded by their sum, and the sum saturates at one because a bound of more than a
313 /// hundred percent says nothing that `Estimated` does not say. Two certificates that bound
314 /// opposite sides combine to [`Direction::Within`], because that is all that is still provable about
315 /// the pair. Anything involving an estimate is an estimate.
316 #[must_use]
317 pub fn combine(self, other: Self) -> Self {
318 match (self, other) {
319 (Self::Exact, Self::Exact) => Self::Exact,
320 (Self::Exact, class) | (class, Self::Exact) => class,
321 (
322 Self::Certified { bound: left, direction: first },
323 Self::Certified { bound: right, direction: second },
324 ) => Self::Certified {
325 bound: (left + right).min(1.0),
326 direction: if first == second { first } else { Direction::Within },
327 },
328 _ => Self::Estimated,
329 }
330 }
331}
332
333impl fmt::Display for Class {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 match self {
336 Self::Exact => f.write_str("exact"),
337 Self::Certified { bound, direction } => {
338 write!(f, "certified {direction} {:.2}%", bound * 100.0)
339 }
340 Self::Estimated => f.write_str("estimated"),
341 }
342 }
343}
344
345impl<T> Stat<T> {
346 /// A value that was counted, compared or maintained rather than guessed, and what produced it.
347 pub const fn exact(value: T, provenance: Provenance) -> Self {
348 Self::Known { value, class: Class::Exact, provenance }
349 }
350
351 /// A value wrong by no more than `bound` as a fraction of itself, on the side `direction` says.
352 pub const fn certified(
353 value: T,
354 bound: f64,
355 direction: Direction,
356 provenance: Provenance,
357 ) -> Self {
358 Self::Known { value, class: Class::Certified { bound, direction }, provenance }
359 }
360
361 /// A guess, and where it came from.
362 pub const fn estimated(value: T, provenance: Provenance) -> Self {
363 Self::Known { value, class: Class::Estimated, provenance }
364 }
365
366 /// Whether there is an answer at all.
367 #[must_use]
368 pub const fn is_known(&self) -> bool {
369 matches!(self, Self::Known { .. })
370 }
371
372 /// Whether there is no answer.
373 #[must_use]
374 pub const fn is_unknown(&self) -> bool {
375 matches!(self, Self::Unknown)
376 }
377
378 /// The value, whatever its class.
379 ///
380 /// Present because plenty of code wants to print a number or compare two of them without making
381 /// a claim about either. A caller that is about to act on it wants [`Self::decide`],
382 /// [`Self::answer`] or [`Self::enable`] instead, because those say which of the three uses is
383 /// happening and this one does not.
384 #[must_use]
385 pub const fn value(&self) -> Option<&T> {
386 match self {
387 Self::Known { value, .. } => Some(value),
388 Self::Unknown => None,
389 }
390 }
391
392 /// The value for a decision that chooses between two plans producing the same rows.
393 ///
394 /// Build side, grouping strategy, join order, reduction schedule, memory reservation, parallel
395 /// degree. Entitled to any class, so this is [`Self::value`] under a name that says what is
396 /// being done with it. `None` means the caller takes its documented default, and the worst case
397 /// is a slow query with a printed reason.
398 #[must_use]
399 pub const fn decide(&self) -> Option<&T> {
400 self.value()
401 }
402
403 /// The value for a rewrite that would be wrong if the value were wrong.
404 ///
405 /// Join elimination, sort elimination, distinct elimination, group by elimination, partition
406 /// pruning, an exact `IN` list filter, narrowing arithmetic, folding a predicate away. Exact and
407 /// nothing else, because a bound is not an equality and these need an equality.
408 ///
409 /// This is the strictest of the three and the one easiest to get wrong, because an enabling
410 /// rewrite on a statistic that is merely close does not produce a slow query, it produces a
411 /// wrong answer. See `spec/stats/05-every-query.md` sections 5.1.1 and 5.10.
412 #[must_use]
413 pub const fn enable(&self) -> Option<&T> {
414 match self {
415 Self::Known { value, class: Class::Exact, .. } => Some(value),
416 _ => None,
417 }
418 }
419
420 /// The value for a statistic that is itself the result, where that value is exact.
421 ///
422 /// `COUNT(*)` out of a row count, `MIN` out of a zone map whose bounds are values rather than
423 /// widened bounds. A certified answer does not come back from here, because answering from a
424 /// certificate needs the proof obligation discharged and this function has nothing to discharge
425 /// it with. Use [`Self::answer_certified`] for that case and keep the fallback.
426 #[must_use]
427 pub const fn answer(&self) -> Option<&T> {
428 self.enable()
429 }
430
431 /// The value for a statistic that is itself the result, where a certificate is acceptable and
432 /// the caller can discharge it.
433 ///
434 /// `discharge` is handed the bound and its direction and says whether this particular query can
435 /// live with them. A top-k group by out of a frequency synopsis is the case this exists for: the
436 /// synopsis answers when the k-th count is above the certified maximum of everything it omitted,
437 /// and does not otherwise. A caller that returns `true` unconditionally has written
438 /// [`Self::decide`] with extra steps and should say so.
439 #[must_use]
440 pub fn answer_certified(&self, discharge: impl FnOnce(f64, Direction) -> bool) -> Option<&T> {
441 match self {
442 Self::Known { value, class: Class::Exact, .. } => Some(value),
443 Self::Known { value, class: Class::Certified { bound, direction }, .. } => {
444 discharge(*bound, *direction).then_some(value)
445 }
446 _ => None,
447 }
448 }
449
450 /// The value, but only when it is exact.
451 ///
452 /// The older name for [`Self::enable`], kept because the rule it enforces is stated under this
453 /// name in `spec/stats/05-every-query.md` section 5.10 and because a door that changes an answer
454 /// is worth being able to grep for two ways.
455 #[must_use]
456 pub const fn exact_value(&self) -> Option<&T> {
457 self.enable()
458 }
459
460 /// How much of the answer is knowledge, or `None` when there is no answer.
461 #[must_use]
462 pub const fn class(&self) -> Option<Class> {
463 match self {
464 Self::Known { class, .. } => Some(*class),
465 Self::Unknown => None,
466 }
467 }
468
469 /// What produced the answer, or `None` when there is no answer.
470 #[must_use]
471 pub const fn provenance(&self) -> Option<Provenance> {
472 match self {
473 Self::Known { provenance, .. } => Some(*provenance),
474 Self::Unknown => None,
475 }
476 }
477
478 /// Whether this answer is enough for that use, per the class rule.
479 #[must_use]
480 pub const fn permits(&self, use_: Use) -> bool {
481 use_.permits(self.class())
482 }
483
484 /// The value, or what the caller decided to do without one.
485 #[must_use]
486 pub fn unwrap_or(self, default: T) -> T {
487 match self {
488 Self::Known { value, .. } => value,
489 Self::Unknown => default,
490 }
491 }
492
493 /// The same answer about a different quantity, with the class carried across unchanged.
494 ///
495 /// For a transformation that cannot lose knowledge, such as reading a row count as a byte count
496 /// through a fixed width. A transformation that does lose knowledge should build its answer with
497 /// the class it deserves rather than mapping.
498 #[must_use]
499 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Stat<U> {
500 match self {
501 Self::Known { value, class, provenance } => {
502 Stat::Known { value: f(value), class, provenance }
503 }
504 Self::Unknown => Stat::Unknown,
505 }
506 }
507
508 /// An answer computed from two, unknown when either is unknown, classed by [`Class::combine`].
509 ///
510 /// The provenance of the result is [`Provenance::Propagation`] unless both sides agree, because
511 /// a number derived from a zone map and a row count came from neither of them on its own.
512 #[must_use]
513 pub fn zip<U, V>(self, other: Stat<U>, f: impl FnOnce(T, U) -> V) -> Stat<V> {
514 match (self, other) {
515 (
516 Self::Known { value: left, class: first, provenance: from },
517 Stat::Known { value: right, class: second, provenance: also },
518 ) => Stat::Known {
519 value: f(left, right),
520 class: first.combine(second),
521 provenance: if from == also { from } else { Provenance::Propagation },
522 },
523 _ => Stat::Unknown,
524 }
525 }
526}
527
528impl<T> Default for Stat<T> {
529 /// `Unknown`, because a statistic nobody filled in is a statistic nobody knows.
530 fn default() -> Self {
531 Self::Unknown
532 }
533}
534
535impl<T: fmt::Display> fmt::Display for Stat<T> {
536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537 match self {
538 Self::Known { value, class, provenance } => {
539 write!(f, "{value} ({class} from {provenance})")
540 }
541 Self::Unknown => f.write_str("unknown"),
542 }
543 }
544}
545
546/// How many decisions were made on what.
547///
548/// The class histogram of `spec/stats/09-measurement.md` section 9.5. For a whole suite, the
549/// fraction of the planner's decisions that were exact, certified, estimated or unknown, which is
550/// the direct measurement of whether the statistics layer is doing its job. It is more diagnostic
551/// than q-error for the first several milestones, because early on the estimates are bad for the
552/// boring reason that there are none.
553#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
554pub struct Classes {
555 exact: u64,
556 certified: u64,
557 estimated: u64,
558 unknown: u64,
559}
560
561impl Classes {
562 /// An empty histogram.
563 #[must_use]
564 pub const fn new() -> Self {
565 Self { exact: 0, certified: 0, estimated: 0, unknown: 0 }
566 }
567
568 /// Counts one decision.
569 pub fn record<T>(&mut self, stat: &Stat<T>) {
570 self.record_class(stat.class());
571 }
572
573 /// Counts one decision whose class is already in hand.
574 pub fn record_class(&mut self, class: Option<Class>) {
575 match class {
576 Some(Class::Exact) => self.exact += 1,
577 Some(Class::Certified { .. }) => self.certified += 1,
578 Some(Class::Estimated) => self.estimated += 1,
579 None => self.unknown += 1,
580 }
581 }
582
583 /// Decisions made on an exact number.
584 #[must_use]
585 pub const fn exact(self) -> u64 {
586 self.exact
587 }
588
589 /// Decisions made on a bounded number.
590 #[must_use]
591 pub const fn certified(self) -> u64 {
592 self.certified
593 }
594
595 /// Decisions made on a guess.
596 #[must_use]
597 pub const fn estimated(self) -> u64 {
598 self.estimated
599 }
600
601 /// Decisions made with no number at all.
602 #[must_use]
603 pub const fn unknown(self) -> u64 {
604 self.unknown
605 }
606
607 /// Every decision counted.
608 #[must_use]
609 pub const fn total(self) -> u64 {
610 self.exact + self.certified + self.estimated + self.unknown
611 }
612
613 /// The fraction of decisions that had a number of any kind behind them, zero for an empty
614 /// histogram.
615 #[must_use]
616 pub fn known_share(self) -> f64 {
617 let total = self.total();
618 if total == 0 {
619 return 0.0;
620 }
621 #[expect(clippy::cast_precision_loss, reason = "a share is a report and not an answer")]
622 {
623 (total - self.unknown) as f64 / total as f64
624 }
625 }
626
627 /// Adds another histogram into this one, for a report that covers a suite rather than a query.
628 pub fn merge(&mut self, other: Self) {
629 self.exact += other.exact;
630 self.certified += other.certified;
631 self.estimated += other.estimated;
632 self.unknown += other.unknown;
633 }
634}
635
636impl fmt::Display for Classes {
637 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638 write!(
639 f,
640 "exact {}, certified {}, estimated {}, unknown {}",
641 self.exact, self.certified, self.estimated, self.unknown
642 )
643 }
644}
645
646#[cfg(test)]
647mod tests {
648 use super::*;
649
650 #[test]
651 fn unknown_is_the_default() {
652 let stat: Stat<u64> = Stat::default();
653 assert!(stat.is_unknown());
654 assert_eq!(stat.value(), None);
655 assert_eq!(stat.class(), None);
656 assert_eq!(stat.unwrap_or(7), 7);
657 }
658
659 /// A certificate to hang a test on, so the tests below read as tests rather than as arguments.
660 const ROUGHLY: Class = Class::Certified { bound: 0.01, direction: Direction::Within };
661
662 #[test]
663 fn only_an_exact_answer_comes_back_from_exact_value() {
664 assert_eq!(Stat::exact(4_u64, Provenance::RowCount).exact_value(), Some(&4));
665 assert_eq!(
666 Stat::certified(4_u64, 0.01, Direction::Within, Provenance::Quantiles).exact_value(),
667 None
668 );
669 assert_eq!(Stat::estimated(4_u64, Provenance::Sketch).exact_value(), None);
670 assert_eq!(Stat::<u64>::Unknown.exact_value(), None);
671 }
672
673 #[test]
674 fn the_three_uses_are_entitled_to_different_classes() {
675 let exact = Stat::exact(4_u64, Provenance::RowCount);
676 let certified =
677 Stat::certified(4_u64, 0.01, Direction::AtMost, Provenance::FrequencySynopsis);
678 let estimated = Stat::estimated(4_u64, Provenance::Default);
679 let unknown = Stat::<u64>::Unknown;
680
681 // Enable is the strict one. Exact and nothing else, because a bound is not an equality.
682 assert_eq!(exact.enable(), Some(&4));
683 assert_eq!(certified.enable(), None);
684 assert_eq!(estimated.enable(), None);
685 assert_eq!(unknown.enable(), None);
686
687 // Answer without a discharge is the same door, because there is nothing here to discharge a
688 // certificate with.
689 assert_eq!(certified.answer(), None);
690 assert_eq!(certified.answer_certified(|bound, _| bound < 0.05), Some(&4));
691 assert_eq!(certified.answer_certified(|bound, _| bound < 0.001), None);
692 // A discharge is never asked about a guess, however generous it is.
693 assert_eq!(estimated.answer_certified(|_, _| true), None);
694
695 // Decide takes anything, and Unknown is a documented default rather than a failure.
696 assert_eq!(exact.decide(), Some(&4));
697 assert_eq!(estimated.decide(), Some(&4));
698 assert_eq!(unknown.decide(), None);
699
700 assert!(exact.permits(Use::Enable));
701 assert!(!certified.permits(Use::Enable));
702 assert!(certified.permits(Use::Answer));
703 assert!(!estimated.permits(Use::Answer));
704 assert!(unknown.permits(Use::Decide));
705 }
706
707 #[test]
708 fn a_class_degrades_when_it_is_combined() {
709 assert_eq!(Class::Exact.combine(Class::Exact), Class::Exact);
710 assert_eq!(Class::Exact.combine(Class::Estimated), Class::Estimated);
711 assert_eq!(Class::Exact.combine(ROUGHLY), ROUGHLY);
712 assert_eq!(
713 Class::Certified { bound: 0.01, direction: Direction::AtMost }
714 .combine(Class::Certified { bound: 0.02, direction: Direction::AtMost }),
715 Class::Certified { bound: 0.03, direction: Direction::AtMost }
716 );
717 assert_eq!(Class::Estimated.combine(ROUGHLY), Class::Estimated);
718 assert_eq!(Class::Estimated.combine(Class::Estimated), Class::Estimated);
719 }
720
721 #[test]
722 fn two_certificates_bounding_opposite_sides_only_bound_both() {
723 assert_eq!(
724 Class::Certified { bound: 0.01, direction: Direction::AtMost }
725 .combine(Class::Certified { bound: 0.02, direction: Direction::AtLeast }),
726 Class::Certified { bound: 0.03, direction: Direction::Within }
727 );
728 }
729
730 #[test]
731 fn a_certified_bound_saturates_rather_than_growing_past_everything() {
732 assert_eq!(
733 Class::Certified { bound: 0.8, direction: Direction::Within }
734 .combine(Class::Certified { bound: 0.7, direction: Direction::Within }),
735 Class::Certified { bound: 1.0, direction: Direction::Within }
736 );
737 }
738
739 #[test]
740 fn zip_is_unknown_when_either_side_is() {
741 let known = Stat::exact(10_u64, Provenance::RowCount);
742 let unknown = Stat::<u64>::Unknown;
743 assert_eq!(known.zip(unknown, |left, right| left + right), Stat::Unknown);
744 assert_eq!(unknown.zip(known, |left, right| left + right), Stat::Unknown);
745 assert_eq!(
746 known.zip(Stat::exact(5, Provenance::RowCount), |left, right| left + right),
747 Stat::exact(15, Provenance::RowCount)
748 );
749 }
750
751 #[test]
752 fn a_derived_answer_says_it_was_derived_rather_than_naming_one_side() {
753 let rows = Stat::exact(10_u64, Provenance::RowCount);
754 let nulls = Stat::exact(2_u64, Provenance::NullCount);
755 let counted = rows.zip(nulls, |rows, nulls| rows - nulls);
756 assert_eq!(counted.value(), Some(&8));
757 assert_eq!(counted.class(), Some(Class::Exact));
758 assert_eq!(counted.provenance(), Some(Provenance::Propagation));
759 }
760
761 #[test]
762 fn map_carries_the_class_and_the_provenance() {
763 let bytes = Stat::certified(100_u64, 0.05, Direction::Within, Provenance::Quantiles)
764 .map(|rows| rows * 8);
765 assert_eq!(bytes, Stat::certified(800, 0.05, Direction::Within, Provenance::Quantiles));
766 }
767
768 #[test]
769 fn the_histogram_counts_what_it_was_shown() {
770 let mut classes = Classes::new();
771 classes.record(&Stat::exact(1_u64, Provenance::RowCount));
772 classes.record(&Stat::certified(1_u64, 0.1, Direction::Within, Provenance::Quantiles));
773 classes.record(&Stat::estimated(1_u64, Provenance::ZoneMap));
774 classes.record(&Stat::<u64>::Unknown);
775 assert_eq!(classes.total(), 4);
776 assert_eq!(classes.exact(), 1);
777 assert_eq!(classes.known_share(), 0.75);
778 assert_eq!(classes.to_string(), "exact 1, certified 1, estimated 1, unknown 1");
779
780 let mut all = Classes::new();
781 all.merge(classes);
782 all.merge(classes);
783 assert_eq!(all.total(), 8);
784 }
785
786 #[test]
787 fn an_empty_histogram_knows_nothing_rather_than_everything() {
788 assert_eq!(Classes::new().known_share(), 0.0);
789 assert_eq!(Classes::new().total(), 0);
790 }
791
792 #[test]
793 fn an_answer_prints_its_class_and_its_provenance() {
794 assert_eq!(
795 Stat::exact(12_u64, Provenance::RowCount).to_string(),
796 "12 (exact from row count)"
797 );
798 assert_eq!(
799 Stat::certified(12_u64, 0.025, Direction::AtMost, Provenance::FrequencySynopsis)
800 .to_string(),
801 "12 (certified at most 2.50% from frequency synopsis)"
802 );
803 assert_eq!(
804 Stat::estimated(12_u64, Provenance::Default).to_string(),
805 "12 (estimated from default)"
806 );
807 assert_eq!(Stat::<u64>::Unknown.to_string(), "unknown");
808 }
809
810 #[test]
811 fn an_exact_number_says_where_it_came_from_too() {
812 // The whole reason provenance moved off Estimated. Two exact row counts, one out of a
813 // catalog and one out of a link header, and a reader of EXPLAIN can tell them apart.
814 let counted = Stat::exact(1_000_u64, Provenance::RowCount);
815 let joined = Stat::exact(1_000_u64, Provenance::LinkHeader);
816 assert_eq!(counted.class(), joined.class());
817 assert_ne!(counted.provenance(), joined.provenance());
818 assert_ne!(counted.to_string(), joined.to_string());
819 }
820
821 #[test]
822 fn an_observation_is_distinguishable_from_a_measurement_of_the_file() {
823 assert!(Provenance::Observed.is_observed());
824 for provenance in [
825 Provenance::RowCount,
826 Provenance::ZoneMap,
827 Provenance::NullCount,
828 Provenance::Sketch,
829 Provenance::FrequencySynopsis,
830 Provenance::Quantiles,
831 Provenance::Dictionary,
832 Provenance::Sortedness,
833 Provenance::Distinctness,
834 Provenance::LinkHeader,
835 Provenance::DegreeDistribution,
836 Provenance::Sample,
837 Provenance::Default,
838 Provenance::Propagation,
839 ] {
840 assert!(!provenance.is_observed(), "{provenance} is not an observation");
841 }
842 }
843}