Skip to content

Commit 2172d3f

Browse files
authored
Explain bindings (apache#264)
* Introduce to_variant trait function to LogicalNode and create Explain LogicalNode bindings * Cargo fmt * Add missing classes to list of exports so test_imports will pass
1 parent 5ae7c50 commit 2172d3f

17 files changed

Lines changed: 214 additions & 36 deletions

datafusion/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
Cast,
7777
TryCast,
7878
Between,
79+
Explain,
7980
)
8081

8182
__version__ = importlib_metadata.version(__name__)
@@ -127,6 +128,7 @@
127128
"Cast",
128129
"TryCast",
129130
"Between",
131+
"Explain",
130132
]
131133

132134

src/errors.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,7 @@ pub fn py_runtime_err(e: impl Debug) -> PyErr {
8282
pub fn py_datafusion_err(e: impl Debug) -> PyErr {
8383
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!("{e:?}"))
8484
}
85+
86+
pub fn py_unsupported_variant_err(e: impl Debug) -> PyErr {
87+
PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("{e:?}"))
88+
}

src/expr.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub mod column;
5050
pub mod cross_join;
5151
pub mod empty_relation;
5252
pub mod exists;
53+
pub mod explain;
5354
pub mod filter;
5455
pub mod grouping_set;
5556
pub mod in_list;
@@ -260,10 +261,7 @@ pub(crate) fn init_module(m: &PyModule) -> PyResult<()> {
260261
m.add_class::<cast::PyTryCast>()?;
261262
m.add_class::<between::PyBetween>()?;
262263
m.add_class::<indexed_field::PyGetIndexedField>()?;
263-
// operators
264-
m.add_class::<table_scan::PyTableScan>()?;
265-
m.add_class::<projection::PyProjection>()?;
266-
m.add_class::<filter::PyFilter>()?;
264+
m.add_class::<explain::PyExplain>()?;
267265
m.add_class::<limit::PyLimit>()?;
268266
m.add_class::<aggregate::PyAggregate>()?;
269267
m.add_class::<sort::PySort>()?;
@@ -274,5 +272,8 @@ pub(crate) fn init_module(m: &PyModule) -> PyResult<()> {
274272
m.add_class::<join::PyJoinConstraint>()?;
275273
m.add_class::<cross_join::PyCrossJoin>()?;
276274
m.add_class::<union::PyUnion>()?;
275+
m.add_class::<filter::PyFilter>()?;
276+
m.add_class::<projection::PyProjection>()?;
277+
m.add_class::<table_scan::PyTableScan>()?;
277278
Ok(())
278279
}

src/expr/aggregate.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use datafusion_expr::logical_plan::Aggregate;
2020
use pyo3::prelude::*;
2121
use std::fmt::{self, Display, Formatter};
2222

23+
use super::logical_node::LogicalNode;
2324
use crate::common::df_schema::PyDFSchema;
24-
use crate::expr::logical_node::LogicalNode;
2525
use crate::expr::PyExpr;
2626
use crate::sql::logical::PyLogicalPlan;
2727

@@ -103,4 +103,8 @@ impl LogicalNode for PyAggregate {
103103
fn inputs(&self) -> Vec<PyLogicalPlan> {
104104
vec![PyLogicalPlan::from((*self.aggregate.input).clone())]
105105
}
106+
107+
fn to_variant(&self, py: Python) -> PyResult<PyObject> {
108+
Ok(self.clone().into_py(py))
109+
}
106110
}

src/expr/analyze.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use datafusion_expr::logical_plan::Analyze;
1919
use pyo3::prelude::*;
2020
use std::fmt::{self, Display, Formatter};
2121

22+
use super::logical_node::LogicalNode;
2223
use crate::common::df_schema::PyDFSchema;
23-
use crate::expr::logical_node::LogicalNode;
2424
use crate::sql::logical::PyLogicalPlan;
2525

2626
#[pyclass(name = "Analyze", module = "datafusion.expr", subclass)]
@@ -77,4 +77,8 @@ impl LogicalNode for PyAnalyze {
7777
fn inputs(&self) -> Vec<PyLogicalPlan> {
7878
vec![PyLogicalPlan::from((*self.analyze.input).clone())]
7979
}
80+
81+
fn to_variant(&self, py: Python) -> PyResult<PyObject> {
82+
Ok(self.clone().into_py(py))
83+
}
8084
}

src/expr/cross_join.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use datafusion_expr::logical_plan::CrossJoin;
1919
use pyo3::prelude::*;
2020
use std::fmt::{self, Display, Formatter};
2121

22+
use super::logical_node::LogicalNode;
2223
use crate::common::df_schema::PyDFSchema;
23-
use crate::expr::logical_node::LogicalNode;
2424
use crate::sql::logical::PyLogicalPlan;
2525

2626
#[pyclass(name = "CrossJoin", module = "datafusion.expr", subclass)]
@@ -87,4 +87,8 @@ impl LogicalNode for PyCrossJoin {
8787
PyLogicalPlan::from((*self.cross_join.right).clone()),
8888
]
8989
}
90+
91+
fn to_variant(&self, py: Python) -> PyResult<PyObject> {
92+
Ok(self.clone().into_py(py))
93+
}
9094
}

src/expr/empty_relation.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use crate::common::df_schema::PyDFSchema;
18+
use crate::{common::df_schema::PyDFSchema, sql::logical::PyLogicalPlan};
1919
use datafusion_expr::EmptyRelation;
2020
use pyo3::prelude::*;
2121
use std::fmt::{self, Display, Formatter};
2222

