1use std::sync::Arc;
46
47use datafusion::arrow::datatypes::SchemaRef;
48use datafusion::common::exec_err;
49use datafusion::config::TableOptions;
50use datafusion::dataframe::DataFrame;
51use datafusion::datasource::listing::{
52 ListingOptions, ListingTable, ListingTableConfig, ListingTableUrl,
53};
54use datafusion::error::Result;
55use datafusion::execution::config::SessionConfig;
56use datafusion::execution::context::{DataFilePaths, SessionContext, SessionState};
57use datafusion::execution::options::ReadOptions;
58
59use async_trait::async_trait;
60
61mod file_format;
62mod file_source;
63mod object_store_reader;
64mod physical_exec;
65
66pub use file_format::OrcFormat;
67pub use file_source::OrcSource;
68
69#[derive(Clone)]
71pub struct OrcReadOptions<'a> {
72 pub file_extension: &'a str,
73}
74
75impl Default for OrcReadOptions<'_> {
76 fn default() -> Self {
77 Self {
78 file_extension: "orc",
79 }
80 }
81}
82
83#[async_trait]
84impl ReadOptions<'_> for OrcReadOptions<'_> {
85 fn to_listing_options(
86 &self,
87 _config: &SessionConfig,
88 _table_options: TableOptions,
89 ) -> ListingOptions {
90 ListingOptions::new(Arc::new(OrcFormat)).with_file_extension(self.file_extension)
91 }
92
93 async fn get_resolved_schema(
94 &self,
95 config: &SessionConfig,
96 state: SessionState,
97 table_path: ListingTableUrl,
98 ) -> Result<SchemaRef> {
99 self._get_resolved_schema(config, state, table_path, None)
100 .await
101 }
102}
103
104pub trait SessionContextOrcExt {
107 fn read_orc<P: DataFilePaths + Send>(
108 &self,
109 table_paths: P,
110 options: OrcReadOptions<'_>,
111 ) -> impl std::future::Future<Output = Result<DataFrame>> + Send;
112
113 fn register_orc(
114 &self,
115 name: &str,
116 table_path: &str,
117 options: OrcReadOptions<'_>,
118 ) -> impl std::future::Future<Output = Result<()>> + Send;
119}
120
121impl SessionContextOrcExt for SessionContext {
122 async fn read_orc<P: DataFilePaths + Send>(
123 &self,
124 table_paths: P,
125 options: OrcReadOptions<'_>,
126 ) -> Result<DataFrame> {
127 let table_paths = table_paths.to_urls()?;
129 let session_config = self.copied_config();
130 let listing_options =
131 ListingOptions::new(Arc::new(OrcFormat)).with_file_extension(options.file_extension);
132
133 let option_extension = listing_options.file_extension.clone();
134
135 if table_paths.is_empty() {
136 return exec_err!("No table paths were provided");
137 }
138
139 for path in &table_paths {
141 let file_path = path.as_str();
142 if !file_path.ends_with(option_extension.clone().as_str()) && !path.is_collection() {
143 return exec_err!(
144 "File path '{file_path}' does not match the expected extension '{option_extension}'"
145 );
146 }
147 }
148
149 let resolved_schema = options
150 .get_resolved_schema(&session_config, self.state(), table_paths[0].clone())
151 .await?;
152 let config = ListingTableConfig::new_with_multi_paths(table_paths)
153 .with_listing_options(listing_options)
154 .with_schema(resolved_schema);
155 let provider = ListingTable::try_new(config)?;
156 self.read_table(Arc::new(provider))
157 }
158
159 async fn register_orc(
160 &self,
161 name: &str,
162 table_path: &str,
163 options: OrcReadOptions<'_>,
164 ) -> Result<()> {
165 let listing_options =
166 options.to_listing_options(&self.copied_config(), self.copied_table_options());
167 self.register_listing_table(name, table_path, listing_options, None, None)
168 .await?;
169 Ok(())
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use datafusion::assert_batches_sorted_eq;
176
177 use super::*;
178
179 #[tokio::test]
180 async fn dataframe() -> Result<()> {
181 let ctx = SessionContext::new();
182 ctx.register_orc(
183 "table1",
184 "tests/basic/data/alltypes.snappy.orc",
185 OrcReadOptions::default(),
186 )
187 .await?;
188
189 let actual = ctx
190 .sql("select int16, utf8 from table1 limit 5")
191 .await?
192 .collect()
193 .await?;
194
195 assert_batches_sorted_eq!(
196 [
197 "+-------+--------+",
198 "| int16 | utf8 |",
199 "+-------+--------+",
200 "| | |",
201 "| -1 | |",
202 "| 0 | |",
203 "| 1 | a |",
204 "| 32767 | encode |",
205 "+-------+--------+",
206 ],
207 &actual
208 );
209
210 Ok(())
211 }
212}