1mod required_indices;
21
22use crate::optimizer::ApplyOrder;
23use crate::{OptimizerConfig, OptimizerRule};
24use std::sync::Arc;
25
26use datafusion_common::{
27 Column, DFSchema, HashMap, JoinType, Result, assert_eq_or_internal_err,
28 get_required_group_by_exprs_indices, internal_datafusion_err, internal_err,
29};
30use datafusion_expr::expr::Alias;
31use datafusion_expr::{
32 Aggregate, Distinct, EmptyRelation, Expr, Projection, TableScan, Unnest, Window,
33 logical_plan::LogicalPlan,
34};
35
36use crate::optimize_projections::required_indices::RequiredIndices;
37use crate::utils::NamePreserver;
38use datafusion_common::tree_node::{
39 Transformed, TreeNode, TreeNodeContainer, TreeNodeRecursion,
40};
41
42#[derive(Default, Debug)]
76pub struct OptimizeProjections {}
77
78impl OptimizeProjections {
79 #[expect(missing_docs)]
80 pub fn new() -> Self {
81 Self {}
82 }
83}
84
85impl OptimizerRule for OptimizeProjections {
86 fn name(&self) -> &str {
87 "optimize_projections"
88 }
89
90 fn apply_order(&self) -> Option<ApplyOrder> {
91 None
92 }
93
94 fn supports_rewrite(&self) -> bool {
95 true
96 }
97
98 fn rewrite(
99 &self,
100 plan: LogicalPlan,
101 config: &dyn OptimizerConfig,
102 ) -> Result<Transformed<LogicalPlan>> {
103 let indices = RequiredIndices::new_for_all_exprs(&plan);
105 optimize_projections(plan, config, indices)
106 }
107}
108
109#[cfg_attr(feature = "recursive_protection", recursive::recursive)]
129fn optimize_projections(
130 plan: LogicalPlan,
131 config: &dyn OptimizerConfig,
132 indices: RequiredIndices,
133) -> Result<Transformed<LogicalPlan>> {
134 match plan {
137 LogicalPlan::Projection(proj) => {
138 return merge_consecutive_projections(proj)?
139 .transform_data(|proj| {
140 rewrite_projection_given_requirements(proj, config, &indices)
141 })?
142 .transform_data(|plan| optimize_subqueries(plan, config));
143 }
144 LogicalPlan::Aggregate(aggregate) => {
145 let n_group_exprs = aggregate.group_expr_len()?;
147 let (group_by_reqs, aggregate_reqs) = indices.split_off(n_group_exprs);
150
151 let new_group_bys = if aggregate
156 .input
157 .schema()
158 .functional_dependencies()
159 .is_empty()
160 {
161 aggregate.group_expr
162 } else {
163 let group_by_expr_existing = aggregate
164 .group_expr
165 .iter()
166 .map(|group_by_expr| group_by_expr.schema_name().to_string())
167 .collect::<Vec<_>>();
168
169 if let Some(simplest_groupby_indices) =
170 get_required_group_by_exprs_indices(
171 aggregate.input.schema(),
172 &group_by_expr_existing,
173 )
174 {
175 group_by_reqs
179 .append(&simplest_groupby_indices)
180 .get_at_indices(&aggregate.group_expr)
181 } else {
182 aggregate.group_expr
183 }
184 };
185
186 let new_aggr_expr = aggregate_reqs.get_at_indices(&aggregate.aggr_expr);
189
190 if new_group_bys.is_empty() && new_aggr_expr.is_empty() {
191 return Ok(Transformed::yes(LogicalPlan::EmptyRelation(
193 EmptyRelation {
194 produce_one_row: true,
195 schema: Arc::new(DFSchema::empty()),
196 },
197 )));
198 }
199
200 let all_exprs_iter = new_group_bys.iter().chain(new_aggr_expr.iter());
201 let schema = aggregate.input.schema();
202 let necessary_indices =
203 RequiredIndices::new().with_exprs(schema, all_exprs_iter);
204 let necessary_exprs = necessary_indices.get_required_exprs(schema);
205
206 return optimize_projections(
207 Arc::unwrap_or_clone(aggregate.input),
208 config,
209 necessary_indices,
210 )?
211 .transform_data(|aggregate_input| {
212 add_projection_on_top_if_helpful(aggregate_input, necessary_exprs)
217 })?
218 .map_data(|aggregate_input| {
219 Aggregate::try_new(
222 Arc::new(aggregate_input),
223 new_group_bys,
224 new_aggr_expr,
225 )
226 .map(LogicalPlan::Aggregate)
227 })?
228 .transform_data(|plan| optimize_subqueries(plan, config));
229 }
230 LogicalPlan::Window(window) => {
231 let input_schema = Arc::clone(window.input.schema());
232 let n_input_fields = input_schema.fields().len();
234 let (child_reqs, window_reqs) = indices.split_off(n_input_fields);
237
238 let new_window_expr = window_reqs.get_at_indices(&window.window_expr);
241
242 let required_indices = child_reqs.with_exprs(&input_schema, &new_window_expr);
245
246 return optimize_projections(
247 Arc::unwrap_or_clone(window.input),
248 config,
249 required_indices.clone(),
250 )?
251 .transform_data(|window_child| {
252 if new_window_expr.is_empty() {
253 Ok(Transformed::no(window_child))
255 } else {
256 let required_exprs =
260 required_indices.get_required_exprs(&input_schema);
261 let window_child =
262 add_projection_on_top_if_helpful(window_child, required_exprs)?
263 .data;
264 Window::try_new(new_window_expr, Arc::new(window_child))
265 .map(LogicalPlan::Window)
266 .map(Transformed::yes)
267 }
268 })?
269 .transform_data(|plan| optimize_subqueries(plan, config));
270 }
271 LogicalPlan::TableScan(table_scan) => {
272 let TableScan {
273 table_name,
274 source,
275 projection,
276 filters,
277 fetch,
278 projected_schema: _,
279 } = table_scan;
280
281 let projection = match &projection {
284 Some(projection) => indices.into_mapped_indices(|idx| projection[idx]),
285 None => indices.into_inner(),
286 };
287 let new_scan =
288 TableScan::try_new(table_name, source, Some(projection), filters, fetch)?;
289
290 return Transformed::yes(LogicalPlan::TableScan(new_scan))
291 .transform_data(|plan| optimize_subqueries(plan, config));
292 }
293 _ => {}
295 };
296
297 let mut child_required_indices: Vec<RequiredIndices> = match &plan {
300 LogicalPlan::Sort(_)
301 | LogicalPlan::Filter(_)
302 | LogicalPlan::Repartition(_)
303 | LogicalPlan::Union(_)
304 | LogicalPlan::SubqueryAlias(_)
305 | LogicalPlan::Distinct(Distinct::On(_)) => {
306 plan.inputs()
311 .into_iter()
312 .map(|input| {
313 indices
314 .clone()
315 .with_projection_beneficial()
316 .with_plan_exprs(&plan, input.schema())
317 })
318 .collect::<Result<_>>()?
319 }
320 LogicalPlan::Limit(_) => {
321 plan.inputs()
326 .into_iter()
327 .map(|input| indices.clone().with_plan_exprs(&plan, input.schema()))
328 .collect::<Result<_>>()?
329 }
330 LogicalPlan::Copy(_)
331 | LogicalPlan::Ddl(_)
332 | LogicalPlan::Dml(_)
333 | LogicalPlan::Explain(_)
334 | LogicalPlan::Analyze(_)
335 | LogicalPlan::Subquery(_)
336 | LogicalPlan::Statement(_)
337 | LogicalPlan::Distinct(Distinct::All(_)) => {
338 plan.inputs()
344 .into_iter()
345 .map(RequiredIndices::new_for_all_exprs)
346 .collect()
347 }
348 LogicalPlan::Extension(extension) => {
349 if let Some(necessary_children_indices) =
350 extension.node.necessary_children_exprs(indices.indices())
351 {
352 let children = extension.node.inputs();
353 assert_eq_or_internal_err!(
354 children.len(),
355 necessary_children_indices.len(),
356 "Inconsistent length between children and necessary children indices. \
357 Make sure `.necessary_children_exprs` implementation of the \
358 `UserDefinedLogicalNode` is consistent with actual children length \
359 for the node."
360 );
361 children
362 .into_iter()
363 .zip(necessary_children_indices)
364 .map(|(child, necessary_indices)| {
365 RequiredIndices::new_from_indices(necessary_indices)
366 .with_plan_exprs(&plan, child.schema())
367 })
368 .collect::<Result<Vec<_>>>()?
369 } else {
370 plan.inputs()
373 .into_iter()
374 .map(RequiredIndices::new_for_all_exprs)
375 .collect()
376 }
377 }
378 LogicalPlan::EmptyRelation(_)
379 | LogicalPlan::Values(_)
380 | LogicalPlan::DescribeTable(_) => {
381 return Ok(Transformed::no(plan));
383 }
384 LogicalPlan::RecursiveQuery(recursive) => {
385 if plan_contains_other_subqueries(
389 recursive.static_term.as_ref(),
390 &recursive.name,
391 ) || plan_contains_other_subqueries(
392 recursive.recursive_term.as_ref(),
393 &recursive.name,
394 ) {
395 return Ok(Transformed::no(plan));
396 }
397
398 plan.inputs()
399 .into_iter()
400 .map(|input| {
401 indices
402 .clone()
403 .with_projection_beneficial()
404 .with_plan_exprs(&plan, input.schema())
405 })
406 .collect::<Result<Vec<_>>>()?
407 }
408 LogicalPlan::Join(join) => {
409 let left_len = join.left.schema().fields().len();
410 let right_len = join.right.schema().fields().len();
411 let (left_req_indices, right_req_indices) =
412 split_join_requirements(left_len, right_len, indices, &join.join_type);
413 let mut left_indices =
414 left_req_indices.with_plan_exprs(&plan, join.left.schema())?;
415 let mut right_indices =
416 right_req_indices.with_plan_exprs(&plan, join.right.schema())?;
417 match join.join_type {
419 JoinType::LeftMark if right_indices.indices().is_empty() => {
420 right_indices = right_indices.append(&[0]);
421 }
422 JoinType::RightMark if left_indices.indices().is_empty() => {
423 left_indices = left_indices.append(&[0]);
424 }
425 _ => {}
426 }
427 vec![
430 left_indices.with_projection_beneficial(),
431 right_indices.with_projection_beneficial(),
432 ]
433 }
434 LogicalPlan::Projection(_)
436 | LogicalPlan::Aggregate(_)
437 | LogicalPlan::Window(_)
438 | LogicalPlan::TableScan(_) => {
439 return internal_err!(
440 "OptimizeProjection: should have handled in the match statement above"
441 );
442 }
443 LogicalPlan::Unnest(Unnest {
444 input,
445 dependency_indices,
446 ..
447 }) => {
448 let required_indices =
450 RequiredIndices::new().with_plan_exprs(&plan, input.schema())?;
451
452 let mut additional_necessary_child_indices = Vec::new();
454 indices.indices().iter().for_each(|idx| {
455 if let Some(index) = dependency_indices.get(*idx) {
456 additional_necessary_child_indices.push(*index);
457 }
458 });
459 vec![required_indices.append(&additional_necessary_child_indices)]
460 }
461 };
462
463 child_required_indices.reverse();
466 assert_eq_or_internal_err!(
467 child_required_indices.len(),
468 plan.inputs().len(),
469 "OptimizeProjection: child_required_indices length mismatch with plan inputs"
470 );
471
472 let transformed_plan = plan.map_children(|child| {
474 let required_indices = child_required_indices.pop().ok_or_else(|| {
475 internal_datafusion_err!(
476 "Unexpected number of required_indices in OptimizeProjections rule"
477 )
478 })?;
479
480 let projection_beneficial = required_indices.projection_beneficial();
481 let project_exprs = required_indices.get_required_exprs(child.schema());
482
483 optimize_projections(child, config, required_indices)?.transform_data(
484 |new_input| {
485 if projection_beneficial {
486 add_projection_on_top_if_helpful(new_input, project_exprs)
487 } else {
488 Ok(Transformed::no(new_input))
489 }
490 },
491 )
492 })?;
493
494 let transformed_plan =
495 transformed_plan.transform_data(|plan| optimize_subqueries(plan, config))?;
496
497 if transformed_plan.transformed {
499 transformed_plan.map_data(|plan| plan.recompute_schema())
500 } else {
501 Ok(transformed_plan)
502 }
503}
504
505fn optimize_subqueries(
509 plan: LogicalPlan,
510 config: &dyn OptimizerConfig,
511) -> Result<Transformed<LogicalPlan>> {
512 plan.map_uncorrelated_subqueries(|subquery_plan| {
513 let indices = RequiredIndices::new_for_all_exprs(&subquery_plan);
514 optimize_projections(subquery_plan, config, indices)
515 })
516}
517
518fn merge_consecutive_projections(proj: Projection) -> Result<Transformed<Projection>> {
549 let Projection {
550 expr,
551 input,
552 schema,
553 ..
554 } = proj;
555 let LogicalPlan::Projection(prev_projection) = input.as_ref() else {
556 return Projection::try_new_with_schema(expr, input, schema).map(Transformed::no);
557 };
558
559 if prev_projection.expr == expr {
562 return Projection::try_new_with_schema(
563 expr,
564 Arc::clone(&prev_projection.input),
565 schema,
566 )
567 .map(Transformed::yes);
568 }
569
570 let mut column_referral_map = HashMap::<&Column, usize>::new();
572 expr.iter()
573 .for_each(|expr| expr.add_column_ref_counts(&mut column_referral_map));
574
575 if column_referral_map.into_iter().any(|(col, usage)| {
579 usage > 1
580 && !prev_projection.expr[prev_projection.schema.index_of_column(col).unwrap()]
581 .placement()
582 .should_push_to_leaves()
583 }) {
584 return Projection::try_new_with_schema(expr, input, schema).map(Transformed::no);
586 }
587
588 let LogicalPlan::Projection(prev_projection) = Arc::unwrap_or_clone(input) else {
589 unreachable!();
591 };
592
593 let name_preserver = NamePreserver::new_for_projection();
596 let mut original_names = vec![];
597 let new_exprs = expr.map_elements(|expr| {
598 original_names.push(name_preserver.save(&expr));
599
600 match expr {
602 Expr::Alias(Alias {
603 expr,
604 relation,
605 name,
606 metadata,
607 }) => rewrite_expr(*expr, &prev_projection).map(|result| {
608 result.update_data(|expr| {
609 if metadata.is_none() && expr.schema_name().to_string() == name {
616 expr
617 } else {
618 Expr::Alias(Alias {
619 expr: Box::new(expr),
620 relation,
621 name,
622 metadata,
623 })
624 }
625 })
626 }),
627 e => rewrite_expr(e, &prev_projection),
628 }
629 })?;
630
631 if new_exprs.transformed {
634 let new_exprs = new_exprs
636 .data
637 .into_iter()
638 .zip(original_names)
639 .map(|(expr, original_name)| original_name.restore(expr))
640 .collect::<Vec<_>>();
641 Projection::try_new(new_exprs, prev_projection.input).map(Transformed::yes)
642 } else {
643 let input = Arc::new(LogicalPlan::Projection(prev_projection));
645 Projection::try_new_with_schema(new_exprs.data, input, schema)
646 .map(Transformed::no)
647 }
648}
649
650fn rewrite_expr(expr: Expr, input: &Projection) -> Result<Transformed<Expr>> {
696 expr.transform_up(|expr| {
697 match expr {
698 Expr::Alias(alias) => {
700 match alias
701 .metadata
702 .as_ref()
703 .map(|h| h.is_empty())
704 .unwrap_or(true)
705 {
706 true => Ok(Transformed::yes(*alias.expr)),
707 false => Ok(Transformed::no(Expr::Alias(alias))),
708 }
709 }
710 Expr::Column(col) => {
711 let idx = input.schema.index_of_column(&col)?;
713 let input_expr = input.expr[idx].clone().unalias_nested().data;
721 Ok(Transformed::yes(input_expr))
722 }
723 _ => Ok(Transformed::no(expr)),
725 }
726 })
727}
728
729fn split_join_requirements(
760 left_len: usize,
761 right_len: usize,
762 indices: RequiredIndices,
763 join_type: &JoinType,
764) -> (RequiredIndices, RequiredIndices) {
765 match join_type {
766 JoinType::Inner | JoinType::Left | JoinType::Right | JoinType::Full => {
768 indices.split_off(left_len)
771 }
772 JoinType::LeftMark => {
773 let (left_indices, _mark) = indices.split_off(left_len);
777 (left_indices, RequiredIndices::new())
778 }
779 JoinType::RightMark => {
780 let (right_indices, _mark) = indices.split_off(right_len);
782 (RequiredIndices::new(), right_indices)
783 }
784 JoinType::LeftAnti | JoinType::LeftSemi => (indices, RequiredIndices::new()),
786 JoinType::RightSemi | JoinType::RightAnti => (RequiredIndices::new(), indices),
789 }
790}
791
792fn add_projection_on_top_if_helpful(
810 plan: LogicalPlan,
811 project_exprs: Vec<Expr>,
812) -> Result<Transformed<LogicalPlan>> {
813 if project_exprs.len() >= plan.schema().fields().len() {
815 Ok(Transformed::no(plan))
816 } else {
817 Projection::try_new(project_exprs, Arc::new(plan))
818 .map(LogicalPlan::Projection)
819 .map(Transformed::yes)
820 }
821}
822
823fn rewrite_projection_given_requirements(
841 proj: Projection,
842 config: &dyn OptimizerConfig,
843 indices: &RequiredIndices,
844) -> Result<Transformed<LogicalPlan>> {
845 let Projection { expr, input, .. } = proj;
846
847 let exprs_used = indices.get_at_indices(&expr);
848
849 let required_indices =
850 RequiredIndices::new().with_exprs(input.schema(), exprs_used.iter());
851
852 optimize_projections(Arc::unwrap_or_clone(input), config, required_indices)?
855 .transform_data(|input| {
856 if is_projection_unnecessary(&input, &exprs_used)? {
857 Ok(Transformed::yes(input))
858 } else {
859 Projection::try_new(exprs_used, Arc::new(input))
860 .map(LogicalPlan::Projection)
861 .map(Transformed::yes)
862 }
863 })
864}
865
866pub fn is_projection_unnecessary(
870 input: &LogicalPlan,
871 proj_exprs: &[Expr],
872) -> Result<bool> {
873 if proj_exprs.len() != input.schema().fields().len() {
875 return Ok(false);
876 }
877 Ok(input.schema().iter().zip(proj_exprs.iter()).all(
878 |((field_relation, field_name), expr)| {
879 if let Expr::Column(col) = expr {
881 col.relation.as_ref() == field_relation && col.name.eq(field_name.name())
882 } else {
883 false
884 }
885 },
886 ))
887}
888
889fn plan_contains_other_subqueries(plan: &LogicalPlan, cte_name: &str) -> bool {
895 if let LogicalPlan::SubqueryAlias(alias) = plan
896 && alias.alias.table() != cte_name
897 && !subquery_alias_targets_recursive_cte(alias.input.as_ref(), cte_name)
898 {
899 return true;
900 }
901
902 let mut found = false;
903 plan.apply_expressions(|expr| {
904 if expr_contains_subquery(expr) {
905 found = true;
906 Ok(TreeNodeRecursion::Stop)
907 } else {
908 Ok(TreeNodeRecursion::Continue)
909 }
910 })
911 .expect("expression traversal never fails");
912 if found {
913 return true;
914 }
915
916 plan.inputs()
917 .into_iter()
918 .any(|child| plan_contains_other_subqueries(child, cte_name))
919}
920
921fn expr_contains_subquery(expr: &Expr) -> bool {
922 expr.exists(|e| match e {
923 Expr::ScalarSubquery(_) | Expr::Exists(_) | Expr::InSubquery(_) => Ok(true),
924 _ => Ok(false),
925 })
926 .unwrap()
928}
929
930fn subquery_alias_targets_recursive_cte(plan: &LogicalPlan, cte_name: &str) -> bool {
931 match plan {
932 LogicalPlan::TableScan(scan) => scan.table_name.table() == cte_name,
933 LogicalPlan::SubqueryAlias(alias) => {
934 subquery_alias_targets_recursive_cte(alias.input.as_ref(), cte_name)
935 }
936 _ => {
937 let inputs = plan.inputs();
938 if inputs.len() == 1 {
939 subquery_alias_targets_recursive_cte(inputs[0], cte_name)
940 } else {
941 false
942 }
943 }
944 }
945}
946
947#[cfg(test)]
948mod tests {
949 use std::cmp::Ordering;
950 use std::collections::HashMap;
951 use std::fmt::Formatter;
952 use std::ops::Add;
953 use std::sync::Arc;
954 use std::vec;
955
956 use crate::optimize_projections::OptimizeProjections;
957 use crate::optimizer::Optimizer;
958 use crate::test::{
959 assert_fields_eq, scan_empty, test_table_scan, test_table_scan_fields,
960 test_table_scan_with_name,
961 };
962 use crate::{OptimizerContext, OptimizerRule};
963 use arrow::datatypes::{DataType, Field, Schema};
964 use datafusion_common::{
965 Column, DFSchema, DFSchemaRef, JoinType, Result, TableReference,
966 };
967 use datafusion_expr::ExprFunctionExt;
968 use datafusion_expr::{
969 BinaryExpr, Expr, Extension, Like, LogicalPlan, Operator, Projection,
970 UserDefinedLogicalNodeCore, WindowFunctionDefinition, binary_expr,
971 build_join_schema,
972 builder::table_scan_with_filters,
973 col,
974 expr::{self, Cast},
975 lit,
976 logical_plan::{builder::LogicalPlanBuilder, table_scan},
977 not, try_cast, when,
978 };
979 use insta::assert_snapshot;
980
981 use crate::assert_optimized_plan_eq_snapshot;
982 use datafusion_functions_aggregate::count::count_udaf;
983 use datafusion_functions_aggregate::expr_fn::{count, max, min};
984 use datafusion_functions_aggregate::min_max::max_udaf;
985
986 macro_rules! assert_optimized_plan_equal {
987 (
988 $plan:expr,
989 @ $expected:literal $(,)?
990 ) => {{
991 let optimizer_ctx = OptimizerContext::new().with_max_passes(1);
992 let rules: Vec<Arc<dyn crate::OptimizerRule + Send + Sync>> = vec![Arc::new(OptimizeProjections::new())];
993 assert_optimized_plan_eq_snapshot!(
994 optimizer_ctx,
995 rules,
996 $plan,
997 @ $expected,
998 )
999 }};
1000 }
1001
1002 #[derive(Debug, Hash, PartialEq, Eq)]
1003 struct NoOpUserDefined {
1004 exprs: Vec<Expr>,
1005 schema: DFSchemaRef,
1006 input: Arc<LogicalPlan>,
1007 }
1008
1009 impl NoOpUserDefined {
1010 fn new(schema: DFSchemaRef, input: Arc<LogicalPlan>) -> Self {
1011 Self {
1012 exprs: vec![],
1013 schema,
1014 input,
1015 }
1016 }
1017
1018 fn with_exprs(mut self, exprs: Vec<Expr>) -> Self {
1019 self.exprs = exprs;
1020 self
1021 }
1022 }
1023
1024 impl PartialOrd for NoOpUserDefined {
1026 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1027 match self.exprs.partial_cmp(&other.exprs) {
1028 Some(Ordering::Equal) => self.input.partial_cmp(&other.input),
1029 cmp => cmp,
1030 }
1031 .filter(|cmp| *cmp != Ordering::Equal || self == other)
1033 }
1034 }
1035
1036 impl UserDefinedLogicalNodeCore for NoOpUserDefined {
1037 fn name(&self) -> &str {
1038 "NoOpUserDefined"
1039 }
1040
1041 fn inputs(&self) -> Vec<&LogicalPlan> {
1042 vec![&self.input]
1043 }
1044
1045 fn schema(&self) -> &DFSchemaRef {
1046 &self.schema
1047 }
1048
1049 fn expressions(&self) -> Vec<Expr> {
1050 self.exprs.clone()
1051 }
1052
1053 fn fmt_for_explain(&self, f: &mut Formatter) -> std::fmt::Result {
1054 write!(f, "NoOpUserDefined")
1055 }
1056
1057 fn with_exprs_and_inputs(
1058 &self,
1059 exprs: Vec<Expr>,
1060 mut inputs: Vec<LogicalPlan>,
1061 ) -> Result<Self> {
1062 Ok(Self {
1063 exprs,
1064 input: Arc::new(inputs.swap_remove(0)),
1065 schema: Arc::clone(&self.schema),
1066 })
1067 }
1068
1069 fn necessary_children_exprs(
1070 &self,
1071 output_columns: &[usize],
1072 ) -> Option<Vec<Vec<usize>>> {
1073 Some(vec![output_columns.to_vec()])
1075 }
1076
1077 fn supports_limit_pushdown(&self) -> bool {
1078 false }
1080 }
1081
1082 #[derive(Debug, Hash, PartialEq, Eq)]
1083 struct UserDefinedCrossJoin {
1084 exprs: Vec<Expr>,
1085 schema: DFSchemaRef,
1086 left_child: Arc<LogicalPlan>,
1087 right_child: Arc<LogicalPlan>,
1088 }
1089
1090 impl UserDefinedCrossJoin {
1091 fn new(left_child: Arc<LogicalPlan>, right_child: Arc<LogicalPlan>) -> Self {
1092 let left_schema = left_child.schema();
1093 let right_schema = right_child.schema();
1094 let schema = Arc::new(
1095 build_join_schema(left_schema, right_schema, &JoinType::Inner).unwrap(),
1096 );
1097 Self {
1098 exprs: vec![],
1099 schema,
1100 left_child,
1101 right_child,
1102 }
1103 }
1104 }
1105
1106 impl PartialOrd for UserDefinedCrossJoin {
1108 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1109 match self.exprs.partial_cmp(&other.exprs) {
1110 Some(Ordering::Equal) => {
1111 match self.left_child.partial_cmp(&other.left_child) {
1112 Some(Ordering::Equal) => {
1113 self.right_child.partial_cmp(&other.right_child)
1114 }
1115 cmp => cmp,
1116 }
1117 }
1118 cmp => cmp,
1119 }
1120 .filter(|cmp| *cmp != Ordering::Equal || self == other)
1122 }
1123 }
1124
1125 impl UserDefinedLogicalNodeCore for UserDefinedCrossJoin {
1126 fn name(&self) -> &str {
1127 "UserDefinedCrossJoin"
1128 }
1129
1130 fn inputs(&self) -> Vec<&LogicalPlan> {
1131 vec![&self.left_child, &self.right_child]
1132 }
1133
1134 fn schema(&self) -> &DFSchemaRef {
1135 &self.schema
1136 }
1137
1138 fn expressions(&self) -> Vec<Expr> {
1139 self.exprs.clone()
1140 }
1141
1142 fn fmt_for_explain(&self, f: &mut Formatter) -> std::fmt::Result {
1143 write!(f, "UserDefinedCrossJoin")
1144 }
1145
1146 fn with_exprs_and_inputs(
1147 &self,
1148 exprs: Vec<Expr>,
1149 mut inputs: Vec<LogicalPlan>,
1150 ) -> Result<Self> {
1151 assert_eq!(inputs.len(), 2);
1152 Ok(Self {
1153 exprs,
1154 left_child: Arc::new(inputs.remove(0)),
1155 right_child: Arc::new(inputs.remove(0)),
1156 schema: Arc::clone(&self.schema),
1157 })
1158 }
1159
1160 fn necessary_children_exprs(
1161 &self,
1162 output_columns: &[usize],
1163 ) -> Option<Vec<Vec<usize>>> {
1164 let left_child_len = self.left_child.schema().fields().len();
1165 let mut left_reqs = vec![];
1166 let mut right_reqs = vec![];
1167 for &out_idx in output_columns {
1168 if out_idx < left_child_len {
1169 left_reqs.push(out_idx);
1170 } else {
1171 right_reqs.push(out_idx - left_child_len)
1174 }
1175 }
1176 Some(vec![left_reqs, right_reqs])
1177 }
1178
1179 fn supports_limit_pushdown(&self) -> bool {
1180 false }
1182 }
1183
1184 #[derive(Debug, Hash, PartialEq, Eq)]
1188 struct OpaqueRequirementsUserDefined {
1189 input: Arc<LogicalPlan>,
1190 schema: DFSchemaRef,
1191 }
1192
1193 impl PartialOrd for OpaqueRequirementsUserDefined {
1195 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1196 self.input
1197 .partial_cmp(&other.input)
1198 .filter(|cmp| *cmp != Ordering::Equal || self == other)
1199 }
1200 }
1201
1202 impl UserDefinedLogicalNodeCore for OpaqueRequirementsUserDefined {
1203 fn name(&self) -> &str {
1204 "OpaqueRequirementsUserDefined"
1205 }
1206
1207 fn inputs(&self) -> Vec<&LogicalPlan> {
1208 vec![&self.input]
1209 }
1210
1211 fn schema(&self) -> &DFSchemaRef {
1212 &self.schema
1213 }
1214
1215 fn expressions(&self) -> Vec<Expr> {
1216 vec![]
1217 }
1218
1219 fn with_exprs_and_inputs(
1220 &self,
1221 _exprs: Vec<Expr>,
1222 mut inputs: Vec<LogicalPlan>,
1223 ) -> Result<Self> {
1224 Ok(Self {
1225 input: Arc::new(inputs.swap_remove(0)),
1226 schema: Arc::clone(&self.schema),
1227 })
1228 }
1229
1230 fn fmt_for_explain(&self, f: &mut Formatter) -> std::fmt::Result {
1231 write!(f, "OpaqueRequirementsUserDefined")
1232 }
1233 }
1234
1235 #[test]
1236 fn merge_two_projection() -> Result<()> {
1237 let table_scan = test_table_scan()?;
1238 let plan = LogicalPlanBuilder::from(table_scan)
1239 .project(vec![col("a")])?
1240 .project(vec![binary_expr(lit(1), Operator::Plus, col("a"))])?
1241 .build()?;
1242
1243 assert_optimized_plan_equal!(
1244 plan,
1245 @r"
1246 Projection: Int32(1) + test.a
1247 TableScan: test projection=[a]
1248 "
1249 )
1250 }
1251
1252 #[test]
1253 fn merge_three_projection() -> Result<()> {
1254 let table_scan = test_table_scan()?;
1255 let plan = LogicalPlanBuilder::from(table_scan)
1256 .project(vec![col("a"), col("b")])?
1257 .project(vec![col("a")])?
1258 .project(vec![binary_expr(lit(1), Operator::Plus, col("a"))])?
1259 .build()?;
1260
1261 assert_optimized_plan_equal!(
1262 plan,
1263 @r"
1264 Projection: Int32(1) + test.a
1265 TableScan: test projection=[a]
1266 "
1267 )
1268 }
1269
1270 #[test]
1271 fn merge_alias() -> Result<()> {
1272 let table_scan = test_table_scan()?;
1273 let plan = LogicalPlanBuilder::from(table_scan)
1274 .project(vec![col("a")])?
1275 .project(vec![col("a").alias("alias")])?
1276 .build()?;
1277
1278 assert_optimized_plan_equal!(
1279 plan,
1280 @r"
1281 Projection: test.a AS alias
1282 TableScan: test projection=[a]
1283 "
1284 )
1285 }
1286
1287 #[test]
1288 fn merge_nested_alias() -> Result<()> {
1289 let table_scan = test_table_scan()?;
1290 let plan = LogicalPlanBuilder::from(table_scan)
1291 .project(vec![col("a").alias("alias1").alias("alias2")])?
1292 .project(vec![col("alias2").alias("alias")])?
1293 .build()?;
1294
1295 assert_optimized_plan_equal!(
1296 plan,
1297 @r"
1298 Projection: test.a AS alias
1299 TableScan: test projection=[a]
1300 "
1301 )
1302 }
1303
1304 #[test]
1305 fn test_nested_count() -> Result<()> {
1306 let schema = Schema::new(vec![Field::new("foo", DataType::Int32, false)]);
1307
1308 let groups: Vec<Expr> = vec![];
1309
1310 let plan = table_scan(TableReference::none(), &schema, None)
1311 .unwrap()
1312 .aggregate(groups.clone(), vec![count(lit(1))])
1313 .unwrap()
1314 .aggregate(groups, vec![count(lit(1))])
1315 .unwrap()
1316 .build()
1317 .unwrap();
1318
1319 assert_optimized_plan_equal!(
1320 plan,
1321 @r"
1322 Aggregate: groupBy=[[]], aggr=[[count(Int32(1))]]
1323 EmptyRelation: rows=1
1324 "
1325 )
1326 }
1327
1328 #[test]
1329 fn test_neg_push_down() -> Result<()> {
1330 let table_scan = test_table_scan()?;
1331 let plan = LogicalPlanBuilder::from(table_scan)
1332 .project(vec![-col("a")])?
1333 .build()?;
1334
1335 assert_optimized_plan_equal!(
1336 plan,
1337 @r"
1338 Projection: (- test.a)
1339 TableScan: test projection=[a]
1340 "
1341 )
1342 }
1343
1344 #[test]
1345 fn test_is_null() -> Result<()> {
1346 let table_scan = test_table_scan()?;
1347 let plan = LogicalPlanBuilder::from(table_scan)
1348 .project(vec![col("a").is_null()])?
1349 .build()?;
1350
1351 assert_optimized_plan_equal!(
1352 plan,
1353 @r"
1354 Projection: test.a IS NULL
1355 TableScan: test projection=[a]
1356 "
1357 )
1358 }
1359
1360 #[test]
1361 fn test_is_not_null() -> Result<()> {
1362 let table_scan = test_table_scan()?;
1363 let plan = LogicalPlanBuilder::from(table_scan)
1364 .project(vec![col("a").is_not_null()])?
1365 .build()?;
1366
1367 assert_optimized_plan_equal!(
1368 plan,
1369 @r"
1370 Projection: test.a IS NOT NULL
1371 TableScan: test projection=[a]
1372 "
1373 )
1374 }
1375
1376 #[test]
1377 fn test_is_true() -> Result<()> {
1378 let table_scan = test_table_scan()?;
1379 let plan = LogicalPlanBuilder::from(table_scan)
1380 .project(vec![col("a").is_true()])?
1381 .build()?;
1382
1383 assert_optimized_plan_equal!(
1384 plan,
1385 @r"
1386 Projection: test.a IS TRUE
1387 TableScan: test projection=[a]
1388 "
1389 )
1390 }
1391
1392 #[test]
1393 fn test_is_not_true() -> Result<()> {
1394 let table_scan = test_table_scan()?;
1395 let plan = LogicalPlanBuilder::from(table_scan)
1396 .project(vec![col("a").is_not_true()])?
1397 .build()?;
1398
1399 assert_optimized_plan_equal!(
1400 plan,
1401 @r"
1402 Projection: test.a IS NOT TRUE
1403 TableScan: test projection=[a]
1404 "
1405 )
1406 }
1407
1408 #[test]
1409 fn test_is_false() -> Result<()> {
1410 let table_scan = test_table_scan()?;
1411 let plan = LogicalPlanBuilder::from(table_scan)
1412 .project(vec![col("a").is_false()])?
1413 .build()?;
1414
1415 assert_optimized_plan_equal!(
1416 plan,
1417 @r"
1418 Projection: test.a IS FALSE
1419 TableScan: test projection=[a]
1420 "
1421 )
1422 }
1423
1424 #[test]
1425 fn test_is_not_false() -> Result<()> {
1426 let table_scan = test_table_scan()?;
1427 let plan = LogicalPlanBuilder::from(table_scan)
1428 .project(vec![col("a").is_not_false()])?
1429 .build()?;
1430
1431 assert_optimized_plan_equal!(
1432 plan,
1433 @r"
1434 Projection: test.a IS NOT FALSE
1435 TableScan: test projection=[a]
1436 "
1437 )
1438 }
1439
1440 #[test]
1441 fn test_is_unknown() -> Result<()> {
1442 let table_scan = test_table_scan()?;
1443 let plan = LogicalPlanBuilder::from(table_scan)
1444 .project(vec![col("a").is_unknown()])?
1445 .build()?;
1446
1447 assert_optimized_plan_equal!(
1448 plan,
1449 @r"
1450 Projection: test.a IS UNKNOWN
1451 TableScan: test projection=[a]
1452 "
1453 )
1454 }
1455
1456 #[test]
1457 fn test_is_not_unknown() -> Result<()> {
1458 let table_scan = test_table_scan()?;
1459 let plan = LogicalPlanBuilder::from(table_scan)
1460 .project(vec![col("a").is_not_unknown()])?
1461 .build()?;
1462
1463 assert_optimized_plan_equal!(
1464 plan,
1465 @r"
1466 Projection: test.a IS NOT UNKNOWN
1467 TableScan: test projection=[a]
1468 "
1469 )
1470 }
1471
1472 #[test]
1473 fn test_not() -> Result<()> {
1474 let table_scan = test_table_scan()?;
1475 let plan = LogicalPlanBuilder::from(table_scan)
1476 .project(vec![not(col("a"))])?
1477 .build()?;
1478
1479 assert_optimized_plan_equal!(
1480 plan,
1481 @r"
1482 Projection: NOT test.a
1483 TableScan: test projection=[a]
1484 "
1485 )
1486 }
1487
1488 #[test]
1489 fn test_try_cast() -> Result<()> {
1490 let table_scan = test_table_scan()?;
1491 let plan = LogicalPlanBuilder::from(table_scan)
1492 .project(vec![try_cast(col("a"), DataType::Float64)])?
1493 .build()?;
1494
1495 assert_optimized_plan_equal!(
1496 plan,
1497 @r"
1498 Projection: TRY_CAST(test.a AS Float64)
1499 TableScan: test projection=[a]
1500 "
1501 )
1502 }
1503
1504 #[test]
1505 fn test_similar_to() -> Result<()> {
1506 let table_scan = test_table_scan()?;
1507 let expr = Box::new(col("a"));
1508 let pattern = Box::new(lit("[0-9]"));
1509 let similar_to_expr =
1510 Expr::SimilarTo(Like::new(false, expr, pattern, None, false));
1511 let plan = LogicalPlanBuilder::from(table_scan)
1512 .project(vec![similar_to_expr])?
1513 .build()?;
1514
1515 assert_optimized_plan_equal!(
1516 plan,
1517 @r#"
1518 Projection: test.a SIMILAR TO Utf8("[0-9]")
1519 TableScan: test projection=[a]
1520 "#
1521 )
1522 }
1523
1524 #[test]
1525 fn test_between() -> Result<()> {
1526 let table_scan = test_table_scan()?;
1527 let plan = LogicalPlanBuilder::from(table_scan)
1528 .project(vec![col("a").between(lit(1), lit(3))])?
1529 .build()?;
1530
1531 assert_optimized_plan_equal!(
1532 plan,
1533 @r"
1534 Projection: test.a BETWEEN Int32(1) AND Int32(3)
1535 TableScan: test projection=[a]
1536 "
1537 )
1538 }
1539
1540 #[test]
1542 fn test_case_merged() -> Result<()> {
1543 let table_scan = test_table_scan()?;
1544 let plan = LogicalPlanBuilder::from(table_scan)
1545 .project(vec![col("a"), lit(0).alias("d")])?
1546 .project(vec![
1547 col("a"),
1548 when(col("a").eq(lit(1)), lit(10))
1549 .otherwise(col("d"))?
1550 .alias("d"),
1551 ])?
1552 .build()?;
1553
1554 assert_optimized_plan_equal!(
1555 plan,
1556 @r"
1557 Projection: test.a, CASE WHEN test.a = Int32(1) THEN Int32(10) ELSE Int32(0) END AS d
1558 TableScan: test projection=[a]
1559 "
1560 )
1561 }
1562
1563 #[test]
1566 fn test_derived_column() -> Result<()> {
1567 let table_scan = test_table_scan()?;
1568 let plan = LogicalPlanBuilder::from(table_scan)
1569 .project(vec![col("a").add(lit(1)).alias("a"), lit(0).alias("d")])?
1570 .project(vec![
1571 col("a"),
1572 when(col("a").eq(lit(1)), lit(10))
1573 .otherwise(col("d"))?
1574 .alias("d"),
1575 ])?
1576 .build()?;
1577
1578 assert_optimized_plan_equal!(
1579 plan,
1580 @r"
1581 Projection: a, CASE WHEN a = Int32(1) THEN Int32(10) ELSE d END AS d
1582 Projection: test.a + Int32(1) AS a, Int32(0) AS d
1583 TableScan: test projection=[a]
1584 "
1585 )
1586 }
1587
1588 #[test]
1591 fn test_user_defined_logical_plan_node() -> Result<()> {
1592 let table_scan = test_table_scan()?;
1593 let custom_plan = LogicalPlan::Extension(Extension {
1594 node: Arc::new(NoOpUserDefined::new(
1595 Arc::clone(table_scan.schema()),
1596 Arc::new(table_scan.clone()),
1597 )),
1598 });
1599 let plan = LogicalPlanBuilder::from(custom_plan)
1600 .project(vec![col("a"), lit(0).alias("d")])?
1601 .build()?;
1602
1603 assert_optimized_plan_equal!(
1604 plan,
1605 @r"
1606 Projection: test.a, Int32(0) AS d
1607 NoOpUserDefined
1608 TableScan: test projection=[a]
1609 "
1610 )
1611 }
1612
1613 #[test]
1618 fn test_user_defined_logical_plan_node2() -> Result<()> {
1619 let table_scan = test_table_scan()?;
1620 let exprs = vec![Expr::Column(Column::from_qualified_name("b"))];
1621 let custom_plan = LogicalPlan::Extension(Extension {
1622 node: Arc::new(
1623 NoOpUserDefined::new(
1624 Arc::clone(table_scan.schema()),
1625 Arc::new(table_scan.clone()),
1626 )
1627 .with_exprs(exprs),
1628 ),
1629 });
1630 let plan = LogicalPlanBuilder::from(custom_plan)
1631 .project(vec![col("a"), lit(0).alias("d")])?
1632 .build()?;
1633
1634 assert_optimized_plan_equal!(
1635 plan,
1636 @r"
1637 Projection: test.a, Int32(0) AS d
1638 NoOpUserDefined
1639 TableScan: test projection=[a, b]
1640 "
1641 )
1642 }
1643
1644 #[test]
1650 fn test_user_defined_logical_plan_node3() -> Result<()> {
1651 let table_scan = test_table_scan()?;
1652 let left_expr = Expr::Column(Column::from_qualified_name("b"));
1653 let right_expr = Expr::Column(Column::from_qualified_name("c"));
1654 let binary_expr = Expr::BinaryExpr(BinaryExpr::new(
1655 Box::new(left_expr),
1656 Operator::Plus,
1657 Box::new(right_expr),
1658 ));
1659 let exprs = vec![binary_expr];
1660 let custom_plan = LogicalPlan::Extension(Extension {
1661 node: Arc::new(
1662 NoOpUserDefined::new(
1663 Arc::clone(table_scan.schema()),
1664 Arc::new(table_scan.clone()),
1665 )
1666 .with_exprs(exprs),
1667 ),
1668 });
1669 let plan = LogicalPlanBuilder::from(custom_plan)
1670 .project(vec![col("a"), lit(0).alias("d")])?
1671 .build()?;
1672
1673 assert_optimized_plan_equal!(
1674 plan,
1675 @r"
1676 Projection: test.a, Int32(0) AS d
1677 NoOpUserDefined
1678 TableScan: test projection=[a, b, c]
1679 "
1680 )
1681 }
1682
1683 #[test]
1688 fn test_user_defined_logical_plan_node4() -> Result<()> {
1689 let left_table = test_table_scan_with_name("l")?;
1690 let right_table = test_table_scan_with_name("r")?;
1691 let custom_plan = LogicalPlan::Extension(Extension {
1692 node: Arc::new(UserDefinedCrossJoin::new(
1693 Arc::new(left_table),
1694 Arc::new(right_table),
1695 )),
1696 });
1697 let plan = LogicalPlanBuilder::from(custom_plan)
1698 .project(vec![col("l.a"), col("l.c"), col("r.a"), lit(0).alias("d")])?
1699 .build()?;
1700
1701 assert_optimized_plan_equal!(
1702 plan,
1703 @r"
1704 Projection: l.a, l.c, r.a, Int32(0) AS d
1705 UserDefinedCrossJoin
1706 TableScan: l projection=[a, c]
1707 TableScan: r projection=[a]
1708 "
1709 )
1710 }
1711
1712 #[test]
1713 fn aggregate_no_group_by() -> Result<()> {
1714 let table_scan = test_table_scan()?;
1715
1716 let plan = LogicalPlanBuilder::from(table_scan)
1717 .aggregate(Vec::<Expr>::new(), vec![max(col("b"))])?
1718 .build()?;
1719
1720 assert_optimized_plan_equal!(
1721 plan,
1722 @r"
1723 Aggregate: groupBy=[[]], aggr=[[max(test.b)]]
1724 TableScan: test projection=[b]
1725 "
1726 )
1727 }
1728
1729 #[test]
1730 fn aggregate_group_by() -> Result<()> {
1731 let table_scan = test_table_scan()?;
1732
1733 let plan = LogicalPlanBuilder::from(table_scan)
1734 .aggregate(vec![col("c")], vec![max(col("b"))])?
1735 .build()?;
1736
1737 assert_optimized_plan_equal!(
1738 plan,
1739 @r"
1740 Aggregate: groupBy=[[test.c]], aggr=[[max(test.b)]]
1741 TableScan: test projection=[b, c]
1742 "
1743 )
1744 }
1745
1746 #[test]
1747 fn aggregate_group_by_with_table_alias() -> Result<()> {
1748 let table_scan = test_table_scan()?;
1749
1750 let plan = LogicalPlanBuilder::from(table_scan)
1751 .alias("a")?
1752 .aggregate(vec![col("c")], vec![max(col("b"))])?
1753 .build()?;
1754
1755 assert_optimized_plan_equal!(
1756 plan,
1757 @r"
1758 Aggregate: groupBy=[[a.c]], aggr=[[max(a.b)]]
1759 SubqueryAlias: a
1760 TableScan: test projection=[b, c]
1761 "
1762 )
1763 }
1764
1765 #[test]
1766 fn aggregate_no_group_by_with_filter() -> Result<()> {
1767 let table_scan = test_table_scan()?;
1768
1769 let plan = LogicalPlanBuilder::from(table_scan)
1770 .filter(col("c").gt(lit(1)))?
1771 .aggregate(Vec::<Expr>::new(), vec![max(col("b"))])?
1772 .build()?;
1773
1774 assert_optimized_plan_equal!(
1775 plan,
1776 @r"
1777 Aggregate: groupBy=[[]], aggr=[[max(test.b)]]
1778 Projection: test.b
1779 Filter: test.c > Int32(1)
1780 TableScan: test projection=[b, c]
1781 "
1782 )
1783 }
1784
1785 #[test]
1786 fn aggregate_with_periods() -> Result<()> {
1787 let schema = Schema::new(vec![Field::new("tag.one", DataType::Utf8, false)]);
1788
1789 let plan = table_scan(Some("m4"), &schema, None)?
1796 .aggregate(
1797 Vec::<Expr>::new(),
1798 vec![max(col(Column::new_unqualified("tag.one"))).alias("tag.one")],
1799 )?
1800 .project([col(Column::new_unqualified("tag.one"))])?
1801 .build()?;
1802
1803 assert_optimized_plan_equal!(
1804 plan,
1805 @r"
1806 Aggregate: groupBy=[[]], aggr=[[max(m4.tag.one) AS tag.one]]
1807 TableScan: m4 projection=[tag.one]
1808 "
1809 )
1810 }
1811
1812 #[test]
1813 fn redundant_project() -> Result<()> {
1814 let table_scan = test_table_scan()?;
1815
1816 let plan = LogicalPlanBuilder::from(table_scan)
1817 .project(vec![col("a"), col("b"), col("c")])?
1818 .project(vec![col("a"), col("c"), col("b")])?
1819 .build()?;
1820 assert_optimized_plan_equal!(
1821 plan,
1822 @r"
1823 Projection: test.a, test.c, test.b
1824 TableScan: test projection=[a, b, c]
1825 "
1826 )
1827 }
1828
1829 #[test]
1830 fn reorder_scan() -> Result<()> {
1831 let schema = Schema::new(test_table_scan_fields());
1832
1833 let plan = table_scan(Some("test"), &schema, Some(vec![1, 0, 2]))?.build()?;
1834 assert_optimized_plan_equal!(
1835 plan,
1836 @"TableScan: test projection=[b, a, c]"
1837 )
1838 }
1839
1840 #[test]
1841 fn reorder_scan_projection() -> Result<()> {
1842 let schema = Schema::new(test_table_scan_fields());
1843
1844 let plan = table_scan(Some("test"), &schema, Some(vec![1, 0, 2]))?
1845 .project(vec![col("a"), col("b")])?
1846 .build()?;
1847 assert_optimized_plan_equal!(
1848 plan,
1849 @r"
1850 Projection: test.a, test.b
1851 TableScan: test projection=[b, a]
1852 "
1853 )
1854 }
1855
1856 #[test]
1857 fn reorder_projection() -> Result<()> {
1858 let table_scan = test_table_scan()?;
1859
1860 let plan = LogicalPlanBuilder::from(table_scan)
1861 .project(vec![col("c"), col("b"), col("a")])?
1862 .build()?;
1863 assert_optimized_plan_equal!(
1864 plan,
1865 @r"
1866 Projection: test.c, test.b, test.a
1867 TableScan: test projection=[a, b, c]
1868 "
1869 )
1870 }
1871
1872 #[test]
1873 fn noncontinuous_redundant_projection() -> Result<()> {
1874 let table_scan = test_table_scan()?;
1875
1876 let plan = LogicalPlanBuilder::from(table_scan)
1877 .project(vec![col("c"), col("b"), col("a")])?
1878 .filter(col("c").gt(lit(1)))?
1879 .project(vec![col("c"), col("a"), col("b")])?
1880 .filter(col("b").gt(lit(1)))?
1881 .filter(col("a").gt(lit(1)))?
1882 .project(vec![col("a"), col("c"), col("b")])?
1883 .build()?;
1884 assert_optimized_plan_equal!(
1885 plan,
1886 @r"
1887 Projection: test.a, test.c, test.b
1888 Filter: test.a > Int32(1)
1889 Filter: test.b > Int32(1)
1890 Projection: test.c, test.a, test.b
1891 Filter: test.c > Int32(1)
1892 Projection: test.c, test.b, test.a
1893 TableScan: test projection=[a, b, c]
1894 "
1895 )
1896 }
1897
1898 #[test]
1899 fn join_schema_trim_full_join_column_projection() -> Result<()> {
1900 let table_scan = test_table_scan()?;
1901
1902 let schema = Schema::new(vec![Field::new("c1", DataType::UInt32, false)]);
1903 let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
1904
1905 let plan = LogicalPlanBuilder::from(table_scan)
1906 .join(table2_scan, JoinType::Left, (vec!["a"], vec!["c1"]), None)?
1907 .project(vec![col("a"), col("b"), col("c1")])?
1908 .build()?;
1909
1910 let optimized_plan = optimize(plan)?;
1911
1912 assert_snapshot!(
1914 optimized_plan.clone(),
1915 @r"
1916 Left Join: test.a = test2.c1
1917 TableScan: test projection=[a, b]
1918 TableScan: test2 projection=[c1]
1919 "
1920 );
1921
1922 let optimized_join = optimized_plan;
1924 assert_eq!(
1925 **optimized_join.schema(),
1926 DFSchema::new_with_metadata(
1927 vec![
1928 (
1929 Some("test".into()),
1930 Arc::new(Field::new("a", DataType::UInt32, false))
1931 ),
1932 (
1933 Some("test".into()),
1934 Arc::new(Field::new("b", DataType::UInt32, false))
1935 ),
1936 (
1937 Some("test2".into()),
1938 Arc::new(Field::new("c1", DataType::UInt32, true))
1939 ),
1940 ],
1941 HashMap::new()
1942 )?,
1943 );
1944
1945 Ok(())
1946 }
1947
1948 #[test]
1949 fn join_schema_trim_partial_join_column_projection() -> Result<()> {
1950 let table_scan = test_table_scan()?;
1953
1954 let schema = Schema::new(vec![Field::new("c1", DataType::UInt32, false)]);
1955 let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
1956
1957 let plan = LogicalPlanBuilder::from(table_scan)
1958 .join(table2_scan, JoinType::Left, (vec!["a"], vec!["c1"]), None)?
1959 .project(vec![col("a"), col("b")])?
1962 .build()?;
1963
1964 let optimized_plan = optimize(plan)?;
1965
1966 assert_snapshot!(
1968 optimized_plan.clone(),
1969 @r"
1970 Projection: test.a, test.b
1971 Left Join: test.a = test2.c1
1972 TableScan: test projection=[a, b]
1973 TableScan: test2 projection=[c1]
1974 "
1975 );
1976
1977 let optimized_join = optimized_plan.inputs()[0];
1979 assert_eq!(
1980 **optimized_join.schema(),
1981 DFSchema::new_with_metadata(
1982 vec![
1983 (
1984 Some("test".into()),
1985 Arc::new(Field::new("a", DataType::UInt32, false))
1986 ),
1987 (
1988 Some("test".into()),
1989 Arc::new(Field::new("b", DataType::UInt32, false))
1990 ),
1991 (
1992 Some("test2".into()),
1993 Arc::new(Field::new("c1", DataType::UInt32, true))
1994 ),
1995 ],
1996 HashMap::new()
1997 )?,
1998 );
1999
2000 Ok(())
2001 }
2002
2003 #[test]
2004 fn join_schema_trim_using_join() -> Result<()> {
2005 let table_scan = test_table_scan()?;
2008
2009 let schema = Schema::new(vec![Field::new("a", DataType::UInt32, false)]);
2010 let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
2011
2012 let plan = LogicalPlanBuilder::from(table_scan)
2013 .join_using(table2_scan, JoinType::Left, vec!["a".into()])?
2014 .project(vec![col("a"), col("b")])?
2015 .build()?;
2016
2017 let optimized_plan = optimize(plan)?;
2018
2019 assert_snapshot!(
2021 optimized_plan.clone(),
2022 @r"
2023 Projection: test.a, test.b
2024 Left Join: Using test.a = test2.a
2025 TableScan: test projection=[a, b]
2026 TableScan: test2 projection=[a]
2027 "
2028 );
2029
2030 let optimized_join = optimized_plan.inputs()[0];
2032 assert_eq!(
2033 **optimized_join.schema(),
2034 DFSchema::new_with_metadata(
2035 vec![
2036 (
2037 Some("test".into()),
2038 Arc::new(Field::new("a", DataType::UInt32, false))
2039 ),
2040 (
2041 Some("test".into()),
2042 Arc::new(Field::new("b", DataType::UInt32, false))
2043 ),
2044 (
2045 Some("test2".into()),
2046 Arc::new(Field::new("a", DataType::UInt32, true))
2047 ),
2048 ],
2049 HashMap::new()
2050 )?,
2051 );
2052
2053 Ok(())
2054 }
2055
2056 #[test]
2057 fn cast() -> Result<()> {
2058 let table_scan = test_table_scan()?;
2059
2060 let plan = LogicalPlanBuilder::from(table_scan)
2061 .project(vec![Expr::Cast(Cast::new(
2062 Box::new(col("c")),
2063 DataType::Float64,
2064 ))])?
2065 .build()?;
2066
2067 assert_optimized_plan_equal!(
2068 plan,
2069 @r"
2070 Projection: CAST(test.c AS Float64)
2071 TableScan: test projection=[c]
2072 "
2073 )
2074 }
2075
2076 #[test]
2077 fn table_scan_projected_schema() -> Result<()> {
2078 let table_scan = test_table_scan()?;
2079 let plan = LogicalPlanBuilder::from(test_table_scan()?)
2080 .project(vec![col("a"), col("b")])?
2081 .build()?;
2082
2083 assert_eq!(3, table_scan.schema().fields().len());
2084 assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2085 assert_fields_eq(&plan, vec!["a", "b"]);
2086
2087 assert_optimized_plan_equal!(
2088 plan,
2089 @"TableScan: test projection=[a, b]"
2090 )
2091 }
2092
2093 #[test]
2094 fn table_scan_projected_schema_non_qualified_relation() -> Result<()> {
2095 let table_scan = test_table_scan()?;
2096 let input_schema = table_scan.schema();
2097 assert_eq!(3, input_schema.fields().len());
2098 assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2099
2100 let expr = vec![col("test.a"), col("test.b")];
2104 let plan =
2105 LogicalPlan::Projection(Projection::try_new(expr, Arc::new(table_scan))?);
2106
2107 assert_fields_eq(&plan, vec!["a", "b"]);
2108
2109 assert_optimized_plan_equal!(
2110 plan,
2111 @"TableScan: test projection=[a, b]"
2112 )
2113 }
2114
2115 #[test]
2116 fn table_limit() -> Result<()> {
2117 let table_scan = test_table_scan()?;
2118 assert_eq!(3, table_scan.schema().fields().len());
2119 assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2120
2121 let plan = LogicalPlanBuilder::from(table_scan)
2122 .project(vec![col("c"), col("a")])?
2123 .limit(0, Some(5))?
2124 .build()?;
2125
2126 assert_fields_eq(&plan, vec!["c", "a"]);
2127
2128 assert_optimized_plan_equal!(
2129 plan,
2130 @r"
2131 Limit: skip=0, fetch=5
2132 Projection: test.c, test.a
2133 TableScan: test projection=[a, c]
2134 "
2135 )
2136 }
2137
2138 #[test]
2139 fn table_scan_without_projection() -> Result<()> {
2140 let table_scan = test_table_scan()?;
2141 let plan = LogicalPlanBuilder::from(table_scan).build()?;
2142 assert_optimized_plan_equal!(
2144 plan,
2145 @"TableScan: test projection=[a, b, c]"
2146 )
2147 }
2148
2149 #[test]
2150 fn table_scan_with_literal_projection() -> Result<()> {
2151 let table_scan = test_table_scan()?;
2152 let plan = LogicalPlanBuilder::from(table_scan)
2153 .project(vec![lit(1_i64), lit(2_i64)])?
2154 .build()?;
2155 assert_optimized_plan_equal!(
2156 plan,
2157 @r"
2158 Projection: Int64(1), Int64(2)
2159 TableScan: test projection=[]
2160 "
2161 )
2162 }
2163
2164 #[test]
2166 fn table_unused_column() -> Result<()> {
2167 let table_scan = test_table_scan()?;
2168 assert_eq!(3, table_scan.schema().fields().len());
2169 assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2170
2171 let plan = LogicalPlanBuilder::from(table_scan)
2173 .project(vec![col("c"), col("a"), col("b")])?
2174 .filter(col("c").gt(lit(1)))?
2175 .aggregate(vec![col("c")], vec![max(col("a"))])?
2176 .build()?;
2177
2178 assert_fields_eq(&plan, vec!["c", "max(test.a)"]);
2179
2180 let plan = optimize(plan).expect("failed to optimize plan");
2181 assert_optimized_plan_equal!(
2182 plan,
2183 @r"
2184 Aggregate: groupBy=[[test.c]], aggr=[[max(test.a)]]
2185 Filter: test.c > Int32(1)
2186 Projection: test.c, test.a
2187 TableScan: test projection=[a, c]
2188 "
2189 )
2190 }
2191
2192 #[test]
2194 fn table_unused_projection() -> Result<()> {
2195 let table_scan = test_table_scan()?;
2196 assert_eq!(3, table_scan.schema().fields().len());
2197 assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2198
2199 let plan = LogicalPlanBuilder::from(table_scan)
2201 .project(vec![col("b")])?
2202 .project(vec![lit(1).alias("a")])?
2203 .build()?;
2204
2205 assert_fields_eq(&plan, vec!["a"]);
2206
2207 assert_optimized_plan_equal!(
2208 plan,
2209 @r"
2210 Projection: Int32(1) AS a
2211 TableScan: test projection=[]
2212 "
2213 )
2214 }
2215
2216 #[test]
2217 fn table_full_filter_pushdown() -> Result<()> {
2218 let schema = Schema::new(test_table_scan_fields());
2219
2220 let table_scan = table_scan_with_filters(
2221 Some("test"),
2222 &schema,
2223 None,
2224 vec![col("b").eq(lit(1))],
2225 )?
2226 .build()?;
2227 assert_eq!(3, table_scan.schema().fields().len());
2228 assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2229
2230 let plan = LogicalPlanBuilder::from(table_scan)
2232 .project(vec![col("b")])?
2233 .project(vec![lit(1).alias("a")])?
2234 .build()?;
2235
2236 assert_fields_eq(&plan, vec!["a"]);
2237
2238 assert_optimized_plan_equal!(
2239 plan,
2240 @r"
2241 Projection: Int32(1) AS a
2242 TableScan: test projection=[], full_filters=[b = Int32(1)]
2243 "
2244 )
2245 }
2246
2247 #[test]
2249 fn test_double_optimization() -> Result<()> {
2250 let table_scan = test_table_scan()?;
2251
2252 let plan = LogicalPlanBuilder::from(table_scan)
2253 .project(vec![col("b")])?
2254 .project(vec![lit(1).alias("a")])?
2255 .build()?;
2256
2257 let optimized_plan1 = optimize(plan).expect("failed to optimize plan");
2258 let optimized_plan2 =
2259 optimize(optimized_plan1.clone()).expect("failed to optimize plan");
2260
2261 let formatted_plan1 = format!("{optimized_plan1:?}");
2262 let formatted_plan2 = format!("{optimized_plan2:?}");
2263 assert_eq!(formatted_plan1, formatted_plan2);
2264 Ok(())
2265 }
2266
2267 #[test]
2268 fn test_continue_processing_through_extension() -> Result<()> {
2269 let table_scan = test_table_scan()?;
2270 let plan = LogicalPlanBuilder::from(table_scan.clone())
2271 .project(vec![col("a")])?
2272 .project(vec![col("a")])?
2273 .build()?;
2274 let plan = LogicalPlan::Extension(Extension {
2275 node: Arc::new(OpaqueRequirementsUserDefined {
2276 input: Arc::new(plan),
2277 schema: Arc::clone(table_scan.schema()),
2278 }),
2279 });
2280 let plan = optimize(plan).expect("failed to optimize plan");
2281 assert_optimized_plan_equal!(
2282 plan,
2283 @r"
2284 OpaqueRequirementsUserDefined
2285 TableScan: test projection=[a]
2286 "
2287 )
2288 }
2289
2290 #[test]
2292 fn table_unused_aggregate() -> Result<()> {
2293 let table_scan = test_table_scan()?;
2294 assert_eq!(3, table_scan.schema().fields().len());
2295 assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2296
2297 let plan = LogicalPlanBuilder::from(table_scan)
2299 .aggregate(vec![col("a"), col("c")], vec![max(col("b")), min(col("b"))])?
2300 .filter(col("c").gt(lit(1)))?
2301 .project(vec![col("c"), col("a"), col("max(test.b)")])?
2302 .build()?;
2303
2304 assert_fields_eq(&plan, vec!["c", "a", "max(test.b)"]);
2305
2306 assert_optimized_plan_equal!(
2307 plan,
2308 @r"
2309 Projection: test.c, test.a, max(test.b)
2310 Filter: test.c > Int32(1)
2311 Aggregate: groupBy=[[test.a, test.c]], aggr=[[max(test.b)]]
2312 TableScan: test projection=[a, b, c]
2313 "
2314 )
2315 }
2316
2317 #[test]
2318 fn aggregate_filter_pushdown() -> Result<()> {
2319 let table_scan = test_table_scan()?;
2320 let aggr_with_filter = count_udaf()
2321 .call(vec![col("b")])
2322 .filter(col("c").gt(lit(42)))
2323 .build()?;
2324 let plan = LogicalPlanBuilder::from(table_scan)
2325 .aggregate(
2326 vec![col("a")],
2327 vec![count(col("b")), aggr_with_filter.alias("count2")],
2328 )?
2329 .build()?;
2330
2331 assert_optimized_plan_equal!(
2332 plan,
2333 @r"
2334 Aggregate: groupBy=[[test.a]], aggr=[[count(test.b), count(test.b) FILTER (WHERE test.c > Int32(42)) AS count2]]
2335 TableScan: test projection=[a, b, c]
2336 "
2337 )
2338 }
2339
2340 #[test]
2341 fn pushdown_through_distinct() -> Result<()> {
2342 let table_scan = test_table_scan()?;
2343
2344 let plan = LogicalPlanBuilder::from(table_scan)
2345 .project(vec![col("a"), col("b")])?
2346 .distinct()?
2347 .project(vec![col("a")])?
2348 .build()?;
2349
2350 assert_optimized_plan_equal!(
2351 plan,
2352 @r"
2353 Projection: test.a
2354 Distinct:
2355 TableScan: test projection=[a, b]
2356 "
2357 )
2358 }
2359
2360 #[test]
2361 fn test_window() -> Result<()> {
2362 let table_scan = test_table_scan()?;
2363
2364 let max1 = Expr::from(expr::WindowFunction::new(
2365 WindowFunctionDefinition::AggregateUDF(max_udaf()),
2366 vec![col("test.a")],
2367 ))
2368 .partition_by(vec![col("test.b")])
2369 .build()
2370 .unwrap();
2371
2372 let max2 = Expr::from(expr::WindowFunction::new(
2373 WindowFunctionDefinition::AggregateUDF(max_udaf()),
2374 vec![col("test.b")],
2375 ));
2376 let col1 = col(max1.schema_name().to_string());
2377 let col2 = col(max2.schema_name().to_string());
2378
2379 let plan = LogicalPlanBuilder::from(table_scan)
2380 .window(vec![max1])?
2381 .window(vec![max2])?
2382 .project(vec![col1, col2])?
2383 .build()?;
2384
2385 assert_optimized_plan_equal!(
2386 plan,
2387 @r"
2388 Projection: max(test.a) PARTITION BY [test.b] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING, max(test.b) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
2389 WindowAggr: windowExpr=[[max(test.b) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING]]
2390 Projection: test.b, max(test.a) PARTITION BY [test.b] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
2391 WindowAggr: windowExpr=[[max(test.a) PARTITION BY [test.b] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING]]
2392 TableScan: test projection=[a, b]
2393 "
2394 )
2395 }
2396
2397 #[test]
2401 fn optimize_projections_exists_or_exists_with_outer_join() -> Result<()> {
2402 use datafusion_expr::utils::disjunction;
2403 use datafusion_expr::{exists, out_ref_col};
2404
2405 let table_a = test_table_scan_with_name("a")?;
2406 let table_b = test_table_scan_with_name("b")?;
2407
2408 let sq_a = Arc::new(
2409 LogicalPlanBuilder::from(test_table_scan_with_name("sq_a")?)
2410 .filter(col("sq_a.a").eq(out_ref_col(DataType::UInt32, "a.a")))?
2411 .project(vec![lit(1)])?
2412 .build()?,
2413 );
2414
2415 let sq_b = Arc::new(
2416 LogicalPlanBuilder::from(test_table_scan_with_name("sq_b")?)
2417 .filter(col("sq_b.b").eq(out_ref_col(DataType::UInt32, "a.b")))?
2418 .project(vec![lit(1)])?
2419 .build()?,
2420 );
2421
2422 let plan = LogicalPlanBuilder::from(table_a)
2423 .filter(disjunction(vec![exists(sq_a), exists(sq_b)]).unwrap())?
2424 .join(table_b, JoinType::Left, (vec!["a"], vec!["a"]), None)?
2425 .build()?;
2426
2427 let optimizer = Optimizer::new();
2428 let config = OptimizerContext::new();
2429 optimizer.optimize(plan, &config, observe)?;
2430
2431 Ok(())
2432 }
2433
2434 #[test]
2435 fn optimize_projections_left_mark_join_with_projection() -> Result<()> {
2436 let table_a = test_table_scan_with_name("a")?;
2437 let table_b = test_table_scan_with_name("b")?;
2438 let table_c = test_table_scan_with_name("c")?;
2439
2440 let plan = LogicalPlanBuilder::from(table_a)
2441 .join(table_b, JoinType::LeftMark, (vec!["a"], vec!["a"]), None)?
2442 .project(vec![col("a.a"), col("a.b"), col("a.c")])?
2443 .join(table_c, JoinType::Left, (vec!["a"], vec!["a"]), None)?
2444 .build()?;
2445
2446 assert_optimized_plan_equal!(
2447 plan,
2448 @r"
2449 Left Join: a.a = c.a
2450 Projection: a.a, a.b, a.c
2451 LeftMark Join: a.a = b.a
2452 TableScan: a projection=[a, b, c]
2453 TableScan: b projection=[a]
2454 TableScan: c projection=[a, b, c]
2455 "
2456 )
2457 }
2458
2459 #[test]
2462 fn optimize_projections_stacked_mark_joins_keep_qualified_mark() -> Result<()> {
2463 let person = test_table_scan_with_name("person")?;
2464
2465 let aliased_scan = |table: &str, alias: &str| -> Result<LogicalPlan> {
2466 LogicalPlanBuilder::from(test_table_scan_with_name(table)?)
2467 .project(vec![col(format!("{table}.a"))])?
2468 .alias(alias)?
2469 .build()
2470 };
2471
2472 let plan = LogicalPlanBuilder::from(person)
2473 .join_on(
2474 aliased_scan("s1", "__correlated_sq_1")?,
2475 JoinType::LeftMark,
2476 vec![lit(true)],
2477 )?
2478 .join_on(
2479 aliased_scan("s2", "__correlated_sq_2")?,
2480 JoinType::LeftMark,
2481 vec![lit(true)],
2482 )?
2483 .join_on(
2484 aliased_scan("s3", "__correlated_sq_3")?,
2485 JoinType::LeftMark,
2486 vec![lit(true)],
2487 )?
2488 .filter(
2489 col("__correlated_sq_1.mark")
2490 .or(col("__correlated_sq_2.mark"))
2491 .and(not(col("__correlated_sq_3.mark"))),
2492 )?
2493 .project(vec![col("person.a")])?
2494 .build()?;
2495
2496 assert_optimized_plan_equal!(
2497 plan,
2498 @r"
2499 Projection: person.a
2500 Filter: (__correlated_sq_1.mark OR __correlated_sq_2.mark) AND NOT __correlated_sq_3.mark
2501 LeftMark Join: Filter: Boolean(true)
2502 LeftMark Join: Filter: Boolean(true)
2503 LeftMark Join: Filter: Boolean(true)
2504 TableScan: person projection=[a]
2505 SubqueryAlias: __correlated_sq_1
2506 TableScan: s1 projection=[a]
2507 SubqueryAlias: __correlated_sq_2
2508 TableScan: s2 projection=[a]
2509 SubqueryAlias: __correlated_sq_3
2510 TableScan: s3 projection=[a]
2511 "
2512 )
2513 }
2514
2515 fn observe(_plan: &LogicalPlan, _rule: &dyn OptimizerRule) {}
2516
2517 fn optimize(plan: LogicalPlan) -> Result<LogicalPlan> {
2518 let optimizer = Optimizer::with_rules(vec![Arc::new(OptimizeProjections::new())]);
2519 let optimized_plan =
2520 optimizer.optimize(plan, &OptimizerContext::new(), observe)?;
2521 Ok(optimized_plan)
2522 }
2523}