rudb_graph/keymap.rs
1//! Turning a parent key value into a [`Rid`].
2//!
3//! A link is built from an equality between a child column and a parent column, and to build it the
4//! parent column's values have to become row ids. That map is the key map. It has the three
5//! physical forms of spec/graph/02-the-data-model.md section 2.2, chosen by measurement at build
6//! time rather than by declaration, and the form that was chosen is recorded in the header so that
7//! a reader does not have to guess.
8//!
9//! The three exist because they are three different answers to the same question and the cheapest
10//! one is usually available:
11//!
12//! - [`Form::Identity`] when the keys are exactly `base .. base + n` in order. Nothing is stored
13//! but two numbers, and TPC-H hits this on six of its eight tables.
14//! - [`Form::Dense`] when the keys are distinct integers packed densely enough into a range that a
15//! bitmap plus a rank index beats storing them.
16//! - [`Form::Sorted`] for everything else, including every string key, which arrives here as
17//! dictionary codes rather than as text.
18//!
19//! What is deliberately absent is a hash. A minimal perfect hash is faster to probe than the sorted
20//! form and much slower to build, and there is no measurement yet saying the probe is where the
21//! time goes. spec/graph/11-open-questions.md keeps it open, and adding it later costs nothing
22//! because the form is a tag in a header that a reader is already required to be able to not
23//! recognize.
24
25use rudb_common::{Error, Result};
26use rudb_encoding::bitpack;
27
28use crate::bits::Rank;
29use crate::rid::Rid;
30
31/// How dense a range has to be before the bitmap form beats the sorted form.
32///
33/// One in eight, per section 2.2. Below it the bitmap is larger than storing the keys: a bitmap
34/// costs `range / 8` bytes plus about an eighth again for the rank index, and the sorted form costs
35/// `count` keys plus `count` permutation entries, so the crossover is a ratio rather than a size.
36/// The default is here as a named constant rather than inline because it is a number somebody will
37/// want to move once there is a measurement that says where, and moving it should be a diff.
38pub const DENSE_THRESHOLD: u64 = 8;
39
40/// Which of the three physical forms a key map took.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum Form {
43 /// `rid = key - base`, and nothing is stored but `base` and the count.
44 Identity,
45 /// `rid = rank(key - base)` over a bitmap of the range, with a two level rank index.
46 Dense,
47 /// Binary search over the sorted keys, then a permutation lookup.
48 Sorted,
49}
50
51impl Form {
52 /// The tag this form takes in a section header.
53 #[must_use]
54 pub fn tag(self) -> u8 {
55 match self {
56 Self::Identity => 0,
57 Self::Dense => 1,
58 Self::Sorted => 2,
59 }
60 }
61
62 /// What this form is called where a person reads it, which is `rudb_links()`.
63 #[must_use]
64 pub fn label(self) -> &'static str {
65 match self {
66 Self::Identity => "identity",
67 Self::Dense => "dense",
68 Self::Sorted => "sorted",
69 }
70 }
71
72 /// The form a header tag names.
73 ///
74 /// # Errors
75 ///
76 /// If the tag is not one of the three. A reader that meets an unfamiliar form has met a file
77 /// written by a later build, and the right response is the one section 3.2 requires of an
78 /// unfamiliar section kind: ignore this key map and answer the query without it. So this
79 /// returns an error and the caller drops the section rather than failing the open.
80 pub fn from_tag(tag: u8) -> Result<Self> {
81 match tag {
82 0 => Ok(Self::Identity),
83 1 => Ok(Self::Dense),
84 2 => Ok(Self::Sorted),
85 _ => Err(malformed(format!("key map form {tag} is not one this build knows"))),
86 }
87 }
88}
89
90/// What the build saw while it read the parent key column.
91///
92/// This is the cardinality verification of section 2.3, and it is written into the header rather
93/// than recomputed because the build already had every value in front of it. Recording what was
94/// observed rather than what was declared is what keeps a wrong `FOREIGN KEY` from producing a
95/// wrong answer: a declaration that fails verification is reported, and no link is built.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub struct Observed {
98 /// Non-null values seen.
99 pub rows: u64,
100 /// Nulls seen, which are not keys and match no child row.
101 pub nulls: u64,
102 /// Whether every non-null value was distinct. False means no link may be built at all.
103 pub distinct: bool,
104 /// Whether the values arrived in non-decreasing order.
105 pub sorted: bool,
106 /// The smallest non-null value, or `None` when there were none.
107 pub min: Option<i128>,
108 /// The largest non-null value, or `None` when there were none.
109 pub max: Option<i128>,
110}
111
112impl Observed {
113 /// Whether this column can be the parent side of a link.
114 ///
115 /// Distinctness is the whole requirement. A parent side that is not unique is not an error and
116 /// is not a link: section 2.3 says it is a relationship that has to be executed as an ordinary
117 /// join, and the planner is told so rather than left to find out.
118 #[must_use]
119 pub fn usable_as_parent(&self) -> bool {
120 self.distinct
121 }
122}
123
124/// The three forms, behind one interface.
125#[derive(Debug, Clone)]
126enum Body {
127 Identity {
128 base: i128,
129 count: u64,
130 },
131 Dense {
132 base: i128,
133 range: u64,
134 bits: Vec<u64>,
135 rank: Rank,
136 },
137 Sorted {
138 /// The smallest key, so that every stored key is a `u64` offset from it whatever the
139 /// column's own type was.
140 base: i128,
141 /// Bits one stored key offset takes.
142 key_width: usize,
143 /// The key offsets in ascending order, bit packed.
144 keys: Vec<u8>,
145 /// Bits one permutation entry takes, which is `ceil(log2(rows))`.
146 rid_width: usize,
147 /// Sorted position to `rid`, bit packed.
148 perm: Vec<u8>,
149 count: u64,
150 },
151}
152
153/// A map from a parent key value to the `rid` of the row that holds it.
154#[derive(Debug, Clone)]
155pub struct KeyMap {
156 body: Body,
157 observed: Observed,
158}
159
160impl KeyMap {
161 /// Builds the cheapest correct form for these keys.
162 ///
163 /// `keys` is the parent key column in `rid` order, with `None` for a null. The `rid` of a value
164 /// is its index, which is what makes this the whole build: the caller has already read the
165 /// column in append order, so the row ids are the positions and there is nothing to look up.
166 ///
167 /// String keys arrive here as dictionary codes rather than as text, per section 2.2. That is
168 /// not a convenience, it is the reason a sorted key map over a `VARCHAR` column never touches a
169 /// byte of text: the codes of a file wide stable dictionary are integers with the column's own
170 /// order, so the search is over `u32`.
171 ///
172 /// # Errors
173 ///
174 /// If the column's values span more than a `u64`, if it holds more rows than a `u64` of
175 /// `rid`s, or if a bit packed payload cannot be written. A non-distinct column is not an
176 /// error: it produces a key map whose [`Observed`] says so, and the caller is expected to ask
177 /// before building a link on it.
178 pub fn build(keys: &[Option<i128>]) -> Result<Self> {
179 let mut observed = observe(keys);
180 // See [`KeyMap::build_from`], which takes the same shortcut for the same reason and is
181 // where the reason is written. The two paths agree on every column or a table's key map
182 // depends on which of them built it.
183 if !observed.distinct {
184 return Ok(Self { body: Body::Identity { base: 0, count: 0 }, observed });
185 }
186 Ok(match plan(&observed)? {
187 Plan::Empty => Self { body: Body::Identity { base: 0, count: 0 }, observed },
188 Plan::Identity { base, count } => {
189 Self { body: Body::Identity { base, count }, observed }
190 }
191 Plan::Dense { base, range } => Self { body: dense(keys, base, range)?, observed },
192 Plan::Sorted { base } => {
193 // The sorted form sorts, so it is the one place distinctness can be settled for a
194 // column that did not arrive in order. `observe` can only see an adjacent
195 // duplicate; this sees every duplicate, and the answer replaces the guess.
196 let (body, distinct) = sorted(keys, base, observed.rows)?;
197 observed.distinct = distinct;
198 Self { body, observed }
199 }
200 })
201 }
202
203 /// Builds the cheapest correct form by reading the column rather than by holding it.
204 ///
205 /// The same build as [`KeyMap::build`] and the same decision, taken from a source that can be
206 /// scanned twice instead of from a slice that is already in memory. That difference is the
207 /// whole reason this exists. A parent key column at TPC-H SF10 is fifteen million rows of
208 /// `orders`, and a `Vec<Option<i128>>` of those is four hundred and eighty megabytes held for
209 /// the length of a build that does not need a single one of them twice. At SF100 it is four and
210 /// a half gigabytes, which is not a slow build, it is a build that does not happen.
211 ///
212 /// So the first scan observes and nothing else, and what the second scan does depends on what
213 /// the first one found. The identity form, which is the form every TPC-H parent key takes,
214 /// needs no second scan at all: the four observed facts are the whole map. The dense form fills
215 /// a bitmap sized from the range, which is bounded by the table rather than by the scan. Only
216 /// the sorted form has to hold the column, because sorting is what it is, and it says so here
217 /// rather than surprising a caller with it.
218 ///
219 /// # Errors
220 ///
221 /// If the scan fails, or for any of the reasons [`KeyMap::build`] fails.
222 pub fn build_from<K: Keys + ?Sized>(keys: &K) -> Result<Self> {
223 let mut observer = Observer::new();
224 keys.scan(&mut |key| {
225 observer.push(key);
226 Ok(())
227 })?;
228 let mut observed = observer.observed;
229 // A column the first scan already saw a repeat in gets no body at all. No form answers a
230 // rid for a key that is in two rows, so every byte spent on one is spent on a map nothing
231 // may use, and the bytes are not small: TPC-H SF10 `lineitem(l_orderkey)` sorts sixty
232 // million keys into three hundred and ninety megabytes before the budget throws all of it
233 // away. This is only reachable where the duplicates are adjacent, which is where the column
234 // arrived in order, and that is the case this is for. A repeat that only the sort can find
235 // is still found by the sort, below.
236 if !observed.distinct {
237 return Ok(Self { body: Body::Identity { base: 0, count: 0 }, observed });
238 }
239 Ok(match plan(&observed)? {
240 Plan::Empty => Self { body: Body::Identity { base: 0, count: 0 }, observed },
241 Plan::Identity { base, count } => {
242 Self { body: Body::Identity { base, count }, observed }
243 }
244 Plan::Dense { base, range } => {
245 let mut bits = DenseBits::new(base, range);
246 keys.scan(&mut |key| match key {
247 Some(key) => bits.push(key),
248 None => Ok(()),
249 })?;
250 Self { body: bits.finish(), observed }
251 }
252 Plan::Sorted { base } => {
253 let mut held = Vec::with_capacity(
254 usize::try_from(observed.rows + observed.nulls).unwrap_or_default(),
255 );
256 keys.scan(&mut |key| {
257 held.push(key);
258 Ok(())
259 })?;
260 let (body, distinct) = sorted(&held, base, observed.rows)?;
261 observed.distinct = distinct;
262 Self { body, observed }
263 }
264 })
265 }
266
267 /// Which form this map took.
268 #[must_use]
269 pub fn form(&self) -> Form {
270 match self.body {
271 Body::Identity { .. } => Form::Identity,
272 Body::Dense { .. } => Form::Dense,
273 Body::Sorted { .. } => Form::Sorted,
274 }
275 }
276
277 /// The smallest key and how many key values from it the map spans, when the keys are compact.
278 ///
279 /// The identity and dense forms are the two that exist because the keys fill most of a range,
280 /// at most one hole in [`DENSE_THRESHOLD`] values, so a bitmap over that range is at most that
281 /// many bits a parent row. That is what lets a join test a child's key against a set of parents
282 /// with one subtraction and one bit, and with no link at all. The sorted form is the one for
283 /// keys spread over a range too wide for that, and answers `None`.
284 #[must_use]
285 pub fn span(&self) -> Option<(i128, u64)> {
286 match self.body {
287 Body::Identity { base, count } => Some((base, count)),
288 Body::Dense { base, range, .. } => Some((base, range)),
289 Body::Sorted { .. } => None,
290 }
291 }
292
293 /// What the build saw, which is the cardinality verification.
294 #[must_use]
295 pub fn observed(&self) -> &Observed {
296 &self.observed
297 }
298
299 /// The value every stored key is an offset from, which is the smallest key.
300 pub(crate) fn base(&self) -> i128 {
301 match &self.body {
302 Body::Identity { base, .. } | Body::Dense { base, .. } | Body::Sorted { base, .. } => {
303 *base
304 }
305 }
306 }
307
308 /// Appends the form's own bytes, after the header that `wire` has already written.
309 ///
310 /// Nothing here is stored that the header and the form together derive. The identity form
311 /// writes nothing at all, because its count is the header's row count, which is section 3.3's
312 /// "no extents beyond the header" in code rather than in prose.
313 pub(crate) fn write_body(&self, out: &mut Vec<u8>) -> Result<()> {
314 match &self.body {
315 Body::Identity { .. } => Ok(()),
316 Body::Dense { range, bits, rank, .. } => {
317 out.extend_from_slice(&range.to_le_bytes());
318 for word in bits {
319 out.extend_from_slice(&word.to_le_bytes());
320 }
321 rank.write(out);
322 Ok(())
323 }
324 Body::Sorted { key_width, keys, rid_width, perm, .. } => {
325 // The widths are a byte each, and a width past sixty four is a width no `u64` key
326 // offset can have taken, so it is a torn header rather than a wide key.
327 let widths = [*key_width, *rid_width];
328 for width in widths {
329 let width = u8::try_from(width)
330 .map_err(|_| malformed("a sorted key map's width does not fit a byte"))?;
331 out.push(width);
332 }
333 out.extend_from_slice(keys);
334 out.extend_from_slice(perm);
335 Ok(())
336 }
337 }
338 }
339
340 /// Reads back what [`KeyMap::write_body`] wrote, and fills in the maximum key.
341 ///
342 /// The maximum is not in the header because each form derives it: identity from its count,
343 /// dense from its range, sorted from its last stored key. That is the whole reason this takes
344 /// [`Observed`] and returns a map rather than taking a finished one.
345 ///
346 /// # Errors
347 ///
348 /// If the body is not exactly the length its header implies. Exactly, not at least: a body
349 /// longer than its form needs means the header and the body disagree about which form this is,
350 /// and the safe reading of a disagreement is neither of them.
351 pub(crate) fn read_body(
352 form: Form,
353 base: i128,
354 mut observed: Observed,
355 body: &[u8],
356 ) -> Result<Self> {
357 match form {
358 Form::Identity => {
359 if !body.is_empty() {
360 return Err(malformed("an identity key map has no body"));
361 }
362 if observed.rows > 0 {
363 observed.max = Some(
364 base.checked_add(i128::from(observed.rows) - 1)
365 .ok_or_else(|| malformed("an identity key map's range overflows"))?,
366 );
367 }
368 Ok(Self { body: Body::Identity { base, count: observed.rows }, observed })
369 }
370 Form::Dense => {
371 let Some(head) = body.get(..size_of::<u64>()) else {
372 return Err(malformed("a dense key map has no range"));
373 };
374 let range = u64::from_le_bytes(head.try_into().expect("eight bytes"));
375 let Ok(range_usize) = usize::try_from(range) else {
376 return Err(malformed("a dense key map's range does not fit this machine"));
377 };
378 let words = range_usize.div_ceil(64);
379 let bitmap = words * size_of::<u64>();
380 let rest = &body[size_of::<u64>()..];
381 if rest.len() < bitmap {
382 return Err(malformed("a dense key map's bitmap is shorter than its range"));
383 }
384 let bits: Vec<u64> = rest[..bitmap]
385 .chunks_exact(size_of::<u64>())
386 .map(|word| u64::from_le_bytes(word.try_into().expect("eight bytes")))
387 .collect();
388 let rank = Rank::read(&rest[bitmap..], words)?;
389 observed.max = Some(
390 base.checked_add(i128::from(range) - 1)
391 .ok_or_else(|| malformed("a dense key map's range overflows"))?,
392 );
393 Ok(Self { body: Body::Dense { base, range, bits, rank }, observed })
394 }
395 Form::Sorted => {
396 if body.len() < 2 {
397 return Err(malformed("a sorted key map has no widths"));
398 }
399 let key_width = usize::from(body[0]);
400 let rid_width = usize::from(body[1]);
401 if key_width == 0 || key_width > 64 || rid_width == 0 || rid_width > 64 {
402 return Err(malformed("a sorted key map's width is not one a u64 can take"));
403 }
404 let count = observed.rows;
405 let Ok(count_usize) = usize::try_from(count) else {
406 return Err(malformed(
407 "a sorted key map holds more keys than this machine can",
408 ));
409 };
410 let key_bytes = (count_usize * key_width).div_ceil(8);
411 let perm_bytes = (count_usize * rid_width).div_ceil(8);
412 let rest = &body[2..];
413 if rest.len() != key_bytes + perm_bytes {
414 return Err(malformed(
415 "a sorted key map's arrays are not the size its widths and count imply",
416 ));
417 }
418 let keys = rest[..key_bytes].to_vec();
419 let perm = rest[key_bytes..].to_vec();
420 if count > 0 {
421 let largest = bitpack::tail_at(&keys, key_width, count_usize - 1)?;
422 observed.max =
423 Some(base.checked_add(i128::from(largest)).ok_or_else(|| {
424 malformed("a sorted key map's largest key overflows")
425 })?);
426 }
427 Ok(Self {
428 body: Body::Sorted { base, key_width, keys, rid_width, perm, count },
429 observed,
430 })
431 }
432 }
433 }
434
435 /// Keys this map resolves.
436 #[must_use]
437 pub fn len(&self) -> u64 {
438 match &self.body {
439 Body::Identity { count, .. } | Body::Sorted { count, .. } => *count,
440 Body::Dense { .. } => self.observed.rows,
441 }
442 }
443
444 /// Whether this map resolves nothing.
445 #[must_use]
446 pub fn is_empty(&self) -> bool {
447 self.len() == 0
448 }
449
450 /// Bytes this map holds resident, for the budget of section 3.7 and the cache of section 4.4.
451 ///
452 /// Identity is twenty four bytes and says so, which is the number that makes the budget
453 /// livable on TPC-H.
454 #[must_use]
455 pub fn bytes(&self) -> usize {
456 match &self.body {
457 Body::Identity { .. } => size_of::<i128>() + size_of::<u64>(),
458 Body::Dense { bits, rank, .. } => bits.len() * size_of::<u64>() + rank.bytes(),
459 Body::Sorted { keys, perm, .. } => keys.len() + perm.len(),
460 }
461 }
462
463 /// The `rid` of the row holding this key, or `None` when no row holds it.
464 ///
465 /// `None` is the ordinary answer and not an exceptional one: a child key with no matching
466 /// parent is what section 2.4 reserves *no parent* for, and a null child key never reaches
467 /// here at all.
468 ///
469 /// # Errors
470 ///
471 /// If a bit packed payload is torn, which is a corrupt section rather than a missing key.
472 pub fn lookup(&self, key: i128) -> Result<Option<Rid>> {
473 match &self.body {
474 Body::Identity { base, count } => {
475 let Some(offset) = key.checked_sub(*base) else {
476 return Ok(None);
477 };
478 match u64::try_from(offset) {
479 Ok(rid) if rid < *count => Ok(Some(rid)),
480 _ => Ok(None),
481 }
482 }
483 Body::Dense { base, range, bits, rank } => {
484 let Some(offset) = key.checked_sub(*base) else {
485 return Ok(None);
486 };
487 let Ok(offset) = u64::try_from(offset) else {
488 return Ok(None);
489 };
490 if offset >= *range {
491 return Ok(None);
492 }
493 #[expect(
494 clippy::cast_possible_truncation,
495 reason = "the build checked the range fits a usize"
496 )]
497 let at = offset as usize;
498 if bits[at / 64] >> (at % 64) & 1 == 0 {
499 return Ok(None);
500 }
501 Ok(Some(rank.rank(bits, at)))
502 }
503 Body::Sorted { base, key_width, keys, rid_width, perm, count } => {
504 let Some(offset) = key.checked_sub(*base) else {
505 return Ok(None);
506 };
507 let Ok(wanted) = u64::try_from(offset) else {
508 return Ok(None);
509 };
510 #[expect(
511 clippy::cast_possible_truncation,
512 reason = "the build refused a column wider than a usize of rows"
513 )]
514 let len = *count as usize;
515 // A plain binary search over the packed keys. Branchless in the sense that matters
516 // here, which is that the comparison drives an index rather than a branch to a
517 // different loop, and every probe is one `tail_at` rather than a decode of the
518 // block around it.
519 let mut low = 0_usize;
520 let mut high = len;
521 while low < high {
522 let mid = low + (high - low) / 2;
523 let at = bitpack::tail_at(keys, *key_width, mid)?;
524 if at < wanted {
525 low = mid + 1;
526 } else {
527 high = mid;
528 }
529 }
530 if low >= len || bitpack::tail_at(keys, *key_width, low)? != wanted {
531 return Ok(None);
532 }
533 Ok(Some(bitpack::tail_at(perm, *rid_width, low)?))
534 }
535 }
536 }
537}
538
539/// A parent key column that can be read more than once, in `rid` order.
540///
541/// The build wants two passes over a column it does not want to hold, so this is what it reads
542/// instead of a slice: something that can be asked to produce the column again. A file can do that
543/// for the price of a read, and the second read is against pages the first one just warmed.
544///
545/// Values arrive as `Option<i128>`, with `None` for a null. A string key arrives as its dictionary
546/// code rather than as text, per section 2.2, which is why one integer signature covers every key
547/// type rudb has.
548pub trait Keys {
549 /// Calls `each` once per row of the column, in `rid` order.
550 ///
551 /// # Errors
552 ///
553 /// If the column cannot be read, or if `each` fails, which stops the scan rather than
554 /// continuing past a value that could not be used.
555 fn scan(&self, each: &mut dyn FnMut(Option<i128>) -> Result<()>) -> Result<()>;
556}
557
558impl Keys for [Option<i128>] {
559 fn scan(&self, each: &mut dyn FnMut(Option<i128>) -> Result<()>) -> Result<()> {
560 for key in self {
561 each(*key)?;
562 }
563 Ok(())
564 }
565}
566
567/// Which form the build chose, decided once and carried out twice.
568///
569/// Separating the decision from the filling is what lets [`KeyMap::build`] and
570/// [`KeyMap::build_from`] be the same build. A second copy of these three conditions is a second
571/// place for the positional guard below to be got wrong.
572enum Plan {
573 Empty,
574 Identity { base: i128, count: u64 },
575 Dense { base: i128, range: u64 },
576 Sorted { base: i128 },
577}
578
579/// Picks the cheapest form that is correct for what the column turned out to hold.
580fn plan(observed: &Observed) -> Result<Plan> {
581 // A column with no keys in it at all is an identity map over nothing. It is worth having rather
582 // than refusing, because an empty parent table is a legal table and a join against it returns
583 // no rows rather than failing.
584 if observed.rows == 0 {
585 return Ok(Plan::Empty);
586 }
587 let (Some(min), Some(max)) = (observed.min, observed.max) else {
588 // A non-zero row count guarantees both, so this is unreachable. It is an error rather than
589 // an `expect` because a key map that panicked on its own bookkeeping would take down a
590 // query that section 3.1 promises can always be answered without it.
591 return Err(malformed("a column with keys in it reported no minimum"));
592 };
593 let range = range_of(min, max)?;
594
595 // Both of the cheap forms answer with a *count of keys below the value*, and both are correct
596 // only where that count is the `rid`. It is the `rid` when the column is ascending and holds no
597 // nulls, and it is not otherwise: a null earlier in the column, or a value out of order, shifts
598 // every row after it. Getting this wrong would not fail, it would resolve every key to a
599 // neighbour of the right row, which is the one failure mode section 3.1 does not catch for
600 // free. So the guard is shared and stated once.
601 let positional = observed.distinct && observed.sorted && observed.nulls == 0;
602
603 if positional && range == observed.rows {
604 // Identity needs more than positional: it needs the values to be exactly the positions,
605 // which on a distinct ascending column is the range equalling the row count. The check is
606 // subtraction rather than a walk because the walk already happened in the observation.
607 return Ok(Plan::Identity { base: min, count: observed.rows });
608 }
609
610 // The bitmap is over the value range, so a range that does not fit a `usize` cannot be one
611 // however dense it is.
612 if positional && usize::try_from(range).is_ok() && range / observed.rows < DENSE_THRESHOLD {
613 return Ok(Plan::Dense { base: min, range });
614 }
615
616 Ok(Plan::Sorted { base: min })
617}
618
619/// The four facts section 3.3 says the build records, accumulated one value at a time.
620///
621/// One value at a time rather than one column at a time so that the pass can be driven by a scan
622/// of a file as easily as by a slice. See [`KeyMap::build_from`] for why that matters.
623struct Observer {
624 observed: Observed,
625 previous: Option<i128>,
626}
627
628impl Observer {
629 fn new() -> Self {
630 Self {
631 observed: Observed {
632 rows: 0,
633 nulls: 0,
634 distinct: true,
635 sorted: true,
636 min: None,
637 max: None,
638 },
639 previous: None,
640 }
641 }
642
643 // Distinctness on a column that is not sorted cannot be settled in one pass without a set, so
644 // this settles it for the sorted case and leaves the unsorted case to the sort that the sorted
645 // form does anyway. That is why `distinct` is fixed up in `sorted` below rather than being
646 // final here, and it is worth the awkwardness: the common case on real keys is ascending, and a
647 // hash set over fifteen million rows to discover what adjacency already proves is the build
648 // cost this avoids.
649 fn push(&mut self, key: Option<i128>) {
650 let Some(key) = key else {
651 self.observed.nulls += 1;
652 return;
653 };
654 self.observed.rows += 1;
655 self.observed.min = Some(self.observed.min.map_or(key, |held| held.min(key)));
656 self.observed.max = Some(self.observed.max.map_or(key, |held| held.max(key)));
657 if let Some(previous) = self.previous {
658 if key < previous {
659 self.observed.sorted = false;
660 } else if key == previous {
661 self.observed.distinct = false;
662 }
663 }
664 self.previous = Some(key);
665 }
666}
667
668/// One pass over the column, recording the four facts section 3.3 says the build records.
669fn observe(keys: &[Option<i128>]) -> Observed {
670 let mut observer = Observer::new();
671 for key in keys {
672 observer.push(*key);
673 }
674 observer.observed
675}
676
677/// How many distinct values lie between `min` and `max` inclusive.
678///
679/// The arithmetic is in `u128` and not `i128` because a column holding both `i128::MIN` and
680/// `i128::MAX` has a range of `2^128`, and `max - min` on an `i128` for that column is an overflow
681/// rather than a number. A `HUGEINT` key column spanning more than a `u64` of values is pathological
682/// but legal, so it gets an error naming what happened rather than a panic in a build: the caller
683/// records the relationship as not built, exactly as it does for one that does not fit the budget.
684///
685/// `max >= min` always holds here, so the wrapping subtraction is exact in `u128`.
686fn range_of(min: i128, max: i128) -> Result<u64> {
687 let span = max.wrapping_sub(min) as u128;
688 u64::try_from(span)
689 .ok()
690 .and_then(|span| span.checked_add(1))
691 .ok_or_else(|| malformed("the key column spans more than a u64 of values"))
692}
693
694/// The offset a key takes from the base.
695///
696/// `range_of` bounded the span to a `u64` before either form that uses this was chosen, so the
697/// subtraction cannot overflow and the offset cannot exceed a `u64`. Both are checked anyway: this
698/// is the one arithmetic in the crate whose silent failure would resolve keys to the wrong rows.
699fn offset_of(key: i128, base: i128) -> Result<u64> {
700 let offset = key
701 .checked_sub(base)
702 .ok_or_else(|| malformed("a key is further from the base than an i128 holds"))?;
703 u64::try_from(offset)
704 .map_err(|_| malformed("a key is below the base or further from it than a u64 holds"))
705}
706
707/// Builds the bitmap form one key at a time.
708///
709/// The caller guarantees the column is distinct, ascending and null free, which is what makes a
710/// rank equal to a `rid`. The assertion restates it where the correctness depends on it rather than
711/// where the decision was made.
712struct DenseBits {
713 base: i128,
714 range: u64,
715 bits: Vec<u64>,
716 previous: Option<i128>,
717}
718
719impl DenseBits {
720 fn new(base: i128, range: u64) -> Self {
721 #[expect(
722 clippy::cast_possible_truncation,
723 reason = "the caller checked the range fits a usize"
724 )]
725 let range_usize = range as usize;
726 Self { base, range, bits: vec![0_u64; range_usize.div_ceil(64)], previous: None }
727 }
728
729 fn push(&mut self, key: i128) -> Result<()> {
730 debug_assert!(
731 self.previous.is_none_or(|held| key > held),
732 "the bitmap form needs a distinct ascending column, because a rank is a count of keys below a value and that is a rid only there"
733 );
734 self.previous = Some(key);
735 let offset = offset_of(key, self.base)?;
736 #[expect(
737 clippy::cast_possible_truncation,
738 reason = "the caller checked the range fits a usize and the offset is inside it"
739 )]
740 let at = offset as usize;
741 self.bits[at / 64] |= 1 << (at % 64);
742 Ok(())
743 }
744
745 fn finish(self) -> Body {
746 let rank = Rank::build(&self.bits);
747 Body::Dense { base: self.base, range: self.range, bits: self.bits, rank }
748 }
749}
750
751fn dense(keys: &[Option<i128>], base: i128, range: u64) -> Result<Body> {
752 let mut bits = DenseBits::new(base, range);
753 for key in keys.iter().flatten() {
754 bits.push(*key)?;
755 }
756 Ok(bits.finish())
757}
758
759/// Builds the general form, and settles distinctness on the way.
760///
761/// Returns the body and whether every key was distinct. The second is not a courtesy: the sort this
762/// form performs is the only place a duplicate that is not adjacent in the column can be seen, and
763/// section 2.3 needs that answer to decide whether a link may be built at all.
764fn sorted(keys: &[Option<i128>], base: i128, rows: u64) -> Result<(Body, bool)> {
765 let mut pairs: Vec<(u64, u64)> = Vec::with_capacity(keys.len());
766 for (rid, key) in keys.iter().enumerate() {
767 let Some(key) = *key else { continue };
768 let offset = offset_of(key, base)?;
769 let rid = u64::try_from(rid).map_err(|_| malformed("the column is too long for a rid"))?;
770 pairs.push((offset, rid));
771 }
772 // Sorted by key, then by rid so that a duplicated key resolves to its first row rather than to
773 // whichever one the sort happened to leave first. A duplicated key means no link gets built, so
774 // this only decides what a map nobody should be using returns, and deciding it anyway is what
775 // keeps a test of this form reproducible.
776 pairs.sort_unstable();
777 let distinct = pairs.windows(2).all(|pair| pair[0].0 != pair[1].0);
778 debug_assert_eq!(
779 u64::try_from(pairs.len()).ok(),
780 Some(rows),
781 "the pair list is the non-null column"
782 );
783 let key_width = width_for(pairs.last().map_or(0, |pair| pair.0));
784 let rows_width = u64::try_from(keys.len().saturating_sub(1))
785 .map_err(|_| malformed("the column is too long for a rid"))?;
786 let rid_width = width_for(rows_width);
787 let mut key_bytes = Vec::new();
788 let mut rid_bytes = Vec::new();
789 let key_values: Vec<u64> = pairs.iter().map(|pair| pair.0).collect();
790 let rid_values: Vec<u64> = pairs.iter().map(|pair| pair.1).collect();
791 // The linear packer and not the tail one, because a tail is bounded at a thousand values and a
792 // parent key column is not. The layout is the same and `tail_at` reads either.
793 bitpack::pack_linear(&key_values, key_width, &mut key_bytes)?;
794 bitpack::pack_linear(&rid_values, rid_width, &mut rid_bytes)?;
795 Ok((
796 Body::Sorted { base, key_width, keys: key_bytes, rid_width, perm: rid_bytes, count: rows },
797 distinct,
798 ))
799}
800
801/// Bits needed to hold every value up to and including `largest`.
802///
803/// One rather than zero for a largest of zero, because a width of zero is a packed payload with no
804/// bytes in it and `tail_at` on one of those has nothing to return. A column of a single key is a
805/// real column.
806fn width_for(largest: u64) -> usize {
807 let bits = u64::BITS - largest.leading_zeros();
808 bits.max(1) as usize
809}
810
811fn malformed(message: impl Into<String>) -> Error {
812 Error::invalid_input(format!("invalid rudb key map: {}", message.into()))
813}
814
815#[cfg(test)]
816mod tests {
817 use super::*;
818
819 fn keys(values: &[i128]) -> Vec<Option<i128>> {
820 values.iter().copied().map(Some).collect()
821 }
822
823 /// Every key in the column resolves to the row that holds it, whatever form was chosen.
824 fn resolves(column: &[Option<i128>], map: &KeyMap) {
825 for (rid, key) in column.iter().enumerate() {
826 let Some(key) = *key else { continue };
827 let found = map.lookup(key).expect("lookup").expect("a key in the column resolves");
828 assert_eq!(found, rid as u64, "key {key} resolved to {found} rather than {rid}");
829 }
830 }
831
832 #[test]
833 fn a_sequence_from_one_is_the_identity_form_and_stores_two_numbers() {
834 // TPC-H's `region`, `nation`, `supplier`, `customer`, `part` and `orders` all land here,
835 // which is the case the whole budget in section 3.7 depends on.
836 let column = keys(&(1..=1000).collect::<Vec<i128>>());
837 let map = KeyMap::build(&column).expect("build");
838 assert_eq!(map.form(), Form::Identity);
839 assert_eq!(map.bytes(), 24, "section 4.2 says identity is twenty four bytes");
840 assert_eq!(map.len(), 1000);
841 resolves(&column, &map);
842 assert_eq!(map.lookup(0).expect("lookup"), None, "below the base");
843 assert_eq!(map.lookup(1001).expect("lookup"), None, "past the end");
844 assert_eq!(map.span(), Some((1, 1000)));
845 }
846
847 #[test]
848 fn a_sequence_from_zero_is_also_the_identity_form() {
849 let column = keys(&(0..64).collect::<Vec<i128>>());
850 let map = KeyMap::build(&column).expect("build");
851 assert_eq!(map.form(), Form::Identity);
852 resolves(&column, &map);
853 }
854
855 #[test]
856 fn a_sequence_with_a_gap_in_it_is_the_dense_form() {
857 // Every other value over a range of two thousand, which is a density of one in two and
858 // comfortably inside the threshold.
859 let column = keys(&(0..1000).map(|value| value * 2).collect::<Vec<i128>>());
860 let map = KeyMap::build(&column).expect("build");
861 assert_eq!(map.form(), Form::Dense);
862 resolves(&column, &map);
863 assert_eq!(
864 map.lookup(1).expect("lookup"),
865 None,
866 "a value in the range and not in the column"
867 );
868 assert_eq!(map.lookup(2001).expect("lookup"), None, "past the range");
869 assert_eq!(map.span(), Some((0, 1999)), "from the smallest key to the largest");
870 }
871
872 #[test]
873 fn a_range_too_sparse_for_a_bitmap_is_the_sorted_form() {
874 // A thousand keys spread over a million, which is a density of one in a thousand: the
875 // bitmap would be 125 KB to hold a thousand values and the sorted form is a few kilobytes.
876 let column = keys(&(0..1000).map(|value| value * 1000).collect::<Vec<i128>>());
877 let map = KeyMap::build(&column).expect("build");
878 assert_eq!(map.form(), Form::Sorted);
879 resolves(&column, &map);
880 assert_eq!(map.lookup(500).expect("lookup"), None);
881 assert_eq!(map.span(), None, "too sparse for a bitmap over the range");
882 }
883
884 #[test]
885 fn the_sorted_form_is_not_bounded_by_a_packed_unit() {
886 // The sorted form packs its keys and its permutation sequentially, and the sequential
887 // packer a column uses is for the remainder past the last transposed unit, so it refuses a
888 // thousand and twenty four values. A parent key column is sixty times that at SF1 and
889 // fifteen thousand times it at SF10, so the form would exist only for toy tables. This is
890 // the smallest column that would have hit it.
891 let column = keys(&(0..5000).map(|value| (value * 7919) % 100_003).collect::<Vec<i128>>());
892 let map = KeyMap::build(&column).expect("build");
893 assert_eq!(map.form(), Form::Sorted);
894 resolves(&column, &map);
895 }
896
897 #[test]
898 fn keys_in_no_order_at_all_resolve_to_the_rows_that_hold_them() {
899 // The case the permutation exists for. The column is not sorted, so the sorted form's
900 // position is not the rid, and a map that confused the two would resolve every key to the
901 // wrong row while looking exactly like a working map.
902 let column = keys(&[500, 3, 9000, 12, 7, 88, 41, 6]);
903 let map = KeyMap::build(&column).expect("build");
904 assert_eq!(map.form(), Form::Sorted);
905 resolves(&column, &map);
906 }
907
908 #[test]
909 fn a_descending_column_dense_enough_for_a_bitmap_still_resolves_correctly() {
910 // The trap in the dense form: a bitmap is in value order, so a rank is a position in value
911 // order, and on a descending column that is not the rid. `dense` detects it and falls back.
912 let column = keys(&(0..500).rev().collect::<Vec<i128>>());
913 let map = KeyMap::build(&column).expect("build");
914 assert_eq!(map.form(), Form::Sorted, "a descending column cannot take the bitmap");
915 resolves(&column, &map);
916 }
917
918 #[test]
919 fn nulls_are_not_keys_and_do_not_shift_the_rows_around_them() {
920 // This column is distinct, ascending, and dense enough for a bitmap on the numbers alone:
921 // three keys over a range of twenty one. It cannot have one, because a rank counts keys
922 // below a value and the nulls in between mean that count is not the row's position. A map
923 // that took the bitmap here would resolve key 20 to row 1 and look entirely healthy doing
924 // it.
925 let column = vec![Some(10), None, Some(20), None, Some(30)];
926 let map = KeyMap::build(&column).expect("build");
927 assert_eq!(
928 map.form(),
929 Form::Sorted,
930 "a null before a key shifts it out of the cheap forms"
931 );
932 resolves(&column, &map);
933 assert_eq!(map.observed().nulls, 2);
934 assert_eq!(map.observed().rows, 3);
935 assert_eq!(
936 map.lookup(20).expect("lookup"),
937 Some(2),
938 "the rid is the position in the column"
939 );
940 }
941
942 #[test]
943 fn a_leading_null_keeps_an_otherwise_perfect_sequence_out_of_the_identity_form() {
944 // The same trap on the form that would otherwise be free. Worth its own test because a
945 // sequence from one is the case every TPC-H table hits, and the version of it with a null
946 // in front is one `INSERT` away.
947 let mut column = vec![None];
948 column.extend((1..=1000).map(Some));
949 let map = KeyMap::build(&column).expect("build");
950 assert_ne!(map.form(), Form::Identity);
951 resolves(&column, &map);
952 assert_eq!(map.lookup(1).expect("lookup"), Some(1), "row zero is the null, not key one");
953 }
954
955 #[test]
956 fn a_null_only_column_builds_and_resolves_nothing() {
957 let column = vec![None, None, None];
958 let map = KeyMap::build(&column).expect("build");
959 assert!(map.is_empty());
960 assert_eq!(map.observed().nulls, 3);
961 assert_eq!(map.lookup(0).expect("lookup"), None);
962 }
963
964 #[test]
965 fn an_empty_column_builds_and_resolves_nothing() {
966 let map = KeyMap::build(&[]).expect("build");
967 assert!(map.is_empty());
968 assert_eq!(map.lookup(0).expect("lookup"), None);
969 assert!(map.observed().usable_as_parent(), "an empty parent is unique, vacuously");
970 }
971
972 #[test]
973 fn a_duplicated_key_is_reported_rather_than_resolved_to_one_of_its_rows() {
974 // Section 2.3's verification. The map still builds, because the caller is the one that
975 // decides what to do about it, and what it decides is to build no link.
976 let column = keys(&[5, 7, 5, 9]);
977 let map = KeyMap::build(&column).expect("build");
978 assert!(!map.observed().distinct);
979 assert!(!map.observed().usable_as_parent(), "a non-unique parent side takes no link");
980 }
981
982 #[test]
983 fn a_column_that_arrives_with_its_repeats_together_is_not_sorted_into_a_map() {
984 // The scan sees the repeat, so nothing is packed. What matters is the bytes: this is
985 // `lineitem(l_orderkey)`, where the form that would have been chosen holds one packed key
986 // and one packed permutation entry per row.
987 let column = keys(&[1, 1, 2, 2, 2, 90_000, 90_000]);
988 let map = KeyMap::build_from(&column[..]).expect("build");
989 assert!(!map.observed().distinct);
990 assert_eq!(map.observed().rows, 7, "the column was still counted");
991 assert_eq!(map.observed().max, Some(90_000));
992 assert_eq!(map.bytes(), KeyMap::build(&keys(&[])).expect("build").bytes());
993 assert_eq!(map.lookup(2).expect("lookup"), None, "and it answers nothing, as it must");
994 }
995
996 #[test]
997 fn a_single_key_column_resolves_it() {
998 // The width of zero case: one key at the base is an offset of zero, and a packed payload of
999 // width zero has no bytes for `tail_at` to read.
1000 let column = keys(&[42]);
1001 let map = KeyMap::build(&column).expect("build");
1002 resolves(&column, &map);
1003 assert_eq!(map.lookup(41).expect("lookup"), None);
1004 assert_eq!(map.lookup(43).expect("lookup"), None);
1005 }
1006
1007 #[test]
1008 fn negative_keys_resolve_because_the_base_is_the_minimum_and_not_zero() {
1009 let column = keys(&[-9000, -3, -1, 0, 7]);
1010 let map = KeyMap::build(&column).expect("build");
1011 resolves(&column, &map);
1012 assert_eq!(map.lookup(-9001).expect("lookup"), None);
1013 }
1014
1015 #[test]
1016 fn a_column_spanning_more_than_a_u64_of_values_is_refused_and_not_panicked_over() {
1017 // `max - min` on a HUGEINT column holding both ends of the type overflows an i128, so this
1018 // is where a build panics if the range arithmetic is done in the column's own type. It is
1019 // refused instead, and the caller records the relationship as not built.
1020 let column = keys(&[i128::MIN, 0, i128::MAX]);
1021 let error = KeyMap::build(&column).expect_err("refused");
1022 assert!(error.to_string().contains("spans more than a u64"), "{error}");
1023 }
1024
1025 #[test]
1026 fn keys_at_the_far_end_of_the_integer_type_resolve_when_their_range_is_narrow() {
1027 // The other half of the same arithmetic: the values are extreme and the range is not, which
1028 // is a column a key map has to handle rather than refuse.
1029 let column = keys(&[i128::MIN, i128::MIN + 5, i128::MIN + 2]);
1030 let map = KeyMap::build(&column).expect("build");
1031 resolves(&column, &map);
1032 assert_eq!(map.lookup(i128::MAX).expect("lookup"), None);
1033 assert_eq!(map.lookup(0).expect("lookup"), None);
1034 }
1035
1036 #[test]
1037 fn the_rank_index_agrees_with_counting_the_bits_by_hand() {
1038 // The rank structure is two levels and an eight word popcount, and an off by one in any of
1039 // the three resolves every key past the fault to the row before or after the right one. So
1040 // it is checked against the naive count over a bitmap wide enough to use every level: 4096
1041 // bits is one superblock exactly, so 20,000 forces five of them and the last one partial.
1042 let column = keys(&(0..10_000).map(|value| value * 2).collect::<Vec<i128>>());
1043 let map = KeyMap::build(&column).expect("build");
1044 assert_eq!(map.form(), Form::Dense);
1045 resolves(&column, &map);
1046 }
1047
1048 #[test]
1049 fn a_string_key_arrives_as_dictionary_codes_and_never_as_text() {
1050 // Section 2.2's composition with the global dictionary. There is nothing string shaped in
1051 // this crate and that is the point: the codes of a file wide stable dictionary carry the
1052 // column's own order, so a sorted key map over a VARCHAR is this and the search is over
1053 // integers.
1054 let codes = keys(&[7, 1, 4, 9, 2]);
1055 let map = KeyMap::build(&codes).expect("build");
1056 resolves(&codes, &map);
1057 }
1058
1059 #[test]
1060 fn the_form_tag_round_trips_and_an_unknown_one_is_refused() {
1061 for form in [Form::Identity, Form::Dense, Form::Sorted] {
1062 assert_eq!(Form::from_tag(form.tag()).expect("a known tag"), form);
1063 }
1064 assert!(Form::from_tag(3).is_err(), "an unfamiliar form is refused rather than guessed");
1065 }
1066
1067 #[test]
1068 fn the_dense_form_costs_a_bitmap_and_about_an_eighth_again() {
1069 // The space claim in section 3.3, checked. A range of 80,000 bits is 10,000 bytes and the
1070 // index is a u16 per 512 bits plus a u32 per 4096, which is about 12.5 percent.
1071 let column = keys(&(0..10_000).map(|value| value * 8).collect::<Vec<i128>>());
1072 let map = KeyMap::build(&column).expect("build");
1073 assert_eq!(map.form(), Form::Dense);
1074 let bitmap = 80_000 / 8;
1075 let bytes = map.bytes();
1076 assert!(bytes > bitmap, "the map took {bytes} bytes and the bitmap alone is {bitmap}");
1077 assert!(
1078 bytes < bitmap * 5 / 4,
1079 "the map took {bytes} bytes, more than a quarter over the bitmap's {bitmap}"
1080 );
1081 }
1082
1083 /// A column that counts how many times it was read, so a test can say what a build cost.
1084 struct Counted {
1085 column: Vec<Option<i128>>,
1086 scans: std::cell::Cell<usize>,
1087 }
1088
1089 impl Keys for Counted {
1090 fn scan(&self, each: &mut dyn FnMut(Option<i128>) -> Result<()>) -> Result<()> {
1091 self.scans.set(self.scans.get() + 1);
1092 self.column.scan(each)
1093 }
1094 }
1095
1096 #[test]
1097 fn a_build_from_a_scan_is_the_same_map_as_a_build_from_a_slice() {
1098 // The two builds have to agree on every column, because the streaming one is not a second
1099 // implementation, it is the same decision carried out against a source that is read twice.
1100 // If these ever disagree, a table's key map depends on which path built it.
1101 let columns: Vec<Vec<Option<i128>>> = vec![
1102 Vec::new(),
1103 keys(&[]),
1104 keys(&(1..=1000).collect::<Vec<i128>>()),
1105 keys(&(0..500).map(|value| value * 4).collect::<Vec<i128>>()),
1106 keys(&[100, 3, 40, 7, 9000]),
1107 keys(&[5, 5, 9]),
1108 vec![Some(10), None, Some(20), None, Some(30)],
1109 vec![None, None],
1110 ];
1111 for column in &columns {
1112 let held = KeyMap::build(column).expect("build from a slice");
1113 let read = KeyMap::build_from(&column[..]).expect("build from a scan");
1114 assert_eq!(read.form(), held.form(), "{column:?}");
1115 assert_eq!(read.observed(), held.observed(), "{column:?}");
1116 assert_eq!(read.len(), held.len(), "{column:?}");
1117 assert_eq!(read.bytes(), held.bytes(), "{column:?}");
1118 // A column with a repeat in it has no one right row for its key, which is exactly why
1119 // section 2.3 refuses to build a link on one. So the round trip is checked where the
1120 // question has an answer.
1121 if read.observed().usable_as_parent() {
1122 resolves(column, &read);
1123 }
1124 }
1125 }
1126
1127 #[test]
1128 fn the_identity_form_is_built_without_reading_the_column_twice() {
1129 // The reason `build_from` exists. Every TPC-H parent key takes the identity form, and the
1130 // identity form is two numbers, so a build of one has no business holding fifteen million
1131 // values or reading them a second time.
1132 let identity =
1133 Counted { column: keys(&(1..=1000).collect::<Vec<i128>>()), scans: 0.into() };
1134 assert_eq!(KeyMap::build_from(&identity).expect("build").form(), Form::Identity);
1135 assert_eq!(
1136 identity.scans.get(),
1137 1,
1138 "the identity form is the observation and nothing more"
1139 );
1140
1141 // The other two forms have something to fill, so they read it again, and once is the number
1142 // that matters: a form that scanned per value would be a build nobody could afford.
1143 let dense = Counted {
1144 column: keys(&(0..500).map(|v| v * 4).collect::<Vec<i128>>()),
1145 scans: 0.into(),
1146 };
1147 assert_eq!(KeyMap::build_from(&dense).expect("build").form(), Form::Dense);
1148 assert_eq!(dense.scans.get(), 2);
1149
1150 let sorted = Counted { column: keys(&[100, 3, 40, 7, 9000]), scans: 0.into() };
1151 assert_eq!(KeyMap::build_from(&sorted).expect("build").form(), Form::Sorted);
1152 assert_eq!(sorted.scans.get(), 2);
1153 }
1154
1155 #[test]
1156 fn a_scan_that_fails_stops_the_build_rather_than_half_finishing_it() {
1157 struct Broken;
1158 impl Keys for Broken {
1159 fn scan(&self, _: &mut dyn FnMut(Option<i128>) -> Result<()>) -> Result<()> {
1160 Err(malformed("the column could not be read"))
1161 }
1162 }
1163 let error = KeyMap::build_from(&Broken).expect_err("a build over an unreadable column");
1164 assert!(error.to_string().contains("could not be read"), "{error}");
1165 }
1166}