1use std::collections::BTreeMap;
16use std::io::{self, Read, Write};
17
18use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
19
20pub const FAST_FIELD_MAGIC: u32 = 0x31545346;
24
25pub const FAST_FIELD_FOOTER_SIZE: u64 = 16;
27
28pub const TEXT_MISSING_ORDINAL: u64 = u64::MAX;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[repr(u8)]
36pub enum FastFieldColumnType {
37 U64 = 0,
38 I64 = 1,
39 F64 = 2,
40 TextOrdinal = 3,
41}
42
43impl FastFieldColumnType {
44 pub fn from_u8(v: u8) -> Option<Self> {
45 match v {
46 0 => Some(Self::U64),
47 1 => Some(Self::I64),
48 2 => Some(Self::F64),
49 3 => Some(Self::TextOrdinal),
50 _ => None,
51 }
52 }
53}
54
55#[inline]
59pub fn zigzag_encode(v: i64) -> u64 {
60 ((v << 1) ^ (v >> 63)) as u64
61}
62
63#[inline]
65pub fn zigzag_decode(v: u64) -> i64 {
66 ((v >> 1) as i64) ^ -((v & 1) as i64)
67}
68
69#[inline]
73pub fn f64_to_sortable_u64(f: f64) -> u64 {
74 let bits = f.to_bits();
75 if (bits >> 63) == 0 {
76 bits ^ (1u64 << 63) } else {
78 !bits }
80}
81
82#[inline]
84pub fn sortable_u64_to_f64(v: u64) -> f64 {
85 let bits = if (v >> 63) != 0 {
86 v ^ (1u64 << 63) } else {
88 !v };
90 f64::from_bits(bits)
91}
92
93#[inline]
95pub fn bits_needed_u64(val: u64) -> u8 {
96 if val == 0 {
97 0
98 } else {
99 64 - val.leading_zeros() as u8
100 }
101}
102
103pub fn bitpack_write(values: &[u64], bits_per_value: u8, out: &mut Vec<u8>) {
108 if bits_per_value == 0 {
109 return; }
111 let bpv = bits_per_value as usize;
112 let total_bits = values.len() * bpv;
113 let total_bytes = total_bits.div_ceil(8);
114 out.reserve(total_bytes);
115
116 let start = out.len();
117 out.resize(start + total_bytes, 0);
118 let buf = &mut out[start..];
119
120 for (i, &val) in values.iter().enumerate() {
121 let bit_offset = i * bpv;
122 let byte_offset = bit_offset / 8;
123 let bit_shift = bit_offset % 8;
124
125 let mut remaining_bits = bpv;
127 let mut v = val;
128 let mut bo = byte_offset;
129 let mut bs = bit_shift;
130
131 while remaining_bits > 0 {
132 let can_write = (8 - bs).min(remaining_bits);
133 let mask = (1u64 << can_write) - 1;
134 buf[bo] |= ((v & mask) << bs) as u8;
135 v >>= can_write;
136 remaining_bits -= can_write;
137 bo += 1;
138 bs = 0;
139 }
140 }
141}
142
143#[inline]
149pub fn bitpack_read(data: &[u8], bits_per_value: u8, index: usize) -> u64 {
150 if bits_per_value == 0 {
151 return 0;
152 }
153 let bpv = bits_per_value as usize;
154 let bit_offset = index * bpv;
155 let byte_offset = bit_offset / 8;
156 let bit_shift = bit_offset % 8;
157
158 if byte_offset + 8 <= data.len() {
161 let raw = u64::from_le_bytes(data[byte_offset..byte_offset + 8].try_into().unwrap());
162 let mask = if bpv >= 64 {
163 u64::MAX
164 } else {
165 (1u64 << bpv) - 1
166 };
167 return (raw >> bit_shift) & mask;
168 }
169
170 let mut result: u64 = 0;
172 let mut remaining_bits = bpv;
173 let mut bo = byte_offset;
174 let mut bs = bit_shift;
175 let mut out_shift = 0;
176
177 while remaining_bits > 0 {
178 let can_read = (8 - bs).min(remaining_bits);
179 let mask = ((1u64 << can_read) - 1) as u8;
180 let byte_val = if bo < data.len() { data[bo] } else { 0 };
181 result |= (((byte_val >> bs) & mask) as u64) << out_shift;
182 remaining_bits -= can_read;
183 out_shift += can_read;
184 bo += 1;
185 bs = 0;
186 }
187
188 result
189}
190
191#[derive(Debug, Clone)]
198pub struct FastFieldTocEntry {
199 pub field_id: u32,
200 pub column_type: FastFieldColumnType,
201 pub data_offset: u64,
202 pub num_docs: u32,
203 pub min_value: u64,
204 pub bits_per_value: u8,
205 pub dict_offset: u64,
207 pub dict_count: u32,
209}
210
211pub const FAST_FIELD_TOC_ENTRY_SIZE: usize = 4 + 1 + 8 + 4 + 8 + 1 + 8 + 4; impl FastFieldTocEntry {
214 pub fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
215 w.write_u32::<LittleEndian>(self.field_id)?;
216 w.write_u8(self.column_type as u8)?;
217 w.write_u64::<LittleEndian>(self.data_offset)?;
218 w.write_u32::<LittleEndian>(self.num_docs)?;
219 w.write_u64::<LittleEndian>(self.min_value)?;
220 w.write_u8(self.bits_per_value)?;
221 w.write_u64::<LittleEndian>(self.dict_offset)?;
222 w.write_u32::<LittleEndian>(self.dict_count)?;
223 Ok(())
224 }
225
226 pub fn read_from(r: &mut dyn Read) -> io::Result<Self> {
227 let field_id = r.read_u32::<LittleEndian>()?;
228 let ct = r.read_u8()?;
229 let column_type = FastFieldColumnType::from_u8(ct)
230 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "bad column type"))?;
231 let data_offset = r.read_u64::<LittleEndian>()?;
232 let num_docs = r.read_u32::<LittleEndian>()?;
233 let min_value = r.read_u64::<LittleEndian>()?;
234 let bits_per_value = r.read_u8()?;
235 let dict_offset = r.read_u64::<LittleEndian>()?;
236 let dict_count = r.read_u32::<LittleEndian>()?;
237 Ok(Self {
238 field_id,
239 column_type,
240 data_offset,
241 num_docs,
242 min_value,
243 bits_per_value,
244 dict_offset,
245 dict_count,
246 })
247 }
248}
249
250pub struct FastFieldWriter {
254 pub column_type: FastFieldColumnType,
255 values: Vec<u64>,
258 text_values: Option<BTreeMap<String, u32>>,
261 text_per_doc: Option<Vec<Option<String>>>,
263}
264
265impl FastFieldWriter {
266 pub fn new_numeric(column_type: FastFieldColumnType) -> Self {
268 debug_assert!(matches!(
269 column_type,
270 FastFieldColumnType::U64 | FastFieldColumnType::I64 | FastFieldColumnType::F64
271 ));
272 Self {
273 column_type,
274 values: Vec::new(),
275 text_values: None,
276 text_per_doc: None,
277 }
278 }
279
280 pub fn new_text() -> Self {
282 Self {
283 column_type: FastFieldColumnType::TextOrdinal,
284 values: Vec::new(),
285 text_values: Some(BTreeMap::new()),
286 text_per_doc: Some(Vec::new()),
287 }
288 }
289
290 pub fn add_u64(&mut self, doc_id: u32, value: u64) {
292 let idx = doc_id as usize;
293 if idx >= self.values.len() {
294 self.values.resize(idx + 1, 0);
295 if let Some(ref mut tpd) = self.text_per_doc {
296 tpd.resize(idx + 1, None);
297 }
298 }
299 self.values[idx] = value;
300 }
301
302 pub fn add_i64(&mut self, doc_id: u32, value: i64) {
304 self.add_u64(doc_id, zigzag_encode(value));
305 }
306
307 pub fn add_f64(&mut self, doc_id: u32, value: f64) {
309 self.add_u64(doc_id, f64_to_sortable_u64(value));
310 }
311
312 pub fn add_text(&mut self, doc_id: u32, value: &str) {
314 let idx = doc_id as usize;
315 if idx >= self.values.len() {
316 self.values.resize(idx + 1, 0);
317 }
318 if let Some(ref mut tpd) = self.text_per_doc {
319 if idx >= tpd.len() {
320 tpd.resize(idx + 1, None);
321 }
322 tpd[idx] = Some(value.to_string());
323 }
324 if let Some(ref mut dict) = self.text_values {
325 let next_id = dict.len() as u32;
326 dict.entry(value.to_string()).or_insert(next_id);
327 }
328 }
329
330 pub fn pad_to(&mut self, num_docs: u32) {
332 let n = num_docs as usize;
333 if self.values.len() < n {
334 self.values.resize(n, 0);
335 if let Some(ref mut tpd) = self.text_per_doc {
336 tpd.resize(n, None);
337 }
338 }
339 }
340
341 pub fn num_docs(&self) -> u32 {
343 self.values.len() as u32
344 }
345
346 pub fn serialize(
351 &mut self,
352 writer: &mut dyn Write,
353 data_offset: u64,
354 ) -> io::Result<(FastFieldTocEntry, u64)> {
355 let num_docs = self.values.len() as u32;
356
357 if self.column_type == FastFieldColumnType::TextOrdinal {
359 self.resolve_text_ordinals();
360 }
361
362 let (min_value, bits_per_value) = if self.values.is_empty() {
364 (0u64, 0u8)
365 } else {
366 let min_val = *self.values.iter().min().unwrap();
367 let max_val = *self.values.iter().max().unwrap();
368 let range = max_val - min_val;
369 (min_val, bits_needed_u64(range))
370 };
371
372 let mut packed = Vec::new();
374 if bits_per_value > 0 {
375 let shifted: Vec<u64> = self.values.iter().map(|&v| v - min_value).collect();
376 bitpack_write(&shifted, bits_per_value, &mut packed);
377 }
378
379 writer.write_all(&packed)?;
381 let data_end = data_offset + packed.len() as u64;
382
383 let (dict_offset, dict_count, dict_bytes) =
385 if self.column_type == FastFieldColumnType::TextOrdinal {
386 let dict_off = data_end;
387 let (count, bytes) = self.write_text_dictionary(writer)?;
388 (dict_off, count, bytes)
389 } else {
390 (0u64, 0u32, 0u64)
391 };
392
393 let toc = FastFieldTocEntry {
394 field_id: 0, column_type: self.column_type,
396 data_offset,
397 num_docs,
398 min_value,
399 bits_per_value,
400 dict_offset,
401 dict_count,
402 };
403
404 Ok((toc, packed.len() as u64 + dict_bytes))
405 }
406
407 fn resolve_text_ordinals(&mut self) {
409 let dict = self.text_values.as_ref().expect("text_values required");
410 let tpd = self.text_per_doc.as_ref().expect("text_per_doc required");
411
412 let sorted_ordinals: BTreeMap<&str, u64> = dict
414 .keys()
415 .enumerate()
416 .map(|(ord, key)| (key.as_str(), ord as u64))
417 .collect();
418
419 for (i, doc_text) in tpd.iter().enumerate() {
421 match doc_text {
422 Some(text) => {
423 self.values[i] = sorted_ordinals[text.as_str()];
424 }
425 None => {
426 self.values[i] = TEXT_MISSING_ORDINAL;
427 }
428 }
429 }
430 }
431
432 fn write_text_dictionary(&self, writer: &mut dyn Write) -> io::Result<(u32, u64)> {
434 let dict = self.text_values.as_ref().expect("text_values required");
435 let mut bytes_written = 0u64;
436
437 let count = dict.len() as u32;
439 for key in dict.keys() {
440 let key_bytes = key.as_bytes();
441 writer.write_u32::<LittleEndian>(key_bytes.len() as u32)?;
442 writer.write_all(key_bytes)?;
443 bytes_written += 4 + key_bytes.len() as u64;
444 }
445
446 Ok((count, bytes_written))
447 }
448}
449
450use crate::directories::OwnedBytes;
453
454pub struct FastFieldReader {
459 pub column_type: FastFieldColumnType,
460 pub num_docs: u32,
461 min_value: u64,
462 bits_per_value: u8,
463 data: OwnedBytes,
465 text_dict: Option<TextDictReader>,
467}
468
469impl FastFieldReader {
470 pub fn open(file_data: &OwnedBytes, toc: &FastFieldTocEntry) -> io::Result<Self> {
473 let data_start = toc.data_offset as usize;
474 let bpv = toc.bits_per_value as usize;
475 let data_len = if bpv == 0 {
476 0
477 } else {
478 (toc.num_docs as usize * bpv).div_ceil(8)
479 };
480 let data_end = data_start + data_len;
481
482 if data_end > file_data.len() {
483 return Err(io::Error::new(
484 io::ErrorKind::UnexpectedEof,
485 "fast field data out of bounds",
486 ));
487 }
488
489 let data = file_data.slice(data_start..data_end);
491
492 let text_dict = if toc.column_type == FastFieldColumnType::TextOrdinal && toc.dict_count > 0
493 {
494 let dict_start = toc.dict_offset as usize;
495 Some(TextDictReader::open(file_data, dict_start, toc.dict_count)?)
496 } else {
497 None
498 };
499
500 Ok(Self {
501 column_type: toc.column_type,
502 num_docs: toc.num_docs,
503 min_value: toc.min_value,
504 bits_per_value: toc.bits_per_value,
505 data,
506 text_dict,
507 })
508 }
509
510 #[inline]
512 pub fn get_u64(&self, doc_id: u32) -> u64 {
513 if doc_id >= self.num_docs {
514 return 0;
515 }
516 if self.bits_per_value == 0 {
517 return self.min_value;
518 }
519 let raw = bitpack_read(self.data.as_slice(), self.bits_per_value, doc_id as usize);
520 raw + self.min_value
521 }
522
523 #[inline]
525 pub fn get_i64(&self, doc_id: u32) -> i64 {
526 zigzag_decode(self.get_u64(doc_id))
527 }
528
529 #[inline]
531 pub fn get_f64(&self, doc_id: u32) -> f64 {
532 sortable_u64_to_f64(self.get_u64(doc_id))
533 }
534
535 #[inline]
537 pub fn get_ordinal(&self, doc_id: u32) -> u64 {
538 self.get_u64(doc_id)
539 }
540
541 pub fn get_text(&self, doc_id: u32) -> Option<&str> {
544 let ordinal = self.get_ordinal(doc_id);
545 if ordinal == TEXT_MISSING_ORDINAL {
546 return None;
547 }
548 self.text_dict.as_ref().and_then(|d| d.get(ordinal as u32))
549 }
550
551 pub fn text_ordinal(&self, text: &str) -> Option<u64> {
553 self.text_dict.as_ref().and_then(|d| d.ordinal(text))
554 }
555
556 pub fn text_dict(&self) -> Option<&TextDictReader> {
558 self.text_dict.as_ref()
559 }
560
561 pub fn min_value(&self) -> u64 {
563 self.min_value
564 }
565
566 pub fn bits_per_value(&self) -> u8 {
568 self.bits_per_value
569 }
570
571 pub fn raw_data(&self) -> &OwnedBytes {
573 &self.data
574 }
575}
576
577pub struct TextDictReader {
585 data: OwnedBytes,
587 offsets: Vec<(u32, u32)>,
589}
590
591impl TextDictReader {
592 pub fn open(file_data: &OwnedBytes, dict_start: usize, count: u32) -> io::Result<Self> {
594 let dict_slice = file_data.as_slice();
596 let mut pos = dict_start;
597 let mut offsets = Vec::with_capacity(count as usize);
598
599 for _ in 0..count {
600 if pos + 4 > dict_slice.len() {
601 return Err(io::Error::new(
602 io::ErrorKind::UnexpectedEof,
603 "text dict truncated",
604 ));
605 }
606 let len = u32::from_le_bytes([
607 dict_slice[pos],
608 dict_slice[pos + 1],
609 dict_slice[pos + 2],
610 dict_slice[pos + 3],
611 ]) as usize;
612 pos += 4;
613 if pos + len > dict_slice.len() {
614 return Err(io::Error::new(
615 io::ErrorKind::UnexpectedEof,
616 "text dict entry truncated",
617 ));
618 }
619 std::str::from_utf8(&dict_slice[pos..pos + len]).map_err(|e| {
621 io::Error::new(io::ErrorKind::InvalidData, format!("invalid utf8: {}", e))
622 })?;
623 offsets.push((pos as u32, len as u32));
624 pos += len;
625 }
626
627 let data = file_data.slice(dict_start..pos);
629
630 for entry in offsets.iter_mut() {
632 entry.0 -= dict_start as u32;
633 }
634
635 Ok(Self { data, offsets })
636 }
637
638 pub fn get(&self, ordinal: u32) -> Option<&str> {
640 let &(off, len) = self.offsets.get(ordinal as usize)?;
641 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
642 Some(unsafe { std::str::from_utf8_unchecked(slice) })
644 }
645
646 pub fn ordinal(&self, text: &str) -> Option<u64> {
648 self.offsets
649 .binary_search_by(|&(off, len)| {
650 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
651 let entry = unsafe { std::str::from_utf8_unchecked(slice) };
653 entry.cmp(text)
654 })
655 .ok()
656 .map(|i| i as u64)
657 }
658
659 pub fn len(&self) -> u32 {
661 self.offsets.len() as u32
662 }
663
664 pub fn is_empty(&self) -> bool {
666 self.offsets.is_empty()
667 }
668
669 pub fn iter(&self) -> impl Iterator<Item = &str> {
671 self.offsets.iter().map(|&(off, len)| {
672 let slice = &self.data.as_slice()[off as usize..off as usize + len as usize];
673 unsafe { std::str::from_utf8_unchecked(slice) }
674 })
675 }
676}
677
678pub fn write_fast_field_toc_and_footer(
682 writer: &mut dyn Write,
683 toc_offset: u64,
684 entries: &[FastFieldTocEntry],
685) -> io::Result<()> {
686 for e in entries {
687 e.write_to(writer)?;
688 }
689 writer.write_u64::<LittleEndian>(toc_offset)?;
690 writer.write_u32::<LittleEndian>(entries.len() as u32)?;
691 writer.write_u32::<LittleEndian>(FAST_FIELD_MAGIC)?;
692 Ok(())
693}
694
695pub fn read_fast_field_footer(file_data: &[u8]) -> io::Result<(u64, u32)> {
698 let len = file_data.len();
699 if len < FAST_FIELD_FOOTER_SIZE as usize {
700 return Err(io::Error::new(
701 io::ErrorKind::UnexpectedEof,
702 "fast field file too small for footer",
703 ));
704 }
705 let footer = &file_data[len - FAST_FIELD_FOOTER_SIZE as usize..];
706 let mut cursor = std::io::Cursor::new(footer);
707 let toc_offset = cursor.read_u64::<LittleEndian>()?;
708 let num_columns = cursor.read_u32::<LittleEndian>()?;
709 let magic = cursor.read_u32::<LittleEndian>()?;
710 if magic != FAST_FIELD_MAGIC {
711 return Err(io::Error::new(
712 io::ErrorKind::InvalidData,
713 format!("bad fast field magic: 0x{:08x}", magic),
714 ));
715 }
716 Ok((toc_offset, num_columns))
717}
718
719pub fn read_fast_field_toc(
721 file_data: &[u8],
722 toc_offset: u64,
723 num_columns: u32,
724) -> io::Result<Vec<FastFieldTocEntry>> {
725 let start = toc_offset as usize;
726 let expected = num_columns as usize * FAST_FIELD_TOC_ENTRY_SIZE;
727 if start + expected > file_data.len() {
728 return Err(io::Error::new(
729 io::ErrorKind::UnexpectedEof,
730 "fast field TOC out of bounds",
731 ));
732 }
733 let mut cursor = std::io::Cursor::new(&file_data[start..start + expected]);
734 let mut entries = Vec::with_capacity(num_columns as usize);
735 for _ in 0..num_columns {
736 entries.push(FastFieldTocEntry::read_from(&mut cursor)?);
737 }
738 Ok(entries)
739}
740
741#[cfg(test)]
744mod tests {
745 use super::*;
746
747 #[test]
748 fn test_zigzag_roundtrip() {
749 for v in [0i64, 1, -1, 42, -42, i64::MAX, i64::MIN] {
750 assert_eq!(zigzag_decode(zigzag_encode(v)), v);
751 }
752 }
753
754 #[test]
755 fn test_f64_sortable_roundtrip() {
756 for v in [0.0f64, 1.0, -1.0, f64::MAX, f64::MIN, f64::MIN_POSITIVE] {
757 assert_eq!(sortable_u64_to_f64(f64_to_sortable_u64(v)), v);
758 }
759 }
760
761 #[test]
762 fn test_f64_sortable_order() {
763 let values = [-100.0f64, -1.0, -0.0, 0.0, 0.5, 1.0, 100.0];
764 let encoded: Vec<u64> = values.iter().map(|&v| f64_to_sortable_u64(v)).collect();
765 for i in 1..encoded.len() {
766 assert!(
767 encoded[i] >= encoded[i - 1],
768 "{} >= {} failed for {} vs {}",
769 encoded[i],
770 encoded[i - 1],
771 values[i],
772 values[i - 1]
773 );
774 }
775 }
776
777 #[test]
778 fn test_bitpack_roundtrip() {
779 let values: Vec<u64> = vec![0, 3, 7, 15, 0, 1, 6, 12];
780 let bpv = 4u8;
781 let mut packed = Vec::new();
782 bitpack_write(&values, bpv, &mut packed);
783
784 for (i, &expected) in values.iter().enumerate() {
785 let got = bitpack_read(&packed, bpv, i);
786 assert_eq!(got, expected, "index {}", i);
787 }
788 }
789
790 #[test]
791 fn test_bitpack_various_widths() {
792 for bpv in [1u8, 2, 3, 5, 7, 8, 13, 16, 32, 64] {
793 let max_val = if bpv == 64 {
794 u64::MAX
795 } else {
796 (1u64 << bpv) - 1
797 };
798 let values: Vec<u64> = (0..100)
799 .map(|i: u64| {
800 if max_val == u64::MAX {
801 i
802 } else {
803 i % (max_val + 1)
804 }
805 })
806 .collect();
807 let mut packed = Vec::new();
808 bitpack_write(&values, bpv, &mut packed);
809
810 for (i, &expected) in values.iter().enumerate() {
811 let got = bitpack_read(&packed, bpv, i);
812 assert_eq!(got, expected, "bpv={} index={}", bpv, i);
813 }
814 }
815 }
816
817 fn owned(buf: Vec<u8>) -> OwnedBytes {
819 OwnedBytes::new(buf)
820 }
821
822 #[test]
823 fn test_writer_reader_u64_roundtrip() {
824 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
825 writer.add_u64(0, 100);
826 writer.add_u64(1, 200);
827 writer.add_u64(2, 150);
828 writer.add_u64(4, 300); writer.pad_to(5);
830
831 let mut buf = Vec::new();
832 let (mut toc, _bytes) = writer.serialize(&mut buf, 0).unwrap();
833 toc.field_id = 42;
834
835 let toc_offset = buf.len() as u64;
837 write_fast_field_toc_and_footer(&mut buf, toc_offset, &[toc]).unwrap();
838
839 let ob = owned(buf);
841 let (toc_off, num_cols) = read_fast_field_footer(&ob).unwrap();
842 assert_eq!(num_cols, 1);
843 let tocs = read_fast_field_toc(&ob, toc_off, num_cols).unwrap();
844 assert_eq!(tocs.len(), 1);
845 assert_eq!(tocs[0].field_id, 42);
846
847 let reader = FastFieldReader::open(&ob, &tocs[0]).unwrap();
848 assert_eq!(reader.get_u64(0), 100);
849 assert_eq!(reader.get_u64(1), 200);
850 assert_eq!(reader.get_u64(2), 150);
851 assert_eq!(reader.get_u64(3), 0); assert_eq!(reader.get_u64(4), 300);
853 }
854
855 #[test]
856 fn test_writer_reader_i64_roundtrip() {
857 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::I64);
858 writer.add_i64(0, -100);
859 writer.add_i64(1, 50);
860 writer.add_i64(2, 0);
861 writer.pad_to(3);
862
863 let mut buf = Vec::new();
864 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
865 let ob = owned(buf);
866 let reader = FastFieldReader::open(&ob, &toc).unwrap();
867 assert_eq!(reader.get_i64(0), -100);
868 assert_eq!(reader.get_i64(1), 50);
869 assert_eq!(reader.get_i64(2), 0);
870 }
871
872 #[test]
873 fn test_writer_reader_f64_roundtrip() {
874 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::F64);
875 writer.add_f64(0, -1.5);
876 writer.add_f64(1, 3.15);
877 writer.add_f64(2, 0.0);
878 writer.pad_to(3);
879
880 let mut buf = Vec::new();
881 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
882 let ob = owned(buf);
883 let reader = FastFieldReader::open(&ob, &toc).unwrap();
884 assert_eq!(reader.get_f64(0), -1.5);
885 assert_eq!(reader.get_f64(1), 3.15);
886 assert_eq!(reader.get_f64(2), 0.0);
887 }
888
889 #[test]
890 fn test_writer_reader_text_roundtrip() {
891 let mut writer = FastFieldWriter::new_text();
892 writer.add_text(0, "banana");
893 writer.add_text(1, "apple");
894 writer.add_text(2, "cherry");
895 writer.add_text(3, "apple"); writer.pad_to(5);
898
899 let mut buf = Vec::new();
900 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
901 let ob = owned(buf);
902 let reader = FastFieldReader::open(&ob, &toc).unwrap();
903
904 assert_eq!(reader.get_text(0), Some("banana"));
906 assert_eq!(reader.get_text(1), Some("apple"));
907 assert_eq!(reader.get_text(2), Some("cherry"));
908 assert_eq!(reader.get_text(3), Some("apple"));
909 assert_eq!(reader.get_text(4), None); assert_eq!(reader.text_ordinal("apple"), Some(0));
913 assert_eq!(reader.text_ordinal("banana"), Some(1));
914 assert_eq!(reader.text_ordinal("cherry"), Some(2));
915 assert_eq!(reader.text_ordinal("durian"), None);
916 }
917
918 #[test]
919 fn test_constant_column() {
920 let mut writer = FastFieldWriter::new_numeric(FastFieldColumnType::U64);
921 for i in 0..100 {
922 writer.add_u64(i, 42);
923 }
924
925 let mut buf = Vec::new();
926 let (toc, _) = writer.serialize(&mut buf, 0).unwrap();
927 assert_eq!(toc.bits_per_value, 0); assert_eq!(toc.min_value, 42);
929
930 let ob = owned(buf);
931 let reader = FastFieldReader::open(&ob, &toc).unwrap();
932 for i in 0..100 {
933 assert_eq!(reader.get_u64(i), 42);
934 }
935 }
936}