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