1use 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#[derive(Debug)]
35pub struct KeyColumn<'a> {
36 reader: &'a Reader,
37 columns: Vec<usize>,
38}
39
40impl<'a> KeyColumn<'a> {
41 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 _ => None,
85 },
86 };
87 each(key)?;
88 }
89 }
90 Ok(())
91 }
92}
93
94const PAIR: usize = 1 << 31;
103
104const PAIR_BITS: u32 = 15;
106
107#[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#[must_use]
123pub fn pair(first: usize, second: usize) -> Option<usize> {
124 key_of(&[first, second])
125}
126
127#[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
137fn 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
155fn 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
171fn 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
189fn 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#[derive(Debug, Clone, Copy)]
227pub struct Built {
228 pub column: usize,
230 pub form: Form,
232 pub rows: u64,
234 pub distinct: bool,
237 pub bytes: usize,
239 pub column_bytes: u64,
241 pub built: bool,
245 pub build: Duration,
247}
248
249pub fn build_key_map(reader: &Reader, column: usize) -> Result<KeyMap> {
255 KeyMap::build_from(&KeyColumn::new(reader, column)?)
256}
257
258pub const BUDGET_SHARE: u64 = 10;
266
267pub const ADJACENCY_SHARE: u64 = 25;
277
278pub const KEY_MAP_SHARE: u64 = 25;
289
290pub const BUDGET_FLOOR: u64 = 64 * 1024;
303
304pub 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
317pub 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 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 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 drop(reader);
388 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
409fn held_bytes(reader: &Reader, replacing: &[usize]) -> Result<u64> {
420 held_kind_bytes(reader, *section::KEY_MAP, replacing)
421}
422
423#[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 if tag != key_tag(table.fields(), column)? {
448 return None;
449 }
450 Some(map)
451}
452
453#[derive(Debug, Clone)]
459pub struct Edge {
460 pub child: String,
462 pub child_column: usize,
464 pub parent: String,
466 pub parent_column: usize,
468}
469
470#[derive(Debug, Clone)]
472pub struct BuiltLink {
473 pub edge: Edge,
475 pub form: Option<link::Form>,
477 pub children: u64,
479 pub parents: u64,
481 pub linked: u64,
484 pub bytes: usize,
486 pub table_bytes: u64,
489 pub degrees: Option<Degrees>,
495 pub built: bool,
497 pub adjacency_bytes: usize,
500 pub adjacency: bool,
502 pub note: Option<String>,
504 pub build: Duration,
506}
507
508pub fn build_links(path: &Path, edges: &[Edge]) -> Result<Vec<BuiltLink>> {
520 build_links_within(path, edges, BUDGET_SHARE)
521}
522
523pub 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
552fn 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 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 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 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 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 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
734type OneLink = (BuiltLink, Vec<u8>, Option<Vec<u8>>);
736
737fn 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 let degrees = Degrees::of(&parents_of, map.len(), true);
785 let bytes = encode_link(&link, &parent, edge).map_err(|error| error.to_string())?;
786 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
818fn 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
843fn cost(bytes: usize) -> u32 {
849 u32::try_from(bytes).unwrap_or(u32::MAX)
850}
851
852fn binding_bytes(parent: &str) -> usize {
857 16 + parent.len().div_ceil(8) * 8
858}
859
860fn 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
888fn 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#[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#[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#[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#[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
989fn 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
1000fn 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#[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#[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#[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#[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
1079fn 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
1090fn 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 fn graph_sections(reader: &Reader) -> Vec<§ion::Section> {
1134 reader.table().sections().iter().filter(|held| held.among(section::GRAPH_KINDS)).collect()
1135 }
1136
1137 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 type Pair = (Option<i64>, Option<i64>);
1535
1536 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 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 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)); 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 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 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 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 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 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 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 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 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 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 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 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 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 assert!(report[0].degrees.is_some(), "it was measured");
1858 assert!(stored_degrees(&child, 0).is_none(), "and not written");
1859 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 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 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}