use std::sync::Arc;
use arrow::datatypes::SchemaRef;
use arrow::pyarrow::ToPyArrow;
use async_trait::async_trait;
use datafusion::catalog::{Session, TableProviderFactory};
use datafusion::common::Column;
use datafusion::datasource::{TableProvider, TableType};
use datafusion::logical_expr::{
CreateExternalTable, Expr, LogicalPlanBuilder, TableProviderFilterPushDown,
};
use datafusion::physical_plan::ExecutionPlan;
use datafusion::prelude::DataFrame;
use datafusion_ffi::proto::logical_extension_codec::FFI_LogicalExtensionCodec;
use datafusion_python_util::{create_logical_extension_capsule, table_provider_from_pycapsule};
use pyo3::IntoPyObjectExt;
use pyo3::prelude::*;
use crate::context::PySessionContext;
use crate::dataframe::PyDataFrame;
use crate::dataset::Dataset;
use crate::errors;
use crate::expr::create_external_table::PyCreateExternalTable;
#[pyclass(
from_py_object,
frozen,
name = "RawTable",
module = "datafusion.catalog",
subclass
)]
#[derive(Clone)]
pub struct PyTable {
pub table: Arc<dyn TableProvider>,
}
impl PyTable {
pub fn table(&self) -> Arc<dyn TableProvider> {
self.table.clone()
}
}
#[pymethods]
impl PyTable {
#[new]
pub fn new(obj: Bound<'_, PyAny>, session: Option<Bound<PyAny>>) -> PyResult<Self> {
let py = obj.py();
if let Ok(py_table) = obj.extract::<PyTable>() {
Ok(py_table)
} else if let Ok(py_table) = obj
.getattr("_inner")
.and_then(|inner| inner.extract::<PyTable>().map_err(Into::<PyErr>::into))
{
Ok(py_table)
} else if let Ok(py_df) = obj.extract::<PyDataFrame>() {
let provider = py_df.inner_df().as_ref().clone().into_view();
Ok(PyTable::from(provider))
} else if let Ok(py_df) = obj
.getattr("df")
.and_then(|inner| inner.extract::<PyDataFrame>().map_err(Into::<PyErr>::into))
{
let provider = py_df.inner_df().as_ref().clone().into_view();
Ok(PyTable::from(provider))
} else if let Some(provider) = {
let session = match session {
Some(session) => session,
None => PySessionContext::global_ctx()?.into_bound_py_any(obj.py())?,
};
table_provider_from_pycapsule(obj.clone(), session)?
} {
Ok(PyTable::from(provider))
} else {
let provider = Arc::new(Dataset::new(&obj, py)?) as Arc<dyn TableProvider>;
Ok(PyTable::from(provider))
}
}
#[getter]
fn schema<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
self.table.schema().to_pyarrow(py)
}
#[getter]
fn kind(&self) -> &str {
match self.table.table_type() {
TableType::Base => "physical",
TableType::View => "view",
TableType::Temporary => "temporary",
}
}
fn __repr__(&self) -> PyResult<String> {
let kind = self.kind();
Ok(format!("Table(kind={kind})"))
}
}
impl From<Arc<dyn TableProvider>> for PyTable {
fn from(table: Arc<dyn TableProvider>) -> Self {
Self { table }
}
}
#[derive(Clone, Debug)]
pub(crate) struct TempViewTable {
df: Arc<DataFrame>,
}
impl TempViewTable {
pub(crate) fn new(df: Arc<DataFrame>) -> Self {
Self { df }
}
}
#[async_trait]
impl TableProvider for TempViewTable {
fn schema(&self) -> SchemaRef {
Arc::new(self.df.schema().as_arrow().clone())
}
fn table_type(&self) -> TableType {
TableType::Temporary
}
async fn scan(
&self,
state: &dyn Session,
projection: Option<&Vec<usize>>,
filters: &[Expr],
limit: Option<usize>,
) -> datafusion::common::Result<Arc<dyn ExecutionPlan>> {
let filter = filters.iter().cloned().reduce(|acc, new| acc.and(new));
let plan = self.df.logical_plan().clone();
let mut plan = LogicalPlanBuilder::from(plan);
if let Some(filter) = filter {
plan = plan.filter(filter)?;
}
let mut plan = if let Some(projection) = projection {
let current_projection = (0..plan.schema().fields().len()).collect::<Vec<usize>>();
if projection == ¤t_projection {
plan
} else {
let fields: Vec<Expr> = projection
.iter()
.map(|i| {
Expr::Column(Column::from(
self.df.logical_plan().schema().qualified_field(*i),
))
})
.collect();
plan.project(fields)?
}
} else {
plan
};
if let Some(limit) = limit {
plan = plan.limit(0, Some(limit))?;
}
state.create_physical_plan(&plan.build()?).await
}
fn supports_filters_pushdown(
&self,
filters: &[&Expr],
) -> datafusion::common::Result<Vec<TableProviderFilterPushDown>> {
Ok(vec![TableProviderFilterPushDown::Exact; filters.len()])
}
}
#[derive(Debug)]
pub(crate) struct RustWrappedPyTableProviderFactory {
pub(crate) table_provider_factory: Py<PyAny>,
pub(crate) codec: Arc<FFI_LogicalExtensionCodec>,
}
impl RustWrappedPyTableProviderFactory {
pub fn new(table_provider_factory: Py<PyAny>, codec: Arc<FFI_LogicalExtensionCodec>) -> Self {
Self {
table_provider_factory,
codec,
}
}
fn create_inner(
&self,
cmd: CreateExternalTable,
codec: Bound<PyAny>,
) -> PyResult<Arc<dyn TableProvider>> {
Python::attach(|py| {
let provider = self.table_provider_factory.bind(py);
let cmd = PyCreateExternalTable::from(cmd);
provider
.call_method1("create", (cmd,))
.and_then(|t| PyTable::new(t, Some(codec)))
.map(|t| t.table())
})
}
}
#[async_trait]
impl TableProviderFactory for RustWrappedPyTableProviderFactory {
async fn create(
&self,
_: &dyn Session,
cmd: &CreateExternalTable,
) -> datafusion::common::Result<Arc<dyn TableProvider>> {
Python::attach(|py| {
let codec = create_logical_extension_capsule(py, self.codec.as_ref())
.map_err(errors::to_datafusion_err)?;
self.create_inner(cmd.clone(), codec.into_any())
.map_err(errors::to_datafusion_err)
})
}
}