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(
555 &mut self,
556 writer: &mut dyn Write,
557 data_offset: u64,
558 ) -> io::Result<(FastFieldTocEntry, u64)> {
559 if self.column_type == FastFieldColumnType::TextOrdinal {
561 self.resolve_text_ordinals();
562 }
563
564 let num_docs = self.num_docs();
565
566 let mut block_data = Vec::new();
568 if self.multi {
569 let offsets_u64: Vec<u64> = self.multi_offsets.iter().map(|&v| v as u64).collect();
571 let mut offset_buf = Vec::new();
572 codec::serialize_auto(&offsets_u64, &mut offset_buf)?;
573
574 block_data.write_u32::<LittleEndian>(offset_buf.len() as u32)?;
575 block_data.write_all(&offset_buf)?;
576
577 codec::serialize_auto(&self.multi_values, &mut block_data)?;
578 } else {
579 codec::serialize_auto(&self.values, &mut block_data)?;
580 }
581
582 let mut dict_buf = Vec::new();
584 let dict_count = if self.column_type == FastFieldColumnType::TextOrdinal {
585 let (count, _) = self.write_text_dictionary(&mut dict_buf)?;
586 count
587 } else {
588 0u32
589 };
590
591 let block_entry = BlockIndexEntry {
593 num_docs,
594 data_len: block_data.len() as u32,
595 dict_count,
596 dict_len: dict_buf.len() as u32,
597 };
598
599 let mut total_bytes = 0u64;
601
602 writer.write_u32::<LittleEndian>(1u32)?; total_bytes += 4;
604
605 block_entry.write_to(writer)?;
606 total_bytes += BLOCK_INDEX_ENTRY_SIZE as u64;
607
608 writer.write_all(&block_data)?;
609 total_bytes += block_data.len() as u64;
610
611 writer.write_all(&dict_buf)?;
612 total_bytes += dict_buf.len() as u64;
613
614 let toc = FastFieldTocEntry {
615 field_id: 0, column_type: self.column_type,
617 multi: self.multi,
618 data_offset,
619 data_len: total_bytes,
620 num_docs,
621 dict_offset: 0, dict_count: 0,
623 };
624
625 Ok((toc, total_bytes))
626 }
627
628 fn resolve_text_ordinals(&mut self) {
630 let dict = self.text_values.as_ref().expect("text_values required");
631
632 let sorted_ordinals: BTreeMap<&str, u64> = dict
634 .keys()
635 .enumerate()
636 .map(|(ord, key)| (key.as_str(), ord as u64))
637 .collect();
638
639 if self.multi {
640 if let Some(ref tmv) = self.text_multi_values {
642 for (i, text) in tmv.iter().enumerate() {
643 self.multi_values[i] = sorted_ordinals[text.as_str()];
644 }
645 }
646 } else {
647 let tpd = self.text_per_doc.as_ref().expect("text_per_doc required");
649 for (i, doc_text) in tpd.iter().enumerate() {
650 match doc_text {
651 Some(text) => {
652 self.values[i] = sorted_ordinals[text.as_str()];
653 }
654 None => {
655 self.values[i] = FAST_FIELD_MISSING;
656 }
657 }
658 }
659 }
660 }
661
662 fn write_text_dictionary(&self, writer: &mut dyn Write) -> io::Result<(u32, u64)> {
664 let dict = self.text_values.as_ref().expect("text_values required");
665 let mut bytes_written = 0u64;
666
667 let count = dict.len() as u32;
669 for key in dict.keys() {
670 let key_bytes = key.as_bytes();
671 writer.write_u32::<LittleEndian>(key_bytes.len() as u32)?;
672 writer.write_all(key_bytes)?;
673 bytes_written += 4 + key_bytes.len() as u64;
674 }
675
676 Ok((count, bytes_written))
677 }
678}
679
680use crate::directories::OwnedBytes;
683
684pub struct ColumnBlock {
688 pub cumulative_docs: u32,
690 pub num_docs: u32,
692 pub data: OwnedBytes,
694 pub offset_data: OwnedBytes,
696 pub value_data: OwnedBytes,
698 pub dict: Option<TextDictReader>,
700 pub raw_dict: OwnedBytes,
702}
703
704pub struct FastFieldReader {
715 pub column_type: FastFieldColumnType,
716 pub num_docs: u32,
717 pub multi: bool,
718
719 blocks: Vec<ColumnBlock>,
721
722 text_state: OnceLock<TextState>,
725}
726
727struct TextState {
729 global_dict: TextDictReader,
731 ordinal_maps: Vec<Vec<u32>>,
734}
735
736impl FastFieldReader {
737 pub fn disk_bytes(&self) -> u64 {
739 self.blocks
740 .iter()
741 .map(|block| {
742 (block.data.len()
743 + block.offset_data.len()
744 + block.value_data.len()
745 + block.raw_dict.len()) as u64
746 })
747 .sum()
748 }
749
750 pub fn open(file_data: &OwnedBytes, toc: &FastFieldTocEntry) -> io::Result<Self> {
755 let region_start = usize::try_from(toc.data_offset).map_err(|_| {
756 io::Error::new(
757 io::ErrorKind::InvalidData,
758 "fast field data offset exceeds address space",
759 )
760 })?;
761 let region_len = usize::try_from(toc.data_len).map_err(|_| {
762 io::Error::new(
763 io::ErrorKind::InvalidData,
764 "fast field data length exceeds address space",
765 )
766 })?;
767 let region_end = region_start.checked_add(region_len).ok_or_else(|| {
768 io::Error::new(io::ErrorKind::InvalidData, "fast field data range overflow")
769 })?;
770
771 if region_end > file_data.len() {
772 return Err(io::Error::new(
773 io::ErrorKind::UnexpectedEof,
774 "fast field data out of bounds",
775 ));
776 }
777
778 let raw = file_data.as_slice();
779
780 let mut pos = region_start;
782 if pos.checked_add(4).is_none_or(|end| end > region_end) {
783 return Err(io::Error::new(
784 io::ErrorKind::UnexpectedEof,
785 "fast field: missing num_blocks",
786 ));
787 }
788 let num_blocks = u32::from_le_bytes(raw[pos..pos + 4].try_into().unwrap());
789 pos += 4;
790
791 let idx_size = (num_blocks as usize)
793 .checked_mul(BLOCK_INDEX_ENTRY_SIZE)
794 .ok_or_else(|| {
795 io::Error::new(
796 io::ErrorKind::InvalidData,
797 "fast field block index overflow",
798 )
799 })?;
800 let index_end = pos.checked_add(idx_size).ok_or_else(|| {
801 io::Error::new(
802 io::ErrorKind::InvalidData,
803 "fast field block index overflow",
804 )
805 })?;
806 if index_end > region_end {
807 return Err(io::Error::new(
808 io::ErrorKind::UnexpectedEof,
809 "fast field: block index truncated",
810 ));
811 }
812 let mut block_entries = Vec::new();
813 block_entries
814 .try_reserve_exact(num_blocks as usize)
815 .map_err(|_| {
816 io::Error::new(io::ErrorKind::InvalidData, "too many fast field blocks")
817 })?;
818 {
819 let mut cursor = std::io::Cursor::new(&raw[pos..index_end]);
820 for _ in 0..num_blocks {
821 block_entries.push(BlockIndexEntry::read_from(&mut cursor)?);
822 }
823 }
824 pos = index_end;
825
826 let empty = OwnedBytes::new(Vec::new());
827
828 let mut blocks = Vec::new();
830 blocks.try_reserve_exact(num_blocks as usize).map_err(|_| {
831 io::Error::new(io::ErrorKind::InvalidData, "too many fast field blocks")
832 })?;
833 let mut cumulative = 0u32;
834
835 for entry in &block_entries {
836 let data_start = pos;
837 let data_end = data_start
838 .checked_add(entry.data_len as usize)
839 .ok_or_else(|| {
840 io::Error::new(
841 io::ErrorKind::InvalidData,
842 "fast field block range overflow",
843 )
844 })?;
845 let dict_start = data_end;
846 let dict_end = dict_start
847 .checked_add(entry.dict_len as usize)
848 .ok_or_else(|| {
849 io::Error::new(io::ErrorKind::InvalidData, "fast field dict range overflow")
850 })?;
851
852 if dict_end > region_end {
853 return Err(io::Error::new(
854 io::ErrorKind::UnexpectedEof,
855 "fast field: block data/dict truncated",
856 ));
857 }
858
859 let (block_data, offset_data, value_data) = if toc.multi {
861 let block_raw = &raw[data_start..data_end];
862 if block_raw.len() < 4 {
863 return Err(io::Error::new(
864 io::ErrorKind::UnexpectedEof,
865 "fast field multi-value header is truncated",
866 ));
867 }
868 let offset_col_len =
869 u32::from_le_bytes(block_raw[0..4].try_into().unwrap()) as usize;
870 let o_start = data_start + 4;
871 let o_end = o_start.checked_add(offset_col_len).ok_or_else(|| {
872 io::Error::new(
873 io::ErrorKind::InvalidData,
874 "fast field offset column range overflow",
875 )
876 })?;
877 if o_end > data_end {
878 return Err(io::Error::new(
879 io::ErrorKind::UnexpectedEof,
880 "fast field offset column is truncated",
881 ));
882 }
883 let v_start = o_end;
884 let v_end = data_end;
885 let offset_data = file_data.slice(o_start..o_end);
886 let value_data = file_data.slice(v_start..v_end);
887 let offset_count = (entry.num_docs as usize).checked_add(1).ok_or_else(|| {
888 io::Error::new(io::ErrorKind::InvalidData, "fast field doc count overflow")
889 })?;
890 codec::validate_auto(offset_data.as_slice(), offset_count)?;
891
892 let mut previous = 0u64;
893 for index in 0..offset_count {
894 let offset = codec::auto_read(offset_data.as_slice(), index);
895 if offset > u32::MAX as u64 || (index == 0 && offset != 0) || offset < previous
896 {
897 return Err(io::Error::new(
898 io::ErrorKind::InvalidData,
899 "fast field value offsets are invalid",
900 ));
901 }
902 previous = offset;
903 }
904 codec::validate_auto(value_data.as_slice(), previous as usize)?;
905
906 (
907 file_data.slice(data_start..data_end),
908 offset_data,
909 value_data,
910 )
911 } else {
912 let block_data = file_data.slice(data_start..data_end);
913 codec::validate_auto(block_data.as_slice(), entry.num_docs as usize)?;
914 (block_data, empty.clone(), empty.clone())
915 };
916
917 if toc.column_type == FastFieldColumnType::TextOrdinal {
918 if entry.dict_count == 0 && entry.dict_len != 0 {
919 return Err(io::Error::new(
920 io::ErrorKind::InvalidData,
921 "empty fast field dictionary has data",
922 ));
923 }
924 validate_text_dict_bytes(&raw[dict_start..dict_end], entry.dict_count)?;
925 } else if entry.dict_count != 0 || entry.dict_len != 0 {
926 return Err(io::Error::new(
927 io::ErrorKind::InvalidData,
928 "numeric fast field contains a text dictionary",
929 ));
930 }
931
932 let dict = if entry.dict_count > 0 {
934 Some(TextDictReader::new_lazy(
935 file_data.slice(dict_start..dict_end),
936 entry.dict_count,
937 ))
938 } else {
939 None
940 };
941
942 let raw_dict = if entry.dict_len > 0 {
943 file_data.slice(dict_start..dict_end)
944 } else {
945 empty.clone()
946 };
947
948 blocks.push(ColumnBlock {
949 cumulative_docs: cumulative,
950 num_docs: entry.num_docs,
951 data: block_data,
952 offset_data,
953 value_data,
954 dict,
955 raw_dict,
956 });
957
958 cumulative = cumulative.checked_add(entry.num_docs).ok_or_else(|| {
959 io::Error::new(io::ErrorKind::InvalidData, "fast field doc count overflow")
960 })?;
961 pos = dict_end;
962 }
963
964 if pos != region_end || cumulative != toc.num_docs {
965 return Err(io::Error::new(
966 io::ErrorKind::InvalidData,
967 "fast field block totals are inconsistent with the TOC",
968 ));
969 }
970 if toc.num_docs > 0 && blocks.is_empty() {
971 return Err(io::Error::new(
972 io::ErrorKind::InvalidData,
973 "non-empty fast field has no blocks",
974 ));
975 }
976
977 Ok(Self {
978 column_type: toc.column_type,
979 num_docs: toc.num_docs,
980 multi: toc.multi,
981 blocks,
982 text_state: OnceLock::new(),
983 })
984 }
985
986 fn ensure_text_state(&self) -> &TextState {
989 self.text_state
990 .get_or_init(|| Self::build_text_state(&self.blocks))
991 }
992
993 fn build_text_state(blocks: &[ColumnBlock]) -> TextState {
996 let blocks_with_dict = blocks.iter().filter(|b| b.dict.is_some()).count();
999 if blocks_with_dict <= 1 {
1000 for block in blocks.iter() {
1001 if let Some(ref dict) = block.dict {
1002 return TextState {
1004 global_dict: TextDictReader::new_lazy(block.raw_dict.clone(), dict.len()),
1005 ordinal_maps: vec![Vec::new(); blocks.len()],
1006 };
1007 }
1008 }
1009 return TextState {
1011 global_dict: TextDictReader::new_lazy(OwnedBytes::new(Vec::new()), 0),
1012 ordinal_maps: vec![Vec::new(); blocks.len()],
1013 };
1014 }
1015
1016 let mut unique_map: BTreeMap<String, u32> = BTreeMap::new();
1027 for block in blocks.iter() {
1028 if let Some(ref dict) = block.dict {
1029 for ord in 0..dict.len() {
1030 if let Some(text) = dict.get(ord) {
1031 unique_map.entry(text.to_string()).or_insert(0);
1032 }
1033 }
1034 }
1035 }
1036 for (i, value) in unique_map.values_mut().enumerate() {
1038 *value = i as u32;
1039 }
1040
1041 let mut ordinal_maps = Vec::with_capacity(blocks.len());
1043 for block in blocks.iter() {
1044 if let Some(ref dict) = block.dict {
1045 let mut map = Vec::with_capacity(dict.len() as usize);
1046 for local_ord in 0..dict.len() {
1047 let text = dict
1048 .get(local_ord)
1049 .expect("block dict ordinal out of range");
1050 let global_ord = *unique_map
1051 .get(text)
1052 .expect("block dict entry not found in merged global dict");
1053 map.push(global_ord);
1054 }
1055 ordinal_maps.push(map);
1056 } else {
1057 ordinal_maps.push(Vec::new());
1058 }
1059 }
1060
1061 let mut dict_buf = Vec::new();
1063 let count = unique_map.len() as u32;
1064 for s in unique_map.keys() {
1065 let bytes = s.as_bytes();
1066 dict_buf.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
1067 dict_buf.extend_from_slice(bytes);
1068 }
1069
1070 TextState {
1071 global_dict: TextDictReader::new_lazy(OwnedBytes::new(dict_buf), count),
1072 ordinal_maps,
1073 }
1074 }
1075
1076 #[inline]
1079 fn remap_ordinal(&self, block_idx: usize, raw: u64) -> u64 {
1080 if self.column_type == FastFieldColumnType::TextOrdinal
1081 && raw != FAST_FIELD_MISSING
1082 && self.blocks.len() > 1
1083 {
1084 let state = self.ensure_text_state();
1085 let map = &state.ordinal_maps[block_idx];
1086 if !map.is_empty() {
1087 let idx = raw as usize;
1088 if idx < map.len() {
1089 map[idx] as u64
1090 } else {
1091 FAST_FIELD_MISSING
1092 }
1093 } else {
1094 raw
1095 }
1096 } else {
1097 raw
1098 }
1099 }
1100
1101 #[inline]
1103 fn find_block(&self, doc_id: u32) -> (usize, u32) {
1104 debug_assert!(!self.blocks.is_empty());
1105 if self.blocks.len() == 1 {
1107 return (0, doc_id);
1108 }
1109 let bi = self
1111 .blocks
1112 .partition_point(|b| b.cumulative_docs <= doc_id)
1113 .saturating_sub(1);
1114 (bi, doc_id - self.blocks[bi].cumulative_docs)
1115 }
1116
1117 #[inline]
1125 pub fn get_u64(&self, doc_id: u32) -> u64 {
1126 if doc_id >= self.num_docs {
1127 return FAST_FIELD_MISSING;
1128 }
1129 let (bi, local) = self.find_block(doc_id);
1130 let block = &self.blocks[bi];
1131
1132 if self.multi {
1133 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1134 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1135 if start >= end {
1136 return FAST_FIELD_MISSING;
1137 }
1138 let raw = codec::auto_read(block.value_data.as_slice(), start as usize);
1139 return self.remap_ordinal(bi, raw);
1140 }
1141
1142 let raw = codec::auto_read(block.data.as_slice(), local as usize);
1143 self.remap_ordinal(bi, raw)
1144 }
1145
1146 #[inline]
1149 fn block_value_range(&self, doc_id: u32) -> (usize, u32, u32) {
1150 if !self.multi || doc_id >= self.num_docs {
1151 return (0, 0, 0);
1152 }
1153 let (bi, local) = self.find_block(doc_id);
1154 let block = &self.blocks[bi];
1155 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1156 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1157 (bi, start, end)
1158 }
1159
1160 #[inline]
1164 pub fn value_range(&self, doc_id: u32) -> (u32, u32) {
1165 let (_, start, end) = self.block_value_range(doc_id);
1166 (start, end)
1167 }
1168
1169 #[inline]
1172 pub fn get_value_at(&self, index: u32) -> u64 {
1173 if self.blocks.len() == 1 {
1175 let raw = codec::auto_read(self.blocks[0].value_data.as_slice(), index as usize);
1176 return self.remap_ordinal(0, raw);
1177 }
1178 0
1180 }
1181
1182 pub fn get_multi_values(&self, doc_id: u32) -> Vec<u64> {
1184 let (bi, start, end) = self.block_value_range(doc_id);
1185 if start >= end {
1186 return Vec::new();
1187 }
1188 let block = &self.blocks[bi];
1189 (start..end)
1190 .map(|idx| {
1191 let raw = codec::auto_read(block.value_data.as_slice(), idx as usize);
1192 self.remap_ordinal(bi, raw)
1193 })
1194 .collect()
1195 }
1196
1197 #[inline]
1200 pub fn for_each_multi_value(&self, doc_id: u32, mut f: impl FnMut(u64) -> bool) -> bool {
1201 let (bi, start, end) = self.block_value_range(doc_id);
1202 if start >= end {
1203 return false;
1204 }
1205 let block = &self.blocks[bi];
1206 for idx in start..end {
1207 let raw = codec::auto_read(block.value_data.as_slice(), idx as usize);
1208 if f(self.remap_ordinal(bi, raw)) {
1209 return true;
1210 }
1211 }
1212 false
1213 }
1214
1215 pub fn scan_single_values(&self, mut f: impl FnMut(u32, u64)) {
1222 if self.multi {
1223 return;
1224 }
1225 const BATCH: usize = 256;
1226 let mut buf = [0u64; BATCH];
1227 let needs_remap =
1228 self.column_type == FastFieldColumnType::TextOrdinal && self.blocks.len() > 1;
1229
1230 let ordinal_maps = if needs_remap {
1232 Some(&self.ensure_text_state().ordinal_maps)
1233 } else {
1234 None
1235 };
1236
1237 for (block_idx, block) in self.blocks.iter().enumerate() {
1238 let n = block.num_docs as usize;
1239 let mut pos = 0;
1240
1241 let map = ordinal_maps.map(|maps| &maps[block_idx]);
1242 let has_map = map.is_some_and(|m| !m.is_empty());
1243
1244 while pos < n {
1245 let chunk = (n - pos).min(BATCH);
1246 codec::auto_read_batch(block.data.as_slice(), pos, &mut buf[..chunk]);
1247
1248 if has_map {
1249 let map = map.unwrap();
1250 for (i, &raw) in buf[..chunk].iter().enumerate() {
1251 let val = if raw != FAST_FIELD_MISSING {
1252 let idx = raw as usize;
1253 if idx < map.len() {
1254 map[idx] as u64
1255 } else {
1256 FAST_FIELD_MISSING
1257 }
1258 } else {
1259 raw
1260 };
1261 f(block.cumulative_docs + pos as u32 + i as u32, val);
1262 }
1263 } else {
1264 for (i, &val) in buf[..chunk].iter().enumerate() {
1265 f(block.cumulative_docs + pos as u32 + i as u32, val);
1266 }
1267 }
1268 pos += chunk;
1269 }
1270 }
1271 }
1272
1273 #[inline]
1278 pub fn has_value(&self, doc_id: u32) -> bool {
1279 if !self.multi {
1280 return doc_id < self.num_docs && self.get_u64(doc_id) != FAST_FIELD_MISSING;
1281 }
1282 let (_, start, end) = self.block_value_range(doc_id);
1283 start < end
1284 }
1285
1286 #[inline]
1291 pub fn get_i64(&self, doc_id: u32) -> i64 {
1292 zigzag_decode(self.get_u64(doc_id))
1293 }
1294
1295 #[inline]
1300 pub fn get_f64(&self, doc_id: u32) -> f64 {
1301 sortable_u64_to_f64(self.get_u64(doc_id))
1302 }
1303
1304 #[inline]
1306 pub fn get_ordinal(&self, doc_id: u32) -> u64 {
1307 self.get_u64(doc_id)
1308 }
1309
1310 pub fn get_text(&self, doc_id: u32) -> Option<&str> {
1313 if doc_id >= self.num_docs {
1314 return None;
1315 }
1316 let (bi, local) = self.find_block(doc_id);
1317 let block = &self.blocks[bi];
1318 let raw_ordinal = if self.multi {
1319 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1320 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1321 if start >= end {
1322 return None;
1323 }
1324 codec::auto_read(block.value_data.as_slice(), start as usize)
1325 } else {
1326 codec::auto_read(block.data.as_slice(), local as usize)
1327 };
1328 if raw_ordinal == FAST_FIELD_MISSING {
1329 return None;
1330 }
1331 block.dict.as_ref().and_then(|d| d.get(raw_ordinal as u32))
1332 }
1333
1334 pub fn text_ordinal(&self, text: &str) -> Option<u64> {
1336 if self.column_type != FastFieldColumnType::TextOrdinal {
1337 return None;
1338 }
1339 self.ensure_text_state().global_dict.ordinal(text)
1340 }
1341
1342 pub fn text_dict(&self) -> Option<&TextDictReader> {
1344 if self.column_type != FastFieldColumnType::TextOrdinal {
1345 return None;
1346 }
1347 Some(&self.ensure_text_state().global_dict)
1348 }
1349
1350 pub fn num_blocks(&self) -> usize {
1352 self.blocks.len()
1353 }
1354
1355 pub fn blocks(&self) -> &[ColumnBlock] {
1357 &self.blocks
1358 }
1359}
1360
1361pub struct TextDictReader {
1369 data: OwnedBytes,
1371 count: u32,
1373 offsets: OnceLock<Vec<(u32, u32)>>,
1375}
1376
1377impl TextDictReader {
1378 fn new_lazy(data: OwnedBytes, count: u32) -> Self {
1381 Self {
1382 data,
1383 count,
1384 offsets: OnceLock::new(),
1385 }
1386 }
1387
1388 pub fn open(file_data: &OwnedBytes, dict_start: usize, count: u32) -> io::Result<Self> {
1391 if count == 0 {
1392 return Ok(Self::new_lazy(OwnedBytes::new(Vec::new()), 0));
1393 }
1394 let dict_slice = file_data.as_slice();
1396 if dict_start > dict_slice.len() {
1397 return Err(io::Error::new(
1398 io::ErrorKind::UnexpectedEof,
1399 "text dict offset out of bounds",
1400 ));
1401 }
1402 let mut pos = dict_start;
1403 for _ in 0..count {
1404 if pos.checked_add(4).is_none_or(|end| end > dict_slice.len()) {
1405 return Err(io::Error::new(
1406 io::ErrorKind::UnexpectedEof,
1407 "text dict truncated",
1408 ));
1409 }
1410 let len = u32::from_le_bytes(dict_slice[pos..pos + 4].try_into().unwrap()) as usize;
1411 pos += 4;
1412 if pos
1413 .checked_add(len)
1414 .is_none_or(|end| end > dict_slice.len())
1415 {
1416 return Err(io::Error::new(
1417 io::ErrorKind::UnexpectedEof,
1418 "text dict entry truncated",
1419 ));
1420 }
1421 std::str::from_utf8(&dict_slice[pos..pos + len])
1422 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
1423 pos += len;
1424 }
1425 let data = file_data.slice(dict_start..pos);
1426 Ok(Self::new_lazy(data, count))
1427 }
1428
1429 pub fn open_from_raw(raw_dict: &OwnedBytes, count: u32) -> io::Result<Self> {
1431 validate_text_dict_bytes(raw_dict.as_slice(), count)?;
1432 Ok(Self::new_lazy(raw_dict.clone(), count))
1433 }
1434
1435 #[inline]
1437 fn ensure_offsets(&self) -> &[(u32, u32)] {
1438 self.offsets.get_or_init(|| {
1439 let dict_slice = self.data.as_slice();
1440 let mut pos = 0usize;
1441 let mut offsets = Vec::with_capacity(self.count as usize);
1442 for _ in 0..self.count {
1443 debug_assert!(
1444 pos + 4 <= dict_slice.len(),
1445 "text dict truncated during lazy init"
1446 );
1447 let len = u32::from_le_bytes(dict_slice[pos..pos + 4].try_into().unwrap()) as usize;
1448 pos += 4;
1449 debug_assert!(
1450 pos + len <= dict_slice.len(),
1451 "text dict entry truncated during lazy init"
1452 );
1453 offsets.push((pos as u32, len as u32));
1454 pos += len;
1455 }
1456 offsets
1457 })
1458 }
1459
1460 pub fn get(&self, ordinal: u32) -> Option<&str> {
1462 let offsets = self.ensure_offsets();
1463 let &(off, len) = offsets.get(ordinal as usize)?;
1464 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1465 std::str::from_utf8(slice).ok()
1466 }
1467
1468 pub fn ordinal(&self, text: &str) -> Option<u64> {
1470 let offsets = self.ensure_offsets();
1471 offsets
1472 .binary_search_by(|&(off, len)| {
1473 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1474 std::str::from_utf8(slice).unwrap_or("").cmp(text)
1475 })
1476 .ok()
1477 .map(|i| i as u64)
1478 }
1479
1480 pub fn len(&self) -> u32 {
1482 self.count
1483 }
1484
1485 pub fn is_empty(&self) -> bool {
1487 self.count == 0
1488 }
1489
1490 pub fn iter(&self) -> impl Iterator<Item = &str> {
1492 let offsets = self.ensure_offsets();
1493 offsets.iter().map(|&(off, len)| {
1494 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1495 std::str::from_utf8(slice).unwrap_or("")
1496 })
1497 }
1498}
1499
1500fn validate_text_dict_bytes(data: &[u8], count: u32) -> io::Result<()> {
1501 let minimum = (count as usize).checked_mul(4).ok_or_else(|| {
1502 io::Error::new(io::ErrorKind::InvalidData, "text dictionary size overflow")
1503 })?;
1504 if minimum > data.len() {
1505 return Err(io::Error::new(
1506 io::ErrorKind::UnexpectedEof,
1507 "text dictionary entry table is truncated",
1508 ));
1509 }
1510
1511 let mut pos = 0usize;
1512 let mut previous: Option<&str> = None;
1513 for _ in 0..count {
1514 let len_end = pos.checked_add(4).ok_or_else(|| {
1515 io::Error::new(
1516 io::ErrorKind::InvalidData,
1517 "text dictionary offset overflow",
1518 )
1519 })?;
1520 let len = u32::from_le_bytes(data[pos..len_end].try_into().unwrap()) as usize;
1521 pos = len_end;
1522 let end = pos.checked_add(len).ok_or_else(|| {
1523 io::Error::new(
1524 io::ErrorKind::InvalidData,
1525 "text dictionary offset overflow",
1526 )
1527 })?;
1528 if end > data.len() {
1529 return Err(io::Error::new(
1530 io::ErrorKind::UnexpectedEof,
1531 "text dictionary entry is truncated",
1532 ));
1533 }
1534 let value = std::str::from_utf8(&data[pos..end])
1535 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
1536 if previous.is_some_and(|previous| previous >= value) {
1537 return Err(io::Error::new(
1538 io::ErrorKind::InvalidData,
1539 "text dictionary entries are not strictly increasing",
1540 ));
1541 }
1542 previous = Some(value);
1543 pos = end;
1544 }
1545 if pos != data.len() {
1546 return Err(io::Error::new(
1547 io::ErrorKind::InvalidData,
1548 "text dictionary contains trailing data",
1549 ));
1550 }
1551 Ok(())
1552}
1553
1554pub fn write_fast_field_toc_and_footer(
1558 writer: &mut dyn Write,
1559 toc_offset: u64,
1560 entries: &[FastFieldTocEntry],
1561) -> io::Result<()> {
1562 for e in entries {
1563 e.write_to(writer)?;
1564 }
1565 writer.write_u64::<LittleEndian>(toc_offset)?;
1566 writer.write_u32::<LittleEndian>(entries.len() as u32)?;
1567 writer.write_u32::<LittleEndian>(FAST_FIELD_MAGIC)?;
1568 Ok(())
1569}
1570
1571pub fn read_fast_field_footer(file_data: &[u8]) -> io::Result<(u64, u32)> {
1574 let len = file_data.len();
1575 if len < FAST_FIELD_FOOTER_SIZE as usize {
1576 return Err(io::Error::new(
1577 io::ErrorKind::UnexpectedEof,
1578 "fast field file too small for footer",
1579 ));
1580 }
1581 let footer = &file_data[len - FAST_FIELD_FOOTER_SIZE as usize..];
1582 let mut cursor = std::io::Cursor::new(footer);
1583 let toc_offset = cursor.read_u64::<LittleEndian>()?;
1584 let num_columns = cursor.read_u32::<LittleEndian>()?;
1585 let magic = cursor.read_u32::<LittleEndian>()?;
1586 if magic != FAST_FIELD_MAGIC {
1587 return Err(io::Error::new(
1588 io::ErrorKind::InvalidData,
1589 format!("bad fast field magic: 0x{:08x}", magic),
1590 ));
1591 }
1592 Ok((toc_offset, num_columns))
1593}
1594
1595pub fn read_fast_field_toc(
1597 file_data: &[u8],
1598 toc_offset: u64,
1599 num_columns: u32,
1600) -> io::Result<Vec<FastFieldTocEntry>> {
1601 let start = usize::try_from(toc_offset).map_err(|_| {
1602 io::Error::new(
1603 io::ErrorKind::InvalidData,
1604 "fast field TOC offset exceeds address space",
1605 )
1606 })?;
1607 let expected = (num_columns as usize)
1608 .checked_mul(FAST_FIELD_TOC_ENTRY_SIZE)
1609 .ok_or_else(|| {
1610 io::Error::new(io::ErrorKind::InvalidData, "fast field TOC size overflow")
1611 })?;
1612 let end = start.checked_add(expected).ok_or_else(|| {
1613 io::Error::new(io::ErrorKind::InvalidData, "fast field TOC range overflow")
1614 })?;
1615 if end > file_data.len() {
1616 return Err(io::Error::new(
1617 io::ErrorKind::UnexpectedEof,
1618 "fast field TOC out of bounds",
1619 ));
1620 }
1621 let mut cursor = std::io::Cursor::new(&file_data[start..end]);
1622 let mut entries = Vec::new();
1623 entries
1624 .try_reserve_exact(num_columns as usize)
1625 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "too many fast field columns"))?;
1626 for _ in 0..num_columns {
1627 entries.push(FastFieldTocEntry::read_from(&mut cursor)?);
1628 }
1629 Ok(entries)
1630}
1631
1632#[cfg(test)]
1635mod tests {
1636 use super::*;
1637
1638 #[test]
1639 fn test_zigzag_roundtrip() {
1640 for v in [0i64, 1, -1, 42, -42, i64::MAX, i64::MIN] {
1641 assert_eq!(zigzag_decode(zigzag_encode(v)), v);
1642 }
1643 }
1644
1645 #[test]
1646 fn test_f64_sortable_roundtrip() {
1647 for v in [0.0f64, 1.0, -1.0, f64::MAX, f64::MIN, f64::MIN_POSITIVE] {
1648 assert_eq!(sortable_u64_to_f64(f64_to_sortable_u64(v)), v);
1649 }
1650 }
1651
1652 #[test]
1653 fn test_f64_sortable_order() {
1654 let values = [-100.0f64, -1.0, -0.0, 0.0, 0.5, 1.0, 100.0];
1655 let encoded: Vec<u64> = values.iter().map(|&v| f64_to_sortable_u64(v)).collect();
1656 for i in 1..encoded.len() {
1657 assert!(
1658 encoded[i] >= encoded[i - 1],
1659 "{} >= {} failed for {} vs {}",
1660 encoded[i],
1661 encoded[i - 1],
1662 values[i],
1663 values[i - 1]
1664 );
1665 }
1666 }
1667
1668 #[test]
1669 fn test_bitpack_roundtrip() {
1670 let values: Vec<u64> = vec![0, 3, 7, 15, 0, 1, 6, 12];
1671 let bpv = 4u8;
1672 let mut packed = Vec::new();
1673 bitpack_write(&values, bpv, &mut packed);
1674
1675 for (i, &expected) in values.iter().enumerate() {
1676 let got = bitpack_read(&packed, bpv, i);
1677 assert_eq!(got, expected, "index {}", i);
1678 }
1679 }
1680
1681 #[test]
1682 fn test_bitpack_high_bpv_regression() {
1683 for bpv in [57u8, 58, 59, 60, 63, 64] {
1686 let max_val = if bpv == 64 {
1687 u64::MAX
1688 } else {
1689 (1u64 << bpv) - 1
1690 };
1691 let values: Vec<u64> = (0..32)
1692 .map(|i: u64| {
1693 if max_val == u64::MAX {
1694 i * 7
1695 } else {
1696 (i * 7) % (max_val + 1)
1697 }
1698 })
1699 .collect();
1700 let mut packed = Vec::new();
1701 bitpack_write(&values, bpv, &mut packed);
1702 for (i, &expected) in values.iter().enumerate() {
1703 let got = bitpack_read(&packed, bpv, i);
1704 assert_eq!(got, expected, "high bpv={} index={}", bpv, i);
1705 }
1706 }
1707 }
1708
1709 #[test]
1710 fn test_bitpack_various_widths() {
1711 for bpv in [1u8, 2, 3, 5, 7, 8, 13, 16, 32, 64] {
1712 let max_val = if bpv == 64 {
1713 u64::MAX
1714 } else {
1715 (1u64 << bpv) - 1
1716 };
1717 let values: Vec<u64> = (0..100)
1718 .map(|i: u64| {
1719 if max_val == u64::MAX {
1720 i
1721 } else {
1722 i % (max_val + 1)
1723 }
1724 })
1725 .collect();
1726 let mut packed = Vec::new();
1727 bitpack_write(&values, bpv, &mut packed);
1728
1729 for (i, &expected) in values.iter().enumerate() {
1730 let got = bitpack_read(&packed, bpv, i);
1731 assert_eq!(got, expected, "bpv={} index={}", bpv, i);
1732 }
1733 }
1734 }
1735
1736 fn owned(buf: Vec<u8>) -> OwnedBytes {
1738 OwnedBytes::new(buf)
1739 }
1740
1741 #[test]
1742 fn test_writer_reader_u64_roundtrip() {
1743 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
1744 writer.add_u64(0, 100);
1745 writer.add_u64(1, 200);
1746 writer.add_u64(2, 150);
1747 writer.add_u64(4, 300); writer.pad_to(5);
1749
1750 let mut buf = Vec::new();
1751 let (mut toc, _bytes) = writer.serialize(&mut buf, 0).unwrap();
1752 toc.field_id = 42;
1753
1754 let toc_offset = buf.len() as u64;
1756 write_fast_field_toc_and_footer(&mut buf, toc_offset, &[toc]).unwrap();
1757
1758 let ob = owned(buf);
1760 let (toc_off, num_cols) = read_fast_field_footer(&ob).unwrap();
1761 assert_eq!(num_cols, 1);
1762 let tocs = read_fast_field_toc(&ob, toc_off, num_cols).unwrap();
1763 assert_eq!(tocs.len(), 1);
1764 assert_eq!(tocs[0].field_id, 42);
1765
1766 let reader = FastFieldReader::open(&ob, &tocs[0]).unwrap();
1767 assert_eq!(reader.get_u64(0), 100);
1768 assert_eq!(reader.get_u64(1), 200);
1769 assert_eq!(reader.get_u64(2), 150);
1770 assert_eq!(reader.get_u64(3), FAST_FIELD_MISSING); assert_eq!(reader.get_u64(4), 300);
1772 }
1773
1774 #[test]
1775 fn test_writer_reader_i64_roundtrip() {
1776 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
1777 writer.add_i64(0, -100);
1778 writer.add_i64(1, 50);
1779 writer.add_i64(2, 0);
1780 writer.pad_to(3);
1781
1782 let mut buf = Vec::new();
1783 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1784 let ob = owned(buf);
1785 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1786 assert_eq!(reader.get_i64(0), -100);
1787 assert_eq!(reader.get_i64(1), 50);
1788 assert_eq!(reader.get_i64(2), 0);
1789 }
1790
1791 #[test]
1792 fn test_writer_reader_f64_roundtrip() {
1793 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::F64);
1794 writer.add_f64(0, -1.5);
1795 writer.add_f64(1, 3.15);
1796 writer.add_f64(2, 0.0);
1797 writer.pad_to(3);
1798
1799 let mut buf = Vec::new();
1800 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1801 let ob = owned(buf);
1802 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1803 assert_eq!(reader.get_f64(0), -1.5);
1804 assert_eq!(reader.get_f64(1), 3.15);
1805 assert_eq!(reader.get_f64(2), 0.0);
1806 }
1807
1808 #[test]
1809 fn test_writer_reader_text_roundtrip() {
1810 let mut writer = FastFieldWriter::new_text();
1811 writer.add_text(0, "banana");
1812 writer.add_text(1, "apple");
1813 writer.add_text(2, "cherry");
1814 writer.add_text(3, "apple"); writer.pad_to(5);
1817
1818 let mut buf = Vec::new();
1819 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1820 let ob = owned(buf);
1821 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1822
1823 assert_eq!(reader.get_text(0), Some("banana"));
1825 assert_eq!(reader.get_text(1), Some("apple"));
1826 assert_eq!(reader.get_text(2), Some("cherry"));
1827 assert_eq!(reader.get_text(3), Some("apple"));
1828 assert_eq!(reader.get_text(4), None); assert_eq!(reader.text_ordinal("apple"), Some(0));
1832 assert_eq!(reader.text_ordinal("banana"), Some(1));
1833 assert_eq!(reader.text_ordinal("cherry"), Some(2));
1834 assert_eq!(reader.text_ordinal("durian"), None);
1835 }
1836
1837 #[test]
1838 fn test_constant_column() {
1839 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
1840 for i in 0..100 {
1841 writer.add_u64(i, 42);
1842 }
1843
1844 let mut buf = Vec::new();
1845 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1846
1847 let ob = owned(buf);
1848 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1849 for i in 0..100 {
1850 assert_eq!(reader.get_u64(i), 42);
1851 }
1852 }
1853
1854 #[test]
1857 fn test_multi_value_u64_roundtrip() {
1858 let mut writer = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
1859 writer.add_u64(0, 10);
1861 writer.add_u64(0, 20);
1862 writer.add_u64(0, 30);
1863 writer.add_u64(2, 100);
1866 writer.add_u64(3, 5);
1868 writer.add_u64(3, 15);
1869 writer.pad_to(4);
1870
1871 let mut buf = Vec::new();
1872 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1873 assert!(toc.multi);
1874 assert_eq!(toc.num_docs, 4);
1875
1876 let ob = owned(buf);
1877 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1878 assert!(reader.multi);
1879
1880 assert_eq!(reader.get_u64(0), 10);
1882 let (s, e) = reader.value_range(0);
1883 assert_eq!(e - s, 3);
1884 assert_eq!(reader.get_value_at(s), 10);
1885 assert_eq!(reader.get_value_at(s + 1), 20);
1886 assert_eq!(reader.get_value_at(s + 2), 30);
1887
1888 assert_eq!(reader.get_u64(1), FAST_FIELD_MISSING);
1890 let (s, e) = reader.value_range(1);
1891 assert_eq!(s, e);
1892 assert!(!reader.has_value(1));
1893
1894 assert_eq!(reader.get_u64(2), 100);
1896 assert!(reader.has_value(2));
1897
1898 assert_eq!(reader.get_u64(3), 5);
1900 let (s, e) = reader.value_range(3);
1901 assert_eq!(e - s, 2);
1902 assert_eq!(reader.get_value_at(s), 5);
1903 assert_eq!(reader.get_value_at(s + 1), 15);
1904 }
1905
1906 #[test]
1907 fn test_multi_value_text_roundtrip() {
1908 let mut writer = FastFieldWriter::new_text_multi();
1909 writer.add_text(0, "banana");
1911 writer.add_text(0, "apple");
1912 writer.add_text(1, "cherry");
1914 writer.pad_to(3);
1916
1917 let mut buf = Vec::new();
1918 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1919 assert!(toc.multi);
1920
1921 let ob = owned(buf);
1922 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1923
1924 let (s, e) = reader.value_range(0);
1926 assert_eq!(e - s, 2);
1927 let ord0 = reader.get_value_at(s);
1928 let ord1 = reader.get_value_at(s + 1);
1929 assert_eq!(reader.text_dict().unwrap().get(ord0 as u32), Some("banana"));
1930 assert_eq!(reader.text_dict().unwrap().get(ord1 as u32), Some("apple"));
1931
1932 let (s, e) = reader.value_range(1);
1934 assert_eq!(e - s, 1);
1935 let ord = reader.get_value_at(s);
1936 assert_eq!(reader.text_dict().unwrap().get(ord as u32), Some("cherry"));
1937
1938 assert!(!reader.has_value(2));
1940 }
1941
1942 #[test]
1943 fn test_multi_value_full_toc_roundtrip() {
1944 let mut writer = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
1945 writer.add_u64(0, 1);
1946 writer.add_u64(0, 2);
1947 writer.add_u64(1, 3);
1948 writer.pad_to(2);
1949
1950 let mut buf = Vec::new();
1951 let (mut toc, _) = writer.serialize(&mut buf, 0).unwrap();
1952 toc.field_id = 7;
1953
1954 let toc_offset = buf.len() as u64;
1955 write_fast_field_toc_and_footer(&mut buf, toc_offset, &[toc]).unwrap();
1956
1957 let ob = owned(buf);
1958 let (toc_off, num_cols) = read_fast_field_footer(&ob).unwrap();
1959 let tocs = read_fast_field_toc(&ob, toc_off, num_cols).unwrap();
1960 assert_eq!(tocs[0].field_id, 7);
1961 assert!(tocs[0].multi);
1962
1963 let reader = FastFieldReader::open(&ob, &tocs[0]).unwrap();
1964 assert_eq!(reader.get_u64(0), 1);
1965 assert_eq!(reader.get_u64(1), 3);
1966 }
1967
1968 fn serialize_single_block(writer: &mut FastFieldWriter) -> (Vec<u8>, Vec<u8>, BlockIndexEntry) {
1971 let mut buf = Vec::new();
1972 let (_toc, _) = writer.serialize(&mut buf, 0).unwrap();
1973 let mut cursor = std::io::Cursor::new(&buf[4..4 + BLOCK_INDEX_ENTRY_SIZE]);
1975 let entry = BlockIndexEntry::read_from(&mut cursor).unwrap();
1976 let data_start = 4 + BLOCK_INDEX_ENTRY_SIZE;
1977 let data_end = data_start + entry.data_len as usize;
1978 let dict_end = data_end + entry.dict_len as usize;
1979 let data = buf[data_start..data_end].to_vec();
1980 let dict = if dict_end > data_end {
1981 buf[data_end..dict_end].to_vec()
1982 } else {
1983 Vec::new()
1984 };
1985 (data, dict, entry)
1986 }
1987
1988 fn assemble_blocked_column(
1990 field_id: u32,
1991 column_type: FastFieldColumnType,
1992 multi: bool,
1993 blocks: &[(u32, &[u8], u32, &[u8])], ) -> (Vec<u8>, FastFieldTocEntry) {
1995 use byteorder::{LittleEndian, WriteBytesExt};
1996
1997 let mut buf = Vec::new();
1998 let num_blocks = blocks.len() as u32;
1999
2000 buf.write_u32::<LittleEndian>(num_blocks).unwrap();
2002
2003 for &(num_docs, data, dict_count, dict) in blocks {
2005 let entry = BlockIndexEntry {
2006 num_docs,
2007 data_len: data.len() as u32,
2008 dict_count,
2009 dict_len: dict.len() as u32,
2010 };
2011 entry.write_to(&mut buf).unwrap();
2012 }
2013
2014 let mut total_docs = 0u32;
2016 for &(num_docs, data, _, dict) in blocks {
2017 buf.extend_from_slice(data);
2018 buf.extend_from_slice(dict);
2019 total_docs += num_docs;
2020 }
2021
2022 let data_len = buf.len() as u64;
2023
2024 let toc = FastFieldTocEntry {
2026 field_id,
2027 column_type,
2028 multi,
2029 data_offset: 0,
2030 data_len,
2031 num_docs: total_docs,
2032 dict_offset: 0,
2033 dict_count: 0,
2034 };
2035
2036 let toc_offset = buf.len() as u64;
2037 write_fast_field_toc_and_footer(&mut buf, toc_offset, std::slice::from_ref(&toc)).unwrap();
2038
2039 (buf, toc)
2040 }
2041
2042 #[test]
2043 fn test_multi_block_numeric_roundtrip() {
2044 let mut wa = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2046 wa.add_u64(0, 10);
2047 wa.add_u64(1, 20);
2048 wa.add_u64(2, 30);
2049 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2050
2051 let mut wb = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2053 wb.add_u64(0, 40);
2054 wb.add_u64(1, 50);
2055 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2056
2057 let (buf, toc) = assemble_blocked_column(
2058 1,
2059 FastFieldColumnType::U64,
2060 false,
2061 &[
2062 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2063 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2064 ],
2065 );
2066
2067 let ob = owned(buf);
2068 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2069
2070 assert_eq!(reader.num_docs, 5);
2071 assert_eq!(reader.num_blocks(), 2);
2072 assert_eq!(reader.get_u64(0), 10);
2073 assert_eq!(reader.get_u64(1), 20);
2074 assert_eq!(reader.get_u64(2), 30);
2075 assert_eq!(reader.get_u64(3), 40);
2076 assert_eq!(reader.get_u64(4), 50);
2077 }
2078
2079 #[test]
2080 fn test_multi_block_text_roundtrip() {
2081 let mut wa = FastFieldWriter::new_text();
2083 wa.add_text(0, "alpha");
2084 wa.add_text(1, "beta");
2085 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2086
2087 let mut wb = FastFieldWriter::new_text();
2089 wb.add_text(0, "gamma");
2090 wb.add_text(1, "alpha");
2091 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2092
2093 let (buf, toc) = assemble_blocked_column(
2094 2,
2095 FastFieldColumnType::TextOrdinal,
2096 false,
2097 &[
2098 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2099 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2100 ],
2101 );
2102
2103 let ob = owned(buf);
2104 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2105
2106 assert_eq!(reader.num_docs, 4);
2107 assert_eq!(reader.num_blocks(), 2);
2108
2109 assert_eq!(reader.text_dict().unwrap().len(), 3);
2111
2112 assert_eq!(reader.get_text(0), Some("alpha"));
2114 assert_eq!(reader.get_text(1), Some("beta"));
2115
2116 assert_eq!(reader.get_text(2), Some("gamma"));
2118 assert_eq!(reader.get_text(3), Some("alpha"));
2119
2120 assert_eq!(reader.text_ordinal("alpha"), Some(0));
2122 assert_eq!(reader.text_ordinal("beta"), Some(1));
2123 assert_eq!(reader.text_ordinal("gamma"), Some(2));
2124
2125 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); }
2131
2132 #[test]
2141 fn test_multi_block_text_ordinal_mismatch_regression() {
2142 let mut wa = FastFieldWriter::new_text();
2144 wa.add_text(0, "book");
2145 wa.add_text(1, "wiki");
2146 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2147
2148 let mut wb = FastFieldWriter::new_text();
2150 wb.add_text(0, "apple");
2151 wb.add_text(1, "wiki");
2152 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2153
2154 let (buf, toc) = assemble_blocked_column(
2155 2,
2156 FastFieldColumnType::TextOrdinal,
2157 false,
2158 &[
2159 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2160 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2161 ],
2162 );
2163
2164 let ob = owned(buf);
2165 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2166
2167 assert_eq!(reader.text_dict().unwrap().len(), 3);
2169 assert_eq!(reader.text_ordinal("apple"), Some(0));
2170 assert_eq!(reader.text_ordinal("book"), Some(1));
2171 assert_eq!(reader.text_ordinal("wiki"), Some(2));
2172
2173 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();
2181 assert_eq!(reader.get_u64(1), wiki_ord, "wiki doc should match");
2182 assert_eq!(reader.get_u64(3), wiki_ord, "wiki doc should match");
2183 assert_ne!(reader.get_u64(0), wiki_ord, "book doc must NOT match wiki");
2184 assert_ne!(reader.get_u64(2), wiki_ord, "apple doc must NOT match wiki");
2185 }
2186
2187 #[test]
2190 fn test_i64_timestamps_with_missing_roundtrip() {
2191 let base_ts = 1724630400i64; let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
2193
2194 let mut expected_values: Vec<Option<i64>> = Vec::new();
2196 for i in 0..100u32 {
2197 if i % 5 == 0 {
2198 expected_values.push(None); } else {
2200 let ts = base_ts - (i as i64 * 86400);
2201 writer.add_i64(i, ts);
2202 expected_values.push(Some(ts));
2203 }
2204 }
2205 writer.pad_to(100);
2206
2207 let mut buf = Vec::new();
2208 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2209 let ob = owned(buf);
2210 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2211
2212 for (i, expected) in expected_values.iter().enumerate() {
2213 let raw = reader.get_u64(i as u32);
2214 match expected {
2215 None => {
2216 assert_eq!(
2217 raw, FAST_FIELD_MISSING,
2218 "doc {}: expected MISSING, got raw {}",
2219 i, raw
2220 );
2221 }
2222 Some(ts) => {
2223 assert_ne!(
2224 raw, FAST_FIELD_MISSING,
2225 "doc {}: expected timestamp {}, got MISSING",
2226 i, ts
2227 );
2228 let decoded = zigzag_decode(raw);
2229 assert_eq!(
2230 decoded,
2231 *ts,
2232 "doc {}: expected i64 {}, got i64 {} (raw zigzag: {}, expected zigzag: {})",
2233 i,
2234 ts,
2235 decoded,
2236 raw,
2237 zigzag_encode(*ts)
2238 );
2239 }
2240 }
2241 }
2242 }
2243
2244 #[test]
2247 fn test_issued_at_1724630400_various_sizes() {
2248 let target_ts = 1724630400i64;
2249 let target_zigzag = zigzag_encode(target_ts);
2250
2251 for num_docs in [2, 5, 10, 50, 100, 500, 1000, 2000] {
2252 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
2253 let target_doc = num_docs / 3;
2254
2255 for i in 0..num_docs as u32 {
2256 if i == target_doc as u32 {
2257 writer.add_i64(i, target_ts);
2258 } else if i % 3 == 0 {
2259 } else {
2261 let ts = 1700000000i64 + (i as i64 * 86400);
2262 writer.add_i64(i, ts);
2263 }
2264 }
2265 writer.pad_to(num_docs as u32);
2266
2267 let mut buf = Vec::new();
2268 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2269 let ob = owned(buf);
2270 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2271
2272 let raw = reader.get_u64(target_doc as u32);
2273 assert_eq!(
2274 raw,
2275 target_zigzag,
2276 "num_docs={}: doc {} expected zigzag {} (ts {}), got {} (decoded i64: {})",
2277 num_docs,
2278 target_doc,
2279 target_zigzag,
2280 target_ts,
2281 raw,
2282 zigzag_decode(raw)
2283 );
2284 }
2285 }
2286
2287 #[test]
2288 fn test_multi_block_multi_value_numeric() {
2289 let mut wa = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2291 wa.add_u64(0, 1);
2292 wa.add_u64(0, 2);
2293 wa.add_u64(1, 3);
2294 wa.pad_to(2);
2295 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2296
2297 let mut wb = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2299 wb.add_u64(0, 4);
2300 wb.add_u64(0, 5);
2301 wb.add_u64(0, 6);
2302 wb.pad_to(2);
2303 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2304
2305 let (buf, toc) = assemble_blocked_column(
2306 3,
2307 FastFieldColumnType::U64,
2308 true,
2309 &[
2310 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2311 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2312 ],
2313 );
2314
2315 let ob = owned(buf);
2316 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2317
2318 assert_eq!(reader.num_docs, 4);
2319 assert_eq!(reader.num_blocks(), 2);
2320
2321 assert_eq!(reader.get_multi_values(0), vec![1, 2]);
2323 assert_eq!(reader.get_multi_values(1), vec![3]);
2325 assert_eq!(reader.get_multi_values(2), vec![4, 5, 6]);
2327 assert_eq!(reader.get_multi_values(3), Vec::<u64>::new());
2329 }
2330}