1use std::sync::Arc;
21
22use datafusion_common::tree_node::{Transformed, TreeNode};
23use datafusion_common::{Column, DFSchema, DFSchemaRef, DataFusionError, Result};
24use datafusion_expr::logical_plan::{Aggregate, LogicalPlan, Projection};
25use datafusion_expr::simplify::SimplifyContext;
26use datafusion_expr::utils::{
27 columnize_expr, find_aggregate_exprs, grouping_set_to_exprlist, merge_schema,
28};
29use datafusion_expr::{DmlStatement, Expr, WriteOp};
30
31use super::ExprSimplifier;
32use crate::optimizer::ApplyOrder;
33use crate::simplify_expressions::linear_aggregates::rewrite_multiple_linear_aggregates;
34use crate::utils::NamePreserver;
35use crate::{OptimizerConfig, OptimizerRule};
36
37#[derive(Default, Debug)]
51pub struct SimplifyExpressions {}
52
53impl OptimizerRule for SimplifyExpressions {
54 fn name(&self) -> &str {
55 "simplify_expressions"
56 }
57
58 fn apply_order(&self) -> Option<ApplyOrder> {
59 Some(ApplyOrder::BottomUp)
60 }
61
62 fn supports_rewrite(&self) -> bool {
63 true
64 }
65
66 fn rewrite(
67 &self,
68 plan: LogicalPlan,
69 config: &dyn OptimizerConfig,
70 ) -> Result<Transformed<LogicalPlan>, DataFusionError> {
71 Self::optimize_internal(plan, config)
72 }
73}
74
75impl SimplifyExpressions {
76 fn optimize_internal(
77 plan: LogicalPlan,
78 config: &dyn OptimizerConfig,
79 ) -> Result<Transformed<LogicalPlan>> {
80 let schema = if let LogicalPlan::Dml(DmlStatement {
81 op: WriteOp::MergeInto(_),
82 table_name,
83 target,
84 ..
85 }) = &plan
86 {
87 let mut schema = merge_schema(&plan.inputs());
88 schema.merge(&DFSchema::try_from_qualified_schema(
89 table_name.clone(),
90 &target.schema(),
91 )?);
92 DFSchemaRef::new(schema)
93 } else if !plan.inputs().is_empty() {
94 DFSchemaRef::new(merge_schema(&plan.inputs()))
95 } else if let LogicalPlan::TableScan(scan) = &plan {
96 Arc::new(DFSchema::try_from_qualified_schema(
107 scan.table_name.clone(),
108 &scan.source.schema(),
109 )?)
110 } else {
111 Arc::new(DFSchema::empty())
112 };
113
114 let info = SimplifyContext::builder()
115 .with_schema(schema)
116 .with_config_options(config.options())
117 .with_query_execution_start_time(config.query_execution_start_time())
118 .build();
119
120 let simplifier = ExprSimplifier::new(info);
124
125 let simplifier = if let LogicalPlan::Join(_) = plan {
133 simplifier.with_canonicalize(false)
134 } else {
135 simplifier
136 };
137
138 let name_preserver = NamePreserver::new(&plan);
140 let mut rewrite_expr = |expr: Expr| {
141 let name = name_preserver.save(&expr);
142 let expr = simplifier.simplify_with_cycle_count_transformed(expr)?.0;
143 Ok(Transformed::new_transformed(
144 name.restore(expr.data),
145 expr.transformed,
146 ))
147 };
148
149 plan.map_expressions(|expr| {
150 if let Expr::GroupingSet(_) = &expr {
152 expr.map_children(&mut rewrite_expr)
153 } else {
154 rewrite_expr(expr)
155 }
156 })?
157 .transform_data(rewrite_aggregate_non_aggregate_aggr_expr)
158 }
159}
160
161impl SimplifyExpressions {
162 #[expect(missing_docs)]
163 pub fn new() -> Self {
164 Self {}
165 }
166}
167
168fn rewrite_aggregate_non_aggregate_aggr_expr(
188 plan: LogicalPlan,
189) -> Result<Transformed<LogicalPlan>> {
190 let LogicalPlan::Aggregate(Aggregate {
191 input,
192 group_expr,
193 mut aggr_expr,
194 schema,
195 ..
196 }) = plan
197 else {
198 return Ok(Transformed::no(plan));
199 };
200
201 let rewrote_aggs = rewrite_multiple_linear_aggregates(&mut aggr_expr)?;
202
203 if aggr_expr.iter().all(is_top_level_aggregate_expr) {
205 let new_plan = LogicalPlan::Aggregate(Aggregate::try_new_with_schema(
206 input, group_expr, aggr_expr, schema,
207 )?);
208 return if !rewrote_aggs {
209 Ok(Transformed::no(new_plan))
210 } else {
211 Ok(Transformed::yes(new_plan))
212 };
213 }
214
215 let inner_aggr_expr = find_aggregate_exprs(aggr_expr.iter());
219 let inner_aggregate = LogicalPlan::Aggregate(Aggregate::try_new(
220 Arc::clone(&input),
221 group_expr.clone(),
222 inner_aggr_expr,
223 )?);
224 let inner_aggregate = Arc::new(inner_aggregate);
225
226 let mut projection_exprs = aggregate_output_exprs(&group_expr)?;
227 projection_exprs.extend(aggr_expr);
228 let projection_exprs = projection_exprs
229 .into_iter()
230 .map(|expr| columnize_expr(expr, inner_aggregate.as_ref()))
231 .collect::<Result<Vec<_>>>()?;
232
233 Ok(Transformed::yes(LogicalPlan::Projection(
234 Projection::try_new(projection_exprs, inner_aggregate)?,
235 )))
236}
237
238fn is_top_level_aggregate_expr(expr: &Expr) -> bool {
239 matches!(
240 expr.clone().unalias_nested().data,
241 Expr::AggregateFunction(_)
242 )
243}
244
245fn aggregate_output_exprs(group_expr: &[Expr]) -> Result<Vec<Expr>> {
246 let mut output_exprs = grouping_set_to_exprlist(group_expr)?
247 .into_iter()
248 .cloned()
249 .collect::<Vec<_>>();
250
251 if matches!(group_expr, [Expr::GroupingSet(_)]) {
252 output_exprs.push(Expr::Column(Column::from_name(
253 Aggregate::INTERNAL_GROUPING_ID,
254 )));
255 }
256
257 Ok(output_exprs)
258}
259
260#[cfg(test)]
261mod tests {
262 use std::ops::Not;
263
264 use arrow::datatypes::{DataType, Field, Schema};
265 use chrono::{DateTime, Utc};
266
267 use datafusion_common::ScalarValue;
268 use datafusion_expr::logical_plan::builder::table_scan_with_filters;
269 use datafusion_expr::logical_plan::table_scan;
270 use datafusion_expr::*;
271 use datafusion_functions_aggregate::expr_fn::{max, min, sum};
272
273 use crate::OptimizerContext;
274 use crate::assert_optimized_plan_eq_snapshot;
275 use crate::test::{assert_fields_eq, test_table_scan_with_name};
276
277 use super::*;
278
279 fn test_table_scan() -> LogicalPlan {
280 let schema = Schema::new(vec![
281 Field::new("a", DataType::Boolean, false),
282 Field::new("b", DataType::Boolean, false),
283 Field::new("c", DataType::Boolean, false),
284 Field::new("d", DataType::UInt32, false),
285 Field::new("e", DataType::UInt32, true),
286 ]);
287 table_scan(Some("test"), &schema, None)
288 .expect("creating scan")
289 .build()
290 .expect("building plan")
291 }
292
293 macro_rules! assert_optimized_plan_equal {
294 (
295 $plan:expr,
296 @ $expected:literal $(,)?
297 ) => {{
298 let rules: Vec<Arc<dyn crate::OptimizerRule + Send + Sync>> = vec![Arc::new(SimplifyExpressions::new())];
299 let optimizer_ctx = OptimizerContext::new();
300 assert_optimized_plan_eq_snapshot!(
301 optimizer_ctx,
302 rules,
303 $plan,
304 @ $expected,
305 )
306 }};
307 }
308
309 #[test]
310 fn test_simplify_table_full_filter_in_scan() -> Result<()> {
311 let fields = vec![
312 Field::new("a", DataType::UInt32, false),
313 Field::new("b", DataType::UInt32, false),
314 Field::new("c", DataType::UInt32, false),
315 ];
316
317 let schema = Schema::new(fields);
318
319 let table_scan = table_scan_with_filters(
320 Some("test"),
321 &schema,
322 Some(vec![0]),
323 vec![col("b").is_not_null()],
324 )?
325 .build()?;
326 assert_eq!(1, table_scan.schema().fields().len());
327 assert_fields_eq(&table_scan, vec!["a"]);
328
329 assert_optimized_plan_equal!(
330 table_scan,
331 @ "TableScan: test projection=[a], full_filters=[Boolean(true)]"
332 )
333 }
334
335 #[test]
336 fn test_simplify_filter_pushdown() -> Result<()> {
337 let table_scan = test_table_scan();
338 let plan = LogicalPlanBuilder::from(table_scan)
339 .project(vec![col("a")])?
340 .filter(and(col("b").gt(lit(1)), col("b").gt(lit(1))))?
341 .build()?;
342
343 assert_optimized_plan_equal!(
344 plan,
345 @ r"
346 Filter: test.b > Int32(1)
347 Projection: test.a
348 TableScan: test
349 "
350 )
351 }
352
353 #[test]
354 fn test_simplify_optimized_plan() -> Result<()> {
355 let table_scan = test_table_scan();
356 let plan = LogicalPlanBuilder::from(table_scan)
357 .project(vec![col("a")])?
358 .filter(and(col("b").gt(lit(1)), col("b").gt(lit(1))))?
359 .build()?;
360
361 assert_optimized_plan_equal!(
362 plan,
363 @ r"
364 Filter: test.b > Int32(1)
365 Projection: test.a
366 TableScan: test
367 "
368 )
369 }
370
371 #[test]
372 fn test_simplify_udaf_to_non_aggregate_expr() -> Result<()> {
373 let schema = Schema::new(vec![Field::new("a", DataType::Int64, false)]);
374 let table_scan = table_scan(Some("test"), &schema, None)?
375 .build()
376 .expect("building scan");
377
378 let plan = LogicalPlanBuilder::from(table_scan)
379 .aggregate(Vec::<Expr>::new(), vec![sum(col("a") + lit(2i64))])?
380 .build()?;
381
382 assert_optimized_plan_equal!(
383 plan,
384 @r"
385 Aggregate: groupBy=[[]], aggr=[[sum(test.a + Int64(2))]]
386 TableScan: test
387 "
388 )?;
389 Ok(())
390 }
391
392 #[test]
393 fn test_simplify_common_sum_arg() -> Result<()> {
394 let schema = Schema::new(vec![Field::new("a", DataType::Int64, false)]);
395 let table_scan = table_scan(Some("test"), &schema, None)?
396 .build()
397 .expect("building scan");
398
399 let plan = LogicalPlanBuilder::from(table_scan)
400 .aggregate(
401 Vec::<Expr>::new(),
402 vec![sum(col("a") + lit(2i64)), sum(col("a") + lit(3i64))],
403 )?
404 .build()?;
405
406 assert_optimized_plan_equal!(
407 plan,
408 @r"
409 Projection: sum(test.a) + Int64(2) * CAST(count(test.a) AS Int64) AS sum(test.a + Int64(2)), sum(test.a) + Int64(3) * CAST(count(test.a) AS Int64) AS sum(test.a + Int64(3))
410 Aggregate: groupBy=[[]], aggr=[[sum(test.a), count(test.a)]]
411 TableScan: test
412 "
413 )?;
414 Ok(())
415 }
416
417 #[test]
418 fn test_simplify_optimized_plan_with_or() -> Result<()> {
419 let table_scan = test_table_scan();
420 let plan = LogicalPlanBuilder::from(table_scan)
421 .project(vec![col("a")])?
422 .filter(or(col("b").gt(lit(1)), col("b").gt(lit(1))))?
423 .build()?;
424
425 assert_optimized_plan_equal!(
426 plan,
427 @ r"
428 Filter: test.b > Int32(1)
429 Projection: test.a
430 TableScan: test
431 "
432 )
433 }
434
435 #[test]
436 fn test_simplify_optimized_plan_with_composed_and() -> Result<()> {
437 let table_scan = test_table_scan();
438 let plan = LogicalPlanBuilder::from(table_scan)
440 .project(vec![col("a"), col("b")])?
441 .filter(and(
442 and(col("a").gt(lit(5)), col("b").lt(lit(6))),
443 col("a").gt(lit(5)),
444 ))?
445 .build()?;
446
447 assert_optimized_plan_equal!(
448 plan,
449 @ r"
450 Filter: test.a > Int32(5) AND test.b < Int32(6)
451 Projection: test.a, test.b
452 TableScan: test
453 "
454 )
455 }
456
457 #[test]
458 fn test_simplify_optimized_plan_eq_expr() -> Result<()> {
459 let table_scan = test_table_scan();
460 let plan = LogicalPlanBuilder::from(table_scan)
461 .filter(col("b").eq(lit(true)))?
462 .filter(col("c").eq(lit(false)))?
463 .project(vec![col("a")])?
464 .build()?;
465
466 assert_optimized_plan_equal!(
467 plan,
468 @ r"
469 Projection: test.a
470 Filter: NOT test.c
471 Filter: test.b
472 TableScan: test
473 "
474 )
475 }
476
477 #[test]
478 fn test_simplify_optimized_plan_not_eq_expr() -> Result<()> {
479 let table_scan = test_table_scan();
480 let plan = LogicalPlanBuilder::from(table_scan)
481 .filter(col("b").not_eq(lit(true)))?
482 .filter(col("c").not_eq(lit(false)))?
483 .limit(0, Some(1))?
484 .project(vec![col("a")])?
485 .build()?;
486
487 assert_optimized_plan_equal!(
488 plan,
489 @ r"
490 Projection: test.a
491 Limit: skip=0, fetch=1
492 Filter: test.c
493 Filter: NOT test.b
494 TableScan: test
495 "
496 )
497 }
498
499 #[test]
500 fn test_simplify_optimized_plan_and_expr() -> Result<()> {
501 let table_scan = test_table_scan();
502 let plan = LogicalPlanBuilder::from(table_scan)
503 .filter(col("b").not_eq(lit(true)).and(col("c").eq(lit(true))))?
504 .project(vec![col("a")])?
505 .build()?;
506
507 assert_optimized_plan_equal!(
508 plan,
509 @ r"
510 Projection: test.a
511 Filter: NOT test.b AND test.c
512 TableScan: test
513 "
514 )
515 }
516
517 #[test]
518 fn test_simplify_optimized_plan_or_expr() -> Result<()> {
519 let table_scan = test_table_scan();
520 let plan = LogicalPlanBuilder::from(table_scan)
521 .filter(col("b").not_eq(lit(true)).or(col("c").eq(lit(false))))?
522 .project(vec![col("a")])?
523 .build()?;
524
525 assert_optimized_plan_equal!(
526 plan,
527 @ r"
528 Projection: test.a
529 Filter: NOT test.b OR NOT test.c
530 TableScan: test
531 "
532 )
533 }
534
535 #[test]
536 fn test_simplify_optimized_plan_not_expr() -> Result<()> {
537 let table_scan = test_table_scan();
538 let plan = LogicalPlanBuilder::from(table_scan)
539 .filter(col("b").eq(lit(false)).not())?
540 .project(vec![col("a")])?
541 .build()?;
542
543 assert_optimized_plan_equal!(
544 plan,
545 @ r"
546 Projection: test.a
547 Filter: test.b
548 TableScan: test
549 "
550 )
551 }
552
553 #[test]
554 fn test_simplify_optimized_plan_support_projection() -> Result<()> {
555 let table_scan = test_table_scan();
556 let plan = LogicalPlanBuilder::from(table_scan)
557 .project(vec![col("a"), col("d"), col("b").eq(lit(false))])?
558 .build()?;
559
560 assert_optimized_plan_equal!(
561 plan,
562 @ r"
563 Projection: test.a, test.d, NOT test.b AS test.b = Boolean(false)
564 TableScan: test
565 "
566 )
567 }
568
569 #[test]
570 fn test_simplify_optimized_plan_support_aggregate() -> Result<()> {
571 let table_scan = test_table_scan();
572 let plan = LogicalPlanBuilder::from(table_scan)
573 .project(vec![col("a"), col("c"), col("b")])?
574 .aggregate(
575 vec![col("a"), col("c")],
576 vec![max(col("b").eq(lit(true))), min(col("b"))],
577 )?
578 .build()?;
579
580 assert_optimized_plan_equal!(
581 plan,
582 @ r"
583 Aggregate: groupBy=[[test.a, test.c]], aggr=[[max(test.b) AS max(test.b = Boolean(true)), min(test.b)]]
584 Projection: test.a, test.c, test.b
585 TableScan: test
586 "
587 )
588 }
589
590 #[test]
591 fn test_simplify_optimized_plan_support_values() -> Result<()> {
592 let expr1 = Expr::BinaryExpr(BinaryExpr::new(
593 Box::new(lit(1)),
594 Operator::Plus,
595 Box::new(lit(2)),
596 ));
597 let expr2 = Expr::BinaryExpr(BinaryExpr::new(
598 Box::new(lit(2)),
599 Operator::Minus,
600 Box::new(lit(1)),
601 ));
602 let values = vec![vec![expr1, expr2]];
603 let plan = LogicalPlanBuilder::values(values)?.build()?;
604
605 assert_optimized_plan_equal!(
606 plan,
607 @ "Values: (Int32(3) AS Int32(1) + Int32(2), Int32(1) AS Int32(2) - Int32(1))"
608 )
609 }
610
611 fn get_optimized_plan_formatted(
612 plan: LogicalPlan,
613 date_time: &DateTime<Utc>,
614 ) -> String {
615 let config = OptimizerContext::new().with_query_execution_start_time(*date_time);
616 let rule = SimplifyExpressions::new();
617
618 let optimized_plan = rule.rewrite(plan, &config).unwrap().data;
619 format!("{optimized_plan}")
620 }
621
622 #[test]
623 fn cast_expr() -> Result<()> {
624 let table_scan = test_table_scan();
625 let proj = vec![Expr::Cast(Cast::new(Box::new(lit("0")), DataType::Int32))];
626 let plan = LogicalPlanBuilder::from(table_scan)
627 .project(proj)?
628 .build()?;
629
630 let expected = "Projection: Int32(0) AS Utf8(\"0\")\
631 \n TableScan: test";
632 let actual = get_optimized_plan_formatted(plan, &Utc::now());
633 assert_eq!(expected, actual);
634 Ok(())
635 }
636
637 #[test]
638 fn simplify_and_eval() -> Result<()> {
639 let table_scan = test_table_scan();
642 let time = Utc::now();
643 let proj = vec![lit(true).or(lit(false)).not_eq(col("a"))];
645 let plan = LogicalPlanBuilder::from(table_scan)
646 .project(proj)?
647 .build()?;
648
649 let actual = get_optimized_plan_formatted(plan, &time);
650 let expected = "Projection: NOT test.a AS Boolean(true) OR Boolean(false) != test.a\
651 \n TableScan: test";
652
653 assert_eq!(expected, actual);
654 Ok(())
655 }
656
657 #[test]
658 fn simplify_not_binary() -> Result<()> {
659 let table_scan = test_table_scan();
660
661 let plan = LogicalPlanBuilder::from(table_scan)
662 .filter(col("d").gt(lit(10)).not())?
663 .build()?;
664
665 assert_optimized_plan_equal!(
666 plan,
667 @ r"
668 Filter: test.d <= Int32(10)
669 TableScan: test
670 "
671 )
672 }
673
674 #[test]
675 fn simplify_not_bool_and() -> Result<()> {
676 let table_scan = test_table_scan();
677
678 let plan = LogicalPlanBuilder::from(table_scan)
679 .filter(col("d").gt(lit(10)).and(col("d").lt(lit(100))).not())?
680 .build()?;
681
682 assert_optimized_plan_equal!(
683 plan,
684 @ r"
685 Filter: test.d <= Int32(10) OR test.d >= Int32(100)
686 TableScan: test
687 "
688 )
689 }
690
691 #[test]
692 fn simplify_not_bool_or() -> Result<()> {
693 let table_scan = test_table_scan();
694
695 let plan = LogicalPlanBuilder::from(table_scan)
696 .filter(col("d").gt(lit(10)).or(col("d").lt(lit(100))).not())?
697 .build()?;
698
699 assert_optimized_plan_equal!(
700 plan,
701 @ r"
702 Filter: test.d <= Int32(10) AND test.d >= Int32(100)
703 TableScan: test
704 "
705 )
706 }
707
708 #[test]
709 fn simplify_not_not() -> Result<()> {
710 let table_scan = test_table_scan();
711
712 let plan = LogicalPlanBuilder::from(table_scan)
713 .filter(col("d").gt(lit(10)).not().not())?
714 .build()?;
715
716 assert_optimized_plan_equal!(
717 plan,
718 @ r"
719 Filter: test.d > Int32(10)
720 TableScan: test
721 "
722 )
723 }
724
725 #[test]
726 fn simplify_not_null() -> Result<()> {
727 let table_scan = test_table_scan();
728
729 let plan = LogicalPlanBuilder::from(table_scan)
730 .filter(col("e").is_null().not())?
731 .build()?;
732
733 assert_optimized_plan_equal!(
734 plan,
735 @ r"
736 Filter: test.e IS NOT NULL
737 TableScan: test
738 "
739 )
740 }
741
742 #[test]
743 fn simplify_not_not_null() -> Result<()> {
744 let table_scan = test_table_scan();
745
746 let plan = LogicalPlanBuilder::from(table_scan)
747 .filter(col("e").is_not_null().not())?
748 .build()?;
749
750 assert_optimized_plan_equal!(
751 plan,
752 @ r"
753 Filter: test.e IS NULL
754 TableScan: test
755 "
756 )
757 }
758
759 #[test]
760 fn simplify_not_in() -> Result<()> {
761 let table_scan = test_table_scan();
762
763 let plan = LogicalPlanBuilder::from(table_scan)
764 .filter(col("d").in_list(vec![lit(1), lit(2), lit(3)], false).not())?
765 .build()?;
766
767 assert_optimized_plan_equal!(
768 plan,
769 @ r"
770 Filter: test.d != Int32(1) AND test.d != Int32(2) AND test.d != Int32(3)
771 TableScan: test
772 "
773 )
774 }
775
776 #[test]
777 fn simplify_not_not_in() -> Result<()> {
778 let table_scan = test_table_scan();
779
780 let plan = LogicalPlanBuilder::from(table_scan)
781 .filter(col("d").in_list(vec![lit(1), lit(2), lit(3)], true).not())?
782 .build()?;
783
784 assert_optimized_plan_equal!(
785 plan,
786 @ r"
787 Filter: test.d = Int32(1) OR test.d = Int32(2) OR test.d = Int32(3)
788 TableScan: test
789 "
790 )
791 }
792
793 #[test]
794 fn simplify_not_between() -> Result<()> {
795 let table_scan = test_table_scan();
796 let qual = col("d").between(lit(1), lit(10));
797
798 let plan = LogicalPlanBuilder::from(table_scan)
799 .filter(qual.not())?
800 .build()?;
801
802 assert_optimized_plan_equal!(
803 plan,
804 @ r"
805 Filter: test.d < Int32(1) OR test.d > Int32(10)
806 TableScan: test
807 "
808 )
809 }
810
811 #[test]
812 fn simplify_not_not_between() -> Result<()> {
813 let table_scan = test_table_scan();
814 let qual = col("d").not_between(lit(1), lit(10));
815
816 let plan = LogicalPlanBuilder::from(table_scan)
817 .filter(qual.not())?
818 .build()?;
819
820 assert_optimized_plan_equal!(
821 plan,
822 @ r"
823 Filter: test.d >= Int32(1) AND test.d <= Int32(10)
824 TableScan: test
825 "
826 )
827 }
828
829 #[test]
830 fn simplify_not_like() -> Result<()> {
831 let schema = Schema::new(vec![
832 Field::new("a", DataType::Utf8, false),
833 Field::new("b", DataType::Utf8, false),
834 ]);
835 let table_scan = table_scan(Some("test"), &schema, None)
836 .expect("creating scan")
837 .build()
838 .expect("building plan");
839
840 let plan = LogicalPlanBuilder::from(table_scan)
841 .filter(col("a").like(col("b")).not())?
842 .build()?;
843
844 assert_optimized_plan_equal!(
845 plan,
846 @ r"
847 Filter: test.a NOT LIKE test.b
848 TableScan: test
849 "
850 )
851 }
852
853 #[test]
854 fn simplify_not_not_like() -> Result<()> {
855 let schema = Schema::new(vec![
856 Field::new("a", DataType::Utf8, false),
857 Field::new("b", DataType::Utf8, false),
858 ]);
859 let table_scan = table_scan(Some("test"), &schema, None)
860 .expect("creating scan")
861 .build()
862 .expect("building plan");
863
864 let plan = LogicalPlanBuilder::from(table_scan)
865 .filter(col("a").not_like(col("b")).not())?
866 .build()?;
867
868 assert_optimized_plan_equal!(
869 plan,
870 @ r"
871 Filter: test.a LIKE test.b
872 TableScan: test
873 "
874 )
875 }
876
877 #[test]
878 fn simplify_not_ilike() -> Result<()> {
879 let schema = Schema::new(vec![
880 Field::new("a", DataType::Utf8, false),
881 Field::new("b", DataType::Utf8, false),
882 ]);
883 let table_scan = table_scan(Some("test"), &schema, None)
884 .expect("creating scan")
885 .build()
886 .expect("building plan");
887
888 let plan = LogicalPlanBuilder::from(table_scan)
889 .filter(col("a").ilike(col("b")).not())?
890 .build()?;
891
892 assert_optimized_plan_equal!(
893 plan,
894 @ r"
895 Filter: test.a NOT ILIKE test.b
896 TableScan: test
897 "
898 )
899 }
900
901 #[test]
902 fn simplify_not_distinct_from() -> Result<()> {
903 let table_scan = test_table_scan();
904
905 let plan = LogicalPlanBuilder::from(table_scan)
906 .filter(binary_expr(col("d"), Operator::IsDistinctFrom, lit(10)).not())?
907 .build()?;
908
909 assert_optimized_plan_equal!(
910 plan,
911 @ r"
912 Filter: test.d IS NOT DISTINCT FROM Int32(10)
913 TableScan: test
914 "
915 )
916 }
917
918 #[test]
919 fn simplify_not_not_distinct_from() -> Result<()> {
920 let table_scan = test_table_scan();
921
922 let plan = LogicalPlanBuilder::from(table_scan)
923 .filter(binary_expr(col("d"), Operator::IsNotDistinctFrom, lit(10)).not())?
924 .build()?;
925
926 assert_optimized_plan_equal!(
927 plan,
928 @ r"
929 Filter: test.d IS DISTINCT FROM Int32(10)
930 TableScan: test
931 "
932 )
933 }
934
935 #[test]
936 fn simplify_equijoin_predicate() -> Result<()> {
937 let t1 = test_table_scan_with_name("t1")?;
938 let t2 = test_table_scan_with_name("t2")?;
939
940 let left_key = col("t1.a") + lit(1i64).cast_to(&DataType::UInt32, t1.schema())?;
941 let right_key =
942 col("t2.a") + lit(2i64).cast_to(&DataType::UInt32, t2.schema())?;
943 let plan = LogicalPlanBuilder::from(t1)
944 .join_with_expr_keys(
945 t2,
946 JoinType::Inner,
947 (vec![left_key], vec![right_key]),
948 None,
949 )?
950 .build()?;
951
952 assert_optimized_plan_equal!(
955 plan,
956 @ r"
957 Inner Join: t1.a + UInt32(1) = t2.a + UInt32(2)
958 TableScan: t1
959 TableScan: t2
960 "
961 )
962 }
963
964 #[test]
965 fn simplify_is_not_null() -> Result<()> {
966 let table_scan = test_table_scan();
967
968 let plan = LogicalPlanBuilder::from(table_scan)
969 .filter(col("d").is_not_null())?
970 .build()?;
971
972 assert_optimized_plan_equal!(
973 plan,
974 @ r"
975 Filter: Boolean(true)
976 TableScan: test
977 "
978 )
979 }
980
981 #[test]
982 fn simplify_is_null() -> Result<()> {
983 let table_scan = test_table_scan();
984
985 let plan = LogicalPlanBuilder::from(table_scan)
986 .filter(col("d").is_null())?
987 .build()?;
988
989 assert_optimized_plan_equal!(
990 plan,
991 @ r"
992 Filter: Boolean(false)
993 TableScan: test
994 "
995 )
996 }
997
998 #[test]
999 fn simplify_grouping_sets() -> Result<()> {
1000 let table_scan = test_table_scan();
1001 let plan = LogicalPlanBuilder::from(table_scan)
1002 .aggregate(
1003 [grouping_set(vec![
1004 vec![(lit(42).alias("prev") + lit(1)).alias("age"), col("a")],
1005 vec![col("a").or(col("b")).and(lit(1).lt(lit(0))).alias("cond")],
1006 vec![col("d").alias("e"), (lit(1) + lit(2))],
1007 ])],
1008 [] as [Expr; 0],
1009 )?
1010 .build()?;
1011
1012 assert_optimized_plan_equal!(
1013 plan,
1014 @ r"
1015 Aggregate: groupBy=[[GROUPING SETS ((Int32(43) AS age, test.a), (Boolean(false) AS cond), (test.d AS e, Int32(3) AS Int32(1) + Int32(2)))]], aggr=[[]]
1016 TableScan: test
1017 "
1018 )
1019 }
1020
1021 #[test]
1022 fn test_simplify_regex_special_cases() -> Result<()> {
1023 let schema = Schema::new(vec![
1024 Field::new("a", DataType::Utf8, true),
1025 Field::new("b", DataType::Utf8, false),
1026 ]);
1027 let table_scan = table_scan(Some("test"), &schema, None)?.build()?;
1028
1029 let plan = LogicalPlanBuilder::from(table_scan.clone())
1031 .filter(binary_expr(col("a"), Operator::RegexMatch, lit(".*")))?
1032 .build()?;
1033
1034 assert_optimized_plan_equal!(
1035 plan,
1036 @ r"
1037 Filter: test.a IS NOT NULL
1038 TableScan: test
1039 "
1040 )?;
1041
1042 let plan = LogicalPlanBuilder::from(table_scan.clone())
1044 .filter(binary_expr(col("a"), Operator::RegexNotMatch, lit(".*")))?
1045 .build()?;
1046
1047 assert_optimized_plan_equal!(
1048 plan,
1049 @ r"
1050 Filter: test.a IS NULL AND Boolean(NULL)
1051 TableScan: test
1052 "
1053 )?;
1054
1055 let plan = LogicalPlanBuilder::from(table_scan.clone())
1059 .filter(binary_expr(col("b"), Operator::RegexIMatch, lit(".*")))?
1060 .build()?;
1061
1062 assert_optimized_plan_equal!(
1063 plan,
1064 @ r"
1065 Filter: Boolean(true)
1066 TableScan: test
1067 "
1068 )?;
1069
1070 let plan = LogicalPlanBuilder::from(table_scan.clone())
1072 .filter(binary_expr(
1073 lit(ScalarValue::Utf8(None)),
1074 Operator::RegexNotMatch,
1075 lit(".*"),
1076 ))?
1077 .build()?;
1078
1079 assert_optimized_plan_equal!(
1080 plan,
1081 @ r"
1082 Filter: Boolean(NULL)
1083 TableScan: test
1084 "
1085 )?;
1086
1087 let plan = LogicalPlanBuilder::from(table_scan.clone())
1089 .filter(binary_expr(col("a"), Operator::RegexNotIMatch, lit(".*")))?
1090 .build()?;
1091
1092 assert_optimized_plan_equal!(
1093 plan,
1094 @ r"
1095 Filter: test.a IS NULL AND Boolean(NULL)
1096 TableScan: test
1097 "
1098 )?;
1099
1100 let plan = LogicalPlanBuilder::from(table_scan.clone())
1102 .filter(binary_expr(
1103 lit(ScalarValue::Utf8(None)),
1104 Operator::RegexNotIMatch,
1105 lit(".*"),
1106 ))?
1107 .build()?;
1108
1109 assert_optimized_plan_equal!(
1110 plan,
1111 @ r"
1112 Filter: Boolean(NULL)
1113 TableScan: test
1114 "
1115 )
1116 }
1117
1118 #[test]
1119 fn simplify_not_in_list() -> Result<()> {
1120 let schema = Schema::new(vec![Field::new("a", DataType::Utf8, false)]);
1121 let table_scan = table_scan(Some("test"), &schema, None)?.build()?;
1122
1123 let plan = LogicalPlanBuilder::from(table_scan)
1124 .filter(col("a").in_list(vec![lit("a"), lit("b")], false).not())?
1125 .build()?;
1126
1127 assert_optimized_plan_equal!(
1128 plan,
1129 @ r#"
1130 Filter: test.a != Utf8("a") AND test.a != Utf8("b")
1131 TableScan: test
1132 "#
1133 )
1134 }
1135
1136 #[test]
1137 fn simplify_not_not_in_list() -> Result<()> {
1138 let schema = Schema::new(vec![Field::new("a", DataType::Utf8, false)]);
1139 let table_scan = table_scan(Some("test"), &schema, None)?.build()?;
1140
1141 let plan = LogicalPlanBuilder::from(table_scan)
1142 .filter(
1143 col("a")
1144 .in_list(vec![lit("a"), lit("b")], false)
1145 .not()
1146 .not(),
1147 )?
1148 .build()?;
1149
1150 assert_optimized_plan_equal!(
1151 plan,
1152 @ r#"
1153 Filter: test.a = Utf8("a") OR test.a = Utf8("b")
1154 TableScan: test
1155 "#
1156 )
1157 }
1158
1159 #[test]
1160 fn simplify_not_exists() -> Result<()> {
1161 let schema = Schema::new(vec![Field::new("a", DataType::Utf8, false)]);
1162 let table_scan = table_scan(Some("test"), &schema, None)?.build()?;
1163 let table_scan2 =
1164 datafusion_expr::table_scan(Some("test2"), &schema, None)?.build()?;
1165
1166 let plan = LogicalPlanBuilder::from(table_scan)
1167 .filter(
1168 exists(Arc::new(LogicalPlanBuilder::from(table_scan2).build()?)).not(),
1169 )?
1170 .build()?;
1171
1172 assert_optimized_plan_equal!(
1173 plan,
1174 @ r"
1175 Filter: NOT EXISTS (<subquery>)
1176 Subquery:
1177 TableScan: test2
1178 TableScan: test
1179 "
1180 )
1181 }
1182
1183 #[test]
1184 fn simplify_not_not_exists() -> Result<()> {
1185 let schema = Schema::new(vec![Field::new("a", DataType::Utf8, false)]);
1186 let table_scan = table_scan(Some("test"), &schema, None)?.build()?;
1187 let table_scan2 =
1188 datafusion_expr::table_scan(Some("test2"), &schema, None)?.build()?;
1189
1190 let plan = LogicalPlanBuilder::from(table_scan)
1191 .filter(
1192 exists(Arc::new(LogicalPlanBuilder::from(table_scan2).build()?))
1193 .not()
1194 .not(),
1195 )?
1196 .build()?;
1197
1198 assert_optimized_plan_equal!(
1199 plan,
1200 @ r"
1201 Filter: EXISTS (<subquery>)
1202 Subquery:
1203 TableScan: test2
1204 TableScan: test
1205 "
1206 )
1207 }
1208
1209 #[test]
1210 fn simplify_not_in_subquery() -> Result<()> {
1211 let schema = Schema::new(vec![Field::new("a", DataType::Utf8, false)]);
1212 let table_scan = table_scan(Some("test"), &schema, None)?.build()?;
1213 let table_scan2 =
1214 datafusion_expr::table_scan(Some("test2"), &schema, None)?.build()?;
1215
1216 let plan = LogicalPlanBuilder::from(table_scan)
1217 .filter(
1218 in_subquery(
1219 col("a"),
1220 Arc::new(LogicalPlanBuilder::from(table_scan2).build()?),
1221 )
1222 .not(),
1223 )?
1224 .build()?;
1225
1226 assert_optimized_plan_equal!(
1227 plan,
1228 @ r"
1229 Filter: test.a NOT IN (<subquery>)
1230 Subquery:
1231 TableScan: test2
1232 TableScan: test
1233 "
1234 )
1235 }
1236
1237 #[test]
1238 fn simplify_not_not_in_subquery() -> Result<()> {
1239 let schema = Schema::new(vec![Field::new("a", DataType::Utf8, false)]);
1240 let table_scan = table_scan(Some("test"), &schema, None)?.build()?;
1241 let table_scan2 =
1242 datafusion_expr::table_scan(Some("test2"), &schema, None)?.build()?;
1243
1244 let plan = LogicalPlanBuilder::from(table_scan)
1245 .filter(
1246 in_subquery(
1247 col("a"),
1248 Arc::new(LogicalPlanBuilder::from(table_scan2).build()?),
1249 )
1250 .not()
1251 .not(),
1252 )?
1253 .build()?;
1254
1255 assert_optimized_plan_equal!(
1256 plan,
1257 @ r"
1258 Filter: test.a IN (<subquery>)
1259 Subquery:
1260 TableScan: test2
1261 TableScan: test
1262 "
1263 )
1264 }
1265}