Skip to main content

rudb_native/
graph.rs

1//! Building a table's graph sections from the table's own columns.
2//!
3//! This is where the two halves meet. `rudb-graph` at rank 5 knows what a key map is and knows
4//! nothing about a file; the rest of this crate knows how to put an opaque payload in a file and
5//! nothing about what one means. Neither of them can build a key map for a real table, because
6//! doing that means reading a column back, so it happens here, in the crate that is allowed to see
7//! both.
8//!
9//! Everything here obeys spec/graph/03-the-file-format.md section 3.1. A column that cannot be
10//! mapped is a column with no key map, not an error at open; a section that is stale, torn, or of a
11//! form this build does not know is a section that is not there. That is why [`key_map`] answers
12//! with an [`Option`] and not a [`Result`]: there is no failure it could report that is not
13//! answered by running the query the way it ran before the section existed.
14
15use std::collections::HashMap;
16use std::path::Path;
17use std::sync::{Arc, Mutex};
18use std::time::{Duration, Instant};
19
20use rudb_common::{LogicalType, Result, Value};
21use rudb_graph::{Adjacency, Degrees, Form, KeyMap, Keys, NO_PARENT, link, wire};
22use rudb_vector::Chunk;
23
24use crate::section::{self, Attachment};
25use crate::{Catalog, Reader, invalid, type_tag};
26
27/// One column of a committed table, scanned in `rid` order.
28///
29/// A `rid` is a row's position in append order, and the parts of a table are in append order, so a
30/// scan of the parts in order is a scan in `rid` order and there is nothing to look up. That is the
31/// whole of the correspondence and it is worth stating, because a build that read the parts in any
32/// other order would produce a map that resolved every key to the wrong row without failing.
33///
34/// Or two columns of it, when the key is a [`pair`]. Both are read from the same part in one call,
35/// so the two values of a row are the same row's.
36#[derive(Debug)]
37pub struct KeyColumn<'a> {
38    reader: &'a Reader,
39    columns: Vec<usize>,
40}
41
42impl<'a> KeyColumn<'a> {
43    /// Names a column of a table, or a [`pair`] of them, as the key of a relationship's side.
44    ///
45    /// # Errors
46    ///
47    /// If there is no such column, or if its type has no integer key form. `VARCHAR` is the second
48    /// of those today: section 2.2 says a string key is mapped through its dictionary codes rather
49    /// than its text, and the code path is not built yet. None of TPC-H's eight relationships needs
50    /// it, so it is refused by name rather than approximated.
51    pub fn new(reader: &'a Reader, key: usize) -> Result<Self> {
52        let fields = reader.table().fields();
53        let columns = columns_of(key);
54        for &column in &columns {
55            let Some(field) = fields.get(column) else {
56                return Err(invalid(&format!(
57                    "column {column} is past the {} of table {}",
58                    fields.len(),
59                    reader.table().name()
60                )));
61            };
62            if !mappable(&field.ty) {
63                return Err(invalid(&format!(
64                    "a key map over {} needs an integer key form, and {} has none",
65                    field.name, field.ty
66                )));
67            }
68        }
69        Ok(Self { reader, columns })
70    }
71}
72
73impl Keys for KeyColumn<'_> {
74    fn scan(&self, each: &mut dyn FnMut(Option<i128>) -> Result<()>) -> Result<()> {
75        for part in 0..self.reader.parts() {
76            let chunk = self.reader.read(part, &self.columns)?;
77            let first = chunk.column(0)?;
78            let second = if self.columns.len() == 2 { Some(chunk.column(1)?) } else { None };
79            for row in 0..chunk.len() {
80                let key = key_at(&chunk, first, 0, row)?;
81                let key = match second {
82                    None => key,
83                    Some(second) => match (key, key_at(&chunk, second, 1, row)?) {
84                        (Some(high), Some(low)) => Some(fold(high, low)?),
85                        // A composite with a null in it matches nothing, the way SQL compares it.
86                        _ => None,
87                    },
88                };
89                each(key)?;
90            }
91        }
92        Ok(())
93    }
94}
95
96/// Where a key over two columns sits among the column numbers.
97///
98/// A relationship's key is named by a number everywhere it is stored: the id of a key map or a link
99/// section, and the parent column in a link's binding. A key over one column is that column's index
100/// and always was. A key over two is this bit, with the two indexes packed under it, so the files
101/// written before there were two column keys read exactly as they did, and nothing that stores a
102/// key needs a second field for the rare key that has two columns. TPC-H has one of them,
103/// `partsupp(ps_partkey, ps_suppkey)`, which spec/graph/02-the-data-model.md section 2.3 names.
104const PAIR: usize = 1 << 31;
105
106/// How many bits each column index of a [`pair`] gets, which is room for 32,768 columns.
107const PAIR_BITS: u32 = 15;
108
109/// The number that names a key over these columns, or `None` for a list this cannot name.
110///
111/// One column is its own index. Two are a pair. More than two, or an index too large to pack, is a
112/// key nothing is built for, which section 3.1 says is a slower query and never a wrong one.
113#[must_use]
114pub fn key_of(columns: &[usize]) -> Option<usize> {
115    let fits = |column: usize| column < 1 << PAIR_BITS;
116    match *columns {
117        [column] if column < PAIR => Some(column),
118        [first, second] if fits(first) && fits(second) => Some(PAIR | first << PAIR_BITS | second),
119        _ => None,
120    }
121}
122
123/// The two columns of a pair key, first then second.
124#[must_use]
125pub fn pair(first: usize, second: usize) -> Option<usize> {
126    key_of(&[first, second])
127}
128
129/// The columns a key number names, which [`key_of`] made.
130#[must_use]
131pub fn columns_of(key: usize) -> Vec<usize> {
132    if key & PAIR == 0 {
133        return vec![key];
134    }
135    let mask = (1 << PAIR_BITS) - 1;
136    vec![(key >> PAIR_BITS) & mask, key & mask]
137}
138
139/// Two key values as one, with nothing lost.
140///
141/// Each value has to fit a 32 bit integer. The second moves up by 2^31 into `0..2^32` and the first
142/// is multiplied past that, so two different pairs never give the same number, which is the property
143/// a key map needs: a hash would be smaller and would also let two keys meet, and a key map has no
144/// second look at the row to tell them apart. The result fits an `i64`, because a key map's keys have
145/// to span no more than a `u64` does. A value outside that range is an error and the relationship
146/// gets no link, which section 3.1 says is a slower query and not a wrong one. TPC-H's keys are
147/// under two hundred million at scale factor 1000.
148fn fold(high: i128, low: i128) -> Result<i128> {
149    const SHIFT: i128 = 1 << 32;
150    let fits = |value: i128| i128::from(i32::MIN) <= value && value <= i128::from(i32::MAX);
151    if !fits(high) || !fits(low) {
152        return Err(invalid("a two column key holds a value too wide to fold into one key"));
153    }
154    Ok(high * SHIFT + (low - i128::from(i32::MIN)))
155}
156
157/// The type tag a key map over this key is stamped with, which is the column's own for one column.
158///
159/// A pair folds into an `i64`, so its map is stamped as a `BIGINT`, which is the type of the numbers
160/// in it. What keeps it from passing for a map over one column is its id, which no column has.
161fn key_tag(fields: &[rudb_common::Field], key: usize) -> Option<u8> {
162    match *columns_of(key) {
163        [column] => type_tag(&fields.get(column)?.ty).ok(),
164        [first, second] => {
165            fields.get(first)?;
166            fields.get(second)?;
167            type_tag(&LogicalType::BigInt).ok()
168        }
169        _ => None,
170    }
171}
172
173/// Whether a column of this type can be a key at all.
174fn mappable(ty: &LogicalType) -> bool {
175    matches!(
176        ty,
177        LogicalType::TinyInt
178            | LogicalType::SmallInt
179            | LogicalType::Integer
180            | LogicalType::BigInt
181            | LogicalType::HugeInt
182            | LogicalType::UTinyInt
183            | LogicalType::USmallInt
184            | LogicalType::UInteger
185            | LogicalType::UBigInt
186            | LogicalType::Date
187            | LogicalType::Decimal { .. }
188    )
189}
190
191/// One key out of a decoded part.
192///
193/// The fast answer first, because it covers the flat and dictionary forms and is a load. It hands
194/// back `None` for a null and for a form it cannot read, and those two are not the same thing at
195/// all: a null shifts every row after it and a value this could not read would shift nothing while
196/// silently becoming one. So the slow path settles which it was, and a value that is neither is an
197/// error rather than a null.
198fn key_at(
199    chunk: &Chunk,
200    values: &rudb_vector::Vector,
201    column: usize,
202    row: usize,
203) -> Result<Option<i128>> {
204    if let Some(key) = values.signed_at(row) {
205        return Ok(Some(key));
206    }
207    match chunk.value_at(row, column) {
208        Value::Null => Ok(None),
209        Value::TinyInt(key) => Ok(Some(i128::from(key))),
210        Value::SmallInt(key) => Ok(Some(i128::from(key))),
211        Value::Integer(key) | Value::Date(key) => Ok(Some(i128::from(key))),
212        Value::BigInt(key) | Value::Time(key) | Value::Timestamp(key) => Ok(Some(i128::from(key))),
213        Value::HugeInt(key) | Value::Decimal { unscaled: key, .. } => Ok(Some(key)),
214        Value::UTinyInt(key) => Ok(Some(i128::from(key))),
215        Value::USmallInt(key) => Ok(Some(i128::from(key))),
216        Value::UInteger(key) => Ok(Some(i128::from(key))),
217        Value::UBigInt(key) => Ok(Some(i128::from(key))),
218        other => Err(invalid(&format!("a key column holds {other}, which is not a key"))),
219    }
220}
221
222/// What building one key map cost and what it bought.
223///
224/// G1's exit measurement in spec/graph/10-milestones.md wants build time and bytes reported per
225/// table, so the build reports them rather than being timed from outside. The form is here because
226/// it is the number that explains the bytes: an identity map over fifteen million rows is the same
227/// size as one over five.
228#[derive(Debug, Clone, Copy)]
229pub struct Built {
230    /// Which column was mapped.
231    pub column: usize,
232    /// Which of the three forms the measurement chose.
233    pub form: Form,
234    /// Non-null keys in the column.
235    pub rows: u64,
236    /// Whether every key was distinct, which is section 2.3's verification and decides whether a
237    /// link may be built on this column at all.
238    pub distinct: bool,
239    /// What the map takes in the file, header included, or would have taken when it was not kept.
240    pub bytes: usize,
241    /// What the column it maps takes in the file, which is what the budget is a share of.
242    pub column_bytes: u64,
243    /// Whether the map was kept. False means it was built, measured, and found to cost more than
244    /// section 3.7 allows, so the file does not have it and the query plans as though key maps had
245    /// never been implemented.
246    pub built: bool,
247    /// How long the build took, the reading of the column included.
248    pub build: Duration,
249}
250
251/// Builds the key map for one column of a committed table.
252///
253/// # Errors
254///
255/// If the column cannot be read, is not a key type, or holds a value that is not a key.
256pub fn build_key_map(reader: &Reader, column: usize) -> Result<KeyMap> {
257    KeyMap::build_from(&KeyColumn::new(reader, column)?)
258}
259
260/// The share of a table's stored column bytes its graph sections are allowed to cost together.
261///
262/// Section 3.7. Ten percent, and the number matters less than the fact that there is one: a layer
263/// that can only make queries faster is a layer with no reason to stop, and this is the reason.
264/// What does not fit is not built, and the report says what it would have cost, so whether a larger
265/// budget would buy anything is a measurement rather than an argument. The `graph_budget` setting
266/// is what will move it, which is why the builder below takes it rather than reading this.
267pub const BUDGET_SHARE: u64 = 10;
268
269/// The share of a table's stored column bytes its adjacencies are allowed to cost together.
270///
271/// Section 3.7. Apart from `BUDGET_SHARE`, because an adjacency is a different kind of thing from
272/// a link or a key map. Those are about the size of the key column they answer for, and a tenth of
273/// the table holds several of them. An adjacency lists every child row once under its parent, so
274/// it costs a row id per child whatever the parent is: on SF1 `lineitem` that is 6 million ids of
275/// 23 bits, about 18 MB, which is more than the whole of the 10 percent share on its own. Out of the
276/// same share it can never be kept. A quarter holds two of them on `lineitem`, which is what the
277/// queries that filter `part` and `supplier` hard read, and the ranking below decides which two.
278pub const ADJACENCY_SHARE: u64 = 25;
279
280/// The share of a table's stored column bytes its key maps are allowed to cost together.
281///
282/// Section 3.7. Apart from `BUDGET_SHARE` for the same reason the adjacencies are: a key map is
283/// what every reduction into its table starts from, and out of one share with the table's own
284/// links it lost to them. On SF1 `orders` stored in date order the map over `o_orderkey` has to
285/// be the permuted form, a bitmap and a row id per key, 4.7 MB. The link from `orders` to
286/// `customer` already held 3.4 MB of the 10 percent, so the map was refused, and without it no
287/// join keyed on an order could reduce `lineitem` at all. A map over distinct keys costs at most a
288/// row id and a bit per row, which is a quarter or less of the columns of any table with more than
289/// its key in it.
290pub const KEY_MAP_SHARE: u64 = 25;
291
292/// The size below which a table's graph sections always fit, whatever the share works out to.
293///
294/// A percentage of the stored bytes is the right rule for a structure whose size is worth arguing
295/// about, and it stops making sense at the bottom. An identity key map is forty bytes on a table of
296/// any size, and a key column of sequential integers is a constant delta, which encodes to almost
297/// nothing: ten percent of almost nothing is less than forty bytes, so the pure rule throws away
298/// the cheapest structure in the system for being expensive. What it would be measuring there is
299/// how well the column compressed, not what the cache costs.
300///
301/// Sixty four kilobytes is the point below which no answer to "should this be kept" is worth the
302/// cost of asking. It is four pages, it is invisible next to any table the graph layer is for, and
303/// it leaves every budget decision that matters to the share above.
304pub const BUDGET_FLOOR: u64 = 64 * 1024;
305
306/// Builds a key map for each of these columns and attaches them all in one commit.
307///
308/// One commit and not one each, because a checkpoint that built six maps and published six
309/// generations would be six chances to be interrupted halfway and six directories written where one
310/// would do.
311///
312/// # Errors
313///
314/// If the file cannot be opened, a column cannot be mapped, or the attach fails.
315pub fn build_key_maps(path: &Path, table: &str, columns: &[usize]) -> Result<Vec<Built>> {
316    build_key_maps_within(path, table, columns, KEY_MAP_SHARE)
317}
318
319/// The same, against a budget of `share` percent of the table's stored column bytes.
320///
321/// The budget is over the table and not over a column, because that is what section 3.7 says and
322/// because a per column rule would throw away the cheapest maps there are: an identity map is forty
323/// bytes whatever the table, and a narrow, well compressed key column can be smaller than four
324/// hundred. The sections already in the file that this call does not replace are counted as spent.
325///
326/// When the budget binds, the cheapest maps are admitted first. Section 3.7 orders by expected
327/// value, child rows over section bytes, and for a key map on its own the numerator is not yet
328/// known: nothing has declared a relationship over these columns, so no column is worth more than
329/// another and the ordering degenerates to the denominator. Cheapest first is that, and it is also
330/// the order that fits the most maps in the room there is. The forward link builder is where the
331/// numerator arrives.
332///
333/// # Errors
334///
335/// If the file cannot be opened, a column cannot be mapped, or the attach fails.
336pub fn build_key_maps_within(
337    path: &Path,
338    table: &str,
339    columns: &[usize],
340    share: u64,
341) -> Result<Vec<Built>> {
342    let reader = Catalog::open(path)?.table(table)?;
343    let column_bytes = reader.layout().columns_total();
344    let allowance = (column_bytes.saturating_mul(share) / 100).max(BUDGET_FLOOR);
345    let mut spent = held_bytes(&reader, columns)?;
346    let mut report = Vec::with_capacity(columns.len());
347    let mut payloads = Vec::with_capacity(columns.len());
348    for &column in columns {
349        let start = Instant::now();
350        let map = build_key_map(&reader, column)?;
351        let tag = key_tag(reader.table().fields(), column)
352            .ok_or_else(|| invalid("a key map over a column the table does not have"))?;
353        let payload = wire::encode(&map, tag)?;
354        report.push(Built {
355            column,
356            form: map.form(),
357            rows: map.observed().rows,
358            distinct: map.observed().distinct,
359            bytes: payload.bytes.len(),
360            column_bytes,
361            built: false,
362            build: start.elapsed(),
363        });
364        payloads.push((column, payload));
365    }
366    // Cheapest first, and the report keeps the order it was asked in, so the two are walked through
367    // an index rather than by sorting either of them.
368    let mut order = (0..payloads.len()).collect::<Vec<_>>();
369    order.sort_by_key(|&at| payloads[at].1.bytes.len());
370    let mut keep = vec![false; payloads.len()];
371    for at in order {
372        // Before the budget, because this is not a budget decision. A key map over a column whose
373        // key repeats cannot answer a rid for any of its keys, so keeping it would spend the
374        // table's allowance on something no join may read, and the report already says what it
375        // would have cost.
376        if !report[at].distinct {
377            continue;
378        }
379        let cost = payloads[at].1.bytes.len() as u64;
380        if spent.saturating_add(cost) <= allowance {
381            spent += cost;
382            keep[at] = true;
383            report[at].built = true;
384        }
385    }
386    // The reader holds the file open and the attach opens it again to write. Dropping it first is
387    // not required by any platform we build for, and it is done anyway so that the moment the
388    // file is being written is a moment nothing else in this function is reading it.
389    drop(reader);
390    // Every column that was asked for gets an entry, and a column whose map was not kept gets one
391    // with no bytes. That is section 3.7's budget record: what it would have cost is in the entry
392    // rather than in a payload, so `rudb_links()` reports a number instead of a silence and the
393    // file grows by fifty six bytes for the columns it decided against.
394    let attachments = payloads
395        .iter()
396        .zip(&keep)
397        .map(|((column, payload), &keep)| {
398            Ok(Attachment {
399                kind: *section::KEY_MAP,
400                id: u64::try_from(*column).map_err(|_| invalid("column index overflow"))?,
401                flags: payload.flags,
402                header_bytes: if keep { payload.header_bytes } else { cost(payload.bytes.len()) },
403                bytes: if keep { &payload.bytes } else { &[] },
404            })
405        })
406        .collect::<Result<Vec<_>>>()?;
407    crate::attach(path, table, &attachments)?;
408    Ok(report)
409}
410
411/// What the table's existing key maps cost, leaving out the ones this build is replacing.
412///
413/// Key maps only. The statistics layer has its own two percent per `spec/stats` section 3.8, and
414/// the links and the adjacencies have their own shares here, and a budget that counted another
415/// share's sections would be a budget the other one eats, which is the thing the shares being
416/// separate numbers exists to prevent.
417///
418/// Reading the extent tables is what this costs, which is one small read per section and not a read
419/// of a payload. A section whose extent table does not checksum is counted as nothing, because it
420/// is a section that is already not there.
421fn held_bytes(reader: &Reader, replacing: &[usize]) -> Result<u64> {
422    held_kind_bytes(reader, *section::KEY_MAP, replacing)
423}
424
425/// The key map this table carries for a column, when it carries one this build can use.
426///
427/// `None` covers every reason there is not one, and covering them all is the point rather than an
428/// omission. Section 3.1 says deleting every graph section changes no answer, only the time, so
429/// there is no reason to distinguish *no map was ever built* from *the map is stale*, *the payload
430/// does not checksum*, or *the form is one a later build invented*: the answer to all four is to
431/// run the query the way it ran before key maps existed. A caller that wants to know which it was
432/// reads the entry out of [`crate::Table::sections`], which is where `rudb_links()` will look.
433#[must_use]
434pub fn key_map(reader: &Reader, column: usize) -> Option<KeyMap> {
435    let table = reader.table();
436    let id = u64::try_from(column).ok()?;
437    let held = table
438        .sections()
439        .iter()
440        .find(|section| section.kind == *section::KEY_MAP && section.id == id)?;
441    if !held.usable(table.generation()) {
442        return None;
443    }
444    let (map, tag) = wire::decode(&reader.payload(held).ok()?).ok()?;
445    // A map built against a different type than the column now has is a map built for a table that
446    // is no longer this one. It should be unreachable, since changing a column's type rewrites the
447    // table and moves its generation, and it is checked rather than assumed because the cost of
448    // being wrong is every key resolving to a plausible wrong row.
449    if tag != key_tag(table.fields(), column)? {
450        return None;
451    }
452    Some(map)
453}
454
455/// The graph structures of one open table, each decoded the first time a plan asks for it and
456/// shared from then on.
457///
458/// Every join the graph layer reduces asks for the parent's key map, and a join over a relationship
459/// asks for the child's link or adjacency, each through its own operator. TPC-H q21 has four joins
460/// that reduce, and each read, checked and decoded the same `orders` key map and the same
461/// `lineitem` adjacency, which made the adjacency alone a third of the query's page faults. A
462/// table's sections do not change for as long as a reader is open, since a reader is over one
463/// generation of the table, so decoding one once per reader is decoding it once.
464///
465/// A link and an adjacency are kept by the parent they were checked against as well, the parent's
466/// name, column and generation, because the check of the binding at the front of the section is
467/// against those and a different parent is a different question. A `None` is kept too, since a
468/// section that is not there or does not check is not going to be there on the next ask.
469#[derive(Debug, Default)]
470pub(crate) struct Decoded {
471    key_maps: Mutex<HashMap<usize, Option<Arc<KeyMap>>>>,
472    links: Mutex<HashMap<Binding, Option<Arc<link::Link>>>>,
473    adjacencies: Mutex<HashMap<Binding, Option<Arc<Adjacency>>>>,
474}
475
476/// The child column and the parent's name, column and generation a structure was checked against.
477type Binding = (usize, String, usize, u64);
478
479fn binding_of(parent: &Reader, edge: &Edge) -> Binding {
480    (edge.child_column, edge.parent.clone(), edge.parent_column, parent.table().generation())
481}
482
483/// What `make` gives for `key`, made once while the lock is held, so that the workers of a plan
484/// that all ask at once read the section once between them.
485fn once<K: std::hash::Hash + Eq, T>(
486    held: &Mutex<HashMap<K, Option<Arc<T>>>>,
487    key: K,
488    make: impl FnOnce() -> Option<T>,
489) -> Option<Arc<T>> {
490    let mut held = held.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
491    held.entry(key).or_insert_with(|| make().map(Arc::new)).clone()
492}
493
494/// [`key_map`], decoded once per open table. See `Decoded`.
495#[must_use]
496pub fn shared_key_map(reader: &Reader, column: usize) -> Option<Arc<KeyMap>> {
497    once(&reader.graph.key_maps, column, || key_map(reader, column))
498}
499
500/// [`stored_link`], decoded once per open table and parent. See `Decoded`.
501#[must_use]
502pub fn shared_link(child: &Reader, parent: &Reader, edge: &Edge) -> Option<Arc<link::Link>> {
503    once(&child.graph.links, binding_of(parent, edge), || stored_link(child, parent, edge))
504}
505
506/// [`stored_adjacency`], decoded once per open table and parent. See `Decoded`.
507#[must_use]
508pub fn shared_adjacency(child: &Reader, parent: &Reader, edge: &Edge) -> Option<Arc<Adjacency>> {
509    once(&child.graph.adjacencies, binding_of(parent, edge), || {
510        stored_adjacency(child, parent, edge)
511    })
512}
513
514/// One relationship, with both sides resolved to a table and a column of it.
515///
516/// Names and not [`rudb_graph::Relationship`], because by the time a build runs the caller has
517/// already turned a declaration's column names into positions against the catalog, and doing it
518/// again here would be a second place for the two to disagree.
519#[derive(Debug, Clone)]
520pub struct Edge {
521    /// The many side, which is where the link is stored.
522    pub child: String,
523    /// Which column of it holds the key.
524    pub child_column: usize,
525    /// The one side, which is where the key map is.
526    pub parent: String,
527    /// Which column of it holds the key.
528    pub parent_column: usize,
529}
530
531/// What building one forward link cost and what it bought.
532#[derive(Debug, Clone)]
533pub struct BuiltLink {
534    /// The relationship this is a link for.
535    pub edge: Edge,
536    /// Which form section 3.4's measurement chose, or `None` when nothing was built.
537    pub form: Option<link::Form>,
538    /// Rows in the child table.
539    pub children: u64,
540    /// Rows in the parent table, or none when nothing was built.
541    pub parents: u64,
542    /// Children that found a parent. Below `children` means the foreign key is not total, which is
543    /// legal and is also what keeps the relationship out of the monotone form.
544    pub linked: u64,
545    /// What the link takes in the file, header included, or would have taken when it was not kept.
546    pub bytes: usize,
547    /// The stored column bytes of the child table, which is what section 3.7's budget is a share
548    /// of and what the size claim of section 9.1 is measured against.
549    pub table_bytes: u64,
550    /// What the build measured of the relationship's shape, or `None` when nothing was built.
551    ///
552    /// These ride along with the link rather than being computed for their own sake, because the
553    /// pass that resolves every child's parent is the pass that counts degrees. They are stored in
554    /// their own section and are what `rudb_links()` reports in its degree columns.
555    pub degrees: Option<Degrees>,
556    /// Whether it is in the file.
557    pub built: bool,
558    /// What the backward adjacency takes in the file or would have, and zero when the link is
559    /// monotone and answers that direction itself, or when there is no link.
560    pub adjacency_bytes: usize,
561    /// Whether the backward adjacency is in the file.
562    pub adjacency: bool,
563    /// Why not, when not. `None` when it is.
564    pub note: Option<String>,
565    /// How long the build took, the reading of the child column included.
566    pub build: Duration,
567}
568
569/// Builds a forward link for each relationship and attaches each child table's in one commit.
570///
571/// The parent's key map is the one the file holds when there is one, and otherwise one built here
572/// for the link and dropped once the link is written, so a key map the budget refused does not take
573/// the link with it. A parent key that is not unique is a note on the report rather than an
574/// error: the relationship is one the file does not accelerate, and by section 3.1 that changes no
575/// answer.
576///
577/// # Errors
578///
579/// If the file cannot be opened, a child key column cannot be read, or the attach fails.
580pub fn build_links(path: &Path, edges: &[Edge]) -> Result<Vec<BuiltLink>> {
581    build_links_within(path, edges, BUDGET_SHARE)
582}
583
584/// The same, against a budget of `share` percent of each child table's stored column bytes.
585///
586/// One commit per child table, for the reason [`build_key_maps`] commits once: a checkpoint that
587/// published a generation per section would be a chance to be interrupted per section.
588///
589/// The budget is where a link differs from a key map. Section 3.7 orders by expected value, child
590/// rows over section bytes, and for a link both numbers are in hand: the child rows are the rows
591/// the link would skip a hash table for. So this sorts by rows over bytes descending, which admits
592/// the monotone links first on any TPC-H sized file, because they are the ones with the most rows
593/// behind the fewest bytes.
594///
595/// # Errors
596///
597/// If the file cannot be opened, a child key column cannot be read, or the attach fails.
598pub fn build_links_within(path: &Path, edges: &[Edge], share: u64) -> Result<Vec<BuiltLink>> {
599    let mut tables: Vec<&str> = Vec::new();
600    for edge in edges {
601        if !tables.iter().any(|held| *held == edge.child) {
602            tables.push(&edge.child);
603        }
604    }
605    let mut report = Vec::with_capacity(edges.len());
606    for table in tables {
607        let mine = edges.iter().filter(|edge| edge.child == table).cloned().collect::<Vec<Edge>>();
608        report.extend(links_of_one_table(path, table, &mine, share)?);
609    }
610    Ok(report)
611}
612
613/// Every link stored in one child table, built and admitted and attached together.
614fn links_of_one_table(
615    path: &Path,
616    table: &str,
617    edges: &[Edge],
618    share: u64,
619) -> Result<Vec<BuiltLink>> {
620    let catalog = Catalog::open(path)?;
621    let child = catalog.table(table)?;
622    let column_bytes = child.layout().columns_total();
623    let allowance = (column_bytes.saturating_mul(share) / 100).max(BUDGET_FLOOR);
624    let replacing = edges.iter().map(|edge| edge.child_column).collect::<Vec<usize>>();
625    let index_allowance = (column_bytes.saturating_mul(ADJACENCY_SHARE) / 100).max(BUDGET_FLOOR);
626    let mut spent = held_kind_bytes(&child, *section::FORWARD_LINK, &replacing)?;
627    let mut indexed = held_kind_bytes(&child, *section::ADJACENCY, &replacing)?;
628    let mut report = Vec::with_capacity(edges.len());
629    let mut payloads: Vec<Option<Vec<u8>>> = Vec::with_capacity(edges.len());
630    let mut adjacencies: Vec<Option<Vec<u8>>> = Vec::with_capacity(edges.len());
631    for edge in edges {
632        let start = Instant::now();
633        match one_link(&catalog, &child, edge) {
634            Ok((built, bytes, adjacency)) => {
635                report.push(BuiltLink {
636                    build: start.elapsed(),
637                    table_bytes: column_bytes,
638                    ..built
639                });
640                payloads.push(Some(bytes));
641                adjacencies.push(adjacency);
642            }
643            Err(note) => {
644                report.push(BuiltLink {
645                    edge: edge.clone(),
646                    form: None,
647                    children: child.table().rows() as u64,
648                    parents: 0,
649                    linked: 0,
650                    bytes: 0,
651                    table_bytes: column_bytes,
652                    degrees: None,
653                    built: false,
654                    adjacency_bytes: 0,
655                    adjacency: false,
656                    note: Some(note),
657                    build: start.elapsed(),
658                });
659                payloads.push(None);
660                adjacencies.push(None);
661            }
662        }
663    }
664    // A link is candidate `at` and its adjacency is candidate `at` plus the number of edges, so one
665    // ranking covers both and each keeps its own place in the report.
666    let edge_count = report.len();
667    let mut order = (0..edge_count)
668        .filter(|at| payloads[*at].is_some())
669        .chain((0..edge_count).filter(|at| adjacencies[*at].is_some()).map(|at| at + edge_count))
670        .collect::<Vec<_>>();
671    // Most rows saved per byte first. What a link saves is the hash table the join would build
672    // without it, and a hash join builds its smaller side, so the rows saved are the smaller of the
673    // child and the parent. Counting the children alone ranks every link of one table by its size
674    // and nothing else, since they all have the same children. On `lineitem` that kept the link to
675    // `part`, which saves a table of 200,000 rows, over the one to `partsupp`, which saves 800,000
676    // and costs a tenth more. A link over no rows is worth nothing per byte and sorts last rather
677    // than dividing by zero.
678    //
679    // What an adjacency saves is different. It turns a reduction from a small set of parents into
680    // reading their children, where without it the scan tests every child row, so the rows it
681    // saves are the child rows. Adjacencies are paid for out of `ADJACENCY_SHARE` and links out of
682    // the table's allowance, so the two never compete for the same bytes, and among adjacencies of
683    // one table, which all save the same child rows, the smaller comes first.
684    let value = |at: usize| -> f64 {
685        if at < edge_count {
686            let bytes = report[at].bytes.max(1);
687            report[at].children.min(report[at].parents) as f64 / bytes as f64
688        } else {
689            let at = at - edge_count;
690            report[at].linked as f64 / report[at].adjacency_bytes.max(1) as f64
691        }
692    };
693    order.sort_by(|left, right| {
694        value(*right).partial_cmp(&value(*left)).unwrap_or(std::cmp::Ordering::Equal)
695    });
696    for at in order {
697        let (cost, link) = if at < edge_count {
698            (report[at].bytes as u64, true)
699        } else {
700            (report[at - edge_count].adjacency_bytes as u64, false)
701        };
702        let fits = if link {
703            spent.saturating_add(cost) <= allowance
704        } else {
705            indexed.saturating_add(cost) <= index_allowance
706        };
707        if fits && link {
708            spent += cost;
709        } else if fits {
710            indexed += cost;
711        }
712        match (link, fits) {
713            (true, true) => report[at].built = true,
714            (true, false) => {
715                report[at].note = Some(format!("over the budget of {allowance} bytes"));
716            }
717            (false, fits) => report[at - edge_count].adjacency = fits,
718        }
719    }
720    drop(child);
721    // The degree payloads are held here rather than built inside the loop below, because an
722    // attachment borrows its bytes and a temporary would not outlive the call.
723    let measured = report
724        .iter()
725        .filter(|built| built.built)
726        .filter_map(|built| {
727            let mut bytes = Vec::with_capacity(rudb_graph::degree::BYTES);
728            built.degrees.as_ref()?.write(&mut bytes);
729            Some((built.edge.child_column, bytes))
730        })
731        .collect::<Vec<_>>();
732    // A relationship whose link was built gets the link. One that was measured and then turned away
733    // gets an entry with no bytes, holding the form it would have taken and what it would have cost,
734    // which is section 3.7's budget record and is what exit criterion 3 of G3 reads. One that could
735    // not be built at all gets nothing, because there is no size to report: the note on the report
736    // is the whole of what is known about it.
737    let mut attachments = report
738        .iter()
739        .zip(&payloads)
740        .filter(|(_, payload)| payload.is_some())
741        .map(|(built, payload)| {
742            let bytes = payload.as_ref().expect("filtered to the measured");
743            Ok(Attachment {
744                kind: *section::FORWARD_LINK,
745                id: u64::try_from(built.edge.child_column)
746                    .map_err(|_| invalid("column index overflow"))?,
747                flags: built.form.map_or(0, |form| u32::from(form.tag())),
748                header_bytes: if built.built {
749                    u32::try_from(binding_bytes(&built.edge.parent))
750                        .map_err(|_| invalid("a parent name longer than a section header"))?
751                } else {
752                    cost(bytes.len())
753                },
754                bytes: if built.built { bytes } else { &[] },
755            })
756        })
757        .collect::<Result<Vec<_>>>()?;
758    // The same id as the link, so that a rebuild replaces both and a reader that wants the shape of
759    // a relationship it can resolve finds them the same way. The degrees are attached only for a
760    // link that was kept: on their own they would describe a relationship the file cannot follow,
761    // which is a planning hint for a plan that is not available.
762    // An adjacency stands without its link: a reduction through it starts from the parent's key
763    // map and ends at child rows, and never asks which parent a child has. One that was measured
764    // and refused gets a budget record, the same as a link.
765    for ((built, payload), adjacency) in report.iter().zip(&payloads).zip(&adjacencies) {
766        let (Some(_), Some(bytes)) = (payload, adjacency) else { continue };
767        let kept = built.adjacency;
768        attachments.push(Attachment {
769            kind: *section::ADJACENCY,
770            id: u64::try_from(built.edge.child_column)
771                .map_err(|_| invalid("column index overflow"))?,
772            flags: 0,
773            header_bytes: if kept {
774                u32::try_from(binding_bytes(&built.edge.parent))
775                    .map_err(|_| invalid("a parent name longer than a section header"))?
776            } else {
777                cost(bytes.len())
778            },
779            bytes: if kept { bytes } else { &[] },
780        });
781    }
782    for (column, bytes) in &measured {
783        attachments.push(Attachment {
784            kind: *section::DEGREES,
785            id: u64::try_from(*column).map_err(|_| invalid("column index overflow"))?,
786            flags: 0,
787            header_bytes: 0,
788            bytes,
789        });
790    }
791    crate::attach(path, table, &attachments)?;
792    Ok(report)
793}
794
795/// A built link, its payload, and the payload of its adjacency when it has one.
796type OneLink = (BuiltLink, Vec<u8>, Option<Vec<u8>>);
797
798/// Builds one link, or says in one sentence why there is not one.
799///
800/// The error type is a `String` and not an [`rudb_common::Error`] on purpose. Every reason a link
801/// cannot be built here is a reason to not have one, which section 3.1 says is a slower query and
802/// not a failed one, so the caller's response is the same for all of them and a message is what it
803/// needs. A genuine I/O failure still arrives as an error, through the `?` on the scan.
804fn one_link(
805    catalog: &Catalog,
806    child: &Reader,
807    edge: &Edge,
808) -> std::result::Result<OneLink, String> {
809    let parent =
810        catalog.table(&edge.parent).map_err(|_| format!("no table named {}", edge.parent))?;
811    let map = parent_map(&parent, edge)?;
812    if !map.observed().usable_as_parent() {
813        return Err(format!("the key of {} is not unique", edge.parent));
814    }
815    let keys = KeyColumn::new(child, edge.child_column).map_err(|error| error.to_string())?;
816    let mut parents_of = Vec::with_capacity(child.table().rows());
817    let mut failed = None;
818    keys.scan(&mut |key| {
819        let parent = match key {
820            None => NO_PARENT,
821            Some(key) => match map.lookup(key) {
822                Ok(found) => found.unwrap_or(NO_PARENT),
823                Err(error) => {
824                    failed = Some(error.to_string());
825                    NO_PARENT
826                }
827            },
828        };
829        parents_of.push(parent);
830        Ok(())
831    })
832    .map_err(|error| error.to_string())?;
833    if let Some(failed) = failed {
834        return Err(failed);
835    }
836    let link = link::Link::build(&parents_of, map.len()).map_err(|error| error.to_string())?;
837    // The parent key is unique, because the check above refused the relationship otherwise. So the
838    // certificate is recorded here rather than discovered: a link only exists over a key map whose
839    // parent side was counted and found distinct.
840    //
841    // Its own pass over the same slice rather than a loop fused into the one above. The cost of
842    // measuring degrees is the scattered increment into a counter per parent and not the sequential
843    // read of the child column, which the build makes twice already, so fusing would save the cheap
844    // half and put a histogram inside a function whose job is to choose a form.
845    let degrees = Degrees::of(&parents_of, map.len(), true);
846    let bytes = encode_link(&link, &parent, edge).map_err(|error| error.to_string())?;
847    // A monotone link answers the backward direction itself, so the adjacency is only for the
848    // packed form. It is built from the same slice the link was, a counting sort over it.
849    let adjacency = match link.form() {
850        link::Form::Monotone => None,
851        link::Form::Packed => {
852            let adjacency = Adjacency::build(&parents_of, map.len())
853                .and_then(|adjacency| encode_adjacency(&adjacency, &parent, edge))
854                .map_err(|error| error.to_string())?;
855            Some(adjacency)
856        }
857    };
858    Ok((
859        BuiltLink {
860            edge: edge.clone(),
861            form: Some(link.form()),
862            children: link.children(),
863            parents: map.len(),
864            linked: link.linked(),
865            bytes: bytes.len(),
866            table_bytes: 0,
867            degrees: Some(degrees),
868            built: false,
869            adjacency_bytes: adjacency.as_ref().map_or(0, Vec::len),
870            adjacency: false,
871            note: None,
872            build: Duration::ZERO,
873        },
874        bytes,
875        adjacency,
876    ))
877}
878
879/// The parent's key map for one link: the stored one when the file keeps one, and one built here
880/// when it does not.
881///
882/// A key map that the budget turned away still has to be built for the link, because the link is
883/// the smaller structure and the more useful one. `orders` stored by date needs the permuted form
884/// for `o_orderkey`, which is 4.7 MB on SF1 against a budget that is about four, while the
885/// `lineitem -> orders` link it lets the build write is under 1 MB and monotone. Refusing the link
886/// because the map was refused would make the one structure depend on the budget of the other.
887///
888/// A pair's map is not kept, because nothing reads it but this build. The query follows the link and
889/// never looks a key up, and the map a pair needs is the expensive kind: its keys are sparse, so it
890/// is the sorted form, which on `partsupp` is several megabytes against a budget that is about four.
891/// Kept, it would be refused by the budget and take the link down with it. Built here, it costs one
892/// read of two parent columns per checkpoint, which is less than the child scan beside it.
893fn parent_map(parent: &Reader, edge: &Edge) -> std::result::Result<KeyMap, String> {
894    if columns_of(edge.parent_column).len() == 1
895        && let Some(stored) = key_map(parent, edge.parent_column)
896    {
897        return Ok(stored);
898    }
899    KeyColumn::new(parent, edge.parent_column)
900        .and_then(|keys| KeyMap::build_from(&keys))
901        .map_err(|error| error.to_string())
902}
903
904/// What a structure that did not fit is recorded as having cost.
905///
906/// Saturating rather than erroring, because the number is a budget record and not a length: a
907/// structure past four gigabytes did not fit any budget this project sets, and refusing to write the
908/// record would turn a relationship that is merely too big into a build that fails.
909fn cost(bytes: usize) -> u32 {
910    u32::try_from(bytes).unwrap_or(u32::MAX)
911}
912
913/// Bytes of binding in front of a link's payload: which parent table, column and generation.
914///
915/// Eight for the generation, four for the column, four for the name's length, then the name padded
916/// out to eight so that the link's own header lands on a boundary.
917fn binding_bytes(parent: &str) -> usize {
918    16 + parent.len().div_ceil(8) * 8
919}
920
921/// The payload: the binding, then the link.
922///
923/// The binding is here and not in `rudb-graph`'s [`link::Link`], because a table name and a
924/// generation are file concepts and that crate is not allowed to know what a file is. It exists
925/// because the section's own id says only which child column the link is for, and a link resolved
926/// against the wrong parent is the one failure in this layer that is a wrong answer rather than a
927/// slow one. Section 3.1's staleness rule is *ignore, do not repair*, and this is what gives
928/// [`stored_link`] something to check before it believes a payload.
929fn encode_link(link: &link::Link, parent: &Reader, edge: &Edge) -> Result<Vec<u8>> {
930    let name = edge.parent.as_bytes();
931    let mut bytes = Vec::with_capacity(binding_bytes(&edge.parent) + link.bytes());
932    bytes.extend_from_slice(&parent.table().generation().to_le_bytes());
933    bytes.extend_from_slice(
934        &u32::try_from(edge.parent_column)
935            .map_err(|_| invalid("column index overflow"))?
936            .to_le_bytes(),
937    );
938    bytes.extend_from_slice(
939        &u32::try_from(name.len())
940            .map_err(|_| invalid("a parent name longer than a u32"))?
941            .to_le_bytes(),
942    );
943    bytes.extend_from_slice(name);
944    bytes.resize(binding_bytes(&edge.parent), 0);
945    link.write(&mut bytes)?;
946    Ok(bytes)
947}
948
949/// The payload of an adjacency: the same binding a link has, then the adjacency.
950///
951/// The binding is the link's for the link's reason, since an adjacency read against a parent that
952/// has been rewritten names children of rows that are not the rows the key map now gives.
953fn encode_adjacency(adjacency: &Adjacency, parent: &Reader, edge: &Edge) -> Result<Vec<u8>> {
954    let mut bytes = Vec::with_capacity(binding_bytes(&edge.parent) + adjacency.bytes() + 32);
955    let name = edge.parent.as_bytes();
956    bytes.extend_from_slice(&parent.table().generation().to_le_bytes());
957    bytes.extend_from_slice(
958        &u32::try_from(edge.parent_column)
959            .map_err(|_| invalid("column index overflow"))?
960            .to_le_bytes(),
961    );
962    bytes.extend_from_slice(
963        &u32::try_from(name.len())
964            .map_err(|_| invalid("a parent name longer than a u32"))?
965            .to_le_bytes(),
966    );
967    bytes.extend_from_slice(name);
968    bytes.resize(binding_bytes(&edge.parent), 0);
969    adjacency.write(&mut bytes)?;
970    Ok(bytes)
971}
972
973/// The backward adjacency this child table carries for a column, under the same rules as
974/// [`stored_link`]: current, bound to the parent being asked about, and readable, or nothing.
975#[must_use]
976pub fn stored_adjacency(child: &Reader, parent: &Reader, edge: &Edge) -> Option<Adjacency> {
977    let table = child.table();
978    let id = u64::try_from(edge.child_column).ok()?;
979    let held = table
980        .sections()
981        .iter()
982        .find(|section| section.kind == *section::ADJACENCY && section.id == id)?;
983    if !held.usable(table.generation()) || held.refused().is_some() {
984        return None;
985    }
986    let bytes = child.payload(held).ok()?;
987    let binding = bound(&bytes, parent, edge)?;
988    Adjacency::read_from(bytes, binding).ok()
989}
990
991/// The forward link this child table carries for a column, when it carries one this build can use
992/// and the parent it was built against is still the parent being asked about.
993///
994/// `None` for every reason there might not be one, for the reason [`key_map`] answers the same way.
995/// The extra check here is the binding: a link whose stored parent name, column or generation is
996/// not the one the caller is asking for is a link built against a table that has since been
997/// rewritten, and resolving through it would produce a plausible wrong row rather than an error.
998#[must_use]
999pub fn stored_link(child: &Reader, parent: &Reader, edge: &Edge) -> Option<link::Link> {
1000    let held = link_section(child, edge)?;
1001    let bytes = child.payload(held).ok()?;
1002    let binding = bound(&bytes, parent, edge)?;
1003    link::Link::read_from(bytes, binding).ok()
1004}
1005
1006/// The counts at the front of the stored link [`stored_link`] would return, read without the link.
1007///
1008/// The same section, the same binding and the same header checks, over the first few dozen bytes of
1009/// the payload rather than all of it. Whether a relationship is verified is a question a planner
1010/// asks of every declared one before its first query, and the answer is whether `linked` equals
1011/// `children`. Reading the links whole to find that out cost about 95 million instructions at SF1,
1012/// most of it copying and faulting in lineitem's links, on a query that then read none of them.
1013///
1014/// The bytes are not checksummed, for the reason on [`Reader::payload_head`]: a plan that reads the
1015/// link loads it through [`stored_link`], which checks everything, and refuses to run if that fails.
1016#[must_use]
1017pub fn stored_link_counts(child: &Reader, parent: &Reader, edge: &Edge) -> Option<link::Counts> {
1018    let held = link_section(child, edge)?;
1019    let binding = binding_bytes(&edge.parent);
1020    let bytes = child.payload_head(held, binding + link::HEADER_BYTES).ok()?;
1021    let binding = bound(&bytes, parent, edge)?;
1022    link::Link::counts(&bytes[binding..]).ok()
1023}
1024
1025/// The parent table and column the current forward link for this child column was built against.
1026///
1027/// Read off the binding at the front of the section, so the link itself is not read. A caller
1028/// that follows the link asks [`stored_link`] with the edge made from this, which checks the
1029/// binding again against the parent's generation, so a link built against an older parent is
1030/// found here and then refused there.
1031#[must_use]
1032pub fn link_parent(child: &Reader, child_column: usize) -> Option<(String, usize)> {
1033    let table = child.table();
1034    let id = u64::try_from(child_column).ok()?;
1035    let held = table
1036        .sections()
1037        .iter()
1038        .find(|section| section.kind == *section::FORWARD_LINK && section.id == id)?;
1039    if !held.usable(table.generation()) || held.refused().is_some() {
1040        return None;
1041    }
1042    let head = child.payload_head(held, 16).ok()?;
1043    let column = u32::from_le_bytes(head.get(8..12)?.try_into().ok()?);
1044    let length = u32::from_le_bytes(head.get(12..16)?.try_into().ok()?) as usize;
1045    let bytes = child.payload_head(held, 16 + length).ok()?;
1046    let name = std::str::from_utf8(bytes.get(16..16 + length)?).ok()?;
1047    Some((name.to_owned(), column as usize))
1048}
1049
1050/// The child's current forward link section for this edge's child column.
1051fn link_section<'a>(child: &'a Reader, edge: &Edge) -> Option<&'a section::Section> {
1052    let table = child.table();
1053    let id = u64::try_from(edge.child_column).ok()?;
1054    let held = table
1055        .sections()
1056        .iter()
1057        .find(|section| section.kind == *section::FORWARD_LINK && section.id == id)?;
1058    held.usable(table.generation()).then_some(held)
1059}
1060
1061/// Where the link starts in a payload whose binding names this edge's parent as it is now, or
1062/// `None` when it names another table, another column, or an older generation of this one.
1063fn bound(bytes: &[u8], parent: &Reader, edge: &Edge) -> Option<usize> {
1064    let binding = binding_bytes(&edge.parent);
1065    if bytes.len() < binding {
1066        return None;
1067    }
1068    let generation = u64::from_le_bytes(bytes[0..8].try_into().ok()?);
1069    let column = u32::from_le_bytes(bytes[8..12].try_into().ok()?);
1070    let length = u32::from_le_bytes(bytes[12..16].try_into().ok()?) as usize;
1071    if generation != parent.table().generation()
1072        || column as usize != edge.parent_column
1073        || length != edge.parent.len()
1074        || &bytes[16..16 + length] != edge.parent.as_bytes()
1075    {
1076        return None;
1077    }
1078    Some(binding)
1079}
1080
1081/// What the build measured of a relationship's shape, when the child table carries it.
1082///
1083/// There is no binding to check, unlike [`stored_link`], because there is nothing here to resolve
1084/// against the parent. Every number is about the child column and the generation stamp is the whole
1085/// of what makes one of these current. A caller that wants to know the relationship is still the
1086/// one it means asks [`stored_link`] as well, which it is doing anyway if it plans to follow it.
1087#[must_use]
1088pub fn stored_degrees(child: &Reader, child_column: usize) -> Option<Degrees> {
1089    let table = child.table();
1090    let id = u64::try_from(child_column).ok()?;
1091    let held = table
1092        .sections()
1093        .iter()
1094        .find(|section| section.kind == *section::DEGREES && section.id == id)?;
1095    if !held.usable(table.generation()) {
1096        return None;
1097    }
1098    Degrees::read(&child.payload(held).ok()?).ok()
1099}
1100
1101/// Whether this table holds a key map over this column at its current generation.
1102///
1103/// The build only writes a key map over a column whose values it found distinct, nulls aside, so
1104/// one being there says the column is a key of the table. That is a fact about the parent alone
1105/// and holds whether or not a child's link to it fit its own budget. A record of a map that did not
1106/// fit is not a map and does not count. Nothing is decoded, so asking this costs nothing.
1107#[must_use]
1108pub fn holds_key_map(reader: &Reader, column: usize) -> bool {
1109    let table = reader.table();
1110    let Ok(id) = u64::try_from(column) else { return false };
1111    table.sections().iter().any(|section| {
1112        section.kind == *section::KEY_MAP
1113            && section.id == id
1114            && section.usable(table.generation())
1115            && section.refused().is_none()
1116    })
1117}
1118
1119/// What a key map over this column would have cost, when a build measured one and did not keep it.
1120///
1121/// This and [`key_map`] are exclusive: an entry either holds a map or records the absence of one,
1122/// and which it is comes off the entry rather than out of a payload, so asking this costs nothing.
1123/// Both answer `None` for a column no build has looked at, which is the third state and is the one
1124/// where `rudb_links()` should say nothing rather than zero.
1125#[must_use]
1126pub fn refused_key_map(reader: &Reader, column: usize) -> Option<(Form, u64)> {
1127    let (form, bytes) = refused(reader, *section::KEY_MAP, column)?;
1128    Some((Form::from_tag(form).ok()?, bytes))
1129}
1130
1131/// What a forward link for this column would have cost, when a build measured one and did not keep
1132/// it. The counterpart of [`stored_link`], the way [`refused_key_map`] is the counterpart of
1133/// [`key_map`].
1134#[must_use]
1135pub fn refused_link(child: &Reader, child_column: usize) -> Option<(link::Form, u64)> {
1136    let (form, bytes) = refused(child, *section::FORWARD_LINK, child_column)?;
1137    Some((link::Form::from_tag(form).ok()?, bytes))
1138}
1139
1140/// The form tag and the size out of a budget record, when the table holds one for this id.
1141fn refused(reader: &Reader, kind: [u8; 8], id: usize) -> Option<(u8, u64)> {
1142    let table = reader.table();
1143    let id = u64::try_from(id).ok()?;
1144    let held = table.sections().iter().find(|section| section.kind == kind && section.id == id)?;
1145    if !held.usable(table.generation()) {
1146        return None;
1147    }
1148    Some((u8::try_from(held.flags).ok()?, held.refused()?))
1149}
1150
1151/// The bytes the sections of one kind a table already holds cost, leaving out the columns about to
1152/// be rebuilt.
1153///
1154/// Each of key maps, links and adjacencies is paid for out of its own share, so each counts only
1155/// what it owns.
1156fn held_kind_bytes(reader: &Reader, kind: [u8; 8], replacing: &[usize]) -> Result<u64> {
1157    let mut total = 0;
1158    for held in reader.table().sections() {
1159        if held.kind != kind || !held.usable(reader.table().generation()) {
1160            continue;
1161        }
1162        if replacing.iter().any(|&id| u64::try_from(id) == Ok(held.id)) {
1163            continue;
1164        }
1165        let Ok(extents) = reader.extents(held) else { continue };
1166        total += extents.iter().map(|extent| u64::from(extent.length)).sum::<u64>();
1167    }
1168    Ok(total)
1169}
1170
1171#[cfg(test)]
1172mod tests {
1173    use std::fs;
1174    use std::path::PathBuf;
1175    use std::time::{SystemTime, UNIX_EPOCH};
1176
1177    use rudb_common::Field;
1178    use rudb_graph::Rid;
1179    use rudb_vector::Vector;
1180
1181    use super::*;
1182    use crate::Writer;
1183
1184    fn path(label: &str) -> PathBuf {
1185        let stamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("time advances").as_nanos();
1186        std::env::temp_dir().join(format!("rudb-graph-{label}-{}-{stamp}.rdb", std::process::id()))
1187    }
1188
1189    /// The graph sections of a table, which is every section this module could have written.
1190    ///
1191    /// A table carries a summary and a sketch per column out of the write itself now, and a test
1192    /// about key maps is not about those. Filtering by kind rather than subtracting a count, so a
1193    /// table whose summaries did not fit the budget does not quietly change what is asserted.
1194    fn graph_sections(reader: &Reader) -> Vec<&section::Section> {
1195        reader.table().sections().iter().filter(|held| held.among(section::GRAPH_KINDS)).collect()
1196    }
1197
1198    /// A one column table of these keys, written a thousand rows to a part.
1199    fn table_of(label: &str, keys: &[Option<i64>]) -> PathBuf {
1200        let path = path(label);
1201        let mut writer =
1202            Writer::create(&path, "parent", vec![Field::new("key", LogicalType::BigInt)])
1203                .expect("new file");
1204        for part in keys.chunks(1000) {
1205            let values =
1206                part.iter().map(|key| key.map_or(Value::Null, Value::BigInt)).collect::<Vec<_>>();
1207            let chunk =
1208                Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1209                    .expect("one column");
1210            writer.append(&chunk).expect("a part");
1211        }
1212        writer.finish().expect("commit");
1213        path
1214    }
1215
1216    /// Every key in the column resolves to the row that holds it.
1217    fn resolves(keys: &[Option<i64>], map: &KeyMap) {
1218        for (rid, key) in keys.iter().enumerate() {
1219            let Some(key) = *key else { continue };
1220            let found =
1221                map.lookup(i128::from(key)).expect("lookup").expect("a key in the column resolves");
1222            assert_eq!(found, rid as Rid, "key {key} resolved to {found} rather than {rid}");
1223        }
1224    }
1225
1226    #[test]
1227    fn a_key_map_built_over_a_file_resolves_every_key_to_its_own_row() {
1228        // The whole point, end to end: the column goes to disk, comes back through the reader, and
1229        // every key finds the row it was written in. Three thousand rows so that the scan crosses
1230        // part boundaries, because a build that read the parts in the wrong order would be right
1231        // for one part and wrong for the rest.
1232        let keys = (1..=3000_i64).map(Some).collect::<Vec<_>>();
1233        let path = table_of("identity", &keys);
1234        let built = build_key_maps(&path, "parent", &[0]).expect("build");
1235        assert_eq!(built.len(), 1);
1236        assert_eq!(built[0].form, Form::Identity);
1237        assert_eq!(built[0].rows, 3000);
1238        assert!(built[0].distinct);
1239        assert_eq!(built[0].bytes, wire::HEADER_BYTES, "identity is a header and nothing else");
1240
1241        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1242        let map = key_map(&reader, 0).expect("the map is in the file");
1243        assert_eq!(map.form(), Form::Identity);
1244        resolves(&keys, &map);
1245        assert_eq!(map.lookup(0).expect("a key below the column"), None);
1246        assert_eq!(map.lookup(3001).expect("a key past the column"), None);
1247
1248        fs::remove_file(&path).expect("clean up");
1249    }
1250
1251    #[test]
1252    fn a_column_with_gaps_takes_the_bitmap_form_and_still_resolves() {
1253        let keys = (0..2000_i64).map(|value| Some(value * 4 + 7)).collect::<Vec<_>>();
1254        let path = table_of("dense", &keys);
1255        let built = build_key_maps(&path, "parent", &[0]).expect("build");
1256        assert_eq!(built[0].form, Form::Dense);
1257
1258        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1259        let map = key_map(&reader, 0).expect("the map is in the file");
1260        resolves(&keys, &map);
1261        assert_eq!(map.lookup(8).expect("a value in the range but not the column"), None);
1262
1263        fs::remove_file(&path).expect("clean up");
1264    }
1265
1266    #[test]
1267    fn a_column_out_of_order_takes_the_sorted_form_and_still_resolves() {
1268        let keys = (0..1500_i64).map(|value| Some((value * 7919) % 100_003)).collect::<Vec<_>>();
1269        let path = table_of("sorted", &keys);
1270        let built = build_key_maps(&path, "parent", &[0]).expect("build");
1271        assert_eq!(built[0].form, Form::Sorted);
1272        assert!(built[0].distinct, "the sort settles distinctness for an unordered column");
1273
1274        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1275        let map = key_map(&reader, 0).expect("the map is in the file");
1276        resolves(&keys, &map);
1277
1278        fs::remove_file(&path).expect("clean up");
1279    }
1280
1281    #[test]
1282    fn a_null_in_the_key_column_does_not_shift_the_rows_after_it() {
1283        // The failure this whole crate is most exposed to. A null is not a key, but it is a row, so
1284        // a form that answers with a count of keys below a value answers one short for every row
1285        // after it. It does not crash and it does not look wrong: it resolves every key to a
1286        // neighbour of the right row.
1287        let mut keys = (1..=1200_i64).map(Some).collect::<Vec<_>>();
1288        keys[3] = None;
1289        keys[900] = None;
1290        let path = table_of("nulls", &keys);
1291        let built = build_key_maps(&path, "parent", &[0]).expect("build");
1292        assert_eq!(built[0].rows, 1198, "a null is not a key");
1293
1294        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1295        let map = key_map(&reader, 0).expect("the map is in the file");
1296        resolves(&keys, &map);
1297
1298        fs::remove_file(&path).expect("clean up");
1299    }
1300
1301    #[test]
1302    fn a_column_with_a_repeat_in_it_is_mapped_and_reported_as_no_parent() {
1303        // Section 2.3: a parent side that is not unique is not an error and is not a link. It is
1304        // also not a key map. The repeat here is not next to itself, so only the sort can find it
1305        // and the bytes are spent before anybody knows, which is why the report carries what it
1306        // cost and the file does not.
1307        let mut keys = (1..=500_i64).map(Some).collect::<Vec<_>>();
1308        keys[200] = Some(7);
1309        let path = table_of("repeat", &keys);
1310        let built = build_key_maps(&path, "parent", &[0]).expect("build");
1311        assert!(!built[0].distinct, "a repeat is observed rather than declared away");
1312        assert!(!built[0].built, "and a map no rid can be resolved through is not kept");
1313        assert!(built[0].bytes > 0, "what it would have cost is still reported");
1314
1315        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1316        assert!(key_map(&reader, 0).is_none(), "no map was written to read back");
1317        assert!(!holds_key_map(&reader, 0), "and the record of a refusal does not say it is a key");
1318        // What is written is the entry that says so, with no bytes behind it. Section 3.7 wants the
1319        // size to survive the build that decided against it, and fifty six bytes of entry is the
1320        // whole of what a refusal costs.
1321        let (form, bytes) = refused_key_map(&reader, 0).expect("the record of what it would cost");
1322        assert_eq!(form, built[0].form);
1323        assert_eq!(bytes, built[0].bytes as u64);
1324        assert_eq!(graph_sections(&reader).len(), 1, "one entry, and no payload");
1325        assert_eq!(graph_sections(&reader)[0].extents, 0);
1326
1327        fs::remove_file(&path).expect("clean up");
1328    }
1329
1330    #[test]
1331    fn a_table_with_no_key_map_answers_with_none_rather_than_an_error() {
1332        // Section 3.1 at the API. Every query has to be answerable with no section in the file, so
1333        // asking for a map that is not there is a question with an answer and not a failure.
1334        let path = table_of("absent", &[Some(1), Some(2)]);
1335        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1336        assert!(key_map(&reader, 0).is_none());
1337        assert!(key_map(&reader, 99).is_none(), "a column that does not exist is not a panic");
1338        assert!(!holds_key_map(&reader, 0));
1339        fs::remove_file(&path).expect("clean up");
1340    }
1341
1342    #[test]
1343    fn a_stale_key_map_is_ignored_and_the_table_still_reads() {
1344        let path = table_of("stale", &(1..=100_i64).map(Some).collect::<Vec<_>>());
1345        build_key_maps(&path, "parent", &[0]).expect("build");
1346
1347        // A second table in the same file moves the file's generation and not this table's, so the
1348        // map stays current: that is the distinction `Table::generation` exists to make.
1349        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1350        assert!(key_map(&reader, 0).is_some());
1351        assert!(holds_key_map(&reader, 0));
1352        let generation = reader.table().generation();
1353        drop(reader);
1354
1355        // And a map stamped against a generation this table is not at is dropped rather than used.
1356        let held = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1357        let mut entry = *graph_sections(&held).first().copied().expect("the key map");
1358        assert!(entry.usable(generation));
1359        entry.generation = generation + 1;
1360        assert!(!entry.usable(generation), "a rewrite invalidates rather than corrupts");
1361
1362        fs::remove_file(&path).expect("clean up");
1363    }
1364
1365    #[test]
1366    fn a_torn_key_map_costs_the_shortcut_and_not_the_query() {
1367        let keys = (1..=200_i64).map(Some).collect::<Vec<_>>();
1368        let path = table_of("torn", &keys);
1369        build_key_maps(&path, "parent", &[0]).expect("build");
1370
1371        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1372        let extent = reader
1373            .extents(graph_sections(&reader).first().copied().expect("the key map"))
1374            .expect("extent table")
1375            .first()
1376            .copied()
1377            .expect("one extent");
1378        drop(reader);
1379        let file = fs::OpenOptions::new().write(true).open(&path).expect("reopen to corrupt");
1380        crate::write_at(&file, extent.offset, &[0xff; 8]).expect("flip the header");
1381        drop(file);
1382
1383        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1384        assert!(key_map(&reader, 0).is_none(), "a payload that does not checksum is not a map");
1385        assert_eq!(reader.table().rows(), 200, "and the table is untouched");
1386
1387        fs::remove_file(&path).expect("clean up");
1388    }
1389
1390    #[test]
1391    fn a_column_with_no_integer_key_form_is_refused_by_name() {
1392        let path = path("varchar");
1393        let mut writer =
1394            Writer::create(&path, "parent", vec![Field::new("name", LogicalType::Varchar)])
1395                .expect("new file");
1396        let chunk = Chunk::new(vec![
1397            Vector::from_values(LogicalType::Varchar, &[Value::Varchar("a".into())])
1398                .expect("one name"),
1399        ])
1400        .expect("one column");
1401        writer.append(&chunk).expect("a part");
1402        writer.finish().expect("commit");
1403
1404        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1405        let error = KeyColumn::new(&reader, 0).expect_err("a string key needs its codes");
1406        assert!(error.to_string().contains("integer key form"), "{error}");
1407
1408        fs::remove_file(&path).expect("clean up");
1409    }
1410
1411    #[test]
1412    fn several_columns_are_mapped_in_one_commit() {
1413        let path = path("two_columns");
1414        let mut writer = Writer::create(
1415            &path,
1416            "parent",
1417            vec![
1418                Field::required("id", LogicalType::BigInt),
1419                Field::required("code", LogicalType::Integer),
1420            ],
1421        )
1422        .expect("new file");
1423        let ids = (1..=400_i64).map(Value::BigInt).collect::<Vec<_>>();
1424        let codes = (1..=400_i32).map(|code| Value::Integer(code * 3)).collect::<Vec<_>>();
1425        let chunk = Chunk::new(vec![
1426            Vector::from_values(LogicalType::BigInt, &ids).expect("ids"),
1427            Vector::from_values(LogicalType::Integer, &codes).expect("codes"),
1428        ])
1429        .expect("two columns");
1430        writer.append(&chunk).expect("a part");
1431        writer.finish().expect("commit");
1432
1433        let built = build_key_maps(&path, "parent", &[0, 1]).expect("build both");
1434        assert_eq!(built.len(), 2);
1435        assert_eq!(built[0].form, Form::Identity);
1436        assert_eq!(built[1].form, Form::Dense);
1437
1438        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1439        assert_eq!(graph_sections(&reader).len(), 2, "one commit and two entries");
1440        assert_eq!(key_map(&reader, 0).expect("the id map").form(), Form::Identity);
1441        assert_eq!(key_map(&reader, 1).expect("the code map").form(), Form::Dense);
1442        assert_eq!(
1443            key_map(&reader, 1).expect("the code map").lookup(9).expect("lookup"),
1444            Some(2),
1445            "the third code is the third row"
1446        );
1447
1448        fs::remove_file(&path).expect("clean up");
1449    }
1450
1451    #[test]
1452    fn the_statistics_sections_do_not_count_against_the_graph_budget() {
1453        // The two shares are ten percent and two percent of the same column bytes, and separate
1454        // means each counts only what it owns. A graph build that counted summaries would be a
1455        // graph budget the statistics layer eats, and a table would lose key maps for a reason
1456        // that has nothing to do with key maps. The kind lists in `section` are what keeps the two
1457        // apart, and this is the direction of that which lives in this file.
1458        let keys = (1..=3000_i64).map(Some).collect::<Vec<_>>();
1459        let path = table_of("apart", &keys);
1460        crate::stats::build_stats(&path, "parent", &[0]).expect("summaries first");
1461
1462        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1463        let statistics = reader
1464            .table()
1465            .sections()
1466            .iter()
1467            .filter(|held| held.among(section::STATISTICS_KINDS))
1468            .count();
1469        assert_eq!(statistics, 2, "a summary and a sketch are in the file");
1470        assert_eq!(held_bytes(&reader, &[0]).expect("held"), 0, "and neither is the graph's");
1471
1472        drop(reader);
1473        fs::remove_file(&path).expect("clean up");
1474    }
1475
1476    #[test]
1477    fn a_map_that_does_not_fit_the_budget_is_measured_and_not_written() {
1478        // A column of twenty thousand even numbers is about the worst case there is for this: the
1479        // column encodes to a few hundred bytes because it is a run of a constant delta, and the
1480        // bitmap over it cannot be smaller than one bit per value in its range. So the map is an
1481        // order of magnitude larger than the column it maps and section 3.7 says it does not go in
1482        // the file. What comes back is the number, which is the point: a budget that silently drops
1483        // things teaches nobody anything.
1484        let keys = (0..100_000_i64).map(|value| Some(value * 8)).collect::<Vec<_>>();
1485        let path = table_of("budget", &keys);
1486        let built = build_key_maps(&path, "parent", &[0]).expect("build");
1487        assert_eq!(built[0].form, Form::Dense);
1488        assert!(!built[0].built, "a map ten times its column does not fit a tenth of it");
1489        assert!(built[0].bytes as u64 > built[0].column_bytes, "{built:?}");
1490
1491        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1492        assert!(key_map(&reader, 0).is_none(), "and no map was written");
1493        // The record of what it would have cost is what somebody raising `graph_budget` reads, and
1494        // it is the number the build reported rather than a rounding of it.
1495        assert_eq!(refused_key_map(&reader, 0), Some((Form::Dense, built[0].bytes as u64)));
1496        assert_eq!(held_bytes(&reader, &[]).expect("held"), 0, "a record costs the budget nothing");
1497        drop(reader);
1498
1499        // The same build against a budget that allows it keeps it, which is what `graph_budget`
1500        // will be for. Nothing else about the build changes.
1501        let built = build_key_maps_within(&path, "parent", &[0], 100_000).expect("build");
1502        assert!(built[0].built);
1503        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1504        let map = key_map(&reader, 0).expect("the map is in the file");
1505        resolves(&keys, &map);
1506
1507        fs::remove_file(&path).expect("clean up");
1508    }
1509
1510    #[test]
1511    fn the_budget_admits_the_cheapest_maps_it_can_fit() {
1512        // Two columns and room for one of them. The ids take the identity form, which is forty
1513        // bytes whatever the row count, and the scattered keys take the sorted form, which is the
1514        // keys and a permutation and so is larger than the tenth of the table it would need. So the
1515        // budget keeps the first and reports what the second would have cost, and it does that
1516        // although the second was asked for first.
1517        let path = path("budget_order");
1518        let mut writer = Writer::create(
1519            &path,
1520            "parent",
1521            vec![
1522                Field::required("id", LogicalType::BigInt),
1523                Field::required("code", LogicalType::BigInt),
1524            ],
1525        )
1526        .expect("new file");
1527        let ids = (1..=100_000_i64).map(Value::BigInt).collect::<Vec<_>>();
1528        let codes = (1..=100_000_i64)
1529            .map(|code| Value::BigInt((code * 2_147_483_647) % 999_999_937))
1530            .collect::<Vec<_>>();
1531        for part in 0..100 {
1532            let at = part * 1000;
1533            let chunk = Chunk::new(vec![
1534                Vector::from_values(LogicalType::BigInt, &ids[at..at + 1000]).expect("ids"),
1535                Vector::from_values(LogicalType::BigInt, &codes[at..at + 1000]).expect("codes"),
1536            ])
1537            .expect("two columns");
1538            writer.append(&chunk).expect("a part");
1539        }
1540        writer.finish().expect("commit");
1541
1542        let built = build_key_maps(&path, "parent", &[1, 0]).expect("build");
1543        assert_eq!(built[0].column, 1, "the report is in the order it was asked in");
1544        assert_eq!(built[0].form, Form::Sorted);
1545        assert!(!built[0].built, "the sorted map did not fit: {built:?}");
1546        assert!(built[1].built, "the identity map did, and was reached second: {built:?}");
1547
1548        let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1549        assert!(key_map(&reader, 0).is_some());
1550        assert!(key_map(&reader, 1).is_none());
1551
1552        fs::remove_file(&path).expect("clean up");
1553    }
1554
1555    /// A parent table of `parents` sequential keys and a child table of these foreign keys, with
1556    /// the parent's key map already built, which is the state section 3.8 says a link build starts
1557    /// from.
1558    fn related(label: &str, parents: i64, foreign: &[Option<i64>]) -> PathBuf {
1559        let path = table_of(label, &(1..=parents).map(Some).collect::<Vec<_>>());
1560        let mut writer = Writer::open(&path, "child", vec![Field::new("fk", LogicalType::BigInt)])
1561            .expect("a second table");
1562        for part in foreign.chunks(1000) {
1563            let values =
1564                part.iter().map(|key| key.map_or(Value::Null, Value::BigInt)).collect::<Vec<_>>();
1565            let chunk =
1566                Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1567                    .expect("one column");
1568            writer.append(&chunk).expect("a part");
1569        }
1570        writer.finish().expect("commit");
1571        build_key_maps(&path, "parent", &[0]).expect("the parent's key map");
1572        path
1573    }
1574
1575    fn edge() -> Edge {
1576        Edge { child: "child".into(), child_column: 0, parent: "parent".into(), parent_column: 0 }
1577    }
1578
1579    /// Reads the link back out of the file and checks every child against the key it was built
1580    /// from, which is the only assertion that catches a link that is off by a row.
1581    fn links(path: &PathBuf, foreign: &[Option<i64>]) -> link::Link {
1582        let catalog = Catalog::open(path).expect("reopen");
1583        let child = catalog.table("child").expect("the child");
1584        let parent = catalog.table("parent").expect("the parent");
1585        let link = stored_link(&child, &parent, &edge()).expect("the link is in the file");
1586        let map = key_map(&parent, 0).expect("the parent's key map");
1587        for (rid, key) in foreign.iter().enumerate() {
1588            let want = key.and_then(|key| map.lookup(i128::from(key)).expect("lookup"));
1589            assert_eq!(link.forward(rid as Rid), want, "child {rid}");
1590        }
1591        link
1592    }
1593
1594    /// One row of a two column key, either half of which may be null.
1595    type Pair = (Option<i64>, Option<i64>);
1596
1597    /// A two column table of these pairs, a thousand rows to a part.
1598    fn pairs_into(mut writer: Writer, rows: &[Pair]) {
1599        let values = |pick: fn(&Pair) -> Option<i64>, part: &[Pair]| {
1600            let values = part
1601                .iter()
1602                .map(|row| pick(row).map_or(Value::Null, Value::BigInt))
1603                .collect::<Vec<_>>();
1604            Vector::from_values(LogicalType::BigInt, &values).expect("keys")
1605        };
1606        for part in rows.chunks(1000) {
1607            let chunk = Chunk::new(vec![values(|row| row.0, part), values(|row| row.1, part)])
1608                .expect("two columns");
1609            writer.append(&chunk).expect("a part");
1610        }
1611        writer.finish().expect("commit");
1612    }
1613
1614    #[test]
1615    fn a_key_over_one_column_is_named_the_way_it_always_was() {
1616        // The files written before there were pairs name every key by its column's index, so a
1617        // single column has to come out as exactly that or every one of them stops resolving.
1618        assert_eq!(key_of(&[0]), Some(0));
1619        assert_eq!(key_of(&[17]), Some(17));
1620        assert_eq!(columns_of(17), vec![17]);
1621        let pair = key_of(&[1, 2]).expect("a pair");
1622        assert_ne!(pair, key_of(&[2, 1]).expect("a pair"), "the order is part of the key");
1623        assert_eq!(columns_of(pair), vec![1, 2]);
1624        assert!(pair > u32::MAX as usize / 2, "a pair never reads as a column index");
1625        assert_eq!(key_of(&[]), None);
1626        assert_eq!(key_of(&[0, 1, 2]), None, "nothing is built over three columns");
1627        assert_eq!(key_of(&[1 << 15, 0]), None, "an index too wide to pack is refused");
1628    }
1629
1630    #[test]
1631    fn two_values_fold_into_one_key_without_two_pairs_ever_meeting() {
1632        let values = [i128::from(i32::MIN), -1, 0, 1, i128::from(i32::MAX)];
1633        let mut seen = std::collections::HashSet::new();
1634        for &high in &values {
1635            for &low in &values {
1636                assert!(seen.insert(fold(high, low).expect("fits")), "({high}, {low}) met another");
1637            }
1638        }
1639        let wide = i128::from(i32::MAX) + 1;
1640        assert!(fold(0, wide).is_err(), "a second value past 32 bits");
1641        assert!(fold(wide, 0).is_err(), "a first value past 32 bits");
1642        let span = fold(i128::from(i32::MAX), i128::from(i32::MAX)).expect("fits")
1643            - fold(i128::from(i32::MIN), i128::from(i32::MIN)).expect("fits");
1644        assert!(span <= i128::from(u64::MAX), "a key map's keys span no more than a u64");
1645    }
1646
1647    #[test]
1648    fn a_link_over_a_two_column_key_finds_the_row_holding_both_values() {
1649        // `lineitem(l_partkey, l_suppkey) -> partsupp(ps_partkey, ps_suppkey)` in small: four
1650        // suppliers for each of five hundred parts, and a child that names a pair of them. The
1651        // first column alone repeats four times, so this is the case a link over it cannot answer.
1652        let path = path("pair");
1653        let fields =
1654            vec![Field::new("part", LogicalType::BigInt), Field::new("supp", LogicalType::BigInt)];
1655        let parents = (1..=500_i64)
1656            .flat_map(|part| (0..4).map(move |at| (Some(part), Some((part + at * 125) % 1000 + 1))))
1657            .collect::<Vec<_>>();
1658        pairs_into(Writer::create(&path, "parent", fields.clone()).expect("new file"), &parents);
1659        let mut children = (0..3000_i64)
1660            .map(|at| parents[usize::try_from((at * 7) % 2000).expect("small")])
1661            .collect::<Vec<_>>();
1662        children[5] = (Some(3), Some(999)); // a part and a supplier that are never paired
1663        children[6] = (None, Some(4));
1664        children[7] = (Some(4), None);
1665        pairs_into(Writer::open(&path, "child", fields).expect("a second table"), &children);
1666
1667        let key = pair(0, 1).expect("a pair");
1668        let edge = Edge {
1669            child: "child".into(),
1670            child_column: key,
1671            parent: "parent".into(),
1672            parent_column: key,
1673        };
1674        let report = build_links(&path, std::slice::from_ref(&edge)).expect("build");
1675        assert!(report[0].built, "{:?}", report[0].note);
1676        assert_eq!(report[0].linked, 2997, "three children name no parent");
1677
1678        let catalog = Catalog::open(&path).expect("reopen");
1679        let parent = catalog.table("parent").expect("the parent");
1680        let child = catalog.table("child").expect("the child");
1681        assert!(key_map(&parent, key).is_none(), "a pair's map is built for the link and not kept");
1682        let link = stored_link(&child, &parent, &edge).expect("the link is in the file");
1683        for (rid, row) in children.iter().enumerate() {
1684            let want = parents.iter().position(|held| held == row).map(|at| at as Rid);
1685            assert_eq!(link.forward(rid as Rid), want, "child {rid} is {row:?}");
1686        }
1687        let one = Edge { child_column: 0, parent_column: 0, ..edge };
1688        assert!(stored_link(&child, &parent, &one).is_none(), "half of the key is not the key");
1689
1690        fs::remove_file(&path).expect("clean up");
1691    }
1692
1693    #[test]
1694    fn a_clustered_foreign_key_takes_the_monotone_form_and_answers_both_directions() {
1695        // The shape `lineitem` has against `orders`, which is the relationship section 3.4's
1696        // arithmetic is about. Four children each of a thousand parents, in order.
1697        let foreign = (0..4000_i64).map(|child| Some(child / 4 + 1)).collect::<Vec<_>>();
1698        let path = related("monotone", 1000, &foreign);
1699        let report = build_links(&path, &[edge()]).expect("build");
1700        assert_eq!(report.len(), 1);
1701        assert!(report[0].built, "{:?}", report[0].note);
1702        assert_eq!(report[0].form, Some(link::Form::Monotone));
1703        assert_eq!(report[0].children, 4000);
1704        assert_eq!(report[0].linked, 4000);
1705
1706        let link = links(&path, &foreign);
1707        assert_eq!(link.form(), link::Form::Monotone);
1708        assert_eq!(link.backward(0), Some(0..4), "the first parent's four children");
1709        assert_eq!(link.backward(999), Some(3996..4000));
1710        assert_eq!(link.backward(1000), None, "past the last parent");
1711
1712        fs::remove_file(&path).expect("clean up");
1713    }
1714
1715    #[test]
1716    fn an_unclustered_foreign_key_takes_the_packed_form_and_still_resolves() {
1717        let foreign = (0..3000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1718        let path = related("packed", 1000, &foreign);
1719        let report = build_links(&path, &[edge()]).expect("build");
1720        assert!(report[0].built, "{:?}", report[0].note);
1721        assert_eq!(report[0].form, Some(link::Form::Packed));
1722
1723        let link = links(&path, &foreign);
1724        assert_eq!(link.backward(0), None, "the packed form answers one direction");
1725        // Ten bits a child, a min and a max per part, and the header. The check is that it is a rid
1726        // per child and not a byte per child, because a link stored as a u64 array would also pass
1727        // every assertion above it.
1728        assert!(link.bytes() < 3000 * 2 + 3 * 16, "{} bytes is not bit-packed", link.bytes());
1729
1730        fs::remove_file(&path).expect("clean up");
1731    }
1732
1733    #[test]
1734    fn a_built_link_leaves_the_shape_of_the_relationship_beside_it() {
1735        // The same clustered shape as the monotone test, so the expected numbers are arithmetic
1736        // rather than an observation: four children each of a thousand parents, in order.
1737        let foreign = (0..4000_i64).map(|child| Some(child / 4 + 1)).collect::<Vec<_>>();
1738        let path = related("degrees", 1000, &foreign);
1739        let report = build_links(&path, &[edge()]).expect("build");
1740        assert!(report[0].built, "{:?}", report[0].note);
1741        let measured = report[0].degrees.as_ref().expect("the build measured it");
1742        assert!((measured.mean() - 4.0).abs() < 1e-9);
1743
1744        let catalog = Catalog::open(&path).expect("reopen");
1745        let child = catalog.table("child").expect("the child");
1746        let held = stored_degrees(&child, 0).expect("it is in the file");
1747        assert_eq!(&held, measured, "what the build measured is what the file holds");
1748        assert_eq!(held.parents(), 1000);
1749        assert_eq!(held.highest(), 4);
1750        assert!(held.total(), "every child found a parent");
1751        assert!(held.unique(), "and the parent key is why there is a link at all");
1752        // Three thousand nine hundred and ninety nine steps between adjacent children, of which the
1753        // nine hundred and ninety nine that cross into the next parent move by one and the rest
1754        // stay put. Which is what a clustered foreign key is, expressed as a number.
1755        let near = held.locality().expect("something to gather");
1756        assert!((near - 999.0 / 3999.0).abs() < 1e-9, "{near}");
1757        assert!(stored_degrees(&child, 1).is_none(), "and no other column has one");
1758
1759        fs::remove_file(&path).expect("clean up");
1760    }
1761
1762    #[test]
1763    fn a_foreign_key_that_matches_nothing_is_a_child_with_no_parent() {
1764        // Not an error and not a refusal. A foreign key that is not total is legal, and what it
1765        // costs is the monotone form, because every bit of that vector is already spoken for.
1766        let foreign = vec![Some(1), Some(2), None, Some(9999), Some(3)];
1767        let path = related("orphans", 10, &foreign);
1768        let report = build_links(&path, &[edge()]).expect("build");
1769        assert!(report[0].built, "{:?}", report[0].note);
1770        assert_eq!(report[0].form, Some(link::Form::Packed));
1771        assert_eq!(report[0].children, 5);
1772        assert_eq!(report[0].linked, 3, "the null and the key that matches nothing are not links");
1773
1774        let link = links(&path, &foreign);
1775        assert_eq!(link.forward(2), None, "a null is not a link");
1776        assert_eq!(link.forward(3), None, "a key that matches nothing is not a link");
1777
1778        fs::remove_file(&path).expect("clean up");
1779    }
1780
1781    #[test]
1782    fn a_parent_with_no_key_map_stored_gets_its_link_from_a_map_built_for_it() {
1783        // A key map the budget turned away, or one nobody asked for, is not a reason to go without
1784        // the link: the build makes the map it needs and keeps only the link.
1785        let path = table_of("unmapped", &(1..=100_i64).map(Some).collect::<Vec<_>>());
1786        let mut writer = Writer::open(&path, "child", vec![Field::new("fk", LogicalType::BigInt)])
1787            .expect("a second table");
1788        let values = (1..=100_i64).map(Value::BigInt).collect::<Vec<_>>();
1789        writer
1790            .append(
1791                &Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1792                    .expect("one column"),
1793            )
1794            .expect("a part");
1795        writer.finish().expect("commit");
1796
1797        let report = build_links(&path, &[edge()]).expect("build");
1798        assert!(report[0].built, "{:?}", report[0].note);
1799
1800        let catalog = Catalog::open(&path).expect("reopen");
1801        let child = catalog.table("child").expect("the child");
1802        let parent = catalog.table("parent").expect("the parent");
1803        assert!(key_map(&parent, 0).is_none(), "the map it was built with is not kept");
1804        let link = stored_link(&child, &parent, &edge()).expect("the link is kept");
1805        assert_eq!(link.linked(), 100);
1806        assert_eq!(link.forward(99), Some(99));
1807
1808        fs::remove_file(&path).expect("clean up");
1809    }
1810
1811    #[test]
1812    fn a_packed_link_leaves_the_children_of_every_parent_beside_it() {
1813        // Six children a parent, scattered, so the link is packed and the lists are not ranges.
1814        let foreign = (0..6000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1815        let path = related("adjacency", 1000, &foreign);
1816        let report = build_links(&path, &[edge()]).expect("build");
1817        assert_eq!(report[0].form, Some(link::Form::Packed));
1818        assert!(report[0].adjacency, "a packed link's adjacency fits the floor");
1819
1820        let catalog = Catalog::open(&path).expect("reopen");
1821        let child = catalog.table("child").expect("the child");
1822        let parent = catalog.table("parent").expect("the parent");
1823        let adjacency = stored_adjacency(&child, &parent, &edge()).expect("it is in the file");
1824        assert_eq!(
1825            (adjacency.children(), adjacency.parents(), adjacency.edges()),
1826            (6000, 1000, 6000)
1827        );
1828        let mut listed = Vec::new();
1829        for held in [0_u64, 1, 500, 999] {
1830            listed.clear();
1831            adjacency.children_of(held, &mut listed).expect("a parent in range");
1832            let slow = (0..6000_u64).filter(|&at| (at * 7) % 1000 == held).collect::<Vec<_>>();
1833            assert_eq!(listed, slow, "parent {held}");
1834        }
1835        let wrong = Edge { parent: "child".into(), ..edge() };
1836        assert!(stored_adjacency(&child, &parent, &wrong).is_none(), "a different parent name");
1837
1838        fs::remove_file(&path).expect("clean up");
1839    }
1840
1841    #[test]
1842    fn a_link_asked_for_against_the_wrong_parent_is_not_handed_over() {
1843        // The binding check. The section's own id says which child column the link is for and
1844        // nothing about which table it points into, so a caller that asked with a different parent
1845        // would otherwise be handed rids of a table it never named.
1846        let foreign = (0..500_i64).map(|child| Some(child / 5 + 1)).collect::<Vec<_>>();
1847        let path = related("binding", 100, &foreign);
1848        build_links(&path, &[edge()]).expect("build");
1849
1850        let catalog = Catalog::open(&path).expect("reopen");
1851        let child = catalog.table("child").expect("the child");
1852        let parent = catalog.table("parent").expect("the parent");
1853        let held = stored_link(&child, &parent, &edge()).expect("the link is handed over");
1854        // The header alone says what the link says, and is refused wherever the link is.
1855        let counts = stored_link_counts(&child, &parent, &edge()).expect("and so are its counts");
1856        assert_eq!(
1857            counts,
1858            link::Counts {
1859                children: held.children(),
1860                parents: held.parents(),
1861                linked: held.linked(),
1862                form: held.form()
1863            }
1864        );
1865        assert_eq!((counts.children, counts.linked), (500, 500));
1866        let wrong = Edge { parent: "child".into(), ..edge() };
1867        assert!(stored_link(&child, &parent, &wrong).is_none(), "a different parent name");
1868        assert!(stored_link_counts(&child, &parent, &wrong).is_none(), "a different parent name");
1869        let wrong = Edge { parent_column: 1, ..edge() };
1870        assert!(stored_link(&child, &parent, &wrong).is_none(), "a different parent column");
1871        assert!(stored_link_counts(&child, &parent, &wrong).is_none(), "a different parent column");
1872        let wrong = Edge { child_column: 1, ..edge() };
1873        assert!(stored_link(&child, &parent, &wrong).is_none(), "a different child column");
1874        assert!(stored_link_counts(&child, &parent, &wrong).is_none(), "a different child column");
1875
1876        fs::remove_file(&path).expect("clean up");
1877    }
1878
1879    #[test]
1880    fn a_link_does_not_count_against_the_key_maps_of_its_table() {
1881        // `orders` is the case: a child of `customer` whose link to it is 3.4 MB, and a parent of
1882        // `lineitem` whose key map is 4.7 MB stored in date order. Out of one share the link took
1883        // the room the map needed, so each kind is counted against its own.
1884        let foreign = (0..20_000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1885        let path = related("apart_kinds", 1000, &foreign);
1886        let report = build_links(&path, &[edge()]).expect("build");
1887        assert!(report[0].built, "{:?}", report[0].note);
1888
1889        let catalog = Catalog::open(&path).expect("reopen");
1890        let child = catalog.table("child").expect("the child");
1891        let link = held_kind_bytes(&child, *section::FORWARD_LINK, &[]).expect("held");
1892        assert!(link > 0, "the link is in the file");
1893        assert_eq!(held_bytes(&child, &[]).expect("held"), 0, "and costs the key maps nothing");
1894        // The adjacency built beside the link is its own kind again and counted on its own.
1895        assert!(held_kind_bytes(&child, *section::ADJACENCY, &[]).expect("held") > 0);
1896        assert_eq!(held_kind_bytes(&child, *section::FORWARD_LINK, &[0]).expect("held"), 0);
1897
1898        fs::remove_file(&path).expect("clean up");
1899    }
1900
1901    #[test]
1902    fn a_link_that_does_not_fit_the_budget_is_reported_rather_than_stored() {
1903        // Zero percent, which the floor lifts to sixty four kilobytes, against a packed link over
1904        // sixty thousand children at ten bits each, which is seventy five.
1905        let foreign = (0..60_000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1906        let path = related("budget", 1000, &foreign);
1907        let report = build_links_within(&path, &[edge()], 0).expect("build");
1908        assert!(!report[0].built);
1909        assert!(report[0].bytes > 0, "the report says what a larger budget would buy");
1910        assert!(report[0].note.as_deref().unwrap_or_default().contains("budget"), "{report:?}");
1911
1912        let catalog = Catalog::open(&path).expect("reopen");
1913        let child = catalog.table("child").expect("the child");
1914        let parent = catalog.table("parent").expect("the parent");
1915        assert!(stored_link(&child, &parent, &edge()).is_none());
1916        // Measured before it was refused, and not written, because the shape of a relationship the
1917        // file cannot follow describes a plan nobody can make.
1918        assert!(report[0].degrees.is_some(), "it was measured");
1919        assert!(stored_degrees(&child, 0).is_none(), "and not written");
1920        // What does survive is the size and the form, which is exit criterion 3 of G3: somebody
1921        // deciding whether to raise `graph_budget` reads this rather than rebuilding to find out.
1922        assert_eq!(refused_link(&child, 0), Some((link::Form::Packed, report[0].bytes as u64)));
1923
1924        fs::remove_file(&path).expect("clean up");
1925    }
1926
1927    #[test]
1928    fn the_budget_keeps_the_link_that_saves_the_larger_hash_table() {
1929        // Two links out of one child that cannot both fit under the sixty four kilobyte floor. The
1930        // one to a thousand parents is ten bits a child and about 57 kilobytes, the one to four is
1931        // two bits and about 12. By child rows per byte the small one wins, and it saves a hash
1932        // table of four rows. The large one saves a thousand, which is what the budget is for.
1933        let path = table_of("rank", &(1..=1000).map(Some).collect::<Vec<_>>());
1934        let small = [Field::new("key", LogicalType::BigInt)];
1935        let mut writer = Writer::open(&path, "small", small.to_vec()).expect("a second table");
1936        let keys = (1..=4).map(Value::BigInt).collect::<Vec<_>>();
1937        let chunk =
1938            Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &keys).expect("keys")])
1939                .expect("one column");
1940        writer.append(&chunk).expect("a part");
1941        writer.finish().expect("commit");
1942        let rows = (0..45_000_i64)
1943            .map(|child| (Some((child * 7) % 1000 + 1), Some(child % 4 + 1)))
1944            .collect::<Vec<_>>();
1945        let fields = vec![
1946            Field::new("large", LogicalType::BigInt),
1947            Field::new("small", LogicalType::BigInt),
1948        ];
1949        pairs_into(Writer::open(&path, "child", fields).expect("a third table"), &rows);
1950        build_key_maps(&path, "parent", &[0]).expect("the large key map");
1951        build_key_maps(&path, "small", &[0]).expect("the small key map");
1952
1953        let edges = [
1954            edge(),
1955            Edge {
1956                child: "child".into(),
1957                child_column: 1,
1958                parent: "small".into(),
1959                parent_column: 0,
1960            },
1961        ];
1962        let report = build_links_within(&path, &edges, 0).expect("build");
1963        assert!(report[1].bytes < report[0].bytes, "the small link is the cheaper one");
1964        assert!(
1965            (report[0].bytes + report[1].bytes) as u64 > BUDGET_FLOOR,
1966            "the two have to not fit together for this to test anything"
1967        );
1968        assert_eq!((report[0].parents, report[1].parents), (1000, 4));
1969        assert!(report[0].built, "the link that saves a thousand rows was turned away: {report:?}");
1970        assert!(!report[1].built, "the link that saves four rows was kept instead");
1971
1972        fs::remove_file(&path).expect("clean up");
1973    }
1974
1975    #[test]
1976    fn a_parent_whose_key_repeats_gets_no_link_at_all() {
1977        // Section 2.3's verification, which is the one check in this layer that is about
1978        // correctness rather than speed: a link over a non-unique parent resolves to one of the
1979        // rows that held the key, and which one is an accident of the build.
1980        let path = table_of("repeats", &[Some(1), Some(1), Some(2)]);
1981        let mut writer = Writer::open(&path, "child", vec![Field::new("fk", LogicalType::BigInt)])
1982            .expect("a second table");
1983        let values = [Value::BigInt(1), Value::BigInt(2)];
1984        writer
1985            .append(
1986                &Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1987                    .expect("one column"),
1988            )
1989            .expect("a part");
1990        writer.finish().expect("commit");
1991        build_key_maps(&path, "parent", &[0]).expect("the parent's key map");
1992
1993        let report = build_links(&path, &[edge()]).expect("build");
1994        assert!(!report[0].built);
1995        assert_eq!(report[0].note.as_deref(), Some("the key of parent is not unique"));
1996
1997        fs::remove_file(&path).expect("clean up");
1998    }
1999}