1use spark_connect_proto as proto;
7use std::collections::HashMap;
8
9use crate::column::Column;
10use crate::expression::Expression;
11use crate::types::DataType;
12use crate::udf::CommonInlineUserDefinedFunctionExpression;
13
14#[derive(Debug, Clone)]
17pub struct TransformWithStateInfo {
18 pub time_mode: String,
19 pub event_time_column_name: Option<String>,
20 pub output_schema: Option<DataType>,
21}
22
23#[derive(Debug, Clone)]
28pub enum LogicalPlan {
29 Range {
31 start: i64,
32 end: i64,
33 step: i64,
34 num_partitions: Option<i32>,
35 },
36 Sql {
38 query: String,
39 pos_args: Vec<Expression>,
40 named_args: HashMap<String, Expression>,
41 },
42 Project {
44 input: Box<LogicalPlan>,
45 columns: Vec<Column>,
46 },
47 Filter {
49 input: Box<LogicalPlan>,
50 condition: Column,
51 },
52 Aggregate {
54 input: Box<LogicalPlan>,
55 group_type: AggregateGroupType,
56 grouping_expressions: Vec<Expression>,
57 aggregate_expressions: Vec<Expression>,
58 pivot_col: Option<Expression>,
59 pivot_values: Vec<Expression>,
60 grouping_sets: Vec<Vec<Expression>>,
63 },
64 Join {
66 left: Box<LogicalPlan>,
67 right: Box<LogicalPlan>,
68 join_type: JoinType,
69 on: Option<Column>,
70 using_columns: Vec<String>,
71 },
72 LateralJoin {
74 left: Box<LogicalPlan>,
75 right: Box<LogicalPlan>,
76 join_type: JoinType,
77 on: Option<Column>,
78 },
79 SetOperation {
81 left: Box<LogicalPlan>,
82 right: Box<LogicalPlan>,
83 set_op_type: SetOpType,
84 is_all: bool,
85 by_name: bool,
86 allow_missing_columns: bool,
87 },
88 Limit { input: Box<LogicalPlan>, limit: i32 },
90 Offset {
92 input: Box<LogicalPlan>,
93 offset: i32,
94 },
95 Tail { input: Box<LogicalPlan>, limit: i32 },
97 Deduplicate {
99 input: Box<LogicalPlan>,
100 all_columns_as_keys: bool,
101 column_names: Vec<String>,
102 within_watermark: bool,
103 },
104 Sort {
106 input: Box<LogicalPlan>,
107 order: Vec<Expression>,
108 is_global: bool,
109 },
110 Sample {
112 input: Box<LogicalPlan>,
113 lower_bound: f64,
114 upper_bound: f64,
115 with_replacement: bool,
116 seed: Option<i64>,
117 },
118 Repartition {
120 input: Box<LogicalPlan>,
121 num_partitions: i32,
122 shuffle: bool,
123 },
124 RepartitionByExpression {
126 input: Box<LogicalPlan>,
127 num_partitions: i32,
128 expressions: Vec<Expression>,
129 },
130 WithColumns {
132 input: Box<LogicalPlan>,
133 column_names: Vec<String>,
134 columns: Vec<Column>,
135 },
136 WithColumnMetadata {
140 input: Box<LogicalPlan>,
141 column_name: String,
142 metadata_json: String,
143 },
144 WithColumnsRenamed {
146 input: Box<LogicalPlan>,
147 renames: HashMap<String, String>,
148 },
149 Drop {
151 input: Box<LogicalPlan>,
152 columns: Vec<String>,
153 },
154 ToDF {
156 input: Box<LogicalPlan>,
157 column_names: Vec<String>,
158 },
159 ToSchema {
161 input: Box<LogicalPlan>,
162 schema: DataType,
163 },
164 Hint {
166 input: Box<LogicalPlan>,
167 name: String,
168 parameters: Vec<String>,
169 },
170 Unpivot {
172 input: Box<LogicalPlan>,
173 ids: Vec<Column>,
174 values: Option<Vec<Column>>,
175 variable_column_name: String,
176 value_column_name: String,
177 },
178 NAFill {
180 input: Box<LogicalPlan>,
181 fill_value: crate::row::Value,
182 columns: Vec<String>,
183 },
184 NAFillColumns {
186 input: Box<LogicalPlan>,
187 cols: Vec<String>,
188 values: Vec<crate::row::Value>,
189 },
190 NADrop {
192 input: Box<LogicalPlan>,
193 how: String,
194 min_non_null: Option<i32>,
195 columns: Vec<String>,
196 },
197 NAReplace {
199 input: Box<LogicalPlan>,
200 replacements: Vec<(String, String)>,
201 columns: Vec<String>,
202 },
203 Describe {
205 input: Box<LogicalPlan>,
206 columns: Vec<String>,
207 },
208 Summary {
210 input: Box<LogicalPlan>,
211 percentiles: Vec<String>,
212 },
213 ColRegex {
215 input: Box<LogicalPlan>,
216 col_name: String,
217 },
218 SubqueryAlias {
220 input: Box<LogicalPlan>,
221 alias: String,
222 },
223 LocalRelation {
225 schema: DataType,
226 data: Option<Vec<u8>>,
227 },
228 CachedRemoteRelation { relation_id: String },
230 Read {
232 read_type: crate::readwriter::ReadType,
233 is_streaming: bool,
234 },
235 RelationChanges {
238 table_name: String,
239 options: std::collections::HashMap<String, String>,
240 is_streaming: Option<bool>,
241 },
242 WithWatermark {
244 input: Box<LogicalPlan>,
245 time_column: String,
246 delay_threshold: String,
247 },
248 RepartitionByRange {
250 input: Box<LogicalPlan>,
251 num_partitions: Option<i32>,
252 partition_exprs: Vec<Expression>,
253 },
254 StatCrosstab {
256 input: Box<LogicalPlan>,
257 col1: String,
258 col2: String,
259 },
260 StatFreqItems {
262 input: Box<LogicalPlan>,
263 columns: Vec<String>,
264 support: f64,
265 },
266 StatApproxQuantile {
268 input: Box<LogicalPlan>,
269 columns: Vec<String>,
270 probabilities: Vec<f64>,
271 relative_error: f64,
272 },
273 StatCorr {
275 input: Box<LogicalPlan>,
276 col1: String,
277 col2: String,
278 },
279 StatCov {
281 input: Box<LogicalPlan>,
282 col1: String,
283 col2: String,
284 },
285 StatSampleBy {
287 input: Box<LogicalPlan>,
288 col: String,
289 fractions: Vec<(Expression, f64)>,
290 seed: Option<i64>,
291 },
292 Observe {
294 input: Box<LogicalPlan>,
295 name: String,
296 exprs: Vec<Expression>,
297 },
298 UnresolvedTableValuedFunction {
300 name: String,
301 arguments: Vec<Expression>,
302 },
303 Zip {
305 left: Box<LogicalPlan>,
306 right: Box<LogicalPlan>,
307 },
308 MlTransform { ml_relation: proto::Relation },
310 Catalog { catalog: proto::Catalog },
314 Transpose {
317 input: Box<LogicalPlan>,
318 index_columns: Vec<Expression>,
319 },
320 MapPartitions {
322 input: Box<LogicalPlan>,
323 func: CommonInlineUserDefinedFunctionExpression,
324 is_barrier: bool,
325 },
326 GroupMap {
329 input: Box<LogicalPlan>,
330 grouping_expressions: Vec<Expression>,
331 func: CommonInlineUserDefinedFunctionExpression,
332 sorting_expressions: Vec<Expression>,
333 initial_input: Option<Box<LogicalPlan>>,
335 initial_grouping_expressions: Vec<Expression>,
336 is_map_groups_with_state: Option<bool>,
337 output_mode: Option<String>,
338 timeout_conf: Option<String>,
339 state_schema: Option<DataType>,
340 transform_with_state_info: Option<TransformWithStateInfo>,
341 },
342 CoGroupMap {
344 input: Box<LogicalPlan>,
345 input_grouping_expressions: Vec<Expression>,
346 other: Box<LogicalPlan>,
347 other_grouping_expressions: Vec<Expression>,
348 func: CommonInlineUserDefinedFunctionExpression,
349 },
350 NearestByJoin {
352 left: Box<LogicalPlan>,
353 right: Box<LogicalPlan>,
354 ranking_expression: Expression,
355 num_results: i32,
356 join_type: String,
357 mode: String,
358 direction: String,
359 },
360 CommonInlineUdtf {
362 function_name: String,
363 deterministic: bool,
364 arguments: Vec<Expression>,
365 return_type: Option<crate::types::DataType>,
366 eval_type: i32,
367 command: Vec<u8>,
368 python_ver: String,
369 },
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
374pub enum AggregateGroupType {
375 GroupBy,
376 Rollup,
377 Cube,
378 Pivot,
379 GroupingSets,
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
384pub enum JoinType {
385 Inner,
386 LeftOuter,
387 RightOuter,
388 FullOuter,
389 LeftSemi,
390 LeftAnti,
391 Cross,
392}
393
394#[derive(Debug, Clone, Copy, PartialEq, Eq)]
396pub enum SetOpType {
397 Union,
398 Intersect,
399 Except,
400}
401
402impl LogicalPlan {
403 pub fn to_proto(&self) -> proto::Relation {
405 let mut relation = proto::Relation::default();
406 relation.common = Some(proto::RelationCommon::default());
407
408 match self {
409 LogicalPlan::Range {
410 start,
411 end,
412 step,
413 num_partitions,
414 } => {
415 let mut range = proto::Range::default();
416 range.start = Some(*start);
417 range.end = *end;
418 range.step = *step;
419 if let Some(n) = num_partitions {
420 range.num_partitions = Some(*n);
421 }
422 relation.rel_type = Some(proto::relation::RelType::Range(range));
423 }
424
425 LogicalPlan::Sql {
426 query,
427 pos_args,
428 named_args,
429 } => {
430 let mut sql = proto::Sql::default();
431 sql.query = query.clone();
432 for e in pos_args {
433 sql.pos_arguments.push(e.to_proto());
434 }
435 for (k, v) in named_args {
436 sql.named_arguments.insert(k.clone(), v.to_proto());
437 }
438 relation.rel_type = Some(proto::relation::RelType::Sql(sql));
439 }
440
441 LogicalPlan::Project { input, columns } => {
442 let mut project = proto::Project::default();
443 project.input = Some(Box::new(input.to_proto()));
444 for col in columns {
445 project.expressions.push(col.to_proto());
446 }
447 relation.rel_type = Some(proto::relation::RelType::Project(Box::new(project)));
448 }
449
450 LogicalPlan::Filter { input, condition } => {
451 let mut filter = proto::Filter::default();
452 filter.input = Some(Box::new(input.to_proto()));
453 filter.condition = Some(condition.to_proto());
454 relation.rel_type = Some(proto::relation::RelType::Filter(Box::new(filter)));
455 }
456
457 LogicalPlan::Aggregate {
458 input,
459 group_type,
460 grouping_expressions,
461 aggregate_expressions,
462 pivot_col,
463 pivot_values,
464 grouping_sets,
465 } => {
466 let mut agg = proto::Aggregate::default();
467 agg.input = Some(Box::new(input.to_proto()));
468 agg.group_type = match group_type {
469 AggregateGroupType::GroupBy => proto::aggregate::GroupType::Groupby as i32,
470 AggregateGroupType::Rollup => proto::aggregate::GroupType::Rollup as i32,
471 AggregateGroupType::Cube => proto::aggregate::GroupType::Cube as i32,
472 AggregateGroupType::Pivot => proto::aggregate::GroupType::Pivot as i32,
473 AggregateGroupType::GroupingSets => {
474 proto::aggregate::GroupType::GroupingSets as i32
475 }
476 };
477 for expr in grouping_expressions {
478 agg.grouping_expressions.push(expr.to_proto());
479 }
480 for set in grouping_sets {
484 let mut gs = proto::aggregate::GroupingSets::default();
485 for e in set {
486 gs.grouping_set.push(e.to_proto());
487 }
488 agg.grouping_sets.push(gs);
489 }
490 for expr in aggregate_expressions {
491 agg.aggregate_expressions.push(expr.to_proto());
492 }
493 if let Some(pcol) = pivot_col {
494 let mut pivot = proto::aggregate::Pivot::default();
495 pivot.col = Some(pcol.to_proto());
496 for v in pivot_values {
499 if let Some(proto::expression::ExprType::Literal(lit)) =
500 v.to_proto().expr_type
501 {
502 pivot.values.push(lit);
503 }
504 }
505 agg.pivot = Some(pivot);
506 }
507 relation.rel_type = Some(proto::relation::RelType::Aggregate(Box::new(agg)));
508 }
509
510 LogicalPlan::Join {
511 left,
512 right,
513 join_type,
514 on,
515 using_columns,
516 } => {
517 let mut join = proto::Join::default();
518 join.left = Some(Box::new(left.to_proto()));
519 join.right = Some(Box::new(right.to_proto()));
520 join.join_type = match join_type {
521 JoinType::Inner => proto::join::JoinType::Inner as i32,
522 JoinType::LeftOuter => proto::join::JoinType::LeftOuter as i32,
523 JoinType::RightOuter => proto::join::JoinType::RightOuter as i32,
524 JoinType::FullOuter => proto::join::JoinType::FullOuter as i32,
525 JoinType::LeftSemi => proto::join::JoinType::LeftSemi as i32,
526 JoinType::LeftAnti => proto::join::JoinType::LeftAnti as i32,
527 JoinType::Cross => proto::join::JoinType::Cross as i32,
528 };
529 if let Some(condition) = on {
530 join.join_condition = Some(condition.to_proto());
531 }
532 join.using_columns.extend(using_columns.clone());
533 relation.rel_type = Some(proto::relation::RelType::Join(Box::new(join)));
534 }
535
536 LogicalPlan::LateralJoin {
537 left,
538 right,
539 join_type,
540 on,
541 } => {
542 let mut lj = proto::LateralJoin::default();
543 lj.left = Some(Box::new(left.to_proto()));
544 lj.right = Some(Box::new(right.to_proto()));
545 lj.join_type = match join_type {
546 JoinType::Inner => proto::join::JoinType::Inner as i32,
547 JoinType::LeftOuter => proto::join::JoinType::LeftOuter as i32,
548 JoinType::RightOuter => proto::join::JoinType::RightOuter as i32,
549 JoinType::FullOuter => proto::join::JoinType::FullOuter as i32,
550 JoinType::LeftSemi => proto::join::JoinType::LeftSemi as i32,
551 JoinType::LeftAnti => proto::join::JoinType::LeftAnti as i32,
552 JoinType::Cross => proto::join::JoinType::Cross as i32,
553 };
554 if let Some(condition) = on {
555 lj.join_condition = Some(condition.to_proto());
556 }
557 relation.rel_type = Some(proto::relation::RelType::LateralJoin(Box::new(lj)));
558 }
559
560 LogicalPlan::SetOperation {
561 left,
562 right,
563 set_op_type,
564 is_all,
565 by_name,
566 allow_missing_columns,
567 } => {
568 let mut set_op = proto::SetOperation::default();
569 set_op.left_input = Some(Box::new(left.to_proto()));
570 set_op.right_input = Some(Box::new(right.to_proto()));
571 set_op.set_op_type = match set_op_type {
572 SetOpType::Union => proto::set_operation::SetOpType::Union as i32,
573 SetOpType::Intersect => proto::set_operation::SetOpType::Intersect as i32,
574 SetOpType::Except => proto::set_operation::SetOpType::Except as i32,
575 };
576 set_op.is_all = Some(*is_all);
577 set_op.by_name = Some(*by_name);
578 set_op.allow_missing_columns = Some(*allow_missing_columns);
579 relation.rel_type = Some(proto::relation::RelType::SetOp(Box::new(set_op)));
580 }
581
582 LogicalPlan::Limit { input, limit } => {
583 let mut lim = proto::Limit::default();
584 lim.input = Some(Box::new(input.to_proto()));
585 lim.limit = *limit;
586 relation.rel_type = Some(proto::relation::RelType::Limit(Box::new(lim)));
587 }
588
589 LogicalPlan::Offset { input, offset } => {
590 let mut off = proto::Offset::default();
591 off.input = Some(Box::new(input.to_proto()));
592 off.offset = *offset;
593 relation.rel_type = Some(proto::relation::RelType::Offset(Box::new(off)));
594 }
595
596 LogicalPlan::Tail { input, limit } => {
597 let mut tail = proto::Tail::default();
598 tail.input = Some(Box::new(input.to_proto()));
599 tail.limit = *limit;
600 relation.rel_type = Some(proto::relation::RelType::Tail(Box::new(tail)));
601 }
602
603 LogicalPlan::Deduplicate {
604 input,
605 all_columns_as_keys,
606 column_names,
607 within_watermark,
608 } => {
609 let mut dedup = proto::Deduplicate::default();
610 dedup.input = Some(Box::new(input.to_proto()));
611 dedup.all_columns_as_keys = Some(*all_columns_as_keys);
612 dedup.column_names.extend(column_names.clone());
613 dedup.within_watermark = Some(*within_watermark);
614 relation.rel_type = Some(proto::relation::RelType::Deduplicate(Box::new(dedup)));
615 }
616
617 LogicalPlan::Sort {
618 input,
619 order,
620 is_global,
621 } => {
622 use proto::expression::sort_order::{NullOrdering as PbNulls, SortDirection};
625 let mut sort = proto::Sort::default();
626 sort.input = Some(Box::new(input.to_proto()));
627 for expr in order {
628 if let Expression::SortOrder(so) = expr {
630 let mut sort_order = proto::expression::SortOrder::default();
631 sort_order.child = Some(Box::new(so.child.to_proto()));
632 sort_order.direction = if so.ascending {
633 SortDirection::Ascending as i32
634 } else {
635 SortDirection::Descending as i32
636 };
637 sort_order.null_ordering = match so.null_ordering {
638 crate::expression::NullOrdering::First => {
639 PbNulls::SortNullsFirst as i32
640 }
641 crate::expression::NullOrdering::Last => PbNulls::SortNullsLast as i32,
642 };
643 sort.order.push(sort_order);
644 } else {
645 let expr_proto = expr.to_proto();
646 if let Some(proto::expression::ExprType::SortOrder(so)) =
647 expr_proto.expr_type
648 {
649 sort.order.push(*so);
650 } else {
651 let mut sort_order = proto::expression::SortOrder::default();
656 sort_order.child = Some(Box::new(expr_proto));
657 sort_order.direction = SortDirection::Ascending as i32;
658 sort_order.null_ordering = PbNulls::SortNullsFirst as i32;
659 sort.order.push(sort_order);
660 }
661 }
662 }
663 sort.is_global = Some(*is_global);
664 relation.rel_type = Some(proto::relation::RelType::Sort(Box::new(sort)));
665 }
666
667 LogicalPlan::Sample {
668 input,
669 lower_bound,
670 upper_bound,
671 with_replacement,
672 seed,
673 } => {
674 let mut sample = proto::Sample::default();
675 sample.input = Some(Box::new(input.to_proto()));
676 sample.lower_bound = *lower_bound;
677 sample.upper_bound = *upper_bound;
678 sample.with_replacement = Some(*with_replacement);
679 if let Some(s) = seed {
680 sample.seed = Some(*s);
681 }
682 relation.rel_type = Some(proto::relation::RelType::Sample(Box::new(sample)));
683 }
684
685 LogicalPlan::Repartition {
686 input,
687 num_partitions,
688 shuffle,
689 } => {
690 let mut repart = proto::Repartition::default();
691 repart.input = Some(Box::new(input.to_proto()));
692 repart.num_partitions = *num_partitions;
693 repart.shuffle = Some(*shuffle);
694 relation.rel_type = Some(proto::relation::RelType::Repartition(Box::new(repart)));
695 }
696
697 LogicalPlan::RepartitionByExpression {
698 input,
699 num_partitions,
700 expressions,
701 } => {
702 let mut repart = proto::RepartitionByExpression::default();
703 repart.input = Some(Box::new(input.to_proto()));
704 repart.num_partitions = if *num_partitions > 0 {
708 Some(*num_partitions)
709 } else {
710 None
711 };
712 for expr in expressions {
713 repart.partition_exprs.push(expr.to_proto());
714 }
715 relation.rel_type = Some(proto::relation::RelType::RepartitionByExpression(
716 Box::new(repart),
717 ));
718 }
719
720 LogicalPlan::WithColumns {
721 input,
722 column_names,
723 columns,
724 } => {
725 let mut wc = proto::WithColumns::default();
726 wc.input = Some(Box::new(input.to_proto()));
727 for (name, col) in column_names.iter().zip(columns.iter()) {
728 let mut alias = proto::expression::Alias::default();
730 alias.expr = Some(Box::new(col.to_proto()));
731 alias.name = vec![name.clone()];
732 wc.aliases.push(alias);
733 }
734 relation.rel_type = Some(proto::relation::RelType::WithColumns(Box::new(wc)));
735 }
736
737 LogicalPlan::WithColumnMetadata {
738 input,
739 column_name,
740 metadata_json,
741 } => {
742 let mut wc = proto::WithColumns::default();
743 wc.input = Some(Box::new(input.to_proto()));
744 let col_expr = crate::expression::Expression::ColumnReference(
745 crate::expression::ColumnReference::new(column_name.clone()),
746 );
747 let mut alias = proto::expression::Alias::default();
748 alias.expr = Some(Box::new(col_expr.to_proto()));
749 alias.name = vec![column_name.clone()];
750 alias.metadata = Some(metadata_json.clone());
751 wc.aliases.push(alias);
752 relation.rel_type = Some(proto::relation::RelType::WithColumns(Box::new(wc)));
753 }
754
755 LogicalPlan::WithColumnsRenamed { input, renames } => {
756 let mut wcr = proto::WithColumnsRenamed::default();
757 wcr.input = Some(Box::new(input.to_proto()));
758 for (old_name, new_name) in renames.iter() {
759 let mut rename = proto::with_columns_renamed::Rename::default();
760 rename.col_name = old_name.clone();
761 rename.new_col_name = new_name.clone();
762 wcr.renames.push(rename);
763 }
764 relation.rel_type =
765 Some(proto::relation::RelType::WithColumnsRenamed(Box::new(wcr)));
766 }
767
768 LogicalPlan::Drop { input, columns } => {
769 let mut drop = proto::Drop::default();
770 drop.input = Some(Box::new(input.to_proto()));
771 drop.column_names.extend(columns.clone());
772 relation.rel_type = Some(proto::relation::RelType::Drop(Box::new(drop)));
773 }
774
775 LogicalPlan::ToDF {
776 input,
777 column_names,
778 } => {
779 let mut to_df = proto::ToDf::default();
780 to_df.input = Some(Box::new(input.to_proto()));
781 to_df.column_names.extend(column_names.clone());
782 relation.rel_type = Some(proto::relation::RelType::ToDf(Box::new(to_df)));
783 }
784
785 LogicalPlan::ToSchema { input, schema } => {
786 let mut to_schema = proto::ToSchema::default();
787 to_schema.input = Some(Box::new(input.to_proto()));
788 to_schema.schema = Some(schema.to_proto());
789 relation.rel_type = Some(proto::relation::RelType::ToSchema(Box::new(to_schema)));
790 }
791
792 LogicalPlan::Hint {
793 input,
794 name,
795 parameters,
796 } => {
797 let mut hint = proto::Hint::default();
798 hint.input = Some(Box::new(input.to_proto()));
799 hint.name = name.clone();
800 for p in parameters {
806 let lit = if let Ok(n) = p.parse::<i32>() {
807 crate::expression::LiteralExpression::int(n)
808 } else {
809 crate::expression::LiteralExpression::string(p.clone())
810 };
811 hint.parameters.push(lit.to_proto());
812 }
813 relation.rel_type = Some(proto::relation::RelType::Hint(Box::new(hint)));
814 }
815
816 LogicalPlan::Unpivot {
817 input,
818 ids,
819 values,
820 variable_column_name,
821 value_column_name,
822 } => {
823 let mut unpivot = proto::Unpivot::default();
824 unpivot.input = Some(Box::new(input.to_proto()));
825 for col in ids {
826 unpivot.ids.push(col.to_proto());
827 }
828 if let Some(v) = values {
829 let mut vals = proto::unpivot::Values::default();
830 for col in v {
831 vals.values.push(col.to_proto());
832 }
833 unpivot.values = Some(vals);
834 }
835 unpivot.variable_column_name = variable_column_name.clone();
836 unpivot.value_column_name = value_column_name.clone();
837 relation.rel_type = Some(proto::relation::RelType::Unpivot(Box::new(unpivot)));
838 }
839
840 LogicalPlan::NAFill {
841 input,
842 fill_value,
843 columns,
844 } => {
845 let mut na_fill = proto::NaFill::default();
846 na_fill.input = Some(Box::new(input.to_proto()));
847 na_fill.cols.extend(columns.clone());
848 na_fill.values.push(value_to_proto_literal(fill_value));
849 relation.rel_type = Some(proto::relation::RelType::FillNa(Box::new(na_fill)));
850 }
851
852 LogicalPlan::NAFillColumns {
853 input,
854 cols,
855 values,
856 } => {
857 let mut na_fill = proto::NaFill::default();
860 na_fill.input = Some(Box::new(input.to_proto()));
861 na_fill.cols.extend(cols.clone());
862 na_fill
863 .values
864 .extend(values.iter().map(value_to_proto_literal));
865 relation.rel_type = Some(proto::relation::RelType::FillNa(Box::new(na_fill)));
866 }
867
868 LogicalPlan::NADrop {
869 input,
870 how,
871 min_non_null,
872 columns,
873 } => {
874 let mut na_drop = proto::NaDrop::default();
875 na_drop.input = Some(Box::new(input.to_proto()));
876 na_drop.cols.extend(columns.clone());
877 na_drop.min_non_nulls = match min_non_null {
882 Some(m) => Some(*m),
883 None if how == "all" => Some(1),
884 None => None,
885 };
886 relation.rel_type = Some(proto::relation::RelType::DropNa(Box::new(na_drop)));
887 }
888
889 LogicalPlan::NAReplace {
890 input,
891 replacements,
892 columns,
893 } => {
894 let mut na_replace = proto::NaReplace::default();
895 na_replace.input = Some(Box::new(input.to_proto()));
896 for (old_val, new_val) in replacements.iter() {
897 let mut replace = proto::na_replace::Replacement::default();
898 replace.old_value = Some(str_to_proto_literal(old_val));
902 replace.new_value = Some(str_to_proto_literal(new_val));
903 na_replace.replacements.push(replace);
904 }
905 na_replace.cols.extend(columns.clone());
906 relation.rel_type = Some(proto::relation::RelType::Replace(Box::new(na_replace)));
907 }
908
909 LogicalPlan::Describe { input, columns } => {
910 let mut describe = proto::StatDescribe::default();
911 describe.input = Some(Box::new(input.to_proto()));
912 describe.cols.extend(columns.clone());
913 relation.rel_type = Some(proto::relation::RelType::Describe(Box::new(describe)));
914 }
915
916 LogicalPlan::Summary { input, percentiles } => {
917 let mut summary = proto::StatSummary::default();
918 summary.input = Some(Box::new(input.to_proto()));
919 summary.statistics.extend(percentiles.clone());
920 relation.rel_type = Some(proto::relation::RelType::Summary(Box::new(summary)));
921 }
922
923 LogicalPlan::ColRegex { input, col_name } => {
924 let mut project = proto::Project::default();
926 project.input = Some(Box::new(input.to_proto()));
927 let mut expr = proto::Expression::default();
928 expr.expr_type = Some(proto::expression::ExprType::UnresolvedRegex(
929 proto::expression::UnresolvedRegex {
930 col_name: col_name.clone(),
931 plan_id: None,
932 },
933 ));
934 project.expressions.push(expr);
935 relation.rel_type = Some(proto::relation::RelType::Project(Box::new(project)));
936 }
937
938 LogicalPlan::SubqueryAlias { input, alias } => {
939 let mut sq_alias = proto::SubqueryAlias::default();
940 sq_alias.input = Some(Box::new(input.to_proto()));
941 sq_alias.alias = alias.clone();
942 relation.rel_type =
943 Some(proto::relation::RelType::SubqueryAlias(Box::new(sq_alias)));
944 }
945
946 LogicalPlan::LocalRelation { schema, data } => {
947 let mut local = proto::LocalRelation::default();
948 if let Some(d) = data {
949 local.data = Some(d.clone().into());
950 }
951 local.schema = Some(schema.json());
954 relation.rel_type = Some(proto::relation::RelType::LocalRelation(local));
955 }
956
957 LogicalPlan::CachedRemoteRelation { relation_id } => {
958 let mut cached = proto::CachedRemoteRelation::default();
959 cached.relation_id = relation_id.clone();
960 relation.rel_type = Some(proto::relation::RelType::CachedRemoteRelation(cached));
961 }
962
963 LogicalPlan::Read {
964 read_type,
965 is_streaming,
966 } => {
967 let mut read = proto::Read::default();
968 read.is_streaming = *is_streaming;
969
970 match read_type {
971 crate::readwriter::ReadType::DataSource {
972 format,
973 schema,
974 options,
975 paths,
976 predicates,
977 source_name,
978 } => {
979 let mut data_source = proto::read::DataSource::default();
980 if let Some(fmt) = format {
981 data_source.format = Some(fmt.clone());
982 }
983 if let Some(sch) = schema {
984 data_source.schema = Some(sch.clone());
985 }
986 data_source.options.extend(options.clone());
987 data_source.paths.extend(paths.clone());
988 data_source.predicates.extend(predicates.clone());
989 if let Some(sn) = source_name {
990 data_source.source_name = Some(sn.clone());
991 }
992 read.read_type = Some(proto::read::ReadType::DataSource(data_source));
993 }
994 crate::readwriter::ReadType::NamedTable {
995 table_name,
996 options,
997 } => {
998 let mut named_table = proto::read::NamedTable::default();
999 named_table.unparsed_identifier = table_name.clone();
1000 named_table.options.extend(options.clone());
1001 read.read_type = Some(proto::read::ReadType::NamedTable(named_table));
1002 }
1003 }
1004
1005 relation.rel_type = Some(proto::relation::RelType::Read(read));
1006 }
1007
1008 LogicalPlan::RelationChanges {
1009 table_name,
1010 options,
1011 is_streaming,
1012 } => {
1013 let mut changes = proto::RelationChanges::default();
1014 changes.unparsed_identifier = table_name.clone();
1015 changes.options.extend(options.clone());
1016 if let Some(s) = is_streaming {
1017 changes.is_streaming = *s;
1018 }
1019 relation.rel_type = Some(proto::relation::RelType::RelationChanges(changes));
1020 }
1021
1022 LogicalPlan::WithWatermark {
1023 input,
1024 time_column,
1025 delay_threshold,
1026 } => {
1027 let mut watermark = proto::WithWatermark::default();
1028 watermark.input = Some(Box::new(input.to_proto()));
1029 watermark.event_time = time_column.clone();
1030 watermark.delay_threshold = delay_threshold.clone();
1031 relation.rel_type =
1032 Some(proto::relation::RelType::WithWatermark(Box::new(watermark)));
1033 }
1034
1035 LogicalPlan::RepartitionByRange {
1036 input,
1037 num_partitions,
1038 partition_exprs,
1039 } => {
1040 let mut repart = proto::RepartitionByExpression::default();
1041 repart.input = Some(Box::new(input.to_proto()));
1042 if let Some(n) = num_partitions {
1043 repart.num_partitions = Some(*n);
1044 }
1045 for expr in partition_exprs {
1046 repart.partition_exprs.push(expr.to_proto());
1047 }
1048 relation.rel_type = Some(proto::relation::RelType::RepartitionByExpression(
1049 Box::new(repart),
1050 ));
1051 }
1052
1053 LogicalPlan::StatCrosstab { input, col1, col2 } => {
1054 let mut stat = proto::StatCrosstab::default();
1055 stat.input = Some(Box::new(input.to_proto()));
1056 stat.col1 = col1.clone();
1057 stat.col2 = col2.clone();
1058 relation.rel_type = Some(proto::relation::RelType::Crosstab(Box::new(stat)));
1059 }
1060
1061 LogicalPlan::StatFreqItems {
1062 input,
1063 columns,
1064 support,
1065 } => {
1066 let mut stat = proto::StatFreqItems::default();
1067 stat.input = Some(Box::new(input.to_proto()));
1068 stat.cols.extend(columns.clone());
1069 stat.support = Some(*support);
1070 relation.rel_type = Some(proto::relation::RelType::FreqItems(Box::new(stat)));
1071 }
1072
1073 LogicalPlan::StatApproxQuantile {
1074 input,
1075 columns,
1076 probabilities,
1077 relative_error,
1078 } => {
1079 let mut stat = proto::StatApproxQuantile::default();
1080 stat.input = Some(Box::new(input.to_proto()));
1081 stat.cols.extend(columns.clone());
1082 stat.probabilities.extend(probabilities.clone());
1083 stat.relative_error = *relative_error;
1084 relation.rel_type = Some(proto::relation::RelType::ApproxQuantile(Box::new(stat)));
1085 }
1086
1087 LogicalPlan::StatCorr { input, col1, col2 } => {
1088 let mut stat = proto::StatCorr::default();
1089 stat.input = Some(Box::new(input.to_proto()));
1090 stat.col1 = col1.clone();
1091 stat.col2 = col2.clone();
1092 relation.rel_type = Some(proto::relation::RelType::Corr(Box::new(stat)));
1093 }
1094
1095 LogicalPlan::StatCov { input, col1, col2 } => {
1096 let mut stat = proto::StatCov::default();
1097 stat.input = Some(Box::new(input.to_proto()));
1098 stat.col1 = col1.clone();
1099 stat.col2 = col2.clone();
1100 relation.rel_type = Some(proto::relation::RelType::Cov(Box::new(stat)));
1101 }
1102
1103 LogicalPlan::StatSampleBy {
1104 input,
1105 col,
1106 fractions,
1107 seed,
1108 } => {
1109 let mut stat = proto::StatSampleBy::default();
1110 stat.input = Some(Box::new(input.to_proto()));
1111 let mut col_expr = proto::Expression::default();
1113 col_expr.expr_type = Some(proto::expression::ExprType::UnresolvedAttribute(
1114 proto::expression::UnresolvedAttribute {
1115 unparsed_identifier: col.clone(),
1116 plan_id: None,
1117 is_metadata_column: None,
1118 },
1119 ));
1120 stat.col = Some(col_expr);
1121 for (expr, _frac) in fractions {
1122 let mut fraction = proto::stat_sample_by::Fraction::default();
1124 let expr_proto = expr.to_proto();
1125 if let Some(proto::expression::ExprType::Literal(lit)) = expr_proto.expr_type {
1127 fraction.stratum = Some(lit);
1128 }
1129 stat.fractions.push(fraction);
1130 }
1131 if let Some(s) = seed {
1132 stat.seed = Some(*s);
1133 }
1134 relation.rel_type = Some(proto::relation::RelType::SampleBy(Box::new(stat)));
1135 }
1136
1137 LogicalPlan::Observe { input, name, exprs } => {
1138 let mut collect_metrics = proto::CollectMetrics::default();
1139 collect_metrics.input = Some(Box::new(input.to_proto()));
1140 collect_metrics.name = name.clone();
1141 for expr in exprs {
1142 collect_metrics.metrics.push(expr.to_proto());
1143 }
1144 relation.rel_type = Some(proto::relation::RelType::CollectMetrics(Box::new(
1145 collect_metrics,
1146 )));
1147 }
1148
1149 LogicalPlan::UnresolvedTableValuedFunction { name, arguments } => {
1150 let mut tvf = proto::UnresolvedTableValuedFunction::default();
1151 tvf.function_name = name.clone();
1152 for arg in arguments {
1153 tvf.arguments.push(arg.to_proto());
1154 }
1155 relation.rel_type =
1156 Some(proto::relation::RelType::UnresolvedTableValuedFunction(tvf));
1157 }
1158
1159 LogicalPlan::Zip { left, right } => {
1160 let mut zip = proto::Zip::default();
1161 zip.left = Some(Box::new(left.to_proto()));
1162 zip.right = Some(Box::new(right.to_proto()));
1163 relation.rel_type = Some(proto::relation::RelType::Zip(Box::new(zip)));
1164 }
1165
1166 LogicalPlan::MlTransform { ml_relation } => {
1167 return ml_relation.clone();
1168 }
1169 LogicalPlan::Catalog { catalog } => {
1170 relation.rel_type = Some(proto::relation::RelType::Catalog(catalog.clone()));
1171 return relation;
1172 }
1173 LogicalPlan::Transpose {
1174 input,
1175 index_columns,
1176 } => {
1177 let mut transpose = proto::Transpose::default();
1178 transpose.input = Some(Box::new(input.to_proto()));
1179 transpose.index_columns = index_columns.iter().map(|e| e.to_proto()).collect();
1180 relation.rel_type = Some(proto::relation::RelType::Transpose(Box::new(transpose)));
1181 return relation;
1182 }
1183 LogicalPlan::MapPartitions {
1184 input,
1185 func,
1186 is_barrier,
1187 } => {
1188 let mut mp = proto::MapPartitions::default();
1189 mp.input = Some(Box::new(input.to_proto()));
1190 mp.func = Some(func.to_proto());
1191 mp.is_barrier = Some(*is_barrier);
1192 relation.rel_type = Some(proto::relation::RelType::MapPartitions(Box::new(mp)));
1193 }
1194 LogicalPlan::GroupMap {
1195 input,
1196 grouping_expressions,
1197 func,
1198 sorting_expressions,
1199 initial_input,
1200 initial_grouping_expressions,
1201 is_map_groups_with_state,
1202 output_mode,
1203 timeout_conf,
1204 state_schema,
1205 transform_with_state_info,
1206 } => {
1207 let mut gm = proto::GroupMap::default();
1208 gm.input = Some(Box::new(input.to_proto()));
1209 for e in grouping_expressions {
1210 gm.grouping_expressions.push(e.to_proto());
1211 }
1212 gm.func = Some(func.to_proto());
1213 for e in sorting_expressions {
1214 gm.sorting_expressions.push(e.to_proto());
1215 }
1216 if let Some(ii) = initial_input {
1217 gm.initial_input = Some(Box::new(ii.to_proto()));
1218 }
1219 for e in initial_grouping_expressions {
1220 gm.initial_grouping_expressions.push(e.to_proto());
1221 }
1222 gm.is_map_groups_with_state = *is_map_groups_with_state;
1223 gm.output_mode = output_mode.clone();
1224 gm.timeout_conf = timeout_conf.clone();
1225 gm.state_schema = state_schema.as_ref().map(|d| d.to_proto());
1226 gm.transform_with_state_info =
1227 transform_with_state_info
1228 .as_ref()
1229 .map(|t| proto::TransformWithStateInfo {
1230 time_mode: t.time_mode.clone(),
1231 event_time_column_name: t.event_time_column_name.clone(),
1232 output_schema: t.output_schema.as_ref().map(|d| d.to_proto()),
1233 });
1234 relation.rel_type = Some(proto::relation::RelType::GroupMap(Box::new(gm)));
1235 }
1236 LogicalPlan::CoGroupMap {
1237 input,
1238 input_grouping_expressions,
1239 other,
1240 other_grouping_expressions,
1241 func,
1242 } => {
1243 let mut cg = proto::CoGroupMap::default();
1244 cg.input = Some(Box::new(input.to_proto()));
1245 for e in input_grouping_expressions {
1246 cg.input_grouping_expressions.push(e.to_proto());
1247 }
1248 cg.other = Some(Box::new(other.to_proto()));
1249 for e in other_grouping_expressions {
1250 cg.other_grouping_expressions.push(e.to_proto());
1251 }
1252 cg.func = Some(func.to_proto());
1253 relation.rel_type = Some(proto::relation::RelType::CoGroupMap(Box::new(cg)));
1254 }
1255 LogicalPlan::NearestByJoin {
1256 left,
1257 right,
1258 ranking_expression,
1259 num_results,
1260 join_type,
1261 mode,
1262 direction,
1263 } => {
1264 let mut nbj = proto::NearestByJoin::default();
1265 nbj.left = Some(Box::new(left.to_proto()));
1266 nbj.right = Some(Box::new(right.to_proto()));
1267 nbj.ranking_expression = Some(ranking_expression.to_proto());
1268 nbj.num_results = *num_results;
1269 nbj.join_type = join_type.clone();
1270 nbj.mode = mode.clone();
1271 nbj.direction = direction.clone();
1272 relation.rel_type = Some(proto::relation::RelType::NearestByJoin(Box::new(nbj)));
1273 }
1274 LogicalPlan::CommonInlineUdtf {
1275 function_name,
1276 deterministic,
1277 arguments,
1278 return_type,
1279 eval_type,
1280 command,
1281 python_ver,
1282 } => {
1283 let mut udtf = proto::PythonUdtf::default();
1284 if let Some(rt) = return_type {
1285 udtf.return_type = Some(rt.to_proto());
1286 }
1287 udtf.eval_type = *eval_type;
1288 udtf.command = bytes::Bytes::copy_from_slice(command);
1289 udtf.python_ver = python_ver.clone();
1290 let mut f = proto::CommonInlineUserDefinedTableFunction::default();
1291 f.function_name = function_name.clone();
1292 f.deterministic = *deterministic;
1293 f.arguments = arguments.iter().map(|a| a.to_proto()).collect();
1294 f.function = Some(
1295 proto::common_inline_user_defined_table_function::Function::PythonUdtf(udtf),
1296 );
1297 relation.rel_type =
1298 Some(proto::relation::RelType::CommonInlineUserDefinedTableFunction(f));
1299 }
1300 }
1301
1302 relation
1303 }
1304}
1305
1306pub fn range(start: i64, end: i64, step: i64) -> LogicalPlan {
1308 LogicalPlan::Range {
1309 start,
1310 end,
1311 step,
1312 num_partitions: None,
1313 }
1314}
1315
1316pub fn range_with_partitions(start: i64, end: i64, step: i64, num_partitions: i32) -> LogicalPlan {
1318 LogicalPlan::Range {
1319 start,
1320 end,
1321 step,
1322 num_partitions: Some(num_partitions),
1323 }
1324}
1325
1326pub fn sql(query: impl Into<String>) -> LogicalPlan {
1328 LogicalPlan::Sql {
1329 query: query.into(),
1330 pos_args: Vec::new(),
1331 named_args: HashMap::new(),
1332 }
1333}
1334
1335pub fn project<C: Into<Column>>(
1337 input: LogicalPlan,
1338 columns: impl IntoIterator<Item = C>,
1339) -> LogicalPlan {
1340 LogicalPlan::Project {
1341 input: Box::new(input),
1342 columns: columns.into_iter().map(Into::into).collect(),
1343 }
1344}
1345
1346pub fn filter(input: LogicalPlan, condition: Column) -> LogicalPlan {
1348 LogicalPlan::Filter {
1349 input: Box::new(input),
1350 condition,
1351 }
1352}
1353
1354pub fn aggregate(
1356 input: LogicalPlan,
1357 group_type: AggregateGroupType,
1358 grouping_expressions: Vec<Expression>,
1359 aggregate_expressions: Vec<Expression>,
1360) -> LogicalPlan {
1361 LogicalPlan::Aggregate {
1362 input: Box::new(input),
1363 group_type,
1364 grouping_expressions,
1365 aggregate_expressions,
1366 pivot_col: None,
1367 pivot_values: vec![],
1368 grouping_sets: vec![],
1369 }
1370}
1371
1372pub fn aggregate_with_pivot(
1374 input: LogicalPlan,
1375 group_type: AggregateGroupType,
1376 grouping_expressions: Vec<Expression>,
1377 aggregate_expressions: Vec<Expression>,
1378 pivot_col: Expression,
1379 pivot_values: Vec<Expression>,
1380) -> LogicalPlan {
1381 LogicalPlan::Aggregate {
1382 input: Box::new(input),
1383 group_type,
1384 grouping_expressions,
1385 aggregate_expressions,
1386 pivot_col: Some(pivot_col),
1387 pivot_values,
1388 grouping_sets: vec![],
1389 }
1390}
1391
1392pub fn aggregate_with_grouping_sets(
1394 input: LogicalPlan,
1395 grouping_expressions: Vec<Expression>,
1396 aggregate_expressions: Vec<Expression>,
1397 grouping_sets: Vec<Vec<Expression>>,
1398) -> LogicalPlan {
1399 LogicalPlan::Aggregate {
1400 input: Box::new(input),
1401 group_type: AggregateGroupType::GroupingSets,
1402 grouping_expressions,
1403 aggregate_expressions,
1404 pivot_col: None,
1405 pivot_values: vec![],
1406 grouping_sets,
1407 }
1408}
1409
1410pub fn join(
1412 left: LogicalPlan,
1413 right: LogicalPlan,
1414 join_type: JoinType,
1415 on: Option<Column>,
1416 using_columns: Vec<String>,
1417) -> LogicalPlan {
1418 LogicalPlan::Join {
1419 left: Box::new(left),
1420 right: Box::new(right),
1421 join_type,
1422 on,
1423 using_columns,
1424 }
1425}
1426
1427pub fn set_operation(
1429 left: LogicalPlan,
1430 right: LogicalPlan,
1431 set_op_type: SetOpType,
1432 is_all: bool,
1433 by_name: bool,
1434 allow_missing_columns: bool,
1435) -> LogicalPlan {
1436 LogicalPlan::SetOperation {
1437 left: Box::new(left),
1438 right: Box::new(right),
1439 set_op_type,
1440 is_all,
1441 by_name,
1442 allow_missing_columns,
1443 }
1444}
1445
1446pub fn limit(input: LogicalPlan, limit: i32) -> LogicalPlan {
1448 LogicalPlan::Limit {
1449 input: Box::new(input),
1450 limit,
1451 }
1452}
1453
1454pub fn offset(input: LogicalPlan, offset: i32) -> LogicalPlan {
1456 LogicalPlan::Offset {
1457 input: Box::new(input),
1458 offset,
1459 }
1460}
1461
1462pub fn tail(input: LogicalPlan, limit: i32) -> LogicalPlan {
1464 LogicalPlan::Tail {
1465 input: Box::new(input),
1466 limit,
1467 }
1468}
1469
1470pub fn deduplicate(
1472 input: LogicalPlan,
1473 all_columns_as_keys: bool,
1474 column_names: Vec<String>,
1475 within_watermark: bool,
1476) -> LogicalPlan {
1477 LogicalPlan::Deduplicate {
1478 input: Box::new(input),
1479 all_columns_as_keys,
1480 column_names,
1481 within_watermark,
1482 }
1483}
1484
1485pub fn sort(input: LogicalPlan, order: Vec<Expression>, is_global: bool) -> LogicalPlan {
1487 LogicalPlan::Sort {
1488 input: Box::new(input),
1489 order,
1490 is_global,
1491 }
1492}
1493
1494pub fn sample(
1496 input: LogicalPlan,
1497 lower_bound: f64,
1498 upper_bound: f64,
1499 with_replacement: bool,
1500 seed: Option<i64>,
1501) -> LogicalPlan {
1502 LogicalPlan::Sample {
1503 input: Box::new(input),
1504 lower_bound,
1505 upper_bound,
1506 with_replacement,
1507 seed,
1508 }
1509}
1510
1511pub fn repartition(input: LogicalPlan, num_partitions: i32, shuffle: bool) -> LogicalPlan {
1513 LogicalPlan::Repartition {
1514 input: Box::new(input),
1515 num_partitions,
1516 shuffle,
1517 }
1518}
1519
1520pub fn repartition_by_expression(
1522 input: LogicalPlan,
1523 num_partitions: i32,
1524 expressions: Vec<Expression>,
1525) -> LogicalPlan {
1526 LogicalPlan::RepartitionByExpression {
1527 input: Box::new(input),
1528 num_partitions,
1529 expressions,
1530 }
1531}
1532
1533pub fn with_columns(
1535 input: LogicalPlan,
1536 column_names: Vec<String>,
1537 columns: Vec<Column>,
1538) -> LogicalPlan {
1539 LogicalPlan::WithColumns {
1540 input: Box::new(input),
1541 column_names,
1542 columns,
1543 }
1544}
1545
1546pub fn with_columns_renamed(input: LogicalPlan, renames: HashMap<String, String>) -> LogicalPlan {
1548 LogicalPlan::WithColumnsRenamed {
1549 input: Box::new(input),
1550 renames,
1551 }
1552}
1553
1554pub fn drop(input: LogicalPlan, columns: Vec<String>) -> LogicalPlan {
1556 LogicalPlan::Drop {
1557 input: Box::new(input),
1558 columns,
1559 }
1560}
1561
1562pub fn to_df(input: LogicalPlan, column_names: Vec<String>) -> LogicalPlan {
1564 LogicalPlan::ToDF {
1565 input: Box::new(input),
1566 column_names,
1567 }
1568}
1569
1570pub fn to_schema(input: LogicalPlan, schema: DataType) -> LogicalPlan {
1572 LogicalPlan::ToSchema {
1573 input: Box::new(input),
1574 schema,
1575 }
1576}
1577
1578pub fn hint(input: LogicalPlan, name: impl Into<String>, parameters: Vec<String>) -> LogicalPlan {
1580 LogicalPlan::Hint {
1581 input: Box::new(input),
1582 name: name.into(),
1583 parameters,
1584 }
1585}
1586
1587pub fn unpivot(
1589 input: LogicalPlan,
1590 ids: Vec<Column>,
1591 values: Option<Vec<Column>>,
1592 variable_column_name: impl Into<String>,
1593 value_column_name: impl Into<String>,
1594) -> LogicalPlan {
1595 LogicalPlan::Unpivot {
1596 input: Box::new(input),
1597 ids,
1598 values,
1599 variable_column_name: variable_column_name.into(),
1600 value_column_name: value_column_name.into(),
1601 }
1602}
1603
1604pub fn na_fill(
1606 input: LogicalPlan,
1607 fill_value: crate::row::Value,
1608 columns: Vec<String>,
1609) -> LogicalPlan {
1610 LogicalPlan::NAFill {
1611 input: Box::new(input),
1612 fill_value,
1613 columns,
1614 }
1615}
1616
1617fn value_to_proto_literal(v: &crate::row::Value) -> proto::expression::Literal {
1619 use crate::row::Value;
1620 use proto::expression::literal::LiteralType;
1621 let mut lit = proto::expression::Literal::default();
1622 lit.literal_type = Some(match v {
1623 Value::Bool(b) => LiteralType::Boolean(*b),
1624 Value::Byte(x) => LiteralType::Byte(*x as i32),
1625 Value::Short(x) => LiteralType::Short(*x as i32),
1626 Value::Integer(x) => LiteralType::Integer(*x),
1627 Value::Long(x) => LiteralType::Long(*x),
1628 Value::Float(x) => LiteralType::Float(*x),
1629 Value::Double(x) => LiteralType::Double(*x),
1630 Value::String(s) => LiteralType::String(s.clone()),
1631 Value::Date(d) => LiteralType::Date(*d),
1632 Value::Timestamp(t) => LiteralType::Timestamp(*t),
1633 Value::Decimal {
1634 value,
1635 precision,
1636 scale,
1637 } => {
1638 let mut decimal = proto::expression::literal::Decimal::default();
1639 decimal.value = value.clone();
1640 if let Some(p) = precision {
1641 decimal.precision = Some(*p);
1642 }
1643 if let Some(s) = scale {
1644 decimal.scale = Some(*s);
1645 }
1646 LiteralType::Decimal(decimal)
1647 }
1648 other => LiteralType::String(format!("{:?}", other)),
1649 });
1650 lit
1651}
1652
1653fn str_to_proto_literal(s: &str) -> proto::expression::Literal {
1657 use proto::expression::literal::LiteralType;
1658 let mut lit = proto::expression::Literal::default();
1659 lit.literal_type = Some(match s.parse::<f64>() {
1660 Ok(v) => LiteralType::Double(v),
1661 Err(_) => LiteralType::String(s.to_string()),
1662 });
1663 lit
1664}
1665
1666pub fn na_drop(
1668 input: LogicalPlan,
1669 how: impl Into<String>,
1670 min_non_null: Option<i32>,
1671 columns: Vec<String>,
1672) -> LogicalPlan {
1673 LogicalPlan::NADrop {
1674 input: Box::new(input),
1675 how: how.into(),
1676 min_non_null,
1677 columns,
1678 }
1679}
1680
1681pub fn na_replace(
1683 input: LogicalPlan,
1684 replacements: Vec<(String, String)>,
1685 columns: Vec<String>,
1686) -> LogicalPlan {
1687 LogicalPlan::NAReplace {
1688 input: Box::new(input),
1689 replacements,
1690 columns,
1691 }
1692}
1693
1694pub fn describe(input: LogicalPlan, columns: Vec<String>) -> LogicalPlan {
1696 LogicalPlan::Describe {
1697 input: Box::new(input),
1698 columns,
1699 }
1700}
1701
1702pub fn summary(input: LogicalPlan, percentiles: Vec<String>) -> LogicalPlan {
1704 LogicalPlan::Summary {
1705 input: Box::new(input),
1706 percentiles,
1707 }
1708}
1709
1710pub fn col_regex(input: LogicalPlan, col_name: impl Into<String>) -> LogicalPlan {
1712 LogicalPlan::ColRegex {
1713 input: Box::new(input),
1714 col_name: col_name.into(),
1715 }
1716}
1717
1718pub fn subquery_alias(input: LogicalPlan, alias: impl Into<String>) -> LogicalPlan {
1720 LogicalPlan::SubqueryAlias {
1721 input: Box::new(input),
1722 alias: alias.into(),
1723 }
1724}
1725
1726pub fn local_relation(schema: DataType, data: Option<Vec<u8>>) -> LogicalPlan {
1728 LogicalPlan::LocalRelation { schema, data }
1729}
1730
1731pub fn cached_remote_relation(relation_id: impl Into<String>) -> LogicalPlan {
1733 LogicalPlan::CachedRemoteRelation {
1734 relation_id: relation_id.into(),
1735 }
1736}
1737
1738#[cfg(test)]
1739mod argfix_tests {
1740 use super::*;
1744 use crate::column::col;
1745 use crate::expression::LiteralExpression;
1746 use proto::expression::literal::LiteralType;
1747 use spark_connect_proto as proto;
1748
1749 fn base() -> LogicalPlan {
1750 range(0, 10, 1)
1751 }
1752
1753 #[test]
1754 fn local_relation_carries_schema() {
1755 match rel_type(local_relation(DataType::Struct { fields: vec![] }, None)) {
1758 proto::relation::RelType::LocalRelation(lr) => {
1759 assert!(lr.schema.is_some(), "LocalRelation must carry the schema");
1760 }
1761 _ => panic!("expected LocalRelation"),
1762 }
1763 }
1764 fn rel_type(p: LogicalPlan) -> proto::relation::RelType {
1765 p.to_proto().rel_type.expect("rel_type")
1766 }
1767
1768 #[test]
1769 fn dropna_how_all_any_thresh_are_distinct() {
1770 let mk = |how: &str, thresh: Option<i32>| LogicalPlan::NADrop {
1771 input: Box::new(base()),
1772 how: how.to_string(),
1773 min_non_null: thresh,
1774 columns: vec![],
1775 };
1776 let get = |p: LogicalPlan| match rel_type(p) {
1777 proto::relation::RelType::DropNa(d) => d.min_non_nulls,
1778 _ => panic!("expected DropNa"),
1779 };
1780 assert_eq!(get(mk("all", None)), Some(1));
1782 assert_eq!(get(mk("any", None)), None);
1783 assert_eq!(get(mk("any", Some(3))), Some(3));
1784 assert_ne!(get(mk("all", None)), get(mk("any", None)));
1786 }
1787
1788 #[test]
1789 fn hint_carries_parameters() {
1790 let p = LogicalPlan::Hint {
1791 input: Box::new(base()),
1792 name: "REPARTITION".to_string(),
1793 parameters: vec!["10".to_string(), "name".to_string()],
1794 };
1795 match rel_type(p) {
1796 proto::relation::RelType::Hint(h) => {
1797 assert_eq!(h.parameters.len(), 2, "parameters must be forwarded");
1798 let lit = match h.parameters[0].expr_type.as_ref().unwrap() {
1801 proto::expression::ExprType::Literal(l) => l.literal_type.clone().unwrap(),
1802 _ => panic!("expected literal param"),
1803 };
1804 assert!(matches!(lit, LiteralType::Integer(10)));
1805 }
1806 _ => panic!("expected Hint"),
1807 }
1808 }
1809
1810 #[test]
1811 fn replace_sets_string_literals() {
1812 let p = LogicalPlan::NAReplace {
1813 input: Box::new(base()),
1814 replacements: vec![("foo".to_string(), "bar".to_string())],
1815 columns: vec![],
1816 };
1817 match rel_type(p) {
1818 proto::relation::RelType::Replace(r) => {
1819 let repl = &r.replacements[0];
1820 let old = repl
1822 .old_value
1823 .as_ref()
1824 .unwrap()
1825 .literal_type
1826 .clone()
1827 .unwrap();
1828 let new = repl
1829 .new_value
1830 .as_ref()
1831 .unwrap()
1832 .literal_type
1833 .clone()
1834 .unwrap();
1835 assert!(matches!(old, LiteralType::String(ref s) if s == "foo"));
1836 assert!(matches!(new, LiteralType::String(ref s) if s == "bar"));
1837 }
1838 _ => panic!("expected Replace"),
1839 }
1840 }
1841
1842 #[test]
1843 fn pivot_values_are_serialized() {
1844 let p = aggregate_with_pivot(
1845 base(),
1846 AggregateGroupType::Pivot,
1847 vec![],
1848 vec![],
1849 col("k").expression().clone(),
1850 vec![
1851 Expression::Literal(LiteralExpression::string("a")),
1852 Expression::Literal(LiteralExpression::string("b")),
1853 ],
1854 );
1855 match rel_type(p) {
1856 proto::relation::RelType::Aggregate(a) => {
1857 let pivot = a.pivot.expect("pivot set");
1858 assert_eq!(
1859 pivot.values.len(),
1860 2,
1861 "explicit pivot values must be serialized"
1862 );
1863 }
1864 _ => panic!("expected Aggregate"),
1865 }
1866 }
1867
1868 #[test]
1869 fn fillna_double_literal() {
1870 let p = LogicalPlan::NAFill {
1871 input: Box::new(base()),
1872 fill_value: crate::row::Value::Double(1.5),
1873 columns: vec![],
1874 };
1875 match rel_type(p) {
1876 proto::relation::RelType::FillNa(f) => {
1877 let lit = f.values[0].literal_type.clone().unwrap();
1878 assert!(matches!(lit, LiteralType::Double(v) if (v - 1.5).abs() < 1e-9));
1879 }
1880 _ => panic!("expected FillNa"),
1881 }
1882 }
1883}