rudb-opt 0.4.13

The rewrite passes, cardinality estimation, join ordering, predicate transfer and layout adaptation.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
//! What `EXPLAIN` prints.
//!
//! The logical plan with an estimate beside each operator, the pipeline each operator runs in, the
//! edges between those pipelines, and what is running at every seam. That is what
//! `spec/09-optimizer.md` section 9.5 asks of `EXPLAIN` at this milestone. The physical plan is the
//! other half and it waits for there to be a physical plan: today the executor is built straight
//! off the logical one by `crates/rudb-exec/src/build.rs`, so a physical section would be the same
//! tree with different words on it.
//!
//! `EXPLAIN ANALYZE` is [`analyzed`], which is the same three sections with the numbers of a query
//! that actually ran written next to them. It prints from the metrics document rather than from
//! anything of its own, so what it shows and what `--metrics run.json` writes out are the same
//! numbers read two ways, and a disagreement between a printed plan and a recorded run is not a
//! thing that can happen.
//!
//! This is in the optimizer rather than in `rudb-plan` because the estimate is here, and printing
//! the plan without the estimate would be `Plan::to_string`, which already exists.
//!
//! # Why the pipelines are not worked out here
//!
//! They come from [`Shape`], which lives in `rudb-plan`, and the executor builds its tree out of the
//! same call. Two walks that both decide where a plan breaks would agree on the day they were
//! written and disagree some time after, and the one that would be wrong is this one, which is the
//! one somebody reads when they are trying to find out why a query is slow.
//!
//! # Why a join says which algorithm it is and why the other one was not
//!
//! `spec/graph/06-the-optimizer.md` section 6.7, and the argument there is worth repeating: this
//! layer's failure mode is silence. A hash join that is slow is visibly a hash join, and a link
//! that was not read because a projection two nodes up dropped the row id looks exactly like a link
//! that does not exist. So a join over a session that declared relationships carries the reason on
//! its line, and the reason comes from [`crate::link::why`], which is the code the decision was
//! made with rather than a second reading of the same plan.
//!
//! Nothing at all on a session that declared none, which is almost every session. A clause on every
//! join of every query saying no relationship was declared is one people stop reading, and then
//! they stop reading it on the plan where it says something.
//!
//! # What the reference marker means
//!
//! Every operator at F0 is running the simplest correct implementation of everything it does, and a
//! number measured against a reference implementation is not a number worth quoting as the engine's.
//! So every line says so, and the marker comes off a line on the day one of the seams under it has
//! a second implementation registered and chosen. It is read off the registries rather than written
//! down here, so nobody has to remember to take it off.
//!
//! # This output is not a compatibility surface
//!
//! `spec/12-duckdb-compat.md` section 12.5 excludes `EXPLAIN` text from the guarantee, and section
//! 9.5 says why: matching DuckDB's explain text would pin our optimizer to their operator
//! vocabulary, and their vocabulary is a physical one with a `HASH_JOIN` in it. So the shape is
//! DuckDB's, two `VARCHAR` columns called `explain_key` and `explain_value`, because that is what a
//! client reading a result set has to cope with, and the text inside the second column is ours.
//!
//! It is stable enough to commit as a test baseline within a minor version, which is what section
//! 11.8's plan stability test is going to read.

use std::fmt::Write as _;

use rudb_common::stat::{Class, Classes, Stat, Use};
use rudb_metrics::{Document, Operator, commas};
use rudb_plan::{
    ColumnBinding, Keys, Node, NodeRef, OperatorRef, PipelineRef, Plan, Shape, keys_of, seams_of,
};
use rudb_seam::{Registries, SeamId, Settings};

use crate::estimate::{CARDINALITY, DISTINCT, Facts, rows_stat, rows_stat_into};
use crate::pass::Context;

/// Whether `EXPLAIN` was asked what the planner knew.
///
/// A named pair rather than a `bool`, because `explain_with(plan, context, seams, true)` at a call
/// site says nothing about what is true.
///
/// It is off by default because the plan is what somebody reading `EXPLAIN` came for, and a use and
/// a class on every line is a second sentence per line for a question most readers are not asking.
/// `EXPLAIN (STATISTICS)` is the question, and `spec/stats/05-every-query.md` section 5.1.1 is what
/// it answers.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Statistics {
    /// Say what each estimate was read for, and count the classes underneath.
    Asked,
    /// The plan, the pipelines and the seams, which is what a plain `EXPLAIN` prints.
    NotAsked,
}

/// What `EXPLAIN` needs to know about the seams to print the last section.
///
/// Two borrows rather than one, because what is registered is a property of the process and what is
/// pinned is a property of the session, and a query that was run with a hint on it prints
/// differently from the same query without one.
#[derive(Debug, Clone, Copy)]
pub struct Seams<'a> {
    settings: &'a Settings,
    registries: &'a Registries,
}

impl<'a> Seams<'a> {
    /// The settings a statement ran under, against the registries the process assembled.
    #[must_use]
    pub fn new(settings: &'a Settings, registries: &'a Registries) -> Self {
        Self { settings, registries }
    }

    /// The settings the statement runs under.
    ///
    /// The builder needs them, because an operator that sits on a seam chooses in its constructor,
    /// and `EXPLAIN ANALYZE` runs the query through the same path an ordinary statement takes. This
    /// hands back the same settings this was made from, so the plan that is printed and the tree
    /// that ran chose from the same pins.
    #[must_use]
    pub fn settings(&self) -> &'a Settings {
        self.settings
    }

    /// The name of what runs at this seam, said the way the seam section prints it.
    ///
    /// `None` when the seam has no registry, which is what [`Registries::running`] means by it and
    /// is the same answer the executor gets. The formatting is this section's, the decision is not.
    fn chosen(self, seam: SeamId) -> Option<(String, bool)> {
        let running = self.registries.running(seam, self.settings)?;
        let how = if running.pinned { "pinned" } else { "default" };
        Some((format!("{} ({how})", running.name), running.is_reference))
    }

    /// Whether everything this node does is a reference implementation.
    fn all_reference(self, node: &Node) -> bool {
        seams_of(node).iter().all(|seam| self.chosen(*seam).is_none_or(|(_, reference)| reference))
    }
}

/// The plan as `EXPLAIN` prints it, for a caller with no seam state to hand.
///
/// The plan section is the same either way. What is missing is the seam section, which is why this
/// exists for tests and for anything that wants the tree and nothing else.
#[must_use]
pub fn explain(plan: &Plan, context: &Context) -> String {
    let settings = Settings::new();
    let registries = Registries::new();
    explain_with(plan, context, Seams::new(&settings, &registries), Statistics::NotAsked)
}

