Skip to main content

hermes_core/structures/
sstable_index.rs

1//! Memory-efficient SSTable index structures
2//!
3//! This module provides two approaches for memory-efficient block indexing:
4//!
5//! ## Option 1: FST-based Index (native feature)
6//! Uses a Finite State Transducer to map keys to block ordinals. The FST can be
7//! mmap'd directly without parsing into heap-allocated structures.
8//!
9//! ## Option 2: Mmap'd Raw Index
10//! Keeps the prefix-compressed block index as raw bytes and decodes entries
11//! on-demand during binary search. No heap allocation for the index.
12//!
13//! Both approaches use a compact BlockAddrStore with bitpacked offsets/lengths.
14
15use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
16use std::io::{self, Write};
17use std::ops::Range;
18
19use crate::directories::OwnedBytes;
20
21/// Block address - offset and length in the data section
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct BlockAddr {
24    pub offset: u64,
25    pub length: u32,
26}
27
28impl BlockAddr {
29    pub fn byte_range(&self) -> Range<u64> {
30        self.offset..self.offset + self.length as u64
31    }
32}
33
34/// Compact storage for block addresses using delta + bitpacking
35///
36/// Memory layout:
37/// - Header: num_blocks (u32) + offset_bits (u8) + length_bits (u8)
38/// - Bitpacked data: offsets and lengths interleaved
39///
40/// Uses delta encoding for offsets (blocks are sequential) and
41/// stores lengths directly (typically similar sizes).
42#[derive(Debug)]
43pub struct BlockAddrStore {
44    num_blocks: u32,
45    offset_bits: u8,
46    length_bits: u8,
47    /// Eagerly decoded addresses for O(1) random access
48    addrs: Vec<BlockAddr>,
49}
50
51impl BlockAddrStore {
52    /// Build from a list of block addresses
53    pub fn build(addrs: &[BlockAddr]) -> io::Result<Vec<u8>> {
54        if addrs.is_empty() {
55            let mut buf = Vec::with_capacity(6);
56            buf.write_u32::<LittleEndian>(0)?;
57            buf.write_u8(0)?;
58            buf.write_u8(0)?;
59            return Ok(buf);
60        }
61
62        // Compute delta offsets and find max values for bit width
63        let mut deltas = Vec::with_capacity(addrs.len());
64        let mut prev_end: u64 = 0;
65        let mut max_delta: u64 = 0;
66        let mut max_length: u32 = 0;
67
68        for addr in addrs {
69            // Delta from end of previous block (handles gaps)
70            let delta = addr.offset.saturating_sub(prev_end);
71            deltas.push(delta);
72            max_delta = max_delta.max(delta);
73            max_length = max_length.max(addr.length);
74            prev_end = addr.offset.checked_add(addr.length as u64).ok_or_else(|| {
75                io::Error::new(io::ErrorKind::InvalidInput, "block address overflow")
76            })?;
77        }
78
79        // Compute bit widths
80        let offset_bits = if max_delta == 0 {
81            1
82        } else {
83            (64 - max_delta.leading_zeros()) as u8
84        };
85        let length_bits = if max_length == 0 {
86            1
87        } else {
88            (32 - max_length.leading_zeros()) as u8
89        };
90
91        // Calculate packed size
92        let bits_per_entry = offset_bits as usize + length_bits as usize;
93        let total_bits = bits_per_entry.checked_mul(addrs.len()).ok_or_else(|| {
94            io::Error::new(io::ErrorKind::InvalidInput, "block address table too large")
95        })?;
96        let packed_bytes = total_bits.div_ceil(8);
97
98        let mut buf = Vec::with_capacity(6 + packed_bytes);
99        buf.write_u32::<LittleEndian>(addrs.len() as u32)?;
100        buf.write_u8(offset_bits)?;
101        buf.write_u8(length_bits)?;
102
103        // Bitpack the data
104        let mut bit_writer = BitWriter::new(&mut buf);
105        for (i, addr) in addrs.iter().enumerate() {
106            bit_writer.write(deltas[i], offset_bits)?;
107            bit_writer.write(addr.length as u64, length_bits)?;
108        }
109        bit_writer.flush()?;
110
111        Ok(buf)
112    }
113
114    /// Load from raw bytes — eagerly decodes all addresses for O(1) access
115    pub fn load(data: OwnedBytes) -> io::Result<Self> {
116        if data.len() < 6 {
117            return Err(io::Error::new(
118                io::ErrorKind::InvalidData,
119                "BlockAddrStore data too short",
120            ));
121        }
122
123        let mut reader = data.as_slice();
124        let num_blocks = reader.read_u32::<LittleEndian>()?;
125        let offset_bits = reader.read_u8()?;
126        let length_bits = reader.read_u8()?;
127
128        if offset_bits > 64 || length_bits > 32 {
129            return Err(io::Error::new(
130                io::ErrorKind::InvalidData,
131                "invalid block address bit width",
132            ));
133        }
134        if num_blocks > 0 && (offset_bits == 0 || length_bits == 0) {
135            return Err(io::Error::new(
136                io::ErrorKind::InvalidData,
137                "non-empty block address table has a zero bit width",
138            ));
139        }
140
141        let bits_per_entry = offset_bits as usize + length_bits as usize;
142        let total_bits = bits_per_entry
143            .checked_mul(num_blocks as usize)
144            .ok_or_else(|| {
145                io::Error::new(io::ErrorKind::InvalidData, "block address table overflow")
146            })?;
147        let packed_len = total_bits.checked_add(7).ok_or_else(|| {
148            io::Error::new(io::ErrorKind::InvalidData, "block address table overflow")
149        })? / 8;
150        if packed_len > data.len() - 6 {
151            return Err(io::Error::new(
152                io::ErrorKind::UnexpectedEof,
153                "block address table truncated",
154            ));
155        }
156
157        // Eagerly decode all block addresses once at load time
158        let packed_data = &data.as_slice()[6..6 + packed_len];
159        let mut bit_reader = BitReader::new(packed_data);
160        let mut addrs = Vec::new();
161        addrs.try_reserve_exact(num_blocks as usize).map_err(|_| {
162            io::Error::new(io::ErrorKind::InvalidData, "block address table too large")
163        })?;
164        let mut current_offset: u64 = 0;
165
166        for _ in 0..num_blocks {
167            let delta = bit_reader.read(offset_bits)?;
168            let length = bit_reader.read(length_bits)?;
169            let offset = current_offset.checked_add(delta).ok_or_else(|| {
170                io::Error::new(io::ErrorKind::InvalidData, "block address offset overflow")
171            })?;
172            let length = u32::try_from(length).map_err(|_| {
173                io::Error::new(io::ErrorKind::InvalidData, "block length exceeds u32")
174            })?;
175            current_offset = offset.checked_add(length as u64).ok_or_else(|| {
176                io::Error::new(io::ErrorKind::InvalidData, "block address end overflow")
177            })?;
178            addrs.push(BlockAddr { offset, length });
179        }
180
181        Ok(Self {
182            num_blocks,
183            offset_bits,
184            length_bits,
185            addrs,
186        })
187    }
188
189    /// Number of blocks
190    pub fn len(&self) -> usize {
191        self.num_blocks as usize
192    }
193
194    /// Check if empty
195    pub fn is_empty(&self) -> bool {
196        self.num_blocks == 0
197    }
198
199    /// Get block address by index — O(1) from eagerly decoded array
200    #[inline]
201    pub fn get(&self, idx: usize) -> Option<BlockAddr> {
202        self.addrs.get(idx).copied()
203    }
204
205    /// Get all block addresses
206    pub fn all(&self) -> Vec<BlockAddr> {
207        self.addrs.clone()
208    }
209}
210
211/// FST-based block index (Option 1)
212///
213/// Maps keys to block ordinals using an FST. The FST bytes can be mmap'd
214/// directly without any parsing or heap allocation.
215#[cfg(feature = "fst-index")]
216pub struct FstBlockIndex {
217    fst: fst::Map<OwnedBytes>,
218    block_addrs: BlockAddrStore,
219}
220
221#[cfg(feature = "fst-index")]
222impl FstBlockIndex {
223    /// Build FST index from keys and block addresses
224    pub fn build(entries: &[(Vec<u8>, BlockAddr)]) -> io::Result<Vec<u8>> {
225        use fst::MapBuilder;
226
227        // Build FST mapping keys to block ordinals
228        let mut fst_builder = MapBuilder::memory();
229        for (i, (key, _)) in entries.iter().enumerate() {
230            fst_builder
231                .insert(key, i as u64)
232                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
233        }
234        let fst_bytes = fst_builder
235            .into_inner()
236            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
237
238        // Build block address store
239        let addrs: Vec<BlockAddr> = entries.iter().map(|(_, addr)| *addr).collect();
240        let addr_bytes = BlockAddrStore::build(&addrs)?;
241
242        // Combine: fst_len (u32) + fst_bytes + addr_bytes
243        let mut result = Vec::with_capacity(4 + fst_bytes.len() + addr_bytes.len());
244        result.write_u32::<LittleEndian>(fst_bytes.len() as u32)?;
245        result.extend_from_slice(&fst_bytes);
246        result.extend_from_slice(&addr_bytes);
247
248        Ok(result)
249    }
250
251    /// Load from raw bytes
252    pub fn load(data: OwnedBytes) -> io::Result<Self> {
253        if data.len() < 4 {
254            return Err(io::Error::new(
255                io::ErrorKind::InvalidData,
256                "FstBlockIndex data too short",
257            ));
258        }
259
260        let fst_len = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
261
262        let fst_end = 4usize.checked_add(fst_len).ok_or_else(|| {
263            io::Error::new(io::ErrorKind::InvalidData, "FstBlockIndex length overflow")
264        })?;
265        if data.len() < fst_end {
266            return Err(io::Error::new(
267                io::ErrorKind::InvalidData,
268                "FstBlockIndex FST data truncated",
269            ));
270        }
271
272        let fst_data = data.slice(4..fst_end);
273        let addr_data = data.slice(fst_end..data.len());
274
275        let fst =
276            fst::Map::new(fst_data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
277        let block_addrs = BlockAddrStore::load(addr_data)?;
278
279        if fst.len() != block_addrs.len() {
280            return Err(io::Error::new(
281                io::ErrorKind::InvalidData,
282                "FST key count does not match block address count",
283            ));
284        }
285        use fst::Streamer;
286        let mut entries = fst.stream();
287        let mut expected_ordinal = 0u64;
288        while let Some((_key, ordinal)) = entries.next() {
289            if ordinal != expected_ordinal {
290                return Err(io::Error::new(
291                    io::ErrorKind::InvalidData,
292                    "FST block ordinals are not contiguous",
293                ));
294            }
295            expected_ordinal += 1;
296        }
297
298        Ok(Self { fst, block_addrs })
299    }
300
301    /// Look up the block index for a key
302    /// Returns the block ordinal that could contain this key.
303    /// O(key_len) via FST exact lookup + single stream step.
304    pub fn locate(&self, key: &[u8]) -> Option<usize> {
305        // Fast exact match — O(key_len), no stream allocation
306        if let Some(ordinal) = self.fst.get(key) {
307            return Some(ordinal as usize);
308        }
309
310        // Find the first block whose first_key > target (single stream step)
311        use fst::{IntoStreamer, Streamer};
312        let mut stream = self.fst.range().gt(key).into_stream();
313        match stream.next() {
314            Some((_, ordinal)) if ordinal > 0 => Some(ordinal as usize - 1),
315            Some(_) => None, // key < first block's first key
316            None => {
317                // No key > target → target is after all keys; use last block
318                let len = self.fst.len();
319                if len > 0 { Some(len - 1) } else { None }
320            }
321        }
322    }
323
324    /// Get block address by ordinal
325    pub fn get_addr(&self, ordinal: usize) -> Option<BlockAddr> {
326        self.block_addrs.get(ordinal)
327    }
328
329    /// Number of blocks
330    pub fn len(&self) -> usize {
331        self.block_addrs.len()
332    }
333
334    /// Check if empty
335    pub fn is_empty(&self) -> bool {
336        self.block_addrs.is_empty()
337    }
338
339    /// Get all block addresses
340    pub fn all_addrs(&self) -> Vec<BlockAddr> {
341        self.block_addrs.all()
342    }
343}
344
345/// Mmap'd raw block index (Option 2)
346///
347/// Keeps the prefix-compressed block index as raw bytes and decodes
348/// entries on-demand. Uses restart points every R entries for O(log N)
349/// lookup via binary search instead of O(N) linear scan.
350pub struct MmapBlockIndex {
351    data: OwnedBytes,
352    num_blocks: u32,
353    block_addrs: BlockAddrStore,
354    /// Offset where the prefix-compressed keys start
355    keys_offset: usize,
356    /// Offset where the keys section ends (restart array begins)
357    keys_end: usize,
358    /// Byte offset in data where the restart offsets array starts
359    restart_array_offset: usize,
360    /// Number of restart points
361    restart_count: usize,
362    /// Restart interval (R) — a restart point every R entries
363    restart_interval: usize,
364}
365
366/// Restart interval: store full (uncompressed) key every R entries
367const RESTART_INTERVAL: usize = 16;
368
369impl MmapBlockIndex {
370    /// Build mmap-friendly index from entries.
371    ///
372    /// Format: `num_blocks (u32) | BlockAddrStore | prefix-compressed keys
373    /// (with restart points) | restart_offsets[..] | restart_count (u32) | restart_interval (u16)`
374    pub fn build(entries: &[(Vec<u8>, BlockAddr)]) -> io::Result<Vec<u8>> {
375        if entries.is_empty() {
376            let mut buf = Vec::with_capacity(16);
377            buf.write_u32::<LittleEndian>(0)?; // num_blocks
378            buf.extend_from_slice(&BlockAddrStore::build(&[])?);
379            // Empty restart array + footer
380            buf.write_u32::<LittleEndian>(0)?; // restart_count
381            buf.write_u16::<LittleEndian>(RESTART_INTERVAL as u16)?;
382            return Ok(buf);
383        }
384
385        // Build block address store
386        let addrs: Vec<BlockAddr> = entries.iter().map(|(_, addr)| *addr).collect();
387        let addr_bytes = BlockAddrStore::build(&addrs)?;
388
389        // Build prefix-compressed keys with restart points
390        let mut keys_buf = Vec::new();
391        let mut prev_key: Vec<u8> = Vec::new();
392        let mut restart_offsets: Vec<u32> = Vec::new();
393
394        for (i, (key, _)) in entries.iter().enumerate() {
395            let is_restart = i % RESTART_INTERVAL == 0;
396
397            if is_restart {
398                restart_offsets.push(keys_buf.len() as u32);
399                // Store full key (no prefix compression)
400                write_vint(&mut keys_buf, 0)?;
401                write_vint(&mut keys_buf, key.len() as u64)?;
402                keys_buf.extend_from_slice(key);
403            } else {
404                let prefix_len = common_prefix_len(&prev_key, key);
405                let suffix = &key[prefix_len..];
406                write_vint(&mut keys_buf, prefix_len as u64)?;
407                write_vint(&mut keys_buf, suffix.len() as u64)?;
408                keys_buf.extend_from_slice(suffix);
409            }
410
411            prev_key.clear();
412            prev_key.extend_from_slice(key);
413        }
414
415        // Combine: num_blocks + addr_bytes + keys + restart_offsets + footer
416        let restart_count = restart_offsets.len();
417        let mut result =
418            Vec::with_capacity(4 + addr_bytes.len() + keys_buf.len() + restart_count * 4 + 6);
419        result.write_u32::<LittleEndian>(entries.len() as u32)?;
420        result.extend_from_slice(&addr_bytes);
421        result.extend_from_slice(&keys_buf);
422
423        // Write restart offsets array
424        for &off in &restart_offsets {
425            result.write_u32::<LittleEndian>(off)?;
426        }
427
428        // Write footer
429        result.write_u32::<LittleEndian>(restart_count as u32)?;
430        result.write_u16::<LittleEndian>(RESTART_INTERVAL as u16)?;
431
432        Ok(result)
433    }
434
435    /// Load from raw bytes
436    pub fn load(data: OwnedBytes) -> io::Result<Self> {
437        if data.len() < 16 {
438            return Err(io::Error::new(
439                io::ErrorKind::InvalidData,
440                "MmapBlockIndex data too short",
441            ));
442        }
443
444        let num_blocks = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
445
446        // Load block addresses
447        let addr_data_start = 4;
448        let remaining = data.slice(addr_data_start..data.len());
449        let block_addrs = BlockAddrStore::load(remaining.clone())?;
450
451        if block_addrs.len() != num_blocks as usize {
452            return Err(io::Error::new(
453                io::ErrorKind::InvalidData,
454                "block address count does not match key count",
455            ));
456        }
457
458        // Calculate where keys start
459        let bits_per_entry = block_addrs.offset_bits as usize + block_addrs.length_bits as usize;
460        let total_bits = bits_per_entry
461            .checked_mul(num_blocks as usize)
462            .ok_or_else(|| {
463                io::Error::new(io::ErrorKind::InvalidData, "block index size overflow")
464            })?;
465        let addr_packed_size = total_bits.checked_add(7).ok_or_else(|| {
466            io::Error::new(io::ErrorKind::InvalidData, "block index size overflow")
467        })? / 8;
468        let keys_offset = addr_data_start
469            .checked_add(6)
470            .and_then(|v| v.checked_add(addr_packed_size))
471            .ok_or_else(|| {
472                io::Error::new(io::ErrorKind::InvalidData, "block index offset overflow")
473            })?; // 6 = header of BlockAddrStore
474
475        // Read footer (last 6 bytes: restart_count u32 + restart_interval u16)
476        if data.len() < keys_offset + 6 {
477            return Err(io::Error::new(
478                io::ErrorKind::InvalidData,
479                "MmapBlockIndex missing restart footer",
480            ));
481        }
482        let footer_start = data.len() - 6;
483        let restart_count = u32::from_le_bytes([
484            data[footer_start],
485            data[footer_start + 1],
486            data[footer_start + 2],
487            data[footer_start + 3],
488        ]) as usize;
489        let restart_interval =
490            u16::from_le_bytes([data[footer_start + 4], data[footer_start + 5]]) as usize;
491
492        if restart_interval == 0 {
493            return Err(io::Error::new(
494                io::ErrorKind::InvalidData,
495                "block index restart interval is zero",
496            ));
497        }
498
499        let expected_restart_count = (num_blocks as usize).div_ceil(restart_interval);
500        if restart_count != expected_restart_count {
501            return Err(io::Error::new(
502                io::ErrorKind::InvalidData,
503                "block index restart count is inconsistent",
504            ));
505        }
506
507        // Restart offsets array: restart_count × 4 bytes, just before footer
508        let restart_bytes = restart_count.checked_mul(4).ok_or_else(|| {
509            io::Error::new(io::ErrorKind::InvalidData, "restart table size overflow")
510        })?;
511        let restart_array_offset = footer_start.checked_sub(restart_bytes).ok_or_else(|| {
512            io::Error::new(io::ErrorKind::InvalidData, "restart table out of bounds")
513        })?;
514        if restart_array_offset < keys_offset {
515            return Err(io::Error::new(
516                io::ErrorKind::InvalidData,
517                "restart table overlaps block keys",
518            ));
519        }
520
521        // Keys section spans from keys_offset to restart_array_offset
522        let keys_end = restart_array_offset;
523
524        // Validate the complete prefix-compressed key stream and all restart
525        // offsets once so the hot lookup path can remain allocation-light and
526        // infallible without trusting corrupt on-disk lengths.
527        let keys_data = &data.as_slice()[keys_offset..keys_end];
528        let mut reader = keys_data;
529        let mut current_key = Vec::new();
530        let mut previous_key: Option<Vec<u8>> = None;
531        for ordinal in 0..num_blocks as usize {
532            let entry_offset = keys_data.len() - reader.len();
533            if ordinal % restart_interval == 0 {
534                let restart_idx = ordinal / restart_interval;
535                let pos = restart_array_offset + restart_idx * 4;
536                let recorded =
537                    u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
538                        as usize;
539                if recorded != entry_offset {
540                    return Err(io::Error::new(
541                        io::ErrorKind::InvalidData,
542                        "block index restart offset is inconsistent",
543                    ));
544                }
545            }
546
547            let prefix_len = usize::try_from(read_vint(&mut reader)?).map_err(|_| {
548                io::Error::new(io::ErrorKind::InvalidData, "block key prefix is too large")
549            })?;
550            let suffix_len = usize::try_from(read_vint(&mut reader)?).map_err(|_| {
551                io::Error::new(io::ErrorKind::InvalidData, "block key suffix is too large")
552            })?;
553            if ordinal % restart_interval == 0 && prefix_len != 0 {
554                return Err(io::Error::new(
555                    io::ErrorKind::InvalidData,
556                    "block index restart key uses a prefix",
557                ));
558            }
559            if prefix_len > current_key.len() || suffix_len > reader.len() {
560                return Err(io::Error::new(
561                    io::ErrorKind::UnexpectedEof,
562                    "block index key is truncated",
563                ));
564            }
565            current_key.truncate(prefix_len);
566            current_key.extend_from_slice(&reader[..suffix_len]);
567            reader = &reader[suffix_len..];
568
569            if previous_key
570                .as_ref()
571                .is_some_and(|previous| previous.as_slice() >= current_key.as_slice())
572            {
573                return Err(io::Error::new(
574                    io::ErrorKind::InvalidData,
575                    "block index keys are not strictly increasing",
576                ));
577            }
578            previous_key = Some(current_key.clone());
579        }
580        if !reader.is_empty() {
581            return Err(io::Error::new(
582                io::ErrorKind::InvalidData,
583                "block index contains trailing key data",
584            ));
585        }
586
587        Ok(Self {
588            data,
589            num_blocks,
590            block_addrs,
591            keys_offset,
592            keys_end,
593            restart_array_offset,
594            restart_count,
595            restart_interval,
596        })
597    }
598
599    /// Read restart offset at given index directly from mmap'd data
600    #[inline]
601    fn restart_offset(&self, idx: usize) -> u32 {
602        let pos = self.restart_array_offset + idx * 4;
603        u32::from_le_bytes([
604            self.data[pos],
605            self.data[pos + 1],
606            self.data[pos + 2],
607            self.data[pos + 3],
608        ])
609    }
610
611    /// Decode the full key at a restart point (prefix_len is always 0)
612    fn decode_restart_key<'a>(&self, keys_data: &'a [u8], restart_idx: usize) -> &'a [u8] {
613        let offset = self.restart_offset(restart_idx) as usize;
614        let mut reader = &keys_data[offset..];
615
616        let prefix_len = read_vint(&mut reader).unwrap_or(0) as usize;
617        debug_assert_eq!(prefix_len, 0, "restart point should have prefix_len=0");
618        let suffix_len = read_vint(&mut reader).unwrap_or(0) as usize;
619
620        // reader now points to the suffix bytes
621        &reader[..suffix_len]
622    }
623
624    /// O(log(N/R) + R) lookup using binary search on restart points, then
625    /// linear scan with prefix decompression within the interval.
626    pub fn locate(&self, target: &[u8]) -> Option<usize> {
627        if self.num_blocks == 0 {
628            return None;
629        }
630
631        let keys_data = &self.data.as_slice()[self.keys_offset..self.keys_end];
632
633        // Binary search on restart points to find the interval
634        let mut lo = 0usize;
635        let mut hi = self.restart_count;
636
637        while lo < hi {
638            let mid = lo + (hi - lo) / 2;
639            let key = self.decode_restart_key(keys_data, mid);
640            match key.cmp(target) {
641                std::cmp::Ordering::Equal => {
642                    return Some(mid * self.restart_interval);
643                }
644                std::cmp::Ordering::Less => lo = mid + 1,
645                std::cmp::Ordering::Greater => hi = mid,
646            }
647        }
648
649        // lo is the first restart point whose key > target (or restart_count)
650        // Search in the interval starting at restart (lo - 1), or 0 if lo == 0
651        if lo == 0 {
652            // target < first restart key — might be before all keys
653            // but we still need to scan from the beginning
654        }
655
656        let restart_idx = if lo > 0 { lo - 1 } else { 0 };
657        let start_ordinal = restart_idx * self.restart_interval;
658        let end_ordinal = if restart_idx + 1 < self.restart_count {
659            (restart_idx + 1) * self.restart_interval
660        } else {
661            self.num_blocks as usize
662        };
663
664        // Linear scan from restart point through at most R entries
665        let scan_offset = self.restart_offset(restart_idx) as usize;
666        let mut reader = &keys_data[scan_offset..];
667        let mut current_key = Vec::new();
668        let mut last_le_block: Option<usize> = None;
669
670        for i in start_ordinal..end_ordinal {
671            let prefix_len = match read_vint(&mut reader) {
672                Ok(v) => v as usize,
673                Err(_) => break,
674            };
675            let suffix_len = match read_vint(&mut reader) {
676                Ok(v) => v as usize,
677                Err(_) => break,
678            };
679
680            current_key.truncate(prefix_len);
681            if suffix_len > reader.len() {
682                break;
683            }
684            current_key.extend_from_slice(&reader[..suffix_len]);
685            reader = &reader[suffix_len..];
686
687            match current_key.as_slice().cmp(target) {
688                std::cmp::Ordering::Equal => return Some(i),
689                std::cmp::Ordering::Less => last_le_block = Some(i),
690                std::cmp::Ordering::Greater => return last_le_block,
691            }
692        }
693
694        last_le_block
695    }
696
697    /// Get block address by ordinal
698    pub fn get_addr(&self, ordinal: usize) -> Option<BlockAddr> {
699        self.block_addrs.get(ordinal)
700    }
701
702    /// Number of blocks
703    pub fn len(&self) -> usize {
704        self.num_blocks as usize
705    }
706
707    /// Check if empty
708    pub fn is_empty(&self) -> bool {
709        self.num_blocks == 0
710    }
711
712    /// Get all block addresses
713    pub fn all_addrs(&self) -> Vec<BlockAddr> {
714        self.block_addrs.all()
715    }
716
717    /// Decode all keys (for debugging/merging)
718    pub fn all_keys(&self) -> Vec<Vec<u8>> {
719        let mut result = Vec::with_capacity(self.num_blocks as usize);
720        let keys_data = &self.data.as_slice()[self.keys_offset..self.keys_end];
721        let mut reader = keys_data;
722        let mut current_key = Vec::new();
723
724        for _ in 0..self.num_blocks {
725            let prefix_len = match read_vint(&mut reader) {
726                Ok(v) => v as usize,
727                Err(_) => break,
728            };
729            let suffix_len = match read_vint(&mut reader) {
730                Ok(v) => v as usize,
731                Err(_) => break,
732            };
733
734            current_key.truncate(prefix_len);
735            if suffix_len > reader.len() {
736                break;
737            }
738            current_key.extend_from_slice(&reader[..suffix_len]);
739            reader = &reader[suffix_len..];
740
741            result.push(current_key.clone());
742        }
743
744        result
745    }
746}
747
748/// Unified block index that can use either FST or mmap'd raw index
749pub enum BlockIndex {
750    #[cfg(feature = "fst-index")]
751    Fst(FstBlockIndex),
752    Mmap(MmapBlockIndex),
753}
754
755impl BlockIndex {
756    /// Locate the block that could contain the key
757    pub fn locate(&self, key: &[u8]) -> Option<usize> {
758        match self {
759            #[cfg(feature = "fst-index")]
760            BlockIndex::Fst(idx) => idx.locate(key),
761            BlockIndex::Mmap(idx) => idx.locate(key),
762        }
763    }
764
765    /// Get block address by ordinal
766    pub fn get_addr(&self, ordinal: usize) -> Option<BlockAddr> {
767        match self {
768            #[cfg(feature = "fst-index")]
769            BlockIndex::Fst(idx) => idx.get_addr(ordinal),
770            BlockIndex::Mmap(idx) => idx.get_addr(ordinal),
771        }
772    }
773
774    /// Number of blocks
775    pub fn len(&self) -> usize {
776        match self {
777            #[cfg(feature = "fst-index")]
778            BlockIndex::Fst(idx) => idx.len(),
779            BlockIndex::Mmap(idx) => idx.len(),
780        }
781    }
782
783    /// Check if empty
784    pub fn is_empty(&self) -> bool {
785        self.len() == 0
786    }
787
788    /// Get all block addresses
789    pub fn all_addrs(&self) -> Vec<BlockAddr> {
790        match self {
791            #[cfg(feature = "fst-index")]
792            BlockIndex::Fst(idx) => idx.all_addrs(),
793            BlockIndex::Mmap(idx) => idx.all_addrs(),
794        }
795    }
796}
797
798// ============================================================================
799// Helper functions
800// ============================================================================
801
802fn common_prefix_len(a: &[u8], b: &[u8]) -> usize {
803    a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count()
804}
805
806fn write_vint<W: Write>(writer: &mut W, mut value: u64) -> io::Result<()> {
807    loop {
808        let byte = (value & 0x7F) as u8;
809        value >>= 7;
810        if value == 0 {
811            writer.write_all(&[byte])?;
812            return Ok(());
813        } else {
814            writer.write_all(&[byte | 0x80])?;
815        }
816    }
817}
818
819fn read_vint(reader: &mut &[u8]) -> io::Result<u64> {
820    let mut result = 0u64;
821    let mut shift = 0;
822
823    loop {
824        if reader.is_empty() {
825            return Err(io::Error::new(
826                io::ErrorKind::UnexpectedEof,
827                "Unexpected end of varint",
828            ));
829        }
830        let byte = reader[0];
831        *reader = &reader[1..];
832        result |= ((byte & 0x7F) as u64) << shift;
833        if byte & 0x80 == 0 {
834            return Ok(result);
835        }
836        shift += 7;
837        if shift >= 64 {
838            return Err(io::Error::new(
839                io::ErrorKind::InvalidData,
840                "Varint too long",
841            ));
842        }
843    }
844}
845
846/// Simple bit writer for packing
847struct BitWriter<'a> {
848    output: &'a mut Vec<u8>,
849    buffer: u64,
850    bits_in_buffer: u8,
851}
852
853impl<'a> BitWriter<'a> {
854    fn new(output: &'a mut Vec<u8>) -> Self {
855        Self {
856            output,
857            buffer: 0,
858            bits_in_buffer: 0,
859        }
860    }
861
862    fn write(&mut self, value: u64, num_bits: u8) -> io::Result<()> {
863        debug_assert!(num_bits <= 64);
864
865        self.buffer |= value << self.bits_in_buffer;
866        self.bits_in_buffer += num_bits;
867
868        while self.bits_in_buffer >= 8 {
869            self.output.push(self.buffer as u8);
870            self.buffer >>= 8;
871            self.bits_in_buffer -= 8;
872        }
873
874        Ok(())
875    }
876
877    fn flush(&mut self) -> io::Result<()> {
878        if self.bits_in_buffer > 0 {
879            self.output.push(self.buffer as u8);
880            self.buffer = 0;
881            self.bits_in_buffer = 0;
882        }
883        Ok(())
884    }
885}
886
887/// Simple bit reader for unpacking
888struct BitReader<'a> {
889    data: &'a [u8],
890    byte_pos: usize,
891    bit_pos: u8,
892}
893
894impl<'a> BitReader<'a> {
895    fn new(data: &'a [u8]) -> Self {
896        Self {
897            data,
898            byte_pos: 0,
899            bit_pos: 0,
900        }
901    }
902
903    fn read(&mut self, num_bits: u8) -> io::Result<u64> {
904        if num_bits == 0 {
905            return Ok(0);
906        }
907
908        let mut result: u64 = 0;
909        let mut bits_read: u8 = 0;
910
911        while bits_read < num_bits {
912            if self.byte_pos >= self.data.len() {
913                return Err(io::Error::new(
914                    io::ErrorKind::UnexpectedEof,
915                    "Not enough bits",
916                ));
917            }
918
919            let bits_available = 8 - self.bit_pos;
920            let bits_to_read = (num_bits - bits_read).min(bits_available);
921            // Handle edge case where bits_to_read == 8 to avoid overflow
922            let mask = if bits_to_read >= 8 {
923                0xFF
924            } else {
925                (1u8 << bits_to_read) - 1
926            };
927            let bits = (self.data[self.byte_pos] >> self.bit_pos) & mask;
928
929            result |= (bits as u64) << bits_read;
930            bits_read += bits_to_read;
931            self.bit_pos += bits_to_read;
932
933            if self.bit_pos >= 8 {
934                self.byte_pos += 1;
935                self.bit_pos = 0;
936            }
937        }
938
939        Ok(result)
940    }
941}
942
943#[cfg(test)]
944mod tests {
945    use super::*;
946
947    #[test]
948    fn test_block_addr_store_roundtrip() {
949        let addrs = vec![
950            BlockAddr {
951                offset: 0,
952                length: 1000,
953            },
954            BlockAddr {
955                offset: 1000,
956                length: 1500,
957            },
958            BlockAddr {
959                offset: 2500,
960                length: 800,
961            },
962            BlockAddr {
963                offset: 3300,
964                length: 2000,
965            },
966        ];
967
968        let bytes = BlockAddrStore::build(&addrs).unwrap();
969        let store = BlockAddrStore::load(OwnedBytes::new(bytes)).unwrap();
970
971        assert_eq!(store.len(), 4);
972        for (i, expected) in addrs.iter().enumerate() {
973            let actual = store.get(i).unwrap();
974            assert_eq!(actual.offset, expected.offset, "offset mismatch at {}", i);
975            assert_eq!(actual.length, expected.length, "length mismatch at {}", i);
976        }
977    }
978
979    #[test]
980    fn test_block_addr_store_empty() {
981        let bytes = BlockAddrStore::build(&[]).unwrap();
982        let store = BlockAddrStore::load(OwnedBytes::new(bytes)).unwrap();
983        assert_eq!(store.len(), 0);
984        assert!(store.get(0).is_none());
985    }
986
987    #[test]
988    fn test_block_addr_store_rejects_truncated_packed_data() {
989        let bytes = vec![1, 0, 0, 0, 1, 1];
990        assert!(BlockAddrStore::load(OwnedBytes::new(bytes)).is_err());
991    }
992
993    #[test]
994    fn test_mmap_index_rejects_restart_table_underflow() {
995        let entries = vec![(
996            b"key".to_vec(),
997            BlockAddr {
998                offset: 0,
999                length: 1,
1000            },
1001        )];
1002        let mut bytes = MmapBlockIndex::build(&entries).unwrap();
1003        let footer = bytes.len() - 6;
1004        bytes[footer..footer + 4].copy_from_slice(&u32::MAX.to_le_bytes());
1005        assert!(MmapBlockIndex::load(OwnedBytes::new(bytes)).is_err());
1006    }
1007
1008    #[test]
1009    fn test_mmap_block_index_roundtrip() {
1010        let entries = vec![
1011            (
1012                b"aaa".to_vec(),
1013                BlockAddr {
1014                    offset: 0,
1015                    length: 100,
1016                },
1017            ),
1018            (
1019                b"bbb".to_vec(),
1020                BlockAddr {
1021                    offset: 100,
1022                    length: 150,
1023                },
1024            ),
1025            (
1026                b"ccc".to_vec(),
1027                BlockAddr {
1028                    offset: 250,
1029                    length: 200,
1030                },
1031            ),
1032        ];
1033
1034        let bytes = MmapBlockIndex::build(&entries).unwrap();
1035        let index = MmapBlockIndex::load(OwnedBytes::new(bytes)).unwrap();
1036
1037        assert_eq!(index.len(), 3);
1038
1039        // Test locate
1040        assert_eq!(index.locate(b"aaa"), Some(0));
1041        assert_eq!(index.locate(b"bbb"), Some(1));
1042        assert_eq!(index.locate(b"ccc"), Some(2));
1043        assert_eq!(index.locate(b"aab"), Some(0)); // Between aaa and bbb
1044        assert_eq!(index.locate(b"ddd"), Some(2)); // After all keys
1045        assert_eq!(index.locate(b"000"), None); // Before all keys
1046    }
1047
1048    #[cfg(feature = "fst-index")]
1049    #[test]
1050    fn test_fst_block_index_roundtrip() {
1051        let entries = vec![
1052            (
1053                b"aaa".to_vec(),
1054                BlockAddr {
1055                    offset: 0,
1056                    length: 100,
1057                },
1058            ),
1059            (
1060                b"bbb".to_vec(),
1061                BlockAddr {
1062                    offset: 100,
1063                    length: 150,
1064                },
1065            ),
1066            (
1067                b"ccc".to_vec(),
1068                BlockAddr {
1069                    offset: 250,
1070                    length: 200,
1071                },
1072            ),
1073        ];
1074
1075        let bytes = FstBlockIndex::build(&entries).unwrap();
1076        let index = FstBlockIndex::load(OwnedBytes::new(bytes)).unwrap();
1077
1078        assert_eq!(index.len(), 3);
1079
1080        // Test locate
1081        assert_eq!(index.locate(b"aaa"), Some(0));
1082        assert_eq!(index.locate(b"bbb"), Some(1));
1083        assert_eq!(index.locate(b"ccc"), Some(2));
1084        assert_eq!(index.locate(b"aab"), Some(0)); // Between aaa and bbb
1085        assert_eq!(index.locate(b"ddd"), Some(2)); // After all keys
1086    }
1087
1088    #[test]
1089    fn test_bit_writer_reader() {
1090        let mut buf = Vec::new();
1091        let mut writer = BitWriter::new(&mut buf);
1092
1093        writer.write(5, 3).unwrap(); // 101
1094        writer.write(3, 2).unwrap(); // 11
1095        writer.write(15, 4).unwrap(); // 1111
1096        writer.flush().unwrap();
1097
1098        let mut reader = BitReader::new(&buf);
1099        assert_eq!(reader.read(3).unwrap(), 5);
1100        assert_eq!(reader.read(2).unwrap(), 3);
1101        assert_eq!(reader.read(4).unwrap(), 15);
1102    }
1103}