1use std::pin::Pin;
55use std::sync::Arc;
56use std::task::{Context, Poll};
57
58use arrow_array::RecordBatch;
59use arrow_schema::SchemaRef;
60use datafusion::error::DataFusionError;
61use datafusion::execution::SendableRecordBatchStream;
62use datafusion::logical_expr::{BinaryExpr, Expr, Operator};
63use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
64use futures::stream::{self, Stream, StreamExt, TryStreamExt};
65
66type BatchStream = Pin<Box<dyn Stream<Item = Result<RecordBatch, DataFusionError>> + Send>>;
71
72use crate::errors::FnError;
73use crate::traits::catalog::CatalogTable;
74use crate::traits::storage::Storage;
75
76pub const STORAGE_FILTER_UNENCODABLE: u32 = 0x711;
83
84pub struct StorageCatalogTable {
91 storage: Arc<dyn Storage>,
92 table: String,
93 schema: SchemaRef,
94}
95
96impl std::fmt::Debug for StorageCatalogTable {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 f.debug_struct("StorageCatalogTable")
99 .field("table", &self.table)
100 .field("schema", &self.schema)
101 .field("storage", &"<dyn Storage>")
102 .finish()
103 }
104}
105
106impl StorageCatalogTable {
107 #[must_use]
110 pub fn new(storage: Arc<dyn Storage>, table: String, schema: SchemaRef) -> Self {
111 Self {
112 storage,
113 table,
114 schema,
115 }
116 }
117
118 #[must_use]
120 pub fn storage(&self) -> &Arc<dyn Storage> {
121 &self.storage
122 }
123
124 #[must_use]
126 pub fn table(&self) -> &str {
127 &self.table
128 }
129}
130
131impl CatalogTable for StorageCatalogTable {
132 fn schema(&self) -> SchemaRef {
133 Arc::clone(&self.schema)
134 }
135
136 fn scan(
137 &self,
138 projection: Option<&[usize]>,
139 filters: &[Expr],
140 limit: Option<usize>,
141 ) -> Result<SendableRecordBatchStream, FnError> {
142 let storage = Arc::clone(&self.storage);
143 let table = self.table.clone();
144 let predicate = and_combine(filters);
145 let projection_owned: Option<Vec<usize>> = projection.map(<[usize]>::to_vec);
146
147 let output_schema: SchemaRef = match projection_owned.as_deref() {
150 Some(p) => project_schema(&self.schema, p),
151 None => Arc::clone(&self.schema),
152 };
153
154 let inner = stream::once(async move {
155 match storage.read_batch(&table, predicate.as_ref()).await {
156 Ok(s) => Ok(s),
157 Err(e) if e.code == STORAGE_FILTER_UNENCODABLE => {
160 storage.read_batch(&table, None).await.map_err(fn_err_to_df)
161 }
162 Err(e) => Err(fn_err_to_df(e)),
163 }
164 })
165 .try_flatten();
166
167 let projected = ProjectionAndLimitStream::new(inner.boxed(), projection_owned, limit);
168
169 Ok(Box::pin(RecordBatchStreamAdapter::new(
170 output_schema,
171 projected,
172 )))
173 }
174}
175
176fn and_combine(filters: &[Expr]) -> Option<Expr> {
180 let mut iter = filters.iter().cloned();
181 let first = iter.next()?;
182 Some(iter.fold(first, |acc, next| {
183 Expr::BinaryExpr(BinaryExpr::new(
184 Box::new(acc),
185 Operator::And,
186 Box::new(next),
187 ))
188 }))
189}
190
191fn project_schema(schema: &SchemaRef, projection: &[usize]) -> SchemaRef {
193 let fields: Vec<arrow_schema::Field> = projection
194 .iter()
195 .filter_map(|i| schema.fields().get(*i).map(|f| f.as_ref().clone()))
196 .collect();
197 Arc::new(arrow_schema::Schema::new(fields))
198}
199
200fn fn_err_to_df(e: FnError) -> DataFusionError {
202 DataFusionError::Execution(format!(
203 "plugin Storage::read_batch failed (code 0x{:x}): {}",
204 e.code, e.message
205 ))
206}
207
208struct ProjectionAndLimitStream {
213 inner: BatchStream,
214 projection: Option<Vec<usize>>,
215 remaining: Option<usize>,
216 done: bool,
217}
218
219impl ProjectionAndLimitStream {
220 fn new(inner: BatchStream, projection: Option<Vec<usize>>, limit: Option<usize>) -> Self {
221 Self {
222 inner,
223 projection,
224 remaining: limit,
225 done: false,
226 }
227 }
228
229 fn apply(&self, batch: RecordBatch) -> Result<RecordBatch, DataFusionError> {
230 match self.projection.as_deref() {
231 Some(p) => batch
232 .project(p)
233 .map_err(|e| DataFusionError::ArrowError(Box::new(e), None)),
234 None => Ok(batch),
235 }
236 }
237}
238
239impl Stream for ProjectionAndLimitStream {
240 type Item = Result<RecordBatch, DataFusionError>;
241
242 fn poll_next(
243 mut self: std::pin::Pin<&mut Self>,
244 cx: &mut Context<'_>,
245 ) -> Poll<Option<Self::Item>> {
246 if self.done {
247 return Poll::Ready(None);
248 }
249 match self.inner.poll_next_unpin(cx) {
250 Poll::Pending => Poll::Pending,
251 Poll::Ready(None) => {
252 self.done = true;
253 Poll::Ready(None)
254 }
255 Poll::Ready(Some(Err(e))) => {
256 self.done = true;
257 Poll::Ready(Some(Err(e)))
258 }
259 Poll::Ready(Some(Ok(batch))) => {
260 let projected = match self.apply(batch) {
261 Ok(b) => b,
262 Err(e) => {
263 self.done = true;
264 return Poll::Ready(Some(Err(e)));
265 }
266 };
267 let take = match self.remaining {
268 Some(n) if n <= projected.num_rows() => {
269 self.done = true;
270 n
271 }
272 Some(n) => {
273 self.remaining = Some(n - projected.num_rows());
274 projected.num_rows()
275 }
276 None => projected.num_rows(),
277 };
278 if take == projected.num_rows() {
279 Poll::Ready(Some(Ok(projected)))
280 } else {
281 Poll::Ready(Some(Ok(projected.slice(0, take))))
283 }
284 }
285 }
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use arrow_array::{Int64Array, StringArray};
293 use arrow_schema::{DataType, Field, Schema};
294 use async_trait::async_trait;
295 use datafusion::execution::SendableRecordBatchStream;
296 use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
297 use futures::stream::{self, StreamExt};
298 use std::sync::Mutex;
299
300 use crate::traits::storage::WriteHandle;
301
302 struct StaticStorage {
303 batches: Mutex<Vec<RecordBatch>>,
304 schema: SchemaRef,
305 last_predicate: Mutex<Option<Expr>>,
306 fail_on_filter: bool,
307 }
308
309 #[async_trait]
310 impl Storage for StaticStorage {
311 async fn read_batch(
312 &self,
313 _table: &str,
314 predicate: Option<&Expr>,
315 ) -> Result<SendableRecordBatchStream, FnError> {
316 if self.fail_on_filter && predicate.is_some() {
317 return Err(FnError::new(STORAGE_FILTER_UNENCODABLE, "unencodable"));
318 }
319 *self.last_predicate.lock().expect("predicate mutex") = predicate.cloned();
320 let batches = self.batches.lock().expect("batches mutex").clone();
321 let schema = Arc::clone(&self.schema);
322 let s = stream::iter(batches.into_iter().map(Ok));
323 Ok(Box::pin(RecordBatchStreamAdapter::new(schema, s)))
324 }
325
326 async fn write_batch(
327 &self,
328 _table: &str,
329 _batch: &RecordBatch,
330 ) -> Result<WriteHandle, FnError> {
331 Err(FnError::new(1, "read-only fixture"))
332 }
333
334 async fn list_tables(&self) -> Result<Vec<String>, FnError> {
335 Ok(vec!["t".to_owned()])
336 }
337
338 async fn delete(&self, _table: &str, _predicate: &Expr) -> Result<u64, FnError> {
339 Err(FnError::new(1, "read-only fixture"))
340 }
341 }
342
343 fn fixture_schema() -> SchemaRef {
344 Arc::new(Schema::new(vec![
345 Field::new("id", DataType::Int64, false),
346 Field::new("name", DataType::Utf8, true),
347 ]))
348 }
349
350 fn fixture_batch(schema: &SchemaRef, ids: &[i64], names: &[&str]) -> RecordBatch {
351 let id_arr = Arc::new(Int64Array::from(ids.to_vec()));
352 let name_arr = Arc::new(StringArray::from_iter(names.iter().map(|s| Some(*s))));
353 RecordBatch::try_new(Arc::clone(schema), vec![id_arr, name_arr]).expect("fixture batch")
354 }
355
356 #[tokio::test]
357 async fn full_scan_streams_all_rows() {
358 let schema = fixture_schema();
359 let storage = Arc::new(StaticStorage {
360 batches: Mutex::new(vec![fixture_batch(&schema, &[1, 2, 3], &["a", "b", "c"])]),
361 schema: Arc::clone(&schema),
362 last_predicate: Mutex::new(None),
363 fail_on_filter: false,
364 });
365 let storage: Arc<dyn Storage> = storage;
366 let table = StorageCatalogTable::new(storage, "people".to_owned(), schema);
367
368 let mut stream = table.scan(None, &[], None).expect("scan starts");
369 let mut total = 0usize;
370 while let Some(b) = stream.next().await {
371 total += b.expect("batch").num_rows();
372 }
373 assert_eq!(total, 3);
374 }
375
376 #[tokio::test]
377 async fn limit_is_applied_client_side() {
378 let schema = fixture_schema();
379 let storage = Arc::new(StaticStorage {
380 batches: Mutex::new(vec![fixture_batch(&schema, &[1, 2, 3], &["a", "b", "c"])]),
381 schema: Arc::clone(&schema),
382 last_predicate: Mutex::new(None),
383 fail_on_filter: false,
384 });
385 let storage: Arc<dyn Storage> = storage;
386 let table = StorageCatalogTable::new(storage, "people".to_owned(), schema);
387
388 let mut stream = table.scan(None, &[], Some(2)).expect("scan starts");
389 let mut total = 0usize;
390 while let Some(b) = stream.next().await {
391 total += b.expect("batch").num_rows();
392 }
393 assert_eq!(total, 2);
394 }
395
396 #[tokio::test]
397 async fn projection_drops_columns() {
398 let schema = fixture_schema();
399 let storage = Arc::new(StaticStorage {
400 batches: Mutex::new(vec![fixture_batch(&schema, &[1, 2], &["a", "b"])]),
401 schema: Arc::clone(&schema),
402 last_predicate: Mutex::new(None),
403 fail_on_filter: false,
404 });
405 let table = StorageCatalogTable::new(storage, "people".to_owned(), Arc::clone(&schema));
406
407 let mut stream = table.scan(Some(&[0]), &[], None).expect("scan starts");
408 let mut total_cols = 0usize;
409 while let Some(b) = stream.next().await {
410 let b = b.expect("batch");
411 total_cols = b.num_columns();
412 }
413 assert_eq!(total_cols, 1, "projection should drop name column");
414 }
415
416 #[tokio::test]
417 async fn unencodable_filter_falls_back_to_unfiltered() {
418 use datafusion::logical_expr::{col, lit};
419 let schema = fixture_schema();
420 let storage = Arc::new(StaticStorage {
421 batches: Mutex::new(vec![fixture_batch(&schema, &[1, 2, 3], &["a", "b", "c"])]),
422 schema: Arc::clone(&schema),
423 last_predicate: Mutex::new(None),
424 fail_on_filter: true,
425 });
426 let storage: Arc<dyn Storage> = storage;
427 let table = StorageCatalogTable::new(storage, "people".to_owned(), schema);
428
429 let filter = col("id").eq(lit(2_i64));
430 let mut stream = table.scan(None, &[filter], None).expect("scan starts");
431 let mut total = 0usize;
432 while let Some(b) = stream.next().await {
433 total += b.expect("batch").num_rows();
434 }
435 assert_eq!(total, 3);
439 }
440}