faucet_core/file_format/
orc.rs1use super::OrcOptions;
19use crate::error::FaucetError;
20use arrow::array::RecordBatch;
21use arrow::datatypes::SchemaRef;
22use orc_rust::projection::ProjectionMask;
23use orc_rust::reader::ChunkReader;
24use serde_json::Value;
25
26#[derive(Debug)]
31pub enum OrcInput {
32 File(std::fs::File),
34 Bytes(bytes::Bytes),
36}
37
38pub fn decode(bytes: &[u8], opts: &OrcOptions) -> Result<Vec<Value>, FaucetError> {
40 decode_input(OrcInput::Bytes(bytes::Bytes::copy_from_slice(bytes)), opts)
41}
42
43pub fn decode_owned(bytes: Vec<u8>, opts: &OrcOptions) -> Result<Vec<Value>, FaucetError> {
46 decode_input(OrcInput::Bytes(bytes::Bytes::from(bytes)), opts)
47}
48
49fn decode_input(input: OrcInput, opts: &OrcOptions) -> Result<Vec<Value>, FaucetError> {
50 let mut out = Vec::new();
51 read_batches(input, opts, 0, &mut |b| {
52 out.extend(crate::columnar::record_batch_to_values(&b)?);
53 Ok(())
54 })?;
55 Ok(out)
56}
57
58pub fn read_batches(
62 input: OrcInput,
63 opts: &OrcOptions,
64 batch_size: usize,
65 f: &mut dyn FnMut(RecordBatch) -> Result<(), FaucetError>,
66) -> Result<SchemaRef, FaucetError> {
67 read_batches_checked(input, opts, batch_size, &mut |_| Ok(()), f)
68}
69
70pub fn read_batches_checked(
74 input: OrcInput,
75 opts: &OrcOptions,
76 batch_size: usize,
77 check: &mut dyn FnMut(&SchemaRef) -> Result<(), FaucetError>,
78 f: &mut dyn FnMut(RecordBatch) -> Result<(), FaucetError>,
79) -> Result<SchemaRef, FaucetError> {
80 match input {
81 OrcInput::File(file) => read_from(file, opts, batch_size, check, f),
82 OrcInput::Bytes(b) => read_from(b, opts, batch_size, check, f),
83 }
84}
85
86fn read_from<R: ChunkReader>(
87 reader: R,
88 opts: &OrcOptions,
89 batch_size: usize,
90 check: &mut dyn FnMut(&SchemaRef) -> Result<(), FaucetError>,
91 f: &mut dyn FnMut(RecordBatch) -> Result<(), FaucetError>,
92) -> Result<SchemaRef, FaucetError> {
93 let mut builder = orc_rust::ArrowReaderBuilder::try_new(reader)
94 .map_err(|e| FaucetError::Source(format!("orc: not a readable ORC file: {e}")))?;
95 if let Some(cols) = &opts.columns {
96 let root = builder.file_metadata().root_data_type();
97 let known: Vec<&str> = root.children().iter().map(|c| c.name()).collect();
98 if let Some(missing) = cols.iter().find(|c| !known.contains(&c.as_str())) {
99 return Err(FaucetError::Source(format!(
100 "orc: column {missing:?} is not in the file (columns: {})",
101 known.join(", ")
102 )));
103 }
104 let mask = ProjectionMask::named_roots(root, cols);
105 builder = builder.with_projection(mask);
106 }
107 if batch_size > 0 {
108 builder = builder.with_batch_size(batch_size);
109 }
110 let schema = builder.schema();
111 check(&schema)?;
112 for batch in builder.build() {
113 let batch = batch.map_err(|e| FaucetError::Source(format!("orc: decode error: {e}")))?;
114 if batch.num_rows() > 0 {
115 f(batch)?;
116 }
117 }
118 Ok(schema)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 const FIXTURE: &[u8] = include_bytes!("../../tests/fixtures/orc/people.orc");
126
127 #[test]
128 fn reads_every_column_and_row() {
129 let rows = decode(FIXTURE, &OrcOptions::default()).expect("decode");
130 assert_eq!(rows.len(), 3);
131 assert_eq!(rows[0]["id"], serde_json::json!(1));
132 assert_eq!(rows[0]["name"], serde_json::json!("ada"));
133 assert!(rows[0].get("score").is_some());
134 }
135
136 #[test]
137 fn projection_keeps_only_the_named_columns() {
138 let opts = OrcOptions {
139 columns: Some(vec!["name".into()]),
140 };
141 let mut schemas = Vec::new();
142 let mut rows = Vec::new();
143 let schema = read_batches(
144 OrcInput::Bytes(bytes::Bytes::from_static(FIXTURE)),
145 &opts,
146 2,
147 &mut |b| {
148 schemas.push(b.schema());
149 rows.extend(crate::columnar::record_batch_to_values(&b)?);
150 Ok(())
151 },
152 )
153 .expect("read");
154 assert_eq!(schema.fields().len(), 1);
155 assert_eq!(schemas.len(), 2, "batch size 2 over 3 rows");
156 assert_eq!(rows[2], serde_json::json!({"name": "grace"}));
157 }
158
159 #[test]
160 fn unknown_columns_and_garbage_are_errors() {
161 let opts = OrcOptions {
162 columns: Some(vec!["nope".into()]),
163 };
164 let err = decode(FIXTURE, &opts).expect_err("unknown column");
165 assert!(err.to_string().contains("nope"), "{err}");
166 let err = decode(b"definitely not orc", &OrcOptions::default()).expect_err("garbage");
167 assert!(err.to_string().contains("orc"), "{err}");
168 }
169
170 #[test]
171 fn reads_from_a_local_file() {
172 let dir = tempfile::tempdir().expect("tmp");
173 let path = dir.path().join("p.orc");
174 std::fs::write(&path, FIXTURE).expect("write");
175 let mut n = 0;
176 read_batches(
177 OrcInput::File(std::fs::File::open(&path).expect("open")),
178 &OrcOptions::default(),
179 0,
180 &mut |b| {
181 n += b.num_rows();
182 Ok(())
183 },
184 )
185 .expect("read");
186 assert_eq!(n, 3);
187 }
188}