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
680#[cfg(feature = "native")]
685pub(crate) fn missing_block_data(multi: bool) -> io::Result<Vec<u8>> {
686 let mut data = Vec::with_capacity(23);
687 if multi {
688 let mut offsets = Vec::with_capacity(9);
689 codec::serialize_auto(&[0], &mut offsets)?;
690 data.write_u32::<LittleEndian>(offsets.len() as u32)?;
691 data.write_all(&offsets)?;
692 codec::serialize_auto(&[], &mut data)?;
693 } else {
694 codec::serialize_auto(&[FAST_FIELD_MISSING], &mut data)?;
695 }
696 Ok(data)
697}
698
699use crate::directories::OwnedBytes;
702
703pub struct ColumnBlock {
707 pub cumulative_docs: u32,
709 pub num_docs: u32,
711 pub data: OwnedBytes,
713 pub offset_data: OwnedBytes,
715 pub value_data: OwnedBytes,
717 pub dict: Option<TextDictReader>,
719 pub raw_dict: OwnedBytes,
721}
722
723pub struct FastFieldReader {
736 pub column_type: FastFieldColumnType,
737 pub num_docs: u32,
738 pub multi: bool,
739
740 blocks: Vec<ColumnBlock>,
742
743 text_state: OnceLock<TextState>,
746}
747
748struct TextState {
750 global_dict: TextDictReader,
752 ordinal_maps: Vec<Vec<u32>>,
755}
756
757impl FastFieldReader {
758 pub fn disk_bytes(&self) -> u64 {
760 self.blocks
761 .iter()
762 .map(|block| {
763 (block.data.len()
764 + block.offset_data.len()
765 + block.value_data.len()
766 + block.raw_dict.len()) as u64
767 })
768 .sum()
769 }
770
771 pub fn open(file_data: &OwnedBytes, toc: &FastFieldTocEntry) -> io::Result<Self> {
776 let region_start = usize::try_from(toc.data_offset).map_err(|_| {
777 io::Error::new(
778 io::ErrorKind::InvalidData,
779 "fast field data offset exceeds address space",
780 )
781 })?;
782 let region_len = usize::try_from(toc.data_len).map_err(|_| {
783 io::Error::new(
784 io::ErrorKind::InvalidData,
785 "fast field data length exceeds address space",
786 )
787 })?;
788 let region_end = region_start.checked_add(region_len).ok_or_else(|| {
789 io::Error::new(io::ErrorKind::InvalidData, "fast field data range overflow")
790 })?;
791
792 if region_end > file_data.len() {
793 return Err(io::Error::new(
794 io::ErrorKind::UnexpectedEof,
795 "fast field data out of bounds",
796 ));
797 }
798
799 let raw = file_data.as_slice();
800
801 let mut pos = region_start;
803 if pos.checked_add(4).is_none_or(|end| end > region_end) {
804 return Err(io::Error::new(
805 io::ErrorKind::UnexpectedEof,
806 "fast field: missing num_blocks",
807 ));
808 }
809 let num_blocks = u32::from_le_bytes(raw[pos..pos + 4].try_into().unwrap());
810 pos += 4;
811
812 let idx_size = (num_blocks as usize)
814 .checked_mul(BLOCK_INDEX_ENTRY_SIZE)
815 .ok_or_else(|| {
816 io::Error::new(
817 io::ErrorKind::InvalidData,
818 "fast field block index overflow",
819 )
820 })?;
821 let index_end = pos.checked_add(idx_size).ok_or_else(|| {
822 io::Error::new(
823 io::ErrorKind::InvalidData,
824 "fast field block index overflow",
825 )
826 })?;
827 if index_end > region_end {
828 return Err(io::Error::new(
829 io::ErrorKind::UnexpectedEof,
830 "fast field: block index truncated",
831 ));
832 }
833 let mut block_entries = Vec::new();
834 block_entries
835 .try_reserve_exact(num_blocks as usize)
836 .map_err(|_| {
837 io::Error::new(io::ErrorKind::InvalidData, "too many fast field blocks")
838 })?;
839 {
840 let mut cursor = std::io::Cursor::new(&raw[pos..index_end]);
841 for _ in 0..num_blocks {
842 block_entries.push(BlockIndexEntry::read_from(&mut cursor)?);
843 }
844 }
845 pos = index_end;
846
847 let empty = OwnedBytes::new(Vec::new());
848
849 let mut blocks = Vec::new();
851 blocks.try_reserve_exact(num_blocks as usize).map_err(|_| {
852 io::Error::new(io::ErrorKind::InvalidData, "too many fast field blocks")
853 })?;
854 let mut cumulative = 0u32;
855
856 for entry in &block_entries {
857 let data_start = pos;
858 let data_end = data_start
859 .checked_add(entry.data_len as usize)
860 .ok_or_else(|| {
861 io::Error::new(
862 io::ErrorKind::InvalidData,
863 "fast field block range overflow",
864 )
865 })?;
866 let dict_start = data_end;
867 let dict_end = dict_start
868 .checked_add(entry.dict_len as usize)
869 .ok_or_else(|| {
870 io::Error::new(io::ErrorKind::InvalidData, "fast field dict range overflow")
871 })?;
872
873 if dict_end > region_end {
874 return Err(io::Error::new(
875 io::ErrorKind::UnexpectedEof,
876 "fast field: block data/dict truncated",
877 ));
878 }
879
880 let (block_data, offset_data, value_data) = if toc.multi {
882 let block_raw = &raw[data_start..data_end];
883 if block_raw.len() < 4 {
884 return Err(io::Error::new(
885 io::ErrorKind::UnexpectedEof,
886 "fast field multi-value header is truncated",
887 ));
888 }
889 let offset_col_len =
890 u32::from_le_bytes(block_raw[0..4].try_into().unwrap()) as usize;
891 let o_start = data_start + 4;
892 let o_end = o_start.checked_add(offset_col_len).ok_or_else(|| {
893 io::Error::new(
894 io::ErrorKind::InvalidData,
895 "fast field offset column range overflow",
896 )
897 })?;
898 if o_end > data_end {
899 return Err(io::Error::new(
900 io::ErrorKind::UnexpectedEof,
901 "fast field offset column is truncated",
902 ));
903 }
904 let v_start = o_end;
905 let v_end = data_end;
906 let offset_data = file_data.slice(o_start..o_end);
907 let value_data = file_data.slice(v_start..v_end);
908 let offset_count = (entry.num_docs as usize).checked_add(1).ok_or_else(|| {
909 io::Error::new(io::ErrorKind::InvalidData, "fast field doc count overflow")
910 })?;
911 codec::validate_auto(offset_data.as_slice(), offset_count)?;
912
913 let mut previous = 0u64;
914 for index in 0..offset_count {
915 let offset = codec::auto_read(offset_data.as_slice(), index);
916 if offset > u32::MAX as u64 || (index == 0 && offset != 0) || offset < previous
917 {
918 return Err(io::Error::new(
919 io::ErrorKind::InvalidData,
920 "fast field value offsets are invalid",
921 ));
922 }
923 previous = offset;
924 }
925 codec::validate_auto(value_data.as_slice(), previous as usize)?;
926
927 (
928 file_data.slice(data_start..data_end),
929 offset_data,
930 value_data,
931 )
932 } else {
933 let block_data = file_data.slice(data_start..data_end);
934 codec::validate_auto(block_data.as_slice(), entry.num_docs as usize)?;
935 (block_data, empty.clone(), empty.clone())
936 };
937
938 if toc.column_type == FastFieldColumnType::TextOrdinal {
939 if entry.dict_count == 0 && entry.dict_len != 0 {
940 return Err(io::Error::new(
941 io::ErrorKind::InvalidData,
942 "empty fast field dictionary has data",
943 ));
944 }
945 validate_text_dict_bytes(&raw[dict_start..dict_end], entry.dict_count)?;
946 } else if entry.dict_count != 0 || entry.dict_len != 0 {
947 return Err(io::Error::new(
948 io::ErrorKind::InvalidData,
949 "numeric fast field contains a text dictionary",
950 ));
951 }
952
953 let dict = if entry.dict_count > 0 {
955 Some(TextDictReader::new_lazy(
956 file_data.slice(dict_start..dict_end),
957 entry.dict_count,
958 ))
959 } else {
960 None
961 };
962
963 let raw_dict = if entry.dict_len > 0 {
964 file_data.slice(dict_start..dict_end)
965 } else {
966 empty.clone()
967 };
968
969 blocks.push(ColumnBlock {
970 cumulative_docs: cumulative,
971 num_docs: entry.num_docs,
972 data: block_data,
973 offset_data,
974 value_data,
975 dict,
976 raw_dict,
977 });
978
979 cumulative = cumulative.checked_add(entry.num_docs).ok_or_else(|| {
980 io::Error::new(io::ErrorKind::InvalidData, "fast field doc count overflow")
981 })?;
982 pos = dict_end;
983 }
984
985 if pos != region_end || cumulative != toc.num_docs {
986 return Err(io::Error::new(
987 io::ErrorKind::InvalidData,
988 "fast field block totals are inconsistent with the TOC",
989 ));
990 }
991 if toc.num_docs > 0 && blocks.is_empty() {
992 return Err(io::Error::new(
993 io::ErrorKind::InvalidData,
994 "non-empty fast field has no blocks",
995 ));
996 }
997
998 Ok(Self {
999 column_type: toc.column_type,
1000 num_docs: toc.num_docs,
1001 multi: toc.multi,
1002 blocks,
1003 text_state: OnceLock::new(),
1004 })
1005 }
1006
1007 fn ensure_text_state(&self) -> &TextState {
1010 self.text_state
1011 .get_or_init(|| Self::build_text_state(&self.blocks))
1012 }
1013
1014 fn build_text_state(blocks: &[ColumnBlock]) -> TextState {
1017 let blocks_with_dict = blocks.iter().filter(|b| b.dict.is_some()).count();
1020 if blocks_with_dict <= 1 {
1021 for block in blocks.iter() {
1022 if let Some(ref dict) = block.dict {
1023 return TextState {
1025 global_dict: TextDictReader::new_lazy(block.raw_dict.clone(), dict.len()),
1026 ordinal_maps: vec![Vec::new(); blocks.len()],
1027 };
1028 }
1029 }
1030 return TextState {
1032 global_dict: TextDictReader::new_lazy(OwnedBytes::new(Vec::new()), 0),
1033 ordinal_maps: vec![Vec::new(); blocks.len()],
1034 };
1035 }
1036
1037 let mut unique_map: BTreeMap<String, u32> = BTreeMap::new();
1048 for block in blocks.iter() {
1049 if let Some(ref dict) = block.dict {
1050 for ord in 0..dict.len() {
1051 if let Some(text) = dict.get(ord) {
1052 unique_map.entry(text.to_string()).or_insert(0);
1053 }
1054 }
1055 }
1056 }
1057 for (i, value) in unique_map.values_mut().enumerate() {
1059 *value = i as u32;
1060 }
1061
1062 let mut ordinal_maps = Vec::with_capacity(blocks.len());
1064 for block in blocks.iter() {
1065 if let Some(ref dict) = block.dict {
1066 let mut map = Vec::with_capacity(dict.len() as usize);
1067 for local_ord in 0..dict.len() {
1068 let text = dict
1069 .get(local_ord)
1070 .expect("block dict ordinal out of range");
1071 let global_ord = *unique_map
1072 .get(text)
1073 .expect("block dict entry not found in merged global dict");
1074 map.push(global_ord);
1075 }
1076 ordinal_maps.push(map);
1077 } else {
1078 ordinal_maps.push(Vec::new());
1079 }
1080 }
1081
1082 let mut dict_buf = Vec::new();
1084 let count = unique_map.len() as u32;
1085 for s in unique_map.keys() {
1086 let bytes = s.as_bytes();
1087 dict_buf.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
1088 dict_buf.extend_from_slice(bytes);
1089 }
1090
1091 TextState {
1092 global_dict: TextDictReader::new_lazy(OwnedBytes::new(dict_buf), count),
1093 ordinal_maps,
1094 }
1095 }
1096
1097 #[inline]
1100 fn remap_ordinal(&self, block_idx: usize, raw: u64) -> u64 {
1101 if self.column_type == FastFieldColumnType::TextOrdinal
1102 && raw != FAST_FIELD_MISSING
1103 && self.blocks.len() > 1
1104 {
1105 let state = self.ensure_text_state();
1106 let map = &state.ordinal_maps[block_idx];
1107 if !map.is_empty() {
1108 let idx = raw as usize;
1109 if idx < map.len() {
1110 map[idx] as u64
1111 } else {
1112 FAST_FIELD_MISSING
1113 }
1114 } else {
1115 raw
1116 }
1117 } else {
1118 raw
1119 }
1120 }
1121
1122 #[inline]
1124 fn find_block(&self, doc_id: u32) -> (usize, u32) {
1125 debug_assert!(!self.blocks.is_empty());
1126 if self.blocks.len() == 1 {
1128 return (0, doc_id);
1129 }
1130 let bi = self
1132 .blocks
1133 .partition_point(|b| b.cumulative_docs <= doc_id)
1134 .saturating_sub(1);
1135 (bi, doc_id - self.blocks[bi].cumulative_docs)
1136 }
1137
1138 #[inline]
1146 pub fn get_u64(&self, doc_id: u32) -> u64 {
1147 if doc_id >= self.num_docs {
1148 return FAST_FIELD_MISSING;
1149 }
1150 let (bi, local) = self.find_block(doc_id);
1151 let block = &self.blocks[bi];
1152
1153 if self.multi {
1154 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1155 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1156 if start >= end {
1157 return FAST_FIELD_MISSING;
1158 }
1159 let raw = codec::auto_read(block.value_data.as_slice(), start as usize);
1160 return self.remap_ordinal(bi, raw);
1161 }
1162
1163 let raw = codec::auto_read(block.data.as_slice(), local as usize);
1164 self.remap_ordinal(bi, raw)
1165 }
1166
1167 #[inline]
1170 fn block_value_range(&self, doc_id: u32) -> (usize, u32, u32) {
1171 if !self.multi || doc_id >= self.num_docs {
1172 return (0, 0, 0);
1173 }
1174 let (bi, local) = self.find_block(doc_id);
1175 let block = &self.blocks[bi];
1176 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1177 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1178 (bi, start, end)
1179 }
1180
1181 #[inline]
1185 pub fn value_range(&self, doc_id: u32) -> (u32, u32) {
1186 let (_, start, end) = self.block_value_range(doc_id);
1187 (start, end)
1188 }
1189
1190 #[inline]
1193 pub fn get_value_at(&self, index: u32) -> u64 {
1194 if self.blocks.len() == 1 {
1196 let raw = codec::auto_read(self.blocks[0].value_data.as_slice(), index as usize);
1197 return self.remap_ordinal(0, raw);
1198 }
1199 0
1201 }
1202
1203 pub fn get_multi_values(&self, doc_id: u32) -> Vec<u64> {
1205 let (bi, start, end) = self.block_value_range(doc_id);
1206 if start >= end {
1207 return Vec::new();
1208 }
1209 let block = &self.blocks[bi];
1210 (start..end)
1211 .map(|idx| {
1212 let raw = codec::auto_read(block.value_data.as_slice(), idx as usize);
1213 self.remap_ordinal(bi, raw)
1214 })
1215 .collect()
1216 }
1217
1218 #[inline]
1221 pub fn for_each_multi_value(&self, doc_id: u32, mut f: impl FnMut(u64) -> bool) -> bool {
1222 let (bi, start, end) = self.block_value_range(doc_id);
1223 if start >= end {
1224 return false;
1225 }
1226 let block = &self.blocks[bi];
1227 for idx in start..end {
1228 let raw = codec::auto_read(block.value_data.as_slice(), idx as usize);
1229 if f(self.remap_ordinal(bi, raw)) {
1230 return true;
1231 }
1232 }
1233 false
1234 }
1235
1236 pub fn scan_single_values(&self, mut f: impl FnMut(u32, u64)) {
1243 if self.multi {
1244 return;
1245 }
1246 const BATCH: usize = 256;
1247 let mut buf = [0u64; BATCH];
1248 let needs_remap =
1249 self.column_type == FastFieldColumnType::TextOrdinal && self.blocks.len() > 1;
1250
1251 let ordinal_maps = if needs_remap {
1253 Some(&self.ensure_text_state().ordinal_maps)
1254 } else {
1255 None
1256 };
1257
1258 for (block_idx, block) in self.blocks.iter().enumerate() {
1259 let n = block.num_docs as usize;
1260 let mut pos = 0;
1261
1262 let map = ordinal_maps.map(|maps| &maps[block_idx]);
1263 let has_map = map.is_some_and(|m| !m.is_empty());
1264
1265 while pos < n {
1266 let chunk = (n - pos).min(BATCH);
1267 codec::auto_read_batch(block.data.as_slice(), pos, &mut buf[..chunk]);
1268
1269 if has_map {
1270 let map = map.unwrap();
1271 for (i, &raw) in buf[..chunk].iter().enumerate() {
1272 let val = if raw != FAST_FIELD_MISSING {
1273 let idx = raw as usize;
1274 if idx < map.len() {
1275 map[idx] as u64
1276 } else {
1277 FAST_FIELD_MISSING
1278 }
1279 } else {
1280 raw
1281 };
1282 f(block.cumulative_docs + pos as u32 + i as u32, val);
1283 }
1284 } else {
1285 for (i, &val) in buf[..chunk].iter().enumerate() {
1286 f(block.cumulative_docs + pos as u32 + i as u32, val);
1287 }
1288 }
1289 pos += chunk;
1290 }
1291 }
1292 }
1293
1294 #[inline]
1299 pub fn has_value(&self, doc_id: u32) -> bool {
1300 if !self.multi {
1301 return doc_id < self.num_docs && self.get_u64(doc_id) != FAST_FIELD_MISSING;
1302 }
1303 let (_, start, end) = self.block_value_range(doc_id);
1304 start < end
1305 }
1306
1307 #[inline]
1312 pub fn get_i64(&self, doc_id: u32) -> i64 {
1313 zigzag_decode(self.get_u64(doc_id))
1314 }
1315
1316 #[inline]
1321 pub fn get_f64(&self, doc_id: u32) -> f64 {
1322 sortable_u64_to_f64(self.get_u64(doc_id))
1323 }
1324
1325 #[inline]
1327 pub fn get_ordinal(&self, doc_id: u32) -> u64 {
1328 self.get_u64(doc_id)
1329 }
1330
1331 pub fn get_text(&self, doc_id: u32) -> Option<&str> {
1334 if doc_id >= self.num_docs {
1335 return None;
1336 }
1337 let (bi, local) = self.find_block(doc_id);
1338 let block = &self.blocks[bi];
1339 let raw_ordinal = if self.multi {
1340 let start = codec::auto_read(block.offset_data.as_slice(), local as usize) as u32;
1341 let end = codec::auto_read(block.offset_data.as_slice(), local as usize + 1) as u32;
1342 if start >= end {
1343 return None;
1344 }
1345 codec::auto_read(block.value_data.as_slice(), start as usize)
1346 } else {
1347 codec::auto_read(block.data.as_slice(), local as usize)
1348 };
1349 if raw_ordinal == FAST_FIELD_MISSING {
1350 return None;
1351 }
1352 block.dict.as_ref().and_then(|d| d.get(raw_ordinal as u32))
1353 }
1354
1355 pub fn text_ordinal(&self, text: &str) -> Option<u64> {
1357 if self.column_type != FastFieldColumnType::TextOrdinal {
1358 return None;
1359 }
1360 self.ensure_text_state().global_dict.ordinal(text)
1361 }
1362
1363 pub fn text_dict(&self) -> Option<&TextDictReader> {
1365 if self.column_type != FastFieldColumnType::TextOrdinal {
1366 return None;
1367 }
1368 Some(&self.ensure_text_state().global_dict)
1369 }
1370
1371 pub fn num_blocks(&self) -> usize {
1373 self.blocks.len()
1374 }
1375
1376 pub fn blocks(&self) -> &[ColumnBlock] {
1378 &self.blocks
1379 }
1380}
1381
1382pub struct TextDictReader {
1390 data: OwnedBytes,
1392 count: u32,
1394 offsets: OnceLock<Vec<(u32, u32)>>,
1396}
1397
1398impl TextDictReader {
1399 fn new_lazy(data: OwnedBytes, count: u32) -> Self {
1402 Self {
1403 data,
1404 count,
1405 offsets: OnceLock::new(),
1406 }
1407 }
1408
1409 pub fn open(file_data: &OwnedBytes, dict_start: usize, count: u32) -> io::Result<Self> {
1412 if count == 0 {
1413 return Ok(Self::new_lazy(OwnedBytes::new(Vec::new()), 0));
1414 }
1415 let dict_slice = file_data.as_slice();
1417 if dict_start > dict_slice.len() {
1418 return Err(io::Error::new(
1419 io::ErrorKind::UnexpectedEof,
1420 "text dict offset out of bounds",
1421 ));
1422 }
1423 let mut pos = dict_start;
1424 for _ in 0..count {
1425 if pos.checked_add(4).is_none_or(|end| end > dict_slice.len()) {
1426 return Err(io::Error::new(
1427 io::ErrorKind::UnexpectedEof,
1428 "text dict truncated",
1429 ));
1430 }
1431 let len = u32::from_le_bytes(dict_slice[pos..pos + 4].try_into().unwrap()) as usize;
1432 pos += 4;
1433 if pos
1434 .checked_add(len)
1435 .is_none_or(|end| end > dict_slice.len())
1436 {
1437 return Err(io::Error::new(
1438 io::ErrorKind::UnexpectedEof,
1439 "text dict entry truncated",
1440 ));
1441 }
1442 std::str::from_utf8(&dict_slice[pos..pos + len])
1443 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
1444 pos += len;
1445 }
1446 let data = file_data.slice(dict_start..pos);
1447 Ok(Self::new_lazy(data, count))
1448 }
1449
1450 pub fn open_from_raw(raw_dict: &OwnedBytes, count: u32) -> io::Result<Self> {
1452 validate_text_dict_bytes(raw_dict.as_slice(), count)?;
1453 Ok(Self::new_lazy(raw_dict.clone(), count))
1454 }
1455
1456 #[inline]
1458 fn ensure_offsets(&self) -> &[(u32, u32)] {
1459 self.offsets.get_or_init(|| {
1460 let dict_slice = self.data.as_slice();
1461 let mut pos = 0usize;
1462 let mut offsets = Vec::with_capacity(self.count as usize);
1463 for _ in 0..self.count {
1464 debug_assert!(
1465 pos + 4 <= dict_slice.len(),
1466 "text dict truncated during lazy init"
1467 );
1468 let len = u32::from_le_bytes(dict_slice[pos..pos + 4].try_into().unwrap()) as usize;
1469 pos += 4;
1470 debug_assert!(
1471 pos + len <= dict_slice.len(),
1472 "text dict entry truncated during lazy init"
1473 );
1474 offsets.push((pos as u32, len as u32));
1475 pos += len;
1476 }
1477 offsets
1478 })
1479 }
1480
1481 pub fn get(&self, ordinal: u32) -> Option<&str> {
1483 let offsets = self.ensure_offsets();
1484 let &(off, len) = offsets.get(ordinal as usize)?;
1485 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1486 std::str::from_utf8(slice).ok()
1487 }
1488
1489 pub fn ordinal(&self, text: &str) -> Option<u64> {
1491 let offsets = self.ensure_offsets();
1492 offsets
1493 .binary_search_by(|&(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("").cmp(text)
1496 })
1497 .ok()
1498 .map(|i| i as u64)
1499 }
1500
1501 pub fn len(&self) -> u32 {
1503 self.count
1504 }
1505
1506 pub fn is_empty(&self) -> bool {
1508 self.count == 0
1509 }
1510
1511 pub fn iter(&self) -> impl Iterator<Item = &str> {
1513 let offsets = self.ensure_offsets();
1514 offsets.iter().map(|&(off, len)| {
1515 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
1516 std::str::from_utf8(slice).unwrap_or("")
1517 })
1518 }
1519}
1520
1521fn validate_text_dict_bytes(data: &[u8], count: u32) -> io::Result<()> {
1522 let minimum = (count as usize).checked_mul(4).ok_or_else(|| {
1523 io::Error::new(io::ErrorKind::InvalidData, "text dictionary size overflow")
1524 })?;
1525 if minimum > data.len() {
1526 return Err(io::Error::new(
1527 io::ErrorKind::UnexpectedEof,
1528 "text dictionary entry table is truncated",
1529 ));
1530 }
1531
1532 let mut pos = 0usize;
1533 let mut previous: Option<&str> = None;
1534 for _ in 0..count {
1535 let len_end = pos.checked_add(4).ok_or_else(|| {
1536 io::Error::new(
1537 io::ErrorKind::InvalidData,
1538 "text dictionary offset overflow",
1539 )
1540 })?;
1541 let len = u32::from_le_bytes(data[pos..len_end].try_into().unwrap()) as usize;
1542 pos = len_end;
1543 let end = pos.checked_add(len).ok_or_else(|| {
1544 io::Error::new(
1545 io::ErrorKind::InvalidData,
1546 "text dictionary offset overflow",
1547 )
1548 })?;
1549 if end > data.len() {
1550 return Err(io::Error::new(
1551 io::ErrorKind::UnexpectedEof,
1552 "text dictionary entry is truncated",
1553 ));
1554 }
1555 let value = std::str::from_utf8(&data[pos..end])
1556 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
1557 if previous.is_some_and(|previous| previous >= value) {
1558 return Err(io::Error::new(
1559 io::ErrorKind::InvalidData,
1560 "text dictionary entries are not strictly increasing",
1561 ));
1562 }
1563 previous = Some(value);
1564 pos = end;
1565 }
1566 if pos != data.len() {
1567 return Err(io::Error::new(
1568 io::ErrorKind::InvalidData,
1569 "text dictionary contains trailing data",
1570 ));
1571 }
1572 Ok(())
1573}
1574
1575pub fn write_fast_field_toc_and_footer(
1579 writer: &mut dyn Write,
1580 toc_offset: u64,
1581 entries: &[FastFieldTocEntry],
1582) -> io::Result<()> {
1583 for e in entries {
1584 e.write_to(writer)?;
1585 }
1586 writer.write_u64::<LittleEndian>(toc_offset)?;
1587 writer.write_u32::<LittleEndian>(entries.len() as u32)?;
1588 writer.write_u32::<LittleEndian>(FAST_FIELD_MAGIC)?;
1589 Ok(())
1590}
1591
1592pub fn read_fast_field_footer(file_data: &[u8]) -> io::Result<(u64, u32)> {
1595 let len = file_data.len();
1596 if len < FAST_FIELD_FOOTER_SIZE as usize {
1597 return Err(io::Error::new(
1598 io::ErrorKind::UnexpectedEof,
1599 "fast field file too small for footer",
1600 ));
1601 }
1602 let footer = &file_data[len - FAST_FIELD_FOOTER_SIZE as usize..];
1603 let mut cursor = std::io::Cursor::new(footer);
1604 let toc_offset = cursor.read_u64::<LittleEndian>()?;
1605 let num_columns = cursor.read_u32::<LittleEndian>()?;
1606 let magic = cursor.read_u32::<LittleEndian>()?;
1607 if magic != FAST_FIELD_MAGIC {
1608 return Err(io::Error::new(
1609 io::ErrorKind::InvalidData,
1610 format!("bad fast field magic: 0x{:08x}", magic),
1611 ));
1612 }
1613 Ok((toc_offset, num_columns))
1614}
1615
1616pub fn read_fast_field_toc(
1618 file_data: &[u8],
1619 toc_offset: u64,
1620 num_columns: u32,
1621) -> io::Result<Vec<FastFieldTocEntry>> {
1622 let start = usize::try_from(toc_offset).map_err(|_| {
1623 io::Error::new(
1624 io::ErrorKind::InvalidData,
1625 "fast field TOC offset exceeds address space",
1626 )
1627 })?;
1628 let expected = (num_columns as usize)
1629 .checked_mul(FAST_FIELD_TOC_ENTRY_SIZE)
1630 .ok_or_else(|| {
1631 io::Error::new(io::ErrorKind::InvalidData, "fast field TOC size overflow")
1632 })?;
1633 let end = start.checked_add(expected).ok_or_else(|| {
1634 io::Error::new(io::ErrorKind::InvalidData, "fast field TOC range overflow")
1635 })?;
1636 if end > file_data.len() {
1637 return Err(io::Error::new(
1638 io::ErrorKind::UnexpectedEof,
1639 "fast field TOC out of bounds",
1640 ));
1641 }
1642 let mut cursor = std::io::Cursor::new(&file_data[start..end]);
1643 let mut entries = Vec::new();
1644 entries
1645 .try_reserve_exact(num_columns as usize)
1646 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "too many fast field columns"))?;
1647 for _ in 0..num_columns {
1648 entries.push(FastFieldTocEntry::read_from(&mut cursor)?);
1649 }
1650 Ok(entries)
1651}
1652
1653#[cfg(test)]
1656mod tests {
1657 use super::*;
1658
1659 #[test]
1660 fn test_zigzag_roundtrip() {
1661 for v in [0i64, 1, -1, 42, -42, i64::MAX, i64::MIN] {
1662 assert_eq!(zigzag_decode(zigzag_encode(v)), v);
1663 }
1664 }
1665
1666 #[test]
1667 fn test_f64_sortable_roundtrip() {
1668 for v in [0.0f64, 1.0, -1.0, f64::MAX, f64::MIN, f64::MIN_POSITIVE] {
1669 assert_eq!(sortable_u64_to_f64(f64_to_sortable_u64(v)), v);
1670 }
1671 }
1672
1673 #[test]
1674 fn test_f64_sortable_order() {
1675 let values = [-100.0f64, -1.0, -0.0, 0.0, 0.5, 1.0, 100.0];
1676 let encoded: Vec<u64> = values.iter().map(|&v| f64_to_sortable_u64(v)).collect();
1677 for i in 1..encoded.len() {
1678 assert!(
1679 encoded[i] >= encoded[i - 1],
1680 "{} >= {} failed for {} vs {}",
1681 encoded[i],
1682 encoded[i - 1],
1683 values[i],
1684 values[i - 1]
1685 );
1686 }
1687 }
1688
1689 #[test]
1690 fn test_bitpack_roundtrip() {
1691 let values: Vec<u64> = vec![0, 3, 7, 15, 0, 1, 6, 12];
1692 let bpv = 4u8;
1693 let mut packed = Vec::new();
1694 bitpack_write(&values, bpv, &mut packed);
1695
1696 for (i, &expected) in values.iter().enumerate() {
1697 let got = bitpack_read(&packed, bpv, i);
1698 assert_eq!(got, expected, "index {}", i);
1699 }
1700 }
1701
1702 #[test]
1703 fn test_bitpack_high_bpv_regression() {
1704 for bpv in [57u8, 58, 59, 60, 63, 64] {
1707 let max_val = if bpv == 64 {
1708 u64::MAX
1709 } else {
1710 (1u64 << bpv) - 1
1711 };
1712 let values: Vec<u64> = (0..32)
1713 .map(|i: u64| {
1714 if max_val == u64::MAX {
1715 i * 7
1716 } else {
1717 (i * 7) % (max_val + 1)
1718 }
1719 })
1720 .collect();
1721 let mut packed = Vec::new();
1722 bitpack_write(&values, bpv, &mut packed);
1723 for (i, &expected) in values.iter().enumerate() {
1724 let got = bitpack_read(&packed, bpv, i);
1725 assert_eq!(got, expected, "high bpv={} index={}", bpv, i);
1726 }
1727 }
1728 }
1729
1730 #[test]
1731 fn test_bitpack_various_widths() {
1732 for bpv in [1u8, 2, 3, 5, 7, 8, 13, 16, 32, 64] {
1733 let max_val = if bpv == 64 {
1734 u64::MAX
1735 } else {
1736 (1u64 << bpv) - 1
1737 };
1738 let values: Vec<u64> = (0..100)
1739 .map(|i: u64| {
1740 if max_val == u64::MAX {
1741 i
1742 } else {
1743 i % (max_val + 1)
1744 }
1745 })
1746 .collect();
1747 let mut packed = Vec::new();
1748 bitpack_write(&values, bpv, &mut packed);
1749
1750 for (i, &expected) in values.iter().enumerate() {
1751 let got = bitpack_read(&packed, bpv, i);
1752 assert_eq!(got, expected, "bpv={} index={}", bpv, i);
1753 }
1754 }
1755 }
1756
1757 fn owned(buf: Vec<u8>) -> OwnedBytes {
1759 OwnedBytes::new(buf)
1760 }
1761
1762 #[test]
1763 fn test_writer_reader_u64_roundtrip() {
1764 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
1765 writer.add_u64(0, 100);
1766 writer.add_u64(1, 200);
1767 writer.add_u64(2, 150);
1768 writer.add_u64(4, 300); writer.pad_to(5);
1770
1771 let mut buf = Vec::new();
1772 let (mut toc, _bytes) = writer.serialize(&mut buf, 0).unwrap();
1773 toc.field_id = 42;
1774
1775 let toc_offset = buf.len() as u64;
1777 write_fast_field_toc_and_footer(&mut buf, toc_offset, &[toc]).unwrap();
1778
1779 let ob = owned(buf);
1781 let (toc_off, num_cols) = read_fast_field_footer(&ob).unwrap();
1782 assert_eq!(num_cols, 1);
1783 let tocs = read_fast_field_toc(&ob, toc_off, num_cols).unwrap();
1784 assert_eq!(tocs.len(), 1);
1785 assert_eq!(tocs[0].field_id, 42);
1786
1787 let reader = FastFieldReader::open(&ob, &tocs[0]).unwrap();
1788 assert_eq!(reader.get_u64(0), 100);
1789 assert_eq!(reader.get_u64(1), 200);
1790 assert_eq!(reader.get_u64(2), 150);
1791 assert_eq!(reader.get_u64(3), FAST_FIELD_MISSING); assert_eq!(reader.get_u64(4), 300);
1793 }
1794
1795 #[test]
1796 fn test_writer_reader_i64_roundtrip() {
1797 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
1798 writer.add_i64(0, -100);
1799 writer.add_i64(1, 50);
1800 writer.add_i64(2, 0);
1801 writer.pad_to(3);
1802
1803 let mut buf = Vec::new();
1804 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1805 let ob = owned(buf);
1806 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1807 assert_eq!(reader.get_i64(0), -100);
1808 assert_eq!(reader.get_i64(1), 50);
1809 assert_eq!(reader.get_i64(2), 0);
1810 }
1811
1812 #[test]
1813 fn test_writer_reader_f64_roundtrip() {
1814 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::F64);
1815 writer.add_f64(0, -1.5);
1816 writer.add_f64(1, 3.15);
1817 writer.add_f64(2, 0.0);
1818 writer.pad_to(3);
1819
1820 let mut buf = Vec::new();
1821 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1822 let ob = owned(buf);
1823 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1824 assert_eq!(reader.get_f64(0), -1.5);
1825 assert_eq!(reader.get_f64(1), 3.15);
1826 assert_eq!(reader.get_f64(2), 0.0);
1827 }
1828
1829 #[test]
1830 fn test_writer_reader_text_roundtrip() {
1831 let mut writer = FastFieldWriter::new_text();
1832 writer.add_text(0, "banana");
1833 writer.add_text(1, "apple");
1834 writer.add_text(2, "cherry");
1835 writer.add_text(3, "apple"); writer.pad_to(5);
1838
1839 let mut buf = Vec::new();
1840 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1841 let ob = owned(buf);
1842 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1843
1844 assert_eq!(reader.get_text(0), Some("banana"));
1846 assert_eq!(reader.get_text(1), Some("apple"));
1847 assert_eq!(reader.get_text(2), Some("cherry"));
1848 assert_eq!(reader.get_text(3), Some("apple"));
1849 assert_eq!(reader.get_text(4), None); assert_eq!(reader.text_ordinal("apple"), Some(0));
1853 assert_eq!(reader.text_ordinal("banana"), Some(1));
1854 assert_eq!(reader.text_ordinal("cherry"), Some(2));
1855 assert_eq!(reader.text_ordinal("durian"), None);
1856 }
1857
1858 #[test]
1859 fn test_constant_column() {
1860 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
1861 for i in 0..100 {
1862 writer.add_u64(i, 42);
1863 }
1864
1865 let mut buf = Vec::new();
1866 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1867
1868 let ob = owned(buf);
1869 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1870 for i in 0..100 {
1871 assert_eq!(reader.get_u64(i), 42);
1872 }
1873 }
1874
1875 #[test]
1878 fn test_multi_value_u64_roundtrip() {
1879 let mut writer = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
1880 writer.add_u64(0, 10);
1882 writer.add_u64(0, 20);
1883 writer.add_u64(0, 30);
1884 writer.add_u64(2, 100);
1887 writer.add_u64(3, 5);
1889 writer.add_u64(3, 15);
1890 writer.pad_to(4);
1891
1892 let mut buf = Vec::new();
1893 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1894 assert!(toc.multi);
1895 assert_eq!(toc.num_docs, 4);
1896
1897 let ob = owned(buf);
1898 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1899 assert!(reader.multi);
1900
1901 assert_eq!(reader.get_u64(0), 10);
1903 let (s, e) = reader.value_range(0);
1904 assert_eq!(e - s, 3);
1905 assert_eq!(reader.get_value_at(s), 10);
1906 assert_eq!(reader.get_value_at(s + 1), 20);
1907 assert_eq!(reader.get_value_at(s + 2), 30);
1908
1909 assert_eq!(reader.get_u64(1), FAST_FIELD_MISSING);
1911 let (s, e) = reader.value_range(1);
1912 assert_eq!(s, e);
1913 assert!(!reader.has_value(1));
1914
1915 assert_eq!(reader.get_u64(2), 100);
1917 assert!(reader.has_value(2));
1918
1919 assert_eq!(reader.get_u64(3), 5);
1921 let (s, e) = reader.value_range(3);
1922 assert_eq!(e - s, 2);
1923 assert_eq!(reader.get_value_at(s), 5);
1924 assert_eq!(reader.get_value_at(s + 1), 15);
1925 }
1926
1927 #[test]
1928 fn test_multi_value_text_roundtrip() {
1929 let mut writer = FastFieldWriter::new_text_multi();
1930 writer.add_text(0, "banana");
1932 writer.add_text(0, "apple");
1933 writer.add_text(1, "cherry");
1935 writer.pad_to(3);
1937
1938 let mut buf = Vec::new();
1939 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
1940 assert!(toc.multi);
1941
1942 let ob = owned(buf);
1943 let reader = FastFieldReader::open(&ob, &toc).unwrap();
1944
1945 let (s, e) = reader.value_range(0);
1947 assert_eq!(e - s, 2);
1948 let ord0 = reader.get_value_at(s);
1949 let ord1 = reader.get_value_at(s + 1);
1950 assert_eq!(reader.text_dict().unwrap().get(ord0 as u32), Some("banana"));
1951 assert_eq!(reader.text_dict().unwrap().get(ord1 as u32), Some("apple"));
1952
1953 let (s, e) = reader.value_range(1);
1955 assert_eq!(e - s, 1);
1956 let ord = reader.get_value_at(s);
1957 assert_eq!(reader.text_dict().unwrap().get(ord as u32), Some("cherry"));
1958
1959 assert!(!reader.has_value(2));
1961 }
1962
1963 #[test]
1964 fn test_multi_value_full_toc_roundtrip() {
1965 let mut writer = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
1966 writer.add_u64(0, 1);
1967 writer.add_u64(0, 2);
1968 writer.add_u64(1, 3);
1969 writer.pad_to(2);
1970
1971 let mut buf = Vec::new();
1972 let (mut toc, _) = writer.serialize(&mut buf, 0).unwrap();
1973 toc.field_id = 7;
1974
1975 let toc_offset = buf.len() as u64;
1976 write_fast_field_toc_and_footer(&mut buf, toc_offset, &[toc]).unwrap();
1977
1978 let ob = owned(buf);
1979 let (toc_off, num_cols) = read_fast_field_footer(&ob).unwrap();
1980 let tocs = read_fast_field_toc(&ob, toc_off, num_cols).unwrap();
1981 assert_eq!(tocs[0].field_id, 7);
1982 assert!(tocs[0].multi);
1983
1984 let reader = FastFieldReader::open(&ob, &tocs[0]).unwrap();
1985 assert_eq!(reader.get_u64(0), 1);
1986 assert_eq!(reader.get_u64(1), 3);
1987 }
1988
1989 fn serialize_single_block(writer: &mut FastFieldWriter) -> (Vec<u8>, Vec<u8>, BlockIndexEntry) {
1992 let mut buf = Vec::new();
1993 let (_toc, _) = writer.serialize(&mut buf, 0).unwrap();
1994 let mut cursor = std::io::Cursor::new(&buf[4..4 + BLOCK_INDEX_ENTRY_SIZE]);
1996 let entry = BlockIndexEntry::read_from(&mut cursor).unwrap();
1997 let data_start = 4 + BLOCK_INDEX_ENTRY_SIZE;
1998 let data_end = data_start + entry.data_len as usize;
1999 let dict_end = data_end + entry.dict_len as usize;
2000 let data = buf[data_start..data_end].to_vec();
2001 let dict = if dict_end > data_end {
2002 buf[data_end..dict_end].to_vec()
2003 } else {
2004 Vec::new()
2005 };
2006 (data, dict, entry)
2007 }
2008
2009 fn assemble_blocked_column(
2011 field_id: u32,
2012 column_type: FastFieldColumnType,
2013 multi: bool,
2014 blocks: &[(u32, &[u8], u32, &[u8])], ) -> (Vec<u8>, FastFieldTocEntry) {
2016 use byteorder::{LittleEndian, WriteBytesExt};
2017
2018 let mut buf = Vec::new();
2019 let num_blocks = blocks.len() as u32;
2020
2021 buf.write_u32::<LittleEndian>(num_blocks).unwrap();
2023
2024 for &(num_docs, data, dict_count, dict) in blocks {
2026 let entry = BlockIndexEntry {
2027 num_docs,
2028 data_len: data.len() as u32,
2029 dict_count,
2030 dict_len: dict.len() as u32,
2031 };
2032 entry.write_to(&mut buf).unwrap();
2033 }
2034
2035 let mut total_docs = 0u32;
2037 for &(num_docs, data, _, dict) in blocks {
2038 buf.extend_from_slice(data);
2039 buf.extend_from_slice(dict);
2040 total_docs += num_docs;
2041 }
2042
2043 let data_len = buf.len() as u64;
2044
2045 let toc = FastFieldTocEntry {
2047 field_id,
2048 column_type,
2049 multi,
2050 data_offset: 0,
2051 data_len,
2052 num_docs: total_docs,
2053 dict_offset: 0,
2054 dict_count: 0,
2055 };
2056
2057 let toc_offset = buf.len() as u64;
2058 write_fast_field_toc_and_footer(&mut buf, toc_offset, std::slice::from_ref(&toc)).unwrap();
2059
2060 (buf, toc)
2061 }
2062
2063 #[test]
2064 fn test_multi_block_numeric_roundtrip() {
2065 let mut wa = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2067 wa.add_u64(0, 10);
2068 wa.add_u64(1, 20);
2069 wa.add_u64(2, 30);
2070 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2071
2072 let mut wb = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
2074 wb.add_u64(0, 40);
2075 wb.add_u64(1, 50);
2076 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2077
2078 let (buf, toc) = assemble_blocked_column(
2079 1,
2080 FastFieldColumnType::U64,
2081 false,
2082 &[
2083 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2084 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2085 ],
2086 );
2087
2088 let ob = owned(buf);
2089 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2090
2091 assert_eq!(reader.num_docs, 5);
2092 assert_eq!(reader.num_blocks(), 2);
2093 assert_eq!(reader.get_u64(0), 10);
2094 assert_eq!(reader.get_u64(1), 20);
2095 assert_eq!(reader.get_u64(2), 30);
2096 assert_eq!(reader.get_u64(3), 40);
2097 assert_eq!(reader.get_u64(4), 50);
2098 }
2099
2100 #[test]
2101 fn test_multi_block_text_roundtrip() {
2102 let mut wa = FastFieldWriter::new_text();
2104 wa.add_text(0, "alpha");
2105 wa.add_text(1, "beta");
2106 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2107
2108 let mut wb = FastFieldWriter::new_text();
2110 wb.add_text(0, "gamma");
2111 wb.add_text(1, "alpha");
2112 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2113
2114 let (buf, toc) = assemble_blocked_column(
2115 2,
2116 FastFieldColumnType::TextOrdinal,
2117 false,
2118 &[
2119 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2120 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2121 ],
2122 );
2123
2124 let ob = owned(buf);
2125 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2126
2127 assert_eq!(reader.num_docs, 4);
2128 assert_eq!(reader.num_blocks(), 2);
2129
2130 assert_eq!(reader.text_dict().unwrap().len(), 3);
2132
2133 assert_eq!(reader.get_text(0), Some("alpha"));
2135 assert_eq!(reader.get_text(1), Some("beta"));
2136
2137 assert_eq!(reader.get_text(2), Some("gamma"));
2139 assert_eq!(reader.get_text(3), Some("alpha"));
2140
2141 assert_eq!(reader.text_ordinal("alpha"), Some(0));
2143 assert_eq!(reader.text_ordinal("beta"), Some(1));
2144 assert_eq!(reader.text_ordinal("gamma"), Some(2));
2145
2146 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); }
2152
2153 #[test]
2162 fn test_multi_block_text_ordinal_mismatch_regression() {
2163 let mut wa = FastFieldWriter::new_text();
2165 wa.add_text(0, "book");
2166 wa.add_text(1, "wiki");
2167 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2168
2169 let mut wb = FastFieldWriter::new_text();
2171 wb.add_text(0, "apple");
2172 wb.add_text(1, "wiki");
2173 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2174
2175 let (buf, toc) = assemble_blocked_column(
2176 2,
2177 FastFieldColumnType::TextOrdinal,
2178 false,
2179 &[
2180 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2181 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2182 ],
2183 );
2184
2185 let ob = owned(buf);
2186 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2187
2188 assert_eq!(reader.text_dict().unwrap().len(), 3);
2190 assert_eq!(reader.text_ordinal("apple"), Some(0));
2191 assert_eq!(reader.text_ordinal("book"), Some(1));
2192 assert_eq!(reader.text_ordinal("wiki"), Some(2));
2193
2194 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();
2202 assert_eq!(reader.get_u64(1), wiki_ord, "wiki doc should match");
2203 assert_eq!(reader.get_u64(3), wiki_ord, "wiki doc should match");
2204 assert_ne!(reader.get_u64(0), wiki_ord, "book doc must NOT match wiki");
2205 assert_ne!(reader.get_u64(2), wiki_ord, "apple doc must NOT match wiki");
2206 }
2207
2208 #[test]
2211 fn test_i64_timestamps_with_missing_roundtrip() {
2212 let base_ts = 1724630400i64; let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
2214
2215 let mut expected_values: Vec<Option<i64>> = Vec::new();
2217 for i in 0..100u32 {
2218 if i % 5 == 0 {
2219 expected_values.push(None); } else {
2221 let ts = base_ts - (i as i64 * 86400);
2222 writer.add_i64(i, ts);
2223 expected_values.push(Some(ts));
2224 }
2225 }
2226 writer.pad_to(100);
2227
2228 let mut buf = Vec::new();
2229 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2230 let ob = owned(buf);
2231 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2232
2233 for (i, expected) in expected_values.iter().enumerate() {
2234 let raw = reader.get_u64(i as u32);
2235 match expected {
2236 None => {
2237 assert_eq!(
2238 raw, FAST_FIELD_MISSING,
2239 "doc {}: expected MISSING, got raw {}",
2240 i, raw
2241 );
2242 }
2243 Some(ts) => {
2244 assert_ne!(
2245 raw, FAST_FIELD_MISSING,
2246 "doc {}: expected timestamp {}, got MISSING",
2247 i, ts
2248 );
2249 let decoded = zigzag_decode(raw);
2250 assert_eq!(
2251 decoded,
2252 *ts,
2253 "doc {}: expected i64 {}, got i64 {} (raw zigzag: {}, expected zigzag: {})",
2254 i,
2255 ts,
2256 decoded,
2257 raw,
2258 zigzag_encode(*ts)
2259 );
2260 }
2261 }
2262 }
2263 }
2264
2265 #[test]
2268 fn test_issued_at_1724630400_various_sizes() {
2269 let target_ts = 1724630400i64;
2270 let target_zigzag = zigzag_encode(target_ts);
2271
2272 for num_docs in [2, 5, 10, 50, 100, 500, 1000, 2000] {
2273 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
2274 let target_doc = num_docs / 3;
2275
2276 for i in 0..num_docs as u32 {
2277 if i == target_doc as u32 {
2278 writer.add_i64(i, target_ts);
2279 } else if i % 3 == 0 {
2280 } else {
2282 let ts = 1700000000i64 + (i as i64 * 86400);
2283 writer.add_i64(i, ts);
2284 }
2285 }
2286 writer.pad_to(num_docs as u32);
2287
2288 let mut buf = Vec::new();
2289 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
2290 let ob = owned(buf);
2291 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2292
2293 let raw = reader.get_u64(target_doc as u32);
2294 assert_eq!(
2295 raw,
2296 target_zigzag,
2297 "num_docs={}: doc {} expected zigzag {} (ts {}), got {} (decoded i64: {})",
2298 num_docs,
2299 target_doc,
2300 target_zigzag,
2301 target_ts,
2302 raw,
2303 zigzag_decode(raw)
2304 );
2305 }
2306 }
2307
2308 #[test]
2309 fn test_multi_block_multi_value_numeric() {
2310 let mut wa = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2312 wa.add_u64(0, 1);
2313 wa.add_u64(0, 2);
2314 wa.add_u64(1, 3);
2315 wa.pad_to(2);
2316 let (data_a, dict_a, entry_a) = serialize_single_block(&mut wa);
2317
2318 let mut wb = FastFieldWriter::new_numeric_multi(FastFieldColumnType::U64);
2320 wb.add_u64(0, 4);
2321 wb.add_u64(0, 5);
2322 wb.add_u64(0, 6);
2323 wb.pad_to(2);
2324 let (data_b, dict_b, entry_b) = serialize_single_block(&mut wb);
2325
2326 let (buf, toc) = assemble_blocked_column(
2327 3,
2328 FastFieldColumnType::U64,
2329 true,
2330 &[
2331 (entry_a.num_docs, &data_a, entry_a.dict_count, &dict_a),
2332 (entry_b.num_docs, &data_b, entry_b.dict_count, &dict_b),
2333 ],
2334 );
2335
2336 let ob = owned(buf);
2337 let reader = FastFieldReader::open(&ob, &toc).unwrap();
2338
2339 assert_eq!(reader.num_docs, 4);
2340 assert_eq!(reader.num_blocks(), 2);
2341
2342 assert_eq!(reader.get_multi_values(0), vec![1, 2]);
2344 assert_eq!(reader.get_multi_values(1), vec![3]);
2346 assert_eq!(reader.get_multi_values(2), vec![4, 5, 6]);
2348 assert_eq!(reader.get_multi_values(3), Vec::<u64>::new());
2350 }
2351}