/// The plan as `EXPLAIN` prints it: the tree, then the pipelines, then the seams.
///
/// Each line of the tree is the operator as [`Plan::operator`] writes it with the estimate, the
/// pipeline and the reference marker appended, so the arguments that decide what an operator does
/// are all still there and a reader who knows the plan format already knows this one.
///
/// An operator whose estimate is unknown says so rather than being left blank. A blank reads as
/// zero, and the difference between "no rows" and "nobody knows" is the whole of what
/// [`crate::estimate`] is careful about.
#[must_use]
pub fn explain_with(
    plan: &Plan,
    context: &Context,
    seams: Seams<'_>,
    statistics: Statistics,
) -> String {
    printed(plan, context, seams, None, statistics)
}

/// The plan as `EXPLAIN ANALYZE` prints it, which is the same three sections with what happened
/// written next to what was expected.
///
/// The document has to be the one the same plan produced. Every number in the output is looked up
/// by the operator id [`Shape`] gives a node, which is the id the builder tagged that operator's
/// counters with, so a document from a different query lines nothing up rather than lining the
/// wrong things up.
#[must_use]
pub fn analyzed(
    plan: &Plan,
    context: &Context,
    seams: Seams<'_>,
    measured: &Document,
    statistics: Statistics,
) -> String {
    printed(plan, context, seams, Some(measured), statistics)
}

/// Writes the estimated row count of every node onto the operator row that node became, and the
/// histogram of how much the planner knew onto the document.
///
/// Done here because the estimate is here and the mapping from a node to an operator is in
/// `rudb-plan`, and neither of those is something the crate that runs a query should be working out
/// for itself. An estimate an order of magnitude away from what happened is how a bad plan explains
/// itself, and `rudb_metrics::warnings` cannot say so over a document where the estimate is missing.
///
/// The histogram counts one decision per operator and not per node, for the same reason the row
/// counts go on operators: a node the physical plan folded away is not a decision anybody acted on,
/// and counting it would move the number with a plan rewrite that changed nothing about what was
/// known.
pub fn record_estimates(plan: &Plan, facts: &Facts, document: &mut Document) {
    let shape = Shape::of(plan);
    let mut estimated = vec![Stat::Unknown; shape.operators() as usize];
    for node in 0..u32::try_from(plan.node_count()).unwrap_or(u32::MAX) {
        if let Some(id) = shape.operator_of(node) {
            estimated[id as usize] = rows_stat(plan, node, facts);
        }
    }
    // A filter the scan below applies is a node with no operator of its own, and the scan's operator
    // counts what came out of the filter rather than what is in the table. The estimate that belongs
    // against those rows is the filter's, so it is written over the scan's here. Leaving the scan's
    // there compares a count of the table against a count of what survived a predicate, which is two
    // different questions and reads as an exact count that was wrong.
    for node in 0..u32::try_from(plan.node_count()).unwrap_or(u32::MAX) {
        let Node::Filter { input, .. } = *plan.node(node) else { continue };
        if crate::bounds::into_scan(plan, node).is_none() {
            continue;
        }
        if let Some(id) = shape.operator_of(input) {
            estimated[id as usize] = rows_stat(plan, node, facts);
        }
    }
    for operator in &mut document.operators {
        if let Some(estimate) = estimated.get(operator.id as usize) {
            operator.estimated_rows = estimate.value().copied();
            operator.estimate_class = estimate.class();
            operator.estimate_provenance = estimate.provenance();
            document.estimates.record(estimate);
        }
    }
}

/// The three sections, with the measured numbers in them if there are any.
fn printed(
    plan: &Plan,
    context: &Context,
    seams: Seams<'_>,
    measured: Option<&Document>,
    statistics: Statistics,
) -> String {
    let facts = context.facts();
    let shape = Shape::of(plan);
    let keys = keys_of(plan);
    let printing =
        Printing { plan, context, facts, shape: &shape, seams, measured, statistics, keys: &keys };
    let mut out = String::new();
    printing.write_node(plan.root(), 0, false, &mut out);
    write_pipelines(&shape, measured, &mut out);
    write_seams(seams, &mut out);
    if statistics == Statistics::Asked {
        write_statistics(reads(plan, facts, &shape), measured, &mut out);
    }
    if let Some(measured) = measured {
        write_totals(measured, &mut out);
    }
    out
}

/// The row count and its class, as the bracket on a plan line reads.
///
/// The tilde is on the guesses and nothing else. A count that was counted is printed without one
/// because it is not approximately anything, and the words after the number say what kind of
/// knowledge it is and where it came from, which is the question somebody asks when a plan went
/// wrong. `spec/stats/04-in-memory.md` section 4.1 asks for both to be printed for exactly that
/// reason: a bad plan is diagnosed by asking which number was wrong and who produced it, and
/// `estimated from default` is the answer that says nobody had a number here at all.
///
/// The provenance is printed next to an exact number too, per `spec/stats/02-the-catalogue.md`
/// section 2.1.1. An exact count out of the catalog and an exact join cardinality out of a link
/// header are different kinds of exact and a reader has to be able to tell them apart.
/// With the statistics asked for, the use the number was read for goes on the end of the same
/// bracket. It belongs next to the class and not in a section of its own, because the question it
/// answers is about this line: a guess read to decide is a slow query at worst, and the same guess
/// read to enable would be a wrong answer, so the pair is what says whether a line is safe.
fn estimate(stat: Stat<u64>, statistics: Statistics) -> String {
    let read = match statistics {
        Statistics::Asked => format!(", read to {CARDINALITY}"),
        Statistics::NotAsked => String::new(),
    };
    match stat {
        Stat::Unknown => format!("rows unknown{read}"),
        Stat::Known { value, class, provenance } => match class {
            Class::Estimated => format!("~{value} rows {class} from {provenance}{read}"),
            class => format!("{value} rows {class} from {provenance}{read}"),
        },
    }
}

/// Everything a line of the tree is written from, which is the same for every line.
///
/// The walk down the tree changes the node and the depth and nothing else, so the rest is carried
/// here rather than as five more arguments repeated at each level.
#[derive(Clone, Copy)]
struct Printing<'a> {
    plan: &'a Plan,
    /// What the planner was told, which is what the join lines are attributed from.
    context: &'a Context,
    facts: &'a Facts,
    shape: &'a Shape,
    seams: Seams<'a>,
    measured: Option<&'a Document>,
    statistics: Statistics,
    /// What tells each node's rows apart, indexed by node, from `rudb_plan::keys_of`.
    keys: &'a [Keys],
}

