Skip to main content

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}