| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| use std::sync::Arc; | |
| use arrow_array::{ArrayRef, RecordBatch}; | |
| use arrow_schema::{DataType, Field, Schema}; | |
| use datafusion_catalog::MemTable; | |
| use datafusion_common::error::{DataFusionError, Result as DataFusionResult}; | |
| use datafusion_ffi::table_provider::FFI_TableProvider; | |
| use datafusion_python_util::ffi_logical_codec_from_pycapsule; | |
| use pyo3::exceptions::PyRuntimeError; | |
| use pyo3::types::PyCapsule; | |
| use pyo3::{Bound, PyAny, PyResult, Python, pyclass, pymethods}; | |
| /// In order to provide a test that demonstrates different sized record batches, | |
| /// the first batch will have num_rows, the second batch num_rows+1, and so on. | |
| #[pyclass( | |
| from_py_object, | |
| name = "MyTableProvider", | |
| module = "datafusion_ffi_example", | |
| subclass | |
| )] | |
| #[derive(Clone)] | |
| pub(crate) struct MyTableProvider { | |
| num_cols: usize, | |
| num_rows: usize, | |
| num_batches: usize, | |
| } | |
| fn create_record_batch( | |
| schema: &Arc<Schema>, | |
| num_cols: usize, | |
| start_value: i32, | |
| num_values: usize, | |
| ) -> DataFusionResult<RecordBatch> { | |
| let end_value = start_value + num_values as i32; | |
| let row_values: Vec<i32> = (start_value..end_value).collect(); | |
| let columns: Vec<_> = (0..num_cols) | |
| .map(|_| Arc::new(arrow::array::Int32Array::from(row_values.clone())) as ArrayRef) | |
| .collect(); | |
| RecordBatch::try_new(Arc::clone(schema), columns).map_err(DataFusionError::from) | |
| } | |
| impl MyTableProvider { | |
| pub fn create_table(&self) -> DataFusionResult<MemTable> { | |
| let fields: Vec<_> = (0..self.num_cols) | |
| .map(|idx| (b'A' + idx as u8) as char) | |
| .map(|col_name| Field::new(col_name, DataType::Int32, true)) | |
| .collect(); | |
| let schema = Arc::new(Schema::new(fields)); | |
| let batches: DataFusionResult<Vec<_>> = (0..self.num_batches) | |
| .map(|batch_idx| { | |
| let start_value = batch_idx * self.num_rows; | |
| create_record_batch( | |
| &schema, | |
| self.num_cols, | |
| start_value as i32, | |
| self.num_rows + batch_idx, | |
| ) | |
| }) | |
| .collect(); | |
| MemTable::try_new(schema, vec![batches?]) | |
| } | |
| } | |
| #[pymethods] | |
| impl MyTableProvider { | |
| #[new] | |
| pub fn new(num_cols: usize, num_rows: usize, num_batches: usize) -> Self { | |
| Self { | |
| num_cols, | |
| num_rows, | |
| num_batches, | |
| } | |
| } | |
| pub fn __datafusion_table_provider__<'py>( | |
| &self, | |
| py: Python<'py>, | |
| session: Bound<PyAny>, | |
| ) -> PyResult<Bound<'py, PyCapsule>> { | |
| let provider = self | |
| .create_table() | |
| .map_err(|e: DataFusionError| PyRuntimeError::new_err(e.to_string()))?; | |
| let codec = ffi_logical_codec_from_pycapsule(session, None)?; | |
| let provider = | |
| FFI_TableProvider::new_with_ffi_codec(Arc::new(provider), false, None, codec); | |
| PyCapsule::new_with_value(py, provider, cr"datafusion_table_provider") | |
| } | |
| } |