impl Printing<'_> {
    /// One line for this node and one for each below it.
    ///
    /// `filtered` says the node above this one is a filter the scan applies itself, which is true of
    /// exactly one node in a plan that has one and says that the rows counted on this line are the
    /// rows that came out of that filter. Carried down rather than looked up, because the node above
    /// is the one that knows and a node cannot see its parent.
    fn write_node(self, node: NodeRef, depth: usize, filtered: bool, out: &mut String) {
        let printed = self.plan.operator(node);
        let estimate = estimate(rows_stat(self.plan, node, self.facts), self.statistics);
        let pipeline = self.shape.pipeline(node);
        let marker =
            if self.seams.all_reference(self.plan.node(node)) { " [reference]" } else { "" };
        // A filter the scan below applies is a node with no operator, so there is nothing to look up
        // and nothing went wrong. It says where its work went instead, and the rows and the time are
        // on the scan's line one level down, counted once.
        //
        // Only under `ANALYZE`, because this is what stands in place of a measurement. A plan printed
        // without one has no column here at all, and a note about where work happens in a run that
        // did not happen would be answering a question nobody asked.
        let moved = self.measured.is_some() && crate::bounds::into_scan(self.plan, node).is_some();
        let actual = if moved {
            "  [applied by the scan below]".to_owned()
        } else {
            self.measured
                .map(|measured| actually(measured, self.shape.operator(node), filtered))
                .unwrap_or_default()
        };
        let told = match self.statistics {
            Statistics::Asked => tells_apart(self.keys.get(node as usize)),
            Statistics::NotAsked => String::new(),
        };
        let chose = chosen(self.plan, node, self.context);
        let room = sized(self.plan, node);
        // The estimate goes after the operator rather than in a column of its own, because the tree
        // is indented and a column would have to be wider than the deepest line to line up.
        let _ = writeln!(
            out,
            "{:indent$}{printed}  [{estimate}] [pipeline {pipeline}]{told}{room}{chose}{marker}{actual}",
            "",
            indent = depth * 2
        );
        if let (Some(measured), Some(gathered)) = (self.measured, self.shape.gathered(node)) {
            // The operator holding the side that finishes first has no line of the plan to sit on,
            // because it is not a node. It gets its own line under the one it belongs to rather
            // than being left out, since it is where the time of a build side actually goes.
            if let Some(operator) = row(measured, gathered) {
                let _ = writeln!(
                    out,
                    "{:indent$}{} of the side that finishes first{}",
                    "",
                    operator.kind,
                    actually(measured, gathered, false),
                    indent = (depth + 1) * 2
                );
            }
        }
        for child in children(self.plan.node(node)) {
            self.write_node(child, depth + 1, moved, out);
        }
    }
}

/// What the statistics decided about an aggregate's table, as a bracket on its line.
///
/// Two passes write a number onto an aggregate and neither of them moves an operator.
/// [`crate::presize`] says how many groups the table should have room for before the first row
/// arrives, and [`crate::dense`] says the key can be an address into an array instead of a probe
/// into buckets. A plan either of them has run over prints exactly as it printed before, so from
/// the outside there is no way to tell a group by that presized from one that did not, and no way to
/// tell a run with `SET stats_presize = false` from a run where the pass found nothing to do. That
/// is the same silence `chosen` below was written for.
///
/// The numbers are printed and not just the fact, because the number is the part that can be wrong.
/// A table sized for a hundred groups that ends up holding a million rehashed its way there anyway,
/// and an array over a range far wider than the groups it holds is the case
/// `spec/stats/05-every-query.md` section 5.4 warns about. Neither is visible from the word alone.
///
/// Nothing on any other kind of node, and nothing on an aggregate no pass wrote a number for, which
/// is every aggregate over a table nobody counted.
fn sized(plan: &Plan, node: NodeRef) -> String {
    let Node::Aggregate { index, .. } = *plan.node(node) else { return String::new() };
    let mut said = Vec::new();
    if let Some(groups) = plan.presized(index) {
        said.push(format!("room for {} groups", commas(groups)));
    }
    // After the size and not instead of it, because the operator keeps both: the array is the index
    // and the buckets are still the proof, per the last section of `crate::dense`. A line that said
    // only one of the two would read as the other one having been declined.
    if let Some((low, values)) = plan.dense(index) {
        said.push(format!("addressed directly over {} values from {low}", commas(values)));
    }
    if plan.clustered(index) {
        said.push("groups closed in key order".to_owned());
    }
    if said.is_empty() { String::new() } else { format!(" [{}]", said.join(", ")) }
}

/// Which join algorithm this node is, and why the other one was not, as a bracket on its line.
///
/// `spec/graph/06-the-optimizer.md` section 6.7 asks for exactly this, and says why: a hash join
/// that is slow is visibly a hash join, and a link that was not read because a projection two nodes
/// up dropped the row id looks from the outside exactly like a link that was never built. Without
/// the reason in the plan output nobody finds the difference.
///
/// Nothing at all on every other kind of node, and nothing on a join in a session that has declared
/// no relationships. A reader of a plan over a database with no graph in it would otherwise get the
/// same sentence on every join of every query, which is the kind of clause people stop reading.
fn chosen(plan: &Plan, node: NodeRef, context: &Context) -> String {
    if context.links().is_empty() {
        return String::new();
    }
    let Some(why) = crate::link::why(plan, node, context) else {
        return String::new();
    };
    // Which algorithm is read off the node and not off the reason, because the node is what ran. A
    // plain join whose reason says the link should have been read is a plan the rewrite never saw,
    // which happens when somebody turned it off, and printing "reads the link" over a hash join
    // because the rule would have chosen one is the plan output lying about the plan.
    let taken = matches!(plan.node(node), Node::LinkJoin { .. });
    match (taken, why.chosen()) {
        (true, _) => format!(" [reads the link, because {why}]"),
        (false, false) => format!(" [builds a hash table, because {why}]"),
        (false, true) => " [builds a hash table, because the link_join rewrite is off]".to_owned(),
    }
}

