Copilot commented on code in PR #25647:
URL: https://github.com/apache/datafusion/pull/25647#discussion_r4082001522


##########
datafusion/optimizer/src/optimize_projections/mod.rs:
##########
@@ -353,6 +353,7 @@ fn optimize_projections(
                     .zip(necessary_children_indices)
                     .map(|(child, necessary_indices)| {
                         RequiredIndices::new_from_indices(necessary_indices)
+                            .with_projection_beneficial()
                             .with_plan_exprs(&plan, child.schema())

Review Comment:
   `with_projection_beneficial()` is applied unconditionally for all extension 
children. This may cause extra `Projection` nodes to be inserted even when (a) 
the parent context did *not* consider projections beneficial, or (b) 
`necessary_indices` already covers all columns for that child, creating 
avoidable plan nodes and work. Consider conditioning this flag (e.g., only set 
it when the parent `RequiredIndices` indicates projections are beneficial, 
and/or when `necessary_indices` is a true subset of the child’s columns). This 
keeps the fix while reducing the risk of widespread plan-shape changes and 
unnecessary projections.



##########
datafusion/optimizer/src/optimize_projections/mod.rs:
##########
@@ -1724,6 +1725,119 @@ mod tests {
         )
     }
 
+    #[test]
+    fn test_user_defined_logical_plan_above_join() -> Result<()> {
+        let table_scan = test_table_scan()?;
+        let schema = Schema::new(vec![Field::new("c1", DataType::UInt32, 
false)]);
+        let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
+
+        // Join test(a, b, c) and test2(c1) on test.a = test2.c1
+        let join_plan = LogicalPlanBuilder::from(table_scan)
+            .join(table2_scan, JoinType::Left, (vec!["a"], vec!["c1"]), None)?
+            .build()?;
+
+        let custom_plan = LogicalPlan::Extension(Extension {
+            node: Arc::new(NoOpUserDefined::new(
+                Arc::clone(join_plan.schema()),
+                Arc::new(join_plan),
+            )),
+        });
+

Review Comment:
   These three tests repeat substantial setup (creating scans, building the 
same left join, wrapping it in an extension, then projecting). Consider 
extracting small helper(s) within the test module (e.g., 
`build_left_join_test_plan()` / `wrap_noop_extension(plan)`) to reduce 
duplication and make future additions easier and less error-prone.



##########
datafusion/optimizer/src/optimize_projections/mod.rs:
##########
@@ -1724,6 +1725,119 @@ mod tests {
         )
     }
 
+    #[test]
+    fn test_user_defined_logical_plan_above_join() -> Result<()> {

Review Comment:
   The added tests cover the positive case where a `Projection` should be 
inserted beneath an `Extension` (above a `Join`). To guard against regressions 
from unconditionally enabling `projection_beneficial`, add a negative test 
asserting no extra `Projection` is inserted when the parent requires *all* 
columns from an extension child (or when `necessary_indices` equals the full 
set). This will ensure the change doesn’t unintentionally increase projection 
churn in common cases.



##########
datafusion/optimizer/src/optimize_projections/mod.rs:
##########
@@ -1724,6 +1725,119 @@ mod tests {
         )
     }
 
+    #[test]
+    fn test_user_defined_logical_plan_above_join() -> Result<()> {
+        let table_scan = test_table_scan()?;
+        let schema = Schema::new(vec![Field::new("c1", DataType::UInt32, 
false)]);
+        let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
+
+        // Join test(a, b, c) and test2(c1) on test.a = test2.c1
+        let join_plan = LogicalPlanBuilder::from(table_scan)
+            .join(table2_scan, JoinType::Left, (vec!["a"], vec!["c1"]), None)?
+            .build()?;
+
+        let custom_plan = LogicalPlan::Extension(Extension {
+            node: Arc::new(NoOpUserDefined::new(
+                Arc::clone(join_plan.schema()),
+                Arc::new(join_plan),
+            )),
+        });
+
+        // Parent only requires test.a and test.b; join key test2.c1 is not 
needed downstream.
+        let plan = LogicalPlanBuilder::from(custom_plan)
+            .project(vec![col("test.a"), col("test.b")])?
+            .build()?;
+
+        assert_optimized_plan_equal!(
+            plan,
+            @r"
+        Projection: test.a, test.b
+          NoOpUserDefined
+            Projection: test.a, test.b
+              Left Join: test.a = test2.c1
+                TableScan: test projection=[a, b]
+                TableScan: test2 projection=[c1]
+        "
+        )
+    }
+
+    #[test]
+    fn test_user_defined_logical_plan_above_join_with_sort() -> Result<()> {

Review Comment:
   These three tests repeat substantial setup (creating scans, building the 
same left join, wrapping it in an extension, then projecting). Consider 
extracting small helper(s) within the test module (e.g., 
`build_left_join_test_plan()` / `wrap_noop_extension(plan)`) to reduce 
duplication and make future additions easier and less error-prone.



##########
datafusion/optimizer/src/optimize_projections/mod.rs:
##########
@@ -1724,6 +1725,119 @@ mod tests {
         )
     }
 
+    #[test]
+    fn test_user_defined_logical_plan_above_join() -> Result<()> {
+        let table_scan = test_table_scan()?;
+        let schema = Schema::new(vec![Field::new("c1", DataType::UInt32, 
false)]);
+        let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
+
+        // Join test(a, b, c) and test2(c1) on test.a = test2.c1
+        let join_plan = LogicalPlanBuilder::from(table_scan)
+            .join(table2_scan, JoinType::Left, (vec!["a"], vec!["c1"]), None)?
+            .build()?;
+
+        let custom_plan = LogicalPlan::Extension(Extension {
+            node: Arc::new(NoOpUserDefined::new(
+                Arc::clone(join_plan.schema()),
+                Arc::new(join_plan),
+            )),
+        });
+
+        // Parent only requires test.a and test.b; join key test2.c1 is not 
needed downstream.
+        let plan = LogicalPlanBuilder::from(custom_plan)
+            .project(vec![col("test.a"), col("test.b")])?
+            .build()?;
+
+        assert_optimized_plan_equal!(
+            plan,
+            @r"
+        Projection: test.a, test.b
+          NoOpUserDefined
+            Projection: test.a, test.b
+              Left Join: test.a = test2.c1
+                TableScan: test projection=[a, b]
+                TableScan: test2 projection=[c1]
+        "
+        )
+    }
+
+    #[test]
+    fn test_user_defined_logical_plan_above_join_with_sort() -> Result<()> {
+        let table_scan = test_table_scan()?;
+        let schema = Schema::new(vec![Field::new("c1", DataType::UInt32, 
false)]);
+        let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
+
+        // Join test(a, b, c) and test2(c1) on test.a = test2.c1
+        let join_plan = LogicalPlanBuilder::from(table_scan)
+            .join(table2_scan, JoinType::Left, (vec!["a"], vec!["c1"]), None)?
+            .build()?;
+
+        let custom_plan = LogicalPlan::Extension(Extension {
+            node: Arc::new(NoOpUserDefined::new(
+                Arc::clone(join_plan.schema()),
+                Arc::new(join_plan),
+            )),
+        });
+
+        // Sort -> Projection -> Extension -> Join
+        let plan = LogicalPlanBuilder::from(custom_plan)
+            .project(vec![col("test.a"), col("test.b")])?
+            .sort(vec![col("test.a").sort(true, false)])?
+            .build()?;
+
+        assert_optimized_plan_equal!(
+            plan,
+            @r"
+        Sort: test.a ASC NULLS LAST
+          Projection: test.a, test.b
+            NoOpUserDefined
+              Projection: test.a, test.b
+                Left Join: test.a = test2.c1
+                  TableScan: test projection=[a, b]
+                  TableScan: test2 projection=[c1]
+        "
+        )
+    }
+
+    #[test]
+    fn test_user_defined_logical_plan_multi_input_above_join() -> Result<()> {

Review Comment:
   These three tests repeat substantial setup (creating scans, building the 
same left join, wrapping it in an extension, then projecting). Consider 
extracting small helper(s) within the test module (e.g., 
`build_left_join_test_plan()` / `wrap_noop_extension(plan)`) to reduce 
duplication and make future additions easier and less error-prone.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to