-
Notifications
You must be signed in to change notification settings - Fork 65
coordinator: display consumer dynamic filters after execution #623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d417dc6
99a9ab6
429fdec
b8c282b
64f9f5d
1115fb4
00e3f5b
72a8e6c
730266f
f69728c
92fb135
cdd153e
886cf13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,235 @@ | ||
| use datafusion::arrow::datatypes::SchemaRef; | ||
| use datafusion::common::tree_node::{TreeNode, TreeNodeRecursion}; | ||
| use datafusion::common::{HashMap, HashSet, Result, internal_err}; | ||
| use datafusion::physical_expr::PhysicalExpr; | ||
| use datafusion::physical_expr::expressions::DynamicFilterPhysicalExpr; | ||
| use datafusion::physical_plan::ExecutionPlan; | ||
| use std::sync::Arc; | ||
|
|
||
| /// A dynamic-filter consumer discovered in an execution plan along with the schema its evaluated | ||
| /// against. | ||
| #[derive(Clone)] | ||
| pub(crate) struct DiscoveredDynamicFilter { | ||
| pub(crate) id: u64, | ||
| pub(crate) expression: Arc<dyn PhysicalExpr>, | ||
| pub(crate) input_schema: SchemaRef, | ||
| } | ||
|
|
||
| /// Finds dynamic-filter consumers in `plan`, deduplicated by expression ID. | ||
| pub(crate) fn discover_dynamic_filter_consumers( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the way of discovering dynamic filter consumers is:
Seems a bit... convoluted, but this seems to be the only way, so all good 👍
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is pretty much the way we settled on upstream: apache/datafusion#23814. I agree it's a bit convoluted.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's fine, not super ergonomic but not terrible |
||
| plan: &Arc<dyn ExecutionPlan>, | ||
| ) -> Result<Vec<DiscoveredDynamicFilter>> { | ||
| let mut consumers = HashMap::new(); | ||
|
|
||
| plan.apply(|node| { | ||
| let produced_ids: HashSet<_> = node | ||
| .dynamic_expressions_produced() | ||
| .into_iter() | ||
| .map(|produced| { | ||
| let Some(id) = produced.expression_id() else { | ||
| return internal_err!( | ||
| "{}::dynamic_expressions_produced returned an expression without an expression ID", | ||
| node.name() | ||
| ); | ||
| }; | ||
| Ok(id) | ||
| }) | ||
| .collect::<Result<_>>()?; | ||
| let input_schema = node | ||
| .children() | ||
| .first() | ||
| .map(|child| child.schema()) | ||
| .unwrap_or_else(|| node.schema()); | ||
|
|
||
| node.apply_expressions(&mut |root| { | ||
| root.apply(|expression| { | ||
| let Some(_) = expression.downcast_ref::<DynamicFilterPhysicalExpr>() else { | ||
| return Ok(TreeNodeRecursion::Continue); | ||
| }; | ||
|
|
||
| let Some(id) = expression.expression_id() else { | ||
| return internal_err!( | ||
| "DynamicFilterPhysicalExpr did not have an expression ID" | ||
| ); | ||
| }; | ||
| let is_producer_occurrence = produced_ids.contains(&id); | ||
| if !is_producer_occurrence { | ||
| consumers | ||
| .entry(id) | ||
| .or_insert_with(|| DiscoveredDynamicFilter { | ||
| id, | ||
| expression: Arc::clone(expression), | ||
| input_schema: Arc::clone(&input_schema), | ||
| }); | ||
| } | ||
|
|
||
| Ok(TreeNodeRecursion::Continue) | ||
| }) | ||
| })?; | ||
| Ok(TreeNodeRecursion::Continue) | ||
| })?; | ||
|
|
||
| let mut consumers: Vec<_> = consumers.into_values().collect(); | ||
| consumers.sort_unstable_by_key(|consumer| consumer.id); | ||
| Ok(consumers) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use datafusion::arrow::datatypes::{DataType, Field, Schema}; | ||
| use datafusion::common::Result; | ||
| use datafusion::execution::{SendableRecordBatchStream, TaskContext}; | ||
| use datafusion::logical_expr::Operator; | ||
| use datafusion::physical_expr::expressions::{BinaryExpr, Column, lit}; | ||
| use datafusion::physical_plan::empty::EmptyExec; | ||
| use datafusion::physical_plan::union::UnionExec; | ||
| use datafusion::physical_plan::{ | ||
| DisplayAs, DisplayFormatType, PlanProperties, apply_expression_roots, | ||
| }; | ||
| use std::fmt::Formatter; | ||
|
|
||
| #[tokio::test] | ||
| async fn discovers_nested_consumer_but_not_its_producer_occurrence() -> Result<()> { | ||
| let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)])); | ||
| let input = Arc::new(EmptyExec::new(Arc::clone(&schema))) as Arc<dyn ExecutionPlan>; | ||
| let column = Arc::new(Column::new("a", 0)) as Arc<dyn PhysicalExpr>; | ||
| let dynamic_filter = Arc::new(DynamicFilterPhysicalExpr::new( | ||
| vec![Arc::clone(&column)], | ||
| lit(true), | ||
| )) as Arc<dyn PhysicalExpr>; | ||
| let nested = Arc::new(BinaryExpr::new( | ||
| Arc::clone(&dynamic_filter), | ||
| Operator::And, | ||
| lit(true), | ||
| )) as Arc<dyn PhysicalExpr>; | ||
|
|
||
| let consumer = | ||
| Arc::new(ExpressionExec::new(input, nested, false)) as Arc<dyn ExecutionPlan>; | ||
| let plan = Arc::new(ExpressionExec::new( | ||
| consumer, | ||
| Arc::clone(&dynamic_filter), | ||
| true, | ||
| )) as Arc<dyn ExecutionPlan>; | ||
|
|
||
| let discovered = discover_dynamic_filter_consumers(&plan)?; | ||
| assert_eq!(discovered.len(), 1); | ||
| assert_eq!(discovered[0].id, dynamic_filter.expression_id().unwrap()); | ||
|
|
||
| dynamic_filter | ||
| .downcast_ref::<DynamicFilterPhysicalExpr>() | ||
| .unwrap() | ||
| .update(Arc::new(BinaryExpr::new(column, Operator::Gt, lit(10_i32))))?; | ||
| dynamic_filter | ||
| .downcast_ref::<DynamicFilterPhysicalExpr>() | ||
| .unwrap() | ||
| .mark_complete(); | ||
|
|
||
| let current = discovered[0] | ||
| .expression | ||
| .downcast_ref::<DynamicFilterPhysicalExpr>() | ||
| .unwrap() | ||
| .current()?; | ||
| assert_eq!(current.to_string(), "a@0 > 10"); | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[test] | ||
| fn deduplicates_consumers_with_the_same_expression_id() -> Result<()> { | ||
| let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)])); | ||
| let dynamic_filter = Arc::new(DynamicFilterPhysicalExpr::new( | ||
| vec![Arc::new(Column::new("a", 0))], | ||
| lit(true), | ||
| )) as Arc<dyn PhysicalExpr>; | ||
| let consumers = (0..2) | ||
| .map(|_| { | ||
| Arc::new(ExpressionExec::new( | ||
| Arc::new(EmptyExec::new(Arc::clone(&schema))), | ||
| Arc::clone(&dynamic_filter), | ||
| false, | ||
| )) as Arc<dyn ExecutionPlan> | ||
| }) | ||
| .collect(); | ||
| let plan = UnionExec::try_new(consumers)?; | ||
|
|
||
| let discovered = discover_dynamic_filter_consumers(&plan)?; | ||
|
|
||
| assert_eq!(discovered.len(), 1); | ||
| assert_eq!(discovered[0].id, dynamic_filter.expression_id().unwrap()); | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| struct ExpressionExec { | ||
| input: Arc<dyn ExecutionPlan>, | ||
| expression: Arc<dyn PhysicalExpr>, | ||
| produces_expression: bool, | ||
| } | ||
|
|
||
| impl ExpressionExec { | ||
| fn new( | ||
| input: Arc<dyn ExecutionPlan>, | ||
| expression: Arc<dyn PhysicalExpr>, | ||
| produces_expression: bool, | ||
| ) -> Self { | ||
| Self { | ||
| input, | ||
| expression, | ||
| produces_expression, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl DisplayAs for ExpressionExec { | ||
| fn fmt_as(&self, _: DisplayFormatType, f: &mut Formatter) -> std::fmt::Result { | ||
| write!(f, "ExpressionExec") | ||
| } | ||
| } | ||
|
|
||
| impl ExecutionPlan for ExpressionExec { | ||
| fn name(&self) -> &str { | ||
| "ExpressionExec" | ||
| } | ||
|
|
||
| fn properties(&self) -> &Arc<PlanProperties> { | ||
| self.input.properties() | ||
| } | ||
|
|
||
| fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> { | ||
| vec![&self.input] | ||
| } | ||
|
|
||
| fn dynamic_expressions_produced(&self) -> Vec<Arc<dyn PhysicalExpr>> { | ||
| self.produces_expression | ||
| .then(|| Arc::clone(&self.expression)) | ||
| .into_iter() | ||
| .collect() | ||
| } | ||
|
|
||
| fn apply_expressions( | ||
| &self, | ||
| f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>, | ||
| ) -> Result<TreeNodeRecursion> { | ||
| apply_expression_roots([&self.expression], f) | ||
| } | ||
|
|
||
| fn with_new_children( | ||
| self: Arc<Self>, | ||
| mut children: Vec<Arc<dyn ExecutionPlan>>, | ||
| ) -> Result<Arc<dyn ExecutionPlan>> { | ||
| Ok(Arc::new(Self::new( | ||
| children.remove(0), | ||
| Arc::clone(&self.expression), | ||
| self.produces_expression, | ||
| ))) | ||
| } | ||
|
|
||
| fn execute( | ||
| &self, | ||
| partition: usize, | ||
| context: Arc<TaskContext>, | ||
| ) -> Result<SendableRecordBatchStream> { | ||
| self.input.execute(partition, context) | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.