1pub mod codec;
40#[cfg(feature = "native")]
41mod compact;
42
43use std::collections::BTreeMap;
44use std::io::{self, Read, Write};
45use std::sync::OnceLock;
46
47use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
48
49pub const FAST_FIELD_MAGIC: u32 = 0x32545346;
53
54pub const FAST_FIELD_FOOTER_SIZE: u64 = 16;
56
57pub const FAST_FIELD_MISSING: u64 = u64::MAX;
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70#[repr(u8)]
71pub enum FastFieldColumnType {
72 U64 = 0,
73 I64 = 1,
74 F64 = 2,
75 TextOrdinal = 3,
76}
77
78impl FastFieldColumnType {
79 pub fn from_u8(v: u8) -> Option<Self> {
80 match v {
81 0 => Some(Self::U64),
82 1 => Some(Self::I64),
83 2 => Some(Self::F64),
84 3 => Some(Self::TextOrdinal),
85 _ => None,
86 }
87 }
88}
89
90#[inline]
94pub fn zigzag_encode(v: i64) -> u64 {
95 ((v << 1) ^ (v >> 63)) as u64
96}
97
98#[inline]
100pub fn zigzag_decode(v: u64) -> i64 {
101 ((v >> 1) as i64) ^ -((v & 1) as i64)
102}
103
104#[inline]
108pub fn f64_to_sortable_u64(f: f64) -> u64 {
109 let bits = f.to_bits();
110 if (bits >> 63) == 0 {
111 bits ^ (1u64 << 63) } else {
113 !bits }
115}
116
117#[inline]
119pub fn sortable_u64_to_f64(v: u64) -> f64 {
120 let bits = if (v >> 63) != 0 {
121 v ^ (1u64 << 63) } else {
123 !v };
125 f64::from_bits(bits)
126}
127
128#[inline]
130pub fn bits_needed_u64(val: u64) -> u8 {
131 if val == 0 {
132 0
133 } else {
134 64 - val.leading_zeros() as u8
135 }
136}
137
138pub fn bitpack_write(values: &[u64], bits_per_value: u8, out: &mut Vec<u8>) {
143 if bits_per_value == 0 {
144 return; }
146 let bpv = bits_per_value as usize;
147 let total_bits = values.len() * bpv;
148 let total_bytes = total_bits.div_ceil(8);
149 out.reserve(total_bytes);
150
151 let start = out.len();
152 out.resize(start + total_bytes, 0);
153 let buf = &mut out[start..];
154
155 for (i, &val) in values.iter().enumerate() {
156 let bit_offset = i * bpv;
157 let byte_offset = bit_offset / 8;
158 let bit_shift = bit_offset % 8;
159
160 let mut remaining_bits = bpv;
162 let mut v = val;
163 let mut bo = byte_offset;
164 let mut bs = bit_shift;
165
166 while remaining_bits > 0 {
167 let can_write = (8 - bs).min(remaining_bits);
168 let mask = (1u64 << can_write) - 1;
169 buf[bo] |= ((v & mask) << bs) as u8;
170 v >>= can_write;
171 remaining_bits -= can_write;
172 bo += 1;
173 bs = 0;
174 }
175 }
176}
177
178#[inline]
184pub fn bitpack_read(data: &[u8], bits_per_value: u8, index: usize) -> u64 {
185 if bits_per_value == 0 {
186 return 0;
187 }
188 let bpv = bits_per_value as usize;
189 let bit_offset = index * bpv;
190 let byte_offset = bit_offset / 8;
191 let bit_shift = bit_offset % 8;
192
193 if bit_shift + bpv <= 64 && byte_offset + 8 <= data.len() {
196 let raw = u64::from_le_bytes(data[byte_offset..byte_offset + 8].try_into().unwrap());
197 let mask = if bpv >= 64 {
198 u64::MAX
199 } else {
200 (1u64 << bpv) - 1
201 };
202 return (raw >> bit_shift) & mask;
203 }
204
205 let mut result: u64 = 0;
207 let mut remaining_bits = bpv;
208 let mut bo = byte_offset;
209 let mut bs = bit_shift;
210 let mut out_shift = 0;
211
212 while remaining_bits > 0 {
213 let can_read = (8 - bs).min(remaining_bits);
214 let mask = ((1u64 << can_read) - 1) as u8;
215 let byte_val = if bo < data.len() { data[bo] } else { 0 };
216 result |= (((byte_val >> bs) & mask) as u64) << out_shift;
217 remaining_bits -= can_read;
218 out_shift += can_read;
219 bo += 1;
220 bs = 0;
221 }
222
223 result
224}
225
226#[derive(Debug, Clone)]
241pub struct FastFieldTocEntry {
242 pub field_id: u32,
243 pub column_type: FastFieldColumnType,
244 pub multi: bool,
245 pub data_offset: u64,
246 pub data_len: u64,
247 pub num_docs: u32,
248 pub dict_offset: u64,
250 pub dict_count: u32,
252}
253
254pub const FAST_FIELD_TOC_ENTRY_SIZE: usize = 4 + 1 + 1 + 8 + 8 + 4 + 8 + 4; #[derive(Debug, Clone)]
263pub struct BlockIndexEntry {
264 pub num_docs: u32,
265 pub data_len: u32,
266 pub dict_count: u32,
267 pub dict_len: u32,
268}
269
270pub const BLOCK_INDEX_ENTRY_SIZE: usize = 16;
271
272impl BlockIndexEntry {
273 pub fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
274 w.write_u32::<LittleEndian>(self.num_docs)?;
275 w.write_u32::<LittleEndian>(self.data_len)?;
276 w.write_u32::<LittleEndian>(self.dict_count)?;
277 w.write_u32::<LittleEndian>(self.dict_len)?;
278 Ok(())
279 }
280
281 pub fn read_from(r: &mut dyn Read) -> io::Result<Self> {
282 let num_docs = r.read_u32::<LittleEndian>()?;
283 let data_len = r.read_u32::<LittleEndian>()?;
284 let dict_count = r.read_u32::<LittleEndian>()?;
285 let dict_len = r.read_u32::<LittleEndian>()?;
286 Ok(Self {
287 num_docs,
288 data_len,
289 dict_count,
290 dict_len,
291 })
292 }
293}
294
295impl FastFieldTocEntry {
296 pub fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
297 w.write_u32::<LittleEndian>(self.field_id)?;
298 w.write_u8(self.column_type as u8)?;
299 let flags: u8 = if self.multi { 1 } else { 0 };
300 w.write_u8(flags)?;
301 w.write_u64::<LittleEndian>(self.data_offset)?;
302 w.write_u64::<LittleEndian>(self.data_len)?;
303 w.write_u32::<LittleEndian>(self.num_docs)?;
304 w.write_u64::<LittleEndian>(self.dict_offset)?;
305 w.write_u32::<LittleEndian>(self.dict_count)?;
306 Ok(())
307 }
308
309 pub fn read_from(r: &mut dyn Read) -> io::Result<Self> {
310 let field_id = r.read_u32::<LittleEndian>()?;
311 let ct = r.read_u8()?;
312 let column_type = FastFieldColumnType::from_u8(ct)
313 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "bad column type"))?;
314 let flags = r.read_u8()?;
315 if flags & !1 != 0 {
316 return Err(io::Error::new(
317 io::ErrorKind::InvalidData,
318 "unknown fast field flags",
319 ));
320 }
321 let multi = (flags & 1) != 0;
322 let data_offset = r.read_u64::<LittleEndian>()?;
323 let data_len = r.read_u64::<LittleEndian>()?;
324 let num_docs = r.read_u32::<LittleEndian>()?;
325 let dict_offset = r.read_u64::<LittleEndian>()?;
326 let dict_count = r.read_u32::<LittleEndian>()?;
327 Ok(Self {
328 field_id,
329 column_type,
330 multi,
331 data_offset,
332 data_len,
333 num_docs,
334 dict_offset,
335 dict_count,
336 })
337 }
338}
339
340pub struct FastFieldWriter {
348 pub column_type: FastFieldColumnType,
349 pub multi: bool,
351
352 values: Vec<u64>,
355
356 multi_values: Vec<u64>,
359 multi_offsets: Vec<u32>,
362 multi_current_doc: u32,
364
365 text_values: Option<BTreeMap<String, u32>>,
368 text_per_doc: Option<Vec<Option<String>>>,
370 text_multi_values: Option<Vec<String>>,
372}
373
374impl FastFieldWriter {
375 pub fn new_numeric(column_type: FastFieldColumnType) -> Self {
377 debug_assert!(matches!(
378 column_type,
379 FastFieldColumnType::U64 | FastFieldColumnType::I64 | FastFieldColumnType::F64
380 ));
381 Self {
382 column_type,
383 multi: false,
384 values: Vec::new(),
385 multi_values: Vec::new(),
386 multi_offsets: vec![0],
387 multi_current_doc: 0,
388 text_values: None,
389 text_per_doc: None,
390 text_multi_values: None,
391 }
392 }
393
394 pub fn new_numeric_multi(column_type: FastFieldColumnType) -> Self {
396 debug_assert!(matches!(
397 column_type,
398 FastFieldColumnType::U64 | FastFieldColumnType::I64 | FastFieldColumnType::F64
399 ));
400 Self {
401 column_type,
402 multi: true,
403 values: Vec::new(),
404 multi_values: Vec::new(),
405 multi_offsets: vec![0],
406 multi_current_doc: 0,
407 text_values: None,
408 text_per_doc: None,
409 text_multi_values: None,
410 }
411 }
412
413 pub fn new_text() -> Self {
415 Self {
416 column_type: FastFieldColumnType::TextOrdinal,
417 multi: false,
418 values: Vec::new(),
419 multi_values: Vec::new(),
420 multi_offsets: vec![0],
421 multi_current_doc: 0,
422 text_values: Some(BTreeMap::new()),
423 text_per_doc: Some(Vec::new()),
424 text_multi_values: None,
425 }
426 }
427
428 pub fn new_text_multi() -> Self {
430 Self {
431 column_type: FastFieldColumnType::TextOrdinal,
432 multi: true,
433 values: Vec::new(),
434 multi_values: Vec::new(),
435 multi_offsets: vec![0],
436 multi_current_doc: 0,
437 text_values: Some(BTreeMap::new()),
438 text_per_doc: None,
439 text_multi_values: Some(Vec::new()),
440 }
441 }
442
443 pub fn add_u64(&mut self, doc_id: u32, value: u64) {
446 if self.multi {
447 self.add_multi_u64(doc_id, value);
448 return;
449 }
450 let idx = doc_id as usize;
451 if idx >= self.values.len() {
452 self.values.resize(idx + 1, FAST_FIELD_MISSING);
453 if let Some(ref mut tpd) = self.text_per_doc {
454 tpd.resize(idx + 1, None);
455 }
456 }
457 self.values[idx] = value;
458 }
459
460 fn add_multi_u64(&mut self, doc_id: u32, value: u64) {
462 while self.multi_current_doc < doc_id {
464 self.multi_current_doc += 1;
465 self.multi_offsets.push(self.multi_values.len() as u32);
466 }
467 if self.multi_current_doc == doc_id && self.multi_offsets.len() == doc_id as usize + 1 {
469 }
471 self.multi_values.push(value);
472 }
473
474 pub fn add_i64(&mut self, doc_id: u32, value: i64) {
476 self.add_u64(doc_id, zigzag_encode(value));
477 }
478
479 pub fn add_f64(&mut self, doc_id: u32, value: f64) {
481 self.add_u64(doc_id, f64_to_sortable_u64(value));
482 }
483
484 pub fn add_text(&mut self, doc_id: u32, value: &str) {
486 if let Some(ref mut dict) = self.text_values {
487 let next_id = dict.len() as u32;
488 dict.entry(value.to_string()).or_insert(next_id);
489 }
490
491 if self.multi {
492 if let Some(ref mut tmv) = self.text_multi_values {
493 while self.multi_current_doc < doc_id {
495 self.multi_current_doc += 1;
496 self.multi_offsets.push(self.multi_values.len() as u32);
497 }
498 if self.multi_current_doc == doc_id
499 && self.multi_offsets.len() == doc_id as usize + 1
500 {
501 }
503 self.multi_values.push(0); tmv.push(value.to_string());
505 }
506 } else {
507 let idx = doc_id as usize;
508 if idx >= self.values.len() {
509 self.values.resize(idx + 1, FAST_FIELD_MISSING);
510 }
511 if let Some(ref mut tpd) = self.text_per_doc {
512 if idx >= tpd.len() {
513 tpd.resize(idx + 1, None);
514 }
515 tpd[idx] = Some(value.to_string());
516 }
517 }
518 }
519
520 pub fn pad_to(&mut self, num_docs: u32) {
525 let n = num_docs as usize;
526 if self.multi {
527 while (self.multi_offsets.len() as u32) <= num_docs {
528 self.multi_offsets.push(self.multi_values.len() as u32);
529 }
530 self.multi_current_doc = num_docs;
531 } else {
532 if self.values.len() < n {
533 self.values.resize(n, FAST_FIELD_MISSING);
534 if let Some(ref mut tpd) = self.text_per_doc {
535 tpd.resize(n, None);
536 }
537 }
538 }
539 }
540
541 pub fn num_docs(&self) -> u32 {
543 if self.multi {
544 (self.multi_offsets.len() as u32).saturating_sub(1)
546 } else {
547 self.values.len() as u32
548 }
549 }
550
551 pub fn serialize(
557 &mut self,
558 writer: &mut dyn Write,
559 data_offset: u64,
560 ) -> io::Result<(FastFieldTocEntry, u64)> {
561 if self.column_type == FastFieldColumnType::TextOrdinal {
563 self.resolve_text_ordinals();
564 }
565
566 let num_docs = self.num_docs();
567
568 let mut block_data = Vec::new();
570 if self.multi {
571 let offsets_u64: Vec<u64> = self.multi_offsets.iter().map(|&v| v as u64).collect();
573 let mut offset_buf = Vec::new();
574 codec::serialize_auto(&offsets_u64, &mut offset_buf)?;
575
576 block_data.write_u32::<LittleEndian>(offset_buf.len() as u32)?;
577 block_data.write_all(&offset_buf)?;
578
579 codec::serialize_auto(&self.multi_values, &mut block_data)?;
580 } else {
581 codec::serialize_auto(&self.values, &mut block_data)?;
582 }
583
584 let mut dict_buf = Vec::new();
586 let dict_count = if self.column_type == FastFieldColumnType::TextOrdinal {
587 let (count, _) = self.write_text_dictionary(&mut dict_buf)?;
588 count
589 } else {
590 0u32
591 };
592
593 let block_entry = BlockIndexEntry {
595 num_docs,
596 data_len: block_data.len() as u32,
597 dict_count,
598 dict_len: dict_buf.len() as u32,
599 };
600
601 let mut total_bytes = 0u64;
603
604 writer.write_u32::<LittleEndian>(1u32)?; total_bytes += 4;
606
607 block_entry.write_to(writer)?;
608 total_bytes += BLOCK_INDEX_ENTRY_SIZE as u64;
609
610 writer.write_all(&block_data)?;
611 total_bytes += block_data.len() as u64;
612
613 writer.write_all(&dict_buf)?;
614 total_bytes += dict_buf.len() as u64;
615
616 let toc = FastFieldTocEntry {
617 field_id: 0, column_type: self.column_type,
619 multi: self.multi,
620 data_offset,
621 data_len: total_bytes,
622 num_docs,
623 dict_offset: 0, dict_count: 0,
625 };
626
627 Ok((toc, total_bytes))
628 }
629
630 fn resolve_text_ordinals(&mut self) {
632 let dict = self.text_values.as_ref().expect("text_values required");
633
634 let sorted_ordinals: BTreeMap<&str, u64> = dict
636 .keys()
637 .enumerate()
638 .map(|(ord, key)| (key.as_str(), ord as u64))
639 .collect();
640
641 if self.multi {
642 if let Some(ref tmv) = self.text_multi_values {
644 for (i, text) in tmv.iter().enumerate() {
645 self.multi_values[i] = sorted_ordinals[text.as_str()];
646 }
647 }
648 } else {
649 let tpd = self.text_per_doc.as_ref().expect("text_per_doc required");
651 for (i, doc_text) in tpd.iter().enumerate() {
652 match doc_text {
653 Some(text) => {
654 self.values[i] = sorted_ordinals[text.as_str()];
655 }
656 None => {
657 self.values[i] = FAST_FIELD_MISSING;
658 }
659 }
660 }
661 }
662 }
663
664 fn write_text_dictionary(&self, writer: &mut dyn Write) -> io::Result<(u32, u64)> {
666 let dict = self.text_values.as_ref().expect("text_values required");
667 let mut bytes_written = 0u64;
668
669 let count = dict.len() as u32;
671 for key in dict.keys() {
672 let key_bytes = key.as_bytes();
673 writer.write_u32::<LittleEndian>(key_bytes.len() as u32)?;
674 writer.write_all(key_bytes)?;
675 bytes_written += 4 + key_bytes.len() as u64;
676 }
677
678 Ok((count, bytes_written))
679 }
680}
681
682#[cfg(feature = "native")]
687pub(crate) fn missing_block_data(multi: bool) -> io::Result<Vec<u8>> {
688 let mut data = Vec::with_capacity(23);
689 if multi {
690 let mut offsets = Vec::with_capacity(9);
691 codec::serialize_auto(&[0], &mut offsets)?;
692 data.write_u32::<LittleEndian>(offsets.len() as u32)?;
693 data.write_all(&offsets)?;
694 codec::serialize_auto(&[], &mut data)?;
695 } else {
696 codec::serialize_auto(&[FAST_FIELD_MISSING], &mut data)?;
697 }
698 Ok(data)
699}
700
701use crate::directories::OwnedBytes;
704
705pub struct ColumnBlock {
709 pub cumulative_docs: u32,
711 pub num_docs: u32,
713 pub data: OwnedBytes,
715 pub offset_data: OwnedBytes,
717 pub value_data: OwnedBytes,
719 pub dict: Option<TextDictReader>,
721 pub raw_dict: OwnedBytes,
723}
724
725mod checkpoints;
726
727impl ColumnBlock {
728 pub(crate) fn value_bounds(&self) -> Option<(u64, u64)> {
730 codec::value_bounds(self.data.as_slice())
731 }
732}
733
734pub struct FastFieldReader {
747 pub column_type: FastFieldColumnType,
748 pub num_docs: u32,
749 pub multi: bool,
750
751 blocks: Vec<ColumnBlock>,
753
754 text_state: OnceLock<TextState>,
757 checkpoints: checkpoints::Checkpoints,
758}
759
760pub(crate) struct SingleValueCursor<'a> {
763 reader: &'a FastFieldReader,
764 block: usize,
765 codec: codec::BlockwiseLinearCursor,
766}
767
768impl<'a> SingleValueCursor<'a> {
769 pub(crate) fn new(reader: &'a FastFieldReader) -> Self {
770 assert!(!reader.multi);
771 Self {
772 reader,
773 block: usize::MAX,
774 codec: Default::default(),
775 }
776 }
777
778 pub(crate) fn read_batch(&mut self, start: u32, out: &mut [u64]) -> usize {
781 if start >= self.reader.num_docs || out.is_empty() {
782 return 0;
783 }
784 let (block_idx, local) = self.reader.find_block(start);
785 if block_idx != self.block {
786 self.block = block_idx;
787 self.codec = Default::default();
788 }
789 let block = &self.reader.blocks[block_idx];
790 let count = out.len().min((block.num_docs - local) as usize);
791 codec::auto_read_batch_with_cursor(
792 block.data.as_slice(),
793 local as usize,
794 &mut out[..count],
795 &mut self.codec,
796 );
797 if self.reader.column_type == FastFieldColumnType::TextOrdinal
798 && self.reader.blocks.len() > 1
799 {
800 let map = &self.reader.ensure_text_state().ordinal_maps[block_idx];
801 remap_batch(map, &mut out[..count]);
802 }
803 count
804 }
805}
806
807fn remap_batch(map: &[u32], values: &mut [u64]) {
808 if map.is_empty() {
809 return;
810 }
811 for raw in values {
812 if *raw != FAST_FIELD_MISSING {
813 *raw = map
814 .get(*raw as usize)
815 .map_or(FAST_FIELD_MISSING, |&ord| u64::from(ord));
816 }
817 }
818}
819
820struct TextState {
822 global_dict: TextDictReader,
824 ordinal_maps: Vec<Vec<u32>>,
827}
828
829impl FastFieldReader {
830 pub(crate) fn block_metadata_bytes(&self) -> usize {
832 self.blocks.capacity() * std::mem::size_of::<ColumnBlock>() + self.checkpoints.heap_bytes()
833 }
834
835 pub fn disk_bytes(&self) -> u64 {
837 self.blocks
838 .iter()
839 .map(|block| {
840 (block.data.len()
841 + block.offset_data.len()
842 + block.value_data.len()
843 + block.raw_dict.len()) as u64
844 })
845 .sum()
846 }
847
848 pub fn open(file_data: &OwnedBytes, toc: &FastFieldTocEntry) -> io::Result<Self> {
853 let region_start = usize::try_from(toc.data_offset).map_err(|_| {
854 io::Error::new(
855 io::ErrorKind::InvalidData,
856 "fast field data offset exceeds address space",
857 )
858 })?;
859 let region_len = usize::try_from(toc.data_len).map_err(|_| {
860 io::Error::new(
861 io::ErrorKind::InvalidData,
862 "fast field data length exceeds address space",
863 )
864 })?;
865 let region_end = region_start.checked_add(region_len).ok_or_else(|| {
866 io::Error::new(io::ErrorKind::InvalidData, "fast field data range overflow")
867 })?;
868
869 if region_end > file_data.len() {
870 return Err(io::Error::new(
871 io::ErrorKind::UnexpectedEof,
872 "fast field data out of bounds",
873 ));
874 }
875
876 let raw = file_data.as_slice();
877
878 let mut pos = region_start;
880 if pos.checked_add(4).is_none_or(|end| end > region_end) {
881 return Err(io::Error::new(
882 io::ErrorKind::UnexpectedEof,
883 "fast field: missing num_blocks",
884 ));
885 }
886 let num_blocks = u32::from_le_bytes(raw[pos..pos + 4].try_into().unwrap());
887 pos += 4;
888
889 let idx_size = (num_blocks as usize)
891 .checked_mul(BLOCK_INDEX_ENTRY_SIZE)
892 .ok_or_else(|| {
893 io::Error::new(
894 io::ErrorKind::InvalidData,
895 "fast field block index overflow",
896 )
897 })?;
898 let index_end = pos.checked_add(idx_size).ok_or_else(|| {
899 io::Error::new(
900 io::ErrorKind::InvalidData,
901 "fast field block index overflow",
902 )
903 })?;
904 if index_end > region_end {
905 return Err(io::Error::new(
906 io::ErrorKind::UnexpectedEof,
907 "fast field: block index truncated",
908 ));
909 }
910 let mut block_entries = Vec::new();
911 block_entries
912 .try_reserve_exact(num_blocks as usize)
913 .map_err(|_| {
914 io::Error::new(io::ErrorKind::InvalidData, "too many fast field blocks")
915 })?;
916 {
917 let mut cursor = std::io::Cursor::new(&raw[pos..index_end]);
918 for _ in 0..num_blocks {
919 block_entries.push(BlockIndexEntry::read_from(&mut cursor)?);
920 }
921 }
922 pos = index_end;
923
924 let empty = OwnedBytes::new(Vec::new());
925
926 let mut blocks = Vec::new();
928 blocks.try_reserve_exact(num_blocks as usize).map_err(|_| {
929 io::Error::new(io::ErrorKind::InvalidData, "too many fast field blocks")
930 })?;
931 let mut cumulative = 0u32;
932
933 for entry in &block_entries {
934 let data_start = pos;
935 let data_end = data_start
936 .checked_add(entry.data_len as usize)
937 .ok_or_else(|| {
938 io::Error::new(
939 io::ErrorKind::InvalidData,
940 "fast field block range overflow",
941 )
942 })?;
943 let dict_start = data_end;
944 let dict_end = dict_start
945 .checked_add(entry.dict_len as usize)
946 .ok_or_else(|| {
947 io::Error::new(io::ErrorKind::InvalidData, "fast field dict range overflow")
948 })?;
949
950 if dict_end > region_end {
951 return Err(io::Error::new(
952 io::ErrorKind::UnexpectedEof,
953 "fast field: block data/dict truncated",
954 ));
955 }
956
957 let (block_data, offset_data, value_data) = if toc.multi {
959 let block_raw = &raw[data_start..data_end];
960 if block_raw.len() < 4 {
961 return Err(io::Error::new(
962 io::ErrorKind::UnexpectedEof,
963 "fast field multi-value header is truncated",
964 ));
965 }
966 let offset_col_len =
967 u32::from_le_bytes(block_raw[0..4].try_into().unwrap()) as usize;
968 let o_start = data_start + 4;
969 let o_end = o_start.checked_add(offset_col_len).ok_or_else(|| {
970 io::Error::new(
971 io::ErrorKind::InvalidData,
972 "fast field offset column range overflow",
973 )
974 })?;
975 if o_end > data_end {
976 return Err(io::Error::new(
977 io::ErrorKind::UnexpectedEof,
978 "fast field offset column is truncated",
979 ));
980 }
981 let v_start = o_end;
982 let v_end = data_end;
983 let offset_data = file_data.slice(o_start..o_end);
984 let value_data = file_data.slice(v_start..v_end);
985 let offset_count = (entry.num_docs as usize).checked_add(1).ok_or_else(|| {
986 io::Error::new(io::ErrorKind::InvalidData, "fast field doc count overflow")
987 })?;
988 codec::validate_auto(offset_data.as_slice(), offset_count)?;
989
990 let mut previous = 0u64;
991 for index in 0..offset_count {
992 let offset = codec::auto_read(offset_data.as_slice(), index);
993 if offset > u32::MAX as u64 || (index == 0 && offset != 0) || offset < previous
994 {
995 return Err(io::Error::new(
996 io::ErrorKind::InvalidData,
997 "fast field value offsets are invalid",
998 ));
999 }
1000 previous = offset;
1001 }
1002 codec::validate_auto(value_data.as_slice(), previous as usize)?;
1003
1004 (
1005 file_data.slice(data_start..data_end),
1006 offset_data,
1007 value_data,
1008 )
1009 } else {
1010 let block_data = file_data.slice(data_start..data_end);
1011 codec::validate_auto(block_data.as_slice(), entry.num_docs as usize)?;
1012 (block_data, empty.clone(), empty.clone())
1013 };
1014
1015 if toc.column_type == FastFieldColumnType::TextOrdinal {
1016 if entry.dict_count == 0 && entry.dict_len != 0 {
1017 return Err(io::Error::new(
1018 io::ErrorKind::InvalidData,
1019 "empty fast field dictionary has data",
1020 ));
1021 }
1022 validate_text_dict_bytes(&raw[dict_start..dict_end], entry.dict_count)?;
1023 } else if entry.dict_count != 0 || entry.dict_len != 0 {
1024 return Err(io::Error::new(
1025 io::ErrorKind::InvalidData,
1026 "numeric fast field contains a text dictionary",
1027 ));
1028 }
1029
1030 let dict = if entry.dict_count > 0 {
1032 Some(TextDictReader::new_lazy(
1033 file_data.slice(dict_start..dict_end),
1034 entry.dict_count,
1035 ))
1036 } else {
1037 None
1038 };
1039
1040 let raw_dict = if entry.dict_len > 0 {
1041 file_data.slice(dict_start..dict_end)
1042 } else {
1043 empty.clone()
1044 };
1045
1046 blocks.push(ColumnBlock {
1047 cumulative_docs: cumulative,
1048 num_docs: entry.num_docs,
1049 data: block_data,
1050 offset_data,
1051 value_data,
1052 dict,
1053 raw_dict,
1054 });
1055
1056 cumulative = cumulative.checked_add(entry.num_docs).ok_or_else(|| {
1057 io::Error::new(io::ErrorKind::InvalidData, "fast field doc count overflow")
1058 })?;
1059 pos = dict_end;
1060 }
1061
1062 if pos != region_end || cumulative != toc.num_docs {
1063 return Err(io::Error::new(
1064 io::ErrorKind::InvalidData,
1065 "fast field block totals are inconsistent with the TOC",
1066 ));
1067 }
1068 if toc.num_docs > 0 && blocks.is_empty() {
1069 return Err(io::Error::new(
1070 io::ErrorKind::InvalidData,
1071 "non-empty fast field has no blocks",
1072 ));
1073 }
1074
1075 let checkpoints = checkpoints::Checkpoints::new(&blocks, toc.multi);
1076 Ok(Self {
1077 checkpoints,
1078 column_type: toc.column_type,
1079 num_docs: toc.num_docs,
1080 multi: toc.multi,
1081 blocks,
1082 text_state: OnceLock::new(),
1083 })
1084 }
1085
1086 fn ensure_text_state(&self) -> &TextState {
1089 self.text_state
1090 .get_or_init(|| Self::build_text_state(&self.blocks))
1091 }
1092
1093 fn build_text_state(blocks: &[ColumnBlock]) -> TextState {
1096 let blocks_with_dict = blocks.iter().filter(|b| b.dict.is_some()).count();
1099 if blocks_with_dict <= 1 {
1100 for block in blocks.iter() {
1101 if let Some(ref dict) = block.dict {
1102 return TextState {
1104 global_dict: TextDictReader::new_lazy(block.raw_dict.clone(), dict.len()),
1105 ordinal_maps: vec![Vec::new(); blocks.len()],
1106 };
1107 }
1108 }
1109 return TextState {
1111 global_dict: TextDictReader::new_lazy(OwnedBytes::new(Vec::new()), 0),
1112 ordinal_maps: vec![Vec::new(); blocks.len()],
1113 };
1114 }
1115
1116 let mut unique_map: BTreeMap<String, u32> = BTreeMap::new();
1127 for block in blocks.iter() {
1128 if let Some(ref dict) = block.dict {
1129 for ord in 0..dict.len() {
1130 if let Some(text) = dict.get(ord) {
1131 unique_map.entry(text.to_string()).or_insert(0);
1132 }
1133 }
1134 }
1135 }
1136 for (i, value) in unique_map.values_mut().enumerate() {
1138 *value = i as u32;
1139 }
1140
1141 let mut ordinal_maps = Vec::with_capacity(blocks.len());
1143 for block in blocks.iter() {
1144 if let Some(ref dict) = block.dict {
1145 let mut map = Vec::with_capacity(dict.len() as usize);
1146 for local_ord in 0..dict.len() {
1147 let text = dict
1148 .get(local_ord)
1149 .expect("block dict ordinal out of range");
1150 let global_ord = *unique_map
1151 .get(text)
1152 .expect("block dict entry not found in merged global dict");
1153 map.push(global_ord);
1154 }
1155 ordinal_maps.push(map);
1156 } else {
1157 ordinal_maps.push(Vec::new());
1158 }
1159 }
1160
1161 let mut dict_buf = Vec::new();
1163 let count = unique_map.len() as u32;
1164 for s in unique_map.keys() {
1165 let bytes = s.as_bytes();
1166 dict_buf.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
1167 dict_buf.extend_from_slice(bytes);
1168 }
1169
1170 TextState {
1171 global_dict: TextDictReader::new_lazy(OwnedBytes::new(dict_buf), count),
1172 ordinal_maps,
1173 }
1174 }
1175
1176 #[inline]
1179 fn remap_ordinal(&self, block_idx: usize, raw: u64) -> u64 {
1180 if self.column_type == FastFieldColumnType::TextOrdinal
1181 && raw != FAST_FIELD_MISSING
1182 && self.blocks.len() > 1
1183 {
1184 let state = self.ensure_text_state();
1185 let map = &state.ordinal_maps[block_idx];
1186 if !map.is_empty() {
1187 let idx = raw as usize;
1188 if idx < map.len() {
1189 map[idx] as u64
1190 } else {
1191 FAST_FIELD_MISSING
1192 }
1193 } else {
1194 raw
1195 }
1196 } else {
1197 raw
1198 }
1199 }
1200
1201 #[inline]
1203 fn find_block(&self, doc_id: u32) -> (usize, u32) {
1204 debug_assert!(!self.blocks.is_empty());
1205 if self.blocks.len() == 1 {
1207 return (0, doc_id);
1208 }
1209 let bi = self
1211 .blocks
1212 .partition_point(|b| b.cumulative_docs <= doc_id)
1213 .saturating_sub(1);
1214 (bi, doc_id - self.blocks[bi].cumulative_docs)
1215 }
1216
1217 #[inline]
1225 pub fn get_u64(&self, doc_id: u32) -> u64 {
1226 if doc_id >= self.num_docs {
1227 return FAST_FIELD_MISSING;
1228 }
1229 let (bi, local) = self.find_block(doc_id);
1230 let block = &self.blocks[bi];
1231
1232 if self.multi {
1233 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1234 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1235 if start >= end {
1236 return FAST_FIELD_MISSING;
1237 }
1238 let raw = codec::auto_read(block.value_data.as_slice(), start as usize);
1239 return self.remap_ordinal(bi, raw);
1240 }
1241
1242 let raw = self.checkpoints.read(&self.blocks, bi, local);
1243 self.remap_ordinal(bi, raw)
1244 }
1245
1246 #[inline]
1249 fn block_value_range(&self, doc_id: u32) -> (usize, u32, u32) {
1250 if !self.multi || doc_id >= self.num_docs {
1251 return (0, 0, 0);
1252 }
1253 let (bi, local) = self.find_block(doc_id);
1254 let block = &self.blocks[bi];
1255 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1256 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1257 (bi, start, end)
1258 }
1259
1260 #[inline]
1264 pub fn value_range(&self, doc_id: u32) -> (u32, u32) {
1265 let (_, start, end) = self.block_value_range(doc_id);
1266 (start, end)
1267 }
1268
1269 #[inline]
1272 pub fn get_value_at(&self, index: u32) -> u64 {
1273 if self.blocks.len() == 1 {
1275 let raw = codec::auto_read(self.blocks[0].value_data.as_slice(), index as usize);
1276 return self.remap_ordinal(0, raw);
1277 }
1278 0
1280 }
1281
1282 pub fn get_multi_values(&self, doc_id: u32) -> Vec<u64> {
1284 let (bi, start, end) = self.block_value_range(doc_id);
1285 if start >= end {
1286 return Vec::new();
1287 }
1288 let block = &self.blocks[bi];
1289 (start..end)
1290 .map(|idx| {
1291 let raw = codec::auto_read(block.value_data.as_slice(), idx as usize);
1292 self.remap_ordinal(bi, raw)
1293 })
1294 .collect()
1295 }
1296
1297 #[inline]
1300 pub fn for_each_multi_value(&self, doc_id: u32, mut f: impl FnMut(u64) -> bool) -> bool {
1301 let (bi, start, end) = self.block_value_range(doc_id);
1302 if start >= end {
1303 return false;
1304 }
1305 let block = &self.blocks[bi];
1306 for idx in start..end {
1307 let raw = codec::auto_read(block.value_data.as_slice(), idx as usize);
1308 if f(self.remap_ordinal(bi, raw)) {
1309 return true;
1310 }
1311 }
1312 false
1313 }
1314
1315 pub fn scan_single_values(&self, mut f: impl FnMut(u32, u64)) {
1322 let _: Result<(), std::convert::Infallible> = self.try_scan_single_values(|doc, value| {
1323 f(doc, value);
1324 Ok(())
1325 });
1326 }
1327
1328 pub(crate) fn try_scan_single_values<E>(
1330 &self,
1331 mut f: impl FnMut(u32, u64) -> Result<(), E>,
1332 ) -> Result<(), E> {
1333 self.try_scan_single_value_batches(|start, values| {
1334 for (i, &value) in values.iter().enumerate() {
1335 f(start + i as u32, value)?;
1336 }
1337 Ok(())
1338 })
1339 }
1340
1341 pub(crate) fn try_scan_single_value_batches<E>(
1345 &self,
1346 f: impl FnMut(u32, &[u64]) -> Result<(), E>,
1347 ) -> Result<(), E> {
1348 self.try_scan_single_value_batches_where(|_| true, f)
1349 }
1350
1351 pub(crate) fn try_scan_single_value_batches_where<E>(
1354 &self,
1355 mut should_scan: impl FnMut(&ColumnBlock) -> bool,
1356 mut f: impl FnMut(u32, &[u64]) -> Result<(), E>,
1357 ) -> Result<(), E> {
1358 if self.multi {
1359 return Ok(());
1360 }
1361 const BATCH: usize = 256;
1362 let mut buf = [0u64; BATCH];
1363 let needs_remap =
1364 self.column_type == FastFieldColumnType::TextOrdinal && self.blocks.len() > 1;
1365
1366 let ordinal_maps = if needs_remap {
1368 Some(&self.ensure_text_state().ordinal_maps)
1369 } else {
1370 None
1371 };
1372
1373 for (block_idx, block) in self.blocks.iter().enumerate() {
1374 if !should_scan(block) {
1375 continue;
1376 }
1377 let n = block.num_docs as usize;
1378 let mut pos = 0;
1379 let mut cursor = codec::BlockwiseLinearCursor::default();
1380
1381 let map = ordinal_maps.map(|maps| &maps[block_idx]);
1382 let has_map = map.is_some_and(|m| !m.is_empty());
1383
1384 while pos < n {
1385 let chunk = (n - pos).min(BATCH);
1386 codec::auto_read_batch_with_cursor(
1387 block.data.as_slice(),
1388 pos,
1389 &mut buf[..chunk],
1390 &mut cursor,
1391 );
1392
1393 if has_map {
1394 remap_batch(map.unwrap(), &mut buf[..chunk]);
1395 }
1396 f(block.cumulative_docs + pos as u32, &buf[..chunk])?;
1397 pos += chunk;
1398 }
1399 }
1400 Ok(())
1401 }
1402
1403 #[inline]
1408 pub fn has_value(&self, doc_id: u32) -> bool {
1409 if !self.multi {
1410 return doc_id < self.num_docs && self.get_u64(doc_id) != FAST_FIELD_MISSING;
1411 }
1412 let (_, start, end) = self.block_value_range(doc_id);
1413 start < end
1414 }
1415
1416 #[inline]
1421 pub fn get_i64(&self, doc_id: u32) -> i64 {
1422 zigzag_decode(self.get_u64(doc_id))
1423 }
1424
1425 #[inline]
1430 pub fn get_f64(&self, doc_id: u32) -> f64 {
1431 sortable_u64_to_f64(self.get_u64(doc_id))
1432 }
1433
1434 #[inline]
1436 pub fn get_ordinal(&self, doc_id: u32) -> u64 {
1437 self.get_u64(doc_id)
1438 }
1439
1440 pub fn get_text(&self, doc_id: u32) -> Option<&str> {
1443 if doc_id >= self.num_docs {
1444 return None;
1445 }
1446 let (bi, local) = self.find_block(doc_id);
1447 let block = &self.blocks[bi];
1448 let raw_ordinal = if self.multi {
1449 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1450 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1451 if start >= end {
1452 return None;
1453 }
1454 codec::auto_read(block.value_data.as_slice(), start as usize)
1455 } else {
1456 self.checkpoints.read(&self.blocks, bi, local)
1457 };
1458 if raw_ordinal == FAST_FIELD_MISSING {
1459 return None;
1460 }
1461 block.dict.as_ref().and_then(|d| d.get(raw_ordinal as u32))
1462 }
1463
1464 pub fn text_ordinal(&self, text: &str) -> Option<u64> {
1466 if self.column_type != FastFieldColumnType::TextOrdinal {
1467 return None;
1468 }
1469 self.ensure_text_state().global_dict.ordinal(text)
1470 }
1471
1472 pub fn text_dict(&self) -> Option<&TextDictReader> {
1474 if self.column_type != FastFieldColumnType::TextOrdinal {
1475 return None;
1476 }
1477 Some(&self.ensure_text_state().global_dict)
1478 }
1479
1480 pub fn num_blocks(&self) -> usize {
1482 self.blocks.len()
1483 }
1484
1485 pub fn blocks(&self) -> &[ColumnBlock] {
1487 &self.blocks
1488 }
1489}
1490
1491pub struct TextDictReader {
1499 data: OwnedBytes,
1501 count: u32,
1503 offsets: OnceLock<Vec<(u32, u32)>>,
1505}
1506
1507impl TextDictReader {
1508 fn new_lazy(data: OwnedBytes, count: u32) -> Self {
1511 Self {
1512 data,
1513 count,
1514 offsets: OnceLock::new(),
1515 }
1516 }
1517
1518 pub fn open(file_data: &OwnedBytes, dict_start: usize, count: u32) -> io::Result<Self> {
1521 if count == 0 {
1522 return Ok(Self::new_lazy(OwnedBytes::new(Vec::new()), 0));
1523 }
1524 let dict_slice = file_data.as_slice();
1526 if dict_start > dict_slice.len() {
1527 return Err(io::Error::new(
1528 io::ErrorKind::UnexpectedEof,
1529 "text dict offset out of bounds",
1530 ));
1531 }
1532 let mut pos = dict_start;
1533 for _ in 0..count {
1534 if pos.checked_add(4).is_none_or(|end| end > dict_slice.len()) {
1535 return Err(io::Error::new(
1536 io::ErrorKind::UnexpectedEof,
1537 "text dict truncated",
1538 ));
1539 }
1540 let len = u32::from_le_bytes(dict_slice[pos..pos + 4].try_into().unwrap()) as usize;
1541 pos += 4;
1542 if pos
1543 .checked_add(len)
1544 .is_none_or(|end| end > dict_slice.len())
1545 {
1546 return Err(io::Error::new(
1547 io::ErrorKind::UnexpectedEof,
1548 "text dict entry truncated",
1549 ));
1550 }
1551 std::str::from_utf8(&dict_slice[pos..pos + len])
1552 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
1553 pos += len;
1554 }
1555 let data = file_data.slice(dict_start..pos);
1556 Ok(Self::new_lazy(data, count))
1557 }
1558
1559 pub fn open_from_raw(raw_dict: &OwnedBytes, count: u32) -> io::Result<Self> {
1561 validate_text_dict_bytes(raw_dict.as_slice(), count)?;
1562 Ok(Self::new_lazy(raw_dict.clone(), count))
1563 }
1564
1565 #[inline]
1567 fn ensure_offsets(&self) -> &[(u32, u32)] {
1568 self.offsets.get_or_init(|| {
1569 let dict_slice = self.data.as_slice();
1570 let mut pos = 0usize;
1571 let mut offsets = Vec::with_capacity(self.count as usize);
1572 for _ in 0..self.count {
1573 debug_assert!(
1574 pos + 4 <= dict_slice.len(),
1575 "text dict truncated during lazy init"
1576 );
1577 let len = u32::from_le_bytes(dict_slice[pos..pos + 4].try_into().unwrap()) as usize;
1578 pos += 4;
1579 debug_assert!(
1580 pos + len <= dict_slice.len(),
1581 "text dict entry truncated during lazy init"
1582 );
1583 offsets.push((pos as u32, len as u32));
1584 pos += len;
1585 }
1586 offsets
1587 })
1588 }
1589
1590 pub fn get(&self, ordinal: u32) -> Option<&str> {
1592 let offsets = self.ensure_offsets();
1593 let &(off, len) = offsets.get(ordinal as usize)?;
1594 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1595 std::str::from_utf8(slice).ok()
1596 }
1597
1598 pub fn ordinal(&self, text: &str) -> Option<u64> {
1600 let offsets = self.ensure_offsets();
1601 offsets
1602 .binary_search_by(|&(off, len)| {
1603 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1604 std::str::from_utf8(slice).unwrap_or("").cmp(text)
1605 })
1606 .ok()
1607 .map(|i| i as u64)
1608 }
1609
1610 pub fn len(&self) -> u32 {
1612 self.count
1613 }
1614
1615 pub fn is_empty(&self) -> bool {
1617 self.count == 0
1618 }
1619
1620 pub fn iter(&self) -> impl Iterator<Item = &str> {
1622 let offsets = self.ensure_offsets();
1623 offsets.iter().map(|&(off, len)| {
1624 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1625 std::str::from_utf8(slice).unwrap_or("")
1626 })
1627 }
1628}
1629
1630fn validate_text_dict_bytes(data: &[u8], count: u32) -> io::Result<()> {
1631 let minimum = (count as usize).checked_mul(4).ok_or_else(|| {
1632 io::Error::new(io::ErrorKind::InvalidData, "text dictionary size overflow")
1633 })?;
1634 if minimum > data.len() {
1635 return Err(io::Error::new(
1636 io::ErrorKind::UnexpectedEof,
1637 "text dictionary entry table is truncated",
1638 ));
1639 }
1640
1641 let mut pos = 0usize;
1642 let mut previous: Option<&str> = None;
1643 for _ in 0..count {
1644 let len_end = pos.checked_add(4).ok_or_else(|| {
1645 io::Error::new(
1646 io::ErrorKind::InvalidData,
1647 "text dictionary offset overflow",
1648 )
1649 })?;
1650 let len = u32::from_le_bytes(data[pos..len_end].try_into().unwrap()) as usize;
1651 pos = len_end;
1652 let end = pos.checked_add(len).ok_or_else(|| {
1653 io::Error::new(
1654 io::ErrorKind::InvalidData,
1655 "text dictionary offset overflow",
1656 )
1657 })?;
1658 if end > data.len() {
1659 return Err(io::Error::new(
1660 io::ErrorKind::UnexpectedEof,
1661 "text dictionary entry is truncated",
1662 ));
1663 }
1664 let value = std::str::from_utf8(&data[pos..end])
1665 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
1666 if previous.is_some_and(|previous| previous >= value) {
1667 return Err(io::Error::new(
1668 io::ErrorKind::InvalidData,
1669 "text dictionary entries are not strictly increasing",
1670 ));
1671 }
1672 previous = Some(value);
1673 pos = end;
1674 }
1675 if pos != data.len() {
1676 return Err(io::Error::new(
1677 io::ErrorKind::InvalidData,
1678 "text dictionary contains trailing data",
1679 ));
1680 }
1681 Ok(())
1682}
1683
1684pub fn write_fast_field_toc_and_footer(
1688 writer: &mut dyn Write,
1689 toc_offset: u64,
1690 entries: &[FastFieldTocEntry],
1691) -> io::Result<()> {
1692 for e in entries {
1693 e.write_to(writer)?;
1694 }
1695 writer.write_u64::<LittleEndian>(toc_offset)?;
1696 writer.write_u32::<LittleEndian>(entries.len() as u32)?;
1697 writer.write_u32::<LittleEndian>(FAST_FIELD_MAGIC)?;
1698 Ok(())
1699}
1700
1701pub fn read_fast_field_footer(file_data: &[u8]) -> io::Result<(u64, u32)> {
1704 let len = file_data.len();
1705 if len < FAST_FIELD_FOOTER_SIZE as usize {
1706 return Err(io::Error::new(
1707 io::ErrorKind::UnexpectedEof,
1708 "fast field file too small for footer",
1709 ));
1710 }
1711 let footer = &file_data[len - FAST_FIELD_FOOTER_SIZE as usize..];
1712 let mut cursor = std::io::Cursor::new(footer);
1713 let toc_offset = cursor.read_u64::<LittleEndian>()?;
1714 let num_columns = cursor.read_u32::<LittleEndian>()?;
1715 let magic = cursor.read_u32::<LittleEndian>()?;
1716 if magic != FAST_FIELD_MAGIC {
1717 return Err(io::Error::new(
1718 io::ErrorKind::InvalidData,
1719 format!("bad fast field magic: 0x{:08x}", magic),
1720 ));
1721 }
1722 Ok((toc_offset, num_columns))
1723}
1724
1725pub fn read_fast_field_toc(
1727 file_data: &[u8],
1728 toc_offset: u64,
1729 num_columns: u32,
1730) -> io::Result<Vec<FastFieldTocEntry>> {
1731 let start = usize::try_from(toc_offset).map_err(|_| {
1732 io::Error::new(
1733 io::ErrorKind::InvalidData,
1734 "fast field TOC offset exceeds address space",
1735 )
1736 })?;
1737 let expected = (num_columns as usize)
1738 .checked_mul(FAST_FIELD_TOC_ENTRY_SIZE)
1739 .ok_or_else(|| {
1740 io::Error::new(io::ErrorKind::InvalidData, "fast field TOC size overflow")
1741 })?;
1742 let end = start.checked_add(expected).ok_or_else(|| {
1743 io::Error::new(io::ErrorKind::InvalidData, "fast field TOC range overflow")
1744 })?;
1745 if end > file_data.len() {
1746 return Err(io::Error::new(
1747 io::ErrorKind::UnexpectedEof,
1748 "fast field TOC out of bounds",
1749 ));
1750 }
1751 let mut cursor = std::io::Cursor::new(&file_data[start..end]);
1752 let mut entries = Vec::new();
1753 entries
1754 .try_reserve_exact(num_columns as usize)
1755 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "too many fast field columns"))?;
1756 for _ in 0..num_columns {
1757 entries.push(FastFieldTocEntry::read_from(&mut cursor)?);
1758 }
1759 Ok(entries)
1760}
1761
1762#[cfg(test)]
1765mod tests {
1766 use super::*;
1767
1768 #[test]
1769 fn sparse_checkpoints_bound_heap_and_preserve_merged_values_and_bytes() {
1770 use codec::{BlockwiseLinearEstimator, CodecEstimator};
1771 let values: Vec<_> = (0..(512 * 301 + 7))
1772 .map(|i| (i / 512 * 100_000 + i % 512 * 3 + i % 7) as u64)
1773 .collect();
1774 let mut encoded = Vec::new();
1775 BlockwiseLinearEstimator::default()
1776 .serialize(&values, &mut encoded)
1777 .unwrap();
1778 let mut missing = Vec::new();
1779 codec::serialize_auto(&[FAST_FIELD_MISSING], &mut missing).unwrap();
1780 let (bytes, toc) = assemble_blocked_column(
1781 0,
1782 FastFieldColumnType::U64,
1783 false,
1784 &[
1785 (values.len() as u32, &encoded, 0, &[]),
1786 (1, &missing, 0, &[]),
1787 (values.len() as u32, &encoded, 0, &[]),
1788 ],
1789 );
1790 let original = bytes.clone();
1791 let owned = owned(bytes);
1792 let reader = FastFieldReader::open(&owned, &toc).unwrap();
1793 assert!(reader.checkpoints.heap_bytes() > 0);
1794 assert!(reader.checkpoints.heap_bytes() <= 256 * 12);
1795 assert_eq!(
1796 reader.block_metadata_bytes(),
1797 reader.blocks.capacity() * std::mem::size_of::<ColumnBlock>()
1798 + reader.checkpoints.heap_bytes()
1799 );
1800 for start in [0, values.len() + 1] {
1801 for i in (0..values.len())
1802 .step_by(71)
1803 .chain([510, 511, 512, 513, values.len() - 1])
1804 {
1805 assert_eq!(reader.get_u64((start + i) as u32), values[i]);
1806 }
1807 }
1808 assert_eq!(reader.get_u64(values.len() as u32), FAST_FIELD_MISSING);
1809 assert_eq!(reader.get_u64(reader.num_docs), FAST_FIELD_MISSING);
1810 assert_eq!(owned.as_slice(), original);
1811 }
1812
1813 #[test]
1814 fn sparse_checkpoints_preserve_local_text_dictionaries_and_rank_order() {
1815 use codec::{BlockwiseLinearEstimator, CodecEstimator};
1816 let mut a = FastFieldWriter::new_text();
1817 a.add_text(0, "alpha");
1818 a.add_text(1, "beta");
1819 let (_, dict_a, _) = serialize_single_block(&mut a);
1820 let mut b = FastFieldWriter::new_text();
1821 b.add_text(0, "beta");
1822 b.add_text(1, "gamma");
1823 let (_, dict_b, _) = serialize_single_block(&mut b);
1824 let values: Vec<_> = (0..1100).map(|i| (i % 2) as u64).collect();
1825 let mut encoded = Vec::new();
1826 BlockwiseLinearEstimator::default()
1827 .serialize(&values, &mut encoded)
1828 .unwrap();
1829 let (bytes, toc) = assemble_blocked_column(
1830 0,
1831 FastFieldColumnType::TextOrdinal,
1832 false,
1833 &[(1100, &encoded, 2, &dict_a), (1100, &encoded, 2, &dict_b)],
1834 );
1835 let reader = FastFieldReader::open(&owned(bytes), &toc).unwrap();
1836 for doc in [2199, 1100, 511, 512, 1099, 0, 512, 1101] {
1837 let expected = if doc < 1100 {
1838 ["alpha", "beta"]
1839 } else {
1840 ["beta", "gamma"]
1841 };
1842 assert_eq!(reader.get_text(doc), Some(expected[(doc % 2) as usize]));
1843 }
1844 assert_eq!(reader.get_text(2200), None);
1845 assert_eq!(reader.get_ordinal(1101), 2);
1846 }
1847
1848 #[test]
1849 fn test_zigzag_roundtrip() {
1850 for v in [0i64, 1, -1, 42, -42, i64::MAX, i64::MIN] {
1851 assert_eq!(zigzag_decode(zigzag_encode(v)), v);
1852 }
1853 }
1854
1855 #[test]
1856 fn test_f64_sortable_roundtrip() {
1857 for v in [0.0f64, 1.0, -1.0, f64::MAX, f64::MIN, f64::MIN_POSITIVE] {
1858 assert_eq!(sortable_u64_to_f64(f64_to_sortable_u64(v)), v);
1859 }
1860 }
1861
1862 #[test]
1863 fn test_f64_sortable_order() {
1864 let values = [-100.0f64, -1.0, -0.0, 0.0, 0.5, 1.0, 100.0];
1865 let encoded: Vec<u64> = values.iter().map(|&v| f64_to_sortable_u64(v)).collect();
1866 for i in 1..encoded.len() {
1867 assert!(
1868 encoded[i] >= encoded[i - 1],
1869 "{} >= {} failed for {} vs {}",
1870 encoded[i],
1871 encoded[i - 1],
1872 values[i],
1873 values[i - 1]
1874 );
1875 }
1876 }
1877
1878 #[test]
1879 fn test_bitpack_roundtrip() {
1880 let values: Vec<u64> = vec![0, 3, 7, 15, 0, 1, 6, 12];
1881 let bpv = 4u8;
1882 let mut packed = Vec::new();
1883 bitpack_write(&values, bpv, &mut packed);
1884
1885 for (i, &expected) in values.iter().enumerate() {
1886 let got = bitpack_read(&packed, bpv, i);
1887 assert_eq!(got, expected, "index {}", i);
1888 }
1889 }
1890
1891 #[test]
1892 fn test_bitpack_high_bpv_regression() {
1893 for bpv in [57u8, 58, 59, 60, 63, 64] {
1896 let max_val = if bpv == 64 {
1897 u64::MAX
1898 } else {
1899 (1u64 << bpv) - 1
1900 };
1901 let values: Vec<u64> = (0..32)
1902 .map(|i: u64| {
1903 if max_val == u64::MAX {
1904 i * 7
1905 } else {
1906 (i * 7) % (max_val + 1)
1907 }
1908 })
1909 .collect();
1910 let mut packed = Vec::new();
1911 bitpack_write(&values, bpv, &mut packed);
1912 for (i, &expected) in values.iter().enumerate() {
1913 let got = bitpack_read(&packed, bpv, i);
1914 assert_eq!(got, expected, "high bpv={} index={}", bpv, i);
1915 }
1916 }
1917 }
1918
1919 #[test]
1920 fn test_bitpack_various_widths() {
1921 for bpv in [1u8, 2, 3, 5, 7, 8, 13, 16, 32, 64] {
1922 let max_val = if bpv == 64 {
1923 u64::MAX
1924 } else {
1925 (1u64 << bpv) - 1
1926 };
1927 let values: Vec<u64> = (0..100)
1928 .map(|i: u64| {
1929 if max_val == u64::MAX {
1930 i
1931 } else {
1932 i % (max_val + 1)
1933 }
1934 })
1935 .collect();
1936 let mut packed = Vec::new();
1937 bitpack_write(&values, bpv, &mut packed);
1938
1939 for (i, &expected) in values.iter().enumerate() {
1940 let got = bitpack_read(&packed, bpv, i);
1941 assert_eq!(got, expected, "bpv={} index={}", bpv, i);
1942 }
1943 }
1944 }
1945
1946 fn owned(buf: Vec<u8>) -> OwnedBytes {
1948 OwnedBytes::new(buf)
1949 }
1950
1951 #[test]
1952 fn test_writer_reader_u64_roundtrip() {
1953 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
1954 writer.add_u64(0, 100);
1955 writer.add_u64(1, 200);
1956 writer.add_u64(2, 150);
1957 writer.add_u64(4, 300); writer.pad_to(5);
1959
1960 let mut buf = Vec::new();
1961 let (mut toc, _bytes) = writer.serialize(&mut buf, 0).unwrap();
1962 toc.field_id = 42;
1963
1964 let toc_offset = buf.len() as u64;
1966 write_fast_field_toc_and_footer(&mut buf, toc_offset, &[toc]).unwrap();
1967
1968 let ob = owned(buf);
1970 let (toc_off, num_cols) = read_fast_field_footer(&ob).unwrap();
1971 assert_eq!(num_cols, 1);
1972 let tocs = read_fast_field_toc(&ob, toc_off, num_cols).unwrap();
1973 assert_eq!(tocs.len(), 1);
1974 assert_eq!(tocs[0].field_id, 42);
1975
1976 let reader = FastFieldReader::open(&ob, &tocs[0]).unwrap();
1977 assert_eq!(reader.get_u64(0), 100);
1978 assert_eq!(reader.get_u64(1), 200);
1979 assert_eq!(reader.get_u64(2), 150);
1980 assert_eq!(reader.get_u64(3), FAST_FIELD_MISSING); assert_eq!(reader.get_u64(4), 300);
1982 }
1983
1984 fn assert_cursor_matches_scalar_reads(reader: &FastFieldReader) {
1985 let mut cursor = SingleValueCursor::new(reader);
1986 for start in [
1987 0,
1988 1,
1989 63,
1990 511,
1991 512,
1992 599,
1993 600,
1994 1023,
1995 2048,
1996 3,
1997 reader.num_docs,
1998 u32::MAX,
1999 ] {
2000 for len in [0, 1, 7, 64, 257] {
2001 let mut scratch = vec![12345; len];
2002 let read = cursor.read_batch(start, &mut scratch);
2003 assert!(read <= len);
2004 if start < reader.num_docs && len != 0 {
2005 assert!(read > 0);
2006 }
2007 for (offset, &raw) in scratch[..read].iter().enumerate() {
2008 assert_eq!(raw, reader.get_u64(start + offset as u32));
2009 }
2010 assert!(scratch[read..].iter().all(|&v| v == 12345));
2011 }
2012 }
2013 }
2014
2015 #[test]
2016 fn seekable_batches_preserve_scalar_values_across_codec_records_and_backward_reads() {
2017 for layout in 0..5 {
2018 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2019 for doc in 0..2053u32 {
2020 let block = u64::from(doc / 512);
2021 let local = u64::from(doc % 512);
2022 let value = match layout {
2023 0 => 7,
2024 1 => u64::from(doc) * 10,
2025 2 => u64::from(doc.wrapping_mul(40503) & 65535),
2026 3 => ((block * 40503) & 65535) * 65536 + local * (3 + block % 3) + local % 7,
2027 _ if doc % 7 == 0 => FAST_FIELD_MISSING,
2028 _ => u64::from(doc),
2029 };
2030 writer.add_u64(doc, value);
2031 }
2032 let mut bytes = Vec::new();
2033 let (toc, _) = writer.serialize(&mut bytes, 0).unwrap();
2034 let reader = FastFieldReader::open(&owned(bytes), &toc).unwrap();
2035 assert_cursor_matches_scalar_reads(&reader);
2036 }
2037 }
2038
2039 #[test]
2040 fn fallible_column_scan_stops_at_the_first_error_across_batch_boundaries() {
2041 let mut column = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2042 for doc in 0..1024 {
2043 column.add_u64(doc, u64::from(doc) * 17);
2044 }
2045 let mut bytes = Vec::new();
2046 let (toc, _) = column.serialize(&mut bytes, 0).unwrap();
2047 let reader = FastFieldReader::open(&owned(bytes), &toc).unwrap();
2048 let mut visited = 0;
2049 let error = reader
2050 .try_scan_single_values(|doc, value| {
2051 assert_eq!(doc, visited);
2052 assert_eq!(value, u64::from(doc) * 17);
2053 visited += 1;
2054 if doc == 257 { Err("cancelled") } else { Ok(()) }
2055 })
2056 .unwrap_err();
2057 assert_eq!(error, "cancelled");
2058 assert_eq!(visited, 258);
2059 }
2060
2061 #[test]
2062 fn cancellable_text_scans_preserve_global_ordinals_and_stop_at_the_requested_document() {
2063 let mut a = FastFieldWriter::new_text();
2064 let mut b = FastFieldWriter::new_text();
2065 for doc in 0..600 {
2066 if doc % 7 != 0 {
2067 a.add_text(
2068 doc,
2069 if doc % 2 == 0 {
2070 "book"
2071 } else {
2072 "journal-article"
2073 },
2074 );
2075 b.add_text(doc, if doc % 2 == 0 { "article" } else { "book" });
2076 }
2077 }
2078 a.pad_to(600);
2079 b.pad_to(600);
2080 let (data_a, dict_a, entry_a) = serialize_single_block(&mut a);
2081 let (data_b, dict_b, entry_b) = serialize_single_block(&mut b);
2082 let (buf, toc) = assemble_blocked_column(
2083 0,
2084 FastFieldColumnType::TextOrdinal,
2085 false,
2086 &[
2087 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2088 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2089 ],
2090 );
2091 let ob = owned(buf);
2092 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2093 assert_cursor_matches_scalar_reads(&reader);
2094 let expected: Vec<_> = (0..1200).map(|doc| (doc, reader.get_u64(doc))).collect();
2095 let mut complete = Vec::new();
2096 reader.scan_single_values(|doc, ordinal| complete.push((doc, ordinal)));
2097 assert_eq!(complete, expected);
2098 for stop in [0, 255, 256, 599, 600, 1024, 1199] {
2099 let mut visited = Vec::new();
2100 let outcome = reader.try_scan_single_values(|doc, ordinal| {
2101 visited.push((doc, ordinal));
2102 if doc == stop { Err(()) } else { Ok(()) }
2103 });
2104 assert!(outcome.is_err());
2105 assert_eq!(visited, expected[..=stop as usize]);
2106 }
2107 }
2108
2109 #[test]
2110 fn test_writer_reader_i64_roundtrip() {
2111 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
2112 writer.add_i64(0, -100);
2113 writer.add_i64(1, 50);
2114 writer.add_i64(2, 0);
2115 writer.pad_to(3);
2116
2117 let mut buf = Vec::new();
2118 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2119 let ob = owned(buf);
2120 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2121 assert_eq!(reader.get_i64(0), -100);
2122 assert_eq!(reader.get_i64(1), 50);
2123 assert_eq!(reader.get_i64(2), 0);
2124 }
2125
2126 #[test]
2127 fn test_writer_reader_f64_roundtrip() {
2128 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::F64);
2129 writer.add_f64(0, -1.5);
2130 writer.add_f64(1, 3.15);
2131 writer.add_f64(2, 0.0);
2132 writer.pad_to(3);
2133
2134 let mut buf = Vec::new();
2135 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2136 let ob = owned(buf);
2137 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2138 assert_eq!(reader.get_f64(0), -1.5);
2139 assert_eq!(reader.get_f64(1), 3.15);
2140 assert_eq!(reader.get_f64(2), 0.0);
2141 }
2142
2143 #[test]
2144 fn test_writer_reader_text_roundtrip() {
2145 let mut writer = FastFieldWriter::new_text();
2146 writer.add_text(0, "banana");
2147 writer.add_text(1, "apple");
2148 writer.add_text(2, "cherry");
2149 writer.add_text(3, "apple"); writer.pad_to(5);
2152
2153 let mut buf = Vec::new();
2154 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2155 let ob = owned(buf);
2156 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2157
2158 assert_eq!(reader.get_text(0), Some("banana"));
2160 assert_eq!(reader.get_text(1), Some("apple"));
2161 assert_eq!(reader.get_text(2), Some("cherry"));
2162 assert_eq!(reader.get_text(3), Some("apple"));
2163 assert_eq!(reader.get_text(4), None); assert_eq!(reader.text_ordinal("apple"), Some(0));
2167 assert_eq!(reader.text_ordinal("banana"), Some(1));
2168 assert_eq!(reader.text_ordinal("cherry"), Some(2));
2169 assert_eq!(reader.text_ordinal("durian"), None);
2170 }
2171
2172 #[test]
2173 fn test_constant_column() {
2174 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2175 for i in 0..100 {
2176 writer.add_u64(i, 42);
2177 }
2178
2179 let mut buf = Vec::new();
2180 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2181
2182 let ob = owned(buf);
2183 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2184 for i in 0..100 {
2185 assert_eq!(reader.get_u64(i), 42);
2186 }
2187 }
2188
2189 #[test]
2192 fn test_multi_value_u64_roundtrip() {
2193 let mut writer = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2194 writer.add_u64(0, 10);
2196 writer.add_u64(0, 20);
2197 writer.add_u64(0, 30);
2198 writer.add_u64(2, 100);
2201 writer.add_u64(3, 5);
2203 writer.add_u64(3, 15);
2204 writer.pad_to(4);
2205
2206 let mut buf = Vec::new();
2207 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2208 assert!(toc.multi);
2209 assert_eq!(toc.num_docs, 4);
2210
2211 let ob = owned(buf);
2212 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2213 assert!(reader.multi);
2214
2215 assert_eq!(reader.get_u64(0), 10);
2217 let (s, e) = reader.value_range(0);
2218 assert_eq!(e - s, 3);
2219 assert_eq!(reader.get_value_at(s), 10);
2220 assert_eq!(reader.get_value_at(s + 1), 20);
2221 assert_eq!(reader.get_value_at(s + 2), 30);
2222
2223 assert_eq!(reader.get_u64(1), FAST_FIELD_MISSING);
2225 let (s, e) = reader.value_range(1);
2226 assert_eq!(s, e);
2227 assert!(!reader.has_value(1));
2228
2229 assert_eq!(reader.get_u64(2), 100);
2231 assert!(reader.has_value(2));
2232
2233 assert_eq!(reader.get_u64(3), 5);
2235 let (s, e) = reader.value_range(3);
2236 assert_eq!(e - s, 2);
2237 assert_eq!(reader.get_value_at(s), 5);
2238 assert_eq!(reader.get_value_at(s + 1), 15);
2239 }
2240
2241 #[test]
2242 fn test_multi_value_text_roundtrip() {
2243 let mut writer = FastFieldWriter::new_text_multi();
2244 writer.add_text(0, "banana");
2246 writer.add_text(0, "apple");
2247 writer.add_text(1, "cherry");
2249 writer.pad_to(3);
2251
2252 let mut buf = Vec::new();
2253 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2254 assert!(toc.multi);
2255
2256 let ob = owned(buf);
2257 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2258
2259 let (s, e) = reader.value_range(0);
2261 assert_eq!(e - s, 2);
2262 let ord0 = reader.get_value_at(s);
2263 let ord1 = reader.get_value_at(s + 1);
2264 assert_eq!(reader.text_dict().unwrap().get(ord0 as u32), Some("banana"));
2265 assert_eq!(reader.text_dict().unwrap().get(ord1 as u32), Some("apple"));
2266
2267 let (s, e) = reader.value_range(1);
2269 assert_eq!(e - s, 1);
2270 let ord = reader.get_value_at(s);
2271 assert_eq!(reader.text_dict().unwrap().get(ord as u32), Some("cherry"));
2272
2273 assert!(!reader.has_value(2));
2275 }
2276
2277 #[test]
2278 fn test_multi_value_full_toc_roundtrip() {
2279 let mut writer = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2280 writer.add_u64(0, 1);
2281 writer.add_u64(0, 2);
2282 writer.add_u64(1, 3);
2283 writer.pad_to(2);
2284
2285 let mut buf = Vec::new();
2286 let (mut toc, _) = writer.serialize(&mut buf, 0).unwrap();
2287 toc.field_id = 7;
2288
2289 let toc_offset = buf.len() as u64;
2290 write_fast_field_toc_and_footer(&mut buf, toc_offset, &[toc]).unwrap();
2291
2292 let ob = owned(buf);
2293 let (toc_off, num_cols) = read_fast_field_footer(&ob).unwrap();
2294 let tocs = read_fast_field_toc(&ob, toc_off, num_cols).unwrap();
2295 assert_eq!(tocs[0].field_id, 7);
2296 assert!(tocs[0].multi);
2297
2298 let reader = FastFieldReader::open(&ob, &tocs[0]).unwrap();
2299 assert_eq!(reader.get_u64(0), 1);
2300 assert_eq!(reader.get_u64(1), 3);
2301 }
2302
2303 fn serialize_single_block(writer: &mut FastFieldWriter) -> (Vec<u8>, Vec<u8>, BlockIndexEntry) {
2306 let mut buf = Vec::new();
2307 let (_toc, _) = writer.serialize(&mut buf, 0).unwrap();
2308 let mut cursor = std::io::Cursor::new(&buf[4..4 + BLOCK_INDEX_ENTRY_SIZE]);
2310 let entry = BlockIndexEntry::read_from(&mut cursor).unwrap();
2311 let data_start = 4 + BLOCK_INDEX_ENTRY_SIZE;
2312 let data_end = data_start + entry.data_len as usize;
2313 let dict_end = data_end + entry.dict_len as usize;
2314 let data = buf[data_start..data_end].to_vec();
2315 let dict = if dict_end > data_end {
2316 buf[data_end..dict_end].to_vec()
2317 } else {
2318 Vec::new()
2319 };
2320 (data, dict, entry)
2321 }
2322
2323 fn assemble_blocked_column(
2325 field_id: u32,
2326 column_type: FastFieldColumnType,
2327 multi: bool,
2328 blocks: &[(u32, &[u8], u32, &[u8])], ) -> (Vec<u8>, FastFieldTocEntry) {
2330 use byteorder::{LittleEndian, WriteBytesExt};
2331
2332 let mut buf = Vec::new();
2333 let num_blocks = blocks.len() as u32;
2334
2335 buf.write_u32::<LittleEndian>(num_blocks).unwrap();
2337
2338 for &(num_docs, data, dict_count, dict) in blocks {
2340 let entry = BlockIndexEntry {
2341 num_docs,
2342 data_len: data.len() as u32,
2343 dict_count,
2344 dict_len: dict.len() as u32,
2345 };
2346 entry.write_to(&mut buf).unwrap();
2347 }
2348
2349 let mut total_docs = 0u32;
2351 for &(num_docs, data, _, dict) in blocks {
2352 buf.extend_from_slice(data);
2353 buf.extend_from_slice(dict);
2354 total_docs += num_docs;
2355 }
2356
2357 let data_len = buf.len() as u64;
2358
2359 let toc = FastFieldTocEntry {
2361 field_id,
2362 column_type,
2363 multi,
2364 data_offset: 0,
2365 data_len,
2366 num_docs: total_docs,
2367 dict_offset: 0,
2368 dict_count: 0,
2369 };
2370
2371 let toc_offset = buf.len() as u64;
2372 write_fast_field_toc_and_footer(&mut buf, toc_offset, std::slice::from_ref(&toc)).unwrap();
2373
2374 (buf, toc)
2375 }
2376
2377 #[test]
2378 fn test_multi_block_numeric_roundtrip() {
2379 let mut wa = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2381 wa.add_u64(0, 10);
2382 wa.add_u64(1, 20);
2383 wa.add_u64(2, 30);
2384 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2385
2386 let mut wb = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2388 wb.add_u64(0, 40);
2389 wb.add_u64(1, 50);
2390 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2391
2392 let (buf, toc) = assemble_blocked_column(
2393 1,
2394 FastFieldColumnType::U64,
2395 false,
2396 &[
2397 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2398 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2399 ],
2400 );
2401
2402 let ob = owned(buf);
2403 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2404
2405 assert_eq!(reader.num_docs, 5);
2406 assert_eq!(reader.num_blocks(), 2);
2407 assert_eq!(reader.get_u64(0), 10);
2408 assert_eq!(reader.get_u64(1), 20);
2409 assert_eq!(reader.get_u64(2), 30);
2410 assert_eq!(reader.get_u64(3), 40);
2411 assert_eq!(reader.get_u64(4), 50);
2412 }
2413
2414 #[test]
2415 fn test_multi_block_text_roundtrip() {
2416 let mut wa = FastFieldWriter::new_text();
2418 wa.add_text(0, "alpha");
2419 wa.add_text(1, "beta");
2420 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2421
2422 let mut wb = FastFieldWriter::new_text();
2424 wb.add_text(0, "gamma");
2425 wb.add_text(1, "alpha");
2426 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2427
2428 let (buf, toc) = assemble_blocked_column(
2429 2,
2430 FastFieldColumnType::TextOrdinal,
2431 false,
2432 &[
2433 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2434 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2435 ],
2436 );
2437
2438 let ob = owned(buf);
2439 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2440
2441 assert_eq!(reader.num_docs, 4);
2442 assert_eq!(reader.num_blocks(), 2);
2443
2444 assert_eq!(reader.text_dict().unwrap().len(), 3);
2446
2447 assert_eq!(reader.get_text(0), Some("alpha"));
2449 assert_eq!(reader.get_text(1), Some("beta"));
2450
2451 assert_eq!(reader.get_text(2), Some("gamma"));
2453 assert_eq!(reader.get_text(3), Some("alpha"));
2454
2455 assert_eq!(reader.text_ordinal("alpha"), Some(0));
2457 assert_eq!(reader.text_ordinal("beta"), Some(1));
2458 assert_eq!(reader.text_ordinal("gamma"), Some(2));
2459
2460 assert_eq!(reader.get_u64(0), 0); assert_eq!(reader.get_u64(1), 1); assert_eq!(reader.get_u64(2), 2); assert_eq!(reader.get_u64(3), 0); }
2466
2467 #[test]
2476 fn test_multi_block_text_ordinal_mismatch_regression() {
2477 let mut wa = FastFieldWriter::new_text();
2479 wa.add_text(0, "book");
2480 wa.add_text(1, "wiki");
2481 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2482
2483 let mut wb = FastFieldWriter::new_text();
2485 wb.add_text(0, "apple");
2486 wb.add_text(1, "wiki");
2487 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2488
2489 let (buf, toc) = assemble_blocked_column(
2490 2,
2491 FastFieldColumnType::TextOrdinal,
2492 false,
2493 &[
2494 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2495 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2496 ],
2497 );
2498
2499 let ob = owned(buf);
2500 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2501
2502 assert_eq!(reader.text_dict().unwrap().len(), 3);
2504 assert_eq!(reader.text_ordinal("apple"), Some(0));
2505 assert_eq!(reader.text_ordinal("book"), Some(1));
2506 assert_eq!(reader.text_ordinal("wiki"), Some(2));
2507
2508 assert_eq!(reader.get_u64(0), 1); assert_eq!(reader.get_u64(1), 2); assert_eq!(reader.get_u64(2), 0); assert_eq!(reader.get_u64(3), 2); let wiki_ord = reader.text_ordinal("wiki").unwrap();
2516 assert_eq!(reader.get_u64(1), wiki_ord, "wiki doc should match");
2517 assert_eq!(reader.get_u64(3), wiki_ord, "wiki doc should match");
2518 assert_ne!(reader.get_u64(0), wiki_ord, "book doc must NOT match wiki");
2519 assert_ne!(reader.get_u64(2), wiki_ord, "apple doc must NOT match wiki");
2520 }
2521
2522 #[test]
2525 fn test_i64_timestamps_with_missing_roundtrip() {
2526 let base_ts = 1724630400i64; let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
2528
2529 let mut expected_values: Vec<Option<i64>> = Vec::new();
2531 for i in 0..100u32 {
2532 if i % 5 == 0 {
2533 expected_values.push(None); } else {
2535 let ts = base_ts - (i as i64 * 86400);
2536 writer.add_i64(i, ts);
2537 expected_values.push(Some(ts));
2538 }
2539 }
2540 writer.pad_to(100);
2541
2542 let mut buf = Vec::new();
2543 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2544 let ob = owned(buf);
2545 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2546
2547 for (i, expected) in expected_values.iter().enumerate() {
2548 let raw = reader.get_u64(i as u32);
2549 match expected {
2550 None => {
2551 assert_eq!(
2552 raw, FAST_FIELD_MISSING,
2553 "doc {}: expected MISSING, got raw {}",
2554 i, raw
2555 );
2556 }
2557 Some(ts) => {
2558 assert_ne!(
2559 raw, FAST_FIELD_MISSING,
2560 "doc {}: expected timestamp {}, got MISSING",
2561 i, ts
2562 );
2563 let decoded = zigzag_decode(raw);
2564 assert_eq!(
2565 decoded,
2566 *ts,
2567 "doc {}: expected i64 {}, got i64 {} (raw zigzag: {}, expected zigzag: {})",
2568 i,
2569 ts,
2570 decoded,
2571 raw,
2572 zigzag_encode(*ts)
2573 );
2574 }
2575 }
2576 }
2577 }
2578
2579 #[test]
2582 fn test_issued_at_1724630400_various_sizes() {
2583 let target_ts = 1724630400i64;
2584 let target_zigzag = zigzag_encode(target_ts);
2585
2586 for num_docs in [2, 5, 10, 50, 100, 500, 1000, 2000] {
2587 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
2588 let target_doc = num_docs / 3;
2589
2590 for i in 0..num_docs as u32 {
2591 if i == target_doc as u32 {
2592 writer.add_i64(i, target_ts);
2593 } else if i % 3 == 0 {
2594 } else {
2596 let ts = 1700000000i64 + (i as i64 * 86400);
2597 writer.add_i64(i, ts);
2598 }
2599 }
2600 writer.pad_to(num_docs as u32);
2601
2602 let mut buf = Vec::new();
2603 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2604 let ob = owned(buf);
2605 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2606
2607 let raw = reader.get_u64(target_doc as u32);
2608 assert_eq!(
2609 raw,
2610 target_zigzag,
2611 "num_docs={}: doc {} expected zigzag {} (ts {}), got {} (decoded i64: {})",
2612 num_docs,
2613 target_doc,
2614 target_zigzag,
2615 target_ts,
2616 raw,
2617 zigzag_decode(raw)
2618 );
2619 }
2620 }
2621
2622 #[test]
2623 fn test_multi_block_multi_value_numeric() {
2624 let mut wa = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2626 wa.add_u64(0, 1);
2627 wa.add_u64(0, 2);
2628 wa.add_u64(1, 3);
2629 wa.pad_to(2);
2630 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2631
2632 let mut wb = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2634 wb.add_u64(0, 4);
2635 wb.add_u64(0, 5);
2636 wb.add_u64(0, 6);
2637 wb.pad_to(2);
2638 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2639
2640 let (buf, toc) = assemble_blocked_column(
2641 3,
2642 FastFieldColumnType::U64,
2643 true,
2644 &[
2645 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2646 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2647 ],
2648 );
2649
2650 let ob = owned(buf);
2651 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2652
2653 assert_eq!(reader.num_docs, 4);
2654 assert_eq!(reader.num_blocks(), 2);
2655
2656 assert_eq!(reader.get_multi_values(0), vec![1, 2]);
2658 assert_eq!(reader.get_multi_values(1), vec![3]);
2660 assert_eq!(reader.get_multi_values(2), vec![4, 5, 6]);
2662 assert_eq!(reader.get_multi_values(3), Vec::<u64>::new());
2664 }
2665}