datafusion_python/
physical_plan.rs1use std::sync::Arc;
19
20use datafusion::physical_plan::{ExecutionPlan, ExecutionPlanProperties, displayable};
21use datafusion_proto::physical_plan::AsExecutionPlan;
22use prost::Message;
23use pyo3::exceptions::PyRuntimeError;
24use pyo3::prelude::*;
25use pyo3::types::PyBytes;
26
27use crate::codec::PythonPhysicalCodec;
28use crate::context::PySessionContext;
29use crate::errors::PyDataFusionResult;
30use crate::metrics::PyMetricsSet;
31
32#[pyclass(
33 from_py_object,
34 frozen,
35 name = "ExecutionPlan",
36 module = "datafusion",
37 subclass
38)]
39#[derive(Debug, Clone)]
40pub struct PyExecutionPlan {
41 pub plan: Arc<dyn ExecutionPlan>,
42}
43
44impl PyExecutionPlan {
45 pub fn new(plan: Arc<dyn ExecutionPlan>) -> Self {
47 Self { plan }
48 }
49}
50
51#[pymethods]
52impl PyExecutionPlan {
53 pub fn children(&self) -> Vec<PyExecutionPlan> {
55 self.plan
56 .children()
57 .iter()
58 .map(|&p| p.to_owned().into())
59 .collect()
60 }
61
62 pub fn display(&self) -> String {
63 let d = displayable(self.plan.as_ref());
64 format!("{}", d.one_line())
65 }
66
67 pub fn display_indent(&self) -> String {
68 let d = displayable(self.plan.as_ref());
69 format!("{}", d.indent(false))
70 }
71
72 #[pyo3(signature = (ctx=None))]
73 pub fn to_bytes<'py>(
74 &'py self,
75 py: Python<'py>,
76 ctx: Option<PySessionContext>,
77 ) -> PyDataFusionResult<Bound<'py, PyBytes>> {
78 let default_codec;
82 let codec: &dyn datafusion_proto::physical_plan::PhysicalExtensionCodec = match ctx {
83 Some(ref ctx) => ctx.physical_codec().as_ref(),
84 None => {
85 default_codec = PythonPhysicalCodec::default();
86 &default_codec
87 }
88 };
89 let proto = datafusion_proto::protobuf::PhysicalPlanNode::try_from_physical_plan(
90 self.plan.clone(),
91 codec,
92 )?;
93
94 let bytes = proto.encode_to_vec();
95 Ok(PyBytes::new(py, &bytes))
96 }
97
98 #[staticmethod]
99 pub fn from_bytes(
100 ctx: PySessionContext,
101 proto_msg: Bound<'_, PyBytes>,
102 ) -> PyDataFusionResult<Self> {
103 let bytes: &[u8] = proto_msg.extract().map_err(Into::<PyErr>::into)?;
104 let proto_plan =
105 datafusion_proto::protobuf::PhysicalPlanNode::decode(bytes).map_err(|e| {
106 PyRuntimeError::new_err(format!(
107 "Unable to decode physical node from serialized bytes: {e}"
108 ))
109 })?;
110
111 let codec = ctx.physical_codec();
112 let plan =
113 proto_plan.try_into_physical_plan(ctx.ctx.task_ctx().as_ref(), codec.as_ref())?;
114 Ok(Self::new(plan))
115 }
116
117 pub fn metrics(&self) -> Option<PyMetricsSet> {
118 self.plan.metrics().map(PyMetricsSet::new)
119 }
120
121 fn __repr__(&self) -> String {
122 self.display_indent()
123 }
124
125 #[getter]
126 pub fn partition_count(&self) -> usize {
127 self.plan.output_partitioning().partition_count()
128 }
129}
130
131impl From<PyExecutionPlan> for Arc<dyn ExecutionPlan> {
132 fn from(plan: PyExecutionPlan) -> Arc<dyn ExecutionPlan> {
133 plan.plan.clone()
134 }
135}
136
137impl From<Arc<dyn ExecutionPlan>> for PyExecutionPlan {
138 fn from(plan: Arc<dyn ExecutionPlan>) -> PyExecutionPlan {
139 PyExecutionPlan { plan: plan.clone() }
140 }
141}