/// What one operator did, as it goes on the end of its line.
///
/// `filtered` puts the filter back into the sentence for a scan that applied one. The rows on such a
/// line are the rows that came out of the filter above it, and a count that is smaller than the
/// exact count on the same line reads as a wrong count until it says which of the two it is.
fn actually(measured: &Document, id: OperatorRef, filtered: bool) -> String {
    let Some(operator) = row(measured, id) else {
        return "  [not measured]".to_owned();
    };
    let held = operator.memory.high_water;
    let memory = if held == 0 { String::new() } else { format!(", {} held", bytes(held)) };
    // The fall backs are on the operator line rather than only in the totals, because the number
    // is only worth having if a reader can see which node it belongs to without counting rows.
    let slow = match operator.fallbacks.worst() {
        None => String::new(),
        Some((cause, _)) => {
            format!(", {} fell back, most of it {}", operator.fallbacks.total(), cause.name())
        }
    };
    let after = if filtered { " after the filter above" } else { "" };
    // A join has two inputs and one line, so its rows are the driving side alone and the line says
    // nothing about the side the whole shape of the query turns on. Only a join has this, and a
    // join that ran the nested loop is the one thing a reader of a slow plan wants to see first.
    let joined = match &operator.joined {
        None => String::new(),
        Some(joined) => {
            format!(", {} over {} build rows", joined.algorithm.name(), joined.build_rows)
        }
    };
    // A scan that pruned nothing says nothing, because a reader who sees "0 of 5861 parts skipped"
    // on every scan of every query stops reading the clause. A scan that pruned something is the
    // whole reason this number is on the line, since a predicate that prunes and a predicate that
    // matches nothing produce the same row count and want different work.
    let parts = operator.parts_read.saturating_add(operator.parts_pruned);
    let skipped = if operator.parts_pruned == 0 {
        String::new()
    } else {
        format!(", {} of {parts} parts skipped", operator.parts_pruned)
    };
    // The reduction goes next to the parts it skipped, since those are where most of what it saved
    // shows. A reduction that stopped says so in words, because its row count is the whole table
    // and a reader would otherwise take it for a set that happened to hold every row.
    let reduced = match &operator.reduced {
        None => String::new(),
        Some(reduced) if reduced.stopped => {
            ", link reduction stopped after a third of the rows removed nothing".to_owned()
        }
        Some(reduced) if reduced.by_key => {
            format!(", key map kept {} of {} parent keys", reduced.kept, reduced.rows)
        }
        Some(reduced) => format!(", link kept {} of {} rows", reduced.kept, reduced.rows),
    };
    // Both clocks, named, because one number here was read as the other three times. The wall
    // figure is the operator's elapsed time summed over its instances, so on a plan that runs eight
    // ways it can exceed the whole statement's CPU and is not a share of anything. The CPU figure is
    // the thread clock, it is only charged under `EXPLAIN ANALYZE` or `enable_profiling`, and it is
    // the one that divides into the total at the foot of the plan. The pipeline rows below have
    // printed both since they were written.
    let spent = if operator.cpu_ns == 0 {
        duration(operator.wall_ns) + " wall"
    } else {
        format!("{} wall, {} cpu", duration(operator.wall_ns), duration(operator.cpu_ns))
    };
    format!(
        "  [{} rows{after}, {spent}{joined}{skipped}{reduced}{memory}{slow}]",
        operator.rows_out
    )
}

/// The operator row with this id.
fn row(measured: &Document, id: OperatorRef) -> Option<&Operator> {
    measured.operators.iter().find(|operator| operator.id == id)
}

/// The pipelines and what each of them waits for.
///
/// Printed even when there is only one, because a reader who sees no section cannot tell a plan
/// that does not break from a build of `EXPLAIN` that does not say.
fn write_pipelines(shape: &Shape, measured: Option<&Document>, out: &mut String) {
    let _ = writeln!(out, "\nPipelines");
    for pipeline in shape.all() {
        let waits = shape.waits_for(pipeline);
        let waiting = if waits.is_empty() {
            "waits for nothing".to_owned()
        } else {
            format!("waits for {}", listed(waits))
        };
        let root = if pipeline == ROOT { ", and the answer comes out of it" } else { "" };
        let took = measured
            .and_then(|measured| measured.pipelines.iter().find(|row| row.id == pipeline))
            .map(|row| format!("  [{} wall, {} cpu]", duration(row.wall_ns), duration(row.cpu_ns)))
            .unwrap_or_default();
        let _ = writeln!(out, "  pipeline {pipeline} {waiting}{root}{took}");
    }
}

/// How many numbers were read for each of the three uses, and what class each read got.
///
/// Three histograms rather than one, because the class that matters depends on the use. Half the
/// decisions being guesses is a planner with thin statistics and a slow query at the end of it,
/// and one enable on a guess would be a bug the class rule is there to make impossible. A single
/// count could not tell those apart.
#[derive(Debug, Clone, Copy, Default)]
struct Reads {
    answer: Classes,
    enable: Classes,
    decide: Classes,
}

impl Reads {
    /// Counts one read made for that use.
    fn record(&mut self, use_: Use, stat: &Stat<u64>) {
        match use_ {
            Use::Answer => self.answer.record(stat),
            Use::Enable => self.enable.record(stat),
            Use::Decide => self.decide.record(stat),
        }
    }

    /// The histogram for one use.
    const fn of(self, use_: Use) -> Classes {
        match use_ {
            Use::Answer => self.answer,
            Use::Enable => self.enable,
            Use::Decide => self.decide,
        }
    }
}

/// Every statistic this plan was built out of, counted by what it was read for.
///
/// One read per operator and not per node, the same rule [`record_estimates`] counts by and for the
/// same reason: a node the plan folded away is not a decision anybody acted on.
fn reads(plan: &Plan, facts: &Facts, shape: &Shape) -> Reads {
    let mut reads = Reads::default();
    let mut distincts = Vec::new();
    for node in 0..u32::try_from(plan.node_count()).unwrap_or(u32::MAX) {
        if shape.operator_of(node).is_some() {
            distincts.clear();
            let rows = rows_stat_into(plan, node, facts, &mut distincts);
            reads.record(CARDINALITY, &rows);
            // The distinct counts a filter's selectivity and a join's cardinality read on the way to
            // that number. They never appear on a plan line, so this section is the only place a
            // reader can see that they were read at all, and whether anybody had one.
            for distinct in &distincts {
                reads.record(DISTINCT, distinct);
            }
        }
    }
    reads
}

