1use std::collections::HashMap;
21use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
22use std::path::Path;
23
24use crate::error::{IoError, Result};
25
26const FILE_MAGIC: &[u8; 8] = b"ORCLITE\0";
31const STRIPE_MAGIC: &[u8; 4] = b"STRP";
32const FOOTER_MAGIC: &[u8; 4] = b"FOOT";
33const FILE_MAGIC_LEN: usize = 8;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[repr(u8)]
42pub enum ColumnEncoding {
43 Direct = 0,
45 Dictionary = 1,
47 RleV2 = 2,
49 DirectString = 3,
51}
52
53impl ColumnEncoding {
54 fn from_u8(v: u8) -> Result<Self> {
55 match v {
56 0 => Ok(ColumnEncoding::Direct),
57 1 => Ok(ColumnEncoding::Dictionary),
58 2 => Ok(ColumnEncoding::RleV2),
59 3 => Ok(ColumnEncoding::DirectString),
60 other => Err(IoError::FormatError(format!(
61 "Unknown ORC-lite column encoding byte: {}",
62 other
63 ))),
64 }
65 }
66}
67
68#[derive(Debug, Clone, PartialEq)]
74pub enum OrcColumnData {
75 Int64(Vec<i64>),
76 Float64(Vec<f64>),
77 Boolean(Vec<bool>),
78 String(Vec<String>),
79 NullableInt64(Vec<Option<i64>>),
81}
82
83impl OrcColumnData {
84 pub fn len(&self) -> usize {
86 match self {
87 OrcColumnData::Int64(v) => v.len(),
88 OrcColumnData::Float64(v) => v.len(),
89 OrcColumnData::Boolean(v) => v.len(),
90 OrcColumnData::String(v) => v.len(),
91 OrcColumnData::NullableInt64(v) => v.len(),
92 }
93 }
94
95 pub fn is_empty(&self) -> bool {
96 self.len() == 0
97 }
98
99 fn type_tag(&self) -> u8 {
100 match self {
101 OrcColumnData::Int64(_) => 0,
102 OrcColumnData::Float64(_) => 1,
103 OrcColumnData::Boolean(_) => 2,
104 OrcColumnData::String(_) => 3,
105 OrcColumnData::NullableInt64(_) => 4,
106 }
107 }
108}
109
110pub struct IntRleV2;
120
121impl IntRleV2 {
122 pub fn encode(values: &[i64]) -> Vec<u8> {
129 let mut out = Vec::new();
130
131 let count = values.len() as u32;
133 out.extend_from_slice(&count.to_le_bytes());
134
135 if values.is_empty() {
136 return out;
137 }
138
139 encode_zigzag_leb128(values[0], &mut out);
141
142 for i in 1..values.len() {
144 let delta = values[i].wrapping_sub(values[i - 1]);
145 encode_zigzag_leb128(delta, &mut out);
146 }
147
148 out
149 }
150
151 pub fn decode(data: &[u8]) -> Result<Vec<i64>> {
153 if data.len() < 4 {
154 return Err(IoError::FormatError(
155 "RLE v2: too short to read count".into(),
156 ));
157 }
158 let count = u32::from_le_bytes(
159 data[0..4]
160 .try_into()
161 .map_err(|_| IoError::FormatError("RLE v2: count bytes".into()))?,
162 ) as usize;
163
164 if count == 0 {
165 return Ok(Vec::new());
166 }
167
168 let mut pos = 4;
169 let (base, consumed) = decode_zigzag_leb128(&data[pos..])?;
170 pos += consumed;
171
172 let mut values = Vec::with_capacity(count);
173 values.push(base);
174
175 for _ in 1..count {
176 let (delta, consumed) = decode_zigzag_leb128(&data[pos..])?;
177 pos += consumed;
178 let prev = *values.last().expect("values non-empty");
179 values.push(prev.wrapping_add(delta));
180 }
181
182 Ok(values)
183 }
184}
185
186fn encode_zigzag_leb128(value: i64, out: &mut Vec<u8>) {
188 let zigzag = ((value << 1) ^ (value >> 63)) as u64;
190 let mut v = zigzag;
191 loop {
192 let low7 = (v & 0x7F) as u8;
193 v >>= 7;
194 if v == 0 {
195 out.push(low7);
196 break;
197 } else {
198 out.push(low7 | 0x80);
199 }
200 }
201}
202
203fn decode_zigzag_leb128(data: &[u8]) -> Result<(i64, usize)> {
205 let mut result: u64 = 0;
206 let mut shift = 0u32;
207 for (i, &byte) in data.iter().enumerate() {
208 result |= ((byte & 0x7F) as u64) << shift;
209 shift += 7;
210 if byte & 0x80 == 0 {
211 let signed = ((result >> 1) as i64) ^ -((result & 1) as i64);
213 return Ok((signed, i + 1));
214 }
215 if shift >= 64 {
216 return Err(IoError::FormatError(
217 "zigzag LEB-128: overflow (shift >= 64)".into(),
218 ));
219 }
220 }
221 Err(IoError::FormatError(
222 "zigzag LEB-128: truncated encoding".into(),
223 ))
224}
225
226#[derive(Debug, Clone)]
231struct StripeInfo {
232 offset: u64,
234 num_rows: u32,
236 column_names: Vec<String>,
238}
239
240pub struct OrcLiteWriter<W: Write + Seek> {
264 inner: BufWriter<W>,
265 stripes: Vec<StripeInfo>,
266 current_offset: u64,
267}
268
269impl<W: Write + Seek> OrcLiteWriter<W> {
270 pub fn new(writer: W) -> Result<Self> {
272 let mut bw = BufWriter::new(writer);
273 bw.write_all(FILE_MAGIC).map_err(IoError::Io)?;
274 Ok(OrcLiteWriter {
275 inner: bw,
276 stripes: Vec::new(),
277 current_offset: FILE_MAGIC_LEN as u64,
278 })
279 }
280
281 pub fn write_stripe(&mut self, columns: &[(String, OrcColumnData)]) -> Result<()> {
285 if columns.is_empty() {
286 return Ok(());
287 }
288
289 let num_rows = columns[0].1.len();
291 for (name, col) in columns {
292 if col.len() != num_rows {
293 return Err(IoError::ValidationError(format!(
294 "column '{}' has {} rows, expected {}",
295 name,
296 col.len(),
297 num_rows
298 )));
299 }
300 }
301
302 let stripe_offset = self.current_offset;
303
304 self.inner.write_all(STRIPE_MAGIC).map_err(IoError::Io)?;
306 self.current_offset += 4;
307
308 let num_rows_u32 = num_rows as u32;
310 let num_cols_u32 = columns.len() as u32;
311 self.inner
312 .write_all(&num_rows_u32.to_le_bytes())
313 .map_err(IoError::Io)?;
314 self.inner
315 .write_all(&num_cols_u32.to_le_bytes())
316 .map_err(IoError::Io)?;
317 self.current_offset += 8;
318
319 let mut encoded: Vec<(String, ColumnEncoding, u8, Vec<u8>)> = Vec::new();
321 for (name, col) in columns {
322 let (enc, type_tag, bytes) = encode_column(col)?;
323 encoded.push((name.clone(), enc, type_tag, bytes));
324 }
325
326 for (name, enc, type_tag, bytes) in &encoded {
328 let name_bytes = name.as_bytes();
329 let name_len = name_bytes.len() as u16;
330 self.inner
331 .write_all(&name_len.to_le_bytes())
332 .map_err(IoError::Io)?;
333 self.inner.write_all(name_bytes).map_err(IoError::Io)?;
334 self.inner.write_all(&[*enc as u8]).map_err(IoError::Io)?;
335 self.inner.write_all(&[*type_tag]).map_err(IoError::Io)?;
336 let data_len = bytes.len() as u32;
337 self.inner
338 .write_all(&data_len.to_le_bytes())
339 .map_err(IoError::Io)?;
340 self.current_offset += 2 + name_bytes.len() as u64 + 1 + 1 + 4;
341 }
342
343 for (_, _, _, bytes) in &encoded {
345 self.inner.write_all(bytes).map_err(IoError::Io)?;
346 self.current_offset += bytes.len() as u64;
347 }
348
349 self.stripes.push(StripeInfo {
350 offset: stripe_offset,
351 num_rows: num_rows_u32,
352 column_names: columns.iter().map(|(n, _)| n.clone()).collect(),
353 });
354
355 Ok(())
356 }
357
358 pub fn finalize(mut self) -> Result<()> {
360 let footer_offset = self.current_offset;
361
362 self.inner.write_all(FOOTER_MAGIC).map_err(IoError::Io)?;
364
365 let n_stripes = self.stripes.len() as u32;
367 self.inner
368 .write_all(&n_stripes.to_le_bytes())
369 .map_err(IoError::Io)?;
370
371 for stripe in &self.stripes {
373 self.inner
374 .write_all(&stripe.offset.to_le_bytes())
375 .map_err(IoError::Io)?;
376 self.inner
377 .write_all(&stripe.num_rows.to_le_bytes())
378 .map_err(IoError::Io)?;
379 let n_cols = stripe.column_names.len() as u32;
380 self.inner
381 .write_all(&n_cols.to_le_bytes())
382 .map_err(IoError::Io)?;
383 for col_name in &stripe.column_names {
384 let bytes = col_name.as_bytes();
385 let len = bytes.len() as u16;
386 self.inner
387 .write_all(&len.to_le_bytes())
388 .map_err(IoError::Io)?;
389 self.inner.write_all(bytes).map_err(IoError::Io)?;
390 }
391 }
392
393 let footer_length =
395 (self.inner.stream_position().map_err(IoError::Io)? - footer_offset) as u32;
396 self.inner
397 .write_all(&footer_length.to_le_bytes())
398 .map_err(IoError::Io)?;
399
400 self.inner.flush().map_err(IoError::Io)?;
401 Ok(())
402 }
403}
404
405#[derive(Debug, Clone)]
411pub struct StripeMetadata {
412 pub offset: u64,
414 pub num_rows: u32,
416 pub column_names: Vec<String>,
418}
419
420pub struct OrcLiteReader<R: Read + Seek> {
425 inner: BufReader<R>,
426 pub stripes: Vec<StripeMetadata>,
427}
428
429impl<R: Read + Seek> OrcLiteReader<R> {
430 pub fn open(reader: R) -> Result<Self> {
432 let mut br = BufReader::new(reader);
433
434 let mut magic = [0u8; FILE_MAGIC_LEN];
436 br.read_exact(&mut magic).map_err(IoError::Io)?;
437 if &magic != FILE_MAGIC {
438 return Err(IoError::FormatError(
439 "Not an ORC-lite file (bad magic)".into(),
440 ));
441 }
442
443 let file_size = br.seek(SeekFrom::End(0)).map_err(IoError::Io)?;
445 if file_size < FILE_MAGIC_LEN as u64 + 4 {
446 return Err(IoError::FormatError("ORC-lite file too small".into()));
447 }
448 br.seek(SeekFrom::End(-4)).map_err(IoError::Io)?;
449 let mut fl_bytes = [0u8; 4];
450 br.read_exact(&mut fl_bytes).map_err(IoError::Io)?;
451 let footer_length = u32::from_le_bytes(fl_bytes) as u64;
452
453 let footer_start = file_size - 4 - footer_length;
455 br.seek(SeekFrom::Start(footer_start))
456 .map_err(IoError::Io)?;
457
458 let mut fmagic = [0u8; 4];
460 br.read_exact(&mut fmagic).map_err(IoError::Io)?;
461 if &fmagic != FOOTER_MAGIC {
462 return Err(IoError::FormatError("ORC-lite: bad footer magic".into()));
463 }
464
465 let mut n_stripes_bytes = [0u8; 4];
467 br.read_exact(&mut n_stripes_bytes).map_err(IoError::Io)?;
468 let n_stripes = u32::from_le_bytes(n_stripes_bytes) as usize;
469
470 let mut stripes = Vec::with_capacity(n_stripes);
471 for _ in 0..n_stripes {
472 let mut buf8 = [0u8; 8];
473 br.read_exact(&mut buf8).map_err(IoError::Io)?;
474 let offset = u64::from_le_bytes(buf8);
475
476 let mut buf4 = [0u8; 4];
477 br.read_exact(&mut buf4).map_err(IoError::Io)?;
478 let num_rows = u32::from_le_bytes(buf4);
479
480 br.read_exact(&mut buf4).map_err(IoError::Io)?;
481 let n_cols = u32::from_le_bytes(buf4) as usize;
482
483 let mut column_names = Vec::with_capacity(n_cols);
484 for _ in 0..n_cols {
485 let mut len_bytes = [0u8; 2];
486 br.read_exact(&mut len_bytes).map_err(IoError::Io)?;
487 let name_len = u16::from_le_bytes(len_bytes) as usize;
488 let mut name_bytes = vec![0u8; name_len];
489 br.read_exact(&mut name_bytes).map_err(IoError::Io)?;
490 let name = String::from_utf8(name_bytes).map_err(|e| {
491 IoError::FormatError(format!("ORC-lite: invalid UTF-8 column name: {}", e))
492 })?;
493 column_names.push(name);
494 }
495
496 stripes.push(StripeMetadata {
497 offset,
498 num_rows,
499 column_names,
500 });
501 }
502
503 Ok(OrcLiteReader { inner: br, stripes })
504 }
505
506 pub fn read_stripe(&mut self, stripe_idx: usize) -> Result<HashMap<String, OrcColumnData>> {
508 let stripe = self.stripes.get(stripe_idx).ok_or_else(|| {
509 IoError::NotFound(format!(
510 "ORC-lite: stripe index {} out of range",
511 stripe_idx
512 ))
513 })?;
514 let stripe_offset = stripe.offset;
515 let num_rows = stripe.num_rows as usize;
516 let num_cols = stripe.column_names.len();
517
518 self.inner
519 .seek(SeekFrom::Start(stripe_offset))
520 .map_err(IoError::Io)?;
521
522 let mut smagic = [0u8; 4];
524 self.inner.read_exact(&mut smagic).map_err(IoError::Io)?;
525 if &smagic != STRIPE_MAGIC {
526 return Err(IoError::FormatError("ORC-lite: bad stripe magic".into()));
527 }
528
529 let mut skip8 = [0u8; 8];
531 self.inner.read_exact(&mut skip8).map_err(IoError::Io)?;
532
533 let mut col_headers: Vec<(String, ColumnEncoding, u8, u32)> = Vec::with_capacity(num_cols);
535 for _ in 0..num_cols {
536 let mut len_bytes = [0u8; 2];
537 self.inner.read_exact(&mut len_bytes).map_err(IoError::Io)?;
538 let name_len = u16::from_le_bytes(len_bytes) as usize;
539 let mut name_bytes = vec![0u8; name_len];
540 self.inner
541 .read_exact(&mut name_bytes)
542 .map_err(IoError::Io)?;
543 let name = String::from_utf8(name_bytes).map_err(|e| {
544 IoError::FormatError(format!("ORC-lite stripe: invalid UTF-8 column name: {}", e))
545 })?;
546
547 let mut enc_byte = [0u8; 1];
548 self.inner.read_exact(&mut enc_byte).map_err(IoError::Io)?;
549 let enc = ColumnEncoding::from_u8(enc_byte[0])?;
550
551 let mut type_tag_byte = [0u8; 1];
552 self.inner
553 .read_exact(&mut type_tag_byte)
554 .map_err(IoError::Io)?;
555 let type_tag = type_tag_byte[0];
556
557 let mut data_len_bytes = [0u8; 4];
558 self.inner
559 .read_exact(&mut data_len_bytes)
560 .map_err(IoError::Io)?;
561 let data_len = u32::from_le_bytes(data_len_bytes);
562
563 col_headers.push((name, enc, type_tag, data_len));
564 }
565
566 let mut result = HashMap::new();
568 for (name, enc, type_tag, data_len) in col_headers {
569 let mut data_bytes = vec![0u8; data_len as usize];
570 self.inner
571 .read_exact(&mut data_bytes)
572 .map_err(IoError::Io)?;
573 let col_data = decode_column(&data_bytes, enc, type_tag, num_rows)?;
574 result.insert(name, col_data);
575 }
576
577 Ok(result)
578 }
579
580 pub fn read_all(&mut self) -> Result<HashMap<String, OrcColumnData>> {
582 let n = self.stripes.len();
583 if n == 0 {
584 return Ok(HashMap::new());
585 }
586
587 let mut combined: HashMap<String, OrcColumnData> = HashMap::new();
588
589 for i in 0..n {
590 let stripe_data = self.read_stripe(i)?;
591 for (name, col) in stripe_data {
592 let entry = combined.entry(name).or_insert_with(|| match &col {
593 OrcColumnData::Int64(_) => OrcColumnData::Int64(Vec::new()),
594 OrcColumnData::Float64(_) => OrcColumnData::Float64(Vec::new()),
595 OrcColumnData::Boolean(_) => OrcColumnData::Boolean(Vec::new()),
596 OrcColumnData::String(_) => OrcColumnData::String(Vec::new()),
597 OrcColumnData::NullableInt64(_) => OrcColumnData::NullableInt64(Vec::new()),
598 });
599 concat_columns(entry, col)?;
600 }
601 }
602
603 Ok(combined)
604 }
605}
606
607fn encode_column(col: &OrcColumnData) -> Result<(ColumnEncoding, u8, Vec<u8>)> {
612 let type_tag = col.type_tag();
613 match col {
614 OrcColumnData::Int64(v) => {
615 let encoded = IntRleV2::encode(v);
617 Ok((ColumnEncoding::RleV2, type_tag, encoded))
618 }
619 OrcColumnData::Float64(v) => {
620 let mut bytes = Vec::with_capacity(v.len() * 8);
622 for &x in v {
623 bytes.extend_from_slice(&x.to_le_bytes());
624 }
625 Ok((ColumnEncoding::Direct, type_tag, bytes))
626 }
627 OrcColumnData::Boolean(v) => {
628 let mut bytes = Vec::with_capacity((v.len() + 7) / 8 + 4);
630 let count = v.len() as u32;
631 bytes.extend_from_slice(&count.to_le_bytes());
632 let mut byte = 0u8;
633 for (i, &b) in v.iter().enumerate() {
634 if b {
635 byte |= 1 << (i % 8);
636 }
637 if i % 8 == 7 {
638 bytes.push(byte);
639 byte = 0;
640 }
641 }
642 if v.len() % 8 != 0 {
643 bytes.push(byte);
644 }
645 Ok((ColumnEncoding::Direct, type_tag, bytes))
646 }
647 OrcColumnData::String(v) => {
648 let mut bytes = Vec::new();
650 let count = v.len() as u32;
651 bytes.extend_from_slice(&count.to_le_bytes());
652 for s in v {
653 let sb = s.as_bytes();
654 let slen = sb.len() as u32;
655 bytes.extend_from_slice(&slen.to_le_bytes());
656 bytes.extend_from_slice(sb);
657 }
658 Ok((ColumnEncoding::DirectString, type_tag, bytes))
659 }
660 OrcColumnData::NullableInt64(v) => {
661 let count = v.len() as u32;
663 let mut bytes = Vec::new();
664 bytes.extend_from_slice(&count.to_le_bytes());
665 let mut bitmap_byte = 0u8;
667 for (i, opt) in v.iter().enumerate() {
668 if opt.is_some() {
669 bitmap_byte |= 1 << (i % 8);
670 }
671 if i % 8 == 7 {
672 bytes.push(bitmap_byte);
673 bitmap_byte = 0;
674 }
675 }
676 if v.len() % 8 != 0 {
677 bytes.push(bitmap_byte);
678 }
679 let vals: Vec<i64> = v.iter().map(|o| o.unwrap_or(0)).collect();
681 let encoded = IntRleV2::encode(&vals);
682 bytes.extend_from_slice(&encoded);
683 Ok((ColumnEncoding::RleV2, type_tag, bytes))
684 }
685 }
686}
687
688fn decode_column(
689 data: &[u8],
690 enc: ColumnEncoding,
691 type_tag: u8,
692 _num_rows: usize,
693) -> Result<OrcColumnData> {
694 match type_tag {
695 0 => {
696 let values = IntRleV2::decode(data)?;
698 Ok(OrcColumnData::Int64(values))
699 }
700 1 => {
701 if !data.len().is_multiple_of(8) {
703 return Err(IoError::FormatError(
704 "ORC-lite: f64 column data not multiple of 8 bytes".into(),
705 ));
706 }
707 let values: Vec<f64> = data
708 .chunks_exact(8)
709 .map(|chunk| {
710 let arr: [u8; 8] = chunk.try_into().expect("chunk is exactly 8 bytes");
711 f64::from_le_bytes(arr)
712 })
713 .collect();
714 Ok(OrcColumnData::Float64(values))
715 }
716 2 => {
717 if data.len() < 4 {
719 return Err(IoError::FormatError(
720 "ORC-lite: boolean column too short".into(),
721 ));
722 }
723 let count = u32::from_le_bytes(
724 data[0..4]
725 .try_into()
726 .map_err(|_| IoError::FormatError("boolean count bytes".into()))?,
727 ) as usize;
728 let mut values = Vec::with_capacity(count);
729 for i in 0..count {
730 let byte_idx = 4 + i / 8;
731 let bit_idx = i % 8;
732 let byte = data.get(byte_idx).ok_or_else(|| {
733 IoError::FormatError("ORC-lite: boolean bitmap truncated".into())
734 })?;
735 values.push((byte >> bit_idx) & 1 == 1);
736 }
737 Ok(OrcColumnData::Boolean(values))
738 }
739 3 => {
740 if data.len() < 4 {
742 return Err(IoError::FormatError(
743 "ORC-lite: string column too short".into(),
744 ));
745 }
746 let count = u32::from_le_bytes(
747 data[0..4]
748 .try_into()
749 .map_err(|_| IoError::FormatError("string count bytes".into()))?,
750 ) as usize;
751 let mut pos = 4;
752 let mut values = Vec::with_capacity(count);
753 for _ in 0..count {
754 if pos + 4 > data.len() {
755 return Err(IoError::FormatError(
756 "ORC-lite: string length truncated".into(),
757 ));
758 }
759 let slen = u32::from_le_bytes(
760 data[pos..pos + 4]
761 .try_into()
762 .map_err(|_| IoError::FormatError("string len bytes".into()))?,
763 ) as usize;
764 pos += 4;
765 if pos + slen > data.len() {
766 return Err(IoError::FormatError(
767 "ORC-lite: string data truncated".into(),
768 ));
769 }
770 let s = String::from_utf8(data[pos..pos + slen].to_vec()).map_err(|e| {
771 IoError::FormatError(format!("ORC-lite: invalid UTF-8 string: {}", e))
772 })?;
773 values.push(s);
774 pos += slen;
775 }
776 Ok(OrcColumnData::String(values))
777 }
778 4 => {
779 if data.len() < 4 {
781 return Err(IoError::FormatError(
782 "ORC-lite: nullable int64 column too short".into(),
783 ));
784 }
785 let count = u32::from_le_bytes(
786 data[0..4]
787 .try_into()
788 .map_err(|_| IoError::FormatError("nullable count bytes".into()))?,
789 ) as usize;
790
791 let bitmap_bytes = (count + 7) / 8;
792 let bitmap_end = 4 + bitmap_bytes;
793 if bitmap_end > data.len() {
794 return Err(IoError::FormatError(
795 "ORC-lite: nullable bitmap truncated".into(),
796 ));
797 }
798
799 let values_raw = IntRleV2::decode(&data[bitmap_end..])?;
800 let mut values = Vec::with_capacity(count);
801 for i in 0..count {
802 let byte_idx = 4 + i / 8;
803 let bit_idx = i % 8;
804 let byte = data
805 .get(byte_idx)
806 .ok_or_else(|| IoError::FormatError("ORC-lite: nullable bitmap read".into()))?;
807 let is_valid = (byte >> bit_idx) & 1 == 1;
808 values.push(if is_valid {
809 values_raw.get(i).copied().map(Some).unwrap_or(None)
810 } else {
811 None
812 });
813 }
814 Ok(OrcColumnData::NullableInt64(values))
815 }
816 other => Err(IoError::FormatError(format!(
817 "ORC-lite: unknown type tag {}",
818 other
819 ))),
820 }
821}
822
823fn concat_columns(dst: &mut OrcColumnData, src: OrcColumnData) -> Result<()> {
825 match (dst, src) {
826 (OrcColumnData::Int64(a), OrcColumnData::Int64(b)) => a.extend(b),
827 (OrcColumnData::Float64(a), OrcColumnData::Float64(b)) => a.extend(b),
828 (OrcColumnData::Boolean(a), OrcColumnData::Boolean(b)) => a.extend(b),
829 (OrcColumnData::String(a), OrcColumnData::String(b)) => a.extend(b),
830 (OrcColumnData::NullableInt64(a), OrcColumnData::NullableInt64(b)) => a.extend(b),
831 _ => {
832 return Err(IoError::FormatError(
833 "ORC-lite: column type mismatch across stripes".into(),
834 ))
835 }
836 }
837 Ok(())
838}
839
840pub fn write_orc_lite<P: AsRef<Path>>(path: P, columns: &[(String, OrcColumnData)]) -> Result<()> {
846 let file = std::fs::File::create(path.as_ref()).map_err(IoError::Io)?;
847 let mut writer = OrcLiteWriter::new(file)?;
848 writer.write_stripe(columns)?;
849 writer.finalize()
850}
851
852pub fn read_orc_lite<P: AsRef<Path>>(path: P) -> Result<HashMap<String, OrcColumnData>> {
854 let file = std::fs::File::open(path.as_ref()).map_err(IoError::Io)?;
855 let mut reader = OrcLiteReader::open(file)?;
856 reader.read_all()
857}
858
859#[cfg(test)]
864mod tests {
865 use super::*;
866 use std::io::Cursor;
867
868 fn roundtrip(cols: &[(String, OrcColumnData)]) -> HashMap<String, OrcColumnData> {
870 let mut buf: Vec<u8> = Vec::new();
871 {
872 let cursor = Cursor::new(&mut buf);
873 let mut writer = OrcLiteWriter::new(cursor).expect("writer create");
874 writer.write_stripe(cols).expect("write stripe");
875 writer.finalize().expect("finalize");
876 }
877 let cursor = Cursor::new(&buf[..]);
878 let mut reader = OrcLiteReader::open(cursor).expect("reader open");
879 reader.read_all().expect("read all")
880 }
881
882 #[test]
883 fn test_int64_roundtrip() {
884 let vals: Vec<i64> = vec![1, 2, 3, 100, -5, 0, 1000];
885 let cols = vec![("ids".to_string(), OrcColumnData::Int64(vals.clone()))];
886 let result = roundtrip(&cols);
887 assert_eq!(result["ids"], OrcColumnData::Int64(vals));
888 }
889
890 #[test]
891 fn test_float64_roundtrip() {
892 let vals: Vec<f64> = vec![1.1, 2.2, -3.3, 0.0, 1e10];
893 let cols = vec![("values".to_string(), OrcColumnData::Float64(vals.clone()))];
894 let result = roundtrip(&cols);
895 assert_eq!(result["values"], OrcColumnData::Float64(vals));
896 }
897
898 #[test]
899 fn test_boolean_roundtrip() {
900 let vals: Vec<bool> = vec![true, false, true, true, false];
901 let cols = vec![("flags".to_string(), OrcColumnData::Boolean(vals.clone()))];
902 let result = roundtrip(&cols);
903 assert_eq!(result["flags"], OrcColumnData::Boolean(vals));
904 }
905
906 #[test]
907 fn test_string_roundtrip() {
908 let vals: Vec<String> = vec!["hello".into(), "world".into(), "ORC-lite".into(), "".into()];
909 let cols = vec![("names".to_string(), OrcColumnData::String(vals.clone()))];
910 let result = roundtrip(&cols);
911 assert_eq!(result["names"], OrcColumnData::String(vals));
912 }
913
914 #[test]
915 fn test_nullable_int64_roundtrip() {
916 let vals: Vec<Option<i64>> = vec![Some(1), None, Some(3), None, Some(99)];
917 let cols = vec![(
918 "opt_ids".to_string(),
919 OrcColumnData::NullableInt64(vals.clone()),
920 )];
921 let result = roundtrip(&cols);
922 assert_eq!(result["opt_ids"], OrcColumnData::NullableInt64(vals));
923 }
924
925 #[test]
926 fn test_multi_column_roundtrip() {
927 let n = 100;
928 let ids: Vec<i64> = (0..n).collect();
929 let vals: Vec<f64> = (0..n).map(|i| i as f64 * 1.5).collect();
930 let labels: Vec<String> = (0..n).map(|i| format!("item_{}", i)).collect();
931
932 let cols = vec![
933 ("id".to_string(), OrcColumnData::Int64(ids.clone())),
934 ("val".to_string(), OrcColumnData::Float64(vals.clone())),
935 ("label".to_string(), OrcColumnData::String(labels.clone())),
936 ];
937 let result = roundtrip(&cols);
938 assert_eq!(result["id"], OrcColumnData::Int64(ids));
939 assert_eq!(result["val"], OrcColumnData::Float64(vals));
940 assert_eq!(result["label"], OrcColumnData::String(labels));
941 }
942
943 #[test]
944 fn test_multi_stripe_roundtrip() {
945 let mut buf: Vec<u8> = Vec::new();
946 {
947 let cursor = Cursor::new(&mut buf);
948 let mut writer = OrcLiteWriter::new(cursor).expect("writer");
949 for stripe_id in 0..3 {
950 let start = stripe_id * 10i64;
951 let ids: Vec<i64> = (start..start + 10).collect();
952 let cols = vec![("id".to_string(), OrcColumnData::Int64(ids))];
953 writer.write_stripe(&cols).expect("write stripe");
954 }
955 writer.finalize().expect("finalize");
956 }
957
958 let cursor = Cursor::new(&buf[..]);
959 let mut reader = OrcLiteReader::open(cursor).expect("reader");
960 assert_eq!(reader.stripes.len(), 3);
961 let all = reader.read_all().expect("read all");
962 if let OrcColumnData::Int64(ids) = &all["id"] {
963 assert_eq!(ids.len(), 30);
964 assert_eq!(ids[0], 0);
965 assert_eq!(ids[29], 29);
966 } else {
967 panic!("expected Int64 column");
968 }
969 }
970
971 #[test]
972 fn test_int_rle_v2_roundtrip() {
973 let values: Vec<i64> = vec![0, 1, 1, 2, 3, 5, 8, 13, -7, 100, -100, i64::MAX / 2];
974 let encoded = IntRleV2::encode(&values);
975 let decoded = IntRleV2::decode(&encoded).expect("decode");
976 assert_eq!(decoded, values);
977 }
978
979 #[test]
980 fn test_int_rle_v2_empty() {
981 let encoded = IntRleV2::encode(&[]);
982 let decoded = IntRleV2::decode(&encoded).expect("decode empty");
983 assert!(decoded.is_empty());
984 }
985
986 #[test]
987 fn test_bad_magic() {
988 let cursor = Cursor::new(b"NOTMAGIC!!");
989 let result = OrcLiteReader::open(cursor);
990 assert!(result.is_err());
991 }
992
993 #[test]
994 fn test_file_roundtrip() {
995 let tmp_dir = std::env::temp_dir();
996 let path = tmp_dir.join("test_orc_lite.orc");
997
998 let ids: Vec<i64> = vec![10, 20, 30];
999 let vals: Vec<f64> = vec![1.0, 2.0, 3.0];
1000 let cols = vec![
1001 ("id".to_string(), OrcColumnData::Int64(ids.clone())),
1002 ("val".to_string(), OrcColumnData::Float64(vals.clone())),
1003 ];
1004
1005 write_orc_lite(&path, &cols).expect("write file");
1006 let result = read_orc_lite(&path).expect("read file");
1007
1008 assert_eq!(result["id"], OrcColumnData::Int64(ids));
1009 assert_eq!(result["val"], OrcColumnData::Float64(vals));
1010
1011 let _ = std::fs::remove_file(&path);
1013 }
1014}