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.
176 ///
177 /// Carried by both classes, because a bottom-k sketch that has not filled up is holding every
178 /// hash it was given and the count it gives back is the count. So this arrives as `Exact` for a
179 /// column with few enough distinct values and `Estimated` for one with more, and the class is
180 /// where that difference is written rather than here.
181 Sketch,
182 /// A frequency synopsis, with or without its certificate discharged.
183 FrequencySynopsis,
184 /// A quantile summary.
185 Quantiles,
186 /// A dictionary's size, standing in for or answering a distinct count.
187 Dictionary,
188 /// A persisted sortedness flag.
189 Sortedness,
190 /// A persisted distinctness flag.
191 Distinctness,
192 /// The four numbers in a relationship's link header, or a cardinality derived from them. Exact,
193 /// and the most valuable kind of exact there is, because join cardinality is where every cost
194 /// model in the literature goes wrong by orders of magnitude.
195 LinkHeader,
196 /// A relationship's degree distribution.
197 DegreeDistribution,
198 /// The stored sample.
199 Sample,
200 /// A constant in the source. The weakest answer that is not `Unknown`, and the one worth
201 /// searching an `EXPLAIN` for, because it means nobody had a number at that node at all.
202 Default,
203 /// Something a previous execution measured, out of the observation log. Kept apart from every
204 /// other variant here on purpose: the rest are facts about the file and this one is a fact about
205 /// history.
206 Observed,
207 /// A rule applied over two other answers. Not a source of data, and the honest thing to say
208 /// about a number that was derived rather than read.
209 Propagation,
210}
211
212impl Provenance {
213 /// The word `EXPLAIN` prints.
214 #[must_use]
215 pub const fn name(self) -> &'static str {
216 match self {
217 Self::RowCount => "row count",
218 Self::ZoneMap => "zone map",
219 Self::NullCount => "null count",
220 Self::Sketch => "sketch",
221 Self::FrequencySynopsis => "frequency synopsis",
222 Self::Quantiles => "quantiles",
223 Self::Dictionary => "dictionary",
224 Self::Sortedness => "sortedness",
225 Self::Distinctness => "distinctness",
226 Self::LinkHeader => "link header",
227 Self::DegreeDistribution => "degree distribution",
228 Self::Sample => "sample",
229 Self::Default => "default",
230 Self::Observed => "observed",
231 Self::Propagation => "propagation",
232 }
233 }
234
235 /// Whether this is a fact about a previous execution rather than about the file.
236 ///
237 /// The one question a consumer of `spec/stats/06-the-reward.md`'s tier 1 corrections has to be
238 /// able to ask, because an observation is `Exact` only for the generation it was taken on.
239 #[must_use]
240 pub const fn is_observed(self) -> bool {
241 matches!(self, Self::Observed)
242 }
243}
244
245impl fmt::Display for Provenance {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 f.write_str(self.name())
248 }
249}
250
251/// What a caller intends to do with an answer.
252///
253/// The three uses of `spec/stats/05-every-query.md` section 5.1.1. A consumer declares one, the
254/// class rule follows from it rather than from the consumer's judgement, and `EXPLAIN` prints which
255/// one happened. See [`Stat::answer`], [`Stat::enable`] and [`Stat::decide`].
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
257pub enum Use {
258 /// The statistic is the result. Entitled to `Exact`, and to `Certified` where the consumer can
259 /// discharge the proof obligation and has a fallback for when it cannot.
260 Answer,
261 /// The statistic licenses a rewrite that would be wrong if the statistic were wrong. Entitled to
262 /// `Exact` only.
263 Enable,
264 /// The statistic chooses between two plans that produce the same rows. Entitled to any class,
265 /// including none.
266 Decide,
267}
268
269impl Use {
270 /// The word `EXPLAIN` prints.
271 #[must_use]
272 pub const fn name(self) -> &'static str {
273 match self {
274 Self::Answer => "answer",
275 Self::Enable => "enable",
276 Self::Decide => "decide",
277 }
278 }
279
280 /// Whether a class is enough for this use.
281 ///
282 /// `Answer` says yes to a certificate here and the caller still has to discharge it, which is
283 /// what [`Stat::answer_certified`] is for. This function is the class rule and not the whole
284 /// obligation.
285 #[must_use]
286 pub const fn permits(self, class: Option<Class>) -> bool {
287 match self {
288 // Any class, including none, because a decision made from nothing is a decision made
289 // from a documented default and the default is printed as one.
290 Self::Decide => true,
291 // Exact always, and a certificate only where the caller discharges it.
292 Self::Answer => matches!(class, Some(Class::Exact | Class::Certified { .. })),
293 // Exact and nothing else, because a bound is not an equality and these rewrites need
294 // an equality.
295 Self::Enable => matches!(class, Some(Class::Exact)),
296 }
297 }
298}
299
300impl fmt::Display for Use {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 f.write_str(self.name())
303 }
304}
305
306impl Class {
307 /// Whether this is knowledge rather than a guess, which is the test the folding rule applies.
308 #[must_use]
309 pub const fn is_exact(self) -> bool {
310 matches!(self, Self::Exact)
311 }
312
313 /// The class of an answer computed from two others.
314 ///
315 /// Exact combined with anything else is the anything else, which is the honest direction and the
316 /// easy one to get backwards. Two certified bounds add, because a combination of two bounded
317 /// errors is bounded by their sum, and the sum saturates at one because a bound of more than a
318 /// hundred percent says nothing that `Estimated` does not say. Two certificates that bound
319 /// opposite sides combine to [`Direction::Within`], because that is all that is still provable about
320 /// the pair. Anything involving an estimate is an estimate.
321 #[must_use]
322 pub fn combine(self, other: Self) -> Self {
323 match (self, other) {
324 (Self::Exact, Self::Exact) => Self::Exact,
325 (Self::Exact, class) | (class, Self::Exact) => class,
326 (
327 Self::Certified { bound: left, direction: first },
328 Self::Certified { bound: right, direction: second },
329 ) => Self::Certified {
330 bound: (left + right).min(1.0),
331 direction: if first == second { first } else { Direction::Within },
332 },
333 _ => Self::Estimated,
334 }
335 }
336}
337
338impl fmt::Display for Class {
339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340 match self {
341 Self::Exact => f.write_str("exact"),
342 Self::Certified { bound, direction } => {
343 write!(f, "certified {direction} {:.2}%", bound * 100.0)
344 }
345 Self::Estimated => f.write_str("estimated"),
346 }
347 }
348}
349
350impl<T> Stat<T> {
351 /// A value that was counted, compared or maintained rather than guessed, and what produced it.
352 pub const fn exact(value: T, provenance: Provenance) -> Self {
353 Self::Known { value, class: Class::Exact, provenance }
354 }
355
356 /// A value wrong by no more than `bound` as a fraction of itself, on the side `direction` says.
357 pub const fn certified(
358 value: T,
359 bound: f64,
360 direction: Direction,
361 provenance: Provenance,
362 ) -> Self {
363 Self::Known { value, class: Class::Certified { bound, direction }, provenance }
364 }
365
366 /// A guess, and where it came from.
367 pub const fn estimated(value: T, provenance: Provenance) -> Self {
368 Self::Known { value, class: Class::Estimated, provenance }
369 }
370
371 /// Whether there is an answer at all.
372 #[must_use]
373 pub const fn is_known(&self) -> bool {
374 matches!(self, Self::Known { .. })
375 }
376
377 /// Whether there is no answer.
378 #[must_use]
379 pub const fn is_unknown(&self) -> bool {
380 matches!(self, Self::Unknown)
381 }
382
383 /// The value, whatever its class.
384 ///
385 /// Present because plenty of code wants to print a number or compare two of them without making
386 /// a claim about either. A caller that is about to act on it wants [`Self::decide`],
387 /// [`Self::answer`] or [`Self::enable`] instead, because those say which of the three uses is
388 /// happening and this one does not.
389 #[must_use]
390 pub const fn value(&self) -> Option<&T> {
391 match self {
392 Self::Known { value, .. } => Some(value),
393 Self::Unknown => None,
394 }
395 }
396
397 /// The value for a decision that chooses between two plans producing the same rows.
398 ///
399 /// Build side, grouping strategy, join order, reduction schedule, memory reservation, parallel
400 /// degree. Entitled to any class, so this is [`Self::value`] under a name that says what is
401 /// being done with it. `None` means the caller takes its documented default, and the worst case
402 /// is a slow query with a printed reason.
403 #[must_use]
404 pub const fn decide(&self) -> Option<&T> {
405 self.value()
406 }
407
408 /// The value for a rewrite that would be wrong if the value were wrong.
409 ///
410 /// Join elimination, sort elimination, distinct elimination, group by elimination, partition
411 /// pruning, an exact `IN` list filter, narrowing arithmetic, folding a predicate away. Exact and
412 /// nothing else, because a bound is not an equality and these need an equality.
413 ///
414 /// This is the strictest of the three and the one easiest to get wrong, because an enabling
415 /// rewrite on a statistic that is merely close does not produce a slow query, it produces a
416 /// wrong answer. See `spec/stats/05-every-query.md` sections 5.1.1 and 5.10.
417 #[must_use]
418 pub const fn enable(&self) -> Option<&T> {
419 match self {
420 Self::Known { value, class: Class::Exact, .. } => Some(value),
421 _ => None,
422 }
423 }
424
425 /// The value for a statistic that is itself the result, where that value is exact.
426 ///
427 /// `COUNT(*)` out of a row count, `MIN` out of a zone map whose bounds are values rather than
428 /// widened bounds. A certified answer does not come back from here, because answering from a
429 /// certificate needs the proof obligation discharged and this function has nothing to discharge
430 /// it with. Use [`Self::answer_certified`] for that case and keep the fallback.
431 #[must_use]
432 pub const fn answer(&self) -> Option<&T> {
433 self.enable()
434 }
435
436 /// The value for a statistic that is itself the result, where a certificate is acceptable and
437 /// the caller can discharge it.
438 ///
439 /// `discharge` is handed the bound and its direction and says whether this particular query can
440 /// live with them. A top-k group by out of a frequency synopsis is the case this exists for: the
441 /// synopsis answers when the k-th count is above the certified maximum of everything it omitted,
442 /// and does not otherwise. A caller that returns `true` unconditionally has written
443 /// [`Self::decide`] with extra steps and should say so.
444 #[must_use]
445 pub fn answer_certified(&self, discharge: impl FnOnce(f64, Direction) -> bool) -> Option<&T> {
446 match self {
447 Self::Known { value, class: Class::Exact, .. } => Some(value),
448 Self::Known { value, class: Class::Certified { bound, direction }, .. } => {
449 discharge(*bound, *direction).then_some(value)
450 }
451 _ => None,
452 }
453 }
454
455 /// The value for a use the caller is carrying rather than spelling.
456 ///
457 /// The same three rules, chosen by a [`Use`] in hand instead of by which method got called. A
458 /// consumer that keeps its use in a constant reads through here and `EXPLAIN` prints the same
459 /// constant, so the word a plan says a number was read for and the rule that number went
460 /// through can never drift apart.
461 ///
462 /// `Answer` is the exact only half of it, the same half [`Self::answer`] gives, because a
463 /// certificate needs a proof obligation discharged and there is nothing here to discharge it
464 /// with. A caller that can discharge one calls [`Self::answer_certified`] and says so.
465 #[must_use]
466 pub const fn read(&self, use_: Use) -> Option<&T> {
467 match use_ {
468 Use::Answer => self.answer(),
469 Use::Enable => self.enable(),
470 Use::Decide => self.decide(),
471 }
472 }
473
474 /// The value, but only when it is exact.
475 ///
476 /// The older name for [`Self::enable`], kept because the rule it enforces is stated under this
477 /// name in `spec/stats/05-every-query.md` section 5.10 and because a door that changes an answer
478 /// is worth being able to grep for two ways.
479 #[must_use]
480 pub const fn exact_value(&self) -> Option<&T> {
481 self.enable()
482 }
483
484 /// How much of the answer is knowledge, or `None` when there is no answer.
485 #[must_use]
486 pub const fn class(&self) -> Option<Class> {
487 match self {
488 Self::Known { class, .. } => Some(*class),
489 Self::Unknown => None,
490 }
491 }
492
493 /// What produced the answer, or `None` when there is no answer.
494 #[must_use]
495 pub const fn provenance(&self) -> Option<Provenance> {
496 match self {
497 Self::Known { provenance, .. } => Some(*provenance),
498 Self::Unknown => None,
499 }
500 }
501
502 /// Whether this answer is enough for that use, per the class rule.
503 #[must_use]
504 pub const fn permits(&self, use_: Use) -> bool {
505 use_.permits(self.class())
506 }
507
508 /// The value, or what the caller decided to do without one.
509 #[must_use]
510 pub fn unwrap_or(self, default: T) -> T {
511 match self {
512 Self::Known { value, .. } => value,
513 Self::Unknown => default,
514 }
515 }
516
517 /// The same answer about a different quantity, with the class carried across unchanged.
518 ///
519 /// For a transformation that cannot lose knowledge, such as reading a row count as a byte count
520 /// through a fixed width. A transformation that does lose knowledge should build its answer with
521 /// the class it deserves rather than mapping.
522 #[must_use]
523 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Stat<U> {
524 match self {
525 Self::Known { value, class, provenance } => {
526 Stat::Known { value: f(value), class, provenance }
527 }
528 Self::Unknown => Stat::Unknown,
529 }
530 }
531
532 /// An answer computed from two, unknown when either is unknown, classed by [`Class::combine`].
533 ///
534 /// The provenance of the result is [`Provenance::Propagation`] unless both sides agree, because
535 /// a number derived from a zone map and a row count came from neither of them on its own.
536 #[must_use]
537 pub fn zip<U, V>(self, other: Stat<U>, f: impl FnOnce(T, U) -> V) -> Stat<V> {
538 match (self, other) {
539 (
540 Self::Known { value: left, class: first, provenance: from },
541 Stat::Known { value: right, class: second, provenance: also },
542 ) => Stat::Known {
543 value: f(left, right),
544 class: first.combine(second),
545 provenance: if from == also { from } else { Provenance::Propagation },
546 },
547 _ => Stat::Unknown,
548 }
549 }
550}
551
552impl<T> Default for Stat<T> {
553 /// `Unknown`, because a statistic nobody filled in is a statistic nobody knows.
554 fn default() -> Self {
555 Self::Unknown
556 }
557}
558
559impl<T: fmt::Display> fmt::Display for Stat<T> {
560 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561 match self {
562 Self::Known { value, class, provenance } => {
563 write!(f, "{value} ({class} from {provenance})")
564 }
565 Self::Unknown => f.write_str("unknown"),
566 }
567 }
568}
569
570/// How many decisions were made on what.
571///
572/// The class histogram of `spec/stats/09-measurement.md` section 9.5. For a whole suite, the
573/// fraction of the planner's decisions that were exact, certified, estimated or unknown, which is
574/// the direct measurement of whether the statistics layer is doing its job. It is more diagnostic
575/// than q-error for the first several milestones, because early on the estimates are bad for the
576/// boring reason that there are none.
577#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
578pub struct Classes {
579 exact: u64,
580 certified: u64,
581 estimated: u64,
582 unknown: u64,
583}
584
585impl Classes {
586 /// An empty histogram.
587 #[must_use]
588 pub const fn new() -> Self {
589 Self { exact: 0, certified: 0, estimated: 0, unknown: 0 }
590 }
591
592 /// Counts one decision.
593 pub fn record<T>(&mut self, stat: &Stat<T>) {
594 self.record_class(stat.class());
595 }
596
597 /// Counts one decision whose class is already in hand.
598 pub fn record_class(&mut self, class: Option<Class>) {
599 match class {
600 Some(Class::Exact) => self.exact += 1,
601 Some(Class::Certified { .. }) => self.certified += 1,
602 Some(Class::Estimated) => self.estimated += 1,
603 None => self.unknown += 1,
604 }
605 }
606
607 /// Decisions made on an exact number.
608 #[must_use]
609 pub const fn exact(self) -> u64 {
610 self.exact
611 }
612
613 /// Decisions made on a bounded number.
614 #[must_use]
615 pub const fn certified(self) -> u64 {
616 self.certified
617 }
618
619 /// Decisions made on a guess.
620 #[must_use]
621 pub const fn estimated(self) -> u64 {
622 self.estimated
623 }
624
625 /// Decisions made with no number at all.
626 #[must_use]
627 pub const fn unknown(self) -> u64 {
628 self.unknown
629 }
630
631 /// Every decision counted.
632 #[must_use]
633 pub const fn total(self) -> u64 {
634 self.exact + self.certified + self.estimated + self.unknown
635 }
636
637 /// The fraction of decisions that had a number of any kind behind them, zero for an empty
638 /// histogram.
639 #[must_use]
640 pub fn known_share(self) -> f64 {
641 let total = self.total();
642 if total == 0 {
643 return 0.0;
644 }
645 #[expect(clippy::cast_precision_loss, reason = "a share is a report and not an answer")]
646 {
647 (total - self.unknown) as f64 / total as f64
648 }
649 }
650
651 /// Adds another histogram into this one, for a report that covers a suite rather than a query.
652 pub fn merge(&mut self, other: Self) {
653 self.exact += other.exact;
654 self.certified += other.certified;
655 self.estimated += other.estimated;
656 self.unknown += other.unknown;
657 }
658}
659
660impl fmt::Display for Classes {
661 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
662 write!(
663 f,
664 "exact {}, certified {}, estimated {}, unknown {}",
665 self.exact, self.certified, self.estimated, self.unknown
666 )
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673
674 #[test]
675 fn unknown_is_the_default() {
676 let stat: Stat<u64> = Stat::default();
677 assert!(stat.is_unknown());
678 assert_eq!(stat.value(), None);
679 assert_eq!(stat.class(), None);
680 assert_eq!(stat.unwrap_or(7), 7);
681 }
682
683 /// A certificate to hang a test on, so the tests below read as tests rather than as arguments.
684 const ROUGHLY: Class = Class::Certified { bound: 0.01, direction: Direction::Within };
685
686 #[test]
687 fn only_an_exact_answer_comes_back_from_exact_value() {
688 assert_eq!(Stat::exact(4_u64, Provenance::RowCount).exact_value(), Some(&4));
689 assert_eq!(
690 Stat::certified(4_u64, 0.01, Direction::Within, Provenance::Quantiles).exact_value(),
691 None
692 );
693 assert_eq!(Stat::estimated(4_u64, Provenance::Sketch).exact_value(), None);
694 assert_eq!(Stat::<u64>::Unknown.exact_value(), None);
695 }
696
697 #[test]
698 fn the_three_uses_are_entitled_to_different_classes() {
699 let exact = Stat::exact(4_u64, Provenance::RowCount);
700 let certified =
701 Stat::certified(4_u64, 0.01, Direction::AtMost, Provenance::FrequencySynopsis);
702 let estimated = Stat::estimated(4_u64, Provenance::Default);
703 let unknown = Stat::<u64>::Unknown;
704
705 // Enable is the strict one. Exact and nothing else, because a bound is not an equality.
706 assert_eq!(exact.enable(), Some(&4));
707 assert_eq!(certified.enable(), None);
708 assert_eq!(estimated.enable(), None);
709 assert_eq!(unknown.enable(), None);
710
711 // Answer without a discharge is the same door, because there is nothing here to discharge a
712 // certificate with.
713 assert_eq!(certified.answer(), None);
714 assert_eq!(certified.answer_certified(|bound, _| bound < 0.05), Some(&4));
715 assert_eq!(certified.answer_certified(|bound, _| bound < 0.001), None);
716 // A discharge is never asked about a guess, however generous it is.
717 assert_eq!(estimated.answer_certified(|_, _| true), None);
718
719 // Decide takes anything, and Unknown is a documented default rather than a failure.
720 assert_eq!(exact.decide(), Some(&4));
721 assert_eq!(estimated.decide(), Some(&4));
722 assert_eq!(unknown.decide(), None);
723
724 assert!(exact.permits(Use::Enable));
725 assert!(!certified.permits(Use::Enable));
726 assert!(certified.permits(Use::Answer));
727 assert!(!estimated.permits(Use::Answer));
728 assert!(unknown.permits(Use::Decide));
729 }
730
731 #[test]
732 fn a_class_degrades_when_it_is_combined() {
733 assert_eq!(Class::Exact.combine(Class::Exact), Class::Exact);
734 assert_eq!(Class::Exact.combine(Class::Estimated), Class::Estimated);
735 assert_eq!(Class::Exact.combine(ROUGHLY), ROUGHLY);
736 assert_eq!(
737 Class::Certified { bound: 0.01, direction: Direction::AtMost }
738 .combine(Class::Certified { bound: 0.02, direction: Direction::AtMost }),
739 Class::Certified { bound: 0.03, direction: Direction::AtMost }
740 );
741 assert_eq!(Class::Estimated.combine(ROUGHLY), Class::Estimated);
742 assert_eq!(Class::Estimated.combine(Class::Estimated), Class::Estimated);
743 }
744
745 #[test]
746 fn two_certificates_bounding_opposite_sides_only_bound_both() {
747 assert_eq!(
748 Class::Certified { bound: 0.01, direction: Direction::AtMost }
749 .combine(Class::Certified { bound: 0.02, direction: Direction::AtLeast }),
750 Class::Certified { bound: 0.03, direction: Direction::Within }
751 );
752 }
753
754 #[test]
755 fn a_certified_bound_saturates_rather_than_growing_past_everything() {
756 assert_eq!(
757 Class::Certified { bound: 0.8, direction: Direction::Within }
758 .combine(Class::Certified { bound: 0.7, direction: Direction::Within }),
759 Class::Certified { bound: 1.0, direction: Direction::Within }
760 );
761 }
762
763 #[test]
764 fn zip_is_unknown_when_either_side_is() {
765 let known = Stat::exact(10_u64, Provenance::RowCount);
766 let unknown = Stat::<u64>::Unknown;
767 assert_eq!(known.zip(unknown, |left, right| left + right), Stat::Unknown);
768 assert_eq!(unknown.zip(known, |left, right| left + right), Stat::Unknown);
769 assert_eq!(
770 known.zip(Stat::exact(5, Provenance::RowCount), |left, right| left + right),
771 Stat::exact(15, Provenance::RowCount)
772 );
773 }
774
775 #[test]
776 fn a_derived_answer_says_it_was_derived_rather_than_naming_one_side() {
777 let rows = Stat::exact(10_u64, Provenance::RowCount);
778 let nulls = Stat::exact(2_u64, Provenance::NullCount);
779 let counted = rows.zip(nulls, |rows, nulls| rows - nulls);
780 assert_eq!(counted.value(), Some(&8));
781 assert_eq!(counted.class(), Some(Class::Exact));
782 assert_eq!(counted.provenance(), Some(Provenance::Propagation));
783 }
784
785 #[test]
786 fn map_carries_the_class_and_the_provenance() {
787 let bytes = Stat::certified(100_u64, 0.05, Direction::Within, Provenance::Quantiles)
788 .map(|rows| rows * 8);
789 assert_eq!(bytes, Stat::certified(800, 0.05, Direction::Within, Provenance::Quantiles));
790 }
791
792 #[test]
793 fn the_histogram_counts_what_it_was_shown() {
794 let mut classes = Classes::new();
795 classes.record(&Stat::exact(1_u64, Provenance::RowCount));
796 classes.record(&Stat::certified(1_u64, 0.1, Direction::Within, Provenance::Quantiles));
797 classes.record(&Stat::estimated(1_u64, Provenance::ZoneMap));
798 classes.record(&Stat::<u64>::Unknown);
799 assert_eq!(classes.total(), 4);
800 assert_eq!(classes.exact(), 1);
801 assert_eq!(classes.known_share(), 0.75);
802 assert_eq!(classes.to_string(), "exact 1, certified 1, estimated 1, unknown 1");
803
804 let mut all = Classes::new();
805 all.merge(classes);
806 all.merge(classes);
807 assert_eq!(all.total(), 8);
808 }
809
810 #[test]
811 fn an_empty_histogram_knows_nothing_rather_than_everything() {
812 assert_eq!(Classes::new().known_share(), 0.0);
813 assert_eq!(Classes::new().total(), 0);
814 }
815
816 #[test]
817 fn an_answer_prints_its_class_and_its_provenance() {
818 assert_eq!(
819 Stat::exact(12_u64, Provenance::RowCount).to_string(),
820 "12 (exact from row count)"
821 );
822 assert_eq!(
823 Stat::certified(12_u64, 0.025, Direction::AtMost, Provenance::FrequencySynopsis)
824 .to_string(),
825 "12 (certified at most 2.50% from frequency synopsis)"
826 );
827 assert_eq!(
828 Stat::estimated(12_u64, Provenance::Default).to_string(),
829 "12 (estimated from default)"
830 );
831 assert_eq!(Stat::<u64>::Unknown.to_string(), "unknown");
832 }
833
834 #[test]
835 fn an_exact_number_says_where_it_came_from_too() {
836 // The whole reason provenance moved off Estimated. Two exact row counts, one out of a
837 // catalog and one out of a link header, and a reader of EXPLAIN can tell them apart.
838 let counted = Stat::exact(1_000_u64, Provenance::RowCount);
839 let joined = Stat::exact(1_000_u64, Provenance::LinkHeader);
840 assert_eq!(counted.class(), joined.class());
841 assert_ne!(counted.provenance(), joined.provenance());
842 assert_ne!(counted.to_string(), joined.to_string());
843 }
844
845 #[test]
846 fn an_observation_is_distinguishable_from_a_measurement_of_the_file() {
847 assert!(Provenance::Observed.is_observed());
848 for provenance in [
849 Provenance::RowCount,
850 Provenance::ZoneMap,
851 Provenance::NullCount,
852 Provenance::Sketch,
853 Provenance::FrequencySynopsis,
854 Provenance::Quantiles,
855 Provenance::Dictionary,
856 Provenance::Sortedness,
857 Provenance::Distinctness,
858 Provenance::LinkHeader,
859 Provenance::DegreeDistribution,
860 Provenance::Sample,
861 Provenance::Default,
862 Provenance::Propagation,
863 ] {
864 assert!(!provenance.is_observed(), "{provenance} is not an observation");
865 }
866 }
867}