1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
13use parking_lot::RwLock;
14use rustc_hash::FxHashMap;
15use std::io::{self, Write};
16use std::sync::Arc;
17
18use crate::DocId;
19use crate::compression::CompressionDict;
20#[cfg(feature = "native")]
21use crate::compression::CompressionLevel;
22use crate::directories::FileHandle;
23use crate::dsl::{Document, Schema};
24
25const STORE_MAGIC: u32 = 0x53544F52; const STORE_VERSION: u32 = 2; pub const STORE_BLOCK_SIZE: usize = 16 * 1024;
33
34pub const DEFAULT_DICT_SIZE: usize = 4 * 1024;
36
37const MAX_STORE_BLOCK_BYTES: usize = 64 * 1024 * 1024;
41const MAX_STORE_DICTIONARY_BYTES: u64 = 16 * 1024 * 1024;
42
43#[cfg(feature = "native")]
45const DEFAULT_COMPRESSION_LEVEL: CompressionLevel = CompressionLevel(3);
46
47fn write_store_index_and_footer(
51 writer: &mut (impl Write + ?Sized),
52 index: &[StoreBlockIndex],
53 data_end_offset: u64,
54 dict_offset: u64,
55 num_docs: u32,
56 has_dict: bool,
57) -> io::Result<()> {
58 writer.write_u32::<LittleEndian>(u32::try_from(index.len()).map_err(|_| {
59 io::Error::new(
60 io::ErrorKind::InvalidInput,
61 "too many document store blocks",
62 )
63 })?)?;
64 for entry in index {
65 writer.write_u32::<LittleEndian>(entry.first_doc_id)?;
66 writer.write_u64::<LittleEndian>(entry.offset)?;
67 writer.write_u32::<LittleEndian>(entry.length)?;
68 writer.write_u32::<LittleEndian>(entry.num_docs)?;
69 }
70 writer.write_u64::<LittleEndian>(data_end_offset)?;
71 writer.write_u64::<LittleEndian>(dict_offset)?;
72 writer.write_u32::<LittleEndian>(num_docs)?;
73 writer.write_u32::<LittleEndian>(if has_dict { 1 } else { 0 })?;
74 writer.write_u32::<LittleEndian>(STORE_VERSION)?;
75 writer.write_u32::<LittleEndian>(STORE_MAGIC)?;
76 Ok(())
77}
78
79pub fn serialize_document(doc: &Document, schema: &Schema) -> io::Result<Vec<u8>> {
91 let mut buf = Vec::with_capacity(256);
92 serialize_document_into(doc, schema, &mut buf)?;
93 Ok(buf)
94}
95
96pub fn serialize_document_into(
99 doc: &Document,
100 schema: &Schema,
101 buf: &mut Vec<u8>,
102) -> io::Result<()> {
103 use crate::dsl::FieldValue;
104
105 buf.clear();
106
107 let is_stored = |field: &crate::dsl::Field, value: &FieldValue| -> bool {
109 if matches!(
111 value,
112 FieldValue::DenseVector(_) | FieldValue::BinaryDenseVector(_)
113 ) {
114 return false;
115 }
116 schema.get_field_entry(*field).is_some_and(|e| e.stored)
117 };
118
119 let stored_count = doc
120 .field_values()
121 .iter()
122 .filter(|(field, value)| is_stored(field, value))
123 .count();
124
125 buf.write_u16::<LittleEndian>(
126 u16::try_from(stored_count)
127 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "too many stored fields"))?,
128 )?;
129
130 for (field, value) in doc.field_values().iter().filter(|(f, v)| is_stored(f, v)) {
131 buf.write_u16::<LittleEndian>(u16::try_from(field.0).map_err(|_| {
132 io::Error::new(io::ErrorKind::InvalidInput, "stored field id exceeds u16")
133 })?)?;
134 match value {
135 FieldValue::Text(s) => {
136 buf.push(0);
137 let bytes = s.as_bytes();
138 buf.write_u32::<LittleEndian>(u32::try_from(bytes.len()).map_err(|_| {
139 io::Error::new(io::ErrorKind::InvalidInput, "stored text is too large")
140 })?)?;
141 buf.extend_from_slice(bytes);
142 }
143 FieldValue::U64(v) => {
144 buf.push(1);
145 buf.write_u64::<LittleEndian>(*v)?;
146 }
147 FieldValue::I64(v) => {
148 buf.push(2);
149 buf.write_i64::<LittleEndian>(*v)?;
150 }
151 FieldValue::F64(v) => {
152 buf.push(3);
153 buf.write_f64::<LittleEndian>(*v)?;
154 }
155 FieldValue::Bytes(b) => {
156 buf.push(4);
157 buf.write_u32::<LittleEndian>(u32::try_from(b.len()).map_err(|_| {
158 io::Error::new(
159 io::ErrorKind::InvalidInput,
160 "stored byte field is too large",
161 )
162 })?)?;
163 buf.extend_from_slice(b);
164 }
165 FieldValue::SparseVector(entries) => {
166 buf.push(5);
167 buf.write_u32::<LittleEndian>(u32::try_from(entries.len()).map_err(|_| {
168 io::Error::new(
169 io::ErrorKind::InvalidInput,
170 "stored sparse vector is too large",
171 )
172 })?)?;
173 for (idx, val) in entries {
174 buf.write_u32::<LittleEndian>(*idx)?;
175 buf.write_f32::<LittleEndian>(*val)?;
176 }
177 }
178 FieldValue::DenseVector(values) => {
179 buf.push(6);
180 buf.write_u32::<LittleEndian>(u32::try_from(values.len()).map_err(|_| {
181 io::Error::new(
182 io::ErrorKind::InvalidInput,
183 "stored dense vector is too large",
184 )
185 })?)?;
186 let byte_slice = unsafe {
188 std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * 4)
189 };
190 buf.extend_from_slice(byte_slice);
191 }
192 FieldValue::Json(v) => {
193 buf.push(7);
194 let json_bytes = serde_json::to_vec(v)
195 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
196 buf.write_u32::<LittleEndian>(u32::try_from(json_bytes.len()).map_err(|_| {
197 io::Error::new(io::ErrorKind::InvalidInput, "stored JSON is too large")
198 })?)?;
199 buf.extend_from_slice(&json_bytes);
200 }
201 FieldValue::BinaryDenseVector(b) => {
202 buf.push(8);
203 buf.write_u32::<LittleEndian>(u32::try_from(b.len()).map_err(|_| {
204 io::Error::new(
205 io::ErrorKind::InvalidInput,
206 "stored binary dense vector is too large",
207 )
208 })?)?;
209 buf.extend_from_slice(b);
210 }
211 }
212 }
213
214 Ok(())
215}
216
217#[cfg(feature = "native")]
219struct CompressedBlock {
220 seq: usize,
221 first_doc_id: DocId,
222 num_docs: u32,
223 compressed: Vec<u8>,
224}
225
226#[cfg(feature = "native")]
234pub struct EagerParallelStoreWriter<'a> {
235 writer: &'a mut dyn Write,
236 block_buffer: Vec<u8>,
237 serialize_buf: Vec<u8>,
239 compressed_blocks: Vec<CompressedBlock>,
241 pending_handles: Vec<std::thread::JoinHandle<CompressedBlock>>,
243 next_seq: usize,
244 next_doc_id: DocId,
245 block_first_doc: DocId,
246 dict: Option<Arc<CompressionDict>>,
247 compression_level: CompressionLevel,
248}
249
250#[cfg(feature = "native")]
251impl<'a> EagerParallelStoreWriter<'a> {
252 pub fn new(writer: &'a mut dyn Write, _num_threads: usize) -> Self {
254 Self::with_compression_level(writer, _num_threads, DEFAULT_COMPRESSION_LEVEL)
255 }
256
257 pub fn with_compression_level(
259 writer: &'a mut dyn Write,
260 _num_threads: usize,
261 compression_level: CompressionLevel,
262 ) -> Self {
263 Self {
264 writer,
265 block_buffer: Vec::with_capacity(STORE_BLOCK_SIZE),
266 serialize_buf: Vec::with_capacity(512),
267 compressed_blocks: Vec::new(),
268 pending_handles: Vec::new(),
269 next_seq: 0,
270 next_doc_id: 0,
271 block_first_doc: 0,
272 dict: None,
273 compression_level,
274 }
275 }
276
277 pub fn with_dict(
279 writer: &'a mut dyn Write,
280 dict: CompressionDict,
281 _num_threads: usize,
282 ) -> Self {
283 Self::with_dict_and_level(writer, dict, _num_threads, DEFAULT_COMPRESSION_LEVEL)
284 }
285
286 pub fn with_dict_and_level(
288 writer: &'a mut dyn Write,
289 dict: CompressionDict,
290 _num_threads: usize,
291 compression_level: CompressionLevel,
292 ) -> Self {
293 Self {
294 writer,
295 block_buffer: Vec::with_capacity(STORE_BLOCK_SIZE),
296 serialize_buf: Vec::with_capacity(512),
297 compressed_blocks: Vec::new(),
298 pending_handles: Vec::new(),
299 next_seq: 0,
300 next_doc_id: 0,
301 block_first_doc: 0,
302 dict: Some(Arc::new(dict)),
303 compression_level,
304 }
305 }
306
307 pub fn store(&mut self, doc: &Document, schema: &Schema) -> io::Result<DocId> {
308 serialize_document_into(doc, schema, &mut self.serialize_buf)?;
309 if self.serialize_buf.len() > MAX_STORE_BLOCK_BYTES.saturating_sub(4) {
310 return Err(io::Error::new(
311 io::ErrorKind::InvalidInput,
312 "serialized document exceeds store block limit",
313 ));
314 }
315 let doc_id = self.next_doc_id;
316 self.next_doc_id = self
317 .next_doc_id
318 .checked_add(1)
319 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "document id overflow"))?;
320 self.block_buffer
321 .write_u32::<LittleEndian>(self.serialize_buf.len() as u32)?;
322 self.block_buffer.extend_from_slice(&self.serialize_buf);
323 if self.block_buffer.len() >= STORE_BLOCK_SIZE {
324 self.spawn_compression();
325 }
326 Ok(doc_id)
327 }
328
329 pub fn store_raw(&mut self, doc_bytes: &[u8]) -> io::Result<DocId> {
331 if doc_bytes.len() > MAX_STORE_BLOCK_BYTES.saturating_sub(4) {
332 return Err(io::Error::new(
333 io::ErrorKind::InvalidInput,
334 "serialized document exceeds store block limit",
335 ));
336 }
337 let doc_id = self.next_doc_id;
338 self.next_doc_id = self
339 .next_doc_id
340 .checked_add(1)
341 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "document id overflow"))?;
342
343 self.block_buffer
344 .write_u32::<LittleEndian>(doc_bytes.len() as u32)?;
345 self.block_buffer.extend_from_slice(doc_bytes);
346
347 if self.block_buffer.len() >= STORE_BLOCK_SIZE {
348 self.spawn_compression();
349 }
350
351 Ok(doc_id)
352 }
353
354 fn spawn_compression(&mut self) {
356 if self.block_buffer.is_empty() {
357 return;
358 }
359
360 let num_docs = self.next_doc_id - self.block_first_doc;
361 let data = std::mem::replace(&mut self.block_buffer, Vec::with_capacity(STORE_BLOCK_SIZE));
362 let seq = self.next_seq;
363 let first_doc_id = self.block_first_doc;
364 let dict = self.dict.clone();
365
366 self.next_seq += 1;
367 self.block_first_doc = self.next_doc_id;
368
369 let level = self.compression_level;
371 let handle = std::thread::spawn(move || {
372 let compressed = if let Some(ref d) = dict {
373 crate::compression::compress_with_dict(&data, level, d).expect("compression failed")
374 } else {
375 crate::compression::compress(&data, level).expect("compression failed")
376 };
377
378 CompressedBlock {
379 seq,
380 first_doc_id,
381 num_docs,
382 compressed,
383 }
384 });
385
386 self.pending_handles.push(handle);
387 }
388
389 fn collect_completed(&mut self) {
391 let mut remaining = Vec::new();
392 for handle in self.pending_handles.drain(..) {
393 if handle.is_finished() {
394 match handle.join() {
395 Ok(block) => self.compressed_blocks.push(block),
396 Err(payload) => std::panic::resume_unwind(payload),
397 }
398 } else {
399 remaining.push(handle);
400 }
401 }
402 self.pending_handles = remaining;
403 }
404
405 pub fn finish(mut self) -> io::Result<u32> {
406 self.spawn_compression();
408
409 self.collect_completed();
411
412 for handle in self.pending_handles.drain(..) {
414 match handle.join() {
415 Ok(block) => self.compressed_blocks.push(block),
416 Err(payload) => std::panic::resume_unwind(payload),
417 }
418 }
419
420 if self.compressed_blocks.is_empty() {
421 write_store_index_and_footer(&mut self.writer, &[], 0, 0, 0, false)?;
422 return Ok(0);
423 }
424
425 self.compressed_blocks.sort_by_key(|b| b.seq);
427
428 let mut index = Vec::with_capacity(self.compressed_blocks.len());
430 let mut current_offset = 0u64;
431
432 for block in &self.compressed_blocks {
433 index.push(StoreBlockIndex {
434 first_doc_id: block.first_doc_id,
435 offset: current_offset,
436 length: u32::try_from(block.compressed.len()).map_err(|_| {
437 io::Error::new(
438 io::ErrorKind::InvalidData,
439 "compressed store block too large",
440 )
441 })?,
442 num_docs: block.num_docs,
443 });
444
445 self.writer.write_all(&block.compressed)?;
446 current_offset = current_offset
447 .checked_add(block.compressed.len() as u64)
448 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "store size overflow"))?;
449 }
450
451 let data_end_offset = current_offset;
452
453 let dict_offset = if let Some(ref dict) = self.dict {
455 let offset = current_offset;
456 let dict_bytes = dict.as_bytes();
457 self.writer
458 .write_u32::<LittleEndian>(dict_bytes.len() as u32)?;
459 self.writer.write_all(dict_bytes)?;
460 Some(offset)
461 } else {
462 None
463 };
464
465 write_store_index_and_footer(
467 &mut self.writer,
468 &index,
469 data_end_offset,
470 dict_offset.unwrap_or(0),
471 self.next_doc_id,
472 self.dict.is_some(),
473 )?;
474
475 Ok(self.next_doc_id)
476 }
477}
478
479#[derive(Debug, Clone)]
481pub(crate) struct StoreBlockIndex {
482 pub(crate) first_doc_id: DocId,
483 pub(crate) offset: u64,
484 pub(crate) length: u32,
485 pub(crate) num_docs: u32,
486}
487
488pub struct AsyncStoreReader {
490 data_slice: FileHandle,
492 index: Vec<StoreBlockIndex>,
494 num_docs: u32,
495 dict: Option<CompressionDict>,
497 cache: RwLock<StoreBlockCache>,
499}
500
501struct CachedBlock {
507 data: Vec<u8>,
508 offsets: Vec<u32>,
511}
512
513impl CachedBlock {
514 fn build(data: Vec<u8>, num_docs: u32) -> io::Result<Self> {
515 if num_docs as usize > data.len() / 4 {
516 return Err(io::Error::new(
517 io::ErrorKind::InvalidData,
518 "store block document count exceeds block length",
519 ));
520 }
521 let mut offsets = Vec::new();
522 offsets.try_reserve_exact(num_docs as usize).map_err(|_| {
523 io::Error::new(
524 io::ErrorKind::InvalidData,
525 "store block has too many documents",
526 )
527 })?;
528 let mut pos = 0usize;
529 for _ in 0..num_docs {
530 let length_end = pos.checked_add(4).ok_or_else(|| {
531 io::Error::new(io::ErrorKind::InvalidData, "store block offset overflow")
532 })?;
533 if length_end > data.len() {
534 return Err(io::Error::new(
535 io::ErrorKind::InvalidData,
536 "truncated block while building offset table",
537 ));
538 }
539 offsets.push(u32::try_from(pos).map_err(|_| {
540 io::Error::new(io::ErrorKind::InvalidData, "store block offset exceeds u32")
541 })?);
542 let doc_len =
543 u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
544 as usize;
545 pos = length_end.checked_add(doc_len).ok_or_else(|| {
546 io::Error::new(io::ErrorKind::InvalidData, "store document length overflow")
547 })?;
548 if pos > data.len() {
549 return Err(io::Error::new(
550 io::ErrorKind::UnexpectedEof,
551 "store document is truncated",
552 ));
553 }
554 }
555 if pos != data.len() {
556 return Err(io::Error::new(
557 io::ErrorKind::InvalidData,
558 "store block contains trailing data",
559 ));
560 }
561 Ok(Self { data, offsets })
562 }
563
564 fn doc_bytes(&self, doc_offset_in_block: u32) -> io::Result<&[u8]> {
566 let idx = doc_offset_in_block as usize;
567 if idx >= self.offsets.len() {
568 return Err(io::Error::new(
569 io::ErrorKind::InvalidData,
570 "doc offset out of range",
571 ));
572 }
573 let start = self.offsets[idx] as usize;
574 let data_start = start.checked_add(4).ok_or_else(|| {
575 io::Error::new(io::ErrorKind::InvalidData, "store document offset overflow")
576 })?;
577 if data_start > self.data.len() {
578 return Err(io::Error::new(
579 io::ErrorKind::InvalidData,
580 "truncated doc length",
581 ));
582 }
583 let doc_len = u32::from_le_bytes([
584 self.data[start],
585 self.data[start + 1],
586 self.data[start + 2],
587 self.data[start + 3],
588 ]) as usize;
589 let data_end = data_start.checked_add(doc_len).ok_or_else(|| {
590 io::Error::new(io::ErrorKind::InvalidData, "store document length overflow")
591 })?;
592 if data_end > self.data.len() {
593 return Err(io::Error::new(
594 io::ErrorKind::InvalidData,
595 "doc data overflow",
596 ));
597 }
598 Ok(&self.data[data_start..data_end])
599 }
600
601 #[inline]
602 fn retained_bytes(&self) -> usize {
603 self.data.capacity() + self.offsets.capacity() * std::mem::size_of::<u32>()
604 }
605}
606
607struct StoreBlockCache {
613 blocks: FxHashMap<DocId, Arc<CachedBlock>>,
614 lru_order: std::collections::VecDeque<DocId>,
615 max_blocks: usize,
616}
617
618impl StoreBlockCache {
619 fn new(max_blocks: usize) -> Self {
620 Self {
621 blocks: FxHashMap::default(),
622 lru_order: std::collections::VecDeque::with_capacity(max_blocks),
623 max_blocks,
624 }
625 }
626
627 fn peek(&self, first_doc_id: DocId) -> Option<Arc<CachedBlock>> {
629 self.blocks.get(&first_doc_id).map(Arc::clone)
630 }
631
632 fn get(&mut self, first_doc_id: DocId) -> Option<Arc<CachedBlock>> {
633 let block = self.blocks.get(&first_doc_id).map(Arc::clone)?;
634 self.promote(first_doc_id);
635 Some(block)
636 }
637
638 fn insert(&mut self, first_doc_id: DocId, block: Arc<CachedBlock>) {
639 if self.max_blocks == 0 {
640 return;
641 }
642 if self.blocks.contains_key(&first_doc_id) {
643 self.promote(first_doc_id);
644 return;
645 }
646 while self.blocks.len() >= self.max_blocks {
647 if let Some(evict) = self.lru_order.pop_front() {
648 self.blocks.remove(&evict);
649 } else {
650 break;
651 }
652 }
653 self.blocks.insert(first_doc_id, block);
654 self.lru_order.push_back(first_doc_id);
655 }
656
657 fn promote(&mut self, key: DocId) {
658 if let Some(pos) = self.lru_order.iter().position(|&k| k == key) {
659 self.lru_order.remove(pos);
660 self.lru_order.push_back(key);
661 }
662 }
663}
664
665impl AsyncStoreReader {
666 pub async fn open(file_handle: FileHandle, cache_blocks: usize) -> io::Result<Self> {
669 let file_len = file_handle.len();
670 if file_len < 32 {
672 return Err(io::Error::new(
673 io::ErrorKind::InvalidData,
674 "Store too small",
675 ));
676 }
677
678 let footer = file_handle
680 .read_bytes_range(file_len - 32..file_len)
681 .await?;
682 let mut reader = footer.as_slice();
683 let data_end_offset = reader.read_u64::<LittleEndian>()?;
684 let dict_offset = reader.read_u64::<LittleEndian>()?;
685 let num_docs = reader.read_u32::<LittleEndian>()?;
686 let has_dict = reader.read_u32::<LittleEndian>()? != 0;
687 let version = reader.read_u32::<LittleEndian>()?;
688 let magic = reader.read_u32::<LittleEndian>()?;
689
690 if magic != STORE_MAGIC {
691 return Err(io::Error::new(
692 io::ErrorKind::InvalidData,
693 "Invalid store magic",
694 ));
695 }
696 if version != STORE_VERSION {
697 return Err(io::Error::new(
698 io::ErrorKind::InvalidData,
699 format!("Unsupported store version: {}", version),
700 ));
701 }
702
703 let index_end = file_len - 32;
704 if data_end_offset > index_end {
705 return Err(io::Error::new(
706 io::ErrorKind::InvalidData,
707 "store data section extends past its footer",
708 ));
709 }
710
711 let (dict, index_start) = if has_dict {
713 if dict_offset < data_end_offset || dict_offset >= index_end {
714 return Err(io::Error::new(
715 io::ErrorKind::InvalidData,
716 "store dictionary offset is out of bounds",
717 ));
718 }
719 let dict_start = dict_offset;
720 let dict_header_end = dict_start.checked_add(4).ok_or_else(|| {
721 io::Error::new(
722 io::ErrorKind::InvalidData,
723 "store dictionary range overflow",
724 )
725 })?;
726 if dict_header_end > index_end {
727 return Err(io::Error::new(
728 io::ErrorKind::UnexpectedEof,
729 "store dictionary length is truncated",
730 ));
731 }
732 let dict_len_bytes = file_handle
733 .read_bytes_range(dict_start..dict_header_end)
734 .await?;
735 let dict_len = (&dict_len_bytes[..]).read_u32::<LittleEndian>()? as u64;
736 if dict_len > MAX_STORE_DICTIONARY_BYTES {
737 return Err(io::Error::new(
738 io::ErrorKind::InvalidData,
739 "store dictionary exceeds safety limit",
740 ));
741 }
742 let dict_end = dict_header_end.checked_add(dict_len).ok_or_else(|| {
743 io::Error::new(
744 io::ErrorKind::InvalidData,
745 "store dictionary range overflow",
746 )
747 })?;
748 if dict_end > index_end {
749 return Err(io::Error::new(
750 io::ErrorKind::UnexpectedEof,
751 "store dictionary is truncated",
752 ));
753 }
754 let dict_bytes = file_handle
755 .read_bytes_range(dict_header_end..dict_end)
756 .await?;
757 (
758 Some(CompressionDict::from_owned_bytes(dict_bytes)),
759 dict_end,
760 )
761 } else {
762 if dict_offset != 0 {
763 return Err(io::Error::new(
764 io::ErrorKind::InvalidData,
765 "store without a dictionary has a dictionary offset",
766 ));
767 }
768 (None, data_end_offset)
769 };
770
771 if index_start > index_end {
772 return Err(io::Error::new(
773 io::ErrorKind::InvalidData,
774 "store index offset is out of bounds",
775 ));
776 }
777
778 let index_bytes = file_handle.read_bytes_range(index_start..index_end).await?;
779 let mut reader = index_bytes.as_slice();
780
781 let num_blocks = reader.read_u32::<LittleEndian>()? as usize;
782 let required_index_bytes = num_blocks.checked_mul(20).ok_or_else(|| {
783 io::Error::new(io::ErrorKind::InvalidData, "store index size overflow")
784 })?;
785 if reader.len() != required_index_bytes {
786 return Err(io::Error::new(
787 io::ErrorKind::InvalidData,
788 "store index length is inconsistent",
789 ));
790 }
791 let mut index = Vec::new();
792 index
793 .try_reserve_exact(num_blocks)
794 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "too many store blocks"))?;
795
796 let mut expected_doc = 0u32;
797 let mut expected_offset = 0u64;
798
799 for _ in 0..num_blocks {
800 let first_doc_id = reader.read_u32::<LittleEndian>()?;
801 let offset = reader.read_u64::<LittleEndian>()?;
802 let length = reader.read_u32::<LittleEndian>()?;
803 let num_docs_in_block = reader.read_u32::<LittleEndian>()?;
804
805 let end = offset.checked_add(length as u64).ok_or_else(|| {
806 io::Error::new(io::ErrorKind::InvalidData, "store block range overflow")
807 })?;
808 if first_doc_id != expected_doc
809 || num_docs_in_block == 0
810 || offset != expected_offset
811 || end > data_end_offset
812 {
813 return Err(io::Error::new(
814 io::ErrorKind::InvalidData,
815 "store block index is inconsistent",
816 ));
817 }
818 expected_doc = expected_doc.checked_add(num_docs_in_block).ok_or_else(|| {
819 io::Error::new(io::ErrorKind::InvalidData, "store document count overflow")
820 })?;
821 expected_offset = end;
822
823 index.push(StoreBlockIndex {
824 first_doc_id,
825 offset,
826 length,
827 num_docs: num_docs_in_block,
828 });
829 }
830
831 if expected_doc != num_docs || expected_offset != data_end_offset {
832 return Err(io::Error::new(
833 io::ErrorKind::InvalidData,
834 "store footer totals do not match its block index",
835 ));
836 }
837
838 let data_slice = file_handle.slice(0..data_end_offset);
840
841 Ok(Self {
842 data_slice,
843 index,
844 num_docs,
845 dict,
846 cache: RwLock::new(StoreBlockCache::new(cache_blocks)),
847 })
848 }
849
850 pub fn num_docs(&self) -> u32 {
852 self.num_docs
853 }
854
855 pub fn cached_blocks(&self) -> usize {
857 self.cache.read().blocks.len()
858 }
859
860 pub fn cached_bytes(&self) -> usize {
862 self.cache
863 .read()
864 .blocks
865 .values()
866 .map(|block| block.retained_bytes())
867 .sum()
868 }
869
870 pub async fn get(&self, doc_id: DocId, schema: &Schema) -> io::Result<Option<Document>> {
872 if doc_id >= self.num_docs {
873 return Ok(None);
874 }
875
876 let t = crate::observe::Timer::start();
877 let (entry, block) = self.find_and_load_block(doc_id).await?;
878 let doc_bytes = block.doc_bytes(doc_id - entry.first_doc_id)?;
879 let result = deserialize_document(doc_bytes, schema).map(Some);
880 crate::observe::store_get(schema.index_label(), t.secs());
881 result
882 }
883
884 pub async fn get_fields(
890 &self,
891 doc_id: DocId,
892 schema: &Schema,
893 field_ids: &[u32],
894 ) -> io::Result<Option<Document>> {
895 if doc_id >= self.num_docs {
896 return Ok(None);
897 }
898
899 let t = crate::observe::Timer::start();
900 let (entry, block) = self.find_and_load_block(doc_id).await?;
901 let doc_bytes = block.doc_bytes(doc_id - entry.first_doc_id)?;
902 let result = deserialize_document_fields(doc_bytes, schema, field_ids).map(Some);
903 crate::observe::store_get(schema.index_label(), t.secs());
904 result
905 }
906
907 async fn find_and_load_block(
909 &self,
910 doc_id: DocId,
911 ) -> io::Result<(&StoreBlockIndex, Arc<CachedBlock>)> {
912 let block_idx = self
913 .index
914 .binary_search_by(|entry| {
915 if doc_id < entry.first_doc_id {
916 std::cmp::Ordering::Greater
917 } else if doc_id >= entry.first_doc_id + entry.num_docs {
918 std::cmp::Ordering::Less
919 } else {
920 std::cmp::Ordering::Equal
921 }
922 })
923 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Doc not found in index"))?;
924
925 let entry = &self.index[block_idx];
926 let block = self.load_block(entry).await?;
927 Ok((entry, block))
928 }
929
930 async fn load_block(&self, entry: &StoreBlockIndex) -> io::Result<Arc<CachedBlock>> {
931 {
933 let cache = self.cache.read();
934 if let Some(block) = cache.peek(entry.first_doc_id) {
935 return Ok(block);
936 }
937 }
938 {
940 if let Some(block) = self.cache.write().get(entry.first_doc_id) {
941 return Ok(block);
942 }
943 }
944
945 let start = entry.offset;
947 let end = start.checked_add(entry.length as u64).ok_or_else(|| {
948 io::Error::new(io::ErrorKind::InvalidData, "store block range overflow")
949 })?;
950 let compressed = self.data_slice.read_bytes_range(start..end).await?;
951
952 let decompressed = if let Some(ref dict) = self.dict {
954 crate::compression::decompress_with_dict_limited(
955 compressed.as_slice(),
956 dict,
957 MAX_STORE_BLOCK_BYTES,
958 )?
959 } else {
960 crate::compression::decompress_limited(compressed.as_slice(), MAX_STORE_BLOCK_BYTES)?
961 };
962
963 let cached = CachedBlock::build(decompressed, entry.num_docs)?;
965 let block = Arc::new(cached);
966
967 {
969 let mut cache = self.cache.write();
970 cache.insert(entry.first_doc_id, Arc::clone(&block));
971 }
972
973 Ok(block)
974 }
975}
976
977pub fn deserialize_document_fields(
984 data: &[u8],
985 schema: &Schema,
986 field_ids: &[u32],
987) -> io::Result<Document> {
988 deserialize_document_inner(data, schema, Some(field_ids))
989}
990
991pub fn deserialize_document(data: &[u8], schema: &Schema) -> io::Result<Document> {
995 deserialize_document_inner(data, schema, None)
996}
997
998fn deserialize_document_inner(
1000 data: &[u8],
1001 _schema: &Schema,
1002 field_filter: Option<&[u32]>,
1003) -> io::Result<Document> {
1004 use crate::dsl::Field;
1005
1006 let mut reader = data;
1007 let num_fields = reader.read_u16::<LittleEndian>()? as usize;
1008 let mut doc = Document::new();
1009
1010 for _ in 0..num_fields {
1011 let field_id = reader.read_u16::<LittleEndian>()?;
1012 let type_tag = reader.read_u8()?;
1013
1014 let wanted = field_filter.is_none_or(|ids| ids.contains(&(field_id as u32)));
1015
1016 match type_tag {
1017 0 => {
1018 let len = reader.read_u32::<LittleEndian>()? as usize;
1020 let bytes = take_document_bytes(&mut reader, len, "text field")?;
1021 if wanted {
1022 let s = std::str::from_utf8(bytes)
1023 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
1024 doc.add_text(Field(field_id as u32), s);
1025 }
1026 }
1027 1 => {
1028 let v = reader.read_u64::<LittleEndian>()?;
1030 if wanted {
1031 doc.add_u64(Field(field_id as u32), v);
1032 }
1033 }
1034 2 => {
1035 let v = reader.read_i64::<LittleEndian>()?;
1037 if wanted {
1038 doc.add_i64(Field(field_id as u32), v);
1039 }
1040 }
1041 3 => {
1042 let v = reader.read_f64::<LittleEndian>()?;
1044 if wanted {
1045 doc.add_f64(Field(field_id as u32), v);
1046 }
1047 }
1048 4 => {
1049 let len = reader.read_u32::<LittleEndian>()? as usize;
1051 let bytes = take_document_bytes(&mut reader, len, "byte field")?;
1052 if wanted {
1053 doc.add_bytes(Field(field_id as u32), bytes.to_vec());
1054 }
1055 }
1056 5 => {
1057 let count = reader.read_u32::<LittleEndian>()? as usize;
1059 let byte_len = count.checked_mul(8).ok_or_else(|| {
1060 io::Error::new(io::ErrorKind::InvalidData, "sparse vector size overflow")
1061 })?;
1062 let bytes = take_document_bytes(&mut reader, byte_len, "sparse vector")?;
1063 if wanted {
1064 let mut entries = Vec::new();
1065 entries.try_reserve_exact(count).map_err(|_| {
1066 io::Error::new(io::ErrorKind::InvalidData, "sparse vector is too large")
1067 })?;
1068 let mut vector_reader = bytes;
1069 for _ in 0..count {
1070 let idx = vector_reader.read_u32::<LittleEndian>()?;
1071 let val = vector_reader.read_f32::<LittleEndian>()?;
1072 entries.push((idx, val));
1073 }
1074 doc.add_sparse_vector(Field(field_id as u32), entries);
1075 }
1076 }
1077 6 => {
1078 let count = reader.read_u32::<LittleEndian>()? as usize;
1080 let byte_len = count.checked_mul(4).ok_or_else(|| {
1081 io::Error::new(io::ErrorKind::InvalidData, "dense vector size overflow")
1082 })?;
1083 let bytes = take_document_bytes(&mut reader, byte_len, "dense vector")?;
1084 if wanted {
1085 let mut values = vec![0.0f32; count];
1086 unsafe {
1087 std::ptr::copy_nonoverlapping(
1088 bytes.as_ptr(),
1089 values.as_mut_ptr() as *mut u8,
1090 byte_len,
1091 );
1092 }
1093 doc.add_dense_vector(Field(field_id as u32), values);
1094 }
1095 }
1096 7 => {
1097 let len = reader.read_u32::<LittleEndian>()? as usize;
1099 let bytes = take_document_bytes(&mut reader, len, "JSON field")?;
1100 if wanted {
1101 let v: serde_json::Value = serde_json::from_slice(bytes)
1102 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
1103 doc.add_json(Field(field_id as u32), v);
1104 }
1105 }
1106 8 => {
1107 let len = reader.read_u32::<LittleEndian>()? as usize;
1109 let bytes = take_document_bytes(&mut reader, len, "binary dense vector")?;
1110 if wanted {
1111 doc.add_binary_dense_vector(Field(field_id as u32), bytes.to_vec());
1112 }
1113 }
1114 _ => {
1115 return Err(io::Error::new(
1116 io::ErrorKind::InvalidData,
1117 format!("Unknown field type tag: {}", type_tag),
1118 ));
1119 }
1120 }
1121 }
1122
1123 Ok(doc)
1124}
1125
1126fn take_document_bytes<'a>(reader: &mut &'a [u8], len: usize, field: &str) -> io::Result<&'a [u8]> {
1127 if len > reader.len() {
1128 return Err(io::Error::new(
1129 io::ErrorKind::UnexpectedEof,
1130 format!("{field} is truncated"),
1131 ));
1132 }
1133 let (value, remaining) = reader.split_at(len);
1134 *reader = remaining;
1135 Ok(value)
1136}
1137
1138#[derive(Debug, Clone)]
1140pub struct RawStoreBlock {
1141 pub first_doc_id: DocId,
1142 pub num_docs: u32,
1143 pub offset: u64,
1144 pub length: u32,
1145}
1146
1147pub struct StoreMerger<'a, W: Write> {
1158 writer: &'a mut W,
1159 index: Vec<StoreBlockIndex>,
1160 current_offset: u64,
1161 next_doc_id: DocId,
1162}
1163
1164impl<'a, W: Write> StoreMerger<'a, W> {
1165 pub fn new(writer: &'a mut W) -> Self {
1166 Self {
1167 writer,
1168 index: Vec::new(),
1169 current_offset: 0,
1170 next_doc_id: 0,
1171 }
1172 }
1173
1174 pub async fn append_store(
1179 &mut self,
1180 data_slice: &FileHandle,
1181 blocks: &[RawStoreBlock],
1182 ) -> io::Result<()> {
1183 for block in blocks {
1184 let start = block.offset;
1186 let end = start.checked_add(block.length as u64).ok_or_else(|| {
1187 io::Error::new(io::ErrorKind::InvalidData, "store block range overflow")
1188 })?;
1189 if end > data_slice.len() {
1190 return Err(io::Error::new(
1191 io::ErrorKind::UnexpectedEof,
1192 "store block range is out of bounds",
1193 ));
1194 }
1195 let compressed_data = data_slice.read_bytes_range(start..end).await?;
1196
1197 self.writer.write_all(compressed_data.as_slice())?;
1199
1200 self.index.push(StoreBlockIndex {
1202 first_doc_id: self.next_doc_id,
1203 offset: self.current_offset,
1204 length: block.length,
1205 num_docs: block.num_docs,
1206 });
1207
1208 self.current_offset = self
1209 .current_offset
1210 .checked_add(block.length as u64)
1211 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "store size overflow"))?;
1212 self.next_doc_id = self
1213 .next_doc_id
1214 .checked_add(block.num_docs)
1215 .ok_or_else(|| {
1216 io::Error::new(io::ErrorKind::InvalidData, "store document count overflow")
1217 })?;
1218 }
1219
1220 Ok(())
1221 }
1222
1223 pub async fn append_store_recompressing(&mut self, store: &AsyncStoreReader) -> io::Result<()> {
1230 let dict = store.dict();
1231 let data_slice = store.data_slice();
1232 let blocks = store.block_index();
1233
1234 for block in blocks {
1235 let start = block.offset;
1236 let end = start.checked_add(block.length as u64).ok_or_else(|| {
1237 io::Error::new(io::ErrorKind::InvalidData, "store block range overflow")
1238 })?;
1239 if end > data_slice.len() {
1240 return Err(io::Error::new(
1241 io::ErrorKind::UnexpectedEof,
1242 "store block range is out of bounds",
1243 ));
1244 }
1245 let compressed = data_slice.read_bytes_range(start..end).await?;
1246
1247 let decompressed = if let Some(d) = dict {
1249 crate::compression::decompress_with_dict_limited(
1250 compressed.as_slice(),
1251 d,
1252 MAX_STORE_BLOCK_BYTES,
1253 )?
1254 } else {
1255 crate::compression::decompress_limited(
1256 compressed.as_slice(),
1257 MAX_STORE_BLOCK_BYTES,
1258 )?
1259 };
1260
1261 let recompressed = crate::compression::compress(
1263 &decompressed,
1264 crate::compression::CompressionLevel::default(),
1265 )?;
1266
1267 self.writer.write_all(&recompressed)?;
1268
1269 self.index.push(StoreBlockIndex {
1270 first_doc_id: self.next_doc_id,
1271 offset: self.current_offset,
1272 length: u32::try_from(recompressed.len()).map_err(|_| {
1273 io::Error::new(
1274 io::ErrorKind::InvalidData,
1275 "compressed store block too large",
1276 )
1277 })?,
1278 num_docs: block.num_docs,
1279 });
1280
1281 self.current_offset = self
1282 .current_offset
1283 .checked_add(recompressed.len() as u64)
1284 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "store size overflow"))?;
1285 self.next_doc_id = self
1286 .next_doc_id
1287 .checked_add(block.num_docs)
1288 .ok_or_else(|| {
1289 io::Error::new(io::ErrorKind::InvalidData, "store document count overflow")
1290 })?;
1291 }
1292
1293 Ok(())
1294 }
1295
1296 pub fn finish(self) -> io::Result<u32> {
1298 let data_end_offset = self.current_offset;
1299
1300 let dict_offset = 0u64;
1302
1303 write_store_index_and_footer(
1305 self.writer,
1306 &self.index,
1307 data_end_offset,
1308 dict_offset,
1309 self.next_doc_id,
1310 false,
1311 )?;
1312
1313 Ok(self.next_doc_id)
1314 }
1315}
1316
1317impl AsyncStoreReader {
1318 pub fn raw_blocks(&self) -> Vec<RawStoreBlock> {
1320 self.index
1321 .iter()
1322 .map(|entry| RawStoreBlock {
1323 first_doc_id: entry.first_doc_id,
1324 num_docs: entry.num_docs,
1325 offset: entry.offset,
1326 length: entry.length,
1327 })
1328 .collect()
1329 }
1330
1331 pub fn data_slice(&self) -> &FileHandle {
1333 &self.data_slice
1334 }
1335
1336 pub fn has_dict(&self) -> bool {
1338 self.dict.is_some()
1339 }
1340
1341 pub fn dict(&self) -> Option<&CompressionDict> {
1343 self.dict.as_ref()
1344 }
1345
1346 pub(crate) fn block_index(&self) -> &[StoreBlockIndex] {
1348 &self.index
1349 }
1350}
1351
1352#[cfg(test)]
1353mod tests {
1354 use super::*;
1355
1356 #[test]
1357 fn cached_block_rejects_truncated_and_trailing_documents() {
1358 assert!(CachedBlock::build(vec![8, 0, 0, 0, 1], 1).is_err());
1359 assert!(CachedBlock::build(vec![0, 0, 0, 0, 1], 1).is_err());
1360 }
1361
1362 #[test]
1363 fn document_deserializer_rejects_length_prefixed_slice_overrun() {
1364 let schema = Schema::builder().build();
1365 let truncated_text = [1, 0, 0, 0, 0, 5, 0, 0, 0, b'x'];
1366 assert!(deserialize_document(&truncated_text, &schema).is_err());
1367
1368 let truncated_sparse = [1, 0, 0, 0, 5, 2, 0, 0, 0, 1, 0, 0, 0];
1369 assert!(deserialize_document(&truncated_sparse, &schema).is_err());
1370 }
1371}