/// What tells a node's rows apart, as a bracket on its line.
///
/// Printed beside the cardinality because the two answer the same question from opposite ends. The
/// estimate says roughly how many rows there are and this says what makes any two of them
/// different, and the second one is the one an enabling rewrite is allowed to read.
///
/// Nothing at all when nothing is known, which is most leaves today, because a bracket reading
/// `[no key]` on every scan of every plan is noise that hides the lines that do say something. The
/// point of printing this is P1's note that a rewrite which did not fire because an analysis lost a
/// property two nodes up is invisible otherwise: a reader follows the key up the tree to the node
/// where it stops, and a column of empty brackets does not help them do that.
fn tells_apart(keys: Option<&Keys>) -> String {
    let Some(keys) = keys else { return String::new() };
    let mut said = Vec::new();
    if keys.at_most_one_row() {
        said.push("at most one row".to_owned());
    } else {
        let sets: Vec<String> = keys
            .sets()
            .iter()
            .map(|set| set.iter().map(column_name).collect::<Vec<_>>().join(" "))
            .collect();
        if !sets.is_empty() {
            said.push(format!("key {}", among(&sets, "or")));
        } else if keys.row() {
            said.push("key the whole row".to_owned());
        }
    }
    let fixed: Vec<String> = keys.constants().map(|column| column_name(&column)).collect();
    if !fixed.is_empty() {
        said.push(format!("{} fixed", among(&fixed, "and")));
    }
    if said.is_empty() { String::new() } else { format!(" [{}]", said.join(", ")) }
}

/// A column binding the way the rest of a plan prints one.
fn column_name(column: &ColumnBinding) -> String {
    format!("#{}.{}", column.table, column.column)
}

/// What the planner knew, which is the section `EXPLAIN (STATISTICS)` is asked for.
///
/// A line per use that happened and one line for the uses that did not, rather than three lines of
/// zeroes. The uses that did not happen are named instead of being left out, because the line
/// saying nothing was read to enable is the reassuring half of this section and a reader cannot get
/// it from an absence.
///
/// Under `ANALYZE` the same section then says how far each class turned out to be from the rows the
/// run produced, which is the other half of the measurement and the only half that can tell a good
/// source from a bad one. The class histogram says how much of the plan rested on knowledge and the
/// q-error says whether that knowledge was right, and `spec/stats/09-measurement.md` section 9.5
/// asks for both because either on its own can look fine while the other is a disaster.
fn write_statistics(reads: Reads, measured: Option<&Document>, out: &mut String) {
    let _ = writeln!(out, "\nStatistics");
    let mut silent = Vec::new();
    for use_ in [Use::Answer, Use::Enable, Use::Decide] {
        let classes = reads.of(use_);
        if classes.total() == 0 {
            silent.push(format!("to {}", use_.name()));
            continue;
        }
        let share = classes.known_share() * 100.0;
        let _ = writeln!(
            out,
            "  {} read to {use_}: {classes}, {share:.0}% of them with a number behind them",
            classes.total()
        );
    }
    if !silent.is_empty() {
        let _ = writeln!(out, "  nothing was read {}", among(&silent, "or"));
    }
    write_q_errors(measured, out);
}

/// How far the cardinalities were from the rows, by class, which only a run can say.
///
/// Nothing at all without a run, rather than a row of zeroes, because a plan that was printed and
/// not executed has no truth to be measured against and a section of empty buckets would read as if
/// it did and every estimate was perfect.
///
/// A class with nothing in it is left out for the same reason the buckets are. The exact line is the
/// one to read first, and it wants reading in one direction. An operator that produced more rows
/// than an exact count said exist is a number that claimed to have been counted and was not, and the
/// warnings below say that again in stronger words. An operator that produced fewer is usually
/// execution doing its job: a limit stopped the pipeline, the scan applied a filter itself, or a
/// hash join handed the scan under its driving side the key filter of the side that finished first,
/// which on TPC-H q12 takes the scan of `orders` from a million and a half rows to forty four
/// thousand. Both land in the same bucket here, so an exact line that is not all at one is a
/// question to go and answer rather than an answer.
fn write_q_errors(measured: Option<&Document>, out: &mut String) {
    let Some(measured) = measured else { return };
    let errors = measured.q_errors();
    if errors.total() == 0 {
        return;
    }
    let _ = writeln!(
        out,
        "  q-error against the rows the run produced, {} measured",
        commas(errors.total())
    );
    for (class, spread) in errors.named() {
        if spread.total() == 0 {
            continue;
        }
        let _ = writeln!(out, "    {class} {}: {spread}", commas(spread.total()));
    }
}

/// The whole query's numbers, and anything the document has to warn about them.
fn write_totals(measured: &Document, out: &mut String) {
    let timing = &measured.timing;
    let _ = writeln!(out, "\nTotals");
    // Planning gets its own figure rather than being folded into the build, because the build is
    // one walk over a finished plan and the planning is every pass that decided what the plan was.
    // A query whose optimizer costs more than its execution is a query the optimizer made worse,
    // and this line is where that is visible without anybody going looking for it.
    let planning =
        timing.parse_ns.saturating_add(timing.bind_ns).saturating_add(timing.optimize_ns);
    let _ = writeln!(
        out,
        "  {} planning, {} building the tree, {} running it, {} in all",
        duration(planning),
        duration(timing.physical_ns),
        duration(timing.execute_ns),
        duration(timing.total_ns)
    );
    let _ = writeln!(
        out,
        "  {} of cpu, {} held at the peak",
        duration(measured.resource.cpu_ns),
        bytes(measured.resource.peak_bytes)
    );
    let warnings = measured.warnings();
    if !warnings.is_empty() {
        let _ = writeln!(out, "\nWarnings");
        for warning in &warnings {
            let _ = writeln!(out, "  {warning}");
        }
    }
}

/// A duration, in whichever unit a person would say it in.
///
/// Three significant figures and no more, because the fourth is noise on any measurement this is
/// printing and a reader who sees it starts believing it.
fn duration(ns: u64) -> String {
    match ns {
        0 => "0s".to_owned(),
        1..1_000 => format!("{ns}ns"),
        1_000..1_000_000 => format!("{:.3}us", ns as f64 / 1_000.0),
        1_000_000..1_000_000_000 => format!("{:.3}ms", ns as f64 / 1_000_000.0),
        _ => format!("{:.3}s", ns as f64 / 1_000_000_000.0),
    }
}

/// A byte count, in whichever unit a person would say it in.
fn bytes(count: u64) -> String {
    match count {
        0..1024 => format!("{count} bytes"),
        1024..1_048_576 => format!("{:.1} KiB", count as f64 / 1024.0),
        1_048_576..1_073_741_824 => format!("{:.1} MiB", count as f64 / 1_048_576.0),
        _ => format!("{:.1} GiB", count as f64 / 1_073_741_824.0),
    }
}

