1use std::path::Path;
16use std::time::{Duration, Instant};
17
18use rudb_common::{LogicalType, Result, Value};
19use rudb_graph::{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 BUDGET_FLOOR: u64 = 64 * 1024;
280
281pub fn build_key_maps(path: &Path, table: &str, columns: &[usize]) -> Result<Vec<Built>> {
291 build_key_maps_within(path, table, columns, BUDGET_SHARE)
292}
293
294pub fn build_key_maps_within(
312 path: &Path,
313 table: &str,
314 columns: &[usize],
315 share: u64,
316) -> Result<Vec<Built>> {
317 let reader = Catalog::open(path)?.table(table)?;
318 let column_bytes = reader.layout().columns_total();
319 let allowance = (column_bytes.saturating_mul(share) / 100).max(BUDGET_FLOOR);
320 let mut spent = held_bytes(&reader, columns)?;
321 let mut report = Vec::with_capacity(columns.len());
322 let mut payloads = Vec::with_capacity(columns.len());
323 for &column in columns {
324 let start = Instant::now();
325 let map = build_key_map(&reader, column)?;
326 let tag = key_tag(reader.table().fields(), column)
327 .ok_or_else(|| invalid("a key map over a column the table does not have"))?;
328 let payload = wire::encode(&map, tag)?;
329 report.push(Built {
330 column,
331 form: map.form(),
332 rows: map.observed().rows,
333 distinct: map.observed().distinct,
334 bytes: payload.bytes.len(),
335 column_bytes,
336 built: false,
337 build: start.elapsed(),
338 });
339 payloads.push((column, payload));
340 }
341 let mut order = (0..payloads.len()).collect::<Vec<_>>();
344 order.sort_by_key(|&at| payloads[at].1.bytes.len());
345 let mut keep = vec![false; payloads.len()];
346 for at in order {
347 if !report[at].distinct {
352 continue;
353 }
354 let cost = payloads[at].1.bytes.len() as u64;
355 if spent.saturating_add(cost) <= allowance {
356 spent += cost;
357 keep[at] = true;
358 report[at].built = true;
359 }
360 }
361 drop(reader);
365 let attachments = payloads
370 .iter()
371 .zip(&keep)
372 .map(|((column, payload), &keep)| {
373 Ok(Attachment {
374 kind: *section::KEY_MAP,
375 id: u64::try_from(*column).map_err(|_| invalid("column index overflow"))?,
376 flags: payload.flags,
377 header_bytes: if keep { payload.header_bytes } else { cost(payload.bytes.len()) },
378 bytes: if keep { &payload.bytes } else { &[] },
379 })
380 })
381 .collect::<Result<Vec<_>>>()?;
382 crate::attach(path, table, &attachments)?;
383 Ok(report)
384}
385
386fn held_bytes(reader: &Reader, replacing: &[usize]) -> Result<u64> {
396 held_bytes_except(reader, *section::KEY_MAP, replacing)
397}
398
399#[must_use]
408pub fn key_map(reader: &Reader, column: usize) -> Option<KeyMap> {
409 let table = reader.table();
410 let id = u64::try_from(column).ok()?;
411 let held = table
412 .sections()
413 .iter()
414 .find(|section| section.kind == *section::KEY_MAP && section.id == id)?;
415 if !held.usable(table.generation()) {
416 return None;
417 }
418 let (map, tag) = wire::decode(&reader.payload(held).ok()?).ok()?;
419 if tag != key_tag(table.fields(), column)? {
424 return None;
425 }
426 Some(map)
427}
428
429#[derive(Debug, Clone)]
435pub struct Edge {
436 pub child: String,
438 pub child_column: usize,
440 pub parent: String,
442 pub parent_column: usize,
444}
445
446#[derive(Debug, Clone)]
448pub struct BuiltLink {
449 pub edge: Edge,
451 pub form: Option<link::Form>,
453 pub children: u64,
455 pub parents: u64,
457 pub linked: u64,
460 pub bytes: usize,
462 pub table_bytes: u64,
465 pub degrees: Option<Degrees>,
471 pub built: bool,
473 pub note: Option<String>,
475 pub build: Duration,
477}
478
479pub fn build_links(path: &Path, edges: &[Edge]) -> Result<Vec<BuiltLink>> {
490 build_links_within(path, edges, BUDGET_SHARE)
491}
492
493pub fn build_links_within(path: &Path, edges: &[Edge], share: u64) -> Result<Vec<BuiltLink>> {
508 let mut tables: Vec<&str> = Vec::new();
509 for edge in edges {
510 if !tables.iter().any(|held| *held == edge.child) {
511 tables.push(&edge.child);
512 }
513 }
514 let mut report = Vec::with_capacity(edges.len());
515 for table in tables {
516 let mine = edges.iter().filter(|edge| edge.child == table).cloned().collect::<Vec<Edge>>();
517 report.extend(links_of_one_table(path, table, &mine, share)?);
518 }
519 Ok(report)
520}
521
522fn links_of_one_table(
524 path: &Path,
525 table: &str,
526 edges: &[Edge],
527 share: u64,
528) -> Result<Vec<BuiltLink>> {
529 let catalog = Catalog::open(path)?;
530 let child = catalog.table(table)?;
531 let column_bytes = child.layout().columns_total();
532 let allowance = (column_bytes.saturating_mul(share) / 100).max(BUDGET_FLOOR);
533 let replacing = edges.iter().map(|edge| edge.child_column).collect::<Vec<usize>>();
534 let mut spent = held_bytes_except(&child, *section::FORWARD_LINK, &replacing)?;
535 let mut report = Vec::with_capacity(edges.len());
536 let mut payloads: Vec<Option<Vec<u8>>> = Vec::with_capacity(edges.len());
537 for edge in edges {
538 let start = Instant::now();
539 match one_link(&catalog, &child, edge) {
540 Ok((built, bytes)) => {
541 report.push(BuiltLink {
542 build: start.elapsed(),
543 table_bytes: column_bytes,
544 ..built
545 });
546 payloads.push(Some(bytes));
547 }
548 Err(note) => {
549 report.push(BuiltLink {
550 edge: edge.clone(),
551 form: None,
552 children: child.table().rows() as u64,
553 parents: 0,
554 linked: 0,
555 bytes: 0,
556 table_bytes: column_bytes,
557 degrees: None,
558 built: false,
559 note: Some(note),
560 build: start.elapsed(),
561 });
562 payloads.push(None);
563 }
564 }
565 }
566 let mut order = (0..report.len()).filter(|at| payloads[*at].is_some()).collect::<Vec<_>>();
567 order.sort_by(|left, right| {
575 let value = |at: &usize| -> f64 {
576 let bytes = report[*at].bytes.max(1);
577 report[*at].children.min(report[*at].parents) as f64 / bytes as f64
578 };
579 value(right).partial_cmp(&value(left)).unwrap_or(std::cmp::Ordering::Equal)
580 });
581 for at in order {
582 let cost = report[at].bytes as u64;
583 if spent.saturating_add(cost) <= allowance {
584 spent += cost;
585 report[at].built = true;
586 } else {
587 report[at].note = Some(format!("over the budget of {allowance} bytes"));
588 }
589 }
590 drop(child);
591 let measured = report
594 .iter()
595 .filter(|built| built.built)
596 .filter_map(|built| {
597 let mut bytes = Vec::with_capacity(rudb_graph::degree::BYTES);
598 built.degrees.as_ref()?.write(&mut bytes);
599 Some((built.edge.child_column, bytes))
600 })
601 .collect::<Vec<_>>();
602 let mut attachments = report
608 .iter()
609 .zip(&payloads)
610 .filter(|(_, payload)| payload.is_some())
611 .map(|(built, payload)| {
612 let bytes = payload.as_ref().expect("filtered to the measured");
613 Ok(Attachment {
614 kind: *section::FORWARD_LINK,
615 id: u64::try_from(built.edge.child_column)
616 .map_err(|_| invalid("column index overflow"))?,
617 flags: built.form.map_or(0, |form| u32::from(form.tag())),
618 header_bytes: if built.built {
619 u32::try_from(binding_bytes(&built.edge.parent))
620 .map_err(|_| invalid("a parent name longer than a section header"))?
621 } else {
622 cost(bytes.len())
623 },
624 bytes: if built.built { bytes } else { &[] },
625 })
626 })
627 .collect::<Result<Vec<_>>>()?;
628 for (column, bytes) in &measured {
633 attachments.push(Attachment {
634 kind: *section::DEGREES,
635 id: u64::try_from(*column).map_err(|_| invalid("column index overflow"))?,
636 flags: 0,
637 header_bytes: 0,
638 bytes,
639 });
640 }
641 crate::attach(path, table, &attachments)?;
642 Ok(report)
643}
644
645fn one_link(
652 catalog: &Catalog,
653 child: &Reader,
654 edge: &Edge,
655) -> std::result::Result<(BuiltLink, Vec<u8>), String> {
656 let parent =
657 catalog.table(&edge.parent).map_err(|_| format!("no table named {}", edge.parent))?;
658 let map = parent_map(&parent, edge)?;
659 if !map.observed().usable_as_parent() {
660 return Err(format!("the key of {} is not unique", edge.parent));
661 }
662 let keys = KeyColumn::new(child, edge.child_column).map_err(|error| error.to_string())?;
663 let mut parents_of = Vec::with_capacity(child.table().rows());
664 let mut failed = None;
665 keys.scan(&mut |key| {
666 let parent = match key {
667 None => NO_PARENT,
668 Some(key) => match map.lookup(key) {
669 Ok(found) => found.unwrap_or(NO_PARENT),
670 Err(error) => {
671 failed = Some(error.to_string());
672 NO_PARENT
673 }
674 },
675 };
676 parents_of.push(parent);
677 Ok(())
678 })
679 .map_err(|error| error.to_string())?;
680 if let Some(failed) = failed {
681 return Err(failed);
682 }
683 let link = link::Link::build(&parents_of, map.len()).map_err(|error| error.to_string())?;
684 let degrees = Degrees::of(&parents_of, map.len(), true);
693 let bytes = encode_link(&link, &parent, edge).map_err(|error| error.to_string())?;
694 Ok((
695 BuiltLink {
696 edge: edge.clone(),
697 form: Some(link.form()),
698 children: link.children(),
699 parents: map.len(),
700 linked: link.linked(),
701 bytes: bytes.len(),
702 table_bytes: 0,
703 degrees: Some(degrees),
704 built: false,
705 note: None,
706 build: Duration::ZERO,
707 },
708 bytes,
709 ))
710}
711
712fn parent_map(parent: &Reader, edge: &Edge) -> std::result::Result<KeyMap, String> {
721 if columns_of(edge.parent_column).len() == 1 {
722 return key_map(parent, edge.parent_column)
723 .ok_or_else(|| format!("no key map is stored for {}", edge.parent));
724 }
725 KeyColumn::new(parent, edge.parent_column)
726 .and_then(|keys| KeyMap::build_from(&keys))
727 .map_err(|error| error.to_string())
728}
729
730fn cost(bytes: usize) -> u32 {
736 u32::try_from(bytes).unwrap_or(u32::MAX)
737}
738
739fn binding_bytes(parent: &str) -> usize {
744 16 + parent.len().div_ceil(8) * 8
745}
746
747fn encode_link(link: &link::Link, parent: &Reader, edge: &Edge) -> Result<Vec<u8>> {
756 let name = edge.parent.as_bytes();
757 let mut bytes = Vec::with_capacity(binding_bytes(&edge.parent) + link.bytes());
758 bytes.extend_from_slice(&parent.table().generation().to_le_bytes());
759 bytes.extend_from_slice(
760 &u32::try_from(edge.parent_column)
761 .map_err(|_| invalid("column index overflow"))?
762 .to_le_bytes(),
763 );
764 bytes.extend_from_slice(
765 &u32::try_from(name.len())
766 .map_err(|_| invalid("a parent name longer than a u32"))?
767 .to_le_bytes(),
768 );
769 bytes.extend_from_slice(name);
770 bytes.resize(binding_bytes(&edge.parent), 0);
771 link.write(&mut bytes)?;
772 Ok(bytes)
773}
774
775#[must_use]
783pub fn stored_link(child: &Reader, parent: &Reader, edge: &Edge) -> Option<link::Link> {
784 let held = link_section(child, edge)?;
785 let bytes = child.payload(held).ok()?;
786 let binding = bound(&bytes, parent, edge)?;
787 link::Link::read(&bytes[binding..]).ok()
788}
789
790#[must_use]
801pub fn stored_link_counts(child: &Reader, parent: &Reader, edge: &Edge) -> Option<link::Counts> {
802 let held = link_section(child, edge)?;
803 let binding = binding_bytes(&edge.parent);
804 let bytes = child.payload_head(held, binding + link::HEADER_BYTES).ok()?;
805 let binding = bound(&bytes, parent, edge)?;
806 link::Link::counts(&bytes[binding..]).ok()
807}
808
809fn link_section<'a>(child: &'a Reader, edge: &Edge) -> Option<&'a section::Section> {
811 let table = child.table();
812 let id = u64::try_from(edge.child_column).ok()?;
813 let held = table
814 .sections()
815 .iter()
816 .find(|section| section.kind == *section::FORWARD_LINK && section.id == id)?;
817 held.usable(table.generation()).then_some(held)
818}
819
820fn bound(bytes: &[u8], parent: &Reader, edge: &Edge) -> Option<usize> {
823 let binding = binding_bytes(&edge.parent);
824 if bytes.len() < binding {
825 return None;
826 }
827 let generation = u64::from_le_bytes(bytes[0..8].try_into().ok()?);
828 let column = u32::from_le_bytes(bytes[8..12].try_into().ok()?);
829 let length = u32::from_le_bytes(bytes[12..16].try_into().ok()?) as usize;
830 if generation != parent.table().generation()
831 || column as usize != edge.parent_column
832 || length != edge.parent.len()
833 || &bytes[16..16 + length] != edge.parent.as_bytes()
834 {
835 return None;
836 }
837 Some(binding)
838}
839
840#[must_use]
847pub fn stored_degrees(child: &Reader, child_column: usize) -> Option<Degrees> {
848 let table = child.table();
849 let id = u64::try_from(child_column).ok()?;
850 let held = table
851 .sections()
852 .iter()
853 .find(|section| section.kind == *section::DEGREES && section.id == id)?;
854 if !held.usable(table.generation()) {
855 return None;
856 }
857 Degrees::read(&child.payload(held).ok()?).ok()
858}
859
860#[must_use]
867pub fn holds_key_map(reader: &Reader, column: usize) -> bool {
868 let table = reader.table();
869 let Ok(id) = u64::try_from(column) else { return false };
870 table.sections().iter().any(|section| {
871 section.kind == *section::KEY_MAP
872 && section.id == id
873 && section.usable(table.generation())
874 && section.refused().is_none()
875 })
876}
877
878#[must_use]
885pub fn refused_key_map(reader: &Reader, column: usize) -> Option<(Form, u64)> {
886 let (form, bytes) = refused(reader, *section::KEY_MAP, column)?;
887 Some((Form::from_tag(form).ok()?, bytes))
888}
889
890#[must_use]
894pub fn refused_link(child: &Reader, child_column: usize) -> Option<(link::Form, u64)> {
895 let (form, bytes) = refused(child, *section::FORWARD_LINK, child_column)?;
896 Some((link::Form::from_tag(form).ok()?, bytes))
897}
898
899fn refused(reader: &Reader, kind: [u8; 8], id: usize) -> Option<(u8, u64)> {
901 let table = reader.table();
902 let id = u64::try_from(id).ok()?;
903 let held = table.sections().iter().find(|section| section.kind == kind && section.id == id)?;
904 if !held.usable(table.generation()) {
905 return None;
906 }
907 Some((u8::try_from(held.flags).ok()?, held.refused()?))
908}
909
910fn held_bytes_except(reader: &Reader, kind: [u8; 8], replacing: &[usize]) -> Result<u64> {
912 let mut total = 0;
913 for held in reader.table().sections() {
914 if !held.among(section::GRAPH_KINDS) {
915 continue;
916 }
917 let replaced =
918 held.kind == kind && replacing.iter().any(|&id| u64::try_from(id) == Ok(held.id));
919 if replaced || !held.usable(reader.table().generation()) {
920 continue;
921 }
922 let Ok(extents) = reader.extents(held) else { continue };
923 total += extents.iter().map(|extent| u64::from(extent.length)).sum::<u64>();
924 }
925 Ok(total)
926}
927
928#[cfg(test)]
929mod tests {
930 use std::fs;
931 use std::path::PathBuf;
932 use std::time::{SystemTime, UNIX_EPOCH};
933
934 use rudb_common::Field;
935 use rudb_graph::Rid;
936 use rudb_vector::Vector;
937
938 use super::*;
939 use crate::Writer;
940
941 fn path(label: &str) -> PathBuf {
942 let stamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("time advances").as_nanos();
943 std::env::temp_dir().join(format!("rudb-graph-{label}-{}-{stamp}.rdb", std::process::id()))
944 }
945
946 fn graph_sections(reader: &Reader) -> Vec<§ion::Section> {
952 reader.table().sections().iter().filter(|held| held.among(section::GRAPH_KINDS)).collect()
953 }
954
955 fn table_of(label: &str, keys: &[Option<i64>]) -> PathBuf {
957 let path = path(label);
958 let mut writer =
959 Writer::create(&path, "parent", vec![Field::new("key", LogicalType::BigInt)])
960 .expect("new file");
961 for part in keys.chunks(1000) {
962 let values =
963 part.iter().map(|key| key.map_or(Value::Null, Value::BigInt)).collect::<Vec<_>>();
964 let chunk =
965 Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
966 .expect("one column");
967 writer.append(&chunk).expect("a part");
968 }
969 writer.finish().expect("commit");
970 path
971 }
972
973 fn resolves(keys: &[Option<i64>], map: &KeyMap) {
975 for (rid, key) in keys.iter().enumerate() {
976 let Some(key) = *key else { continue };
977 let found =
978 map.lookup(i128::from(key)).expect("lookup").expect("a key in the column resolves");
979 assert_eq!(found, rid as Rid, "key {key} resolved to {found} rather than {rid}");
980 }
981 }
982
983 #[test]
984 fn a_key_map_built_over_a_file_resolves_every_key_to_its_own_row() {
985 let keys = (1..=3000_i64).map(Some).collect::<Vec<_>>();
990 let path = table_of("identity", &keys);
991 let built = build_key_maps(&path, "parent", &[0]).expect("build");
992 assert_eq!(built.len(), 1);
993 assert_eq!(built[0].form, Form::Identity);
994 assert_eq!(built[0].rows, 3000);
995 assert!(built[0].distinct);
996 assert_eq!(built[0].bytes, wire::HEADER_BYTES, "identity is a header and nothing else");
997
998 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
999 let map = key_map(&reader, 0).expect("the map is in the file");
1000 assert_eq!(map.form(), Form::Identity);
1001 resolves(&keys, &map);
1002 assert_eq!(map.lookup(0).expect("a key below the column"), None);
1003 assert_eq!(map.lookup(3001).expect("a key past the column"), None);
1004
1005 fs::remove_file(&path).expect("clean up");
1006 }
1007
1008 #[test]
1009 fn a_column_with_gaps_takes_the_bitmap_form_and_still_resolves() {
1010 let keys = (0..2000_i64).map(|value| Some(value * 4 + 7)).collect::<Vec<_>>();
1011 let path = table_of("dense", &keys);
1012 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1013 assert_eq!(built[0].form, Form::Dense);
1014
1015 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1016 let map = key_map(&reader, 0).expect("the map is in the file");
1017 resolves(&keys, &map);
1018 assert_eq!(map.lookup(8).expect("a value in the range but not the column"), None);
1019
1020 fs::remove_file(&path).expect("clean up");
1021 }
1022
1023 #[test]
1024 fn a_column_out_of_order_takes_the_sorted_form_and_still_resolves() {
1025 let keys = (0..1500_i64).map(|value| Some((value * 7919) % 100_003)).collect::<Vec<_>>();
1026 let path = table_of("sorted", &keys);
1027 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1028 assert_eq!(built[0].form, Form::Sorted);
1029 assert!(built[0].distinct, "the sort settles distinctness for an unordered column");
1030
1031 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1032 let map = key_map(&reader, 0).expect("the map is in the file");
1033 resolves(&keys, &map);
1034
1035 fs::remove_file(&path).expect("clean up");
1036 }
1037
1038 #[test]
1039 fn a_null_in_the_key_column_does_not_shift_the_rows_after_it() {
1040 let mut keys = (1..=1200_i64).map(Some).collect::<Vec<_>>();
1045 keys[3] = None;
1046 keys[900] = None;
1047 let path = table_of("nulls", &keys);
1048 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1049 assert_eq!(built[0].rows, 1198, "a null is not a key");
1050
1051 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1052 let map = key_map(&reader, 0).expect("the map is in the file");
1053 resolves(&keys, &map);
1054
1055 fs::remove_file(&path).expect("clean up");
1056 }
1057
1058 #[test]
1059 fn a_column_with_a_repeat_in_it_is_mapped_and_reported_as_no_parent() {
1060 let mut keys = (1..=500_i64).map(Some).collect::<Vec<_>>();
1065 keys[200] = Some(7);
1066 let path = table_of("repeat", &keys);
1067 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1068 assert!(!built[0].distinct, "a repeat is observed rather than declared away");
1069 assert!(!built[0].built, "and a map no rid can be resolved through is not kept");
1070 assert!(built[0].bytes > 0, "what it would have cost is still reported");
1071
1072 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1073 assert!(key_map(&reader, 0).is_none(), "no map was written to read back");
1074 assert!(!holds_key_map(&reader, 0), "and the record of a refusal does not say it is a key");
1075 let (form, bytes) = refused_key_map(&reader, 0).expect("the record of what it would cost");
1079 assert_eq!(form, built[0].form);
1080 assert_eq!(bytes, built[0].bytes as u64);
1081 assert_eq!(graph_sections(&reader).len(), 1, "one entry, and no payload");
1082 assert_eq!(graph_sections(&reader)[0].extents, 0);
1083
1084 fs::remove_file(&path).expect("clean up");
1085 }
1086
1087 #[test]
1088 fn a_table_with_no_key_map_answers_with_none_rather_than_an_error() {
1089 let path = table_of("absent", &[Some(1), Some(2)]);
1092 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1093 assert!(key_map(&reader, 0).is_none());
1094 assert!(key_map(&reader, 99).is_none(), "a column that does not exist is not a panic");
1095 assert!(!holds_key_map(&reader, 0));
1096 fs::remove_file(&path).expect("clean up");
1097 }
1098
1099 #[test]
1100 fn a_stale_key_map_is_ignored_and_the_table_still_reads() {
1101 let path = table_of("stale", &(1..=100_i64).map(Some).collect::<Vec<_>>());
1102 build_key_maps(&path, "parent", &[0]).expect("build");
1103
1104 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1107 assert!(key_map(&reader, 0).is_some());
1108 assert!(holds_key_map(&reader, 0));
1109 let generation = reader.table().generation();
1110 drop(reader);
1111
1112 let held = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1114 let mut entry = *graph_sections(&held).first().copied().expect("the key map");
1115 assert!(entry.usable(generation));
1116 entry.generation = generation + 1;
1117 assert!(!entry.usable(generation), "a rewrite invalidates rather than corrupts");
1118
1119 fs::remove_file(&path).expect("clean up");
1120 }
1121
1122 #[test]
1123 fn a_torn_key_map_costs_the_shortcut_and_not_the_query() {
1124 let keys = (1..=200_i64).map(Some).collect::<Vec<_>>();
1125 let path = table_of("torn", &keys);
1126 build_key_maps(&path, "parent", &[0]).expect("build");
1127
1128 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1129 let extent = reader
1130 .extents(graph_sections(&reader).first().copied().expect("the key map"))
1131 .expect("extent table")
1132 .first()
1133 .copied()
1134 .expect("one extent");
1135 drop(reader);
1136 let file = fs::OpenOptions::new().write(true).open(&path).expect("reopen to corrupt");
1137 crate::write_at(&file, extent.offset, &[0xff; 8]).expect("flip the header");
1138 drop(file);
1139
1140 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1141 assert!(key_map(&reader, 0).is_none(), "a payload that does not checksum is not a map");
1142 assert_eq!(reader.table().rows(), 200, "and the table is untouched");
1143
1144 fs::remove_file(&path).expect("clean up");
1145 }
1146
1147 #[test]
1148 fn a_column_with_no_integer_key_form_is_refused_by_name() {
1149 let path = path("varchar");
1150 let mut writer =
1151 Writer::create(&path, "parent", vec![Field::new("name", LogicalType::Varchar)])
1152 .expect("new file");
1153 let chunk = Chunk::new(vec![
1154 Vector::from_values(LogicalType::Varchar, &[Value::Varchar("a".into())])
1155 .expect("one name"),
1156 ])
1157 .expect("one column");
1158 writer.append(&chunk).expect("a part");
1159 writer.finish().expect("commit");
1160
1161 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1162 let error = KeyColumn::new(&reader, 0).expect_err("a string key needs its codes");
1163 assert!(error.to_string().contains("integer key form"), "{error}");
1164
1165 fs::remove_file(&path).expect("clean up");
1166 }
1167
1168 #[test]
1169 fn several_columns_are_mapped_in_one_commit() {
1170 let path = path("two_columns");
1171 let mut writer = Writer::create(
1172 &path,
1173 "parent",
1174 vec![
1175 Field::required("id", LogicalType::BigInt),
1176 Field::required("code", LogicalType::Integer),
1177 ],
1178 )
1179 .expect("new file");
1180 let ids = (1..=400_i64).map(Value::BigInt).collect::<Vec<_>>();
1181 let codes = (1..=400_i32).map(|code| Value::Integer(code * 3)).collect::<Vec<_>>();
1182 let chunk = Chunk::new(vec![
1183 Vector::from_values(LogicalType::BigInt, &ids).expect("ids"),
1184 Vector::from_values(LogicalType::Integer, &codes).expect("codes"),
1185 ])
1186 .expect("two columns");
1187 writer.append(&chunk).expect("a part");
1188 writer.finish().expect("commit");
1189
1190 let built = build_key_maps(&path, "parent", &[0, 1]).expect("build both");
1191 assert_eq!(built.len(), 2);
1192 assert_eq!(built[0].form, Form::Identity);
1193 assert_eq!(built[1].form, Form::Dense);
1194
1195 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1196 assert_eq!(graph_sections(&reader).len(), 2, "one commit and two entries");
1197 assert_eq!(key_map(&reader, 0).expect("the id map").form(), Form::Identity);
1198 assert_eq!(key_map(&reader, 1).expect("the code map").form(), Form::Dense);
1199 assert_eq!(
1200 key_map(&reader, 1).expect("the code map").lookup(9).expect("lookup"),
1201 Some(2),
1202 "the third code is the third row"
1203 );
1204
1205 fs::remove_file(&path).expect("clean up");
1206 }
1207
1208 #[test]
1209 fn the_statistics_sections_do_not_count_against_the_graph_budget() {
1210 let keys = (1..=3000_i64).map(Some).collect::<Vec<_>>();
1216 let path = table_of("apart", &keys);
1217 crate::stats::build_stats(&path, "parent", &[0]).expect("summaries first");
1218
1219 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1220 let statistics = reader
1221 .table()
1222 .sections()
1223 .iter()
1224 .filter(|held| held.among(section::STATISTICS_KINDS))
1225 .count();
1226 assert_eq!(statistics, 2, "a summary and a sketch are in the file");
1227 assert_eq!(held_bytes(&reader, &[0]).expect("held"), 0, "and neither is the graph's");
1228
1229 drop(reader);
1230 fs::remove_file(&path).expect("clean up");
1231 }
1232
1233 #[test]
1234 fn a_map_that_does_not_fit_the_budget_is_measured_and_not_written() {
1235 let keys = (0..100_000_i64).map(|value| Some(value * 8)).collect::<Vec<_>>();
1242 let path = table_of("budget", &keys);
1243 let built = build_key_maps(&path, "parent", &[0]).expect("build");
1244 assert_eq!(built[0].form, Form::Dense);
1245 assert!(!built[0].built, "a map ten times its column does not fit a tenth of it");
1246 assert!(built[0].bytes as u64 > built[0].column_bytes, "{built:?}");
1247
1248 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1249 assert!(key_map(&reader, 0).is_none(), "and no map was written");
1250 assert_eq!(refused_key_map(&reader, 0), Some((Form::Dense, built[0].bytes as u64)));
1253 assert_eq!(held_bytes(&reader, &[]).expect("held"), 0, "a record costs the budget nothing");
1254 drop(reader);
1255
1256 let built = build_key_maps_within(&path, "parent", &[0], 100_000).expect("build");
1259 assert!(built[0].built);
1260 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1261 let map = key_map(&reader, 0).expect("the map is in the file");
1262 resolves(&keys, &map);
1263
1264 fs::remove_file(&path).expect("clean up");
1265 }
1266
1267 #[test]
1268 fn the_budget_admits_the_cheapest_maps_it_can_fit() {
1269 let path = path("budget_order");
1275 let mut writer = Writer::create(
1276 &path,
1277 "parent",
1278 vec![
1279 Field::required("id", LogicalType::BigInt),
1280 Field::required("code", LogicalType::BigInt),
1281 ],
1282 )
1283 .expect("new file");
1284 let ids = (1..=100_000_i64).map(Value::BigInt).collect::<Vec<_>>();
1285 let codes = (1..=100_000_i64)
1286 .map(|code| Value::BigInt((code * 2_147_483_647) % 999_999_937))
1287 .collect::<Vec<_>>();
1288 for part in 0..100 {
1289 let at = part * 1000;
1290 let chunk = Chunk::new(vec![
1291 Vector::from_values(LogicalType::BigInt, &ids[at..at + 1000]).expect("ids"),
1292 Vector::from_values(LogicalType::BigInt, &codes[at..at + 1000]).expect("codes"),
1293 ])
1294 .expect("two columns");
1295 writer.append(&chunk).expect("a part");
1296 }
1297 writer.finish().expect("commit");
1298
1299 let built = build_key_maps(&path, "parent", &[1, 0]).expect("build");
1300 assert_eq!(built[0].column, 1, "the report is in the order it was asked in");
1301 assert_eq!(built[0].form, Form::Sorted);
1302 assert!(!built[0].built, "the sorted map did not fit: {built:?}");
1303 assert!(built[1].built, "the identity map did, and was reached second: {built:?}");
1304
1305 let reader = Catalog::open(&path).expect("reopen").table("parent").expect("the table");
1306 assert!(key_map(&reader, 0).is_some());
1307 assert!(key_map(&reader, 1).is_none());
1308
1309 fs::remove_file(&path).expect("clean up");
1310 }
1311
1312 fn related(label: &str, parents: i64, foreign: &[Option<i64>]) -> PathBuf {
1316 let path = table_of(label, &(1..=parents).map(Some).collect::<Vec<_>>());
1317 let mut writer = Writer::open(&path, "child", vec![Field::new("fk", LogicalType::BigInt)])
1318 .expect("a second table");
1319 for part in foreign.chunks(1000) {
1320 let values =
1321 part.iter().map(|key| key.map_or(Value::Null, Value::BigInt)).collect::<Vec<_>>();
1322 let chunk =
1323 Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1324 .expect("one column");
1325 writer.append(&chunk).expect("a part");
1326 }
1327 writer.finish().expect("commit");
1328 build_key_maps(&path, "parent", &[0]).expect("the parent's key map");
1329 path
1330 }
1331
1332 fn edge() -> Edge {
1333 Edge { child: "child".into(), child_column: 0, parent: "parent".into(), parent_column: 0 }
1334 }
1335
1336 fn links(path: &PathBuf, foreign: &[Option<i64>]) -> link::Link {
1339 let catalog = Catalog::open(path).expect("reopen");
1340 let child = catalog.table("child").expect("the child");
1341 let parent = catalog.table("parent").expect("the parent");
1342 let link = stored_link(&child, &parent, &edge()).expect("the link is in the file");
1343 let map = key_map(&parent, 0).expect("the parent's key map");
1344 for (rid, key) in foreign.iter().enumerate() {
1345 let want = key.and_then(|key| map.lookup(i128::from(key)).expect("lookup"));
1346 assert_eq!(link.forward(rid as Rid), want, "child {rid}");
1347 }
1348 link
1349 }
1350
1351 type Pair = (Option<i64>, Option<i64>);
1353
1354 fn pairs_into(mut writer: Writer, rows: &[Pair]) {
1356 let values = |pick: fn(&Pair) -> Option<i64>, part: &[Pair]| {
1357 let values = part
1358 .iter()
1359 .map(|row| pick(row).map_or(Value::Null, Value::BigInt))
1360 .collect::<Vec<_>>();
1361 Vector::from_values(LogicalType::BigInt, &values).expect("keys")
1362 };
1363 for part in rows.chunks(1000) {
1364 let chunk = Chunk::new(vec![values(|row| row.0, part), values(|row| row.1, part)])
1365 .expect("two columns");
1366 writer.append(&chunk).expect("a part");
1367 }
1368 writer.finish().expect("commit");
1369 }
1370
1371 #[test]
1372 fn a_key_over_one_column_is_named_the_way_it_always_was() {
1373 assert_eq!(key_of(&[0]), Some(0));
1376 assert_eq!(key_of(&[17]), Some(17));
1377 assert_eq!(columns_of(17), vec![17]);
1378 let pair = key_of(&[1, 2]).expect("a pair");
1379 assert_ne!(pair, key_of(&[2, 1]).expect("a pair"), "the order is part of the key");
1380 assert_eq!(columns_of(pair), vec![1, 2]);
1381 assert!(pair > u32::MAX as usize / 2, "a pair never reads as a column index");
1382 assert_eq!(key_of(&[]), None);
1383 assert_eq!(key_of(&[0, 1, 2]), None, "nothing is built over three columns");
1384 assert_eq!(key_of(&[1 << 15, 0]), None, "an index too wide to pack is refused");
1385 }
1386
1387 #[test]
1388 fn two_values_fold_into_one_key_without_two_pairs_ever_meeting() {
1389 let values = [i128::from(i32::MIN), -1, 0, 1, i128::from(i32::MAX)];
1390 let mut seen = std::collections::HashSet::new();
1391 for &high in &values {
1392 for &low in &values {
1393 assert!(seen.insert(fold(high, low).expect("fits")), "({high}, {low}) met another");
1394 }
1395 }
1396 let wide = i128::from(i32::MAX) + 1;
1397 assert!(fold(0, wide).is_err(), "a second value past 32 bits");
1398 assert!(fold(wide, 0).is_err(), "a first value past 32 bits");
1399 let span = fold(i128::from(i32::MAX), i128::from(i32::MAX)).expect("fits")
1400 - fold(i128::from(i32::MIN), i128::from(i32::MIN)).expect("fits");
1401 assert!(span <= i128::from(u64::MAX), "a key map's keys span no more than a u64");
1402 }
1403
1404 #[test]
1405 fn a_link_over_a_two_column_key_finds_the_row_holding_both_values() {
1406 let path = path("pair");
1410 let fields =
1411 vec![Field::new("part", LogicalType::BigInt), Field::new("supp", LogicalType::BigInt)];
1412 let parents = (1..=500_i64)
1413 .flat_map(|part| (0..4).map(move |at| (Some(part), Some((part + at * 125) % 1000 + 1))))
1414 .collect::<Vec<_>>();
1415 pairs_into(Writer::create(&path, "parent", fields.clone()).expect("new file"), &parents);
1416 let mut children = (0..3000_i64)
1417 .map(|at| parents[usize::try_from((at * 7) % 2000).expect("small")])
1418 .collect::<Vec<_>>();
1419 children[5] = (Some(3), Some(999)); children[6] = (None, Some(4));
1421 children[7] = (Some(4), None);
1422 pairs_into(Writer::open(&path, "child", fields).expect("a second table"), &children);
1423
1424 let key = pair(0, 1).expect("a pair");
1425 let edge = Edge {
1426 child: "child".into(),
1427 child_column: key,
1428 parent: "parent".into(),
1429 parent_column: key,
1430 };
1431 let report = build_links(&path, std::slice::from_ref(&edge)).expect("build");
1432 assert!(report[0].built, "{:?}", report[0].note);
1433 assert_eq!(report[0].linked, 2997, "three children name no parent");
1434
1435 let catalog = Catalog::open(&path).expect("reopen");
1436 let parent = catalog.table("parent").expect("the parent");
1437 let child = catalog.table("child").expect("the child");
1438 assert!(key_map(&parent, key).is_none(), "a pair's map is built for the link and not kept");
1439 let link = stored_link(&child, &parent, &edge).expect("the link is in the file");
1440 for (rid, row) in children.iter().enumerate() {
1441 let want = parents.iter().position(|held| held == row).map(|at| at as Rid);
1442 assert_eq!(link.forward(rid as Rid), want, "child {rid} is {row:?}");
1443 }
1444 let one = Edge { child_column: 0, parent_column: 0, ..edge };
1445 assert!(stored_link(&child, &parent, &one).is_none(), "half of the key is not the key");
1446
1447 fs::remove_file(&path).expect("clean up");
1448 }
1449
1450 #[test]
1451 fn a_clustered_foreign_key_takes_the_monotone_form_and_answers_both_directions() {
1452 let foreign = (0..4000_i64).map(|child| Some(child / 4 + 1)).collect::<Vec<_>>();
1455 let path = related("monotone", 1000, &foreign);
1456 let report = build_links(&path, &[edge()]).expect("build");
1457 assert_eq!(report.len(), 1);
1458 assert!(report[0].built, "{:?}", report[0].note);
1459 assert_eq!(report[0].form, Some(link::Form::Monotone));
1460 assert_eq!(report[0].children, 4000);
1461 assert_eq!(report[0].linked, 4000);
1462
1463 let link = links(&path, &foreign);
1464 assert_eq!(link.form(), link::Form::Monotone);
1465 assert_eq!(link.backward(0), Some(0..4), "the first parent's four children");
1466 assert_eq!(link.backward(999), Some(3996..4000));
1467 assert_eq!(link.backward(1000), None, "past the last parent");
1468
1469 fs::remove_file(&path).expect("clean up");
1470 }
1471
1472 #[test]
1473 fn an_unclustered_foreign_key_takes_the_packed_form_and_still_resolves() {
1474 let foreign = (0..3000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1475 let path = related("packed", 1000, &foreign);
1476 let report = build_links(&path, &[edge()]).expect("build");
1477 assert!(report[0].built, "{:?}", report[0].note);
1478 assert_eq!(report[0].form, Some(link::Form::Packed));
1479
1480 let link = links(&path, &foreign);
1481 assert_eq!(link.backward(0), None, "the packed form answers one direction");
1482 assert!(link.bytes() < 3000 * 2 + 3 * 16, "{} bytes is not bit-packed", link.bytes());
1486
1487 fs::remove_file(&path).expect("clean up");
1488 }
1489
1490 #[test]
1491 fn a_built_link_leaves_the_shape_of_the_relationship_beside_it() {
1492 let foreign = (0..4000_i64).map(|child| Some(child / 4 + 1)).collect::<Vec<_>>();
1495 let path = related("degrees", 1000, &foreign);
1496 let report = build_links(&path, &[edge()]).expect("build");
1497 assert!(report[0].built, "{:?}", report[0].note);
1498 let measured = report[0].degrees.as_ref().expect("the build measured it");
1499 assert!((measured.mean() - 4.0).abs() < 1e-9);
1500
1501 let catalog = Catalog::open(&path).expect("reopen");
1502 let child = catalog.table("child").expect("the child");
1503 let held = stored_degrees(&child, 0).expect("it is in the file");
1504 assert_eq!(&held, measured, "what the build measured is what the file holds");
1505 assert_eq!(held.parents(), 1000);
1506 assert_eq!(held.highest(), 4);
1507 assert!(held.total(), "every child found a parent");
1508 assert!(held.unique(), "and the parent key is why there is a link at all");
1509 let near = held.locality().expect("something to gather");
1513 assert!((near - 999.0 / 3999.0).abs() < 1e-9, "{near}");
1514 assert!(stored_degrees(&child, 1).is_none(), "and no other column has one");
1515
1516 fs::remove_file(&path).expect("clean up");
1517 }
1518
1519 #[test]
1520 fn a_foreign_key_that_matches_nothing_is_a_child_with_no_parent() {
1521 let foreign = vec![Some(1), Some(2), None, Some(9999), Some(3)];
1524 let path = related("orphans", 10, &foreign);
1525 let report = build_links(&path, &[edge()]).expect("build");
1526 assert!(report[0].built, "{:?}", report[0].note);
1527 assert_eq!(report[0].form, Some(link::Form::Packed));
1528 assert_eq!(report[0].children, 5);
1529 assert_eq!(report[0].linked, 3, "the null and the key that matches nothing are not links");
1530
1531 let link = links(&path, &foreign);
1532 assert_eq!(link.forward(2), None, "a null is not a link");
1533 assert_eq!(link.forward(3), None, "a key that matches nothing is not a link");
1534
1535 fs::remove_file(&path).expect("clean up");
1536 }
1537
1538 #[test]
1539 fn a_parent_with_no_key_map_is_a_relationship_with_no_link_rather_than_an_error() {
1540 let path = table_of("unmapped", &(1..=100_i64).map(Some).collect::<Vec<_>>());
1543 let mut writer = Writer::open(&path, "child", vec![Field::new("fk", LogicalType::BigInt)])
1544 .expect("a second table");
1545 let values = (1..=100_i64).map(Value::BigInt).collect::<Vec<_>>();
1546 writer
1547 .append(
1548 &Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1549 .expect("one column"),
1550 )
1551 .expect("a part");
1552 writer.finish().expect("commit");
1553
1554 let report = build_links(&path, &[edge()]).expect("build");
1555 assert!(!report[0].built);
1556 assert_eq!(report[0].note.as_deref(), Some("no key map is stored for parent"));
1557
1558 let catalog = Catalog::open(&path).expect("reopen");
1559 let child = catalog.table("child").expect("the child");
1560 let parent = catalog.table("parent").expect("the parent");
1561 assert!(stored_link(&child, &parent, &edge()).is_none());
1562
1563 fs::remove_file(&path).expect("clean up");
1564 }
1565
1566 #[test]
1567 fn a_link_asked_for_against_the_wrong_parent_is_not_handed_over() {
1568 let foreign = (0..500_i64).map(|child| Some(child / 5 + 1)).collect::<Vec<_>>();
1572 let path = related("binding", 100, &foreign);
1573 build_links(&path, &[edge()]).expect("build");
1574
1575 let catalog = Catalog::open(&path).expect("reopen");
1576 let child = catalog.table("child").expect("the child");
1577 let parent = catalog.table("parent").expect("the parent");
1578 let held = stored_link(&child, &parent, &edge()).expect("the link is handed over");
1579 let counts = stored_link_counts(&child, &parent, &edge()).expect("and so are its counts");
1581 assert_eq!(
1582 counts,
1583 link::Counts {
1584 children: held.children(),
1585 parents: held.parents(),
1586 linked: held.linked()
1587 }
1588 );
1589 assert_eq!((counts.children, counts.linked), (500, 500));
1590 let wrong = Edge { parent: "child".into(), ..edge() };
1591 assert!(stored_link(&child, &parent, &wrong).is_none(), "a different parent name");
1592 assert!(stored_link_counts(&child, &parent, &wrong).is_none(), "a different parent name");
1593 let wrong = Edge { parent_column: 1, ..edge() };
1594 assert!(stored_link(&child, &parent, &wrong).is_none(), "a different parent column");
1595 assert!(stored_link_counts(&child, &parent, &wrong).is_none(), "a different parent column");
1596 let wrong = Edge { child_column: 1, ..edge() };
1597 assert!(stored_link(&child, &parent, &wrong).is_none(), "a different child column");
1598 assert!(stored_link_counts(&child, &parent, &wrong).is_none(), "a different child column");
1599
1600 fs::remove_file(&path).expect("clean up");
1601 }
1602
1603 #[test]
1604 fn a_link_that_does_not_fit_the_budget_is_reported_rather_than_stored() {
1605 let foreign = (0..60_000_i64).map(|child| Some((child * 7) % 1000 + 1)).collect::<Vec<_>>();
1608 let path = related("budget", 1000, &foreign);
1609 let report = build_links_within(&path, &[edge()], 0).expect("build");
1610 assert!(!report[0].built);
1611 assert!(report[0].bytes > 0, "the report says what a larger budget would buy");
1612 assert!(report[0].note.as_deref().unwrap_or_default().contains("budget"), "{report:?}");
1613
1614 let catalog = Catalog::open(&path).expect("reopen");
1615 let child = catalog.table("child").expect("the child");
1616 let parent = catalog.table("parent").expect("the parent");
1617 assert!(stored_link(&child, &parent, &edge()).is_none());
1618 assert!(report[0].degrees.is_some(), "it was measured");
1621 assert!(stored_degrees(&child, 0).is_none(), "and not written");
1622 assert_eq!(refused_link(&child, 0), Some((link::Form::Packed, report[0].bytes as u64)));
1625
1626 fs::remove_file(&path).expect("clean up");
1627 }
1628
1629 #[test]
1630 fn the_budget_keeps_the_link_that_saves_the_larger_hash_table() {
1631 let path = table_of("rank", &(1..=1000).map(Some).collect::<Vec<_>>());
1636 let small = [Field::new("key", LogicalType::BigInt)];
1637 let mut writer = Writer::open(&path, "small", small.to_vec()).expect("a second table");
1638 let keys = (1..=4).map(Value::BigInt).collect::<Vec<_>>();
1639 let chunk =
1640 Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &keys).expect("keys")])
1641 .expect("one column");
1642 writer.append(&chunk).expect("a part");
1643 writer.finish().expect("commit");
1644 let rows = (0..45_000_i64)
1645 .map(|child| (Some((child * 7) % 1000 + 1), Some(child % 4 + 1)))
1646 .collect::<Vec<_>>();
1647 let fields = vec![
1648 Field::new("large", LogicalType::BigInt),
1649 Field::new("small", LogicalType::BigInt),
1650 ];
1651 pairs_into(Writer::open(&path, "child", fields).expect("a third table"), &rows);
1652 build_key_maps(&path, "parent", &[0]).expect("the large key map");
1653 build_key_maps(&path, "small", &[0]).expect("the small key map");
1654
1655 let edges = [
1656 edge(),
1657 Edge {
1658 child: "child".into(),
1659 child_column: 1,
1660 parent: "small".into(),
1661 parent_column: 0,
1662 },
1663 ];
1664 let report = build_links_within(&path, &edges, 0).expect("build");
1665 assert!(report[1].bytes < report[0].bytes, "the small link is the cheaper one");
1666 assert!(
1667 (report[0].bytes + report[1].bytes) as u64 > BUDGET_FLOOR,
1668 "the two have to not fit together for this to test anything"
1669 );
1670 assert_eq!((report[0].parents, report[1].parents), (1000, 4));
1671 assert!(report[0].built, "the link that saves a thousand rows was turned away: {report:?}");
1672 assert!(!report[1].built, "the link that saves four rows was kept instead");
1673
1674 fs::remove_file(&path).expect("clean up");
1675 }
1676
1677 #[test]
1678 fn a_parent_whose_key_repeats_gets_no_link_at_all() {
1679 let path = table_of("repeats", &[Some(1), Some(1), Some(2)]);
1683 let mut writer = Writer::open(&path, "child", vec![Field::new("fk", LogicalType::BigInt)])
1684 .expect("a second table");
1685 let values = [Value::BigInt(1), Value::BigInt(2)];
1686 writer
1687 .append(
1688 &Chunk::new(vec![Vector::from_values(LogicalType::BigInt, &values).expect("keys")])
1689 .expect("one column"),
1690 )
1691 .expect("a part");
1692 writer.finish().expect("commit");
1693 build_key_maps(&path, "parent", &[0]).expect("the parent's key map");
1694
1695 let report = build_links(&path, &[edge()]).expect("build");
1696 assert!(!report[0].built);
1697 assert_eq!(report[0].note.as_deref(), Some("no key map is stored for parent"));
1698
1699 fs::remove_file(&path).expect("clean up");
1700 }
1701}