Skip to main content

rudb_graph/
wire.rs

1//! The bytes a key map takes in a section payload.
2//!
3//! spec/graph/03-the-file-format.md section 3.3 asks for a fixed header carrying what the build
4//! observed, then the form's own payload. This module is that layout and nothing else: it does not
5//! know what a section is, what an extent is or where in a file the bytes go, because
6//! `rudb-native` is the crate that knows those and it is above this one.
7//!
8//! The header is forty bytes and not the twenty four section 3.3 quotes. Three reasons, and the
9//! difference is worth naming rather than quietly absorbing. `base` has to be an `i128` because a
10//! key can be a `HUGEINT` or a dictionary code and a key map that could not hold one would be a
11//! key map with an exception in it. The null count has to be there because a null is not a key and
12//! the row count alone does not say how many rows the column had. And section 3.3 also requires
13//! the four observed facts in the header, which is where distinctness and sortedness live. Forty
14//! bytes against twenty four, on the six TPC-H tables that take the identity form, is ninety six
15//! bytes in total, so the fidelity that would be lost by padding it back down is worth more than
16//! the bytes.
17//!
18//! What the header does not hold is the maximum key, because every form derives it: identity from
19//! the count, dense from the range, sorted from its last stored key. A number stored twice is a
20//! number that can disagree with itself.
21
22use rudb_common::{Error, Result};
23
24use crate::keymap::{Form, KeyMap, Observed};
25
26/// Bytes of fixed header at the front of a key map payload.
27///
28/// This is what goes in a section entry's `header_bytes`.
29pub const HEADER_BYTES: usize = 40;
30
31/// The payload layout version, in case the forms ever need a second one.
32///
33/// A section's `kind` is `RUDBKM1\0` and the trailing `1` is this number's public face: a second
34/// layout becomes `RUDBKM2\0` and an older reader ignores it by the rule in section 3.2. So this
35/// byte is belt and braces rather than the mechanism, and it exists because a mismatch here is a
36/// clearer error than a misparse further in.
37const LAYOUT: u8 = 1;
38
39/// A key map's bytes, ready to be split into extents and written.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct Payload {
42    /// What goes in the section entry's `flags`, which is the form.
43    ///
44    /// The form being in the entry rather than only in the payload is what lets a reader decide
45    /// whether it wants this key map at all without reading a byte of it.
46    pub flags: u32,
47    /// What goes in the section entry's `header_bytes`.
48    pub header_bytes: u32,
49    /// The whole payload, header first.
50    pub bytes: Vec<u8>,
51}
52
53/// Writes a key map's payload.
54///
55/// `type_tag` is the column's logical type as the format numbers it. It is passed in rather than
56/// derived because the mapping from a logical type to a tag belongs to the format, and this crate
57/// sits below the format on purpose. Section 3.3 wants it in the header so that a section is
58/// self describing: a tool reading a payload out of a file can say what column it was built
59/// against without holding the directory as well.
60///
61/// # Errors
62///
63/// If a length does not fit the width the layout gives it, which means a key map larger than the
64/// format can name.
65pub fn encode(map: &KeyMap, type_tag: u8) -> Result<Payload> {
66    let observed = *map.observed();
67    let mut bytes = Vec::with_capacity(HEADER_BYTES + map.bytes());
68    bytes.extend_from_slice(&map.base().to_le_bytes());
69    bytes.extend_from_slice(&observed.rows.to_le_bytes());
70    bytes.extend_from_slice(&observed.nulls.to_le_bytes());
71    bytes.push(map.form().tag());
72    bytes.push(type_tag);
73    bytes.push(u8::from(observed.distinct));
74    bytes.push(u8::from(observed.sorted));
75    bytes.push(LAYOUT);
76    bytes.extend_from_slice(&[0; 3]);
77    debug_assert_eq!(bytes.len(), HEADER_BYTES, "the key map header is forty bytes");
78    map.write_body(&mut bytes)?;
79    Ok(Payload {
80        flags: u32::from(map.form().tag()),
81        header_bytes: u32::try_from(HEADER_BYTES).map_err(|_| malformed("header overflow"))?,
82        bytes,
83    })
84}
85
86/// Reads a key map's payload, returning it with the type tag it was built against.
87///
88/// # Errors
89///
90/// If the payload is shorter than its header, names a form or a layout this build does not know,
91/// or holds a body that does not match the lengths its header implies. Every one of those is a
92/// section to drop rather than a query to fail: section 3.1 says a table with no sections answers
93/// the same, so a caller's response to an error here is to ignore this key map.
94pub fn decode(bytes: &[u8]) -> Result<(KeyMap, u8)> {
95    if bytes.len() < HEADER_BYTES {
96        return Err(malformed("a key map payload is shorter than its header"));
97    }
98    let base = i128::from_le_bytes(bytes[0..16].try_into().map_err(|_| torn())?);
99    let rows = u64::from_le_bytes(bytes[16..24].try_into().map_err(|_| torn())?);
100    let nulls = u64::from_le_bytes(bytes[24..32].try_into().map_err(|_| torn())?);
101    let form = Form::from_tag(bytes[32])?;
102    let type_tag = bytes[33];
103    let distinct = flag(bytes[34])?;
104    let sorted = flag(bytes[35])?;
105    if bytes[36] != LAYOUT {
106        return Err(malformed(format!("key map layout {} is not one this build knows", bytes[36])));
107    }
108    let observed = Observed {
109        rows,
110        nulls,
111        distinct,
112        sorted,
113        min: (rows > 0).then_some(base),
114        // The maximum is the form's business, because each of the three derives it differently and
115        // none of them stores it. `read_body` fills it in.
116        max: None,
117    };
118    let map = KeyMap::read_body(form, base, observed, &bytes[HEADER_BYTES..])?;
119    Ok((map, type_tag))
120}
121
122/// A boolean on disk is zero or one and nothing else.
123///
124/// Refusing the other two hundred and fifty four is not pedantry: a byte that is neither is a torn
125/// payload, and reading it as true would make a key map claim a distinctness nobody observed,
126/// which is the one thing in this layer that turns into a wrong answer rather than a slow one.
127fn flag(byte: u8) -> Result<bool> {
128    match byte {
129        0 => Ok(false),
130        1 => Ok(true),
131        _ => Err(malformed("a flag byte in a key map header is neither zero nor one")),
132    }
133}
134
135fn torn() -> Error {
136    malformed("a key map header is torn")
137}
138
139fn malformed(message: impl Into<String>) -> Error {
140    Error::invalid_input(format!("invalid rudb key map payload: {}", message.into()))
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    /// The tag the format gives `INTEGER`. Any byte does here; the codec carries it and does not
148    /// interpret it, which is the whole point of it being passed in.
149    const INTEGER: u8 = 4;
150
151    fn keys(values: &[i128]) -> Vec<Option<i128>> {
152        values.iter().copied().map(Some).collect()
153    }
154
155    /// Encodes, decodes, and checks that every key still resolves to the row that held it.
156    ///
157    /// Resolving is the assertion that matters. Comparing two `KeyMap`s field by field would pass
158    /// for a payload that round trips its bytes and its arithmetic separately, and the arithmetic
159    /// is the part a wrong width breaks.
160    fn survives(column: &[Option<i128>]) -> KeyMap {
161        let built = KeyMap::build(column).expect("build");
162        let payload = encode(&built, INTEGER).expect("encode");
163        assert_eq!(payload.header_bytes as usize, HEADER_BYTES);
164        assert_eq!(payload.flags, u32::from(built.form().tag()));
165        let (read, type_tag) = decode(&payload.bytes).expect("decode");
166        assert_eq!(type_tag, INTEGER, "the type tag is carried, not interpreted");
167        assert_eq!(read.form(), built.form(), "the form survives");
168        assert_eq!(read.observed(), built.observed(), "the observed facts survive");
169        for (rid, key) in column.iter().enumerate() {
170            let Some(key) = *key else { continue };
171            assert_eq!(
172                read.lookup(key).expect("lookup"),
173                Some(rid as u64),
174                "key {key} did not survive the round trip"
175            );
176        }
177        read
178    }
179
180    #[test]
181    fn the_identity_form_round_trips_and_is_header_only() {
182        let map = survives(&keys(&(1..=1000).collect::<Vec<i128>>()));
183        assert_eq!(map.form(), Form::Identity);
184        let payload = encode(&map, INTEGER).expect("encode");
185        assert_eq!(payload.bytes.len(), HEADER_BYTES, "section 3.3: no extents beyond the header");
186    }
187
188    #[test]
189    fn the_dense_form_round_trips_with_its_rank_index() {
190        // The index is stored rather than rebuilt at open. Rebuilding is a pass over the bitmap,
191        // and section 3.4's SF100 arithmetic has bitmaps at ninety four megabytes, so a pass is a
192        // thing you notice at open time and twelve percent of the bytes is not.
193        let map = survives(&keys(&(0..20_000).map(|value| value * 2).collect::<Vec<i128>>()));
194        assert_eq!(map.form(), Form::Dense);
195        let payload = encode(&map, INTEGER).expect("encode");
196        let bitmap = 40_000 / 8;
197        let body = payload.bytes.len() - HEADER_BYTES;
198        assert!(body > bitmap, "the body is {body} bytes and the bitmap alone is {bitmap}");
199        assert!(body < bitmap * 5 / 4, "the index costs about an eighth, not {body} over {bitmap}");
200    }
201
202    #[test]
203    fn the_sorted_form_round_trips_with_both_of_its_bit_packed_arrays() {
204        let map = survives(&keys(&[500, 3, 9000, 12, 7, 88, 41, 6]));
205        assert_eq!(map.form(), Form::Sorted);
206    }
207
208    #[test]
209    fn the_permuted_form_round_trips_with_its_bitmap_index_and_rids() {
210        // Keys a third of their range, stored in an order that is not theirs, the shape of
211        // `o_orderkey` on a file clustered by date.
212        let column: Vec<Option<i128>> =
213            (0..5_000_i128).map(|at| Some((at * 7_919 % 5_000) * 3)).collect();
214        let map = survives(&column);
215        assert_eq!(map.form(), Form::Permuted);
216        assert_eq!(map.lookup(1).expect("lookup"), None);
217    }
218
219    #[test]
220    fn a_column_with_nulls_round_trips_and_keeps_its_null_count() {
221        let column = vec![Some(10), None, Some(20), None, Some(30)];
222        let map = survives(&column);
223        assert_eq!(map.observed().nulls, 2);
224        assert_eq!(map.observed().rows, 3);
225    }
226
227    #[test]
228    fn a_column_of_one_key_round_trips() {
229        survives(&keys(&[42]));
230    }
231
232    #[test]
233    fn negative_keys_round_trip_because_the_base_is_an_i128() {
234        // The reason the header is forty bytes rather than twenty four. A base that had to fit an
235        // i64 would refuse this column, and refusing a column is not something a key map gets to
236        // do to a key type the engine supports.
237        survives(&keys(&[i128::MIN + 1, i128::MIN + 9, i128::MIN + 4]));
238    }
239
240    #[test]
241    fn an_empty_key_map_round_trips_and_resolves_nothing() {
242        let built = KeyMap::build(&[]).expect("build");
243        let payload = encode(&built, INTEGER).expect("encode");
244        let (read, _) = decode(&payload.bytes).expect("decode");
245        assert!(read.is_empty());
246        assert_eq!(read.observed().min, None, "an empty map has no minimum, not a minimum of zero");
247        assert_eq!(read.lookup(0).expect("lookup"), None);
248    }
249
250    #[test]
251    fn a_non_distinct_column_carries_that_fact_through_the_round_trip() {
252        // Section 2.3's verification is what decides whether a link gets built at all, so it has to
253        // survive being written down. A payload that lost it would produce a link on a parent side
254        // that is not unique, which is a wrong answer rather than a slow one.
255        let built = KeyMap::build(&keys(&[5, 7, 5, 9])).expect("build");
256        assert!(!built.observed().distinct);
257        let payload = encode(&built, INTEGER).expect("encode");
258        let (read, _) = decode(&payload.bytes).expect("decode");
259        assert!(!read.observed().distinct);
260        assert!(!read.observed().usable_as_parent());
261    }
262
263    #[test]
264    fn a_payload_shorter_than_its_header_is_refused() {
265        let built = KeyMap::build(&keys(&[1, 2, 3])).expect("build");
266        let payload = encode(&built, INTEGER).expect("encode");
267        for cut in [0, 1, HEADER_BYTES - 1] {
268            assert!(decode(&payload.bytes[..cut]).is_err(), "a payload of {cut} bytes is refused");
269        }
270    }
271
272    #[test]
273    fn a_form_this_build_does_not_know_is_refused_rather_than_guessed() {
274        let built = KeyMap::build(&keys(&[1, 2, 3])).expect("build");
275        let mut payload = encode(&built, INTEGER).expect("encode");
276        payload.bytes[32] = 9;
277        let error = decode(&payload.bytes).expect_err("refused");
278        assert!(error.to_string().contains("form 9"), "{error}");
279    }
280
281    #[test]
282    fn a_layout_this_build_does_not_know_is_refused() {
283        let built = KeyMap::build(&keys(&[1, 2, 3])).expect("build");
284        let mut payload = encode(&built, INTEGER).expect("encode");
285        payload.bytes[36] = LAYOUT + 1;
286        let error = decode(&payload.bytes).expect_err("refused");
287        assert!(error.to_string().contains("layout"), "{error}");
288    }
289
290    #[test]
291    fn a_flag_byte_that_is_neither_zero_nor_one_is_refused() {
292        // Reading a torn byte as true would make a key map claim a distinctness nobody observed,
293        // and a link built on that claim resolves to the wrong row.
294        let built = KeyMap::build(&keys(&[1, 2, 3])).expect("build");
295        let mut payload = encode(&built, INTEGER).expect("encode");
296        payload.bytes[34] = 2;
297        assert!(decode(&payload.bytes).is_err(), "a torn distinct flag is refused");
298
299        let mut payload = encode(&built, INTEGER).expect("encode");
300        payload.bytes[35] = 0xff;
301        assert!(decode(&payload.bytes).is_err(), "a torn sorted flag is refused");
302    }
303
304    #[test]
305    fn a_truncated_body_is_refused_rather_than_read_past() {
306        for column in [
307            keys(&(0..2000).map(|value| value * 2).collect::<Vec<i128>>()),
308            keys(&[500, 3, 9000, 12, 7, 88, 41, 6]),
309        ] {
310            let built = KeyMap::build(&column).expect("build");
311            let payload = encode(&built, INTEGER).expect("encode");
312            let short = &payload.bytes[..payload.bytes.len() - 1];
313            assert!(decode(short).is_err(), "a truncated {:?} body is refused", built.form());
314        }
315    }
316
317    #[test]
318    fn a_body_where_the_header_expects_none_is_refused() {
319        // The identity form's payload is its header. Trailing bytes mean the header and the body
320        // disagree about which form this is, and the safe reading of a disagreement is neither.
321        let built = KeyMap::build(&keys(&(1..=10).collect::<Vec<i128>>())).expect("build");
322        let mut payload = encode(&built, INTEGER).expect("encode");
323        assert_eq!(payload.bytes.len(), HEADER_BYTES);
324        payload.bytes.push(0);
325        assert!(decode(&payload.bytes).is_err());
326    }
327}