/// What is running at every seam that has something to choose between.
///
/// A seam with no registry is not listed one line at a time. There are twenty seven of them and
/// listing every one on every `EXPLAIN` would bury the plan under a table that says the same thing
/// every time, so the count is given and `rudb_strategies()` is where the list is.
fn write_seams(seams: Seams<'_>, out: &mut String) {
    let _ = writeln!(out, "\nSeams");
    let mut printed = 0;
    for seam in SeamId::ALL {
        if let Some((chosen, _)) = seams.chosen(*seam) {
            let _ = writeln!(out, "  {} = {chosen}", seam.name());
            printed += 1;
        }
    }
    let unregistered = SeamId::ALL.len() - printed;
    if unregistered > 0 {
        let _ = writeln!(
            out,
            "  {unregistered} seams have nothing registered and are running their reference implementation, see rudb_strategies()"
        );
    }
}

/// The pipeline the answer comes out of.
const ROOT: PipelineRef = 0;

/// A list of pipeline numbers, as somebody would say it out loud.
fn listed(pipelines: &[PipelineRef]) -> String {
    among(&pipelines.iter().map(u32::to_string).collect::<Vec<String>>(), "and")
}

/// A list of anything, as somebody would say it out loud.
///
/// The conjunction is given rather than always being `and`, because a list of things that did not
/// happen reads as `or` and a reader who is told two uses happened when neither did has been told
/// the opposite of the truth.
fn among(words: &[String], conjunction: &str) -> String {
    match words.split_last() {
        None => String::new(),
        Some((last, [])) => last.clone(),
        Some((last, rest)) => format!("{} {conjunction} {last}", rest.join(", ")),
    }
}

/// The children of a node, in the order they print.
fn children(node: &Node) -> Vec<NodeRef> {
    node.children().into_iter().flatten().collect()
}

#[cfg(test)]
mod tests {
    use rudb_common::stat::Class;
    use rudb_metrics::{Document, Operator};
    use rudb_plan::Plan;
    use rudb_seam::{Registries, Settings};

    use super::{Context, Seams, Shape, Statistics, explain, explain_with, record_estimates};
    use crate::estimate::Facts;

    fn parsed(text: &str) -> Plan {
        Plan::parse(text).unwrap_or_else(|error| panic!("{text} did not parse: {error}"))
    }

    /// A planner that knows how large those tables are and nothing else.
    fn knowing(tables: &[(&str, u64)]) -> Context {
        let mut facts = Facts::new();
        for (table, count) in tables {
            facts.record("memory", "main", table, *count);
        }
        let mut context = Context::new();
        context.measure(std::sync::Arc::new(facts));
        context
    }

    fn printed(text: &str, tables: &[(&str, u64)]) -> String {
        explain(&parsed(text), &knowing(tables))
    }

    /// The same thing with the statistics asked for, which is `EXPLAIN (STATISTICS)`.
    fn asked(text: &str, tables: &[(&str, u64)]) -> String {
        let settings = Settings::new();
        let registries = Registries::new();
        explain_with(
            &parsed(text),
            &knowing(tables),
            Seams::new(&settings, &registries),
            Statistics::Asked,
        )
    }

    /// The tree, without the sections under it, which is the part most tests are about.
    fn tree(out: &str) -> Vec<&str> {
        out.lines().take_while(|line| !line.is_empty()).collect()
    }

    #[test]
    fn every_operator_gets_a_line_with_its_own_estimate_on_it() {
        let out = printed(
            concat!(
                "Filter (#0.0::INTEGER > 1::INTEGER)::BOOLEAN\n",
                "  Get memory.main.t AS t #0 [a::INTEGER]\n",
            ),
            &[("t", 1000)],
        );
        // The scan says exact because the catalog counted those rows, and the filter above it says
        // estimated because the fifth it took off them is a constant somebody picked. A reader
        // deciding whether to trust the number wants to be told which of the two it is.
        assert_eq!(
            tree(&out).join("\n"),
            concat!(
                "Filter (#0.0::INTEGER > 1::INTEGER)::BOOLEAN  [~200 rows estimated from default] \
                 [pipeline 0] [reference]\n",
                "  Get memory.main.t AS t #0 [a::INTEGER]  [1000 rows exact from row count] [pipeline 0] \
                 [reference]",
            )
        );
    }

    #[test]
    fn a_plain_explain_says_nothing_about_uses_and_asking_for_the_statistics_says_it_on_every_line()
    {
        let plan = concat!(
            "Filter (#0.0::INTEGER > 1::INTEGER)::BOOLEAN\n",
            "  Get memory.main.t AS t #0 [a::INTEGER]\n",
        );
        let quiet = printed(plan, &[("t", 1000)]);
        assert!(!quiet.contains("read to"), "{quiet}");
        assert!(!quiet.contains("\nStatistics\n"), "{quiet}");

        // Every number in a plan is read to choose between plans that produce the same rows, so
        // every line says decide. A line that said enable over a guess would be a bug, and the
        // point of printing the use is that it would be a visible one.
        let out = asked(plan, &[("t", 1000)]);
        for line in tree(&out) {
            assert!(line.contains(", read to decide]"), "{line}");
        }
    }

    /// An aggregate with whatever the two sizing passes decided already written onto it.
    ///
    /// Written by hand rather than by running the passes, because what is being tested here is what
    /// the line says about a decision and not which decision was made. The passes have their own
    /// tests for the second half, and they need a store with bounds in it to make one at all.
    fn aggregate(room: Option<u64>, array: Option<(i128, u64)>) -> Plan {
        let text = concat!(
            "Aggregate #1 groups=[#0.0::INTEGER] aggregates=[]\n",
            "  Get memory.main.t AS t #0 [a::INTEGER]\n",
        );
        let mut plan = parsed(text);
        if let Some(groups) = room {
            plan.presize(1, groups);
        }
        if let Some((low, values)) = array {
            plan.densify(1, low, values);
        }
        plan
    }

    #[test]
    fn an_aggregate_says_how_much_room_it_asked_for_and_how_it_finds_a_group() {
        let out =
            explain(&aggregate(Some(120_000), Some((100, 1_000_000))), &knowing(&[("t", 10)]));
        let first = tree(&out)[0];
        // Both, in the order the two passes run, because the operator ends up with both: the array
        // is how a row finds its slot and the buckets it sized are still where the group lives.
        assert!(first.contains("[room for 120,000 groups, addressed directly"), "{first}");
        assert!(first.contains("over 1,000,000 values from 100]"), "{first}");
    }