23+
use super::logical_node::LogicalNode;
24+
2325
#[pyclass(name = "EmptyRelation", module = "datafusion.expr", subclass)]
2426
#[derive(Clone)]
2527
pub struct PyEmptyRelation {
@@ -70,3 +72,14 @@ impl PyEmptyRelation {
7072
Ok("EmptyRelation".to_string())
7173
}
7274
}
75+
76+
impl LogicalNode for PyEmptyRelation {
77+
fn inputs(&self) -> Vec<PyLogicalPlan> {
78+
// table scans are leaf nodes and do not have inputs
79+
vec![]
80+
}
81+
82+
fn to_variant(&self, py: Python) -> PyResult<PyObject> {
83+
Ok(self.clone().into_py(py))
84+
}
85+
}

src/expr/explain.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use std::fmt::{self, Display, Formatter};
19+
20+
use datafusion_expr::{logical_plan::Explain, LogicalPlan};
21+
use pyo3::prelude::*;
22+
23+
use crate::{common::df_schema::PyDFSchema, errors::py_type_err, sql::logical::PyLogicalPlan};
24+
25+
use super::logical_node::LogicalNode;
26+
27+
#[pyclass(name = "Explain", module = "datafusion.expr", subclass)]
28+
#[derive(Clone)]
29+
pub struct PyExplain {
30+
explain: Explain,
31+
}
32+
33+
impl From<PyExplain> for Explain {
34+
fn from(explain: PyExplain) -> Self {
35+
explain.explain
36+
}
37+
}
38+
39+
impl From<Explain> for PyExplain {
40+
fn from(explain: Explain) -> PyExplain {
41+
PyExplain { explain }
42+
}
43+
}
44+
45+
impl Display for PyExplain {
46+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
47+
write!(
48+
f,
49+
"Explain
50+
verbose: {:?}
51+
plan: {:?}
52+
stringified_plans: {:?}
53+
schema: {:?}
54+
logical_optimization_succeeded: {:?}",
55+
&self.explain.verbose,
56+
&self.explain.plan,
57+
&self.explain.stringified_plans,
58+
&self.explain.schema,
59+
&self.explain.logical_optimization_succeeded
60+
)
61+
}
62+
}
63+
64+
#[pymethods]
65+
impl PyExplain {
66+
fn explain_string(&self) -> PyResult<Vec<String>> {
67+
let mut string_plans: Vec<String> = Vec::new();
68+
for stringified_plan in &self.explain.stringified_plans {
69+
string_plans.push((*stringified_plan.plan).clone());
70+
}
71+
Ok(string_plans)
72+
}
73+
74+
fn verbose(&self) -> bool {
75+
self.explain.verbose
76+
}
77+
78+
fn plan(&self) -> PyResult<PyLogicalPlan> {
79+
Ok(PyLogicalPlan::from((*self.explain.plan).clone()))
80+
}
81+
82+
fn schema(&self) -> PyDFSchema {
83+
(*self.explain.schema).clone().into()
84+
}
85+
86+
fn logical_optimization_succceeded(&self) -> bool {
87+
self.explain.logical_optimization_succeeded
88+
}
89+
}
90+
91+
impl TryFrom<LogicalPlan> for PyExplain {
92+
type Error = PyErr;
93+
94+
fn try_from(logical_plan: LogicalPlan) -> Result<Self, Self::Error> {
95+
match logical_plan {
96+
LogicalPlan::Explain(explain) => Ok(PyExplain { explain }),
97+
_ => Err(py_type_err("unexpected plan")),
98+
}
99+
}
100+
}
101+
102+
impl LogicalNode for PyExplain {
103+
fn inputs(&self) -> Vec<PyLogicalPlan> {
104+
vec![]
105+
}
106+
107+
fn to_variant(&self, py: Python) -> PyResult<PyObject> {
108+
Ok(self.clone().into_py(py))
109+
}
110+
}

src/expr/filter.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ impl Display for PyFilter {
4747
write!(
4848
f,
4949
"Filter
50-
\nPredicate: {:?}
51-
\nInput: {:?}",
50+
Predicate: {:?}
51+
Input: {:?}",
5252
&self.filter.predicate, &self.filter.input
5353
)
5454
}
@@ -80,4 +80,8 @@ impl LogicalNode for PyFilter {
8080
fn inputs(&self) -> Vec<PyLogicalPlan> {
8181
vec![PyLogicalPlan::from((*self.filter.input).clone())]
8282
}
83+
84+
fn to_variant(&self, py: Python) -> PyResult<PyObject> {
85+
Ok(self.clone().into_py(py))
86+
}
8387
}

src/expr/join.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,14 @@ impl Display for PyJoin {
9595
write!(
9696
f,
9797
"Join
98-
\nLeft: {:?}
99-
\nRight: {:?}
100-
\nOn: {:?}
101-
\nFilter: {:?}
102-
\nJoinType: {:?}
103-
\nJoinConstraint: {:?}
104-
\nSchema: {:?}
105-
\nNullEqualsNull: {:?}",
98+
Left: {:?}
99+
Right: {:?}
100+
On: {:?}
101+
Filter: {:?}
102+
JoinType: {:?}
103+
JoinConstraint: {:?}
104+
Schema: {:?}
105+
NullEqualsNull: {:?}",
106106
&self.join.left,
107107
&self.join.right,
108108
&self.join.on,
@@ -178,4 +178,8 @@ impl LogicalNode for PyJoin {
178178
PyLogicalPlan::from((*self.join.right).clone()),
179179
]
180180
}
181+
182+
fn to_variant(&self, py: Python) -> PyResult<PyObject> {
183+
Ok(self.clone().into_py(py))
184+
}
181185
}

0 commit comments

Comments
 (0)