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 a_column_with_nulls_round_trips_and_keeps_its_null_count() {
210        let column = vec![Some(10), None, Some(20), None, Some(30)];
211        let map = survives(&column);
212        assert_eq!(map.observed().nulls, 2);
213        assert_eq!(map.observed().rows, 3);
214    }
215
216    #[test]
217    fn a_column_of_one_key_round_trips() {
218        survives(&keys(&[42]));
219    }
220
221    #[test]
222    fn negative_keys_round_trip_because_the_base_is_an_i128() {
223        // The reason the header is forty bytes rather than twenty four. A base that had to fit an
224        // i64 would refuse this column, and refusing a column is not something a key map gets to
225        // do to a key type the engine supports.
226        survives(&keys(&[i128::MIN + 1, i128::MIN + 9, i128::MIN + 4]));
227    }
228
229    #[test]
230    fn an_empty_key_map_round_trips_and_resolves_nothing() {
231        let built = KeyMap::build(&[]).expect("build");
232        let payload = encode(&built, INTEGER).expect("encode");
233        let (read, _) = decode(&payload.bytes).expect("decode");
234        assert!(read.is_empty());
235        assert_eq!(read.observed().min, None, "an empty map has no minimum, not a minimum of zero");
236        assert_eq!(read.lookup(0).expect("lookup"), None);
237    }
238
239    #[test]
240    fn a_non_distinct_column_carries_that_fact_through_the_round_trip() {
241        // Section 2.3's verification is what decides whether a link gets built at all, so it has to
242        // survive being written down. A payload that lost it would produce a link on a parent side
243        // that is not unique, which is a wrong answer rather than a slow one.
244        let built = KeyMap::build(&keys(&[5, 7, 5, 9])).expect("build");
245        assert!(!built.observed().distinct);
246        let payload = encode(&built, INTEGER).expect("encode");
247        let (read, _) = decode(&payload.bytes).expect("decode");
248        assert!(!read.observed().distinct);
249        assert!(!read.observed().usable_as_parent());
250    }
251
252    #[test]
253    fn a_payload_shorter_than_its_header_is_refused() {
254        let built = KeyMap::build(&keys(&[1, 2, 3])).expect("build");
255        let payload = encode(&built, INTEGER).expect("encode");
256        for cut in [0, 1, HEADER_BYTES - 1] {
257            assert!(decode(&payload.bytes[..cut]).is_err(), "a payload of {cut} bytes is refused");
258        }
259    }
260
261    #[test]
262    fn a_form_this_build_does_not_know_is_refused_rather_than_guessed() {
263        let built = KeyMap::build(&keys(&[1, 2, 3])).expect("build");
264        let mut payload = encode(&built, INTEGER).expect("encode");
265        payload.bytes[32] = 9;
266        let error = decode(&payload.bytes).expect_err("refused");
267        assert!(error.to_string().contains("form 9"), "{error}");
268    }
269
270    #[test]
271    fn a_layout_this_build_does_not_know_is_refused() {
272        let built = KeyMap::build(&keys(&[1, 2, 3])).expect("build");
273        let mut payload = encode(&built, INTEGER).expect("encode");
274        payload.bytes[36] = LAYOUT + 1;
275        let error = decode(&payload.bytes).expect_err("refused");
276        assert!(error.to_string().contains("layout"), "{error}");
277    }
278
279    #[test]
280    fn a_flag_byte_that_is_neither_zero_nor_one_is_refused() {
281        // Reading a torn byte as true would make a key map claim a distinctness nobody observed,
282        // and a link built on that claim resolves to the wrong row.
283        let built = KeyMap::build(&keys(&[1, 2, 3])).expect("build");
284        let mut payload = encode(&built, INTEGER).expect("encode");
285        payload.bytes[34] = 2;
286        assert!(decode(&payload.bytes).is_err(), "a torn distinct flag is refused");
287
288        let mut payload = encode(&built, INTEGER).expect("encode");
289        payload.bytes[35] = 0xff;
290        assert!(decode(&payload.bytes).is_err(), "a torn sorted flag is refused");
291    }
292
293    #[test]
294    fn a_truncated_body_is_refused_rather_than_read_past() {
295        for column in [
296            keys(&(0..2000).map(|value| value * 2).collect::<Vec<i128>>()),
297            keys(&[500, 3, 9000, 12, 7, 88, 41, 6]),
298        ] {
299            let built = KeyMap::build(&column).expect("build");
300            let payload = encode(&built, INTEGER).expect("encode");
301            let short = &payload.bytes[..payload.bytes.len() - 1];
302            assert!(decode(short).is_err(), "a truncated {:?} body is refused", built.form());
303        }
304    }
305
306    #[test]
307    fn a_body_where_the_header_expects_none_is_refused() {
308        // The identity form's payload is its header. Trailing bytes mean the header and the body
309        // disagree about which form this is, and the safe reading of a disagreement is neither.
310        let built = KeyMap::build(&keys(&(1..=10).collect::<Vec<i128>>())).expect("build");
311        let mut payload = encode(&built, INTEGER).expect("encode");
312        assert_eq!(payload.bytes.len(), HEADER_BYTES);
313        payload.bytes.push(0);
314        assert!(decode(&payload.bytes).is_err());
315    }
316}