From d08f4d0b9cea0cb6bdb7443b75a49f145de9a18d Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Wed, 15 Feb 2023 12:11:26 -0500 Subject: [PATCH 1/5] Add bindings for Projection --- datafusion/__init__.py | 2 + datafusion/tests/test_imports.py | 3 +- src/expr.rs | 3 + src/expr/logical_node.rs | 25 ++++++++ src/expr/projection.rs | 97 ++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/expr/logical_node.rs create mode 100644 src/expr/projection.rs diff --git a/datafusion/__init__.py b/datafusion/__init__.py index 0b4e89643..171c32fe3 100644 --- a/datafusion/__init__.py +++ b/datafusion/__init__.py @@ -37,6 +37,7 @@ from .expr import ( Expr, + Projection, TableScan, ) @@ -54,6 +55,7 @@ "column", "literal", "TableScan", + "Projection", ] diff --git a/datafusion/tests/test_imports.py b/datafusion/tests/test_imports.py index 1e8c796bb..964c1a574 100644 --- a/datafusion/tests/test_imports.py +++ b/datafusion/tests/test_imports.py @@ -28,6 +28,7 @@ from datafusion.expr import ( Expr, + Projection, TableScan, ) @@ -49,7 +50,7 @@ def test_class_module_is_datafusion(): ]: assert klass.__module__ == "datafusion" - for klass in [Expr, TableScan]: + for klass in [Expr, Projection, TableScan]: assert klass.__module__ == "datafusion.expr" diff --git a/src/expr.rs b/src/expr.rs index dceedc1fc..f3695febf 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -24,6 +24,8 @@ use datafusion_expr::{col, lit, Cast, Expr, GetIndexedField}; use datafusion::scalar::ScalarValue; +pub mod logical_node; +pub mod projection; pub mod table_scan; /// A PyExpr that can be used on a DataFrame @@ -140,5 +142,6 @@ impl PyExpr { pub(crate) fn init_module(m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/src/expr/logical_node.rs b/src/expr/logical_node.rs new file mode 100644 index 000000000..6c2ff9960 --- /dev/null +++ b/src/expr/logical_node.rs @@ -0,0 +1,25 @@ +// 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 crate::sql::logical::PyLogicalPlan; + +/// Representation of a `LogicalNode` in the in overall `LogicalPlan` +/// any "node" shares these common traits in common. +pub trait LogicalNode { + /// The input plan to the current logical node instance. + fn input(&self) -> PyLogicalPlan; +} diff --git a/src/expr/projection.rs b/src/expr/projection.rs new file mode 100644 index 000000000..4dd84d5d9 --- /dev/null +++ b/src/expr/projection.rs @@ -0,0 +1,97 @@ +// 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 datafusion_expr::logical_plan::Projection; +use pyo3::prelude::*; +use std::fmt::{self, Display, Formatter}; + +use crate::expr::logical_node::LogicalNode; +use crate::expr::PyExpr; +use crate::sql::logical::PyLogicalPlan; + +#[pyclass(name = "Projection", module = "datafusion.expr", subclass)] +#[derive(Clone)] +pub struct PyProjection { + projection: Projection, +} + +impl From for Projection { + fn from(py_proj: PyProjection) -> Projection { + Projection::try_new_with_schema( + py_proj.projection.expr, + py_proj.projection.input.clone(), + py_proj.projection.schema, + ) + .unwrap() + } +} + +impl From for PyProjection { + fn from(projection: Projection) -> PyProjection { + PyProjection { projection } + } +} + +impl Display for PyProjection { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "Projection + \nExpr(s): {:?} + \nInput: {:?} + \nProjected Schema: {:?}", + &self.projection.expr, &self.projection.input, &self.projection.schema, + ) + } +} + +#[pymethods] +impl PyProjection { + /// Retrieves the expressions for this `Projection` + #[pyo3(name = "projections")] + fn py_projections(&self) -> PyResult> { + Ok(self + .projection + .expr + .iter() + .map(|e| PyExpr::from(e.clone())) + .collect()) + } + + // Retrieves the input `LogicalPlan` to this `Projection` node + #[pyo3(name = "input")] + fn py_input(&self) -> PyResult { + Ok(LogicalNode::input(self)) + } + + // TODO: Need to uncomment once bindings for `DFSchemaRef` are done. + // // Resulting Schema for this `Projection` node instance + // #[pyo3(name = "schema")] + // fn py_schema(&self) -> PyResult { + // Ok(self.projection.schema) + // } + + fn __repr__(&self) -> PyResult { + Ok(format!("Projection({})", self)) + } +} + +impl LogicalNode for PyProjection { + fn input(&self) -> PyLogicalPlan { + todo!() + } +} From 4cbc925c83bd65cc64db53b8982173d5183f20b8 Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Wed, 15 Feb 2023 16:11:01 -0500 Subject: [PATCH 2/5] Address review comments --- src/expr/logical_node.rs | 2 +- src/expr/projection.rs | 37 +++++++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/expr/logical_node.rs b/src/expr/logical_node.rs index 6c2ff9960..1bb3fa75f 100644 --- a/src/expr/logical_node.rs +++ b/src/expr/logical_node.rs @@ -21,5 +21,5 @@ use crate::sql::logical::PyLogicalPlan; /// any "node" shares these common traits in common. pub trait LogicalNode { /// The input plan to the current logical node instance. - fn input(&self) -> PyLogicalPlan; + fn input(&self) -> Vec; } diff --git a/src/expr/projection.rs b/src/expr/projection.rs index 4dd84d5d9..f6b947162 100644 --- a/src/expr/projection.rs +++ b/src/expr/projection.rs @@ -15,10 +15,12 @@ // specific language governing permissions and limitations // under the License. +use datafusion_common::DataFusionError; use datafusion_expr::logical_plan::Projection; use pyo3::prelude::*; use std::fmt::{self, Display, Formatter}; +use crate::errors::py_runtime_err; use crate::expr::logical_node::LogicalNode; use crate::expr::PyExpr; use crate::sql::logical::PyLogicalPlan; @@ -29,20 +31,21 @@ pub struct PyProjection { projection: Projection, } -impl From for Projection { - fn from(py_proj: PyProjection) -> Projection { +impl From for PyProjection { + fn from(projection: Projection) -> PyProjection { + PyProjection { projection } + } +} + +impl TryFrom for Projection { + type Error = DataFusionError; + + fn try_from(py_proj: PyProjection) -> Result { Projection::try_new_with_schema( py_proj.projection.expr, py_proj.projection.input.clone(), py_proj.projection.schema, ) - .unwrap() - } -} - -impl From for PyProjection { - fn from(projection: Projection) -> PyProjection { - PyProjection { projection } } } @@ -61,6 +64,7 @@ impl Display for PyProjection { #[pymethods] impl PyProjection { + /// Retrieves the expressions for this `Projection` #[pyo3(name = "projections")] fn py_projections(&self) -> PyResult> { @@ -75,7 +79,16 @@ impl PyProjection { // Retrieves the input `LogicalPlan` to this `Projection` node #[pyo3(name = "input")] fn py_input(&self) -> PyResult { - Ok(LogicalNode::input(self)) + // DataFusion make a loose guarantee that each Projection should have an input, however + // we check for that hear since we are performing explicit index retrieval + let inputs = LogicalNode::input(self); + if inputs.len() > 0 { + return Ok(inputs[0].clone()); + } + + Err(py_runtime_err(format!( + "Expected `input` field for Projection node: {}", self + ))) } // TODO: Need to uncomment once bindings for `DFSchemaRef` are done. @@ -91,7 +104,7 @@ impl PyProjection { } impl LogicalNode for PyProjection { - fn input(&self) -> PyLogicalPlan { - todo!() + fn input(&self) -> Vec { + vec![PyLogicalPlan::from((*self.projection.input).clone())] } } From 18c09d0339d3060597513e9c5a13d109cb2725e7 Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Wed, 15 Feb 2023 16:28:22 -0500 Subject: [PATCH 3/5] cargo fmt --- src/expr/projection.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/expr/projection.rs b/src/expr/projection.rs index f6b947162..a75dceb37 100644 --- a/src/expr/projection.rs +++ b/src/expr/projection.rs @@ -64,7 +64,6 @@ impl Display for PyProjection { #[pymethods] impl PyProjection { - /// Retrieves the expressions for this `Projection` #[pyo3(name = "projections")] fn py_projections(&self) -> PyResult> { @@ -87,7 +86,8 @@ impl PyProjection { } Err(py_runtime_err(format!( - "Expected `input` field for Projection node: {}", self + "Expected `input` field for Projection node: {}", + self ))) } From 752fe1c124cfe8c087ac0f13b0015a2e5eaa8d6b Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Wed, 15 Feb 2023 19:39:21 -0500 Subject: [PATCH 4/5] datafusion_common::DFField bindings --- datafusion/__init__.py | 2 + datafusion/tests/test_imports.py | 3 +- src/common.rs | 5 ++ src/common/data_type.rs | 8 +-- src/common/df_field.rs | 95 +++++++++++++++++++++++++++----- 5 files changed, 93 insertions(+), 20 deletions(-) diff --git a/datafusion/__init__.py b/datafusion/__init__.py index 4667835e2..b6cd5178a 100644 --- a/datafusion/__init__.py +++ b/datafusion/__init__.py @@ -36,6 +36,7 @@ ) from .common import ( + DFField, DFSchema, ) @@ -61,6 +62,7 @@ "TableScan", "Projection", "DFSchema", + "DFField", ] diff --git a/datafusion/tests/test_imports.py b/datafusion/tests/test_imports.py index 7bdbd8371..e5d958537 100644 --- a/datafusion/tests/test_imports.py +++ b/datafusion/tests/test_imports.py @@ -27,6 +27,7 @@ ) from datafusion.common import ( + DFField, DFSchema, ) @@ -57,7 +58,7 @@ def test_class_module_is_datafusion(): for klass in [Expr, Projection, TableScan]: assert klass.__module__ == "datafusion.expr" - for klass in [DFSchema]: + for klass in [DFField, DFSchema]: assert klass.__module__ == "datafusion.common" diff --git a/src/common.rs b/src/common.rs index ba4438efd..8a8e2adf5 100644 --- a/src/common.rs +++ b/src/common.rs @@ -24,5 +24,10 @@ pub mod df_schema; /// Initializes the `common` module to match the pattern of `datafusion-common` https://docs.rs/datafusion-common/18.0.0/datafusion_common/index.html pub(crate) fn init_module(m: &PyModule) -> PyResult<()> { m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/src/common/data_type.rs b/src/common/data_type.rs index 8ada1c756..e07805c52 100644 --- a/src/common/data_type.rs +++ b/src/common/data_type.rs @@ -31,7 +31,7 @@ use crate::errors::py_datafusion_err; /// to map those types and provide a simple place for developers /// to map types from one system to another. #[derive(Debug, Clone)] -#[pyclass(name = "DataTypeMap", module = "datafusion", subclass)] +#[pyclass(name = "DataTypeMap", module = "datafusion.common", subclass)] pub struct DataTypeMap { #[allow(dead_code)] arrow_type: PyDataType, @@ -419,7 +419,7 @@ impl DataTypeMap { /// Since `DataType` exists in another package we cannot make that happen here so we wrap /// `DataType` as `PyDataType` This exists solely to satisfy those constraints. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[pyclass(name = "DataType", module = "datafusion")] +#[pyclass(name = "DataType", module = "datafusion.common")] pub struct PyDataType { data_type: DataType, } @@ -438,7 +438,7 @@ impl From for PyDataType { /// Represents the possible Python types that can be mapped to the SQL types #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[pyclass(name = "PythonType", module = "datafusion")] +#[pyclass(name = "PythonType", module = "datafusion.common")] pub enum PythonType { Array, Bool, @@ -458,7 +458,7 @@ pub enum PythonType { #[allow(non_camel_case_types)] #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[pyclass(name = "SqlType", module = "datafusion")] +#[pyclass(name = "SqlType", module = "datafusion.common")] pub enum SqlType { ANY, ARRAY, diff --git a/src/common/df_field.rs b/src/common/df_field.rs index 098df9bda..fa65e0495 100644 --- a/src/common/df_field.rs +++ b/src/common/df_field.rs @@ -15,27 +15,92 @@ // specific language governing permissions and limitations // under the License. -use datafusion::arrow::datatypes::Field; +use datafusion::arrow::datatypes::DataType; +use datafusion_common::DFField; use pyo3::prelude::*; -use crate::common::data_type::DataTypeMap; +use super::data_type::PyDataType; /// PyDFField wraps an arrow-datafusion `DFField` struct type /// and also supplies convenience methods for interacting /// with the `DFField` instance in the context of Python -#[pyclass(name = "DFField", module = "datafusion", subclass)] +#[pyclass(name = "DFField", module = "datafusion.common", subclass)] #[derive(Debug, Clone)] pub struct PyDFField { - /// Optional qualifier (usually a table or relation name) - #[allow(dead_code)] - qualifier: Option, - #[allow(dead_code)] - name: String, - #[allow(dead_code)] - data_type: DataTypeMap, - /// Arrow field definition - #[allow(dead_code)] - field: Field, - #[allow(dead_code)] - index: usize, + field: DFField, +} + +impl From for DFField { + fn from(py_field: PyDFField) -> DFField { + py_field.field + } +} + +impl From for PyDFField { + fn from(field: DFField) -> PyDFField { + PyDFField { field } + } +} + +#[pymethods] +impl PyDFField { + #[new] + #[pyo3(signature = (qualifier=None, name="", data_type=DataType::Int64.into(), nullable=false))] + fn new(qualifier: Option<&str>, name: &str, data_type: PyDataType, nullable: bool) -> Self { + PyDFField { + field: DFField::new(qualifier, name, data_type.into(), nullable), + } + } + + // TODO: Need bindings for Array `Field` first + // #[staticmethod] + // #[pyo3(name = "from")] + // fn py_from(field: Field) -> Self {} + + // TODO: Need bindings for Array `Field` first + // #[staticmethod] + // #[pyo3(name = "from_qualified")] + // fn py_from_qualified(field: Field) -> Self {} + + #[pyo3(name = "name")] + fn py_name(&self) -> PyResult { + Ok(self.field.name().clone()) + } + + #[pyo3(name = "data_type")] + fn py_data_type(&self) -> PyResult { + Ok(self.field.data_type().clone().into()) + } + + #[pyo3(name = "is_nullable")] + fn py_is_nullable(&self) -> PyResult { + Ok(self.field.is_nullable()) + } + + #[pyo3(name = "qualified_name")] + fn py_qualified_name(&self) -> PyResult { + Ok(self.field.qualified_name()) + } + + // TODO: Need bindings for `Column` first + // #[pyo3(name = "qualified_column")] + // fn py_qualified_column(&self) -> PyResult {} + + // TODO: Need bindings for `Column` first + // #[pyo3(name = "unqualified_column")] + // fn py_unqualified_column(&self) -> PyResult {} + + #[pyo3(name = "qualifier")] + fn py_qualifier(&self) -> PyResult> { + Ok(self.field.qualifier()) + } + + // TODO: Need bindings for Arrow `Field` first + // #[pyo3(name = "field")] + // fn py_field(&self) -> PyResult {} + + #[pyo3(name = "strip_qualifier")] + fn py_strip_qualifier(&self) -> PyResult { + Ok(self.field.clone().strip_qualifier().into()) + } } From 9dacbbef5483717b93c5500e35768ca3b573cb93 Mon Sep 17 00:00:00 2001 From: Jeremy Dyer Date: Wed, 15 Feb 2023 19:41:29 -0500 Subject: [PATCH 5/5] Revert "datafusion_common::DFField bindings" This reverts commit 752fe1c124cfe8c087ac0f13b0015a2e5eaa8d6b. --- datafusion/__init__.py | 2 - datafusion/tests/test_imports.py | 3 +- src/common.rs | 5 -- src/common/data_type.rs | 8 +-- src/common/df_field.rs | 95 +++++--------------------------- 5 files changed, 20 insertions(+), 93 deletions(-) diff --git a/datafusion/__init__.py b/datafusion/__init__.py index b6cd5178a..4667835e2 100644 --- a/datafusion/__init__.py +++ b/datafusion/__init__.py @@ -36,7 +36,6 @@ ) from .common import ( - DFField, DFSchema, ) @@ -62,7 +61,6 @@ "TableScan", "Projection", "DFSchema", - "DFField", ] diff --git a/datafusion/tests/test_imports.py b/datafusion/tests/test_imports.py index e5d958537..7bdbd8371 100644 --- a/datafusion/tests/test_imports.py +++ b/datafusion/tests/test_imports.py @@ -27,7 +27,6 @@ ) from datafusion.common import ( - DFField, DFSchema, ) @@ -58,7 +57,7 @@ def test_class_module_is_datafusion(): for klass in [Expr, Projection, TableScan]: assert klass.__module__ == "datafusion.expr" - for klass in [DFField, DFSchema]: + for klass in [DFSchema]: assert klass.__module__ == "datafusion.common" diff --git a/src/common.rs b/src/common.rs index 8a8e2adf5..ba4438efd 100644 --- a/src/common.rs +++ b/src/common.rs @@ -24,10 +24,5 @@ pub mod df_schema; /// Initializes the `common` module to match the pattern of `datafusion-common` https://docs.rs/datafusion-common/18.0.0/datafusion_common/index.html pub(crate) fn init_module(m: &PyModule) -> PyResult<()> { m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; Ok(()) } diff --git a/src/common/data_type.rs b/src/common/data_type.rs index e07805c52..8ada1c756 100644 --- a/src/common/data_type.rs +++ b/src/common/data_type.rs @@ -31,7 +31,7 @@ use crate::errors::py_datafusion_err; /// to map those types and provide a simple place for developers /// to map types from one system to another. #[derive(Debug, Clone)] -#[pyclass(name = "DataTypeMap", module = "datafusion.common", subclass)] +#[pyclass(name = "DataTypeMap", module = "datafusion", subclass)] pub struct DataTypeMap { #[allow(dead_code)] arrow_type: PyDataType, @@ -419,7 +419,7 @@ impl DataTypeMap { /// Since `DataType` exists in another package we cannot make that happen here so we wrap /// `DataType` as `PyDataType` This exists solely to satisfy those constraints. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[pyclass(name = "DataType", module = "datafusion.common")] +#[pyclass(name = "DataType", module = "datafusion")] pub struct PyDataType { data_type: DataType, } @@ -438,7 +438,7 @@ impl From for PyDataType { /// Represents the possible Python types that can be mapped to the SQL types #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[pyclass(name = "PythonType", module = "datafusion.common")] +#[pyclass(name = "PythonType", module = "datafusion")] pub enum PythonType { Array, Bool, @@ -458,7 +458,7 @@ pub enum PythonType { #[allow(non_camel_case_types)] #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[pyclass(name = "SqlType", module = "datafusion.common")] +#[pyclass(name = "SqlType", module = "datafusion")] pub enum SqlType { ANY, ARRAY, diff --git a/src/common/df_field.rs b/src/common/df_field.rs index fa65e0495..098df9bda 100644 --- a/src/common/df_field.rs +++ b/src/common/df_field.rs @@ -15,92 +15,27 @@ // specific language governing permissions and limitations // under the License. -use datafusion::arrow::datatypes::DataType; -use datafusion_common::DFField; +use datafusion::arrow::datatypes::Field; use pyo3::prelude::*; -use super::data_type::PyDataType; +use crate::common::data_type::DataTypeMap; /// PyDFField wraps an arrow-datafusion `DFField` struct type /// and also supplies convenience methods for interacting /// with the `DFField` instance in the context of Python -#[pyclass(name = "DFField", module = "datafusion.common", subclass)] +#[pyclass(name = "DFField", module = "datafusion", subclass)] #[derive(Debug, Clone)] pub struct PyDFField { - field: DFField, -} - -impl From for DFField { - fn from(py_field: PyDFField) -> DFField { - py_field.field - } -} - -impl From for PyDFField { - fn from(field: DFField) -> PyDFField { - PyDFField { field } - } -} - -#[pymethods] -impl PyDFField { - #[new] - #[pyo3(signature = (qualifier=None, name="", data_type=DataType::Int64.into(), nullable=false))] - fn new(qualifier: Option<&str>, name: &str, data_type: PyDataType, nullable: bool) -> Self { - PyDFField { - field: DFField::new(qualifier, name, data_type.into(), nullable), - } - } - - // TODO: Need bindings for Array `Field` first - // #[staticmethod] - // #[pyo3(name = "from")] - // fn py_from(field: Field) -> Self {} - - // TODO: Need bindings for Array `Field` first - // #[staticmethod] - // #[pyo3(name = "from_qualified")] - // fn py_from_qualified(field: Field) -> Self {} - - #[pyo3(name = "name")] - fn py_name(&self) -> PyResult { - Ok(self.field.name().clone()) - } - - #[pyo3(name = "data_type")] - fn py_data_type(&self) -> PyResult { - Ok(self.field.data_type().clone().into()) - } - - #[pyo3(name = "is_nullable")] - fn py_is_nullable(&self) -> PyResult { - Ok(self.field.is_nullable()) - } - - #[pyo3(name = "qualified_name")] - fn py_qualified_name(&self) -> PyResult { - Ok(self.field.qualified_name()) - } - - // TODO: Need bindings for `Column` first - // #[pyo3(name = "qualified_column")] - // fn py_qualified_column(&self) -> PyResult {} - - // TODO: Need bindings for `Column` first - // #[pyo3(name = "unqualified_column")] - // fn py_unqualified_column(&self) -> PyResult {} - - #[pyo3(name = "qualifier")] - fn py_qualifier(&self) -> PyResult> { - Ok(self.field.qualifier()) - } - - // TODO: Need bindings for Arrow `Field` first - // #[pyo3(name = "field")] - // fn py_field(&self) -> PyResult {} - - #[pyo3(name = "strip_qualifier")] - fn py_strip_qualifier(&self) -> PyResult { - Ok(self.field.clone().strip_qualifier().into()) - } + /// Optional qualifier (usually a table or relation name) + #[allow(dead_code)] + qualifier: Option, + #[allow(dead_code)] + name: String, + #[allow(dead_code)] + data_type: DataTypeMap, + /// Arrow field definition + #[allow(dead_code)] + field: Field, + #[allow(dead_code)] + index: usize, }