1use std::collections::HashMap;
16use std::path::Path;
17use std::sync::{Arc, Mutex};
18use std::time::{Duration, Instant};
19
20use rudb_common::{LogicalType, Result, Value};
21use rudb_graph::{Adjacency, Degrees, Form, KeyMap, Keys, NO_PARENT, link, wire};
22use rudb_vector::Chunk;
23
24use crate::section::{self, Attachment};
25use crate::{Catalog, Reader, invalid, type_tag};
26
27#[derive(Debug)]
37pub struct KeyColumn<'a> {
38 reader: &'a Reader,
39 columns: Vec<usize>,
40}
41
42impl<'a> KeyColumn<'a> {
43 pub fn new(reader: &'a Reader, key: usize) -> Result<Self> {
52 let fields = reader.table().fields();
53 let columns = columns_of(key);
54 for &column in &columns {
55 let Some(field) = fields.get(column) else {
56 return Err(invalid(&format!(
57 "column {column} is past the {} of table {}",
58 fields.len(),
59 reader.table().name()
60 )));
61 };
62 if !mappable(&field.ty) {
63 return Err(invalid(&format!(
64 "a key map over {} needs an integer key form, and {} has none",
65 field.name, field.ty
66 )));
67 }
68 }
69 Ok(Self { reader, columns })
70 }
71}
72
73impl Keys for KeyColumn<'_> {
74 fn scan(&self, each: &mut dyn FnMut(Option<i128>) -> Result<()>) -> Result<()> {
75 for part in 0..self.reader.parts() {
76 let chunk = self.reader.read(part, &self.columns)?;
77 let first = chunk.column(0)?;
78 let second = if self.columns.len() == 2 { Some(chunk.column(1)?) } else { None };
79 for row in 0..chunk.len() {
80 let key = key_at(&chunk, first, 0, row)?;
81 let key = match second {
82 None => key,
83 Some(second) => match (key, key_at(&chunk, second, 1, row)?) {
84 (Some(high), Some(low)) => Some(fold(high, low)?),
85 _ => None,
87 },
88 };
89 each(key)?;
90 }
91 }
92 Ok(())
93 }
94}
95
96const PAIR: usize = 1 << 31;
105
106const PAIR_BITS: u32 = 15;
108
109#[must_use]
114pub fn key_of(columns: &[usize]) -> Option<usize> {
115 let fits = |column: usize| column < 1 << PAIR_BITS;
116 match *columns {
117 [column] if column < PAIR => Some(column),
118 [first, second] if fits(first) && fits(second) => Some(PAIR | first << PAIR_BITS | second),
119 _ => None,
120 }
121}
122
123#[must_use]
125pub fn pair(first: usize, second: usize) -> Option<usize> {
126 key_of(&[first, second])
127}
128
129#[must_use]
131pub fn columns_of(key: usize) -> Vec<usize> {
132 if key & PAIR == 0 {
133 return vec![key];
134 }
135 let mask = (1 << PAIR_BITS) - 1;
136 vec![(key >> PAIR_BITS) & mask, key & mask]
137}
138
139fn fold(high: i128, low: i128) -> Result<i128> {
149 const SHIFT: i128 = 1 << 32;
150 let fits = |value: i128| i128::from(i32::MIN) <= value && value <= i128::from(i32::MAX);
151 if !fits(high) || !fits(low) {
152 return Err(invalid("a two column key holds a value too wide to fold into one key"));
153 }
154 Ok(high * SHIFT + (low - i128::from(i32::MIN)))
155}
156
157fn key_tag(fields: &[rudb_common::Field], key: usize) -> Option<u8> {
162 match *columns_of(key) {
163 [column] => type_tag(&fields.get(column)?.ty).ok(),
164 [first, second] => {
165 fields.get(first)?;
166 fields.get(second)?;
167 type_tag(&LogicalType::BigInt).ok()
168 }
169 _ => None,
170 }
171}
172
173fn mappable(ty: &LogicalType) -> bool {
175 matches!(
176 ty,
177 LogicalType::TinyInt
178 | LogicalType::SmallInt
179 | LogicalType::Integer
180 | LogicalType::BigInt
181 | LogicalType::HugeInt
182 | LogicalType::UTinyInt
183 | LogicalType::USmallInt
184 | LogicalType::UInteger
185 | LogicalType::UBigInt
186 | LogicalType::Date
187 | LogicalType::Decimal { .. }
188 )
189}
190
191fn key_at(
199 chunk: &Chunk,
200 values: &rudb_vector::Vector,
201 column: usize,
202 row: usize,
203) -> Result<Option<i128>> {
204 if let Some(key) = values.signed_at(row) {
205 return Ok(Some(key));
206 }
207 match chunk.value_at(row, column) {
208 Value::Null => Ok(None),
209 Value::TinyInt(key) => Ok(Some(i128::from(key))),
210 Value::SmallInt(key) => Ok(Some(i128::from(key))),
211 Value::Integer(key) | Value::Date(key) => Ok(Some(i128::from(key))),
212 Value::BigInt(key) | Value::Time(key) | Value::Timestamp(key) => Ok(Some(i128::from(key))),
213 Value::HugeInt(key) | Value::Decimal { unscaled: key, .. } => Ok(Some(key)),
214 Value::UTinyInt(key) => Ok(Some(i128::from(key))),
215 Value::USmallInt(key) => Ok(Some(i128::from(key))),
216 Value::UInteger(key) => Ok(Some(i128::from(key))),
217 Value::UBigInt(key) => Ok(Some(i128::from(key))),
218 other => Err(invalid(&format!("a key column holds {other}, which is not a key"))),
219 }
220}
221
222#[derive(Debug, Clone, Copy)]
229pub struct Built {
230 pub column: usize,
232 pub form: Form,
234 pub rows: u64,
236 pub distinct: bool,
239 pub bytes: usize,
241 pub column_bytes: u64,
243 pub built: bool,
247 pub build: Duration,
249}
250
251pub fn build_key_map(reader: &Reader, column: usize) -> Result<KeyMap> {
257 KeyMap::build_from(&KeyColumn::new(reader, column)?)
258}
259
260pub const BUDGET_SHARE: u64 = 10;
268
269pub const ADJACENCY_SHARE: u64 = 25;
279
280pub const KEY_MAP_SHARE: u64 = 25;
291
292pub const BUDGET_FLOOR: u64 = 64 * 1024;
305
306pub fn build_key_maps(path: &Path, table: &str, columns: &[usize]) -> Result<Vec<Built>> {
316 build_key_maps_within(path, table, columns, KEY_MAP_SHARE)
317}
318
319pub fn build_key_maps_within(
337 path: &Path,
338 table: &str,
339 columns: &[usize],
340 share: u64,
341) -> Result<Vec<Built>> {
342 let reader = Catalog::open(path)?.table(table)?;
343 let column_bytes = reader.layout().columns_total();
344 let allowance = (column_bytes.saturating_mul(share) / 100).max(BUDGET_FLOOR);
345 let mut spent = held_bytes(&reader, columns)?;
346 let mut report = Vec::with_capacity(columns.len());
347 let mut payloads = Vec::with_capacity(columns.len());
348 for &column in columns {
349 let start = Instant::now();
350 let map = build_key_map(&reader, column)?;
351 let tag = key_tag(reader.table().fields(), column)
352 .ok_or_else(|| invalid("a key map over a column the table does not have"))?;
353 let payload = wire::encode(&map, tag)?;
354 report.push(Built {
355 column,
356 form: map.form(),
357 rows: map.observed().rows,
358 distinct: map.observed().distinct,
359 bytes: payload.bytes.len(),
360 column_bytes,
361 built: false,
362 build: start.elapsed(),
363 });
364 payloads.push((column, payload));
365 }
366 let mut order = (0..payloads.len()).collect::<Vec<_>>();
369 order.sort_by_key(|&at| payloads[at].1.bytes.len());
370 let mut keep = vec![false; payloads.len()];
371 for at in order {
372 if !report[at].distinct {
377 continue;
378 }
379 let cost = payloads[at].1.bytes.len() as u64;
380 if spent.saturating_add(cost) <= allowance {
381 spent += cost;
382 keep[at] = true;
383 report[at].built = true;
384 }
385 }
386 drop(reader);
390 let attachments = payloads
395 .iter()
396 .zip(&keep)
397 .map(|((column, payload), &keep)| {
398 Ok(Attachment {
399 kind: *section::KEY_MAP,
400 id: u64::try_from(*column).map_err(|_| invalid("column index overflow"))?,
401 flags: payload.flags,
402 header_bytes: if keep { payload.header_bytes } else { cost(payload.bytes.len()) },
403 bytes: if keep { &payload.bytes } else { &[] },
404 })
405 })
406 .collect::<Result<Vec<_>>>()?;
407 crate::attach(path, table, &attachments)?;
408 Ok(report)
409}
410
411fn held_bytes(reader: &Reader, replacing: &[usize]) -> Result<u64> {
422 held_kind_bytes(reader, *section::KEY_MAP, replacing)
423}
424
425#[must_use]
434pub fn key_map(reader: &Reader, column: usize) -> Option<KeyMap> {
435 let table = reader.table();
436 let id = u64::try_from(column).ok()?;
437 let held = table
438 .sections()
439 .iter()
440 .find(|section| section.kind == *section::KEY_MAP && section.id == id)?;
441 if !held.usable(table.generation()) {
442 return None;
443 }
444 let (map, tag) = wire::decode(&reader.payload(held).ok()?).ok()?;
445 if tag != key_tag(table.fields(), column)? {
450 return None;
451 }
452 Some(map)
453}
454
455#[derive(Debug, Default)]
470pub(crate) struct Decoded {
471 key_maps: Mutex<HashMap<usize, Option<Arc<KeyMap>>>>,
472 links: Mutex<HashMap<Binding, Option<Arc<link::Link>>>>,
473 adjacencies: Mutex<HashMap<Binding, Option<Arc<Adjacency>>>>,
474}
475
476type Binding = (usize, String, usize, u64);
478
479fn binding_of(parent: &Reader, edge: &Edge) -> Binding {
480 (edge.child_column, edge.parent.clone(), edge.parent_column, parent.table().generation())
481}
482
483fn once<K: std::hash::Hash + Eq, T>(
486 held: &Mutex<HashMap<K, Option<Arc<T>>>>,
487 key: K,
488 make: impl FnOnce() -> Option<T>,
489) -> Option<Arc<T>> {
490 let mut held = held.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
491 held.entry(key).or_insert_with(|| make().map(Arc::new)).clone()
492}
493
494#[must_use]
496pub fn shared_key_map(reader: &Reader, column: usize) -> Option<Arc<KeyMap>> {
497 once(&reader.graph.key_maps, column, || key_map(reader, column))
498}
499
500#[must_use]
502pub fn shared_link(child: &Reader, parent: &Reader, edge: &Edge) -> Option<Arc<link::Link>> {
503 once(&child.graph.links, binding_of(parent, edge), || stored_link(child, parent, edge))
504}
505
506#[must_use]
508pub fn shared_adjacency(child: &Reader, parent: &Reader, edge: &Edge) -> Option<Arc<Adjacency>> {
509 once(&child.graph.adjacencies, binding_of(parent, edge), || {
510 stored_adjacency(child, parent, edge)
511 })
512}
513
514#[derive(Debug, Clone)]
520pub struct Edge {
521 pub child: String,
523 pub child_column: usize,
525 pub parent: String,
527 pub parent_column: usize,
529}
530
531#[derive(Debug, Clone)]
533pub struct BuiltLink {
534 pub edge: Edge,
536 pub form: Option<link::Form>,
538 pub children: u64,
540 pub parents: u64,
542 pub linked: u64,
545 pub bytes: usize,
547 pub table_bytes: u64,
550 pub degrees: Option<Degrees>,
556 pub built: bool,
558 pub adjacency_bytes: usize,
561 pub adjacency: bool,
563 pub note: Option<String>,
565 pub build: Duration,
567}
568
569pub fn build_links(path: &Path, edges: &[Edge]) -> Result<Vec<BuiltLink>> {
581 build_links_within(path, edges, BUDGET_SHARE)
582}
583
584pub fn build_links_within(path: &Path, edges: &[Edge], share: u64) -> Result<Vec<BuiltLink>> {
599 let mut tables: Vec<&str> = Vec::new();
600 for edge in edges {
601 if !tables.iter().any(|held| *held == edge.child) {
602 tables.push(&edge.child);
603 }
604 }
605 let mut report = Vec::with_capacity(edges.len());
606 for table in tables {
607 let mine = edges.iter().filter(|edge| edge.child == table).cloned().collect::<Vec<Edge>>();
608 report.extend(links_of_one_table(path, table, &mine, share)?);
609 }
610 Ok(report)
611}
612
613fn links_of_one_table(
615 path: &Path,
616 table: &str,
617 edges: &[Edge],
618 share: u64,
619) -> Result<Vec<BuiltLink>> {
620 let catalog = Catalog::open(path)?;
621 let child = catalog.table(table)?;
622 let column_bytes = child.layout().columns_total();
623 let allowance = (column_bytes.saturating_mul(share) / 100).max(BUDGET_FLOOR);
624 let replacing = edges.iter().map(|edge| edge.child_column).collect::<Vec<usize>>();
625 let index_allowance = (column_bytes.saturating_mul(ADJACENCY_SHARE) / 100).max(BUDGET_FLOOR);
626 let mut spent = held_kind_bytes(&child, *section::FORWARD_LINK, &replacing)?;
627 let mut indexed = held_kind_bytes(&child, *section::ADJACENCY, &replacing)?;
628 let mut report = Vec::with_capacity(edges.len());
629 let mut payloads: Vec<Option<Vec<u8>>> = Vec::with_capacity(edges.len());
630 let mut adjacencies: Vec<Option<Vec<u8>>> = Vec::with_capacity(edges.len());
631 for edge in edges {
632 let start = Instant::now();
633 match one_link(&catalog, &child, edge) {
634 Ok((built, bytes, adjacency)) => {
635 report.push(BuiltLink {
636 build: start.elapsed(),
637 table_bytes: column_bytes,
638 ..built
639 });
640 payloads.push(Some(bytes));
641 adjacencies.push(adjacency);
642 }
643 Err(note) => {
644 report.push(BuiltLink {
645 edge: edge.clone(),
646 form: None,
647 children: child.table().rows() as u64,
648 parents: 0,
649 linked: 0,
650 bytes: 0,
651 table_bytes: column_bytes,
652 degrees: None,
653 built: false,
654 adjacency_bytes: 0,
655 adjacency: false,
656 note: Some(note),
657 build: start.elapsed(),
658 });
659 payloads.push(None);
660 adjacencies.push(None);
661 }
662 }
663 }
664 let edge_count = report.len();
667 let mut order = (0..edge_count)
668 .filter(|at| payloads[*at].is_some())
669 .chain((0..edge_count).filter(|at| adjacencies[*at].is_some()).map(|at| at + edge_count))
670 .collect::<Vec<_>>();
671 let value = |at: usize| -> f64 {
685 if at < edge_count {
686 let bytes = report[at].bytes.max(1);
687 report[at].children.min(report[at].parents) as f64 / bytes as f64
688 } else {
689 let at = at - edge_count;
690 report[at].linked as f64 / report[at].adjacency_bytes.max(1) as f64
691 }
692 };
693 order.sort_by(|left, right| {
694 value(*right).partial_cmp(&value(*left)).unwrap_or(std::cmp::Ordering::Equal)
695 });
696 for at in order {
697 let (cost, link) = if at < edge_count {
698 (report[at].bytes as u64, true)
699 } else {
700 (report[at - edge_count].adjacency_bytes as u64, false)
701 };
702 let fits = if link {
703 spent.saturating_add(cost) <= allowance
704 } else {
705 indexed.saturating_add(cost) <= index_allowance
706 };
707 if fits && link {
708 spent += cost;
709 } else if fits {
710 indexed += cost;
711 }
712 match (link, fits) {
713 (true, true) => report[at].built = true,
714 (true, false) => {
715 report[at].note = Some(format!("over the budget of {allowance} bytes"));
716 }
717 (false, fits) => report[at - edge_count].adjacency = fits,
718 }
719 }
720 drop(child);
721 let measured = report
724 .iter()
725 .filter(|built| built.built)
726 .filter_map(|built| {
727 let mut bytes = Vec::with_capacity(rudb_graph::degree::BYTES);
728 built.degrees.as_ref()?.write(&mut bytes);
729 Some((built.edge.child_column, bytes))
730 })
731 .collect::<Vec<_>>();
732 let mut attachments = report
738 .iter()
739 .zip(&payloads)
740 .filter(|(_, payload)| payload.is_some())
741 .map(|(built, payload)| {
742 let bytes = payload.as_ref().expect("filtered to the measured");
743 Ok(Attachment {
744 kind: *section::FORWARD_LINK,
745 id: u64::try_from(built.edge.child_column)
746 .map_err(|_| invalid("column index overflow"))?,
747 flags: built.form.map_or(0, |form| u32::from(form.tag())),
748 header_bytes: if built.built {
749 u32::try_from(binding_bytes(&built.edge.parent))
750 .map_err(|_| invalid("a parent name longer than a section header"))?
751 } else {
752 cost(bytes.len())
753 },
754 bytes: if built.built { bytes } else { &[] },
755 })
756 })
757 .collect::<Result<Vec<_>>>()?;
758 for ((built, payload), adjacency) in report.iter().zip(&payloads).zip(&adjacencies) {
766 let (Some(_), Some(bytes)) = (payload, adjacency) else { continue };
767 let kept = built.adjacency;
768 attachments.push(Attachment {
769 kind: *section::ADJACENCY,
770 id: u64::try_from(built.edge.child_column)
771 .map_err(|_| invalid("column index overflow"))?,
772 flags: 0,
773 header_bytes: if kept {
774 u32::try_from(binding_bytes(&built.edge.parent))
775 .map_err(|_| invalid("a parent name longer than a section header"))?
776 } else {
777 cost(bytes.len())
778 },
779 bytes: if kept { bytes } else { &[] },
780 });
781 }
782 for (column, bytes) in &measured {
783 attachments.push(Attachment {
784 kind: *section::DEGREES,
785 id: u64::try_from(*column).map_err(|_| invalid("column index overflow"))?,
786 flags: 0,
787 header_bytes: 0,
788 bytes,
789 });
790 }
791 crate::attach(path, table, &attachments)?;
792 Ok(report)
793}
794
795type OneLink = (BuiltLink, Vec<u8>, Option<Vec<u8>>);
797
798fn one_link(
805 catalog: &Catalog,
806 child: &Reader,
807 edge: &Edge,
808) -> std::result::Result<OneLink, String> {
809 let parent =
810 catalog.table(&edge.parent).map_err(|_| format!("no table named {}", edge.parent))?;
811 let map = parent_map(&parent, edge)?;
812 if !map.observed().usable_as_parent() {
813 return Err(format!("the key of {} is not unique", edge.parent));
814 }
815 let keys = KeyColumn::new(child, edge.child_column).map_err(|error| error.to_string())?;
816 let mut parents_of = Vec::with_capacity(child.table().rows());
817 let mut failed = None;
818 keys.scan(&mut |key| {
819 let parent = match key {
820 None => NO_PARENT,
821 Some(key) => match map.lookup(key) {
822 Ok(found) => found.unwrap_or(NO_PARENT),
823 Err(error) => {
824 failed = Some(error.to_string());
825 NO_PARENT
826 }
827 },
828 };
829 parents_of.push(parent);
830 Ok(())
831 })
832 .map_err(|error| error.to_string())?;
833 if let Some(failed) = failed {
834 return Err(failed);
835 }
836 let link = link::Link::build(&parents_of, map.len()).map_err(|error| error.to_string())?;
837 let degrees = Degrees::of(&parents_of, map.len(), true);
846 let bytes = encode_link(&link, &parent, edge).map_err(|error| error.to_string())?;
847 let adjacency = match link.form() {
850 link::Form::Monotone => None,
851 link::Form::Packed => {
852 let adjacency = Adjacency::build(&parents_of, map.len())
853 .and_then(|adjacency| encode_adjacency(&adjacency, &parent, edge))
854 .map_err(|error| error.to_string())?;
855 Some(adjacency)
856 }
857 };
858 Ok((
859 BuiltLink {
860 edge: edge.clone(),
861 form: Some(link.form()),
862 children: link.children(),
863 parents: map.len(),
864 linked: link.linked(),
865 bytes: bytes.len(),
866 table_bytes: 0,
867 degrees: Some(degrees),
868 built: false,
869 adjacency_bytes: adjacency.as_ref().map_or(0, Vec::len),
870 adjacency: false,
871 note: None,
872 build: Duration::ZERO,
873 },
874 bytes,
875 adjacency,
876 ))
877}
878
879fn parent_map(parent: &Reader, edge: &Edge) -> std::result::Result<KeyMap, String> {
894 if columns_of(edge.parent_column).len() == 1
895 && let Some(stored) = key_map(parent, edge.parent_column)
896 {
897 return Ok(stored);
898 }
899 KeyColumn::new(parent, edge.parent_column)
900 .and_then(|keys| KeyMap::build_from(&keys))
901 .map_err(|error| error.to_string())
902}
903
904fn cost(bytes: usize) -> u32 {
910 u32::try_from(bytes).unwrap_or(u32::MAX)
911}
912
913fn binding_bytes(parent: &str) -> usize {
918 16 + parent.len().div_ceil(8) * 8
919}
920
921fn encode_link(link: &link::Link, parent: &Reader, edge: &Edge) -> Result<Vec<u8>> {
930 let name = edge.parent.as_bytes();
931 let mut bytes = Vec::with_capacity(binding_bytes(&edge.parent) + link.bytes());
932 bytes.extend_from_slice(&parent.table().generation().to_le_bytes());
933 bytes.extend_from_slice(
934 &u32::try_from(edge.parent_column)
935 .map_err(|_| invalid("column index overflow"))?
936 .to_le_bytes(),
937 );
938 bytes.extend_from_slice(
939 &u32::try_from(name.len())
940 .map_err(|_| invalid("a parent name longer than a u32"))?
941 .to_le_bytes(),
942 );
943 bytes.extend_from_slice(name);
944 bytes.resize(binding_bytes(&edge.parent), 0);
945 link.write(&mut bytes)?;
946 Ok(bytes)
947}
948
949fn encode_adjacency(adjacency: &Adjacency, parent: &Reader, edge: &Edge) -> Result<Vec<u8>> {
954 let mut bytes = Vec::with_capacity(binding_bytes(&edge.parent) + adjacency.bytes() + 32);
955 let name = edge.parent.as_bytes();
956 bytes.extend_from_slice(&parent.table().generation().to_le_bytes());
957 bytes.extend_from_slice(
958 &u32::try_from(edge.parent_column)
959 .map_err(|_| invalid("column index overflow"))?
960 .to_le_bytes(),
961 );
962 bytes.extend_from_slice(
963 &u32::try_from(name.len())
964 .map_err(|_| invalid("a parent name longer than a u32"))?
965 .to_le_bytes(),
966 );
967 bytes.extend_from_slice(name);
968 bytes.resize(binding_bytes(&edge.parent), 0);
969 adjacency.write(&mut bytes)?;
970 Ok(bytes)
971}
972
973#[must_use]
976pub fn stored_adjacency(child: &Reader, parent: &Reader, edge: &Edge) -> Option<Adjacency> {
977 let table = child.table();
978 let id = u64::try_from(edge.child_column).ok()?;
979 let held = table
980 .sections()
981 .iter()
982 .find(|section| section.kind == *section::ADJACENCY && section.id == id)?;
983 if !held.usable(table.generation()) || held.refused().is_some() {
984 return None;
985 }
986 let bytes = child.payload(held).ok()?;
987 let binding = bound(&bytes, parent, edge)?;
988 Adjacency::read_from(bytes, binding).ok()
989}
990
991#[must_use]
999pub fn stored_link(child: &Reader, parent: &Reader, edge: &Edge) -> Option<link::Link> {
1000 let held = link_section(child, edge)?;
1001 let bytes = child.payload(held).ok()?;
1002 let binding = bound(&bytes, parent, edge)?;
1003 link::Link::read_from(bytes, binding).ok()
1004}
1005
1006#[must_use]
1017pub fn stored_link_counts(child: &Reader, parent: &Reader, edge: &Edge) -> Option<link::Counts> {
1018 let held = link_section(child, edge)?;
1019 let binding = binding_bytes(&edge.parent);
1020 let bytes = child.payload_head(held, binding + link::HEADER_BYTES).ok()?;
1021 let binding = bound(&bytes, parent, edge)?;
1022 link::Link::counts(&bytes[binding..]).ok()
1023}
1024
1025#[must_use]
1032pub fn link_parent(child: &Reader, child_column: usize) -> Option<(String, usize)> {
1033 let table = child.table();
1034 let id = u64::try_from(child_column).ok()?;
1035 let held = table
1036 .sections()
1037 .iter()
1038 .find(|section| section.kind == *section::FORWARD_LINK && section.id == id)?;
1039 if !held.usable(table.generation()) || held.refused().is_some() {
1040 return None;
1041 }
1042 let head = child.payload_head(held, 16).ok()?;
1043 let column = u32::from_le_bytes(head.get(8..12)?.try_into().ok()?);
1044 let length = u32::from_le_bytes(head.get(12..16)?.try_into().ok()?) as usize;
1045 let bytes = child.payload_head(held, 16 + length).ok()?;
1046 let name = std::str::from_utf8(bytes.get(16..16 + length)?).ok()?;
1047 Some((name.to_owned(), column as usize))
1048}
1049
1050fn link_section<'a>(child: &'a Reader, edge: &Edge) -> Option<&'a section::Section> {
1052 let table = child.table();
1053 let id = u64::try_from(edge.child_column).ok()?;
1054 let held = table
1055 .sections()
1056 .iter()
1057 .find(|section| section.kind == *section::FORWARD_LINK && section.id == id)?;
1058 held.usable(table.generation()).then_some(held)
1059}
1060
1061fn bound(bytes: &[u8], parent: &Reader, edge: &Edge) -> Option<usize> {
1064 let binding = binding_bytes(&edge.parent);
1065 if bytes.len() < binding {
1066 return None;
1067 }
1068 let generation = u64::from_le_bytes(bytes[0..8].try_into().ok()?);
1069 let column = u32::from_le_bytes(bytes[8..12].try_into().ok()?);
1070 let length = u32::from_le_bytes(bytes[12..16].try_into().ok()?) as usize;
1071 if generation != parent.table().generation()
1072 || column as usize != edge.parent_column
1073 || length != edge.parent.len()
1074 || &bytes[16..16 + length] != edge.parent.as_bytes()
1075 {
1076 return None;
1077 }
1078 Some(binding)
1079}
1080
1081#[must_use]
1088pub fn stored_degrees(child: &Reader, child_column: usize) -> Option<Degrees> {
1089 let table = child.table();
1090 let id = u64::try_from(child_column).ok()?;
1091 let held = table
1092 .sections()
1093 .iter()
1094 .find(|section| section.kind == *section::DEGREES && section.id == id)?;
1095 if !held.usable(table.generation()) {
1096 return None;
1097 }
1098 Degrees::read(&child.payload(held).ok()?).ok()
1099}
1100
1101#[must_use]
1108pub fn holds_key_map(reader: &Reader, column: usize) -> bool {
1109 let table = reader.table();
1110 let Ok(id) = u64::try_from(column) else { return false };
1111 table.sections().iter().any(|section| {
1112 section.kind == *section::KEY_MAP
1113 && section.id == id
1114 && section.usable(table.generation())
1115 && section.refused().is_none()
1116 })
1117}
1118
1119#[must_use]
1126pub fn refused_key_map(reader: &Reader, column: usize) -> Option<(Form, u64)> {
1127 let (form, bytes) = refused(reader, *section::KEY_MAP, column)?;
1128 Some((Form::from_tag(form).ok()?, bytes))
1129}
1130
1131#[must_use]
1135pub fn refused_link(child: &Reader, child_column: usize) -> Option<(link::Form, u64)> {
1136 let (form, bytes) = refused(child, *section::FORWARD_LINK, child_column)?;
1137 Some((link::Form::from_tag(form).ok()?, bytes))
1138}
1139
1140fn refused(reader: &Reader, kind: [u8; 8], id: usize) -> Option<(u8, u64)> {
1142 let table = reader.table();
1143 let id = u64::try_from(id).ok()?;
1144 let held = table.sections().iter().find(|section| section.kind == kind && section.id == id)?;
1145 if !held.usable(table.generation()) {
1146 return None;
1147 }
1148 Some((u8::try_from(held.flags).ok()?, held.refused()?))
1149}
1150
1151fn held_kind_bytes(reader: &Reader, kind: [u8; 8], replacing: &[usize]) -> Result<u64> {
1157 let mut total = 0;
1158 for held in reader.table().sections() {
1159 if held.kind != kind || !held.usable(reader.table().generation()) {
1160 continue;
1161 }
1162 if replacing.iter().any(|&id| u64::try_from(id) == Ok(held.id)) {
1163 continue;
1164 }
1165 let Ok(extents) = reader.extents(held) else { continue };
1166 total += extents.iter().map(|extent| u64::from(extent.length)).sum::<u64>();
1167 }
1168 Ok(total)
1169}
1170
1171#[cfg(test)]
1172mod tests {
1173 use std::fs;
1174 use std::path::PathBuf;
1175 use std::time::{SystemTime, UNIX_EPOCH};
1176
1177 use rudb_common::Field;
1178 use rudb_graph::Rid;
1179 use rudb_vector::Vector;
1180
1181 use super::*;
1182 use crate::Writer;
1183
1184 fn path(label: &str) -> PathBuf {
1185 let stamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("time advances").as_nanos();
1186 std::env::temp_dir().join(format!("rudb-graph-{label}-{}-{stamp}.rdb", std::process::id()))
1187 }
1188
1189 fn graph_sections(reader: &Reader) -> Vec<§ion::Section> {
1195 reader.table().sections().iter().filter(|held| held.among(section::GRAPH_KINDS)).collect()
1196 }
1197
1198 fn table_of(label: &str, keys: &[Option<i64>]) -> PathBuf {
1200 let path = path(label);
1201 let mut writer =
1202 Writer::create(&path, "parent", vec![Field::new("key", LogicalType::BigInt)])
1203 .expect("new file");
1204 for part in keys.chunks(1000) {
1205 let values =
1206 part.iter().map(|key| key.map_or(Value::Null, Value::BigInt)).collect::<Vec<_>>();
1207 let chunk =
1208 Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1209 .expect("one column");
1210 writer.append(&chunk).expect("a part");
1211 }
1212 writer.finish().expect("commit");
1213 path
1214 }
1215
1216 fn resolves(keys: &[Option<i64>], map: &KeyMap) {
1218 for (rid, key) in keys.iter().enumerate() {
1219 let Some(key) = *key else { continue };
1220 let found =
1221 map.lookup(i128::from(key)).expect("lookup").expect("a key in the column resolves");
1222 assert_eq!(found, rid as Rid, "key {key} resolved to {found} rather than {rid}");
1223 }
1224 }
1225
1226 #[test]
1227 fn a_key_map_built_over_a_file_resolves_every_key_to_its_own_row() {
1228 let keys = (1..=3000_i64).map(Some).collect::<Vec<_>>();
1233 let path = table_of("identity", &keys);
1234 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1235 assert_eq!(built.len(), 1);
1236 assert_eq!(built[0].form, Form::Identity);
1237 assert_eq!(built[0].rows, 3000);
1238 assert!(built[0].distinct);
1239 assert_eq!(built[0].bytes, wire::HEADER_BYTES, "identity is a header and nothing else");
1240
1241 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1242 let map = key_map(&reader, 0).expect("the map is in the file");
1243 assert_eq!(map.form(), Form::Identity);
1244 resolves(&keys, &map);
1245 assert_eq!(map.lookup(0).expect("a key below the column"), None);
1246 assert_eq!(map.lookup(3001).expect("a key past the column"), None);
1247
1248 fs::remove_file(&path).expect("clean up");
1249 }
1250
1251 #[test]
1252 fn a_column_with_gaps_takes_the_bitmap_form_and_still_resolves() {
1253 let keys = (0..2000_i64).map(|value| Some(value * 4 + 7)).collect::<Vec<_>>();
1254 let path = table_of("dense", &keys);
1255 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1256 assert_eq!(built[0].form, Form::Dense);
1257
1258 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1259 let map = key_map(&reader, 0).expect("the map is in the file");
1260 resolves(&keys, &map);
1261 assert_eq!(map.lookup(8).expect("a value in the range but not the column"), None);
1262
1263 fs::remove_file(&path).expect("clean up");
1264 }
1265
1266 #[test]
1267 fn a_column_out_of_order_takes_the_sorted_form_and_still_resolves() {
1268 let keys = (0..1500_i64).map(|value| Some((value * 7919) % 100_003)).collect::<Vec<_>>();
1269 let path = table_of("sorted", &keys);
1270 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1271 assert_eq!(built[0].form, Form::Sorted);
1272 assert!(built[0].distinct, "the sort settles distinctness for an unordered column");
1273
1274 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1275 let map = key_map(&reader, 0).expect("the map is in the file");
1276 resolves(&keys, &map);
1277
1278 fs::remove_file(&path).expect("clean up");
1279 }
1280
1281 #[test]
1282 fn a_null_in_the_key_column_does_not_shift_the_rows_after_it() {
1283 let mut keys = (1..=1200_i64).map(Some).collect::<Vec<_>>();
1288 keys[3] = None;
1289 keys[900] = None;
1290 let path = table_of("nulls", &keys);
1291 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1292 assert_eq!(built[0].rows, 1198, "a null is not a key");
1293
1294 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1295 let map = key_map(&reader, 0).expect("the map is in the file");
1296 resolves(&keys, &map);
1297
1298 fs::remove_file(&path).expect("clean up");
1299 }
1300
1301 #[test]
1302 fn a_column_with_a_repeat_in_it_is_mapped_and_reported_as_no_parent() {
1303 let mut keys = (1..=500_i64).map(Some).collect::<Vec<_>>();
1308 keys[200] = Some(7);
1309 let path = table_of("repeat", &keys);
1310 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1311 assert!(!built[0].distinct, "a repeat is observed rather than declared away");
1312 assert!(!built[0].built, "and a map no rid can be resolved through is not kept");
1313 assert!(built[0].bytes > 0, "what it would have cost is still reported");
1314
1315 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1316 assert!(key_map(&reader, 0).is_none(), "no map was written to read back");
1317 assert!(!holds_key_map(&reader, 0), "and the record of a refusal does not say it is a key");
1318 let (form, bytes) = refused_key_map(&reader, 0).expect("the record of what it would cost");
1322 assert_eq!(form, built[0].form);
1323 assert_eq!(bytes, built[0].bytes as u64);
1324 assert_eq!(graph_sections(&reader).len(), 1, "one entry, and no payload");
1325 assert_eq!(graph_sections(&reader)[0].extents, 0);
1326
1327 fs::remove_file(&path).expect("clean up");
1328 }
1329
1330 #[test]
1331 fn a_table_with_no_key_map_answers_with_none_rather_than_an_error() {
1332 let path = table_of("absent", &[Some(1), Some(2)]);
1335 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1336 assert!(key_map(&reader, 0).is_none());
1337 assert!(key_map(&reader, 99).is_none(), "a column that does not exist is not a panic");
1338 assert!(!holds_key_map(&reader, 0));
1339 fs::remove_file(&path).expect("clean up");
1340 }
1341
1342 #[test]
1343 fn a_stale_key_map_is_ignored_and_the_table_still_reads() {
1344 let path = table_of("stale", &(1..=100_i64).map(Some).collect::<Vec<_>>());
1345 build_key_maps(&path, "parent", &[0]).expect("build");
1346
1347 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1350 assert!(key_map(&reader, 0).is_some());
1351 assert!(holds_key_map(&reader, 0));
1352 let generation = reader.table().generation();
1353 drop(reader);
1354
1355 let held = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1357 let mut entry = *graph_sections(&held).first().copied().expect("the key map");
1358 assert!(entry.usable(generation));
1359 entry.generation = generation + 1;
1360 assert!(!entry.usable(generation), "a rewrite invalidates rather than corrupts");
1361
1362 fs::remove_file(&path).expect("clean up");
1363 }
1364
1365 #[test]
1366 fn a_torn_key_map_costs_the_shortcut_and_not_the_query() {
1367 let keys = (1..=200_i64).map(Some).collect::<Vec<_>>();
1368 let path = table_of("torn", &keys);
1369 build_key_maps(&path, "parent", &[0]).expect("build");
1370
1371 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1372 let extent = reader
1373 .extents(graph_sections(&reader).first().copied().expect("the key map"))
1374 .expect("extent table")
1375 .first()
1376 .copied()
1377 .expect("one extent");
1378 drop(reader);
1379 let file = fs::OpenOptions::new().write(true).open(&path).expect("reopen to corrupt");
1380 crate::write_at(&file, extent.offset, &[0xff; 8]).expect("flip the header");
1381 drop(file);
1382
1383 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1384 assert!(key_map(&reader, 0).is_none(), "a payload that does not checksum is not a map");
1385 assert_eq!(reader.table().rows(), 200, "and the table is untouched");
1386
1387 fs::remove_file(&path).expect("clean up");
1388 }
1389
1390 #[test]
1391 fn a_column_with_no_integer_key_form_is_refused_by_name() {
1392 let path = path("varchar");
1393 let mut writer =
1394 Writer::create(&path, "parent", vec![Field::new("name", LogicalType::Varchar)])
1395 .expect("new file");
1396 let chunk = Chunk::new(vec![
1397 Vector::from_values(LogicalType::Varchar, &[Value::Varchar("a".into())])
1398 .expect("one name"),
1399 ])
1400 .expect("one column");
1401 writer.append(&chunk).expect("a part");
1402 writer.finish().expect("commit");
1403
1404 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1405 let error = KeyColumn::new(&reader, 0).expect_err("a string key needs its codes");
1406 assert!(error.to_string().contains("integer key form"), "{error}");
1407
1408 fs::remove_file(&path).expect("clean up");
1409 }
1410
1411 #[test]
1412 fn several_columns_are_mapped_in_one_commit() {
1413 let path = path("two_columns");
1414 let mut writer = Writer::create(
1415 &path,
1416 "parent",
1417 vec![
1418 Field::required("id", LogicalType::BigInt),
1419 Field::required("code", LogicalType::Integer),
1420 ],
1421 )
1422 .expect("new file");
1423 let ids = (1..=400_i64).map(Value::BigInt).collect::<Vec<_>>();
1424 let codes = (1..=400_i32).map(|code| Value::Integer(code * 3)).collect::<Vec<_>>();
1425 let chunk = Chunk::new(vec![
1426 Vector::from_values(LogicalType::BigInt, &ids).expect("ids"),
1427 Vector::from_values(LogicalType::Integer, &codes).expect("codes"),
1428 ])
1429 .expect("two columns");
1430 writer.append(&chunk).expect("a part");
1431 writer.finish().expect("commit");
1432
1433 let built = build_key_maps(&path, "parent", &[0, 1]).expect("build both");
1434 assert_eq!(built.len(), 2);
1435 assert_eq!(built[0].form, Form::Identity);
1436 assert_eq!(built[1].form, Form::Dense);
1437
1438 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1439 assert_eq!(graph_sections(&reader).len(), 2, "one commit and two entries");
1440 assert_eq!(key_map(&reader, 0).expect("the id map").form(), Form::Identity);
1441 assert_eq!(key_map(&reader, 1).expect("the code map").form(), Form::Dense);
1442 assert_eq!(
1443 key_map(&reader, 1).expect("the code map").lookup(9).expect("lookup"),
1444 Some(2),
1445 "the third code is the third row"
1446 );
1447
1448 fs::remove_file(&path).expect("clean up");
1449 }
1450
1451 #[test]
1452 fn the_statistics_sections_do_not_count_against_the_graph_budget() {
1453 let keys = (1..=3000_i64).map(Some).collect::<Vec<_>>();
1459 let path = table_of("apart", &keys);
1460 crate::stats::build_stats(&path, "parent", &[0]).expect("summaries first");
1461
1462 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1463 let statistics = reader
1464 .table()
1465 .sections()
1466 .iter()
1467 .filter(|held| held.among(section::STATISTICS_KINDS))
1468 .count();
1469 assert_eq!(statistics, 2, "a summary and a sketch are in the file");
1470 assert_eq!(held_bytes(&reader, &[0]).expect("held"), 0, "and neither is the graph's");
1471
1472 drop(reader);
1473 fs::remove_file(&path).expect("clean up");
1474 }
1475
1476 #[test]
1477 fn a_map_that_does_not_fit_the_budget_is_measured_and_not_written() {
1478 let keys = (0..100_000_i64).map(|value| Some(value * 8)).collect::<Vec<_>>();
1485 let path = table_of("budget", &keys);
1486 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1487 assert_eq!(built[0].form, Form::Dense);
1488 assert!(!built[0].built, "a map ten times its column does not fit a tenth of it");
1489 assert!(built[0].bytes as u64 > built[0].column_bytes, "{built:?}");
1490
1491 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1492 assert!(key_map(&reader, 0).is_none(), "and no map was written");
1493 assert_eq!(refused_key_map(&reader, 0), Some((Form::Dense, built[0].bytes as u64)));
1496 assert_eq!(held_bytes(&reader, &[]).expect("held"), 0, "a record costs the budget nothing");
1497 drop(reader);
1498
1499 let built = build_key_maps_within(&path, "parent", &[0], 100_000).expect("build");
1502 assert!(built[0].built);
1503 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1504 let map = key_map(&reader, 0).expect("the map is in the file");
1505 resolves(&keys, &map);
1506
1507 fs::remove_file(&path).expect("clean up");
1508 }
1509
1510 #[test]
1511 fn the_budget_admits_the_cheapest_maps_it_can_fit() {
1512 let path = path("budget_order");
1518 let mut writer = Writer::create(
1519 &path,
1520 "parent",
1521 vec![
1522 Field::required("id", LogicalType::BigInt),
1523 Field::required("code", LogicalType::BigInt),
1524 ],
1525 )
1526 .expect("new file");
1527 let ids = (1..=100_000_i64).map(Value::BigInt).collect::<Vec<_>>();
1528 let codes = (1..=100_000_i64)
1529 .map(|code| Value::BigInt((code * 2_147_483_647) % 999_999_937))
1530 .collect::<Vec<_>>();
1531 for part in 0..100 {
1532 let at = part * 1000;
1533 let chunk = Chunk::new(vec![
1534 Vector::from_values(LogicalType::BigInt, &ids[at..at + 1000]).expect("ids"),
1535 Vector::from_values(LogicalType::BigInt, &codes[at..at + 1000]).expect("codes"),
1536 ])
1537 .expect("two columns");
1538 writer.append(&chunk).expect("a part");
1539 }
1540 writer.finish().expect("commit");
1541
1542 let built = build_key_maps(&path, "parent", &[1, 0]).expect("build");
1543 assert_eq!(built[0].column, 1, "the report is in the order it was asked in");
1544 assert_eq!(built[0].form, Form::Sorted);
1545 assert!(!built[0].built, "the sorted map did not fit: {built:?}");
1546 assert!(built[1].built, "the identity map did, and was reached second: {built:?}");
1547
1548 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1549 assert!(key_map(&reader, 0).is_some());
1550 assert!(key_map(&reader, 1).is_none());
1551
1552 fs::remove_file(&path).expect("clean up");
1553 }
1554
1555 fn related(label: &str, parents: i64, foreign: &[Option<i64>]) -> PathBuf {
1559 let path = table_of(label, &(1..=parents).map(Some).collect::<Vec<_>>());
1560 let mut writer = Writer::open(&path, "child", vec![Field::new("fk", LogicalType::BigInt)])
1561 .expect("a second table");
1562 for part in foreign.chunks(1000) {
1563 let values =
1564 part.iter().map(|key| key.map_or(Value::Null, Value::BigInt)).collect::<Vec<_>>();
1565 let chunk =
1566 Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1567 .expect("one column");
1568 writer.append(&chunk).expect("a part");
1569 }
1570 writer.finish().expect("commit");
1571 build_key_maps(&path, "parent", &[0]).expect("the parent's key map");
1572 path
1573 }
1574
1575 fn edge() -> Edge {
1576 Edge { child: "child".into(), child_column: 0, parent: "parent".into(), parent_column: 0 }
1577 }
1578
1579 fn links(path: &PathBuf, foreign: &[Option<i64>]) -> link::Link {
1582 let catalog = Catalog::open(path).expect("reopen");
1583 let child = catalog.table("child").expect("the child");
1584 let parent = catalog.table("parent").expect("the parent");
1585 let link = stored_link(&child, &parent, &edge()).expect("the link is in the file");
1586 let map = key_map(&parent, 0).expect("the parent's key map");
1587 for (rid, key) in foreign.iter().enumerate() {
1588 let want = key.and_then(|key| map.lookup(i128::from(key)).expect("lookup"));
1589 assert_eq!(link.forward(rid as Rid), want, "child {rid}");
1590 }
1591 link
1592 }
1593
1594 type Pair = (Option<i64>, Option<i64>);
1596
1597 fn pairs_into(mut writer: Writer, rows: &[Pair]) {
1599 let values = |pick: fn(&Pair) -> Option<i64>, part: &[Pair]| {
1600 let values = part
1601 .iter()
1602 .map(|row| pick(row).map_or(Value::Null, Value::BigInt))
1603 .collect::<Vec<_>>();
1604 Vector::from_values(LogicalType::BigInt, &values).expect("keys")
1605 };
1606 for part in rows.chunks(1000) {
1607 let chunk = Chunk::new(vec![values(|row| row.0, part), values(|row| row.1, part)])
1608 .expect("two columns");
1609 writer.append(&chunk).expect("a part");
1610 }
1611 writer.finish().expect("commit");
1612 }
1613
1614 #[test]
1615 fn a_key_over_one_column_is_named_the_way_it_always_was() {
1616 assert_eq!(key_of(&[0]), Some(0));
1619 assert_eq!(key_of(&[17]), Some(17));
1620 assert_eq!(columns_of(17), vec![17]);
1621 let pair = key_of(&[1, 2]).expect("a pair");
1622 assert_ne!(pair, key_of(&[2, 1]).expect("a pair"), "the order is part of the key");
1623 assert_eq!(columns_of(pair), vec![1, 2]);
1624 assert!(pair > u32::MAX as usize / 2, "a pair never reads as a column index");
1625 assert_eq!(key_of(&[]), None);
1626 assert_eq!(key_of(&[0, 1, 2]), None, "nothing is built over three columns");
1627 assert_eq!(key_of(&[1 << 15, 0]), None, "an index too wide to pack is refused");
1628 }
1629
1630 #[test]
1631 fn two_values_fold_into_one_key_without_two_pairs_ever_meeting() {
1632 let values = [i128::from(i32::MIN), -1, 0, 1, i128::from(i32::MAX)];
1633 let mut seen = std::collections::HashSet::new();
1634 for &high in &values {
1635 for &low in &values {
1636 assert!(seen.insert(fold(high, low).expect("fits")), "({high}, {low}) met another");
1637 }
1638 }
1639 let wide = i128::from(i32::MAX) + 1;
1640 assert!(fold(0, wide).is_err(), "a second value past 32 bits");
1641 assert!(fold(wide, 0).is_err(), "a first value past 32 bits");
1642 let span = fold(i128::from(i32::MAX), i128::from(i32::MAX)).expect("fits")
1643 - fold(i128::from(i32::MIN), i128::from(i32::MIN)).expect("fits");
1644 assert!(span <= i128::from(u64::MAX), "a key map's keys span no more than a u64");
1645 }
1646
1647 #[test]
1648 fn a_link_over_a_two_column_key_finds_the_row_holding_both_values() {
1649 let path = path("pair");
1653 let fields =
1654 vec![Field::new("part", LogicalType::BigInt), Field::new("supp", LogicalType::BigInt)];
1655 let parents = (1..=500_i64)
1656 .flat_map(|part| (0..4).map(move |at| (Some(part), Some((part + at * 125) % 1000 + 1))))
1657 .collect::<Vec<_>>();
1658 pairs_into(Writer::create(&path, "parent", fields.clone()).expect("new file"), &parents);
1659 let mut children = (0..3000_i64)
1660 .map(|at| parents[usize::try_from((at * 7) % 2000).expect("small")])
1661 .collect::<Vec<_>>();
1662 children[5] = (Some(3), Some(999)); children[6] = (None, Some(4));
1664 children[7] = (Some(4), None);
1665 pairs_into(Writer::open(&path, "child", fields).expect("a second table"), &children);
1666
1667 let key = pair(0, 1).expect("a pair");
1668 let edge = Edge {
1669 child: "child".into(),
1670 child_column: key,
1671 parent: "parent".into(),
1672 parent_column: key,
1673 };
1674 let report = build_links(&path, std::slice::from_ref(&edge)).expect("build");
1675 assert!(report[0].built, "{:?}", report[0].note);
1676 assert_eq!(report[0].linked, 2997, "three children name no parent");
1677
1678 let catalog = Catalog::open(&path).expect("reopen");
1679 let parent = catalog.table("parent").expect("the parent");
1680 let child = catalog.table("child").expect("the child");
1681 assert!(key_map(&parent, key).is_none(), "a pair's map is built for the link and not kept");
1682 let link = stored_link(&child, &parent, &edge).expect("the link is in the file");
1683 for (rid, row) in children.iter().enumerate() {
1684 let want = parents.iter().position(|held| held == row).map(|at| at as Rid);
1685 assert_eq!(link.forward(rid as Rid), want, "child {rid} is {row:?}");
1686 }
1687 let one = Edge { child_column: 0, parent_column: 0, ..edge };
1688 assert!(stored_link(&child, &parent, &one).is_none(), "half of the key is not the key");
1689
1690 fs::remove_file(&path).expect("clean up");
1691 }
1692
1693 #[test]
1694 fn a_clustered_foreign_key_takes_the_monotone_form_and_answers_both_directions() {
1695 let foreign = (0..4000_i64).map(|child| Some(child / 4 + 1)).collect::<Vec<_>>();
1698 let path = related("monotone", 1000, &foreign);
1699 let report = build_links(&path, &[edge()]).expect("build");
1700 assert_eq!(report.len(), 1);
1701 assert!(report[0].built, "{:?}", report[0].note);
1702 assert_eq!(report[0].form, Some(link::Form::Monotone));
1703 assert_eq!(report[0].children, 4000);
1704 assert_eq!(report[0].linked, 4000);
1705
1706 let link = links(&path, &foreign);
1707 assert_eq!(link.form(), link::Form::Monotone);
1708 assert_eq!(link.backward(0), Some(0..4), "the first parent's four children");
1709 assert_eq!(link.backward(999), Some(3996..4000));
1710 assert_eq!(link.backward(1000), None, "past the last parent");
1711
1712 fs::remove_file(&path).expect("clean up");
1713 }
1714
1715 #[test]
1716 fn an_unclustered_foreign_key_takes_the_packed_form_and_still_resolves() {
1717 let foreign = (0..3000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1718 let path = related("packed", 1000, &foreign);
1719 let report = build_links(&path, &[edge()]).expect("build");
1720 assert!(report[0].built, "{:?}", report[0].note);
1721 assert_eq!(report[0].form, Some(link::Form::Packed));
1722
1723 let link = links(&path, &foreign);
1724 assert_eq!(link.backward(0), None, "the packed form answers one direction");
1725 assert!(link.bytes() < 3000 * 2 + 3 * 16, "{} bytes is not bit-packed", link.bytes());
1729
1730 fs::remove_file(&path).expect("clean up");
1731 }
1732
1733 #[test]
1734 fn a_built_link_leaves_the_shape_of_the_relationship_beside_it() {
1735 let foreign = (0..4000_i64).map(|child| Some(child / 4 + 1)).collect::<Vec<_>>();
1738 let path = related("degrees", 1000, &foreign);
1739 let report = build_links(&path, &[edge()]).expect("build");
1740 assert!(report[0].built, "{:?}", report[0].note);
1741 let measured = report[0].degrees.as_ref().expect("the build measured it");
1742 assert!((measured.mean() - 4.0).abs() < 1e-9);
1743
1744 let catalog = Catalog::open(&path).expect("reopen");
1745 let child = catalog.table("child").expect("the child");
1746 let held = stored_degrees(&child, 0).expect("it is in the file");
1747 assert_eq!(&held, measured, "what the build measured is what the file holds");
1748 assert_eq!(held.parents(), 1000);
1749 assert_eq!(held.highest(), 4);
1750 assert!(held.total(), "every child found a parent");
1751 assert!(held.unique(), "and the parent key is why there is a link at all");
1752 let near = held.locality().expect("something to gather");
1756 assert!((near - 999.0 / 3999.0).abs() < 1e-9, "{near}");
1757 assert!(stored_degrees(&child, 1).is_none(), "and no other column has one");
1758
1759 fs::remove_file(&path).expect("clean up");
1760 }
1761
1762 #[test]
1763 fn a_foreign_key_that_matches_nothing_is_a_child_with_no_parent() {
1764 let foreign = vec![Some(1), Some(2), None, Some(9999), Some(3)];
1767 let path = related("orphans", 10, &foreign);
1768 let report = build_links(&path, &[edge()]).expect("build");
1769 assert!(report[0].built, "{:?}", report[0].note);
1770 assert_eq!(report[0].form, Some(link::Form::Packed));
1771 assert_eq!(report[0].children, 5);
1772 assert_eq!(report[0].linked, 3, "the null and the key that matches nothing are not links");
1773
1774 let link = links(&path, &foreign);
1775 assert_eq!(link.forward(2), None, "a null is not a link");
1776 assert_eq!(link.forward(3), None, "a key that matches nothing is not a link");
1777
1778 fs::remove_file(&path).expect("clean up");
1779 }
1780
1781 #[test]
1782 fn a_parent_with_no_key_map_stored_gets_its_link_from_a_map_built_for_it() {
1783 let path = table_of("unmapped", &(1..=100_i64).map(Some).collect::<Vec<_>>());
1786 let mut writer = Writer::open(&path, "child", vec![Field::new("fk", LogicalType::BigInt)])
1787 .expect("a second table");
1788 let values = (1..=100_i64).map(Value::BigInt).collect::<Vec<_>>();
1789 writer
1790 .append(
1791 &Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1792 .expect("one column"),
1793 )
1794 .expect("a part");
1795 writer.finish().expect("commit");
1796
1797 let report = build_links(&path, &[edge()]).expect("build");
1798 assert!(report[0].built, "{:?}", report[0].note);
1799
1800 let catalog = Catalog::open(&path).expect("reopen");
1801 let child = catalog.table("child").expect("the child");
1802 let parent = catalog.table("parent").expect("the parent");
1803 assert!(key_map(&parent, 0).is_none(), "the map it was built with is not kept");
1804 let link = stored_link(&child, &parent, &edge()).expect("the link is kept");
1805 assert_eq!(link.linked(), 100);
1806 assert_eq!(link.forward(99), Some(99));
1807
1808 fs::remove_file(&path).expect("clean up");
1809 }
1810
1811 #[test]
1812 fn a_packed_link_leaves_the_children_of_every_parent_beside_it() {
1813 let foreign = (0..6000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1815 let path = related("adjacency", 1000, &foreign);
1816 let report = build_links(&path, &[edge()]).expect("build");
1817 assert_eq!(report[0].form, Some(link::Form::Packed));
1818 assert!(report[0].adjacency, "a packed link's adjacency fits the floor");
1819
1820 let catalog = Catalog::open(&path).expect("reopen");
1821 let child = catalog.table("child").expect("the child");
1822 let parent = catalog.table("parent").expect("the parent");
1823 let adjacency = stored_adjacency(&child, &parent, &edge()).expect("it is in the file");
1824 assert_eq!(
1825 (adjacency.children(), adjacency.parents(), adjacency.edges()),
1826 (6000, 1000, 6000)
1827 );
1828 let mut listed = Vec::new();
1829 for held in [0_u64, 1, 500, 999] {
1830 listed.clear();
1831 adjacency.children_of(held, &mut listed).expect("a parent in range");
1832 let slow = (0..6000_u64).filter(|&at| (at * 7) % 1000 == held).collect::<Vec<_>>();
1833 assert_eq!(listed, slow, "parent {held}");
1834 }
1835 let wrong = Edge { parent: "child".into(), ..edge() };
1836 assert!(stored_adjacency(&child, &parent, &wrong).is_none(), "a different parent name");
1837
1838 fs::remove_file(&path).expect("clean up");
1839 }
1840
1841 #[test]
1842 fn a_link_asked_for_against_the_wrong_parent_is_not_handed_over() {
1843 let foreign = (0..500_i64).map(|child| Some(child / 5 + 1)).collect::<Vec<_>>();
1847 let path = related("binding", 100, &foreign);
1848 build_links(&path, &[edge()]).expect("build");
1849
1850 let catalog = Catalog::open(&path).expect("reopen");
1851 let child = catalog.table("child").expect("the child");
1852 let parent = catalog.table("parent").expect("the parent");
1853 let held = stored_link(&child, &parent, &edge()).expect("the link is handed over");
1854 let counts = stored_link_counts(&child, &parent, &edge()).expect("and so are its counts");
1856 assert_eq!(
1857 counts,
1858 link::Counts {
1859 children: held.children(),
1860 parents: held.parents(),
1861 linked: held.linked(),
1862 form: held.form()
1863 }
1864 );
1865 assert_eq!((counts.children, counts.linked), (500, 500));
1866 let wrong = Edge { parent: "child".into(), ..edge() };
1867 assert!(stored_link(&child, &parent, &wrong).is_none(), "a different parent name");
1868 assert!(stored_link_counts(&child, &parent, &wrong).is_none(), "a different parent name");
1869 let wrong = Edge { parent_column: 1, ..edge() };
1870 assert!(stored_link(&child, &parent, &wrong).is_none(), "a different parent column");
1871 assert!(stored_link_counts(&child, &parent, &wrong).is_none(), "a different parent column");
1872 let wrong = Edge { child_column: 1, ..edge() };
1873 assert!(stored_link(&child, &parent, &wrong).is_none(), "a different child column");
1874 assert!(stored_link_counts(&child, &parent, &wrong).is_none(), "a different child column");
1875
1876 fs::remove_file(&path).expect("clean up");
1877 }
1878
1879 #[test]
1880 fn a_link_does_not_count_against_the_key_maps_of_its_table() {
1881 let foreign = (0..20_000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1885 let path = related("apart_kinds", 1000, &foreign);
1886 let report = build_links(&path, &[edge()]).expect("build");
1887 assert!(report[0].built, "{:?}", report[0].note);
1888
1889 let catalog = Catalog::open(&path).expect("reopen");
1890 let child = catalog.table("child").expect("the child");
1891 let link = held_kind_bytes(&child, *section::FORWARD_LINK, &[]).expect("held");
1892 assert!(link > 0, "the link is in the file");
1893 assert_eq!(held_bytes(&child, &[]).expect("held"), 0, "and costs the key maps nothing");
1894 assert!(held_kind_bytes(&child, *section::ADJACENCY, &[]).expect("held") > 0);
1896 assert_eq!(held_kind_bytes(&child, *section::FORWARD_LINK, &[0]).expect("held"), 0);
1897
1898 fs::remove_file(&path).expect("clean up");
1899 }
1900
1901 #[test]
1902 fn a_link_that_does_not_fit_the_budget_is_reported_rather_than_stored() {
1903 let foreign = (0..60_000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1906 let path = related("budget", 1000, &foreign);
1907 let report = build_links_within(&path, &[edge()], 0).expect("build");
1908 assert!(!report[0].built);
1909 assert!(report[0].bytes > 0, "the report says what a larger budget would buy");
1910 assert!(report[0].note.as_deref().unwrap_or_default().contains("budget"), "{report:?}");
1911
1912 let catalog = Catalog::open(&path).expect("reopen");
1913 let child = catalog.table("child").expect("the child");
1914 let parent = catalog.table("parent").expect("the parent");
1915 assert!(stored_link(&child, &parent, &edge()).is_none());
1916 assert!(report[0].degrees.is_some(), "it was measured");
1919 assert!(stored_degrees(&child, 0).is_none(), "and not written");
1920 assert_eq!(refused_link(&child, 0), Some((link::Form::Packed, report[0].bytes as u64)));
1923
1924 fs::remove_file(&path).expect("clean up");
1925 }
1926
1927 #[test]
1928 fn the_budget_keeps_the_link_that_saves_the_larger_hash_table() {
1929 let path = table_of("rank", &(1..=1000).map(Some).collect::<Vec<_>>());
1934 let small = [Field::new("key", LogicalType::BigInt)];
1935 let mut writer = Writer::open(&path, "small", small.to_vec()).expect("a second table");
1936 let keys = (1..=4).map(Value::BigInt).collect::<Vec<_>>();
1937 let chunk =
1938 Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &keys).expect("keys")])
1939 .expect("one column");
1940 writer.append(&chunk).expect("a part");
1941 writer.finish().expect("commit");
1942 let rows = (0..45_000_i64)
1943 .map(|child| (Some((child * 7) % 1000 + 1), Some(child % 4 + 1)))
1944 .collect::<Vec<_>>();
1945 let fields = vec![
1946 Field::new("large", LogicalType::BigInt),
1947 Field::new("small", LogicalType::BigInt),
1948 ];
1949 pairs_into(Writer::open(&path, "child", fields).expect("a third table"), &rows);
1950 build_key_maps(&path, "parent", &[0]).expect("the large key map");
1951 build_key_maps(&path, "small", &[0]).expect("the small key map");
1952
1953 let edges = [
1954 edge(),
1955 Edge {
1956 child: "child".into(),
1957 child_column: 1,
1958 parent: "small".into(),
1959 parent_column: 0,
1960 },
1961 ];
1962 let report = build_links_within(&path, &edges, 0).expect("build");
1963 assert!(report[1].bytes < report[0].bytes, "the small link is the cheaper one");
1964 assert!(
1965 (report[0].bytes + report[1].bytes) as u64 > BUDGET_FLOOR,
1966 "the two have to not fit together for this to test anything"
1967 );
1968 assert_eq!((report[0].parents, report[1].parents), (1000, 4));
1969 assert!(report[0].built, "the link that saves a thousand rows was turned away: {report:?}");
1970 assert!(!report[1].built, "the link that saves four rows was kept instead");
1971
1972 fs::remove_file(&path).expect("clean up");
1973 }
1974
1975 #[test]
1976 fn a_parent_whose_key_repeats_gets_no_link_at_all() {
1977 let path = table_of("repeats", &[Some(1), Some(1), Some(2)]);
1981 let mut writer = Writer::open(&path, "child", vec![Field::new("fk", LogicalType::BigInt)])
1982 .expect("a second table");
1983 let values = [Value::BigInt(1), Value::BigInt(2)];
1984 writer
1985 .append(
1986 &Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1987 .expect("one column"),
1988 )
1989 .expect("a part");
1990 writer.finish().expect("commit");
1991 build_key_maps(&path, "parent", &[0]).expect("the parent's key map");
1992
1993 let report = build_links(&path, &[edge()]).expect("build");
1994 assert!(!report[0].built);
1995 assert_eq!(report[0].note.as_deref(), Some("the key of parent is not unique"));
1996
1997 fs::remove_file(&path).expect("clean up");
1998 }
1999}