|
17 | 17 |
|
18 | 18 | use std::sync::Arc; |
19 | 19 |
|
| 20 | +use datafusion::physical_expr::Partitioning; |
20 | 21 | use datafusion::physical_plan::{ExecutionPlan, ExecutionPlanProperties, displayable}; |
21 | 22 | use datafusion_proto::physical_plan::AsExecutionPlan; |
22 | 23 | use prost::Message; |
@@ -126,6 +127,82 @@ impl PyExecutionPlan { |
126 | 127 | pub fn partition_count(&self) -> usize { |
127 | 128 | self.plan.output_partitioning().partition_count() |
128 | 129 | } |
| 130 | + |
| 131 | + #[getter] |
| 132 | + pub fn output_partitioning(&self) -> PyPhysicalPartitioning { |
| 133 | + self.plan.output_partitioning().clone().into() |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +/// How a physical plan's output rows are spread across its partitions. |
| 138 | +/// |
| 139 | +/// Distinct from `datafusion.expr.Partitioning`, which is the *logical* |
| 140 | +/// partitioning `DataFrame.repartition` takes as a request. This one reports |
| 141 | +/// what a built plan actually does. |
| 142 | +#[pyclass( |
| 143 | + from_py_object, |
| 144 | + frozen, |
| 145 | + name = "PhysicalPartitioning", |
| 146 | + module = "datafusion", |
| 147 | + subclass |
| 148 | +)] |
| 149 | +#[derive(Debug, Clone)] |
| 150 | +pub struct PyPhysicalPartitioning { |
| 151 | + partitioning: Partitioning, |
| 152 | +} |
| 153 | + |
| 154 | +#[pymethods] |
| 155 | +impl PyPhysicalPartitioning { |
| 156 | + /// Which partitioning scheme this is. |
| 157 | + /// |
| 158 | + /// One of `RoundRobinBatch`, `Hash`, `Range`, or `UnknownPartitioning`. |
| 159 | + /// `UnknownPartitioning` is what a plan reports when it knows how many |
| 160 | + /// partitions it has but nothing about how rows are distributed between |
| 161 | + /// them, which is the common case for a file scan. |
| 162 | + #[getter] |
| 163 | + pub fn scheme(&self) -> &'static str { |
| 164 | + match self.partitioning { |
| 165 | + Partitioning::RoundRobinBatch(_) => "RoundRobinBatch", |
| 166 | + Partitioning::Hash(_, _) => "Hash", |
| 167 | + Partitioning::Range(_) => "Range", |
| 168 | + Partitioning::UnknownPartitioning(_) => "UnknownPartitioning", |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + #[getter] |
| 173 | + pub fn partition_count(&self) -> usize { |
| 174 | + self.partitioning.partition_count() |
| 175 | + } |
| 176 | + |
| 177 | + /// The expressions rows are hashed on, or `None` for other schemes. |
| 178 | + /// |
| 179 | + /// These are physical expressions, which have no Python representation, so |
| 180 | + /// they are returned in their displayed form. |
| 181 | + #[getter] |
| 182 | + pub fn hash_expressions(&self) -> Option<Vec<String>> { |
| 183 | + match &self.partitioning { |
| 184 | + Partitioning::Hash(exprs, _) => { |
| 185 | + Some(exprs.iter().map(|expr| format!("{expr}")).collect()) |
| 186 | + } |
| 187 | + _ => None, |
| 188 | + } |
| 189 | + } |
| 190 | + |
| 191 | + fn __repr__(&self) -> String { |
| 192 | + format!("{}", self.partitioning) |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +impl From<Partitioning> for PyPhysicalPartitioning { |
| 197 | + fn from(partitioning: Partitioning) -> Self { |
| 198 | + Self { partitioning } |
| 199 | + } |
| 200 | +} |
| 201 | + |
| 202 | +impl From<PyPhysicalPartitioning> for Partitioning { |
| 203 | + fn from(partitioning: PyPhysicalPartitioning) -> Self { |
| 204 | + partitioning.partitioning |
| 205 | + } |
129 | 206 | } |
130 | 207 |
|
131 | 208 | impl From<PyExecutionPlan> for Arc<dyn ExecutionPlan> { |
|
0 commit comments