Skip to main content

cqlite_core/storage/sstable/bti/
node.rs

1//! BTI trie node implementations
2//!
3//! This module defines the trie node structures and operations for the BTI format.
4//! Each node type is optimized for different branching patterns and storage efficiency.
5
6use crate::error::{Error, Result};
7use std::fmt;
8
9/// BTI-specific result type
10pub type BtiResult<T> = Result<T>;
11
12/// BTI-specific error types
13#[derive(Debug, Clone)]
14pub enum BtiError {
15    /// Parse error with details
16    Parse(String),
17    /// Invalid node structure
18    InvalidNodeStructure(String),
19    /// Navigation error during trie traversal
20    NavigationError(String),
21    /// Invalid node type
22    InvalidNodeType(u8),
23    /// Maximum depth exceeded
24    MaxDepthExceeded(usize),
25    /// Invalid byte-comparable key
26    InvalidByteComparableKey(String),
27    /// Corrupted trie structure
28    CorruptedTrie(String),
29    /// Missing component file
30    MissingComponent(String),
31}
32
33impl fmt::Display for BtiError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            BtiError::Parse(msg) => write!(f, "BTI parse error: {}", msg),
37            BtiError::InvalidNodeStructure(msg) => write!(f, "Invalid BTI node structure: {}", msg),
38            BtiError::NavigationError(msg) => write!(f, "BTI navigation error: {}", msg),
39            BtiError::InvalidNodeType(node_type) => {
40                write!(f, "Invalid BTI trie node type: 0x{:02X}", node_type)
41            }
42            BtiError::MaxDepthExceeded(depth) => {
43                write!(f, "BTI trie depth exceeded maximum: {}", depth)
44            }
45            BtiError::InvalidByteComparableKey(key) => {
46                write!(f, "Invalid byte-comparable key: {}", key)
47            }
48            BtiError::CorruptedTrie(msg) => {
49                write!(f, "Corrupted BTI trie structure: {}", msg)
50            }
51            BtiError::MissingComponent(component) => {
52                write!(f, "Missing BTI component: {}", component)
53            }
54        }
55    }
56}
57
58impl std::error::Error for BtiError {}
59
60impl From<BtiError> for Error {
61    fn from(err: BtiError) -> Self {
62        Error::Parse(format!("BTI error: {}", err))
63    }
64}
65
66/// BTI node types corresponding to the four trie node variants
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum BtiNodeType {
69    /// Payload-only node (leaf)
70    PayloadOnly,
71    /// Single child node
72    Single,
73    /// Sparse node with few children
74    Sparse,
75    /// Dense node with many consecutive children
76    Dense,
77}
78
79impl BtiNodeType {
80    /// Get expected children range for this node type
81    pub fn expected_children_range(&self) -> (usize, Option<usize>) {
82        match self {
83            BtiNodeType::PayloadOnly => (0, Some(0)),
84            BtiNodeType::Single => (1, Some(1)),
85            BtiNodeType::Sparse => (2, Some(256)), // Reasonable upper bound
86            BtiNodeType::Dense => (1, Some(256)),  // Full byte range
87        }
88    }
89}
90
91impl fmt::Display for BtiNodeType {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        match self {
94            BtiNodeType::PayloadOnly => write!(f, "PayloadOnly"),
95            BtiNodeType::Single => write!(f, "Single"),
96            BtiNodeType::Sparse => write!(f, "Sparse"),
97            BtiNodeType::Dense => write!(f, "Dense"),
98        }
99    }
100}
101
102/// Sized pointer for encoding distances between parent and child nodes
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct SizedPointer {
105    /// Distance from current position to target
106    pub distance: u64,
107    /// Size of the pointer encoding (1, 2, 4, or 8 bytes)
108    pub size: u8,
109}
110
111impl SizedPointer {
112    /// Create a new sized pointer
113    pub fn new(distance: u64) -> Self {
114        let size = if distance <= 0xFF {
115            1
116        } else if distance <= 0xFFFF {
117            2
118        } else if distance <= 0xFFFF_FFFF {
119            4
120        } else {
121            8
122        };
123
124        Self { distance, size }
125    }
126
127    /// Encode the pointer to bytes
128    pub fn to_bytes(&self) -> Vec<u8> {
129        match self.size {
130            1 => vec![self.distance as u8],
131            2 => (self.distance as u16).to_be_bytes().to_vec(),
132            4 => (self.distance as u32).to_be_bytes().to_vec(),
133            8 => self.distance.to_be_bytes().to_vec(),
134            _ => panic!("Invalid pointer size: {}", self.size),
135        }
136    }
137
138    /// Decode pointer from bytes
139    pub fn from_bytes(data: &[u8], size: u8) -> BtiResult<Self> {
140        let distance = match size {
141            1 if !data.is_empty() => data[0] as u64,
142            2 if data.len() >= 2 => u16::from_be_bytes([data[0], data[1]]) as u64,
143            4 if data.len() >= 4 => u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as u64,
144            8 if data.len() >= 8 => u64::from_be_bytes([
145                data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
146            ]),
147            _ => {
148                return Err(BtiError::Parse(format!(
149                    "Invalid pointer size {} or insufficient data",
150                    size
151                ))
152                .into());
153            }
154        };
155
156        Ok(Self { distance, size })
157    }
158}
159
160/// Trie node transition representing a path to a child node
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub struct Transition {
163    /// The byte value for this transition
164    pub byte: u8,
165    /// Pointer to the child node
166    pub child: SizedPointer,
167}
168
169impl Transition {
170    pub fn new(byte: u8, child: SizedPointer) -> Self {
171        Self { byte, child }
172    }
173}
174
175/// Payload reference for leaf nodes
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct PayloadRef {
178    /// Offset to the payload data
179    pub offset: u64,
180    /// Length of the payload data
181    pub length: u32,
182    /// Optional checksum for validation
183    pub checksum: Option<u32>,
184}
185
186impl PayloadRef {
187    pub fn new(offset: u64, length: u32) -> Self {
188        Self {
189            offset,
190            length,
191            checksum: None,
192        }
193    }
194
195    pub fn with_checksum(mut self, checksum: u32) -> Self {
196        self.checksum = Some(checksum);
197        self
198    }
199}
200
201/// Base trie node structure
202#[derive(Debug, Clone)]
203pub struct BtiNode {
204    /// Type of this node
205    pub node_type: BtiNodeType,
206    /// Level in the trie (0 = leaf level)
207    pub level: u16,
208    /// Key prefix stored at this node (for optimization)
209    pub key_prefix: Vec<u8>,
210    /// Node-specific data
211    pub data: BtiNodeData,
212}
213
214/// Node-specific data based on node type
215#[derive(Debug, Clone)]
216pub enum BtiNodeData {
217    /// Payload-only node (leaf)
218    PayloadOnly { payload: PayloadRef },
219
220    /// Single child node
221    Single { transition: Transition },
222
223    /// Sparse node with few children
224    Sparse { transitions: Vec<Transition> },
225
226    /// Dense node with many consecutive children
227    Dense {
228        /// Starting byte value for the consecutive range
229        start_byte: u8,
230        /// Child pointers for the consecutive range.
231        ///
232        /// Each slot represents the transition `start_byte + index`.  `None`
233        /// means "no transition" (the raw Dense delta was `0`, the sentinel);
234        /// `Some(ptr)` is a real child.  Presence is tracked explicitly because
235        /// a REAL child can legitimately live at absolute trie offset `0` (the
236        /// first-written leaf in BTI's bottom-up layout): an offset of `0` is a
237        /// valid child and must NOT be confused with the absent-transition
238        /// sentinel.
239        children: Vec<Option<SizedPointer>>,
240    },
241}
242
243impl BtiNode {
244    /// Create a payload-only node
245    pub fn payload_only(level: u16, key_prefix: Vec<u8>, payload: PayloadRef) -> Self {
246        Self {
247            node_type: BtiNodeType::PayloadOnly,
248            level,
249            key_prefix,
250            data: BtiNodeData::PayloadOnly { payload },
251        }
252    }
253
254    /// Create a single child node
255    pub fn single(level: u16, key_prefix: Vec<u8>, transition: Transition) -> Self {
256        Self {
257            node_type: BtiNodeType::Single,
258            level,
259            key_prefix,
260            data: BtiNodeData::Single { transition },
261        }
262    }
263
264    /// Create a sparse node
265    pub fn sparse(level: u16, key_prefix: Vec<u8>, mut transitions: Vec<Transition>) -> Self {
266        // Ensure transitions are sorted by byte value for binary search
267        transitions.sort_by_key(|t| t.byte);
268
269        Self {
270            node_type: BtiNodeType::Sparse,
271            level,
272            key_prefix,
273            data: BtiNodeData::Sparse { transitions },
274        }
275    }
276
277    /// Create a dense node
278    ///
279    /// `children[i]` is the transition for byte `start_byte + i`: `None` for a
280    /// missing transition (raw delta `0`), `Some(ptr)` for a real child (which
281    /// may point at absolute offset `0`).
282    pub fn dense(
283        level: u16,
284        key_prefix: Vec<u8>,
285        start_byte: u8,
286        children: Vec<Option<SizedPointer>>,
287    ) -> Self {
288        Self {
289            node_type: BtiNodeType::Dense,
290            level,
291            key_prefix,
292            data: BtiNodeData::Dense {
293                start_byte,
294                children,
295            },
296        }
297    }
298
299    /// Find the child node pointer for a given byte
300    pub fn find_child(&self, byte: u8) -> Option<&SizedPointer> {
301        match &self.data {
302            BtiNodeData::PayloadOnly { .. } => None,
303
304            BtiNodeData::Single { transition } => {
305                if transition.byte == byte {
306                    Some(&transition.child)
307                } else {
308                    None
309                }
310            }
311
312            BtiNodeData::Sparse { transitions } => {
313                // Binary search on sorted transitions
314                transitions
315                    .binary_search_by_key(&byte, |t| t.byte)
316                    .ok()
317                    .map(|idx| &transitions[idx].child)
318            }
319
320            BtiNodeData::Dense {
321                start_byte,
322                children,
323            } => {
324                if byte >= *start_byte && (byte as usize) < (*start_byte as usize + children.len())
325                {
326                    let index = byte as usize - *start_byte as usize;
327                    // `None` means "no transition" for this byte; `Some(ptr)` is
328                    // a real child (possibly at absolute offset 0).
329                    children.get(index).and_then(|slot| slot.as_ref())
330                } else {
331                    None
332                }
333            }
334        }
335    }
336
337    /// Get all child transitions
338    pub fn get_transitions(&self) -> Vec<&Transition> {
339        match &self.data {
340            BtiNodeData::PayloadOnly { .. } => Vec::new(),
341            BtiNodeData::Single { transition } => vec![transition],
342            BtiNodeData::Sparse { transitions } => transitions.iter().collect(),
343            BtiNodeData::Dense {
344                start_byte: _,
345                children: _,
346            } => {
347                // Convert dense representation to transitions
348                // Note: This creates temporary Transition objects
349                // In practice, you'd want to avoid this allocation
350                Vec::new() // Simplified for this example
351            }
352        }
353    }
354
355    /// Get the payload reference if this is a leaf node
356    pub fn get_payload(&self) -> Option<&PayloadRef> {
357        match &self.data {
358            BtiNodeData::PayloadOnly { payload } => Some(payload),
359            _ => None,
360        }
361    }
362
363    /// Check if this node is a leaf (has payload)
364    pub fn is_leaf(&self) -> bool {
365        matches!(self.data, BtiNodeData::PayloadOnly { .. })
366    }
367
368    /// Get the number of children
369    pub fn child_count(&self) -> usize {
370        match &self.data {
371            BtiNodeData::PayloadOnly { .. } => 0,
372            BtiNodeData::Single { .. } => 1,
373            BtiNodeData::Sparse { transitions } => transitions.len(),
374            BtiNodeData::Dense { children, .. } => children.len(),
375        }
376    }
377
378    /// Validate node structure consistency
379    pub fn validate(&self) -> BtiResult<()> {
380        let expected_range = self.node_type.expected_children_range();
381        let child_count = self.child_count();
382
383        // Check child count is within expected range
384        if child_count < expected_range.0 {
385            return Err(BtiError::InvalidNodeStructure(format!(
386                "Node type {} has {} children, expected at least {}",
387                self.node_type, child_count, expected_range.0
388            ))
389            .into());
390        }
391
392        if let Some(max) = expected_range.1 {
393            if child_count > max {
394                return Err(BtiError::InvalidNodeStructure(format!(
395                    "Node type {} has {} children, expected at most {}",
396                    self.node_type, child_count, max
397                ))
398                .into());
399            }
400        }
401
402        // Type-specific validation
403        match &self.data {
404            BtiNodeData::Sparse { transitions } => {
405                // Check that transitions are sorted
406                for window in transitions.windows(2) {
407                    if window[0].byte >= window[1].byte {
408                        return Err(BtiError::InvalidNodeStructure(
409                            "Sparse node transitions not sorted".to_string(),
410                        )
411                        .into());
412                    }
413                }
414            }
415
416            BtiNodeData::Dense {
417                start_byte,
418                children,
419            } => {
420                // Check that we don't overflow byte range
421                let end_byte = *start_byte as usize + children.len();
422                if end_byte > 256 {
423                    return Err(BtiError::InvalidNodeStructure(
424                        "Dense node range overflows byte values".to_string(),
425                    )
426                    .into());
427                }
428            }
429
430            _ => {} // Other types don't need special validation
431        }
432
433        Ok(())
434    }
435}
436
437/// Trie navigation context for tracking path through the trie
438#[derive(Debug, Clone)]
439pub struct TrieNavigator {
440    /// Current position in the file
441    pub current_offset: u64,
442    /// Path taken through the trie (for debugging/backtracking)
443    pub path: Vec<u8>,
444    /// Nodes visited (for cycle detection)
445    pub visited_offsets: std::collections::HashSet<u64>,
446}
447
448impl TrieNavigator {
449    /// Create a new navigator at the root
450    pub fn new(root_offset: u64) -> Self {
451        Self {
452            current_offset: root_offset,
453            path: Vec::new(),
454            visited_offsets: std::collections::HashSet::new(),
455        }
456    }
457
458    /// Navigate to a child node
459    pub fn navigate_to_child(&mut self, byte: u8, child_pointer: &SizedPointer) -> BtiResult<()> {
460        let target_offset = self.current_offset + child_pointer.distance;
461
462        // Check for cycles
463        if self.visited_offsets.contains(&target_offset) {
464            return Err(
465                BtiError::NavigationError("Cycle detected in trie navigation".to_string()).into(),
466            );
467        }
468
469        self.visited_offsets.insert(self.current_offset);
470        self.current_offset = target_offset;
471        self.path.push(byte);
472
473        Ok(())
474    }
475
476    /// Get the current path as a key prefix
477    pub fn current_path(&self) -> &[u8] {
478        &self.path
479    }
480
481    /// Reset to navigate from root again
482    pub fn reset(&mut self, root_offset: u64) {
483        self.current_offset = root_offset;
484        self.path.clear();
485        self.visited_offsets.clear();
486    }
487}
488
489#[cfg(test)]
490mod tests {
491    use super::*;
492
493    #[test]
494    fn test_sized_pointer() {
495        let small = SizedPointer::new(100);
496        assert_eq!(small.size, 1);
497        assert_eq!(small.to_bytes(), vec![100]);
498
499        let large = SizedPointer::new(0x10000);
500        assert_eq!(large.size, 4);
501        assert_eq!(large.to_bytes(), vec![0x00, 0x01, 0x00, 0x00]);
502    }
503
504    #[test]
505    fn test_node_creation() {
506        let payload = PayloadRef::new(1000, 50);
507        let node = BtiNode::payload_only(0, b"test".to_vec(), payload);
508
509        assert_eq!(node.node_type, BtiNodeType::PayloadOnly);
510        assert_eq!(node.level, 0);
511        assert_eq!(node.key_prefix, b"test");
512        assert!(node.is_leaf());
513        assert_eq!(node.child_count(), 0);
514    }
515
516    #[test]
517    fn test_sparse_node_search() {
518        let transitions = vec![
519            Transition::new(b'a', SizedPointer::new(100)),
520            Transition::new(b'm', SizedPointer::new(200)),
521            Transition::new(b'z', SizedPointer::new(300)),
522        ];
523
524        let node = BtiNode::sparse(1, Vec::new(), transitions);
525
526        assert!(node.find_child(b'a').is_some());
527        assert!(node.find_child(b'm').is_some());
528        assert!(node.find_child(b'z').is_some());
529        assert!(node.find_child(b'b').is_none());
530
531        assert_eq!(node.child_count(), 3);
532    }
533
534    #[test]
535    fn test_dense_node_lookup() {
536        let children = vec![
537            Some(SizedPointer::new(100)),
538            Some(SizedPointer::new(200)),
539            Some(SizedPointer::new(300)),
540        ];
541
542        let node = BtiNode::dense(1, Vec::new(), b'a', children);
543
544        assert!(node.find_child(b'a').is_some());
545        assert!(node.find_child(b'b').is_some());
546        assert!(node.find_child(b'c').is_some());
547        assert!(node.find_child(b'd').is_none());
548        assert!(node.find_child(b'@').is_none()); // Before range
549    }
550
551    /// Finding 1 (issue #832): a Dense node where the FIRST real child points at
552    /// absolute trie offset 0 (a legitimate position — the first-written leaf in
553    /// BTI's bottom-up layout) and a later slot is the "no transition" sentinel
554    /// (`None`).  `find_child` must return the offset-0 child for the real byte
555    /// and `None` for the gap byte — the offset-0 pointer must NOT be treated as
556    /// "no transition".
557    #[test]
558    fn test_dense_node_offset_zero_child_distinct_from_no_transition() {
559        // start_byte = b'a':
560        //   b'a' → real child at offset 0 (SizedPointer distance 0)
561        //   b'b' → no transition (None)
562        //   b'c' → real child at offset 300
563        let children = vec![
564            Some(SizedPointer::new(0)), // real child at absolute offset 0
565            None,                       // no transition
566            Some(SizedPointer::new(300)),
567        ];
568        let node = BtiNode::dense(1, Vec::new(), b'a', children);
569
570        let a = node.find_child(b'a');
571        assert!(a.is_some(), "offset-0 child must be found, not dropped");
572        assert_eq!(
573            a.unwrap().distance,
574            0,
575            "the real child at absolute offset 0 must be returned"
576        );
577        assert!(
578            node.find_child(b'b').is_none(),
579            "no-transition slot must return None"
580        );
581        assert!(node.find_child(b'c').is_some());
582        // child_count is the dense RANGE length (slots), independent of gaps.
583        assert_eq!(node.child_count(), 3);
584    }
585
586    #[test]
587    fn test_node_validation() {
588        // Valid payload-only node
589        let payload_node = BtiNode::payload_only(0, Vec::new(), PayloadRef::new(0, 10));
590        assert!(payload_node.validate().is_ok());
591
592        // Invalid sparse node (not enough children)
593        let _invalid_sparse = BtiNode::sparse(
594            1,
595            Vec::new(),
596            vec![Transition::new(b'a', SizedPointer::new(100))],
597        );
598        // Note: This would be invalid in practice but our implementation
599        // doesn't enforce minimum children for sparse nodes in this test
600    }
601
602    #[test]
603    fn test_trie_navigator() {
604        let mut nav = TrieNavigator::new(1000);
605        assert_eq!(nav.current_offset, 1000);
606        assert_eq!(nav.current_path(), &[] as &[u8]);
607
608        let pointer = SizedPointer::new(100);
609        nav.navigate_to_child(b'a', &pointer).unwrap();
610
611        assert_eq!(nav.current_offset, 1100);
612        assert_eq!(nav.current_path(), b"a");
613    }
614}