    #[test]
    fn an_aggregate_no_pass_wrote_a_number_for_says_nothing_extra_at_all() {
        // Which is every aggregate over a table nobody counted, so it is the common case and the one
        // a plan reader sees. A clause here saying no statistic was available would be a second
        // sentence on every group by of every query for a question nobody asked.
        let out = explain(&aggregate(None, None), &knowing(&[("t", 10)]));
        let first = tree(&out)[0];
        assert!(!first.contains("room for"), "{first}");
        assert!(!first.contains("addressed"), "{first}");
    }

    #[test]
    fn the_statistics_section_counts_the_classes_and_says_which_uses_never_happened() {
        let out = asked(
            concat!(
                "Filter (#0.0::INTEGER > 1::INTEGER)::BOOLEAN\n",
                "  Get memory.main.t AS t #0 [a::INTEGER]\n",
            ),
            &[("t", 1000)],
        );
        assert!(out.contains("\nStatistics\n"), "{out}");
        // Two operators, so two reads: the scan off a counted row count and the filter off a
        // constant. Both had a number behind them, which is what the share is counting.
        assert!(
            out.contains(
                "  2 read to decide: exact 1, certified 0, estimated 1, unknown 0, \
                 100% of them with a number behind them\n"
            ),
            "{out}"
        );
        // The uses that did not happen are said out loud. An absence would read as an oversight,
        // and the whole value of this line is that nobody licensed a rewrite off a guess.
        assert!(out.contains("  nothing was read to answer or to enable\n"), "{out}");
    }

    #[test]
    fn a_plan_nobody_measured_says_so_in_the_section_as_well_as_on_the_lines() {
        let out = asked("Get memory.main.t AS t #0 [a::INTEGER]\n", &[]);
        assert!(out.contains("[rows unknown, read to decide]"), "{out}");
        assert!(
            out.contains(
                "  1 read to decide: exact 0, certified 0, estimated 0, unknown 1, \
                 0% of them with a number behind them\n"
            ),
            "{out}"
        );
    }

    #[test]
    fn an_operator_nobody_can_estimate_says_so_rather_than_saying_nothing() {
        // Blank would read as zero, and a reader who takes an unknown for an empty relation is the
        // reader this wording exists for.
        let out = printed("Get memory.main.t AS t #0 [a::INTEGER]\n", &[]);
        assert!(tree(&out)[0].contains("[rows unknown]"), "{out}");
    }

    #[test]
    fn the_document_gets_one_class_per_operator_and_the_number_that_goes_with_it() {
        // An aggregate rather than a filter over a table, because a filter over a table is applied
        // by the scan whatever its predicate says and the two nodes become one operator. The test
        // below is that case. This one wants two decisions that came out differently, and an
        // aggregate over a table is the shortest plan that has them: the table is counted and what
        // comes out of the grouping is estimated.
        let plan = parsed(concat!(
            "Aggregate #1 groups=[#0.0::INTEGER] aggregates=[count_star()::BIGINT]\n",
            "  Get memory.main.t AS t #0 [a::INTEGER]\n",
        ));
        let mut facts = Facts::new();
        facts.record("memory", "main", "t", 1000);
        let mut document = Document::new("select");
        let shape = Shape::of(&plan);
        for id in 0..shape.operators() {
            document.operators.push(Operator::new(id, 0, "operator"));
        }
        record_estimates(&plan, &facts, &mut document);

        // Two operators, so two decisions, and the histogram is a count of decisions rather than
        // of nodes or of rows.
        assert_eq!(document.estimates.total(), 2);
        assert_eq!(document.estimates.exact(), 1);
        assert_eq!(document.estimates.estimated(), 1);
        assert_eq!(document.estimates.unknown(), 0);
        // And the class on a row always agrees with the number on the same row, so a reader never
        // sees a class over a missing estimate or an estimate with no class on it.
        for operator in &document.operators {
            assert_eq!(operator.estimated_rows.is_some(), operator.estimate_class.is_some());
        }
    }

    #[test]
    fn a_filter_the_scan_applies_is_the_estimate_on_the_scans_row() {
        // The builder makes no operator for a filter a scan can apply, so the scan's row counts the
        // rows that came out of the filter. The estimate against those rows is the filter's, and the
        // table's exact count belongs to a question nobody asked here. Writing the count there
        // instead compares the size of the table with the size of what survived a predicate, and
        // every one of those comparisons reads as the catalog having counted wrong.
        let plan = parsed(concat!(
            "Filter (#0.0::INTEGER > 1::INTEGER)::BOOLEAN\n",
            "  Get memory.main.t AS t #0 [a::INTEGER]\n",
        ));
        let mut facts = Facts::new();
        facts.record("memory", "main", "t", 1000);
        let mut document = Document::new("select");
        let shape = Shape::of(&plan);
        let scan = shape.operator_of(1).expect("the scan is an operator");
        document.operators.push(Operator::new(scan, 0, "Scan"));
        record_estimates(&plan, &facts, &mut document);
        assert_eq!(document.operators[0].estimated_rows, Some(200));
        assert_eq!(document.operators[0].estimate_class, Some(Class::Estimated));
    }

    #[test]
    fn an_operator_nobody_estimated_is_counted_as_nobody_knowing_rather_than_left_out() {
        // The share the series moves is a share of every decision, so a decision made with nothing
        // has to be in the denominator. Dropping it would make a planner that knows less look
        // better than one that knows more.
        let plan = parsed("TableFunction range args=[] #0 [a::BIGINT]\n");
        let mut document = Document::new("select");
        let shape = Shape::of(&plan);
        for id in 0..shape.operators() {
            document.operators.push(Operator::new(id, 0, "operator"));
        }
        record_estimates(&plan, &Facts::new(), &mut document);

        assert_eq!(document.estimates.unknown(), document.estimates.total());
        assert!(document.estimates.total() > 0);
        assert_eq!(document.operators[0].estimated_rows, None);
        assert_eq!(document.operators[0].estimate_class, None);
    }

