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