1pub mod codec;
40
41use std::collections::BTreeMap;
42use std::io::{self, Read, Write};
43use std::sync::OnceLock;
44
45use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
46
47pub const FAST_FIELD_MAGIC: u32 = 0x32545346;
51
52pub const FAST_FIELD_FOOTER_SIZE: u64 = 16;
54
55pub const FAST_FIELD_MISSING: u64 = u64::MAX;
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68#[repr(u8)]
69pub enum FastFieldColumnType {
70 U64 = 0,
71 I64 = 1,
72 F64 = 2,
73 TextOrdinal = 3,
74}
75
76impl FastFieldColumnType {
77 pub fn from_u8(v: u8) -> Option<Self> {
78 match v {
79 0 => Some(Self::U64),
80 1 => Some(Self::I64),
81 2 => Some(Self::F64),
82 3 => Some(Self::TextOrdinal),
83 _ => None,
84 }
85 }
86}
87
88#[inline]
92pub fn zigzag_encode(v: i64) -> u64 {
93 ((v << 1) ^ (v >> 63)) as u64
94}
95
96#[inline]
98pub fn zigzag_decode(v: u64) -> i64 {
99 ((v >> 1) as i64) ^ -((v & 1) as i64)
100}
101
102#[inline]
106pub fn f64_to_sortable_u64(f: f64) -> u64 {
107 let bits = f.to_bits();
108 if (bits >> 63) == 0 {
109 bits ^ (1u64 << 63) } else {
111 !bits }
113}
114
115#[inline]
117pub fn sortable_u64_to_f64(v: u64) -> f64 {
118 let bits = if (v >> 63) != 0 {
119 v ^ (1u64 << 63) } else {
121 !v };
123 f64::from_bits(bits)
124}
125
126#[inline]
128pub fn bits_needed_u64(val: u64) -> u8 {
129 if val == 0 {
130 0
131 } else {
132 64 - val.leading_zeros() as u8
133 }
134}
135
136pub fn bitpack_write(values: &[u64], bits_per_value: u8, out: &mut Vec<u8>) {
141 if bits_per_value == 0 {
142 return; }
144 let bpv = bits_per_value as usize;
145 let total_bits = values.len() * bpv;
146 let total_bytes = total_bits.div_ceil(8);
147 out.reserve(total_bytes);
148
149 let start = out.len();
150 out.resize(start + total_bytes, 0);
151 let buf = &mut out[start..];
152
153 for (i, &val) in values.iter().enumerate() {
154 let bit_offset = i * bpv;
155 let byte_offset = bit_offset / 8;
156 let bit_shift = bit_offset % 8;
157
158 let mut remaining_bits = bpv;
160 let mut v = val;
161 let mut bo = byte_offset;
162 let mut bs = bit_shift;
163
164 while remaining_bits > 0 {
165 let can_write = (8 - bs).min(remaining_bits);
166 let mask = (1u64 << can_write) - 1;
167 buf[bo] |= ((v & mask) << bs) as u8;
168 v >>= can_write;
169 remaining_bits -= can_write;
170 bo += 1;
171 bs = 0;
172 }
173 }
174}
175
176#[inline]
182pub fn bitpack_read(data: &[u8], bits_per_value: u8, index: usize) -> u64 {
183 if bits_per_value == 0 {
184 return 0;
185 }
186 let bpv = bits_per_value as usize;
187 let bit_offset = index * bpv;
188 let byte_offset = bit_offset / 8;
189 let bit_shift = bit_offset % 8;
190
191 if bit_shift + bpv <= 64 && byte_offset + 8 <= data.len() {
194 let raw = u64::from_le_bytes(data[byte_offset..byte_offset + 8].try_into().unwrap());
195 let mask = if bpv >= 64 {
196 u64::MAX
197 } else {
198 (1u64 << bpv) - 1
199 };
200 return (raw >> bit_shift) & mask;
201 }
202
203 let mut result: u64 = 0;
205 let mut remaining_bits = bpv;
206 let mut bo = byte_offset;
207 let mut bs = bit_shift;
208 let mut out_shift = 0;
209
210 while remaining_bits > 0 {
211 let can_read = (8 - bs).min(remaining_bits);
212 let mask = ((1u64 << can_read) - 1) as u8;
213 let byte_val = if bo < data.len() { data[bo] } else { 0 };
214 result |= (((byte_val >> bs) & mask) as u64) << out_shift;
215 remaining_bits -= can_read;
216 out_shift += can_read;
217 bo += 1;
218 bs = 0;
219 }
220
221 result
222}
223
224#[derive(Debug, Clone)]
239pub struct FastFieldTocEntry {
240 pub field_id: u32,
241 pub column_type: FastFieldColumnType,
242 pub multi: bool,
243 pub data_offset: u64,
244 pub data_len: u64,
245 pub num_docs: u32,
246 pub dict_offset: u64,
248 pub dict_count: u32,
250}
251
252pub const FAST_FIELD_TOC_ENTRY_SIZE: usize = 4 + 1 + 1 + 8 + 8 + 4 + 8 + 4; #[derive(Debug, Clone)]
261pub struct BlockIndexEntry {
262 pub num_docs: u32,
263 pub data_len: u32,
264 pub dict_count: u32,
265 pub dict_len: u32,
266}
267
268pub const BLOCK_INDEX_ENTRY_SIZE: usize = 16;
269
270impl BlockIndexEntry {
271 pub fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
272 w.write_u32::<LittleEndian>(self.num_docs)?;
273 w.write_u32::<LittleEndian>(self.data_len)?;
274 w.write_u32::<LittleEndian>(self.dict_count)?;
275 w.write_u32::<LittleEndian>(self.dict_len)?;
276 Ok(())
277 }
278
279 pub fn read_from(r: &mut dyn Read) -> io::Result<Self> {
280 let num_docs = r.read_u32::<LittleEndian>()?;
281 let data_len = r.read_u32::<LittleEndian>()?;
282 let dict_count = r.read_u32::<LittleEndian>()?;
283 let dict_len = r.read_u32::<LittleEndian>()?;
284 Ok(Self {
285 num_docs,
286 data_len,
287 dict_count,
288 dict_len,
289 })
290 }
291}
292
293impl FastFieldTocEntry {
294 pub fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
295 w.write_u32::<LittleEndian>(self.field_id)?;
296 w.write_u8(self.column_type as u8)?;
297 let flags: u8 = if self.multi { 1 } else { 0 };
298 w.write_u8(flags)?;
299 w.write_u64::<LittleEndian>(self.data_offset)?;
300 w.write_u64::<LittleEndian>(self.data_len)?;
301 w.write_u32::<LittleEndian>(self.num_docs)?;
302 w.write_u64::<LittleEndian>(self.dict_offset)?;
303 w.write_u32::<LittleEndian>(self.dict_count)?;
304 Ok(())
305 }
306
307 pub fn read_from(r: &mut dyn Read) -> io::Result<Self> {
308 let field_id = r.read_u32::<LittleEndian>()?;
309 let ct = r.read_u8()?;
310 let column_type = FastFieldColumnType::from_u8(ct)
311 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "bad column type"))?;
312 let flags = r.read_u8()?;
313 if flags & !1 != 0 {
314 return Err(io::Error::new(
315 io::ErrorKind::InvalidData,
316 "unknown fast field flags",
317 ));
318 }
319 let multi = (flags & 1) != 0;
320 let data_offset = r.read_u64::<LittleEndian>()?;
321 let data_len = r.read_u64::<LittleEndian>()?;
322 let num_docs = r.read_u32::<LittleEndian>()?;
323 let dict_offset = r.read_u64::<LittleEndian>()?;
324 let dict_count = r.read_u32::<LittleEndian>()?;
325 Ok(Self {
326 field_id,
327 column_type,
328 multi,
329 data_offset,
330 data_len,
331 num_docs,
332 dict_offset,
333 dict_count,
334 })
335 }
336}
337
338pub struct FastFieldWriter {
346 pub column_type: FastFieldColumnType,
347 pub multi: bool,
349
350 values: Vec<u64>,
353
354 multi_values: Vec<u64>,
357 multi_offsets: Vec<u32>,
360 multi_current_doc: u32,
362
363 text_values: Option<BTreeMap<String, u32>>,
366 text_per_doc: Option<Vec<Option<String>>>,
368 text_multi_values: Option<Vec<String>>,
370}
371
372impl FastFieldWriter {
373 pub fn new_numeric(column_type: FastFieldColumnType) -> Self {
375 debug_assert!(matches!(
376 column_type,
377 FastFieldColumnType::U64 | FastFieldColumnType::I64 | FastFieldColumnType::F64
378 ));
379 Self {
380 column_type,
381 multi: false,
382 values: Vec::new(),
383 multi_values: Vec::new(),
384 multi_offsets: vec![0],
385 multi_current_doc: 0,
386 text_values: None,
387 text_per_doc: None,
388 text_multi_values: None,
389 }
390 }
391
392 pub fn new_numeric_multi(column_type: FastFieldColumnType) -> Self {
394 debug_assert!(matches!(
395 column_type,
396 FastFieldColumnType::U64 | FastFieldColumnType::I64 | FastFieldColumnType::F64
397 ));
398 Self {
399 column_type,
400 multi: true,
401 values: Vec::new(),
402 multi_values: Vec::new(),
403 multi_offsets: vec![0],
404 multi_current_doc: 0,
405 text_values: None,
406 text_per_doc: None,
407 text_multi_values: None,
408 }
409 }
410
411 pub fn new_text() -> Self {
413 Self {
414 column_type: FastFieldColumnType::TextOrdinal,
415 multi: false,
416 values: Vec::new(),
417 multi_values: Vec::new(),
418 multi_offsets: vec![0],
419 multi_current_doc: 0,
420 text_values: Some(BTreeMap::new()),
421 text_per_doc: Some(Vec::new()),
422 text_multi_values: None,
423 }
424 }
425
426 pub fn new_text_multi() -> Self {
428 Self {
429 column_type: FastFieldColumnType::TextOrdinal,
430 multi: true,
431 values: Vec::new(),
432 multi_values: Vec::new(),
433 multi_offsets: vec![0],
434 multi_current_doc: 0,
435 text_values: Some(BTreeMap::new()),
436 text_per_doc: None,
437 text_multi_values: Some(Vec::new()),
438 }
439 }
440
441 pub fn add_u64(&mut self, doc_id: u32, value: u64) {
444 if self.multi {
445 self.add_multi_u64(doc_id, value);
446 return;
447 }
448 let idx = doc_id as usize;
449 if idx >= self.values.len() {
450 self.values.resize(idx + 1, FAST_FIELD_MISSING);
451 if let Some(ref mut tpd) = self.text_per_doc {
452 tpd.resize(idx + 1, None);
453 }
454 }
455 self.values[idx] = value;
456 }
457
458 fn add_multi_u64(&mut self, doc_id: u32, value: u64) {
460 while self.multi_current_doc < doc_id {
462 self.multi_current_doc += 1;
463 self.multi_offsets.push(self.multi_values.len() as u32);
464 }
465 if self.multi_current_doc == doc_id && self.multi_offsets.len() == doc_id as usize + 1 {
467 }
469 self.multi_values.push(value);
470 }
471
472 pub fn add_i64(&mut self, doc_id: u32, value: i64) {
474 self.add_u64(doc_id, zigzag_encode(value));
475 }
476
477 pub fn add_f64(&mut self, doc_id: u32, value: f64) {
479 self.add_u64(doc_id, f64_to_sortable_u64(value));
480 }
481
482 pub fn add_text(&mut self, doc_id: u32, value: &str) {
484 if let Some(ref mut dict) = self.text_values {
485 let next_id = dict.len() as u32;
486 dict.entry(value.to_string()).or_insert(next_id);
487 }
488
489 if self.multi {
490 if let Some(ref mut tmv) = self.text_multi_values {
491 while self.multi_current_doc < doc_id {
493 self.multi_current_doc += 1;
494 self.multi_offsets.push(self.multi_values.len() as u32);
495 }
496 if self.multi_current_doc == doc_id
497 && self.multi_offsets.len() == doc_id as usize + 1
498 {
499 }
501 self.multi_values.push(0); tmv.push(value.to_string());
503 }
504 } else {
505 let idx = doc_id as usize;
506 if idx >= self.values.len() {
507 self.values.resize(idx + 1, FAST_FIELD_MISSING);
508 }
509 if let Some(ref mut tpd) = self.text_per_doc {
510 if idx >= tpd.len() {
511 tpd.resize(idx + 1, None);
512 }
513 tpd[idx] = Some(value.to_string());
514 }
515 }
516 }
517
518 pub fn pad_to(&mut self, num_docs: u32) {
523 let n = num_docs as usize;
524 if self.multi {
525 while (self.multi_offsets.len() as u32) <= num_docs {
526 self.multi_offsets.push(self.multi_values.len() as u32);
527 }
528 self.multi_current_doc = num_docs;
529 } else {
530 if self.values.len() < n {
531 self.values.resize(n, FAST_FIELD_MISSING);
532 if let Some(ref mut tpd) = self.text_per_doc {
533 tpd.resize(n, None);
534 }
535 }
536 }
537 }
538
539 pub fn num_docs(&self) -> u32 {
541 if self.multi {
542 (self.multi_offsets.len() as u32).saturating_sub(1)
544 } else {
545 self.values.len() as u32
546 }
547 }
548
549 pub fn serialize(
554 &mut self,
555 writer: &mut dyn Write,
556 data_offset: u64,
557 ) -> io::Result<(FastFieldTocEntry, u64)> {
558 if self.column_type == FastFieldColumnType::TextOrdinal {
560 self.resolve_text_ordinals();
561 }
562
563 let num_docs = self.num_docs();
564
565 let mut block_data = Vec::new();
567 if self.multi {
568 let offsets_u64: Vec<u64> = self.multi_offsets.iter().map(|&v| v as u64).collect();
570 let mut offset_buf = Vec::new();
571 codec::serialize_auto(&offsets_u64, &mut offset_buf)?;
572
573 block_data.write_u32::<LittleEndian>(offset_buf.len() as u32)?;
574 block_data.write_all(&offset_buf)?;
575
576 codec::serialize_auto(&self.multi_values, &mut block_data)?;
577 } else {
578 codec::serialize_auto(&self.values, &mut block_data)?;
579 }
580
581 let mut dict_buf = Vec::new();
583 let dict_count = if self.column_type == FastFieldColumnType::TextOrdinal {
584 let (count, _) = self.write_text_dictionary(&mut dict_buf)?;
585 count
586 } else {
587 0u32
588 };
589
590 let block_entry = BlockIndexEntry {
592 num_docs,
593 data_len: block_data.len() as u32,
594 dict_count,
595 dict_len: dict_buf.len() as u32,
596 };
597
598 let mut total_bytes = 0u64;
600
601 writer.write_u32::<LittleEndian>(1u32)?; total_bytes += 4;
603
604 block_entry.write_to(writer)?;
605 total_bytes += BLOCK_INDEX_ENTRY_SIZE as u64;
606
607 writer.write_all(&block_data)?;
608 total_bytes += block_data.len() as u64;
609
610 writer.write_all(&dict_buf)?;
611 total_bytes += dict_buf.len() as u64;
612
613 let toc = FastFieldTocEntry {
614 field_id: 0, column_type: self.column_type,
616 multi: self.multi,
617 data_offset,
618 data_len: total_bytes,
619 num_docs,
620 dict_offset: 0, dict_count: 0,
622 };
623
624 Ok((toc, total_bytes))
625 }
626
627 fn resolve_text_ordinals(&mut self) {
629 let dict = self.text_values.as_ref().expect("text_values required");
630
631 let sorted_ordinals: BTreeMap<&str, u64> = dict
633 .keys()
634 .enumerate()
635 .map(|(ord, key)| (key.as_str(), ord as u64))
636 .collect();
637
638 if self.multi {
639 if let Some(ref tmv) = self.text_multi_values {
641 for (i, text) in tmv.iter().enumerate() {
642 self.multi_values[i] = sorted_ordinals[text.as_str()];
643 }
644 }
645 } else {
646 let tpd = self.text_per_doc.as_ref().expect("text_per_doc required");
648 for (i, doc_text) in tpd.iter().enumerate() {
649 match doc_text {
650 Some(text) => {
651 self.values[i] = sorted_ordinals[text.as_str()];
652 }
653 None => {
654 self.values[i] = FAST_FIELD_MISSING;
655 }
656 }
657 }
658 }
659 }
660
661 fn write_text_dictionary(&self, writer: &mut dyn Write) -> io::Result<(u32, u64)> {
663 let dict = self.text_values.as_ref().expect("text_values required");
664 let mut bytes_written = 0u64;
665
666 let count = dict.len() as u32;
668 for key in dict.keys() {
669 let key_bytes = key.as_bytes();
670 writer.write_u32::<LittleEndian>(key_bytes.len() as u32)?;
671 writer.write_all(key_bytes)?;
672 bytes_written += 4 + key_bytes.len() as u64;
673 }
674
675 Ok((count, bytes_written))
676 }
677}
678
679use crate::directories::OwnedBytes;
682
683pub struct ColumnBlock {
687 pub cumulative_docs: u32,
689 pub num_docs: u32,
691 pub data: OwnedBytes,
693 pub offset_data: OwnedBytes,
695 pub value_data: OwnedBytes,
697 pub dict: Option<TextDictReader>,
699 pub raw_dict: OwnedBytes,
701}
702
703pub struct FastFieldReader {
714 pub column_type: FastFieldColumnType,
715 pub num_docs: u32,
716 pub multi: bool,
717
718 blocks: Vec<ColumnBlock>,
720
721 text_state: OnceLock<TextState>,
724}
725
726struct TextState {
728 global_dict: TextDictReader,
730 ordinal_maps: Vec<Vec<u32>>,
733}
734
735impl FastFieldReader {
736 pub fn open(file_data: &OwnedBytes, toc: &FastFieldTocEntry) -> io::Result<Self> {
741 let region_start = usize::try_from(toc.data_offset).map_err(|_| {
742 io::Error::new(
743 io::ErrorKind::InvalidData,
744 "fast field data offset exceeds address space",
745 )
746 })?;
747 let region_len = usize::try_from(toc.data_len).map_err(|_| {
748 io::Error::new(
749 io::ErrorKind::InvalidData,
750 "fast field data length exceeds address space",
751 )
752 })?;
753 let region_end = region_start.checked_add(region_len).ok_or_else(|| {
754 io::Error::new(io::ErrorKind::InvalidData, "fast field data range overflow")
755 })?;
756
757 if region_end > file_data.len() {
758 return Err(io::Error::new(
759 io::ErrorKind::UnexpectedEof,
760 "fast field data out of bounds",
761 ));
762 }
763
764 let raw = file_data.as_slice();
765
766 let mut pos = region_start;
768 if pos.checked_add(4).is_none_or(|end| end > region_end) {
769 return Err(io::Error::new(
770 io::ErrorKind::UnexpectedEof,
771 "fast field: missing num_blocks",
772 ));
773 }
774 let num_blocks = u32::from_le_bytes(raw[pos..pos + 4].try_into().unwrap());
775 pos += 4;
776
777 let idx_size = (num_blocks as usize)
779 .checked_mul(BLOCK_INDEX_ENTRY_SIZE)
780 .ok_or_else(|| {
781 io::Error::new(
782 io::ErrorKind::InvalidData,
783 "fast field block index overflow",
784 )
785 })?;
786 let index_end = pos.checked_add(idx_size).ok_or_else(|| {
787 io::Error::new(
788 io::ErrorKind::InvalidData,
789 "fast field block index overflow",
790 )
791 })?;
792 if index_end > region_end {
793 return Err(io::Error::new(
794 io::ErrorKind::UnexpectedEof,
795 "fast field: block index truncated",
796 ));
797 }
798 let mut block_entries = Vec::new();
799 block_entries
800 .try_reserve_exact(num_blocks as usize)
801 .map_err(|_| {
802 io::Error::new(io::ErrorKind::InvalidData, "too many fast field blocks")
803 })?;
804 {
805 let mut cursor = std::io::Cursor::new(&raw[pos..index_end]);
806 for _ in 0..num_blocks {
807 block_entries.push(BlockIndexEntry::read_from(&mut cursor)?);
808 }
809 }
810 pos = index_end;
811
812 let empty = OwnedBytes::new(Vec::new());
813
814 let mut blocks = Vec::new();
816 blocks.try_reserve_exact(num_blocks as usize).map_err(|_| {
817 io::Error::new(io::ErrorKind::InvalidData, "too many fast field blocks")
818 })?;
819 let mut cumulative = 0u32;
820
821 for entry in &block_entries {
822 let data_start = pos;
823 let data_end = data_start
824 .checked_add(entry.data_len as usize)
825 .ok_or_else(|| {
826 io::Error::new(
827 io::ErrorKind::InvalidData,
828 "fast field block range overflow",
829 )
830 })?;
831 let dict_start = data_end;
832 let dict_end = dict_start
833 .checked_add(entry.dict_len as usize)
834 .ok_or_else(|| {
835 io::Error::new(io::ErrorKind::InvalidData, "fast field dict range overflow")
836 })?;
837
838 if dict_end > region_end {
839 return Err(io::Error::new(
840 io::ErrorKind::UnexpectedEof,
841 "fast field: block data/dict truncated",
842 ));
843 }
844
845 let (block_data, offset_data, value_data) = if toc.multi {
847 let block_raw = &raw[data_start..data_end];
848 if block_raw.len() < 4 {
849 return Err(io::Error::new(
850 io::ErrorKind::UnexpectedEof,
851 "fast field multi-value header is truncated",
852 ));
853 }
854 let offset_col_len =
855 u32::from_le_bytes(block_raw[0..4].try_into().unwrap()) as usize;
856 let o_start = data_start + 4;
857 let o_end = o_start.checked_add(offset_col_len).ok_or_else(|| {
858 io::Error::new(
859 io::ErrorKind::InvalidData,
860 "fast field offset column range overflow",
861 )
862 })?;
863 if o_end > data_end {
864 return Err(io::Error::new(
865 io::ErrorKind::UnexpectedEof,
866 "fast field offset column is truncated",
867 ));
868 }
869 let v_start = o_end;
870 let v_end = data_end;
871 let offset_data = file_data.slice(o_start..o_end);
872 let value_data = file_data.slice(v_start..v_end);
873 let offset_count = (entry.num_docs as usize).checked_add(1).ok_or_else(|| {
874 io::Error::new(io::ErrorKind::InvalidData, "fast field doc count overflow")
875 })?;
876 codec::validate_auto(offset_data.as_slice(), offset_count)?;
877
878 let mut previous = 0u64;
879 for index in 0..offset_count {
880 let offset = codec::auto_read(offset_data.as_slice(), index);
881 if offset > u32::MAX as u64 || (index == 0 && offset != 0) || offset < previous
882 {
883 return Err(io::Error::new(
884 io::ErrorKind::InvalidData,
885 "fast field value offsets are invalid",
886 ));
887 }
888 previous = offset;
889 }
890 codec::validate_auto(value_data.as_slice(), previous as usize)?;
891
892 (
893 file_data.slice(data_start..data_end),
894 offset_data,
895 value_data,
896 )
897 } else {
898 let block_data = file_data.slice(data_start..data_end);
899 codec::validate_auto(block_data.as_slice(), entry.num_docs as usize)?;
900 (block_data, empty.clone(), empty.clone())
901 };
902
903 if toc.column_type == FastFieldColumnType::TextOrdinal {
904 if entry.dict_count == 0 && entry.dict_len != 0 {
905 return Err(io::Error::new(
906 io::ErrorKind::InvalidData,
907 "empty fast field dictionary has data",
908 ));
909 }
910 validate_text_dict_bytes(&raw[dict_start..dict_end], entry.dict_count)?;
911 } else if entry.dict_count != 0 || entry.dict_len != 0 {
912 return Err(io::Error::new(
913 io::ErrorKind::InvalidData,
914 "numeric fast field contains a text dictionary",
915 ));
916 }
917
918 let dict = if entry.dict_count > 0 {
920 Some(TextDictReader::new_lazy(
921 file_data.slice(dict_start..dict_end),
922 entry.dict_count,
923 ))
924 } else {
925 None
926 };
927
928 let raw_dict = if entry.dict_len > 0 {
929 file_data.slice(dict_start..dict_end)
930 } else {
931 empty.clone()
932 };
933
934 blocks.push(ColumnBlock {
935 cumulative_docs: cumulative,
936 num_docs: entry.num_docs,
937 data: block_data,
938 offset_data,
939 value_data,
940 dict,
941 raw_dict,
942 });
943
944 cumulative = cumulative.checked_add(entry.num_docs).ok_or_else(|| {
945 io::Error::new(io::ErrorKind::InvalidData, "fast field doc count overflow")
946 })?;
947 pos = dict_end;
948 }
949
950 if pos != region_end || cumulative != toc.num_docs {
951 return Err(io::Error::new(
952 io::ErrorKind::InvalidData,
953 "fast field block totals are inconsistent with the TOC",
954 ));
955 }
956 if toc.num_docs > 0 && blocks.is_empty() {
957 return Err(io::Error::new(
958 io::ErrorKind::InvalidData,
959 "non-empty fast field has no blocks",
960 ));
961 }
962
963 Ok(Self {
964 column_type: toc.column_type,
965 num_docs: toc.num_docs,
966 multi: toc.multi,
967 blocks,
968 text_state: OnceLock::new(),
969 })
970 }
971
972 fn ensure_text_state(&self) -> &TextState {
975 self.text_state
976 .get_or_init(|| Self::build_text_state(&self.blocks))
977 }
978
979 fn build_text_state(blocks: &[ColumnBlock]) -> TextState {
982 let blocks_with_dict = blocks.iter().filter(|b| b.dict.is_some()).count();
985 if blocks_with_dict <= 1 {
986 for block in blocks.iter() {
987 if let Some(ref dict) = block.dict {
988 return TextState {
990 global_dict: TextDictReader::new_lazy(block.raw_dict.clone(), dict.len()),
991 ordinal_maps: vec![Vec::new(); blocks.len()],
992 };
993 }
994 }
995 return TextState {
997 global_dict: TextDictReader::new_lazy(OwnedBytes::new(Vec::new()), 0),
998 ordinal_maps: vec![Vec::new(); blocks.len()],
999 };
1000 }
1001
1002 let mut unique_map: BTreeMap<String, u32> = BTreeMap::new();
1013 for block in blocks.iter() {
1014 if let Some(ref dict) = block.dict {
1015 for ord in 0..dict.len() {
1016 if let Some(text) = dict.get(ord) {
1017 unique_map.entry(text.to_string()).or_insert(0);
1018 }
1019 }
1020 }
1021 }
1022 for (i, value) in unique_map.values_mut().enumerate() {
1024 *value = i as u32;
1025 }
1026
1027 let mut ordinal_maps = Vec::with_capacity(blocks.len());
1029 for block in blocks.iter() {
1030 if let Some(ref dict) = block.dict {
1031 let mut map = Vec::with_capacity(dict.len() as usize);
1032 for local_ord in 0..dict.len() {
1033 let text = dict
1034 .get(local_ord)
1035 .expect("block dict ordinal out of range");
1036 let global_ord = *unique_map
1037 .get(text)
1038 .expect("block dict entry not found in merged global dict");
1039 map.push(global_ord);
1040 }
1041 ordinal_maps.push(map);
1042 } else {
1043 ordinal_maps.push(Vec::new());
1044 }
1045 }
1046
1047 let mut dict_buf = Vec::new();
1049 let count = unique_map.len() as u32;
1050 for s in unique_map.keys() {
1051 let bytes = s.as_bytes();
1052 dict_buf.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
1053 dict_buf.extend_from_slice(bytes);
1054 }
1055
1056 TextState {
1057 global_dict: TextDictReader::new_lazy(OwnedBytes::new(dict_buf), count),
1058 ordinal_maps,
1059 }
1060 }
1061
1062 #[inline]
1065 fn remap_ordinal(&self, block_idx: usize, raw: u64) -> u64 {
1066 if self.column_type == FastFieldColumnType::TextOrdinal
1067 && raw != FAST_FIELD_MISSING
1068 && self.blocks.len() > 1
1069 {
1070 let state = self.ensure_text_state();
1071 let map = &state.ordinal_maps[block_idx];
1072 if !map.is_empty() {
1073 let idx = raw as usize;
1074 if idx < map.len() {
1075 map[idx] as u64
1076 } else {
1077 FAST_FIELD_MISSING
1078 }
1079 } else {
1080 raw
1081 }
1082 } else {
1083 raw
1084 }
1085 }
1086
1087 #[inline]
1089 fn find_block(&self, doc_id: u32) -> (usize, u32) {
1090 debug_assert!(!self.blocks.is_empty());
1091 if self.blocks.len() == 1 {
1093 return (0, doc_id);
1094 }
1095 let bi = self
1097 .blocks
1098 .partition_point(|b| b.cumulative_docs <= doc_id)
1099 .saturating_sub(1);
1100 (bi, doc_id - self.blocks[bi].cumulative_docs)
1101 }
1102
1103 #[inline]
1111 pub fn get_u64(&self, doc_id: u32) -> u64 {
1112 if doc_id >= self.num_docs {
1113 return FAST_FIELD_MISSING;
1114 }
1115 let (bi, local) = self.find_block(doc_id);
1116 let block = &self.blocks[bi];
1117
1118 if self.multi {
1119 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1120 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1121 if start >= end {
1122 return FAST_FIELD_MISSING;
1123 }
1124 let raw = codec::auto_read(block.value_data.as_slice(), start as usize);
1125 return self.remap_ordinal(bi, raw);
1126 }
1127
1128 let raw = codec::auto_read(block.data.as_slice(), local as usize);
1129 self.remap_ordinal(bi, raw)
1130 }
1131
1132 #[inline]
1135 fn block_value_range(&self, doc_id: u32) -> (usize, u32, u32) {
1136 if !self.multi || doc_id >= self.num_docs {
1137 return (0, 0, 0);
1138 }
1139 let (bi, local) = self.find_block(doc_id);
1140 let block = &self.blocks[bi];
1141 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1142 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1143 (bi, start, end)
1144 }
1145
1146 #[inline]
1150 pub fn value_range(&self, doc_id: u32) -> (u32, u32) {
1151 let (_, start, end) = self.block_value_range(doc_id);
1152 (start, end)
1153 }
1154
1155 #[inline]
1158 pub fn get_value_at(&self, index: u32) -> u64 {
1159 if self.blocks.len() == 1 {
1161 let raw = codec::auto_read(self.blocks[0].value_data.as_slice(), index as usize);
1162 return self.remap_ordinal(0, raw);
1163 }
1164 0
1166 }
1167
1168 pub fn get_multi_values(&self, doc_id: u32) -> Vec<u64> {
1170 let (bi, start, end) = self.block_value_range(doc_id);
1171 if start >= end {
1172 return Vec::new();
1173 }
1174 let block = &self.blocks[bi];
1175 (start..end)
1176 .map(|idx| {
1177 let raw = codec::auto_read(block.value_data.as_slice(), idx as usize);
1178 self.remap_ordinal(bi, raw)
1179 })
1180 .collect()
1181 }
1182
1183 #[inline]
1186 pub fn for_each_multi_value(&self, doc_id: u32, mut f: impl FnMut(u64) -> bool) -> bool {
1187 let (bi, start, end) = self.block_value_range(doc_id);
1188 if start >= end {
1189 return false;
1190 }
1191 let block = &self.blocks[bi];
1192 for idx in start..end {
1193 let raw = codec::auto_read(block.value_data.as_slice(), idx as usize);
1194 if f(self.remap_ordinal(bi, raw)) {
1195 return true;
1196 }
1197 }
1198 false
1199 }
1200
1201 pub fn scan_single_values(&self, mut f: impl FnMut(u32, u64)) {
1208 if self.multi {
1209 return;
1210 }
1211 const BATCH: usize = 256;
1212 let mut buf = [0u64; BATCH];
1213 let needs_remap =
1214 self.column_type == FastFieldColumnType::TextOrdinal && self.blocks.len() > 1;
1215
1216 let ordinal_maps = if needs_remap {
1218 Some(&self.ensure_text_state().ordinal_maps)
1219 } else {
1220 None
1221 };
1222
1223 for (block_idx, block) in self.blocks.iter().enumerate() {
1224 let n = block.num_docs as usize;
1225 let mut pos = 0;
1226
1227 let map = ordinal_maps.map(|maps| &maps[block_idx]);
1228 let has_map = map.is_some_and(|m| !m.is_empty());
1229
1230 while pos < n {
1231 let chunk = (n - pos).min(BATCH);
1232 codec::auto_read_batch(block.data.as_slice(), pos, &mut buf[..chunk]);
1233
1234 if has_map {
1235 let map = map.unwrap();
1236 for (i, &raw) in buf[..chunk].iter().enumerate() {
1237 let val = if raw != FAST_FIELD_MISSING {
1238 let idx = raw as usize;
1239 if idx < map.len() {
1240 map[idx] as u64
1241 } else {
1242 FAST_FIELD_MISSING
1243 }
1244 } else {
1245 raw
1246 };
1247 f(block.cumulative_docs + pos as u32 + i as u32, val);
1248 }
1249 } else {
1250 for (i, &val) in buf[..chunk].iter().enumerate() {
1251 f(block.cumulative_docs + pos as u32 + i as u32, val);
1252 }
1253 }
1254 pos += chunk;
1255 }
1256 }
1257 }
1258
1259 #[inline]
1264 pub fn has_value(&self, doc_id: u32) -> bool {
1265 if !self.multi {
1266 return doc_id < self.num_docs && self.get_u64(doc_id) != FAST_FIELD_MISSING;
1267 }
1268 let (_, start, end) = self.block_value_range(doc_id);
1269 start < end
1270 }
1271
1272 #[inline]
1277 pub fn get_i64(&self, doc_id: u32) -> i64 {
1278 zigzag_decode(self.get_u64(doc_id))
1279 }
1280
1281 #[inline]
1286 pub fn get_f64(&self, doc_id: u32) -> f64 {
1287 sortable_u64_to_f64(self.get_u64(doc_id))
1288 }
1289
1290 #[inline]
1292 pub fn get_ordinal(&self, doc_id: u32) -> u64 {
1293 self.get_u64(doc_id)
1294 }
1295
1296 pub fn get_text(&self, doc_id: u32) -> Option<&str> {
1299 if doc_id >= self.num_docs {
1300 return None;
1301 }
1302 let (bi, local) = self.find_block(doc_id);
1303 let block = &self.blocks[bi];
1304 let raw_ordinal = if self.multi {
1305 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1306 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1307 if start >= end {
1308 return None;
1309 }
1310 codec::auto_read(block.value_data.as_slice(), start as usize)
1311 } else {
1312 codec::auto_read(block.data.as_slice(), local as usize)
1313 };
1314 if raw_ordinal == FAST_FIELD_MISSING {
1315 return None;
1316 }
1317 block.dict.as_ref().and_then(|d| d.get(raw_ordinal as u32))
1318 }
1319
1320 pub fn text_ordinal(&self, text: &str) -> Option<u64> {
1322 if self.column_type != FastFieldColumnType::TextOrdinal {
1323 return None;
1324 }
1325 self.ensure_text_state().global_dict.ordinal(text)
1326 }
1327
1328 pub fn text_dict(&self) -> Option<&TextDictReader> {
1330 if self.column_type != FastFieldColumnType::TextOrdinal {
1331 return None;
1332 }
1333 Some(&self.ensure_text_state().global_dict)
1334 }
1335
1336 pub fn num_blocks(&self) -> usize {
1338 self.blocks.len()
1339 }
1340
1341 pub fn blocks(&self) -> &[ColumnBlock] {
1343 &self.blocks
1344 }
1345}
1346
1347pub struct TextDictReader {
1355 data: OwnedBytes,
1357 count: u32,
1359 offsets: OnceLock<Vec<(u32, u32)>>,
1361}
1362
1363impl TextDictReader {
1364 fn new_lazy(data: OwnedBytes, count: u32) -> Self {
1367 Self {
1368 data,
1369 count,
1370 offsets: OnceLock::new(),
1371 }
1372 }
1373
1374 pub fn open(file_data: &OwnedBytes, dict_start: usize, count: u32) -> io::Result<Self> {
1377 if count == 0 {
1378 return Ok(Self::new_lazy(OwnedBytes::new(Vec::new()), 0));
1379 }
1380 let dict_slice = file_data.as_slice();
1382 if dict_start > dict_slice.len() {
1383 return Err(io::Error::new(
1384 io::ErrorKind::UnexpectedEof,
1385 "text dict offset out of bounds",
1386 ));
1387 }
1388 let mut pos = dict_start;
1389 for _ in 0..count {
1390 if pos.checked_add(4).is_none_or(|end| end > dict_slice.len()) {
1391 return Err(io::Error::new(
1392 io::ErrorKind::UnexpectedEof,
1393 "text dict truncated",
1394 ));
1395 }
1396 let len = u32::from_le_bytes(dict_slice[pos..pos + 4].try_into().unwrap()) as usize;
1397 pos += 4;
1398 if pos
1399 .checked_add(len)
1400 .is_none_or(|end| end > dict_slice.len())
1401 {
1402 return Err(io::Error::new(
1403 io::ErrorKind::UnexpectedEof,
1404 "text dict entry truncated",
1405 ));
1406 }
1407 std::str::from_utf8(&dict_slice[pos..pos + len])
1408 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
1409 pos += len;
1410 }
1411 let data = file_data.slice(dict_start..pos);
1412 Ok(Self::new_lazy(data, count))
1413 }
1414
1415 pub fn open_from_raw(raw_dict: &OwnedBytes, count: u32) -> io::Result<Self> {
1417 validate_text_dict_bytes(raw_dict.as_slice(), count)?;
1418 Ok(Self::new_lazy(raw_dict.clone(), count))
1419 }
1420
1421 #[inline]
1423 fn ensure_offsets(&self) -> &[(u32, u32)] {
1424 self.offsets.get_or_init(|| {
1425 let dict_slice = self.data.as_slice();
1426 let mut pos = 0usize;
1427 let mut offsets = Vec::with_capacity(self.count as usize);
1428 for _ in 0..self.count {
1429 debug_assert!(
1430 pos + 4 <= dict_slice.len(),
1431 "text dict truncated during lazy init"
1432 );
1433 let len = u32::from_le_bytes(dict_slice[pos..pos + 4].try_into().unwrap()) as usize;
1434 pos += 4;
1435 debug_assert!(
1436 pos + len <= dict_slice.len(),
1437 "text dict entry truncated during lazy init"
1438 );
1439 offsets.push((pos as u32, len as u32));
1440 pos += len;
1441 }
1442 offsets
1443 })
1444 }
1445
1446 pub fn get(&self, ordinal: u32) -> Option<&str> {
1448 let offsets = self.ensure_offsets();
1449 let &(off, len) = offsets.get(ordinal as usize)?;
1450 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1451 std::str::from_utf8(slice).ok()
1452 }
1453
1454 pub fn ordinal(&self, text: &str) -> Option<u64> {
1456 let offsets = self.ensure_offsets();
1457 offsets
1458 .binary_search_by(|&(off, len)| {
1459 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1460 std::str::from_utf8(slice).unwrap_or("").cmp(text)
1461 })
1462 .ok()
1463 .map(|i| i as u64)
1464 }
1465
1466 pub fn len(&self) -> u32 {
1468 self.count
1469 }
1470
1471 pub fn is_empty(&self) -> bool {
1473 self.count == 0
1474 }
1475
1476 pub fn iter(&self) -> impl Iterator<Item = &str> {
1478 let offsets = self.ensure_offsets();
1479 offsets.iter().map(|&(off, len)| {
1480 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1481 std::str::from_utf8(slice).unwrap_or("")
1482 })
1483 }
1484}
1485
1486fn validate_text_dict_bytes(data: &[u8], count: u32) -> io::Result<()> {
1487 let minimum = (count as usize).checked_mul(4).ok_or_else(|| {
1488 io::Error::new(io::ErrorKind::InvalidData, "text dictionary size overflow")
1489 })?;
1490 if minimum > data.len() {
1491 return Err(io::Error::new(
1492 io::ErrorKind::UnexpectedEof,
1493 "text dictionary entry table is truncated",
1494 ));
1495 }
1496
1497 let mut pos = 0usize;
1498 let mut previous: Option<&str> = None;
1499 for _ in 0..count {
1500 let len_end = pos.checked_add(4).ok_or_else(|| {
1501 io::Error::new(
1502 io::ErrorKind::InvalidData,
1503 "text dictionary offset overflow",
1504 )
1505 })?;
1506 let len = u32::from_le_bytes(data[pos..len_end].try_into().unwrap()) as usize;
1507 pos = len_end;
1508 let end = pos.checked_add(len).ok_or_else(|| {
1509 io::Error::new(
1510 io::ErrorKind::InvalidData,
1511 "text dictionary offset overflow",
1512 )
1513 })?;
1514 if end > data.len() {
1515 return Err(io::Error::new(
1516 io::ErrorKind::UnexpectedEof,
1517 "text dictionary entry is truncated",
1518 ));
1519 }
1520 let value = std::str::from_utf8(&data[pos..end])
1521 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
1522 if previous.is_some_and(|previous| previous >= value) {
1523 return Err(io::Error::new(
1524 io::ErrorKind::InvalidData,
1525 "text dictionary entries are not strictly increasing",
1526 ));
1527 }
1528 previous = Some(value);
1529 pos = end;
1530 }
1531 if pos != data.len() {
1532 return Err(io::Error::new(
1533 io::ErrorKind::InvalidData,
1534 "text dictionary contains trailing data",
1535 ));
1536 }
1537 Ok(())
1538}
1539
1540pub fn write_fast_field_toc_and_footer(
1544 writer: &mut dyn Write,
1545 toc_offset: u64,
1546 entries: &[FastFieldTocEntry],
1547) -> io::Result<()> {
1548 for e in entries {
1549 e.write_to(writer)?;
1550 }
1551 writer.write_u64::<LittleEndian>(toc_offset)?;
1552 writer.write_u32::<LittleEndian>(entries.len() as u32)?;
1553 writer.write_u32::<LittleEndian>(FAST_FIELD_MAGIC)?;
1554 Ok(())
1555}
1556
1557pub fn read_fast_field_footer(file_data: &[u8]) -> io::Result<(u64, u32)> {
1560 let len = file_data.len();
1561 if len < FAST_FIELD_FOOTER_SIZE as usize {
1562 return Err(io::Error::new(
1563 io::ErrorKind::UnexpectedEof,
1564 "fast field file too small for footer",
1565 ));
1566 }
1567 let footer = &file_data[len - FAST_FIELD_FOOTER_SIZE as usize..];
1568 let mut cursor = std::io::Cursor::new(footer);
1569 let toc_offset = cursor.read_u64::<LittleEndian>()?;
1570 let num_columns = cursor.read_u32::<LittleEndian>()?;
1571 let magic = cursor.read_u32::<LittleEndian>()?;
1572 if magic != FAST_FIELD_MAGIC {
1573 return Err(io::Error::new(
1574 io::ErrorKind::InvalidData,
1575 format!("bad fast field magic: 0x{:08x}", magic),
1576 ));
1577 }
1578 Ok((toc_offset, num_columns))
1579}
1580
1581pub fn read_fast_field_toc(
1583 file_data: &[u8],
1584 toc_offset: u64,
1585 num_columns: u32,
1586) -> io::Result<Vec<FastFieldTocEntry>> {
1587 let start = usize::try_from(toc_offset).map_err(|_| {
1588 io::Error::new(
1589 io::ErrorKind::InvalidData,
1590 "fast field TOC offset exceeds address space",
1591 )
1592 })?;
1593 let expected = (num_columns as usize)
1594 .checked_mul(FAST_FIELD_TOC_ENTRY_SIZE)
1595 .ok_or_else(|| {
1596 io::Error::new(io::ErrorKind::InvalidData, "fast field TOC size overflow")
1597 })?;
1598 let end = start.checked_add(expected).ok_or_else(|| {
1599 io::Error::new(io::ErrorKind::InvalidData, "fast field TOC range overflow")
1600 })?;
1601 if end > file_data.len() {
1602 return Err(io::Error::new(
1603 io::ErrorKind::UnexpectedEof,
1604 "fast field TOC out of bounds",
1605 ));
1606 }
1607 let mut cursor = std::io::Cursor::new(&file_data[start..end]);
1608 let mut entries = Vec::new();
1609 entries
1610 .try_reserve_exact(num_columns as usize)
1611 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "too many fast field columns"))?;
1612 for _ in 0..num_columns {
1613 entries.push(FastFieldTocEntry::read_from(&mut cursor)?);
1614 }
1615 Ok(entries)
1616}
1617
1618#[cfg(test)]
1621mod tests {
1622 use super::*;
1623
1624 #[test]
1625 fn test_zigzag_roundtrip() {
1626 for v in [0i64, 1, -1, 42, -42, i64::MAX, i64::MIN] {
1627 assert_eq!(zigzag_decode(zigzag_encode(v)), v);
1628 }
1629 }
1630
1631 #[test]
1632 fn test_f64_sortable_roundtrip() {
1633 for v in [0.0f64, 1.0, -1.0, f64::MAX, f64::MIN, f64::MIN_POSITIVE] {
1634 assert_eq!(sortable_u64_to_f64(f64_to_sortable_u64(v)), v);
1635 }
1636 }
1637
1638 #[test]
1639 fn test_f64_sortable_order() {
1640 let values = [-100.0f64, -1.0, -0.0, 0.0, 0.5, 1.0, 100.0];
1641 let encoded: Vec<u64> = values.iter().map(|&v| f64_to_sortable_u64(v)).collect();
1642 for i in 1..encoded.len() {
1643 assert!(
1644 encoded[i] >= encoded[i - 1],
1645 "{} >= {} failed for {} vs {}",
1646 encoded[i],
1647 encoded[i - 1],
1648 values[i],
1649 values[i - 1]
1650 );
1651 }
1652 }
1653
1654 #[test]
1655 fn test_bitpack_roundtrip() {
1656 let values: Vec<u64> = vec![0, 3, 7, 15, 0, 1, 6, 12];
1657 let bpv = 4u8;
1658 let mut packed = Vec::new();
1659 bitpack_write(&values, bpv, &mut packed);
1660
1661 for (i, &expected) in values.iter().enumerate() {
1662 let got = bitpack_read(&packed, bpv, i);
1663 assert_eq!(got, expected, "index {}", i);
1664 }
1665 }
1666
1667 #[test]
1668 fn test_bitpack_high_bpv_regression() {
1669 for bpv in [57u8, 58, 59, 60, 63, 64] {
1672 let max_val = if bpv == 64 {
1673 u64::MAX
1674 } else {
1675 (1u64 << bpv) - 1
1676 };
1677 let values: Vec<u64> = (0..32)
1678 .map(|i: u64| {
1679 if max_val == u64::MAX {
1680 i * 7
1681 } else {
1682 (i * 7) % (max_val + 1)
1683 }
1684 })
1685 .collect();
1686 let mut packed = Vec::new();
1687 bitpack_write(&values, bpv, &mut packed);
1688 for (i, &expected) in values.iter().enumerate() {
1689 let got = bitpack_read(&packed, bpv, i);
1690 assert_eq!(got, expected, "high bpv={} index={}", bpv, i);
1691 }
1692 }
1693 }
1694
1695 #[test]
1696 fn test_bitpack_various_widths() {
1697 for bpv in [1u8, 2, 3, 5, 7, 8, 13, 16, 32, 64] {
1698 let max_val = if bpv == 64 {
1699 u64::MAX
1700 } else {
1701 (1u64 << bpv) - 1
1702 };
1703 let values: Vec<u64> = (0..100)
1704 .map(|i: u64| {
1705 if max_val == u64::MAX {
1706 i
1707 } else {
1708 i % (max_val + 1)
1709 }
1710 })
1711 .collect();
1712 let mut packed = Vec::new();
1713 bitpack_write(&values, bpv, &mut packed);
1714
1715 for (i, &expected) in values.iter().enumerate() {
1716 let got = bitpack_read(&packed, bpv, i);
1717 assert_eq!(got, expected, "bpv={} index={}", bpv, i);
1718 }
1719 }
1720 }
1721
1722 fn owned(buf: Vec<u8>) -> OwnedBytes {
1724 OwnedBytes::new(buf)
1725 }
1726
1727 #[test]
1728 fn test_writer_reader_u64_roundtrip() {
1729 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
1730 writer.add_u64(0, 100);
1731 writer.add_u64(1, 200);
1732 writer.add_u64(2, 150);
1733 writer.add_u64(4, 300); writer.pad_to(5);
1735
1736 let mut buf = Vec::new();
1737 let (mut toc, _bytes) = writer.serialize(&mut buf, 0).unwrap();
1738 toc.field_id = 42;
1739
1740 let toc_offset = buf.len() as u64;
1742 write_fast_field_toc_and_footer(&mut buf, toc_offset, &[toc]).unwrap();
1743
1744 let ob = owned(buf);
1746 let (toc_off, num_cols) = read_fast_field_footer(&ob).unwrap();
1747 assert_eq!(num_cols, 1);
1748 let tocs = read_fast_field_toc(&ob, toc_off, num_cols).unwrap();
1749 assert_eq!(tocs.len(), 1);
1750 assert_eq!(tocs[0].field_id, 42);
1751
1752 let reader = FastFieldReader::open(&ob, &tocs[0]).unwrap();
1753 assert_eq!(reader.get_u64(0), 100);
1754 assert_eq!(reader.get_u64(1), 200);
1755 assert_eq!(reader.get_u64(2), 150);
1756 assert_eq!(reader.get_u64(3), FAST_FIELD_MISSING); assert_eq!(reader.get_u64(4), 300);
1758 }
1759
1760 #[test]
1761 fn test_writer_reader_i64_roundtrip() {
1762 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
1763 writer.add_i64(0, -100);
1764 writer.add_i64(1, 50);
1765 writer.add_i64(2, 0);
1766 writer.pad_to(3);
1767
1768 let mut buf = Vec::new();
1769 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1770 let ob = owned(buf);
1771 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1772 assert_eq!(reader.get_i64(0), -100);
1773 assert_eq!(reader.get_i64(1), 50);
1774 assert_eq!(reader.get_i64(2), 0);
1775 }
1776
1777 #[test]
1778 fn test_writer_reader_f64_roundtrip() {
1779 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::F64);
1780 writer.add_f64(0, -1.5);
1781 writer.add_f64(1, 3.15);
1782 writer.add_f64(2, 0.0);
1783 writer.pad_to(3);
1784
1785 let mut buf = Vec::new();
1786 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1787 let ob = owned(buf);
1788 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1789 assert_eq!(reader.get_f64(0), -1.5);
1790 assert_eq!(reader.get_f64(1), 3.15);
1791 assert_eq!(reader.get_f64(2), 0.0);
1792 }
1793
1794 #[test]
1795 fn test_writer_reader_text_roundtrip() {
1796 let mut writer = FastFieldWriter::new_text();
1797 writer.add_text(0, "banana");
1798 writer.add_text(1, "apple");
1799 writer.add_text(2, "cherry");
1800 writer.add_text(3, "apple"); writer.pad_to(5);
1803
1804 let mut buf = Vec::new();
1805 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1806 let ob = owned(buf);
1807 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1808
1809 assert_eq!(reader.get_text(0), Some("banana"));
1811 assert_eq!(reader.get_text(1), Some("apple"));
1812 assert_eq!(reader.get_text(2), Some("cherry"));
1813 assert_eq!(reader.get_text(3), Some("apple"));
1814 assert_eq!(reader.get_text(4), None); assert_eq!(reader.text_ordinal("apple"), Some(0));
1818 assert_eq!(reader.text_ordinal("banana"), Some(1));
1819 assert_eq!(reader.text_ordinal("cherry"), Some(2));
1820 assert_eq!(reader.text_ordinal("durian"), None);
1821 }
1822
1823 #[test]
1824 fn test_constant_column() {
1825 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
1826 for i in 0..100 {
1827 writer.add_u64(i, 42);
1828 }
1829
1830 let mut buf = Vec::new();
1831 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1832
1833 let ob = owned(buf);
1834 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1835 for i in 0..100 {
1836 assert_eq!(reader.get_u64(i), 42);
1837 }
1838 }
1839
1840 #[test]
1843 fn test_multi_value_u64_roundtrip() {
1844 let mut writer = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
1845 writer.add_u64(0, 10);
1847 writer.add_u64(0, 20);
1848 writer.add_u64(0, 30);
1849 writer.add_u64(2, 100);
1852 writer.add_u64(3, 5);
1854 writer.add_u64(3, 15);
1855 writer.pad_to(4);
1856
1857 let mut buf = Vec::new();
1858 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1859 assert!(toc.multi);
1860 assert_eq!(toc.num_docs, 4);
1861
1862 let ob = owned(buf);
1863 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1864 assert!(reader.multi);
1865
1866 assert_eq!(reader.get_u64(0), 10);
1868 let (s, e) = reader.value_range(0);
1869 assert_eq!(e - s, 3);
1870 assert_eq!(reader.get_value_at(s), 10);
1871 assert_eq!(reader.get_value_at(s + 1), 20);
1872 assert_eq!(reader.get_value_at(s + 2), 30);
1873
1874 assert_eq!(reader.get_u64(1), FAST_FIELD_MISSING);
1876 let (s, e) = reader.value_range(1);
1877 assert_eq!(s, e);
1878 assert!(!reader.has_value(1));
1879
1880 assert_eq!(reader.get_u64(2), 100);
1882 assert!(reader.has_value(2));
1883
1884 assert_eq!(reader.get_u64(3), 5);
1886 let (s, e) = reader.value_range(3);
1887 assert_eq!(e - s, 2);
1888 assert_eq!(reader.get_value_at(s), 5);
1889 assert_eq!(reader.get_value_at(s + 1), 15);
1890 }
1891
1892 #[test]
1893 fn test_multi_value_text_roundtrip() {
1894 let mut writer = FastFieldWriter::new_text_multi();
1895 writer.add_text(0, "banana");
1897 writer.add_text(0, "apple");
1898 writer.add_text(1, "cherry");
1900 writer.pad_to(3);
1902
1903 let mut buf = Vec::new();
1904 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1905 assert!(toc.multi);
1906
1907 let ob = owned(buf);
1908 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1909
1910 let (s, e) = reader.value_range(0);
1912 assert_eq!(e - s, 2);
1913 let ord0 = reader.get_value_at(s);
1914 let ord1 = reader.get_value_at(s + 1);
1915 assert_eq!(reader.text_dict().unwrap().get(ord0 as u32), Some("banana"));
1916 assert_eq!(reader.text_dict().unwrap().get(ord1 as u32), Some("apple"));
1917
1918 let (s, e) = reader.value_range(1);
1920 assert_eq!(e - s, 1);
1921 let ord = reader.get_value_at(s);
1922 assert_eq!(reader.text_dict().unwrap().get(ord as u32), Some("cherry"));
1923
1924 assert!(!reader.has_value(2));
1926 }
1927
1928 #[test]
1929 fn test_multi_value_full_toc_roundtrip() {
1930 let mut writer = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
1931 writer.add_u64(0, 1);
1932 writer.add_u64(0, 2);
1933 writer.add_u64(1, 3);
1934 writer.pad_to(2);
1935
1936 let mut buf = Vec::new();
1937 let (mut toc, _) = writer.serialize(&mut buf, 0).unwrap();
1938 toc.field_id = 7;
1939
1940 let toc_offset = buf.len() as u64;
1941 write_fast_field_toc_and_footer(&mut buf, toc_offset, &[toc]).unwrap();
1942
1943 let ob = owned(buf);
1944 let (toc_off, num_cols) = read_fast_field_footer(&ob).unwrap();
1945 let tocs = read_fast_field_toc(&ob, toc_off, num_cols).unwrap();
1946 assert_eq!(tocs[0].field_id, 7);
1947 assert!(tocs[0].multi);
1948
1949 let reader = FastFieldReader::open(&ob, &tocs[0]).unwrap();
1950 assert_eq!(reader.get_u64(0), 1);
1951 assert_eq!(reader.get_u64(1), 3);
1952 }
1953
1954 fn serialize_single_block(writer: &mut FastFieldWriter) -> (Vec<u8>, Vec<u8>, BlockIndexEntry) {
1957 let mut buf = Vec::new();
1958 let (_toc, _) = writer.serialize(&mut buf, 0).unwrap();
1959 let mut cursor = std::io::Cursor::new(&buf[4..4 + BLOCK_INDEX_ENTRY_SIZE]);
1961 let entry = BlockIndexEntry::read_from(&mut cursor).unwrap();
1962 let data_start = 4 + BLOCK_INDEX_ENTRY_SIZE;
1963 let data_end = data_start + entry.data_len as usize;
1964 let dict_end = data_end + entry.dict_len as usize;
1965 let data = buf[data_start..data_end].to_vec();
1966 let dict = if dict_end > data_end {
1967 buf[data_end..dict_end].to_vec()
1968 } else {
1969 Vec::new()
1970 };
1971 (data, dict, entry)
1972 }
1973
1974 fn assemble_blocked_column(
1976 field_id: u32,
1977 column_type: FastFieldColumnType,
1978 multi: bool,
1979 blocks: &[(u32, &[u8], u32, &[u8])], ) -> (Vec<u8>, FastFieldTocEntry) {
1981 use byteorder::{LittleEndian, WriteBytesExt};
1982
1983 let mut buf = Vec::new();
1984 let num_blocks = blocks.len() as u32;
1985
1986 buf.write_u32::<LittleEndian>(num_blocks).unwrap();
1988
1989 for &(num_docs, data, dict_count, dict) in blocks {
1991 let entry = BlockIndexEntry {
1992 num_docs,
1993 data_len: data.len() as u32,
1994 dict_count,
1995 dict_len: dict.len() as u32,
1996 };
1997 entry.write_to(&mut buf).unwrap();
1998 }
1999
2000 let mut total_docs = 0u32;
2002 for &(num_docs, data, _, dict) in blocks {
2003 buf.extend_from_slice(data);
2004 buf.extend_from_slice(dict);
2005 total_docs += num_docs;
2006 }
2007
2008 let data_len = buf.len() as u64;
2009
2010 let toc = FastFieldTocEntry {
2012 field_id,
2013 column_type,
2014 multi,
2015 data_offset: 0,
2016 data_len,
2017 num_docs: total_docs,
2018 dict_offset: 0,
2019 dict_count: 0,
2020 };
2021
2022 let toc_offset = buf.len() as u64;
2023 write_fast_field_toc_and_footer(&mut buf, toc_offset, std::slice::from_ref(&toc)).unwrap();
2024
2025 (buf, toc)
2026 }
2027
2028 #[test]
2029 fn test_multi_block_numeric_roundtrip() {
2030 let mut wa = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2032 wa.add_u64(0, 10);
2033 wa.add_u64(1, 20);
2034 wa.add_u64(2, 30);
2035 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2036
2037 let mut wb = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2039 wb.add_u64(0, 40);
2040 wb.add_u64(1, 50);
2041 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2042
2043 let (buf, toc) = assemble_blocked_column(
2044 1,
2045 FastFieldColumnType::U64,
2046 false,
2047 &[
2048 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2049 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2050 ],
2051 );
2052
2053 let ob = owned(buf);
2054 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2055
2056 assert_eq!(reader.num_docs, 5);
2057 assert_eq!(reader.num_blocks(), 2);
2058 assert_eq!(reader.get_u64(0), 10);
2059 assert_eq!(reader.get_u64(1), 20);
2060 assert_eq!(reader.get_u64(2), 30);
2061 assert_eq!(reader.get_u64(3), 40);
2062 assert_eq!(reader.get_u64(4), 50);
2063 }
2064
2065 #[test]
2066 fn test_multi_block_text_roundtrip() {
2067 let mut wa = FastFieldWriter::new_text();
2069 wa.add_text(0, "alpha");
2070 wa.add_text(1, "beta");
2071 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2072
2073 let mut wb = FastFieldWriter::new_text();
2075 wb.add_text(0, "gamma");
2076 wb.add_text(1, "alpha");
2077 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2078
2079 let (buf, toc) = assemble_blocked_column(
2080 2,
2081 FastFieldColumnType::TextOrdinal,
2082 false,
2083 &[
2084 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2085 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2086 ],
2087 );
2088
2089 let ob = owned(buf);
2090 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2091
2092 assert_eq!(reader.num_docs, 4);
2093 assert_eq!(reader.num_blocks(), 2);
2094
2095 assert_eq!(reader.text_dict().unwrap().len(), 3);
2097
2098 assert_eq!(reader.get_text(0), Some("alpha"));
2100 assert_eq!(reader.get_text(1), Some("beta"));
2101
2102 assert_eq!(reader.get_text(2), Some("gamma"));
2104 assert_eq!(reader.get_text(3), Some("alpha"));
2105
2106 assert_eq!(reader.text_ordinal("alpha"), Some(0));
2108 assert_eq!(reader.text_ordinal("beta"), Some(1));
2109 assert_eq!(reader.text_ordinal("gamma"), Some(2));
2110
2111 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); }
2117
2118 #[test]
2127 fn test_multi_block_text_ordinal_mismatch_regression() {
2128 let mut wa = FastFieldWriter::new_text();
2130 wa.add_text(0, "book");
2131 wa.add_text(1, "wiki");
2132 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2133
2134 let mut wb = FastFieldWriter::new_text();
2136 wb.add_text(0, "apple");
2137 wb.add_text(1, "wiki");
2138 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2139
2140 let (buf, toc) = assemble_blocked_column(
2141 2,
2142 FastFieldColumnType::TextOrdinal,
2143 false,
2144 &[
2145 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2146 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2147 ],
2148 );
2149
2150 let ob = owned(buf);
2151 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2152
2153 assert_eq!(reader.text_dict().unwrap().len(), 3);
2155 assert_eq!(reader.text_ordinal("apple"), Some(0));
2156 assert_eq!(reader.text_ordinal("book"), Some(1));
2157 assert_eq!(reader.text_ordinal("wiki"), Some(2));
2158
2159 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();
2167 assert_eq!(reader.get_u64(1), wiki_ord, "wiki doc should match");
2168 assert_eq!(reader.get_u64(3), wiki_ord, "wiki doc should match");
2169 assert_ne!(reader.get_u64(0), wiki_ord, "book doc must NOT match wiki");
2170 assert_ne!(reader.get_u64(2), wiki_ord, "apple doc must NOT match wiki");
2171 }
2172
2173 #[test]
2176 fn test_i64_timestamps_with_missing_roundtrip() {
2177 let base_ts = 1724630400i64; let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
2179
2180 let mut expected_values: Vec<Option<i64>> = Vec::new();
2182 for i in 0..100u32 {
2183 if i % 5 == 0 {
2184 expected_values.push(None); } else {
2186 let ts = base_ts - (i as i64 * 86400);
2187 writer.add_i64(i, ts);
2188 expected_values.push(Some(ts));
2189 }
2190 }
2191 writer.pad_to(100);
2192
2193 let mut buf = Vec::new();
2194 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2195 let ob = owned(buf);
2196 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2197
2198 for (i, expected) in expected_values.iter().enumerate() {
2199 let raw = reader.get_u64(i as u32);
2200 match expected {
2201 None => {
2202 assert_eq!(
2203 raw, FAST_FIELD_MISSING,
2204 "doc {}: expected MISSING, got raw {}",
2205 i, raw
2206 );
2207 }
2208 Some(ts) => {
2209 assert_ne!(
2210 raw, FAST_FIELD_MISSING,
2211 "doc {}: expected timestamp {}, got MISSING",
2212 i, ts
2213 );
2214 let decoded = zigzag_decode(raw);
2215 assert_eq!(
2216 decoded,
2217 *ts,
2218 "doc {}: expected i64 {}, got i64 {} (raw zigzag: {}, expected zigzag: {})",
2219 i,
2220 ts,
2221 decoded,
2222 raw,
2223 zigzag_encode(*ts)
2224 );
2225 }
2226 }
2227 }
2228 }
2229
2230 #[test]
2233 fn test_issued_at_1724630400_various_sizes() {
2234 let target_ts = 1724630400i64;
2235 let target_zigzag = zigzag_encode(target_ts);
2236
2237 for num_docs in [2, 5, 10, 50, 100, 500, 1000, 2000] {
2238 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
2239 let target_doc = num_docs / 3;
2240
2241 for i in 0..num_docs as u32 {
2242 if i == target_doc as u32 {
2243 writer.add_i64(i, target_ts);
2244 } else if i % 3 == 0 {
2245 } else {
2247 let ts = 1700000000i64 + (i as i64 * 86400);
2248 writer.add_i64(i, ts);
2249 }
2250 }
2251 writer.pad_to(num_docs as u32);
2252
2253 let mut buf = Vec::new();
2254 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2255 let ob = owned(buf);
2256 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2257
2258 let raw = reader.get_u64(target_doc as u32);
2259 assert_eq!(
2260 raw,
2261 target_zigzag,
2262 "num_docs={}: doc {} expected zigzag {} (ts {}), got {} (decoded i64: {})",
2263 num_docs,
2264 target_doc,
2265 target_zigzag,
2266 target_ts,
2267 raw,
2268 zigzag_decode(raw)
2269 );
2270 }
2271 }
2272
2273 #[test]
2274 fn test_multi_block_multi_value_numeric() {
2275 let mut wa = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2277 wa.add_u64(0, 1);
2278 wa.add_u64(0, 2);
2279 wa.add_u64(1, 3);
2280 wa.pad_to(2);
2281 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2282
2283 let mut wb = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2285 wb.add_u64(0, 4);
2286 wb.add_u64(0, 5);
2287 wb.add_u64(0, 6);
2288 wb.pad_to(2);
2289 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2290
2291 let (buf, toc) = assemble_blocked_column(
2292 3,
2293 FastFieldColumnType::U64,
2294 true,
2295 &[
2296 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2297 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2298 ],
2299 );
2300
2301 let ob = owned(buf);
2302 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2303
2304 assert_eq!(reader.num_docs, 4);
2305 assert_eq!(reader.num_blocks(), 2);
2306
2307 assert_eq!(reader.get_multi_values(0), vec![1, 2]);
2309 assert_eq!(reader.get_multi_values(1), vec![3]);
2311 assert_eq!(reader.get_multi_values(2), vec![4, 5, 6]);
2313 assert_eq!(reader.get_multi_values(3), Vec::<u64>::new());
2315 }
2316}