    #[test]
    fn both_sides_of_a_join_are_printed_under_it_and_each_carries_its_own_number() {
        let out = printed(
            concat!(
                "Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]\n",
                "  Get memory.main.small AS small #0 [a::INTEGER]\n",
                "  Get memory.main.big AS big #1 [a::INTEGER]\n",
            ),
            &[("small", 10), ("big", 5000)],
        );
        let lines = tree(&out);
        assert_eq!(lines.len(), 3, "{out}");
        assert!(lines[0].contains("[~5000 rows estimated from default]"), "{out}");
        assert!(
            lines[1].contains("small") && lines[1].contains("[10 rows exact from row count]"),
            "{out}"
        );
        assert!(
            lines[2].contains("big") && lines[2].contains("[5000 rows exact from row count]"),
            "{out}"
        );
        // Indented by depth, so the shape of the tree survives being flattened into lines.
        assert!(lines[1].starts_with("  Get"), "{out}");
    }

    #[test]
    fn a_session_with_no_relationships_says_nothing_about_links_on_any_join() {
        // The clause is worth having because it is rare. A sentence about links on every join of
        // every query over a database with no graph in it is a sentence people stop reading, and
        // then they stop reading it on the one plan where it says something.
        let out = printed(
            concat!(
                "Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]\n",
                "  Get memory.main.l AS l #0 [a::INTEGER]\n",
                "  Get memory.main.r AS r #1 [a::INTEGER]\n",
            ),
            &[("l", 10), ("r", 10)],
        );
        assert!(!out.contains("hash table"), "{out}");
        assert!(!out.contains("reads the link"), "{out}");
    }

    #[test]
    fn a_join_in_a_session_that_has_relationships_says_which_algorithm_and_why() {
        let mut context = knowing(&[("lineitem", 6_000_000), ("orders", 1_500_000)]);
        context.relate(std::sync::Arc::new(vec![crate::link::Linked::built(
            "lineitem",
            "l_orderkey",
            "orders",
            "o_orderkey",
        )]));
        let mut plan = parsed(concat!(
            "Project #2 [#0.0::BIGINT AS k]\n",
            "  Join INNER on=[(#0.0::BIGINT = #1.0::BIGINT)::BOOLEAN]\n",
            "    Get memory.main.lineitem AS lineitem #0 [l_orderkey::BIGINT]\n",
            "    Get memory.main.orders AS orders #1 [o_orderkey::BIGINT]\n",
        ));
        // This prints a plan rather than optimizing one, so the join is still a join over a
        // relationship the rule would have taken. Which is the case the wording is careful about:
        // the algorithm comes off the node, so the line says hash join and blames the rewrite
        // rather than claiming a link was read.
        let out = explain(&plan, &context);
        assert!(
            out.contains("[builds a hash table, because the link_join rewrite is off]"),
            "{out}"
        );
        // And the clause is on the join's line and on no other, because the question is about a
        // join and a scan that answered it would be answering about its parent.
        assert_eq!(
            tree(&out).iter().filter(|line| line.contains("hash table")).count(),
            1,
            "{out}"
        );

        // Then the same plan after the rewrite has had it, which is what an ordinary `EXPLAIN`
        // prints. The reason is the bullet of section 6.4 that took it rather than a repeat of the
        // node's own name.
        crate::pass::Pass::run(&crate::link::LinkJoinRewrite, &mut plan, &context)
            .expect("the pass does not fail");
        let out = explain(&plan, &context);
        assert!(
            out.contains(
                "[reads the link, because the parent does not fit in cache and its projection \
                 is 8 bytes, which is under 32]"
            ),
            "{out}"
        );
    }

    #[test]
    fn a_plan_that_does_not_break_is_one_pipeline_and_says_so() {
        let out = printed("Get memory.main.t AS t #0 [a::INTEGER]\n", &[("t", 4)]);
        assert!(out.contains("[pipeline 0]"), "{out}");
        assert!(
            out.contains("  pipeline 0 waits for nothing, and the answer comes out of it"),
            "{out}"
        );
    }

    #[test]
    fn a_sort_prints_the_pipeline_it_ends_and_the_edge_above_it() {
        let out = printed(
            concat!(
                "Sort [#0.0::INTEGER ASC NULLS LAST]\n",
                "  Get memory.main.t AS t #0 [a::INTEGER]\n",
            ),
            &[("t", 100)],
        );
        let lines = tree(&out);
        assert!(lines[0].contains("[pipeline 1]"), "the sort ends the one below it: {out}");
        assert!(lines[1].contains("[pipeline 1]"), "{out}");
        assert!(out.contains("  pipeline 0 waits for 1"), "{out}");
        assert!(out.contains("  pipeline 1 waits for nothing"), "{out}");
    }

    #[test]
    fn a_join_prints_three_pipelines_in_the_order_they_have_to_run() {
        let out = printed(
            concat!(
                "Join INNER on=[(#0.0::INTEGER = #1.0::INTEGER)::BOOLEAN]\n",
                "  Get memory.main.l AS l #0 [a::INTEGER]\n",
                "  Get memory.main.r AS r #1 [a::INTEGER]\n",
            ),
            &[("l", 10), ("r", 10)],
        );
        let lines = tree(&out);
        assert!(lines[0].contains("[pipeline 2]"), "{out}");
        assert!(lines[1].contains("[pipeline 2]"), "the probing side: {out}");
        assert!(lines[2].contains("[pipeline 1]"), "the gathered side runs first: {out}");
        assert!(out.contains("  pipeline 0 waits for 2"), "{out}");
        assert!(out.contains("  pipeline 2 waits for 1"), "{out}");
    }

    #[test]
    fn with_nothing_registered_the_seam_section_says_what_that_means() {
        let out = printed("Get memory.main.t AS t #0 [a::INTEGER]\n", &[("t", 4)]);
        assert!(out.contains("\nSeams\n"), "{out}");
        assert!(
            out.contains(
                "  27 seams have nothing registered and are running their reference implementation"
            ),
            "{out}"
        );
    }

    #[test]
    fn a_hint_that_pins_a_seam_nobody_has_registered_changes_nothing_that_prints() {
        // The settings are carried all the way down here, so the section has to be the registries
        // and the settings together rather than either one alone. Today the registries are empty,
        // so a pin has nothing to pin and the print says the same thing.
        let mut settings = Settings::new();
        settings.pin(rudb_seam::SeamId::Sort, "merge");
        let registries = Registries::new();
        let plan = parsed(
            "Sort [#0.0::INTEGER ASC NULLS LAST]\n  Get memory.main.t AS t #0 [a::INTEGER]\n",
        );
        let out = explain_with(
            &plan,
            &knowing(&[]),
            Seams::new(&settings, &registries),
            Statistics::NotAsked,
        );
        assert!(out.contains("[reference]"), "{out}");
        assert!(!out.contains("sort = "), "{out}");
    }
}