Skip to main content

hermes_core/segment/
vector_data.rs

1//! Vector index data structures shared between builder and reader
2
3use std::io;
4use std::mem::size_of;
5
6use serde::{Deserialize, Serialize};
7
8use crate::directories::{FileHandle, OwnedBytes};
9use crate::dsl::DenseVectorQuantization;
10use crate::segment::format::{DOC_ID_ENTRY_SIZE, FLAT_BINARY_HEADER_SIZE, FLAT_BINARY_MAGIC};
11use crate::structures::simd::{batch_f32_to_f16, batch_f32_to_u8, f16_to_f32, u8_to_f32};
12
13/// Dequantize raw bytes to f32 based on storage quantization.
14///
15/// `raw` is the quantized byte slice, `out` receives the f32 values.
16/// `num_floats` is the number of f32 values to produce (= num_vectors × dim).
17/// Data-first file layout guarantees alignment for f32/f16 access.
18#[inline]
19pub fn dequantize_raw(
20    raw: &[u8],
21    quant: DenseVectorQuantization,
22    num_floats: usize,
23    out: &mut [f32],
24) -> io::Result<()> {
25    if out.len() < num_floats {
26        return Err(io::Error::new(
27            io::ErrorKind::InvalidInput,
28            format!(
29                "dequantization output is too short: need {num_floats} floats, got {}",
30                out.len()
31            ),
32        ));
33    }
34
35    let element_size = match quant {
36        DenseVectorQuantization::F32 => size_of::<f32>(),
37        DenseVectorQuantization::F16 => size_of::<u16>(),
38        DenseVectorQuantization::UInt8 => size_of::<u8>(),
39        DenseVectorQuantization::Binary => {
40            return Err(io::Error::new(
41                io::ErrorKind::InvalidInput,
42                "binary vectors cannot be dequantized to f32",
43            ));
44        }
45    };
46    let expected_bytes = num_floats.checked_mul(element_size).ok_or_else(|| {
47        io::Error::new(
48            io::ErrorKind::InvalidInput,
49            "dequantization byte length overflows usize",
50        )
51    })?;
52    if raw.len() != expected_bytes {
53        return Err(io::Error::new(
54            io::ErrorKind::InvalidData,
55            format!(
56                "dequantization input length mismatch: need {expected_bytes} bytes, got {}",
57                raw.len()
58            ),
59        ));
60    }
61
62    match quant {
63        DenseVectorQuantization::F32 => {
64            if expected_bytes > 0
65                && !(raw.as_ptr() as usize).is_multiple_of(std::mem::align_of::<f32>())
66            {
67                return Err(io::Error::new(
68                    io::ErrorKind::InvalidData,
69                    "f32 vector data is not 4-byte aligned",
70                ));
71            }
72            out[..num_floats].copy_from_slice(unsafe {
73                // Safety: the exact byte length and f32 alignment were checked above.
74                std::slice::from_raw_parts(raw.as_ptr() as *const f32, num_floats)
75            });
76        }
77        DenseVectorQuantization::F16 => {
78            if expected_bytes > 0
79                && !(raw.as_ptr() as usize).is_multiple_of(std::mem::align_of::<u16>())
80            {
81                return Err(io::Error::new(
82                    io::ErrorKind::InvalidData,
83                    "f16 vector data is not 2-byte aligned",
84                ));
85            }
86            let f16_slice = unsafe {
87                // Safety: the exact byte length and u16 alignment were checked above.
88                std::slice::from_raw_parts(raw.as_ptr() as *const u16, num_floats)
89            };
90            for (i, &h) in f16_slice.iter().enumerate() {
91                out[i] = f16_to_f32(h);
92            }
93        }
94        DenseVectorQuantization::UInt8 => {
95            for (i, &b) in raw.iter().enumerate() {
96                out[i] = u8_to_f32(b);
97            }
98        }
99        DenseVectorQuantization::Binary => unreachable!("validated above"),
100    }
101    Ok(())
102}
103
104/// Flat vector binary format helpers for writing.
105///
106/// Binary format v3:
107/// ```text
108/// [magic(u32)][dim(u32)][num_vectors(u32)][quant_type(u8)][padding(3)]
109/// [vectors: N×dim×element_size]
110/// [doc_ids: N×(u32+u16)]
111/// ```
112///
113/// `element_size` is determined by `quant_type`: f32=4, f16=2, uint8=1.
114/// Reading is handled by [`LazyFlatVectorData`] which loads only doc_ids into memory
115/// and accesses vector data lazily via mmap-backed range reads.
116pub struct FlatVectorData;
117
118impl FlatVectorData {
119    fn validate_shape(
120        dim: usize,
121        num_vectors: usize,
122        quant: DenseVectorQuantization,
123    ) -> io::Result<usize> {
124        if dim == 0 {
125            return Err(io::Error::new(
126                io::ErrorKind::InvalidInput,
127                "flat vector dimension must be greater than zero",
128            ));
129        }
130        if quant == DenseVectorQuantization::Binary && !dim.is_multiple_of(8) {
131            return Err(io::Error::new(
132                io::ErrorKind::InvalidInput,
133                format!("binary flat vector dimension must be a multiple of 8, got {dim}"),
134            ));
135        }
136        u32::try_from(dim).map_err(|_| {
137            io::Error::new(
138                io::ErrorKind::InvalidInput,
139                format!("flat vector dimension {dim} exceeds u32::MAX"),
140            )
141        })?;
142        u32::try_from(num_vectors).map_err(|_| {
143            io::Error::new(
144                io::ErrorKind::InvalidInput,
145                format!("flat vector count {num_vectors} exceeds u32::MAX"),
146            )
147        })?;
148
149        match quant {
150            DenseVectorQuantization::Binary => dim.checked_add(7).map(|bits| bits / 8),
151            _ => dim.checked_mul(quant.element_size()),
152        }
153        .ok_or_else(|| {
154            io::Error::new(
155                io::ErrorKind::InvalidInput,
156                "flat vector byte size overflows usize",
157            )
158        })
159    }
160
161    fn validate_doc_ids(doc_ids: &[(u32, u16)]) -> io::Result<()> {
162        if let Some(pair) = doc_ids.windows(2).find(|pair| pair[0] >= pair[1]) {
163            return Err(io::Error::new(
164                io::ErrorKind::InvalidInput,
165                format!(
166                    "flat vector doc map must be strictly sorted by (doc_id, ordinal), found {:?} before {:?}",
167                    pair[0], pair[1]
168                ),
169            ));
170        }
171        Ok(())
172    }
173
174    /// Validate a dense writer input completely before any bytes are emitted.
175    /// Returns the exact serialized size on success.
176    pub(crate) fn validate_dense_input(
177        dim: usize,
178        flat_vectors: &[f32],
179        doc_ids: &[(u32, u16)],
180        quant: DenseVectorQuantization,
181    ) -> io::Result<usize> {
182        if quant == DenseVectorQuantization::Binary {
183            return Err(io::Error::new(
184                io::ErrorKind::InvalidInput,
185                "binary quantization must use serialize_binary_from_bits_streaming",
186            ));
187        }
188        let num_vectors = doc_ids.len();
189        let expected_floats = num_vectors.checked_mul(dim).ok_or_else(|| {
190            io::Error::new(
191                io::ErrorKind::InvalidInput,
192                "flat f32 vector count overflows usize",
193            )
194        })?;
195        if flat_vectors.len() != expected_floats {
196            return Err(io::Error::new(
197                io::ErrorKind::InvalidInput,
198                format!(
199                    "flat vector input has {} floats, expected {num_vectors} x {dim} = {expected_floats}",
200                    flat_vectors.len()
201                ),
202            ));
203        }
204        Self::validate_doc_ids(doc_ids)?;
205        Self::serialized_binary_size(dim, num_vectors, quant)
206    }
207
208    /// Validate a packed-binary writer input completely before any bytes are
209    /// emitted. Returns the exact serialized size on success.
210    pub(crate) fn validate_binary_input(
211        dim_bits: usize,
212        packed_vectors: &[u8],
213        doc_ids: &[(u32, u16)],
214    ) -> io::Result<usize> {
215        let num_vectors = doc_ids.len();
216        let byte_len =
217            Self::validate_shape(dim_bits, num_vectors, DenseVectorQuantization::Binary)?;
218        let expected_bytes = num_vectors.checked_mul(byte_len).ok_or_else(|| {
219            io::Error::new(
220                io::ErrorKind::InvalidInput,
221                "packed binary vector size overflows usize",
222            )
223        })?;
224        if packed_vectors.len() != expected_bytes {
225            return Err(io::Error::new(
226                io::ErrorKind::InvalidInput,
227                format!(
228                    "packed binary input has {} bytes, expected {num_vectors} x {byte_len} = {expected_bytes}",
229                    packed_vectors.len()
230                ),
231            ));
232        }
233        Self::validate_doc_ids(doc_ids)?;
234        Self::serialized_binary_size(dim_bits, num_vectors, DenseVectorQuantization::Binary)
235    }
236
237    /// Write the binary header to a writer.
238    pub fn write_binary_header(
239        dim: usize,
240        num_vectors: usize,
241        quant: DenseVectorQuantization,
242        writer: &mut dyn std::io::Write,
243    ) -> std::io::Result<()> {
244        Self::validate_shape(dim, num_vectors, quant)?;
245        let dim = u32::try_from(dim).map_err(|_| {
246            io::Error::new(
247                io::ErrorKind::InvalidInput,
248                "flat vector dimension exceeds u32",
249            )
250        })?;
251        let num_vectors = u32::try_from(num_vectors).map_err(|_| {
252            io::Error::new(io::ErrorKind::InvalidInput, "flat vector count exceeds u32")
253        })?;
254        writer.write_all(&FLAT_BINARY_MAGIC.to_le_bytes())?;
255        writer.write_all(&dim.to_le_bytes())?;
256        writer.write_all(&num_vectors.to_le_bytes())?;
257        writer.write_all(&[quant.tag(), 0, 0, 0])?; // quant_type + 3 bytes padding
258        Ok(())
259    }
260
261    /// Compute the serialized size without actually serializing.
262    pub fn serialized_binary_size(
263        dim: usize,
264        num_vectors: usize,
265        quant: DenseVectorQuantization,
266    ) -> io::Result<usize> {
267        let bytes_per_vector = Self::validate_shape(dim, num_vectors, quant)?;
268        let vector_bytes = num_vectors.checked_mul(bytes_per_vector).ok_or_else(|| {
269            io::Error::new(
270                io::ErrorKind::InvalidInput,
271                "flat vector payload size overflows usize",
272            )
273        })?;
274        let doc_id_bytes = num_vectors.checked_mul(DOC_ID_ENTRY_SIZE).ok_or_else(|| {
275            io::Error::new(
276                io::ErrorKind::InvalidInput,
277                "flat vector doc-map size overflows usize",
278            )
279        })?;
280        FLAT_BINARY_HEADER_SIZE
281            .checked_add(vector_bytes)
282            .and_then(|size| size.checked_add(doc_id_bytes))
283            .ok_or_else(|| {
284                io::Error::new(
285                    io::ErrorKind::InvalidInput,
286                    "flat vector serialized size overflows usize",
287                )
288            })
289    }
290
291    /// Stream from flat f32 storage to a writer, quantizing on write.
292    ///
293    /// `flat_vectors` is contiguous storage of dim*n f32 floats.
294    /// Vectors are quantized to the specified format before writing.
295    pub fn serialize_binary_from_flat_streaming(
296        dim: usize,
297        flat_vectors: &[f32],
298        doc_ids: &[(u32, u16)],
299        quant: DenseVectorQuantization,
300        writer: &mut dyn std::io::Write,
301    ) -> std::io::Result<()> {
302        Self::validate_dense_input(dim, flat_vectors, doc_ids, quant)?;
303        let num_vectors = doc_ids.len();
304        Self::write_binary_header(dim, num_vectors, quant, writer)?;
305
306        match quant {
307            DenseVectorQuantization::F32 => {
308                let bytes: &[u8] = unsafe {
309                    std::slice::from_raw_parts(
310                        flat_vectors.as_ptr() as *const u8,
311                        std::mem::size_of_val(flat_vectors),
312                    )
313                };
314                writer.write_all(bytes)?;
315            }
316            DenseVectorQuantization::F16 => {
317                let mut buf = vec![0u16; dim];
318                for v in flat_vectors.chunks_exact(dim) {
319                    batch_f32_to_f16(v, &mut buf);
320                    let bytes: &[u8] =
321                        unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, dim * 2) };
322                    writer.write_all(bytes)?;
323                }
324            }
325            DenseVectorQuantization::UInt8 => {
326                let mut buf = vec![0u8; dim];
327                for v in flat_vectors.chunks_exact(dim) {
328                    batch_f32_to_u8(v, &mut buf);
329                    writer.write_all(&buf)?;
330                }
331            }
332            DenseVectorQuantization::Binary => unreachable!("validated above"),
333        }
334
335        for &(doc_id, ordinal) in doc_ids {
336            writer.write_all(&doc_id.to_le_bytes())?;
337            writer.write_all(&ordinal.to_le_bytes())?;
338        }
339
340        Ok(())
341    }
342
343    /// Stream packed binary vectors (pre-packed bytes) to a writer.
344    ///
345    /// `packed_vectors` is contiguous storage of num_vectors * byte_len bytes.
346    /// `dim_bits` is the number of bits (dimensions).
347    pub fn serialize_binary_from_bits_streaming(
348        dim_bits: usize,
349        packed_vectors: &[u8],
350        doc_ids: &[(u32, u16)],
351        writer: &mut dyn std::io::Write,
352    ) -> std::io::Result<()> {
353        Self::validate_binary_input(dim_bits, packed_vectors, doc_ids)?;
354        let num_vectors = doc_ids.len();
355
356        Self::write_binary_header(
357            dim_bits,
358            num_vectors,
359            DenseVectorQuantization::Binary,
360            writer,
361        )?;
362        writer.write_all(packed_vectors)?;
363
364        for &(doc_id, ordinal) in doc_ids {
365            writer.write_all(&doc_id.to_le_bytes())?;
366            writer.write_all(&ordinal.to_le_bytes())?;
367        }
368
369        Ok(())
370    }
371
372    /// Write raw pre-quantized vector bytes to a writer (for merger streaming).
373    ///
374    /// `raw_bytes` is already in the target quantized format.
375    pub fn write_raw_vector_bytes(
376        raw_bytes: &[u8],
377        writer: &mut dyn std::io::Write,
378    ) -> std::io::Result<()> {
379        writer.write_all(raw_bytes)
380    }
381}
382
383/// Lazy flat vector data — zero-copy doc_id index, vectors via range reads.
384///
385/// The doc_id index is kept as `OwnedBytes` (mmap-backed, zero heap copy).
386/// Vector data stays on disk and is accessed via mmap-backed range reads.
387/// Element size depends on quantization: f32=4, f16=2, uint8=1 bytes/dim.
388///
389/// Used for:
390/// - Brute-force search (batched scoring with native-precision SIMD)
391/// - Reranking (read individual vectors by doc_id via binary search)
392/// - doc() hydration (dequantize to f32 for stored documents)
393/// - Merge streaming (chunked raw vector bytes + doc_id iteration)
394#[derive(Debug, Clone)]
395pub struct LazyFlatVectorData {
396    /// Vector dimension
397    pub dim: usize,
398    /// Total number of vectors
399    pub num_vectors: usize,
400    /// Number of distinct document IDs represented in the flat vector map.
401    num_docs_with_vectors: usize,
402    /// Storage quantization type
403    pub quantization: DenseVectorQuantization,
404    /// Zero-copy doc_id index: packed [u32_le doc_id + u16_le ordinal] × num_vectors
405    doc_ids_bytes: OwnedBytes,
406    /// File handle for this field's flat data region in the .vectors file
407    handle: FileHandle,
408    /// Byte offset within handle where raw vector data starts (after header)
409    vectors_offset: u64,
410    /// Bytes per vector in storage (cached: Binary = ceil(dim/8), else dim * element_size)
411    vbs: usize,
412    /// Exact byte length of the raw vector region, validated when opening.
413    vectors_byte_len: u64,
414}
415
416impl LazyFlatVectorData {
417    /// Open from a lazy file slice pointing to the flat binary data region.
418    ///
419    /// Reads header (16 bytes) + doc_ids (~6 bytes/vector) into memory.
420    /// Vector data stays lazy on disk.
421    pub async fn open(handle: FileHandle) -> io::Result<Self> {
422        Self::open_with_doc_limit(handle, None).await
423    }
424
425    /// Open flat vectors while also validating every referenced document ID.
426    ///
427    /// Segment readers pass their durable `num_docs` here. Keeping the public
428    /// `open` entry point is useful for standalone flat payloads and tests that
429    /// do not have segment metadata available.
430    pub(crate) async fn open_with_doc_limit(
431        handle: FileHandle,
432        total_docs: Option<u32>,
433    ) -> io::Result<Self> {
434        let header_len = u64::try_from(FLAT_BINARY_HEADER_SIZE).map_err(|_| {
435            io::Error::new(
436                io::ErrorKind::InvalidData,
437                "flat vector header size does not fit in u64",
438            )
439        })?;
440        if handle.len() < header_len {
441            return Err(io::Error::new(
442                io::ErrorKind::UnexpectedEof,
443                format!(
444                    "flat vector payload is {} bytes, shorter than its {FLAT_BINARY_HEADER_SIZE}-byte header",
445                    handle.len()
446                ),
447            ));
448        }
449
450        // Read header: magic(4) + dim(4) + num_vectors(4) + quant_type(1) + pad(3) = 16 bytes
451        let header = handle.read_bytes_range(0..header_len).await?;
452        if header.len() != FLAT_BINARY_HEADER_SIZE {
453            return Err(io::Error::new(
454                io::ErrorKind::UnexpectedEof,
455                format!(
456                    "flat vector header read returned {} bytes, expected {FLAT_BINARY_HEADER_SIZE}",
457                    header.len()
458                ),
459            ));
460        }
461        let hdr = header.as_slice();
462
463        let magic = u32::from_le_bytes([hdr[0], hdr[1], hdr[2], hdr[3]]);
464        if magic != FLAT_BINARY_MAGIC {
465            return Err(io::Error::new(
466                io::ErrorKind::InvalidData,
467                "Invalid FlatVectorData binary magic",
468            ));
469        }
470
471        let dim = u32::from_le_bytes([hdr[4], hdr[5], hdr[6], hdr[7]]) as usize;
472        let num_vectors = u32::from_le_bytes([hdr[8], hdr[9], hdr[10], hdr[11]]) as usize;
473        let quantization = DenseVectorQuantization::from_tag(hdr[12]).ok_or_else(|| {
474            io::Error::new(
475                io::ErrorKind::InvalidData,
476                format!("Unknown quantization tag: {}", hdr[12]),
477            )
478        })?;
479        if hdr[13..] != [0, 0, 0] {
480            return Err(io::Error::new(
481                io::ErrorKind::InvalidData,
482                "flat vector header has non-zero reserved bytes",
483            ));
484        }
485
486        // Read doc_ids section as zero-copy OwnedBytes (6 bytes per vector)
487        let vbs =
488            FlatVectorData::validate_shape(dim, num_vectors, quantization).map_err(|error| {
489                io::Error::new(
490                    io::ErrorKind::InvalidData,
491                    format!("invalid flat vector shape: {error}"),
492                )
493            })?;
494        let vectors_byte_len_usize = num_vectors.checked_mul(vbs).ok_or_else(|| {
495            io::Error::new(
496                io::ErrorKind::InvalidData,
497                "flat vector payload size overflows usize",
498            )
499        })?;
500        let doc_ids_byte_len_usize =
501            num_vectors.checked_mul(DOC_ID_ENTRY_SIZE).ok_or_else(|| {
502                io::Error::new(
503                    io::ErrorKind::InvalidData,
504                    "flat vector doc-map size overflows usize",
505                )
506            })?;
507        let expected_len_usize = FLAT_BINARY_HEADER_SIZE
508            .checked_add(vectors_byte_len_usize)
509            .and_then(|size| size.checked_add(doc_ids_byte_len_usize))
510            .ok_or_else(|| {
511                io::Error::new(
512                    io::ErrorKind::InvalidData,
513                    "flat vector serialized size overflows usize",
514                )
515            })?;
516        let expected_len = u64::try_from(expected_len_usize).map_err(|_| {
517            io::Error::new(
518                io::ErrorKind::InvalidData,
519                "flat vector serialized size does not fit in u64",
520            )
521        })?;
522        if handle.len() != expected_len {
523            return Err(io::Error::new(
524                io::ErrorKind::InvalidData,
525                format!(
526                    "flat vector payload has {} bytes, expected exactly {expected_len}",
527                    handle.len()
528                ),
529            ));
530        }
531
532        let vectors_byte_len = u64::try_from(vectors_byte_len_usize).map_err(|_| {
533            io::Error::new(
534                io::ErrorKind::InvalidData,
535                "flat vector payload size does not fit in u64",
536            )
537        })?;
538        let doc_ids_byte_len = u64::try_from(doc_ids_byte_len_usize).map_err(|_| {
539            io::Error::new(
540                io::ErrorKind::InvalidData,
541                "flat vector doc-map size does not fit in u64",
542            )
543        })?;
544        let doc_ids_start = header_len.checked_add(vectors_byte_len).ok_or_else(|| {
545            io::Error::new(
546                io::ErrorKind::InvalidData,
547                "flat vector doc-map offset overflows u64",
548            )
549        })?;
550        let doc_ids_end = doc_ids_start.checked_add(doc_ids_byte_len).ok_or_else(|| {
551            io::Error::new(
552                io::ErrorKind::InvalidData,
553                "flat vector doc-map range overflows u64",
554            )
555        })?;
556
557        let doc_ids_bytes = handle.read_bytes_range(doc_ids_start..doc_ids_end).await?;
558        if doc_ids_bytes.len() != doc_ids_byte_len_usize {
559            return Err(io::Error::new(
560                io::ErrorKind::UnexpectedEof,
561                format!(
562                    "flat vector doc-map read returned {} bytes, expected {doc_ids_byte_len_usize}",
563                    doc_ids_bytes.len()
564                ),
565            ));
566        }
567
568        let mut previous = None;
569        let mut num_docs_with_vectors = 0usize;
570        for entry in doc_ids_bytes.as_slice().chunks_exact(DOC_ID_ENTRY_SIZE) {
571            let doc_id = u32::from_le_bytes([entry[0], entry[1], entry[2], entry[3]]);
572            let ordinal = u16::from_le_bytes([entry[4], entry[5]]);
573            let current = (doc_id, ordinal);
574            if let Some(previous) = previous
575                && previous >= current
576            {
577                return Err(io::Error::new(
578                    io::ErrorKind::InvalidData,
579                    format!(
580                        "flat vector doc map must be strictly sorted by (doc_id, ordinal), found {previous:?} before {current:?}"
581                    ),
582                ));
583            }
584            if let Some(limit) = total_docs
585                && doc_id >= limit
586            {
587                return Err(io::Error::new(
588                    io::ErrorKind::InvalidData,
589                    format!(
590                        "flat vector doc map references document {doc_id}, but segment contains only {} documents",
591                        limit
592                    ),
593                ));
594            }
595            if previous.is_none_or(|(previous_doc_id, _)| previous_doc_id != doc_id) {
596                num_docs_with_vectors = num_docs_with_vectors.checked_add(1).ok_or_else(|| {
597                    io::Error::new(
598                        io::ErrorKind::InvalidData,
599                        "flat vector distinct-document count overflows usize",
600                    )
601                })?;
602            }
603            previous = Some(current);
604        }
605
606        Ok(Self {
607            dim,
608            num_vectors,
609            num_docs_with_vectors,
610            quantization,
611            doc_ids_bytes,
612            handle,
613            vectors_offset: header_len,
614            vbs,
615            vectors_byte_len,
616        })
617    }
618
619    fn checked_vector_range(
620        &self,
621        start_idx: usize,
622        count: usize,
623    ) -> io::Result<(std::ops::Range<u64>, usize)> {
624        let end_idx = start_idx.checked_add(count).ok_or_else(|| {
625            io::Error::new(
626                io::ErrorKind::InvalidInput,
627                "flat vector index range overflows usize",
628            )
629        })?;
630        if end_idx > self.num_vectors {
631            return Err(io::Error::new(
632                io::ErrorKind::InvalidInput,
633                format!(
634                    "flat vector range {start_idx}..{end_idx} exceeds {} vectors",
635                    self.num_vectors
636                ),
637            ));
638        }
639
640        let relative_offset = start_idx.checked_mul(self.vbs).ok_or_else(|| {
641            io::Error::new(
642                io::ErrorKind::InvalidData,
643                "flat vector byte offset overflows usize",
644            )
645        })?;
646        let byte_len = count.checked_mul(self.vbs).ok_or_else(|| {
647            io::Error::new(
648                io::ErrorKind::InvalidInput,
649                "flat vector byte length overflows usize",
650            )
651        })?;
652        let relative_offset = u64::try_from(relative_offset).map_err(|_| {
653            io::Error::new(
654                io::ErrorKind::InvalidData,
655                "flat vector byte offset does not fit in u64",
656            )
657        })?;
658        let byte_len_u64 = u64::try_from(byte_len).map_err(|_| {
659            io::Error::new(
660                io::ErrorKind::InvalidInput,
661                "flat vector byte length does not fit in u64",
662            )
663        })?;
664        let start = self
665            .vectors_offset
666            .checked_add(relative_offset)
667            .ok_or_else(|| {
668                io::Error::new(
669                    io::ErrorKind::InvalidData,
670                    "flat vector byte offset overflows u64",
671                )
672            })?;
673        let end = start.checked_add(byte_len_u64).ok_or_else(|| {
674            io::Error::new(
675                io::ErrorKind::InvalidData,
676                "flat vector byte range overflows u64",
677            )
678        })?;
679        let vectors_end = self
680            .vectors_offset
681            .checked_add(self.vectors_byte_len)
682            .ok_or_else(|| {
683                io::Error::new(
684                    io::ErrorKind::InvalidData,
685                    "flat vector payload boundary overflows u64",
686                )
687            })?;
688        if end > vectors_end || end > self.handle.len() {
689            return Err(io::Error::new(
690                io::ErrorKind::InvalidData,
691                format!(
692                    "flat vector byte range {start}..{end} exceeds payload boundary {vectors_end}"
693                ),
694            ));
695        }
696        Ok((start..end, byte_len))
697    }
698
699    /// Pin the doc-id map (priority 3: every rerank / top-k resolution
700    /// binary-searches it).
701    #[cfg(feature = "native")]
702    pub(crate) fn pin_doc_ids(
703        &mut self,
704        mode: crate::segment::pin::PinMode,
705        remaining: &mut u64,
706        report: &mut crate::segment::pin::PinReport,
707    ) {
708        crate::segment::pin::pin_section(
709            &mut self.doc_ids_bytes,
710            "flat doc_ids",
711            mode,
712            remaining,
713            report,
714        );
715    }
716
717    /// Advise the kernel that vector data will be accessed at random offsets.
718    ///
719    /// Disables kernel readahead for the raw vector region. Rerank reads
720    /// scattered ~vbs-sized records; default readahead pulls in 128KB per
721    /// fault, evicting useful pages in memory-bound environments.
722    /// No-op for non-mmap (RAM, HTTP) backing.
723    #[cfg(feature = "native")]
724    pub fn advise_random_access(&self) {
725        let Some(vectors_end) = self.vectors_offset.checked_add(self.vectors_byte_len) else {
726            return;
727        };
728        self.handle
729            .madvise_range(self.vectors_offset..vectors_end, libc::MADV_RANDOM);
730    }
731
732    /// Prefetch the pages backing a sorted set of vector indexes (`MADV_WILLNEED`).
733    ///
734    /// Coalesces adjacent candidates into ranges so the kernel can overlap
735    /// the page-ins instead of taking one synchronous major fault per vector
736    /// during the rerank read loop. Indexes must be yielded in ascending order.
737    /// No-op for non-mmap backing.
738    #[cfg(feature = "native")]
739    pub fn prefetch_vectors(&self, sorted_flat_indexes: impl IntoIterator<Item = usize>) {
740        /// Gap (in bytes) below which two candidate ranges are merged into one advice call.
741        const COALESCE_GAP: u64 = 64 * 1024;
742        let mut ranges = sorted_flat_indexes.into_iter().filter_map(|idx| {
743            self.checked_vector_range(idx, 1)
744                .ok()
745                .map(|(range, _)| range)
746        });
747        let Some(first) = ranges.next() else {
748            return;
749        };
750        let mut run_start = first.start;
751        let mut run_end = first.end;
752        for range in ranges {
753            if range.start <= run_end.saturating_add(COALESCE_GAP) {
754                run_end = run_end.max(range.end);
755            } else {
756                self.handle
757                    .madvise_range(run_start..run_end, libc::MADV_WILLNEED);
758                run_start = range.start;
759                run_end = range.end;
760            }
761        }
762        self.handle
763            .madvise_range(run_start..run_end, libc::MADV_WILLNEED);
764    }
765
766    /// Read a single vector by index, dequantized to f32.
767    ///
768    /// `out` must have length >= `self.dim`. Returns `Ok(())` on success.
769    /// Used for ANN training and doc() hydration where f32 is needed.
770    pub async fn read_vector_into(&self, idx: usize, out: &mut [f32]) -> io::Result<()> {
771        if out.len() < self.dim {
772            return Err(io::Error::new(
773                io::ErrorKind::InvalidInput,
774                format!(
775                    "flat vector output is too short: need {} floats, got {}",
776                    self.dim,
777                    out.len()
778                ),
779            ));
780        }
781        let bytes = self.read_vectors_batch(idx, 1).await?;
782        dequantize_raw(bytes.as_slice(), self.quantization, self.dim, out)
783    }
784
785    /// Read a single vector by index, dequantized to f32 (allocates a new Vec<f32>).
786    pub async fn get_vector(&self, idx: usize) -> io::Result<Vec<f32>> {
787        let mut vector = vec![0f32; self.dim];
788        self.read_vector_into(idx, &mut vector).await?;
789        Ok(vector)
790    }
791
792    /// Read a single vector's raw bytes (no dequantization) into a caller-provided buffer.
793    ///
794    /// `out` must have length >= `self.vector_byte_size()`.
795    /// Used for native-precision reranking where raw quantized bytes are scored directly.
796    pub async fn read_vector_raw_into(&self, idx: usize, out: &mut [u8]) -> io::Result<()> {
797        self.read_vector_prefix_raw_into(idx, self.vector_byte_size(), out)
798            .await
799    }
800
801    /// Read a prefix of one vector's raw bytes into a caller-provided buffer.
802    ///
803    /// This is used by Matryoshka scoring to avoid reading the unused tail of
804    /// a vector. Unlike the old full-vector boundary, all caller-controlled
805    /// sizes and offset arithmetic are checked in release builds.
806    pub async fn read_vector_prefix_raw_into(
807        &self,
808        idx: usize,
809        prefix_byte_len: usize,
810        out: &mut [u8],
811    ) -> io::Result<()> {
812        let vbs = self.vector_byte_size();
813        if prefix_byte_len > vbs {
814            return Err(io::Error::new(
815                io::ErrorKind::InvalidInput,
816                format!(
817                    "vector prefix is {prefix_byte_len} bytes, but a vector has only {vbs} bytes"
818                ),
819            ));
820        }
821        if out.len() < prefix_byte_len {
822            return Err(io::Error::new(
823                io::ErrorKind::InvalidInput,
824                format!(
825                    "vector prefix output is too short: need {prefix_byte_len} bytes, got {}",
826                    out.len()
827                ),
828            ));
829        }
830        let (full_range, _) = self.checked_vector_range(idx, 1)?;
831        if prefix_byte_len == 0 {
832            return Ok(());
833        }
834        let prefix_byte_len_u64 = u64::try_from(prefix_byte_len).map_err(|_| {
835            io::Error::new(
836                io::ErrorKind::InvalidInput,
837                "vector prefix length does not fit in u64",
838            )
839        })?;
840        let byte_end = full_range
841            .start
842            .checked_add(prefix_byte_len_u64)
843            .ok_or_else(|| {
844                io::Error::new(
845                    io::ErrorKind::InvalidData,
846                    "vector byte range overflows u64",
847                )
848            })?;
849        let bytes = self
850            .handle
851            .read_bytes_range(full_range.start..byte_end)
852            .await?;
853        if bytes.len() != prefix_byte_len {
854            return Err(io::Error::new(
855                io::ErrorKind::UnexpectedEof,
856                format!(
857                    "vector prefix read returned {} bytes, expected {prefix_byte_len}",
858                    bytes.len()
859                ),
860            ));
861        }
862        out[..prefix_byte_len].copy_from_slice(bytes.as_slice());
863        Ok(())
864    }
865
866    /// Read a contiguous batch of raw quantized bytes by index range.
867    ///
868    /// Returns raw bytes for vectors `[start_idx..start_idx+count)`.
869    /// Bytes are in native quantized format — pass to `batch_cosine_scores_f16/u8`
870    /// or `batch_cosine_scores` (for f32) for scoring.
871    pub async fn read_vectors_batch(
872        &self,
873        start_idx: usize,
874        count: usize,
875    ) -> io::Result<OwnedBytes> {
876        let (range, expected_len) = self.checked_vector_range(start_idx, count)?;
877        let bytes = self.handle.read_bytes_range(range).await?;
878        if bytes.len() != expected_len {
879            return Err(io::Error::new(
880                io::ErrorKind::UnexpectedEof,
881                format!(
882                    "flat vector batch read returned {} bytes, expected {expected_len}",
883                    bytes.len()
884                ),
885            ));
886        }
887        Ok(bytes)
888    }
889
890    /// Synchronous read of a single vector's raw bytes.
891    #[cfg(feature = "sync")]
892    pub fn read_vector_raw_into_sync(&self, idx: usize, out: &mut [u8]) -> io::Result<()> {
893        let vbs = self.vector_byte_size();
894        if out.len() < vbs {
895            return Err(io::Error::new(
896                io::ErrorKind::InvalidInput,
897                format!(
898                    "flat vector output is too short: need {vbs} bytes, got {}",
899                    out.len()
900                ),
901            ));
902        }
903        let bytes = self.read_vectors_batch_sync(idx, 1)?;
904        out[..vbs].copy_from_slice(bytes.as_slice());
905        Ok(())
906    }
907
908    /// Synchronous batch read of raw quantized bytes.
909    #[cfg(feature = "sync")]
910    pub fn read_vectors_batch_sync(
911        &self,
912        start_idx: usize,
913        count: usize,
914    ) -> io::Result<OwnedBytes> {
915        let (range, expected_len) = self.checked_vector_range(start_idx, count)?;
916        let bytes = self.handle.read_bytes_range_sync(range)?;
917        if bytes.len() != expected_len {
918            return Err(io::Error::new(
919                io::ErrorKind::UnexpectedEof,
920                format!(
921                    "flat vector batch read returned {} bytes, expected {expected_len}",
922                    bytes.len()
923                ),
924            ));
925        }
926        Ok(bytes)
927    }
928
929    /// Find flat index range for a given doc_id (non-allocating).
930    ///
931    /// Returns `(start_index, count)` — the flat vector index range for this doc_id.
932    /// Use `get_doc_id(start + i)` for `i in 0..count` to read individual entries.
933    /// More efficient than `flat_indexes_for_doc` as it avoids Vec allocation.
934    pub fn flat_indexes_for_doc_range(&self, doc_id: u32) -> (usize, usize) {
935        let n = self.num_vectors;
936        let start = {
937            let mut lo = 0usize;
938            let mut hi = n;
939            while lo < hi {
940                let mid = lo + (hi - lo) / 2;
941                if self.doc_id_at(mid) < doc_id {
942                    lo = mid + 1;
943                } else {
944                    hi = mid;
945                }
946            }
947            lo
948        };
949        let mut count = 0;
950        let mut i = start;
951        while i < n && self.doc_id_at(i) == doc_id {
952            count += 1;
953            i += 1;
954        }
955        (start, count)
956    }
957
958    /// Find flat indexes for a given doc_id via binary search on sorted doc_ids.
959    ///
960    /// doc_ids are sorted by (doc_id, ordinal) — segment builder adds docs
961    /// sequentially. Binary search runs directly on zero-copy mmap bytes.
962    ///
963    /// Returns `(start_index, entries)` where start_index is the flat vector index.
964    pub fn flat_indexes_for_doc(&self, doc_id: u32) -> (usize, Vec<(u32, u16)>) {
965        let n = self.num_vectors;
966        // Binary search: find first entry where doc_id >= target
967        let start = {
968            let mut lo = 0usize;
969            let mut hi = n;
970            while lo < hi {
971                let mid = lo + (hi - lo) / 2;
972                if self.doc_id_at(mid) < doc_id {
973                    lo = mid + 1;
974                } else {
975                    hi = mid;
976                }
977            }
978            lo
979        };
980        // Collect entries with matching doc_id
981        let mut entries = Vec::new();
982        let mut i = start;
983        while i < n {
984            let (did, ord) = self.get_doc_id(i);
985            if did != doc_id {
986                break;
987            }
988            entries.push((did, ord));
989            i += 1;
990        }
991        (start, entries)
992    }
993
994    /// Read doc_id at index from raw bytes (no ordinal).
995    #[inline]
996    fn doc_id_at(&self, idx: usize) -> u32 {
997        let off = idx * DOC_ID_ENTRY_SIZE;
998        let d = &self.doc_ids_bytes[off..];
999        u32::from_le_bytes([d[0], d[1], d[2], d[3]])
1000    }
1001
1002    /// Get doc_id and ordinal at index (parsed from zero-copy mmap bytes).
1003    #[inline]
1004    pub fn get_doc_id(&self, idx: usize) -> (u32, u16) {
1005        let off = idx * DOC_ID_ENTRY_SIZE;
1006        let d = &self.doc_ids_bytes[off..];
1007        let doc_id = u32::from_le_bytes([d[0], d[1], d[2], d[3]]);
1008        let ordinal = u16::from_le_bytes([d[4], d[5]]);
1009        (doc_id, ordinal)
1010    }
1011
1012    /// Bytes per vector in storage (cached).
1013    #[inline]
1014    pub fn vector_byte_size(&self) -> usize {
1015        self.vbs
1016    }
1017
1018    /// Number of distinct documents that have at least one vector in this field.
1019    #[inline]
1020    pub fn num_docs_with_vectors(&self) -> usize {
1021        self.num_docs_with_vectors
1022    }
1023
1024    /// Total byte length of raw vector data (for chunked merger streaming).
1025    pub fn vector_bytes_len(&self) -> u64 {
1026        self.vectors_byte_len
1027    }
1028
1029    /// Byte offset where vector data starts (for direct handle access in merger).
1030    pub fn vectors_byte_offset(&self) -> u64 {
1031        self.vectors_offset
1032    }
1033
1034    /// Access the underlying file handle (for chunked byte-range reads in merger).
1035    pub fn handle(&self) -> &FileHandle {
1036        &self.handle
1037    }
1038
1039    /// Estimated memory usage — doc_ids are mmap-backed (only Arc overhead).
1040    pub fn estimated_memory_bytes(&self) -> usize {
1041        size_of::<Self>() + size_of::<OwnedBytes>()
1042    }
1043}
1044
1045/// IVF-RaBitQ index data (codebook + cluster assignments)
1046///
1047/// Centroids are stored at the index level (`field_X_centroids.bin`),
1048/// not duplicated per segment.
1049#[derive(Debug, Clone, Serialize, Deserialize)]
1050pub struct IVFRaBitQIndexData {
1051    pub index: crate::structures::IVFRaBitQIndex,
1052    pub codebook: crate::structures::RaBitQCodebook,
1053}
1054
1055impl IVFRaBitQIndexData {
1056    pub fn to_bytes(&self) -> std::io::Result<Vec<u8>> {
1057        bincode::serde::encode_to_vec(self, bincode::config::standard())
1058            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
1059    }
1060
1061    pub fn from_bytes(data: &[u8]) -> std::io::Result<Self> {
1062        let value: Self = crate::structures::vector::decode_ann_bincode_exact(data, "IVF-RaBitQ")?;
1063        value
1064            .codebook
1065            .validate()
1066            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
1067        if value.index.config.default_nprobe == 0
1068            || value.index.config.dim != value.codebook.config.dim
1069            || value.index.codebook_version != value.codebook.version
1070        {
1071            return Err(std::io::Error::new(
1072                std::io::ErrorKind::InvalidData,
1073                "IVF-RaBitQ index/codebook metadata mismatch",
1074            ));
1075        }
1076        let mut total_vectors = 0usize;
1077        for (_, cluster) in value.index.clusters.iter() {
1078            if cluster.doc_ids.len() != cluster.ordinals.len()
1079                || cluster.doc_ids.len() != cluster.codes.len()
1080            {
1081                return Err(std::io::Error::new(
1082                    std::io::ErrorKind::InvalidData,
1083                    "IVF-RaBitQ cluster column lengths differ",
1084                ));
1085            }
1086            total_vectors = total_vectors
1087                .checked_add(cluster.codes.len())
1088                .ok_or_else(|| {
1089                    std::io::Error::new(
1090                        std::io::ErrorKind::InvalidData,
1091                        "IVF-RaBitQ vector count overflow",
1092                    )
1093                })?;
1094            for code in &cluster.codes {
1095                value
1096                    .codebook
1097                    .validate_vector(code)
1098                    .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
1099            }
1100        }
1101        if total_vectors != value.index.clusters.total_vectors {
1102            return Err(std::io::Error::new(
1103                std::io::ErrorKind::InvalidData,
1104                "IVF-RaBitQ total vector count is inconsistent",
1105            ));
1106        }
1107        Ok(value)
1108    }
1109}
1110
1111/// ScaNN index data (codebook + cluster assignments)
1112///
1113/// Centroids are stored at the index level (`field_X_centroids.bin`),
1114/// not duplicated per segment.
1115#[derive(Debug, Clone, Serialize, Deserialize)]
1116pub struct ScaNNIndexData {
1117    pub index: crate::structures::IVFPQIndex,
1118    pub codebook: crate::structures::PQCodebook,
1119}
1120
1121impl ScaNNIndexData {
1122    pub fn to_bytes(&self) -> std::io::Result<Vec<u8>> {
1123        bincode::serde::encode_to_vec(self, bincode::config::standard())
1124            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
1125    }
1126
1127    pub fn from_bytes(data: &[u8]) -> std::io::Result<Self> {
1128        let value: Self = crate::structures::vector::decode_ann_bincode_exact(data, "ScaNN")?;
1129        value
1130            .codebook
1131            .validate()
1132            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
1133        if value.index.config.default_nprobe == 0
1134            || value.index.config.dim != value.codebook.config.dim
1135            || value.index.codebook_version != value.codebook.version
1136        {
1137            return Err(std::io::Error::new(
1138                std::io::ErrorKind::InvalidData,
1139                "ScaNN index/codebook metadata mismatch",
1140            ));
1141        }
1142        let expected_codes = value.codebook.config.num_subspaces;
1143        let num_centroids = value.codebook.config.num_centroids;
1144        let mut total_vectors = 0usize;
1145        for (_, cluster) in value.index.clusters.iter() {
1146            if cluster.doc_ids.len() != cluster.ordinals.len()
1147                || cluster.doc_ids.len() != cluster.codes.len()
1148                || cluster.codes.iter().any(|code| {
1149                    code.codes.len() != expected_codes
1150                        || !code.norm.is_finite()
1151                        || code.norm < 0.0
1152                        || code
1153                            .codes
1154                            .iter()
1155                            .any(|&centroid| centroid as usize >= num_centroids)
1156                })
1157            {
1158                return Err(std::io::Error::new(
1159                    std::io::ErrorKind::InvalidData,
1160                    "ScaNN cluster columns or PQ code lengths are invalid",
1161                ));
1162            }
1163            total_vectors = total_vectors
1164                .checked_add(cluster.codes.len())
1165                .ok_or_else(|| {
1166                    std::io::Error::new(
1167                        std::io::ErrorKind::InvalidData,
1168                        "ScaNN vector count overflow",
1169                    )
1170                })?;
1171        }
1172        if total_vectors != value.index.clusters.total_vectors {
1173            return Err(std::io::Error::new(
1174                std::io::ErrorKind::InvalidData,
1175                "ScaNN total vector count is inconsistent",
1176            ));
1177        }
1178        Ok(value)
1179    }
1180}
1181
1182#[cfg(test)]
1183mod tests {
1184    use super::*;
1185
1186    #[test]
1187    fn dequantize_raw_accepts_valid_storage_formats() {
1188        let f32_values = [1.25f32, -2.5];
1189        let f32_bytes = unsafe {
1190            // Safety: viewing an initialized f32 array as bytes is always valid.
1191            std::slice::from_raw_parts(
1192                f32_values.as_ptr().cast::<u8>(),
1193                std::mem::size_of_val(&f32_values),
1194            )
1195        };
1196        let mut out = [0.0; 2];
1197        dequantize_raw(f32_bytes, DenseVectorQuantization::F32, 2, &mut out).unwrap();
1198        assert_eq!(out, f32_values);
1199
1200        let f16_values = [0x3c00u16, 0xc000u16]; // 1.0, -2.0
1201        let f16_bytes = unsafe {
1202            // Safety: viewing an initialized u16 array as bytes is always valid.
1203            std::slice::from_raw_parts(
1204                f16_values.as_ptr().cast::<u8>(),
1205                std::mem::size_of_val(&f16_values),
1206            )
1207        };
1208        dequantize_raw(f16_bytes, DenseVectorQuantization::F16, 2, &mut out).unwrap();
1209        assert_eq!(out, [1.0, -2.0]);
1210
1211        dequantize_raw(&[0, u8::MAX], DenseVectorQuantization::UInt8, 2, &mut out).unwrap();
1212        assert_eq!(out, [u8_to_f32(0), u8_to_f32(u8::MAX)]);
1213    }
1214
1215    #[test]
1216    fn dequantize_raw_rejects_invalid_lengths_and_binary_storage() {
1217        let mut out = [0.0; 2];
1218        assert_eq!(
1219            dequantize_raw(&[0; 7], DenseVectorQuantization::F32, 2, &mut out)
1220                .unwrap_err()
1221                .kind(),
1222            io::ErrorKind::InvalidData
1223        );
1224        assert_eq!(
1225            dequantize_raw(&[0; 8], DenseVectorQuantization::F32, 2, &mut out[..1])
1226                .unwrap_err()
1227                .kind(),
1228            io::ErrorKind::InvalidInput
1229        );
1230        assert_eq!(
1231            dequantize_raw(&[], DenseVectorQuantization::Binary, 0, &mut [])
1232                .unwrap_err()
1233                .kind(),
1234            io::ErrorKind::InvalidInput
1235        );
1236    }
1237
1238    #[test]
1239    fn dequantize_raw_rejects_misaligned_typed_storage() {
1240        let storage = [0u8; 9];
1241        let offset = if (storage.as_ptr() as usize).is_multiple_of(4) {
1242            1
1243        } else {
1244            0
1245        };
1246        let raw = &storage[offset..offset + 8];
1247        assert!(!(raw.as_ptr() as usize).is_multiple_of(4));
1248
1249        let mut out = [0.0; 2];
1250        assert_eq!(
1251            dequantize_raw(raw, DenseVectorQuantization::F32, 2, &mut out)
1252                .unwrap_err()
1253                .kind(),
1254            io::ErrorKind::InvalidData
1255        );
1256    }
1257
1258    #[test]
1259    fn flat_vector_writers_reject_inconsistent_shapes_and_doc_maps() {
1260        let mut encoded = Vec::new();
1261        assert!(
1262            FlatVectorData::serialize_binary_from_flat_streaming(
1263                0,
1264                &[],
1265                &[],
1266                DenseVectorQuantization::F32,
1267                &mut encoded,
1268            )
1269            .is_err()
1270        );
1271        assert!(encoded.is_empty());
1272
1273        assert!(
1274            FlatVectorData::serialize_binary_from_flat_streaming(
1275                2,
1276                &[1.0],
1277                &[(0, 0)],
1278                DenseVectorQuantization::F32,
1279                &mut encoded,
1280            )
1281            .is_err()
1282        );
1283        assert!(encoded.is_empty());
1284
1285        assert!(
1286            FlatVectorData::serialize_binary_from_flat_streaming(
1287                1,
1288                &[1.0],
1289                &[(0, 0)],
1290                DenseVectorQuantization::Binary,
1291                &mut encoded,
1292            )
1293            .is_err()
1294        );
1295        assert!(encoded.is_empty());
1296
1297        assert!(
1298            FlatVectorData::serialize_binary_from_flat_streaming(
1299                1,
1300                &[1.0, 2.0],
1301                &[(1, 0), (0, 0)],
1302                DenseVectorQuantization::F32,
1303                &mut encoded,
1304            )
1305            .is_err()
1306        );
1307        assert!(encoded.is_empty());
1308
1309        assert!(
1310            FlatVectorData::serialize_binary_from_flat_streaming(
1311                1,
1312                &[1.0, 2.0],
1313                &[(0, 0), (0, 0)],
1314                DenseVectorQuantization::F32,
1315                &mut encoded,
1316            )
1317            .is_err()
1318        );
1319        assert!(encoded.is_empty());
1320
1321        assert!(
1322            FlatVectorData::serialize_binary_from_bits_streaming(7, &[0], &[(0, 0)], &mut encoded,)
1323                .is_err()
1324        );
1325        assert!(encoded.is_empty());
1326
1327        assert!(
1328            FlatVectorData::serialize_binary_from_bits_streaming(8, &[], &[(0, 0)], &mut encoded,)
1329                .is_err()
1330        );
1331        assert!(encoded.is_empty());
1332
1333        let vectors = [1.0f32, 2.0, 3.0, 4.0];
1334        let doc_ids = [(0, 0), (1, 0)];
1335        FlatVectorData::serialize_binary_from_flat_streaming(
1336            2,
1337            &vectors,
1338            &doc_ids,
1339            DenseVectorQuantization::F32,
1340            &mut encoded,
1341        )
1342        .unwrap();
1343        assert_eq!(
1344            encoded.len(),
1345            FlatVectorData::serialized_binary_size(2, 2, DenseVectorQuantization::F32).unwrap()
1346        );
1347    }
1348
1349    fn encoded_two_vector_payload() -> Vec<u8> {
1350        let mut encoded = Vec::new();
1351        FlatVectorData::serialize_binary_from_flat_streaming(
1352            2,
1353            &[1.0, 2.0, 3.0, 4.0],
1354            &[(0, 0), (1, 0)],
1355            DenseVectorQuantization::F32,
1356            &mut encoded,
1357        )
1358        .unwrap();
1359        encoded
1360    }
1361
1362    #[tokio::test]
1363    async fn flat_vector_open_rejects_corrupt_layout_and_doc_map() {
1364        let valid = encoded_two_vector_payload();
1365
1366        let mut multi_value = Vec::new();
1367        FlatVectorData::serialize_binary_from_flat_streaming(
1368            1,
1369            &[1.0, 2.0, 3.0],
1370            &[(0, 0), (0, 1), (2, 0)],
1371            DenseVectorQuantization::F32,
1372            &mut multi_value,
1373        )
1374        .unwrap();
1375        let multi_value = LazyFlatVectorData::open_with_doc_limit(
1376            FileHandle::from_bytes(OwnedBytes::new(multi_value)),
1377            Some(3),
1378        )
1379        .await
1380        .unwrap();
1381        assert_eq!(multi_value.num_docs_with_vectors(), 2);
1382
1383        let mut trailing = valid.clone();
1384        trailing.push(0);
1385        assert!(
1386            LazyFlatVectorData::open(FileHandle::from_bytes(OwnedBytes::new(trailing)))
1387                .await
1388                .is_err()
1389        );
1390
1391        let mut truncated = valid.clone();
1392        truncated.pop();
1393        assert!(
1394            LazyFlatVectorData::open(FileHandle::from_bytes(OwnedBytes::new(truncated)))
1395                .await
1396                .is_err()
1397        );
1398
1399        let mut reserved = valid.clone();
1400        reserved[13] = 1;
1401        assert!(
1402            LazyFlatVectorData::open(FileHandle::from_bytes(OwnedBytes::new(reserved)))
1403                .await
1404                .is_err()
1405        );
1406
1407        let doc_map_start = FLAT_BINARY_HEADER_SIZE + 2 * 2 * size_of::<f32>();
1408        let mut unsorted = valid.clone();
1409        let (first, second) = unsorted[doc_map_start..doc_map_start + 2 * DOC_ID_ENTRY_SIZE]
1410            .split_at_mut(DOC_ID_ENTRY_SIZE);
1411        first.swap_with_slice(second);
1412        assert!(
1413            LazyFlatVectorData::open(FileHandle::from_bytes(OwnedBytes::new(unsorted)))
1414                .await
1415                .is_err()
1416        );
1417
1418        let mut duplicate = valid.clone();
1419        duplicate.copy_within(
1420            doc_map_start..doc_map_start + DOC_ID_ENTRY_SIZE,
1421            doc_map_start + DOC_ID_ENTRY_SIZE,
1422        );
1423        assert!(
1424            LazyFlatVectorData::open(FileHandle::from_bytes(OwnedBytes::new(duplicate)))
1425                .await
1426                .is_err()
1427        );
1428
1429        assert!(
1430            LazyFlatVectorData::open_with_doc_limit(
1431                FileHandle::from_bytes(OwnedBytes::new(valid)),
1432                Some(1),
1433            )
1434            .await
1435            .is_err()
1436        );
1437
1438        let mut invalid_binary = Vec::new();
1439        FlatVectorData::serialize_binary_from_bits_streaming(
1440            8,
1441            &[0],
1442            &[(0, 0)],
1443            &mut invalid_binary,
1444        )
1445        .unwrap();
1446        invalid_binary[4..8].copy_from_slice(&7u32.to_le_bytes());
1447        assert!(
1448            LazyFlatVectorData::open(FileHandle::from_bytes(OwnedBytes::new(invalid_binary)))
1449                .await
1450                .is_err()
1451        );
1452    }
1453
1454    #[tokio::test]
1455    async fn flat_vector_batch_and_dequantized_reads_are_checked() {
1456        let flat = LazyFlatVectorData::open(FileHandle::from_bytes(OwnedBytes::new(
1457            encoded_two_vector_payload(),
1458        )))
1459        .await
1460        .unwrap();
1461
1462        assert_eq!(flat.read_vectors_batch(0, 2).await.unwrap().len(), 16);
1463        assert_eq!(flat.read_vectors_batch(2, 0).await.unwrap().len(), 0);
1464        assert!(flat.read_vectors_batch(1, 2).await.is_err());
1465        assert!(flat.read_vectors_batch(usize::MAX, 1).await.is_err());
1466        assert!(flat.read_vectors_batch(0, usize::MAX).await.is_err());
1467
1468        let mut values = [0.0; 2];
1469        flat.read_vector_into(1, &mut values).await.unwrap();
1470        assert_eq!(values, [3.0, 4.0]);
1471        assert!(flat.read_vector_into(2, &mut values).await.is_err());
1472        assert!(flat.read_vector_into(0, &mut values[..1]).await.is_err());
1473
1474        #[cfg(feature = "sync")]
1475        {
1476            assert_eq!(flat.read_vectors_batch_sync(0, 2).unwrap().len(), 16);
1477            assert!(flat.read_vectors_batch_sync(1, 2).is_err());
1478            assert!(flat.read_vectors_batch_sync(usize::MAX, 1).is_err());
1479            let mut too_short = [0; 7];
1480            assert!(flat.read_vector_raw_into_sync(0, &mut too_short).is_err());
1481        }
1482    }
1483
1484    #[cfg(not(target_arch = "wasm32"))]
1485    #[tokio::test]
1486    async fn flat_vector_reads_reject_short_lazy_range_results() {
1487        let payload = std::sync::Arc::new(encoded_two_vector_payload());
1488        let payload_len = payload.len() as u64;
1489        let read_payload = std::sync::Arc::clone(&payload);
1490        let read_fn: crate::directories::RangeReadFn = std::sync::Arc::new(move |range| {
1491            let payload = std::sync::Arc::clone(&read_payload);
1492            Box::pin(async move {
1493                let start = usize::try_from(range.start).unwrap();
1494                let mut end = usize::try_from(range.end).unwrap();
1495                // Header and doc-map reads are exact, allowing open to finish.
1496                // Raw vector reads deliberately violate the range-read contract.
1497                if range.start == FLAT_BINARY_HEADER_SIZE as u64 {
1498                    end -= 1;
1499                }
1500                Ok(OwnedBytes::new(payload[start..end].to_vec()))
1501            })
1502        });
1503        let flat = LazyFlatVectorData::open(FileHandle::lazy(payload_len, read_fn))
1504            .await
1505            .unwrap();
1506
1507        let error = flat.read_vectors_batch(0, 1).await.unwrap_err();
1508        assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
1509        let mut raw = [0; 8];
1510        let error = flat.read_vector_raw_into(0, &mut raw).await.unwrap_err();
1511        assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
1512    }
1513
1514    #[test]
1515    fn ann_bincode_decoders_reject_trailing_and_invalid_semantics() {
1516        let rabitq_codebook =
1517            crate::structures::RaBitQCodebook::new(crate::structures::RaBitQConfig::new(8));
1518        let rabitq = IVFRaBitQIndexData {
1519            index: crate::structures::IVFRaBitQIndex::new(
1520                crate::structures::IVFRaBitQConfig::new(8),
1521                1,
1522                rabitq_codebook.version,
1523            ),
1524            codebook: rabitq_codebook,
1525        };
1526        let mut rabitq_bytes = rabitq.to_bytes().unwrap();
1527        assert!(IVFRaBitQIndexData::from_bytes(&rabitq_bytes).is_ok());
1528        let mut invalid_rabitq = rabitq.clone();
1529        invalid_rabitq.index.config.default_nprobe = 0;
1530        assert!(IVFRaBitQIndexData::from_bytes(&invalid_rabitq.to_bytes().unwrap()).is_err());
1531        invalid_rabitq.index.config.default_nprobe = 1;
1532        invalid_rabitq.codebook.config.query_bits = 0;
1533        assert!(IVFRaBitQIndexData::from_bytes(&invalid_rabitq.to_bytes().unwrap()).is_err());
1534        rabitq_bytes.push(0);
1535        assert!(IVFRaBitQIndexData::from_bytes(&rabitq_bytes).is_err());
1536
1537        let pq_config = crate::structures::PQConfig::new(2);
1538        let pq_codebook = crate::structures::PQCodebook {
1539            centroids: vec![
1540                0.0;
1541                pq_config.num_subspaces
1542                    * pq_config.num_centroids
1543                    * pq_config.dims_per_block
1544            ],
1545            rotation_matrix: None,
1546            centroid_norms: None,
1547            version: 2,
1548            config: pq_config,
1549        };
1550        let scann = ScaNNIndexData {
1551            index: crate::structures::IVFPQIndex::new(
1552                crate::structures::IVFPQConfig::new(2),
1553                1,
1554                pq_codebook.version,
1555            ),
1556            codebook: pq_codebook,
1557        };
1558        let mut scann_bytes = scann.to_bytes().unwrap();
1559        assert!(ScaNNIndexData::from_bytes(&scann_bytes).is_ok());
1560        let mut invalid_scann = scann.clone();
1561        invalid_scann.index.config.default_nprobe = 0;
1562        assert!(ScaNNIndexData::from_bytes(&invalid_scann.to_bytes().unwrap()).is_err());
1563        invalid_scann.index.config.default_nprobe = 1;
1564        invalid_scann.codebook.config.aniso_eta = f32::NAN;
1565        assert!(ScaNNIndexData::from_bytes(&invalid_scann.to_bytes().unwrap()).is_err());
1566        scann_bytes.push(0);
1567        assert!(ScaNNIndexData::from_bytes(&scann_bytes).is_err());
1568    }
1569
1570    #[tokio::test]
1571    async fn vector_prefix_reads_are_checked_and_do_not_fetch_the_tail() {
1572        let vectors = [1.0f32, 2.0, 3.0, 4.0];
1573        let doc_ids = [(0, 0), (1, 0)];
1574        let mut encoded = Vec::new();
1575        FlatVectorData::serialize_binary_from_flat_streaming(
1576            2,
1577            &vectors,
1578            &doc_ids,
1579            DenseVectorQuantization::F32,
1580            &mut encoded,
1581        )
1582        .unwrap();
1583        let flat = LazyFlatVectorData::open(FileHandle::from_bytes(OwnedBytes::new(encoded)))
1584            .await
1585            .unwrap();
1586
1587        let mut prefix = [0xa5; 8];
1588        flat.read_vector_prefix_raw_into(1, 4, &mut prefix)
1589            .await
1590            .unwrap();
1591        assert_eq!(&prefix[..4], &3.0f32.to_ne_bytes());
1592        assert_eq!(&prefix[4..], &[0xa5; 4]);
1593
1594        let mut full = [0; 8];
1595        flat.read_vector_raw_into(1, &mut full).await.unwrap();
1596        assert_eq!(&full[..4], &3.0f32.to_ne_bytes());
1597        assert_eq!(&full[4..], &4.0f32.to_ne_bytes());
1598
1599        assert!(
1600            flat.read_vector_prefix_raw_into(2, 4, &mut prefix)
1601                .await
1602                .is_err()
1603        );
1604        assert!(
1605            flat.read_vector_prefix_raw_into(0, 9, &mut prefix)
1606                .await
1607                .is_err()
1608        );
1609        assert!(
1610            flat.read_vector_prefix_raw_into(0, 4, &mut prefix[..3])
1611                .await
1612                .is_err()
1613        );
1614    }
1615}