1use std::sync::Arc;
21
22use datafusion_common::JoinType;
23use datafusion_common::tree_node::Transformed;
24use datafusion_common::{Column, DFSchemaRef, Result, ScalarValue, plan_err};
25use datafusion_expr::logical_plan::LogicalPlan;
26use datafusion_expr::{EmptyRelation, Expr, GroupingSet, Projection, Union, cast, lit};
27
28use crate::optimizer::ApplyOrder;
29use crate::{OptimizerConfig, OptimizerRule};
30
31#[derive(Default, Debug)]
33pub struct PropagateEmptyRelation;
34
35impl PropagateEmptyRelation {
36 #[expect(missing_docs)]
37 pub fn new() -> Self {
38 Self {}
39 }
40}
41
42impl OptimizerRule for PropagateEmptyRelation {
43 fn name(&self) -> &str {
44 "propagate_empty_relation"
45 }
46
47 fn apply_order(&self) -> Option<ApplyOrder> {
48 Some(ApplyOrder::BottomUp)
49 }
50
51 fn supports_rewrite(&self) -> bool {
52 true
53 }
54
55 fn rewrite(
56 &self,
57 plan: LogicalPlan,
58 _config: &dyn OptimizerConfig,
59 ) -> Result<Transformed<LogicalPlan>> {
60 match plan {
61 LogicalPlan::EmptyRelation(_) => Ok(Transformed::no(plan)),
62 LogicalPlan::Projection(_)
63 | LogicalPlan::Filter(_)
64 | LogicalPlan::Window(_)
65 | LogicalPlan::Sort(_)
66 | LogicalPlan::SubqueryAlias(_)
67 | LogicalPlan::Repartition(_)
68 | LogicalPlan::Limit(_) => {
69 let empty = empty_child(&plan)?;
70 if let Some(empty_plan) = empty {
71 return Ok(Transformed::yes(empty_plan));
72 }
73 Ok(Transformed::no(plan))
74 }
75 LogicalPlan::Join(ref join) => {
76 let (left_empty, right_empty) = binary_plan_children_is_empty(&plan)?;
77 let left_field_count = join.left.schema().fields().len();
78
79 match join.join_type {
80 JoinType::Full if left_empty && right_empty => Ok(Transformed::yes(
82 LogicalPlan::EmptyRelation(EmptyRelation {
83 produce_one_row: false,
84 schema: Arc::clone(&join.schema),
85 }),
86 )),
87 JoinType::Full if right_empty => {
90 Ok(Transformed::yes(build_null_padded_projection(
91 Arc::clone(&join.left),
92 &join.schema,
93 left_field_count,
94 true,
95 )?))
96 }
97 JoinType::Full if left_empty => {
98 Ok(Transformed::yes(build_null_padded_projection(
99 Arc::clone(&join.right),
100 &join.schema,
101 left_field_count,
102 false,
103 )?))
104 }
105 JoinType::Inner if left_empty || right_empty => Ok(Transformed::yes(
106 LogicalPlan::EmptyRelation(EmptyRelation {
107 produce_one_row: false,
108 schema: Arc::clone(&join.schema),
109 }),
110 )),
111 JoinType::Left if left_empty => Ok(Transformed::yes(
112 LogicalPlan::EmptyRelation(EmptyRelation {
113 produce_one_row: false,
114 schema: Arc::clone(&join.schema),
115 }),
116 )),
117 JoinType::Left if right_empty => {
120 Ok(Transformed::yes(build_null_padded_projection(
121 Arc::clone(&join.left),
122 &join.schema,
123 left_field_count,
124 true,
125 )?))
126 }
127 JoinType::Right if right_empty => Ok(Transformed::yes(
128 LogicalPlan::EmptyRelation(EmptyRelation {
129 produce_one_row: false,
130 schema: Arc::clone(&join.schema),
131 }),
132 )),
133 JoinType::Right if left_empty => {
136 Ok(Transformed::yes(build_null_padded_projection(
137 Arc::clone(&join.right),
138 &join.schema,
139 left_field_count,
140 false,
141 )?))
142 }
143 JoinType::LeftSemi if left_empty || right_empty => Ok(
144 Transformed::yes(LogicalPlan::EmptyRelation(EmptyRelation {
145 produce_one_row: false,
146 schema: Arc::clone(&join.schema),
147 })),
148 ),
149 JoinType::RightSemi if left_empty || right_empty => Ok(
150 Transformed::yes(LogicalPlan::EmptyRelation(EmptyRelation {
151 produce_one_row: false,
152 schema: Arc::clone(&join.schema),
153 })),
154 ),
155 JoinType::LeftAnti if left_empty => Ok(Transformed::yes(
156 LogicalPlan::EmptyRelation(EmptyRelation {
157 produce_one_row: false,
158 schema: Arc::clone(&join.schema),
159 }),
160 )),
161 JoinType::LeftAnti if right_empty => {
162 Ok(Transformed::yes((*join.left).clone()))
163 }
164 JoinType::RightAnti if left_empty => {
165 Ok(Transformed::yes((*join.right).clone()))
166 }
167 JoinType::RightAnti if right_empty => Ok(Transformed::yes(
168 LogicalPlan::EmptyRelation(EmptyRelation {
169 produce_one_row: false,
170 schema: Arc::clone(&join.schema),
171 }),
172 )),
173 _ => Ok(Transformed::no(plan)),
174 }
175 }
176 LogicalPlan::Aggregate(ref agg) => {
177 if !agg.group_expr.is_empty()
183 && !has_empty_grouping_set(&agg.group_expr)
184 && let Some(empty_plan) = empty_child(&plan)?
185 {
186 return Ok(Transformed::yes(empty_plan));
187 }
188 Ok(Transformed::no(LogicalPlan::Aggregate(agg.clone())))
189 }
190 LogicalPlan::Union(ref union) => {
191 let new_inputs = union
192 .inputs
193 .iter()
194 .filter(|input| match &***input {
195 LogicalPlan::EmptyRelation(empty) => empty.produce_one_row,
196 _ => true,
197 })
198 .cloned()
199 .collect::<Vec<_>>();
200
201 if new_inputs.len() == union.inputs.len() {
202 Ok(Transformed::no(plan))
203 } else if new_inputs.is_empty() {
204 Ok(Transformed::yes(LogicalPlan::EmptyRelation(
205 EmptyRelation {
206 produce_one_row: false,
207 schema: Arc::clone(plan.schema()),
208 },
209 )))
210 } else if new_inputs.len() == 1 {
211 let mut new_inputs = new_inputs;
212 let input_plan = new_inputs.pop().unwrap(); let child = Arc::unwrap_or_clone(input_plan);
214 if child.schema().eq(plan.schema()) {
215 Ok(Transformed::yes(child))
216 } else {
217 Ok(Transformed::yes(LogicalPlan::Projection(
218 Projection::new_from_schema(
219 Arc::new(child),
220 Arc::clone(plan.schema()),
221 ),
222 )))
223 }
224 } else {
225 Ok(Transformed::yes(LogicalPlan::Union(Union {
226 inputs: new_inputs,
227 schema: Arc::clone(&union.schema),
228 })))
229 }
230 }
231
232 _ => Ok(Transformed::no(plan)),
233 }
234 }
235}
236
237fn binary_plan_children_is_empty(plan: &LogicalPlan) -> Result<(bool, bool)> {
238 match plan.inputs()[..] {
239 [left, right] => {
240 let left_empty = match left {
241 LogicalPlan::EmptyRelation(empty) => !empty.produce_one_row,
242 _ => false,
243 };
244 let right_empty = match right {
245 LogicalPlan::EmptyRelation(empty) => !empty.produce_one_row,
246 _ => false,
247 };
248 Ok((left_empty, right_empty))
249 }
250 _ => plan_err!("plan just can have two child"),
251 }
252}
253
254fn empty_child(plan: &LogicalPlan) -> Result<Option<LogicalPlan>> {
255 match plan.inputs()[..] {
256 [child] => match child {
257 LogicalPlan::EmptyRelation(empty) => {
258 if !empty.produce_one_row {
259 Ok(Some(LogicalPlan::EmptyRelation(EmptyRelation {
260 produce_one_row: false,
261 schema: Arc::clone(plan.schema()),
262 })))
263 } else {
264 Ok(None)
265 }
266 }
267 _ => Ok(None),
268 },
269 _ => plan_err!("plan just can have one child"),
270 }
271}
272
273fn build_null_padded_projection(
293 surviving_plan: Arc<LogicalPlan>,
294 join_schema: &DFSchemaRef,
295 left_field_count: usize,
296 empty_side_is_right: bool,
297) -> Result<LogicalPlan> {
298 let exprs = join_schema
299 .iter()
300 .enumerate()
301 .map(|(i, (qualifier, field))| {
302 let on_empty_side = if empty_side_is_right {
303 i >= left_field_count
304 } else {
305 i < left_field_count
306 };
307
308 if on_empty_side {
309 cast(lit(ScalarValue::Null), field.data_type().clone())
310 .alias_qualified(qualifier.cloned(), field.name())
311 } else {
312 Expr::Column(Column::new(qualifier.cloned(), field.name()))
313 }
314 })
315 .collect::<Vec<_>>();
316
317 Ok(LogicalPlan::Projection(Projection::try_new_with_schema(
318 exprs,
319 surviving_plan,
320 Arc::clone(join_schema),
321 )?))
322}
323
324fn has_empty_grouping_set(group_expr: &[Expr]) -> bool {
337 match group_expr.first() {
338 Some(Expr::GroupingSet(GroupingSet::GroupingSets(groups))) => {
339 groups.iter().any(|g| g.is_empty())
340 }
341 Some(Expr::GroupingSet(GroupingSet::Rollup(_)))
343 | Some(Expr::GroupingSet(GroupingSet::Cube(_))) => true,
344 _ => false,
345 }
346}
347
348#[cfg(test)]
349mod tests {
350
351 use arrow::datatypes::{DataType, Field, Schema};
352
353 use datafusion_common::{Column, DFSchema};
354 use datafusion_expr::logical_plan::table_scan;
355 use datafusion_expr::{
356 Operator, binary_expr, col, lit, logical_plan::builder::LogicalPlanBuilder,
357 };
358
359 use crate::OptimizerContext;
360 use crate::assert_optimized_plan_eq_snapshot;
361 use crate::eliminate_filter::EliminateFilter;
362 use crate::optimize_unions::OptimizeUnions;
363 use crate::test::{
364 assert_optimized_plan_with_rules, test_table_scan, test_table_scan_fields,
365 test_table_scan_with_name,
366 };
367
368 use super::*;
369
370 macro_rules! assert_optimized_plan_equal {
371 (
372 $plan:expr,
373 @ $expected:literal $(,)?
374 ) => {{
375 let optimizer_ctx = OptimizerContext::new().with_max_passes(1);
376 let rules: Vec<Arc<dyn crate::OptimizerRule + Send + Sync>> = vec![Arc::new(PropagateEmptyRelation::new())];
377 assert_optimized_plan_eq_snapshot!(
378 optimizer_ctx,
379 rules,
380 $plan,
381 @ $expected,
382 )
383 }};
384 }
385
386 fn assert_together_optimized_plan(
387 plan: LogicalPlan,
388 expected: &str,
389 eq: bool,
390 ) -> Result<()> {
391 assert_optimized_plan_with_rules(
392 vec![
393 Arc::new(EliminateFilter::new()),
394 Arc::new(OptimizeUnions::new()),
395 Arc::new(PropagateEmptyRelation::new()),
396 ],
397 plan,
398 expected,
399 eq,
400 )
401 }
402
403 #[test]
404 fn propagate_empty() -> Result<()> {
405 let plan = LogicalPlanBuilder::empty(false)
406 .filter(lit(true))?
407 .limit(10, None)?
408 .project(vec![binary_expr(lit(1), Operator::Plus, lit(1))])?
409 .build()?;
410
411 assert_optimized_plan_equal!(plan, @"EmptyRelation: rows=0")
412 }
413
414 #[test]
415 fn cooperate_with_eliminate_filter() -> Result<()> {
416 let table_scan = test_table_scan()?;
417 let left = LogicalPlanBuilder::from(table_scan).build()?;
418 let right_table_scan = test_table_scan_with_name("test2")?;
419 let right = LogicalPlanBuilder::from(right_table_scan)
420 .project(vec![col("a")])?
421 .filter(lit(false))?
422 .build()?;
423
424 let plan = LogicalPlanBuilder::from(left)
425 .join_using(
426 right,
427 JoinType::Inner,
428 vec![Column::from_name("a".to_string())],
429 )?
430 .filter(col("a").lt_eq(lit(1i64)))?
431 .build()?;
432
433 let expected = "EmptyRelation: rows=0";
434 assert_together_optimized_plan(plan, expected, true)
435 }
436
437 #[test]
438 fn propagate_union_empty() -> Result<()> {
439 let left = LogicalPlanBuilder::from(test_table_scan()?).build()?;
440 let right = LogicalPlanBuilder::from(test_table_scan_with_name("test2")?)
441 .filter(lit(false))?
442 .build()?;
443
444 let plan = LogicalPlanBuilder::from(left).union(right)?.build()?;
445
446 let expected = "Projection: a, b, c\n TableScan: test";
447 assert_together_optimized_plan(plan, expected, true)
448 }
449
450 #[test]
451 fn propagate_union_multi_empty() -> Result<()> {
452 let one =
453 LogicalPlanBuilder::from(test_table_scan_with_name("test1")?).build()?;
454 let two = LogicalPlanBuilder::from(test_table_scan_with_name("test2")?)
455 .filter(lit(false))?
456 .build()?;
457 let three = LogicalPlanBuilder::from(test_table_scan_with_name("test3")?)
458 .filter(lit(false))?
459 .build()?;
460 let four =
461 LogicalPlanBuilder::from(test_table_scan_with_name("test4")?).build()?;
462
463 let plan = LogicalPlanBuilder::from(one)
464 .union(two)?
465 .union(three)?
466 .union(four)?
467 .build()?;
468
469 let expected = "Union\
470 \n TableScan: test1\
471 \n TableScan: test4";
472 assert_together_optimized_plan(plan, expected, true)
473 }
474
475 #[test]
476 fn propagate_union_all_empty() -> Result<()> {
477 let one = LogicalPlanBuilder::from(test_table_scan_with_name("test1")?)
478 .filter(lit(false))?
479 .build()?;
480 let two = LogicalPlanBuilder::from(test_table_scan_with_name("test2")?)
481 .filter(lit(false))?
482 .build()?;
483 let three = LogicalPlanBuilder::from(test_table_scan_with_name("test3")?)
484 .filter(lit(false))?
485 .build()?;
486 let four = LogicalPlanBuilder::from(test_table_scan_with_name("test4")?)
487 .filter(lit(false))?
488 .build()?;
489
490 let plan = LogicalPlanBuilder::from(one)
491 .union(two)?
492 .union(three)?
493 .union(four)?
494 .build()?;
495
496 let expected = "EmptyRelation: rows=0";
497 assert_together_optimized_plan(plan, expected, true)
498 }
499
500 #[test]
501 fn propagate_union_children_different_schema() -> Result<()> {
502 let one_schema = Schema::new(vec![Field::new("t1a", DataType::UInt32, false)]);
503 let t1_scan = table_scan(Some("test1"), &one_schema, None)?.build()?;
504 let one = LogicalPlanBuilder::from(t1_scan)
505 .filter(lit(false))?
506 .build()?;
507
508 let two_schema = Schema::new(vec![Field::new("t2a", DataType::UInt32, false)]);
509 let t2_scan = table_scan(Some("test2"), &two_schema, None)?.build()?;
510 let two = LogicalPlanBuilder::from(t2_scan).build()?;
511
512 let three_schema = Schema::new(vec![Field::new("t3a", DataType::UInt32, false)]);
513 let t3_scan = table_scan(Some("test3"), &three_schema, None)?.build()?;
514 let three = LogicalPlanBuilder::from(t3_scan).build()?;
515
516 let plan = LogicalPlanBuilder::from(one)
517 .union(two)?
518 .union(three)?
519 .build()?;
520
521 let expected = "Union\
522 \n TableScan: test2\
523 \n TableScan: test3";
524 assert_together_optimized_plan(plan, expected, true)
525 }
526
527 #[test]
528 fn propagate_union_alias() -> Result<()> {
529 let left = LogicalPlanBuilder::from(test_table_scan()?).build()?;
530 let right = LogicalPlanBuilder::from(test_table_scan_with_name("test2")?)
531 .filter(lit(false))?
532 .build()?;
533
534 let plan = LogicalPlanBuilder::from(left).union(right)?.build()?;
535
536 let expected = "Projection: a, b, c\n TableScan: test";
537 assert_together_optimized_plan(plan, expected, true)
538 }
539
540 #[test]
541 fn cross_join_empty() -> Result<()> {
542 let table_scan = test_table_scan()?;
543 let left = LogicalPlanBuilder::from(table_scan).build()?;
544 let right = LogicalPlanBuilder::empty(false).build()?;
545
546 let plan = LogicalPlanBuilder::from(left)
547 .cross_join(right)?
548 .filter(col("a").lt_eq(lit(1i64)))?
549 .build()?;
550
551 let expected = "EmptyRelation: rows=0";
552 assert_together_optimized_plan(plan, expected, true)
553 }
554
555 fn assert_empty_left_empty_right_lp(
556 left_empty: bool,
557 right_empty: bool,
558 join_type: JoinType,
559 eq: bool,
560 ) -> Result<()> {
561 let left_lp = if left_empty {
562 let left_table_scan = test_table_scan()?;
563
564 LogicalPlanBuilder::from(left_table_scan)
565 .filter(lit(false))?
566 .build()
567 } else {
568 let scan = test_table_scan_with_name("left").unwrap();
569 LogicalPlanBuilder::from(scan).build()
570 }?;
571
572 let right_lp = if right_empty {
573 let right_table_scan = test_table_scan_with_name("right")?;
574
575 LogicalPlanBuilder::from(right_table_scan)
576 .filter(lit(false))?
577 .build()
578 } else {
579 let scan = test_table_scan_with_name("right").unwrap();
580 LogicalPlanBuilder::from(scan).build()
581 }?;
582
583 let plan = LogicalPlanBuilder::from(left_lp)
584 .join_using(
585 right_lp,
586 join_type,
587 vec![Column::from_name("a".to_string())],
588 )?
589 .build()?;
590
591 let expected = "EmptyRelation: rows=0";
592 assert_together_optimized_plan(plan, expected, eq)
593 }
594
595 fn assert_anti_join_empty_join_table_is_base_table(
597 anti_left_join: bool,
598 ) -> Result<()> {
599 let (left, right, join_type, expected) = if anti_left_join {
601 let left = test_table_scan()?;
602 let right = LogicalPlanBuilder::from(test_table_scan()?)
603 .filter(lit(false))?
604 .build()?;
605 let expected = left.display_indent().to_string();
606 (left, right, JoinType::LeftAnti, expected)
607 } else {
608 let right = test_table_scan()?;
609 let left = LogicalPlanBuilder::from(test_table_scan()?)
610 .filter(lit(false))?
611 .build()?;
612 let expected = right.display_indent().to_string();
613 (left, right, JoinType::RightAnti, expected)
614 };
615
616 let plan = LogicalPlanBuilder::from(left)
617 .join_using(right, join_type, vec![Column::from_name("a".to_string())])?
618 .build()?;
619
620 assert_together_optimized_plan(plan, &expected, true)
621 }
622
623 #[test]
624 fn test_join_empty_propagation_rules() -> Result<()> {
625 assert_empty_left_empty_right_lp(true, true, JoinType::Full, true)?;
627
628 assert_empty_left_empty_right_lp(true, false, JoinType::Left, true)?;
630
631 assert_empty_left_empty_right_lp(false, true, JoinType::Right, true)?;
633
634 assert_empty_left_empty_right_lp(true, false, JoinType::LeftSemi, true)?;
636
637 assert_empty_left_empty_right_lp(false, true, JoinType::LeftSemi, true)?;
639
640 assert_empty_left_empty_right_lp(true, false, JoinType::RightSemi, true)?;
642
643 assert_empty_left_empty_right_lp(false, true, JoinType::RightSemi, true)?;
645
646 assert_empty_left_empty_right_lp(true, false, JoinType::LeftAnti, true)?;
648
649 assert_empty_left_empty_right_lp(false, true, JoinType::RightAnti, true)?;
651
652 assert_anti_join_empty_join_table_is_base_table(true)?;
654
655 assert_anti_join_empty_join_table_is_base_table(false)
657 }
658
659 #[test]
660 fn test_join_empty_propagation_rules_noop() -> Result<()> {
661 assert_empty_left_empty_right_lp(false, true, JoinType::Left, false)?;
665
666 assert_empty_left_empty_right_lp(true, false, JoinType::Right, false)?;
668
669 assert_empty_left_empty_right_lp(false, false, JoinType::LeftSemi, false)?;
671
672 assert_empty_left_empty_right_lp(false, false, JoinType::RightSemi, false)?;
674
675 assert_empty_left_empty_right_lp(false, false, JoinType::LeftAnti, false)?;
677
678 assert_empty_left_empty_right_lp(false, true, JoinType::LeftAnti, false)?;
680
681 assert_empty_left_empty_right_lp(false, false, JoinType::RightAnti, false)?;
683
684 assert_empty_left_empty_right_lp(true, false, JoinType::RightAnti, false)
686 }
687
688 #[test]
689 fn test_left_join_right_empty_null_pad() -> Result<()> {
690 let left =
691 LogicalPlanBuilder::from(test_table_scan_with_name("left")?).build()?;
692 let right_empty = LogicalPlanBuilder::from(test_table_scan_with_name("right")?)
693 .filter(lit(false))?
694 .build()?;
695
696 let plan = LogicalPlanBuilder::from(left)
697 .join_using(
698 right_empty,
699 JoinType::Left,
700 vec![Column::from_name("a".to_string())],
701 )?
702 .build()?;
703
704 let expected = "Projection: left.a, left.b, left.c, CAST(NULL AS UInt32) AS a, CAST(NULL AS UInt32) AS b, CAST(NULL AS UInt32) AS c\n TableScan: left";
705 assert_together_optimized_plan(plan, expected, true)
706 }
707
708 #[test]
709 fn test_right_join_left_empty_null_pad() -> Result<()> {
710 let left_empty = LogicalPlanBuilder::from(test_table_scan_with_name("left")?)
711 .filter(lit(false))?
712 .build()?;
713 let right =
714 LogicalPlanBuilder::from(test_table_scan_with_name("right")?).build()?;
715
716 let plan = LogicalPlanBuilder::from(left_empty)
717 .join_using(
718 right,
719 JoinType::Right,
720 vec![Column::from_name("a".to_string())],
721 )?
722 .build()?;
723
724 let expected = "Projection: CAST(NULL AS UInt32) AS a, CAST(NULL AS UInt32) AS b, CAST(NULL AS UInt32) AS c, right.a, right.b, right.c\n TableScan: right";
725 assert_together_optimized_plan(plan, expected, true)
726 }
727
728 #[test]
729 fn test_full_join_right_empty_null_pad() -> Result<()> {
730 let left =
731 LogicalPlanBuilder::from(test_table_scan_with_name("left")?).build()?;
732 let right_empty = LogicalPlanBuilder::from(test_table_scan_with_name("right")?)
733 .filter(lit(false))?
734 .build()?;
735
736 let plan = LogicalPlanBuilder::from(left)
737 .join_using(
738 right_empty,
739 JoinType::Full,
740 vec![Column::from_name("a".to_string())],
741 )?
742 .build()?;
743
744 let expected = "Projection: left.a, left.b, left.c, CAST(NULL AS UInt32) AS a, CAST(NULL AS UInt32) AS b, CAST(NULL AS UInt32) AS c\n TableScan: left";
745 assert_together_optimized_plan(plan, expected, true)
746 }
747
748 #[test]
749 fn test_full_join_left_empty_null_pad() -> Result<()> {
750 let left_empty = LogicalPlanBuilder::from(test_table_scan_with_name("left")?)
751 .filter(lit(false))?
752 .build()?;
753 let right =
754 LogicalPlanBuilder::from(test_table_scan_with_name("right")?).build()?;
755
756 let plan = LogicalPlanBuilder::from(left_empty)
757 .join_using(
758 right,
759 JoinType::Full,
760 vec![Column::from_name("a".to_string())],
761 )?
762 .build()?;
763
764 let expected = "Projection: CAST(NULL AS UInt32) AS a, CAST(NULL AS UInt32) AS b, CAST(NULL AS UInt32) AS c, right.a, right.b, right.c\n TableScan: right";
765 assert_together_optimized_plan(plan, expected, true)
766 }
767
768 #[test]
769 fn test_left_join_complex_on_right_empty_null_pad() -> Result<()> {
770 let left =
771 LogicalPlanBuilder::from(test_table_scan_with_name("left")?).build()?;
772 let right_empty = LogicalPlanBuilder::from(test_table_scan_with_name("right")?)
773 .filter(lit(false))?
774 .build()?;
775
776 let plan = LogicalPlanBuilder::from(left)
778 .join(
779 right_empty,
780 JoinType::Left,
781 (
782 vec![Column::from_name("a".to_string())],
783 vec![Column::from_name("a".to_string())],
784 ),
785 Some(col("left.b").gt(col("right.b"))),
786 )?
787 .build()?;
788
789 let expected = "Projection: left.a, left.b, left.c, CAST(NULL AS UInt32) AS a, CAST(NULL AS UInt32) AS b, CAST(NULL AS UInt32) AS c\n TableScan: left";
790 assert_together_optimized_plan(plan, expected, true)
791 }
792
793 #[test]
794 fn test_empty_with_non_empty() -> Result<()> {
795 let table_scan = test_table_scan()?;
796
797 let fields = test_table_scan_fields();
798
799 let empty = LogicalPlan::EmptyRelation(EmptyRelation {
800 produce_one_row: false,
801 schema: Arc::new(DFSchema::from_unqualified_fields(
802 fields.into(),
803 Default::default(),
804 )?),
805 });
806
807 let one = LogicalPlanBuilder::from(empty.clone()).build()?;
808 let two = LogicalPlanBuilder::from(table_scan).build()?;
809 let three = LogicalPlanBuilder::from(empty).build()?;
810
811 let plan = LogicalPlanBuilder::from(one)
816 .union(two)?
817 .union(three)?
818 .build()?;
819
820 let expected = "Projection: a, b, c\
821 \n TableScan: test";
822
823 assert_together_optimized_plan(plan, expected, true)
824 }
825}