Skip to content

Commit ce502ab

Browse files
authored
fixed PushDownFilter bug [15047] (#15142)
* fixed PushDownFilter bug [15047] by adding a new branch to match to prevent this specific situation * improved syntax as request by CICL process * moved check empty node logic into LogicalPlan::Extension(extensioon_plan) * removed unecessary clone * removed unecessary test
1 parent e7e7758 commit ce502ab

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed

datafusion/optimizer/src/push_down_filter.rs

+85
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,12 @@ impl OptimizerRule for PushDownFilter {
11401140
})
11411141
}
11421142
LogicalPlan::Extension(extension_plan) => {
1143+
// This check prevents the Filter from being removed when the extension node has no children,
1144+
// so we return the original Filter unchanged.
1145+
if extension_plan.node.inputs().is_empty() {
1146+
filter.input = Arc::new(LogicalPlan::Extension(extension_plan));
1147+
return Ok(Transformed::no(LogicalPlan::Filter(filter)));
1148+
}
11431149
let prevent_cols =
11441150
extension_plan.node.prevent_predicate_push_down_columns();
11451151

@@ -3786,4 +3792,83 @@ Projection: a, b
37863792
\n TableScan: test";
37873793
assert_optimized_plan_eq(plan, expected_after)
37883794
}
3795+
3796+
#[test]
3797+
fn test_push_down_filter_to_user_defined_node() -> Result<()> {
3798+
// Define a custom user-defined logical node
3799+
#[derive(Debug, Hash, Eq, PartialEq)]
3800+
struct TestUserNode {
3801+
schema: DFSchemaRef,
3802+
}
3803+
3804+
impl PartialOrd for TestUserNode {
3805+
fn partial_cmp(&self, _other: &Self) -> Option<Ordering> {
3806+
None
3807+
}
3808+
}
3809+
3810+
impl TestUserNode {
3811+
fn new() -> Self {
3812+
let schema = Arc::new(
3813+
DFSchema::new_with_metadata(
3814+
vec![(None, Field::new("a", DataType::Int64, false).into())],
3815+
Default::default(),
3816+
)
3817+
.unwrap(),
3818+
);
3819+
3820+
Self { schema }
3821+
}
3822+
}
3823+
3824+
impl UserDefinedLogicalNodeCore for TestUserNode {
3825+
fn name(&self) -> &str {
3826+
"test_node"
3827+
}
3828+
3829+
fn inputs(&self) -> Vec<&LogicalPlan> {
3830+
vec![]
3831+
}
3832+
3833+
fn schema(&self) -> &DFSchemaRef {
3834+
&self.schema
3835+
}
3836+
3837+
fn expressions(&self) -> Vec<Expr> {
3838+
vec![]
3839+
}
3840+
3841+
fn fmt_for_explain(&self, f: &mut Formatter) -> std::fmt::Result {
3842+
write!(f, "TestUserNode")
3843+
}
3844+
3845+
fn with_exprs_and_inputs(
3846+
&self,
3847+
exprs: Vec<Expr>,
3848+
inputs: Vec<LogicalPlan>,
3849+
) -> Result<Self> {
3850+
assert!(exprs.is_empty());
3851+
assert!(inputs.is_empty());
3852+
Ok(Self {
3853+
schema: Arc::clone(&self.schema),
3854+
})
3855+
}
3856+
}
3857+
3858+
// Create a node and build a plan with a filter
3859+
let node = LogicalPlan::Extension(Extension {
3860+
node: Arc::new(TestUserNode::new()),
3861+
});
3862+
3863+
let plan = LogicalPlanBuilder::from(node).filter(lit(false))?.build()?;
3864+
3865+
// Check the original plan format (not part of the test assertions)
3866+
let expected_before = "Filter: Boolean(false)\
3867+
\n TestUserNode";
3868+
assert_eq!(format!("{plan}"), expected_before);
3869+
3870+
// Check that the filter is pushed down to the user-defined node
3871+
let expected_after = "Filter: Boolean(false)\n TestUserNode";
3872+
assert_optimized_plan_eq(plan, expected_after)
3873+
}
37893874
}

0 commit comments

Comments
 (0)