1use std::collections::HashMap;
6
7use spark_connect_core::client::ReattachableResponseStream;
8use spark_connect_core::error::{Result, SparkError};
9use spark_connect_core::runtime::{block_on, get_runtime};
10use spark_connect_proto as proto;
11
12use crate::column::Column;
13use crate::expression::Expression;
14use crate::plan::{AggregateGroupType, JoinType, LogicalPlan, SetOpType};
15use crate::row::{Row, Value};
16use crate::session::{ExecutionInfo, SparkSession};
17use crate::types::DataType;
18use crate::udf::CommonInlineUserDefinedFunctionExpression;
19
20#[derive(Clone)]
24pub struct DataFrame {
25 pub(crate) session: SparkSession,
26 pub(crate) plan: LogicalPlan,
27}
28
29pub struct LocalRowIterator {
34 current_rows: std::vec::IntoIter<Row>,
36 source: RowSource,
39 done: bool,
41}
42
43enum RowSource {
45 OnDemand {
48 session: SparkSession,
49 stream: ReattachableResponseStream,
50 execution_info: ExecutionInfo,
51 execution_recorded: bool,
52 },
53 Prefetch {
57 rx: tokio::sync::mpsc::Receiver<Result<Vec<Row>>>,
58 },
59}
60
61impl LocalRowIterator {
62 pub(crate) fn new(
64 session: SparkSession,
65 stream: ReattachableResponseStream,
66 prefetch_partitions: bool,
67 ) -> Self {
68 let source = if prefetch_partitions {
69 RowSource::Prefetch {
70 rx: spawn_prefetch(session, stream),
71 }
72 } else {
73 RowSource::OnDemand {
74 session,
75 stream,
76 execution_info: ExecutionInfo::default(),
77 execution_recorded: false,
78 }
79 };
80 LocalRowIterator {
81 current_rows: vec![].into_iter(),
82 source,
83 done: false,
84 }
85 }
86
87 fn fetch_next_batch(&mut self) -> Option<Result<Vec<Row>>> {
92 match &mut self.source {
93 RowSource::OnDemand {
94 session,
95 stream,
96 execution_info,
97 execution_recorded,
98 } => loop {
99 match block_on(stream.message()) {
100 Ok(Some(mut resp)) => {
101 capture_execution(&mut resp, execution_info, session);
102 if let Some(proto::execute_plan_response::ResponseType::ArrowBatch(batch)) =
103 resp.response_type
104 {
105 return Some(decode_arrow_batch(&batch));
106 }
107 }
109 Ok(None) => {
110 if !*execution_recorded {
111 session.record_execution(execution_info.clone());
112 *execution_recorded = true;
113 }
114 return None;
115 }
116 Err(e) => {
117 if !*execution_recorded {
118 session.record_execution(execution_info.clone());
119 *execution_recorded = true;
120 }
121 return Some(Err(e));
122 }
123 }
124 },
125 RowSource::Prefetch { rx } => block_on(rx.recv()),
126 }
127 }
128}
129
130impl Iterator for LocalRowIterator {
131 type Item = Result<Row>;
132
133 fn next(&mut self) -> Option<Self::Item> {
134 loop {
135 if let Some(row) = self.current_rows.next() {
136 return Some(Ok(row));
137 }
138 if self.done {
139 return None;
140 }
141 match self.fetch_next_batch() {
142 Some(Ok(rows)) => self.current_rows = rows.into_iter(),
143 Some(Err(e)) => {
144 self.done = true;
145 return Some(Err(e));
146 }
147 None => {
148 self.done = true;
149 return None;
150 }
151 }
152 }
153 }
154}
155
156fn spawn_prefetch(
161 session: SparkSession,
162 mut stream: ReattachableResponseStream,
163) -> tokio::sync::mpsc::Receiver<Result<Vec<Row>>> {
164 let (tx, rx) = tokio::sync::mpsc::channel::<Result<Vec<Row>>>(1);
165 get_runtime().spawn(async move {
166 let mut execution_info = ExecutionInfo::default();
167 loop {
168 match stream.message().await {
169 Ok(Some(mut resp)) => {
170 capture_execution(&mut resp, &mut execution_info, &session);
171 if let Some(proto::execute_plan_response::ResponseType::ArrowBatch(batch)) =
172 resp.response_type
173 {
174 match decode_arrow_batch(&batch) {
175 Ok(rows) => {
176 if tx.send(Ok(rows)).await.is_err() {
179 break;
180 }
181 }
182 Err(e) => {
183 let _ = tx.send(Err(e)).await;
184 break;
185 }
186 }
187 }
188 }
189 Ok(None) => break,
190 Err(e) => {
191 let _ = tx.send(Err(e)).await;
192 break;
193 }
194 }
195 }
196 session.record_execution(execution_info);
197 });
198 rx
199}
200
201impl DataFrame {
202 pub(crate) fn new(session: SparkSession, plan: LogicalPlan) -> Self {
204 DataFrame { session, plan }
205 }
206
207 pub(crate) fn plan(&self) -> &LogicalPlan {
209 &self.plan
210 }
211
212 pub fn select<C: Into<Column>>(&self, columns: impl IntoIterator<Item = C>) -> DataFrame {
214 let columns: Vec<Column> = columns.into_iter().map(Into::into).collect();
215 let plan = LogicalPlan::Project {
216 input: Box::new(self.plan.clone()),
217 columns,
218 };
219 DataFrame::new(self.session.clone(), plan)
220 }
221
222 pub fn filter(&self, condition: Column) -> DataFrame {
224 let plan = LogicalPlan::Filter {
225 input: Box::new(self.plan.clone()),
226 condition,
227 };
228 DataFrame::new(self.session.clone(), plan)
229 }
230
231 pub fn where_(&self, condition: Column) -> DataFrame {
233 self.filter(condition)
234 }
235
236 pub fn with_column(&self, name: &str, col: Column) -> DataFrame {
238 let plan = LogicalPlan::WithColumns {
239 input: Box::new(self.plan.clone()),
240 column_names: vec![name.to_string()],
241 columns: vec![col],
242 };
243 DataFrame::new(self.session.clone(), plan)
244 }
245
246 pub fn with_columns(&self, columns: Vec<(String, Column)>) -> DataFrame {
248 let (names, cols) = columns.into_iter().unzip();
249 let plan = LogicalPlan::WithColumns {
250 input: Box::new(self.plan.clone()),
251 column_names: names,
252 columns: cols,
253 };
254 DataFrame::new(self.session.clone(), plan)
255 }
256
257 pub fn with_column_renamed(&self, existing: &str, new: &str) -> DataFrame {
259 let mut renames = HashMap::new();
260 renames.insert(existing.to_string(), new.to_string());
261 let plan = LogicalPlan::WithColumnsRenamed {
262 input: Box::new(self.plan.clone()),
263 renames,
264 };
265 DataFrame::new(self.session.clone(), plan)
266 }
267
268 pub fn with_columns_renamed(&self, renames: Vec<(String, String)>) -> DataFrame {
270 let mut rename_map = HashMap::new();
271 for (old, new) in renames {
272 rename_map.insert(old, new);
273 }
274 let plan = LogicalPlan::WithColumnsRenamed {
275 input: Box::new(self.plan.clone()),
276 renames: rename_map,
277 };
278 DataFrame::new(self.session.clone(), plan)
279 }
280
281 pub fn drop(&self, columns: Vec<&str>) -> DataFrame {
283 let col_names = columns.iter().map(|s| s.to_string()).collect();
284 let plan = LogicalPlan::Drop {
285 input: Box::new(self.plan.clone()),
286 columns: col_names,
287 };
288 DataFrame::new(self.session.clone(), plan)
289 }
290
291 pub fn limit(&self, n: i32) -> DataFrame {
293 let plan = LogicalPlan::Limit {
294 input: Box::new(self.plan.clone()),
295 limit: n,
296 };
297 DataFrame::new(self.session.clone(), plan)
298 }
299
300 pub fn offset(&self, n: i32) -> DataFrame {
302 let plan = LogicalPlan::Offset {
303 input: Box::new(self.plan.clone()),
304 offset: n,
305 };
306 DataFrame::new(self.session.clone(), plan)
307 }
308
309 pub fn tail(&self, n: i32) -> DataFrame {
311 let plan = LogicalPlan::Tail {
312 input: Box::new(self.plan.clone()),
313 limit: n,
314 };
315 DataFrame::new(self.session.clone(), plan)
316 }
317
318 pub fn distinct(&self) -> DataFrame {
320 let plan = LogicalPlan::Deduplicate {
321 input: Box::new(self.plan.clone()),
322 all_columns_as_keys: true,
323 column_names: vec![],
324 within_watermark: false,
325 };
326 DataFrame::new(self.session.clone(), plan)
327 }
328
329 pub fn drop_duplicates(&self, column_names: Option<Vec<&str>>) -> DataFrame {
331 let all_cols = column_names.is_none();
332 let cols = column_names
333 .map(|c| c.iter().map(|s| s.to_string()).collect())
334 .unwrap_or_default();
335
336 let plan = LogicalPlan::Deduplicate {
337 input: Box::new(self.plan.clone()),
338 all_columns_as_keys: all_cols,
339 column_names: cols,
340 within_watermark: false,
341 };
342 DataFrame::new(self.session.clone(), plan)
343 }
344
345 pub fn sort(&self, columns: Vec<Expression>) -> DataFrame {
347 let plan = LogicalPlan::Sort {
348 input: Box::new(self.plan.clone()),
349 order: columns,
350 is_global: true,
351 };
352 DataFrame::new(self.session.clone(), plan)
353 }
354
355 pub fn order_by(&self, columns: Vec<Expression>) -> DataFrame {
357 self.sort(columns)
358 }
359
360 pub fn join(&self, right: &DataFrame, on: Option<Column>, join_type: JoinType) -> DataFrame {
362 let plan = LogicalPlan::Join {
363 left: Box::new(self.plan.clone()),
364 right: Box::new(right.plan.clone()),
365 join_type,
366 on,
367 using_columns: vec![],
368 };
369 DataFrame::new(self.session.clone(), plan)
370 }
371
372 pub fn join_using<S: Into<String>>(
374 &self,
375 right: &DataFrame,
376 using_columns: impl IntoIterator<Item = S>,
377 join_type: JoinType,
378 ) -> DataFrame {
379 let plan = LogicalPlan::Join {
380 left: Box::new(self.plan.clone()),
381 right: Box::new(right.plan.clone()),
382 join_type,
383 on: None,
384 using_columns: using_columns.into_iter().map(Into::into).collect(),
385 };
386 DataFrame::new(self.session.clone(), plan)
387 }
388
389 pub fn nearest_by_join(
395 &self,
396 other: &DataFrame,
397 ranking_expression: Column,
398 num_results: i32,
399 mode: &str,
400 direction: &str,
401 join_type: &str,
402 ) -> DataFrame {
403 let plan = LogicalPlan::NearestByJoin {
404 left: Box::new(self.plan.clone()),
405 right: Box::new(other.plan.clone()),
406 ranking_expression: ranking_expression.expression().clone(),
407 num_results,
408 join_type: join_type.to_string(),
409 mode: mode.to_string(),
410 direction: direction.to_string(),
411 };
412 DataFrame::new(self.session.clone(), plan)
413 }
414
415 pub fn cross_join(&self, right: &DataFrame) -> DataFrame {
417 self.join(right, None, JoinType::Cross)
418 }
419
420 pub fn lateral_join(
424 &self,
425 right: &DataFrame,
426 on: Option<Column>,
427 join_type: JoinType,
428 ) -> DataFrame {
429 let plan = LogicalPlan::LateralJoin {
430 left: Box::new(self.plan.clone()),
431 right: Box::new(right.plan.clone()),
432 join_type,
433 on,
434 };
435 DataFrame::new(self.session.clone(), plan)
436 }
437
438 pub fn union(&self, other: &DataFrame) -> DataFrame {
440 let plan = LogicalPlan::SetOperation {
441 left: Box::new(self.plan.clone()),
442 right: Box::new(other.plan.clone()),
443 set_op_type: SetOpType::Union,
444 is_all: true,
445 by_name: false,
446 allow_missing_columns: false,
447 };
448 DataFrame::new(self.session.clone(), plan)
449 }
450
451 pub fn union_by_name(&self, other: &DataFrame) -> DataFrame {
453 self.union_by_name_opt(other, false)
454 }
455
456 pub fn union_by_name_opt(&self, other: &DataFrame, allow_missing_columns: bool) -> DataFrame {
460 let plan = LogicalPlan::SetOperation {
461 left: Box::new(self.plan.clone()),
462 right: Box::new(other.plan.clone()),
463 set_op_type: SetOpType::Union,
464 is_all: true,
465 by_name: true,
466 allow_missing_columns,
467 };
468 DataFrame::new(self.session.clone(), plan)
469 }
470
471 pub fn intersect(&self, other: &DataFrame) -> DataFrame {
473 let plan = LogicalPlan::SetOperation {
474 left: Box::new(self.plan.clone()),
475 right: Box::new(other.plan.clone()),
476 set_op_type: SetOpType::Intersect,
477 is_all: false,
478 by_name: false,
479 allow_missing_columns: false,
480 };
481 DataFrame::new(self.session.clone(), plan)
482 }
483
484 pub fn subtract(&self, other: &DataFrame) -> DataFrame {
486 let plan = LogicalPlan::SetOperation {
487 left: Box::new(self.plan.clone()),
488 right: Box::new(other.plan.clone()),
489 set_op_type: SetOpType::Except,
490 is_all: false,
491 by_name: false,
492 allow_missing_columns: false,
493 };
494 DataFrame::new(self.session.clone(), plan)
495 }
496
497 pub fn repartition(&self, num_partitions: i32) -> DataFrame {
499 let plan = LogicalPlan::Repartition {
500 input: Box::new(self.plan.clone()),
501 num_partitions,
502 shuffle: true,
503 };
504 DataFrame::new(self.session.clone(), plan)
505 }
506
507 pub fn coalesce(&self, num_partitions: i32) -> DataFrame {
509 let plan = LogicalPlan::Repartition {
510 input: Box::new(self.plan.clone()),
511 num_partitions,
512 shuffle: false,
513 };
514 DataFrame::new(self.session.clone(), plan)
515 }
516
517 pub fn hint<S: Into<String>>(
519 &self,
520 name: &str,
521 parameters: impl IntoIterator<Item = S>,
522 ) -> DataFrame {
523 let plan = LogicalPlan::Hint {
524 input: Box::new(self.plan.clone()),
525 name: name.to_string(),
526 parameters: parameters.into_iter().map(Into::into).collect(),
527 };
528 DataFrame::new(self.session.clone(), plan)
529 }
530
531 pub fn broadcast(&self) -> DataFrame {
534 self.hint("broadcast", Vec::<String>::new())
535 }
536
537 pub fn to_df(&self, column_names: Vec<&str>) -> DataFrame {
539 let names = column_names.iter().map(|s| s.to_string()).collect();
540 let plan = LogicalPlan::ToDF {
541 input: Box::new(self.plan.clone()),
542 column_names: names,
543 };
544 DataFrame::new(self.session.clone(), plan)
545 }
546
547 pub fn alias(&self, alias: &str) -> DataFrame {
549 let plan = LogicalPlan::SubqueryAlias {
550 input: Box::new(self.plan.clone()),
551 alias: alias.to_string(),
552 };
553 DataFrame::new(self.session.clone(), plan)
554 }
555
556 pub fn map_in_pandas(
561 &self,
562 func: CommonInlineUserDefinedFunctionExpression,
563 is_barrier: bool,
564 ) -> DataFrame {
565 self.map_partitions(func, is_barrier)
566 }
567
568 pub fn map_in_arrow(
570 &self,
571 func: CommonInlineUserDefinedFunctionExpression,
572 is_barrier: bool,
573 ) -> DataFrame {
574 self.map_partitions(func, is_barrier)
575 }
576
577 fn map_partitions(
579 &self,
580 func: CommonInlineUserDefinedFunctionExpression,
581 is_barrier: bool,
582 ) -> DataFrame {
583 let plan = LogicalPlan::MapPartitions {
584 input: Box::new(self.plan.clone()),
585 func,
586 is_barrier,
587 };
588 DataFrame::new(self.session.clone(), plan)
589 }
590
591 pub fn foreach(&self, func: CommonInlineUserDefinedFunctionExpression) -> Result<()> {
595 let _ = self.map_partitions(func, false).collect()?;
596 Ok(())
597 }
598
599 pub fn foreach_partition(&self, func: CommonInlineUserDefinedFunctionExpression) -> Result<()> {
602 let _ = self.map_partitions(func, false).collect()?;
603 Ok(())
604 }
605
606 pub fn sample(&self, fraction: f64, seed: Option<i64>) -> DataFrame {
608 self.sample_opt(fraction, false, seed)
609 }
610
611 pub fn sample_opt(
614 &self,
615 fraction: f64,
616 with_replacement: bool,
617 seed: Option<i64>,
618 ) -> DataFrame {
619 let plan = LogicalPlan::Sample {
620 input: Box::new(self.plan.clone()),
621 lower_bound: 0.0,
622 upper_bound: fraction,
623 with_replacement,
624 seed,
625 };
626 DataFrame::new(self.session.clone(), plan)
627 }
628
629 pub fn group_by<C: Into<Column>>(
631 &self,
632 group_cols: impl IntoIterator<Item = C>,
633 ) -> crate::group::GroupedData {
634 let group_cols: Vec<Column> = group_cols.into_iter().map(Into::into).collect();
635 crate::group::GroupedData::new(self.clone(), group_cols, AggregateGroupType::GroupBy)
636 }
637
638 pub fn collect(&self) -> Result<Vec<Row>> {
640 let request = self.build_execute_request()?;
641 let mut stream = block_on(self.session.client().execute_plan_reattachable(request))?;
642
643 let mut rows = vec![];
644 let mut info = ExecutionInfo::default();
645 loop {
646 let resp = block_on(stream.message())?;
647 let Some(mut resp) = resp else {
648 break;
649 };
650 capture_execution(&mut resp, &mut info, &self.session);
651 if let Some(proto::execute_plan_response::ResponseType::ArrowBatch(batch)) =
652 resp.response_type
653 {
654 let batch_rows = decode_arrow_batch(&batch)?;
655 rows.extend(batch_rows);
656 }
657 }
658 self.session.record_execution(info);
659
660 Ok(rows)
661 }
662
663 pub fn to_local_iterator(&self, prefetch_partitions: bool) -> Result<LocalRowIterator> {
675 let request = self.build_execute_request()?;
676 let stream = block_on(self.session.client().execute_plan_reattachable(request))?;
677 Ok(LocalRowIterator::new(
678 self.session.clone(),
679 stream,
680 prefetch_partitions,
681 ))
682 }
683
684 pub fn execution_info(&self) -> Result<ExecutionInfo> {
690 self.session.last_execution_info().ok_or_else(|| {
691 SparkError::connect_msg("no execution info available; run an action first")
692 })
693 }
694
695 pub fn collect_record_batches(&self) -> Result<Vec<arrow::record_batch::RecordBatch>> {
701 let request = self.build_execute_request()?;
702 let mut stream = block_on(self.session.client().execute_plan_reattachable(request))?;
703
704 let mut batches = vec![];
705 let mut info = ExecutionInfo::default();
706 loop {
707 let resp = block_on(stream.message())?;
708 let Some(mut resp) = resp else {
709 break;
710 };
711 capture_execution(&mut resp, &mut info, &self.session);
712 if let Some(proto::execute_plan_response::ResponseType::ArrowBatch(batch)) =
713 resp.response_type
714 {
715 let record_batches = decode_arrow_record_batches(&batch)?;
716 batches.extend(record_batches);
717 }
718 }
719 self.session.record_execution(info);
720
721 Ok(batches)
722 }
723
724 pub fn count(&self) -> Result<i64> {
730 let count_expr = crate::functions::count(Column::new(Expression::Literal(
731 crate::expression::LiteralExpression::int(1),
732 )))
733 .expression()
734 .clone();
735
736 let plan = LogicalPlan::Aggregate {
737 input: Box::new(self.plan.clone()),
738 group_type: AggregateGroupType::GroupBy,
739 grouping_expressions: vec![],
740 aggregate_expressions: vec![count_expr],
741 pivot_col: None,
742 pivot_values: vec![],
743 grouping_sets: vec![],
744 };
745 let agg_df = DataFrame::new(self.session.clone(), plan);
746
747 let rows = agg_df.collect()?;
748 match rows.into_iter().next() {
749 Some(row) => row.get(0).and_then(|v| v.as_i64()).ok_or_else(|| {
750 SparkError::connect_msg("count() aggregate returned a non-integer value")
751 }),
752 None => Ok(0),
753 }
754 }
755
756 pub fn show(&self, n: usize) -> Result<()> {
758 let limited = self.limit(n as i32).collect()?;
759 for row in limited {
760 println!("{}", row);
761 }
762 Ok(())
763 }
764
765 pub fn schema(&self) -> Result<DataType> {
767 let request = self.build_analyze_request()?;
768 let response = block_on(self.session.client().analyze_plan(request))?;
769
770 if let Some(proto::analyze_plan_response::Result::Schema(schema)) = response.result {
771 Ok(DataType::from_proto(&schema.schema.ok_or_else(|| {
772 SparkError::connect_msg("Schema is missing")
773 })?)?)
774 } else {
775 Err(SparkError::connect_msg(
776 "Schema analyze failed: no schema in response",
777 ))
778 }
779 }
780
781 pub fn first(&self) -> Result<Option<Row>> {
783 self.limit(1).collect().map(|rows| rows.into_iter().next())
784 }
785
786 pub fn head(&self) -> Result<Option<Row>> {
788 self.first()
789 }
790
791 pub fn take(&self, n: usize) -> Result<Vec<Row>> {
793 self.limit(n as i32).collect()
794 }
795
796 pub fn is_empty(&self) -> Result<bool> {
798 self.limit(1).count().map(|c| c == 0)
799 }
800
801 pub fn columns(&self) -> Result<Vec<String>> {
803 let schema = self.schema()?;
804 match schema {
805 DataType::Struct { fields } => Ok(fields.iter().map(|f| f.name.clone()).collect()),
806 _ => Err(SparkError::connect_msg("Schema is not a struct type")),
807 }
808 }
809
810 fn build_execute_request(&self) -> Result<proto::ExecutePlanRequest> {
812 let mut relation = self.plan.to_proto();
814 assign_plan_ids(&mut relation, &self.session)?;
815
816 let mut request = proto::ExecutePlanRequest::default();
817 request.session_id = self.session.client().session_id().to_string();
818 request.user_context = Some(proto::UserContext::default());
819 request.tags = self.session.tags();
820
821 let mut plan = proto::Plan::default();
822 plan.op_type = Some(proto::plan::OpType::Root(relation));
823 request.plan = Some(plan);
824
825 Ok(request)
826 }
827
828 fn build_analyze_request(&self) -> Result<proto::AnalyzePlanRequest> {
830 let mut relation = self.plan.to_proto();
831 assign_plan_ids(&mut relation, &self.session)?;
832
833 let mut plan = proto::Plan::default();
834 plan.op_type = Some(proto::plan::OpType::Root(relation));
835
836 let mut schema = proto::analyze_plan_request::Schema::default();
837 schema.plan = Some(plan);
838
839 let mut request = proto::AnalyzePlanRequest::default();
840 request.session_id = self.session.client().session_id().to_string();
841 request.user_context = Some(proto::UserContext::default());
842 request.analyze = Some(proto::analyze_plan_request::Analyze::Schema(schema));
843
844 Ok(request)
845 }
846
847 pub fn write(&self) -> crate::readwriter::DataFrameWriter {
851 crate::readwriter::DataFrameWriter::new(self.session.clone(), self.plan.clone())
852 }
853
854 pub fn write_to(&self, table_name: &str) -> crate::readwriter::DataFrameWriterV2 {
859 crate::readwriter::DataFrameWriterV2::new(
860 self.session.clone(),
861 self.plan.clone(),
862 table_name,
863 )
864 }
865
866 pub fn merge_into(&self, table: &str, condition: Column) -> crate::merge::MergeIntoWriter {
872 crate::merge::MergeIntoWriter::new(
873 self.session.clone(),
874 self.plan.clone(),
875 table.to_string(),
876 condition,
877 )
878 }
879
880 pub fn write_stream(&self) -> crate::streaming::DataStreamWriter {
884 crate::streaming::DataStreamWriter::new(self.session.clone(), self.plan.clone())
885 }
886
887 fn memory_and_disk_deser() -> proto::StorageLevel {
889 proto::StorageLevel {
890 use_disk: true,
891 use_memory: true,
892 use_off_heap: false,
893 deserialized: true,
894 replication: 1,
895 }
896 }
897
898 fn analyze_relation(&self) -> Result<proto::Relation> {
901 let mut relation = self.plan.to_proto();
902 assign_plan_ids(&mut relation, &self.session)?;
903 Ok(relation)
904 }
905
906 fn analyze_request(
907 &self,
908 analyze: proto::analyze_plan_request::Analyze,
909 ) -> proto::AnalyzePlanRequest {
910 proto::AnalyzePlanRequest {
911 session_id: self.session.client().session_id().to_string(),
912 user_context: Some(proto::UserContext::default()),
913 analyze: Some(analyze),
914 ..Default::default()
915 }
916 }
917
918 pub fn cache(&self) -> Result<DataFrame> {
922 self.persist(Self::memory_and_disk_deser())
923 }
924
925 pub fn persist(&self, storage_level: proto::StorageLevel) -> Result<DataFrame> {
929 let persist = proto::analyze_plan_request::Persist {
930 relation: Some(self.analyze_relation()?),
931 storage_level: Some(storage_level),
932 };
933 let request = self.analyze_request(proto::analyze_plan_request::Analyze::Persist(persist));
934 block_on(self.session.client().analyze_plan(request))?;
935 Ok(self.clone())
936 }
937
938 pub fn unpersist(&self, blocking: bool) -> Result<DataFrame> {
940 let unpersist = proto::analyze_plan_request::Unpersist {
941 relation: Some(self.analyze_relation()?),
942 blocking: Some(blocking),
943 };
944 let request =
945 self.analyze_request(proto::analyze_plan_request::Analyze::Unpersist(unpersist));
946 block_on(self.session.client().analyze_plan(request))?;
947 Ok(self.clone())
948 }
949
950 pub fn checkpoint(&self) -> Result<DataFrame> {
952 self.checkpoint_impl(false, true)
953 }
954
955 pub fn local_checkpoint(&self) -> Result<DataFrame> {
957 self.checkpoint_impl(true, true)
958 }
959
960 fn checkpoint_impl(&self, local: bool, eager: bool) -> Result<DataFrame> {
964 let mut cmd = proto::CheckpointCommand::default();
965 cmd.relation = Some(self.plan.to_proto());
966 cmd.local = local;
967 cmd.eager = eager;
968 let responses = execute_command_collect(
969 &self.session,
970 proto::command::CommandType::CheckpointCommand(cmd),
971 )?;
972 for resp in &responses {
973 if let Some(proto::execute_plan_response::ResponseType::CheckpointCommandResult(res)) =
974 &resp.response_type
975 {
976 if let Some(rel) = &res.relation {
977 return Ok(DataFrame::new(
978 self.session.clone(),
979 LogicalPlan::CachedRemoteRelation {
980 relation_id: rel.relation_id.clone(),
981 },
982 ));
983 }
984 }
985 }
986 Err(SparkError::connect_msg(
987 "checkpoint: server returned no CheckpointCommandResult",
988 ))
989 }
990
991 pub fn create_temp_view(&self, name: &str) -> Result<()> {
993 self.create_view(name, false, false)
994 }
995
996 pub fn create_or_replace_temp_view(&self, name: &str) -> Result<()> {
998 self.create_view(name, true, false)
999 }
1000
1001 pub fn create_global_temp_view(&self, name: &str) -> Result<()> {
1003 self.create_view(name, false, true)
1004 }
1005
1006 pub fn create_or_replace_global_temp_view(&self, name: &str) -> Result<()> {
1008 self.create_view(name, true, true)
1009 }
1010
1011 fn create_view(&self, name: &str, replace: bool, global: bool) -> Result<()> {
1014 let mut input = self.plan.to_proto();
1015 assign_plan_ids(&mut input, &self.session)?;
1016 let mut cmd = proto::CreateDataFrameViewCommand::default();
1017 cmd.input = Some(input);
1018 cmd.name = name.to_string();
1019 cmd.is_global = global;
1020 cmd.replace = replace;
1021 execute_command(
1022 &self.session,
1023 proto::command::CommandType::CreateDataframeView(cmd),
1024 )
1025 }
1026
1027 pub fn explain(&self) -> Result<()> {
1030 self.explain_mode("simple")
1031 }
1032
1033 pub fn explain_mode(&self, mode: &str) -> Result<()> {
1037 use proto::analyze_plan_request::explain::ExplainMode;
1038 let explain_mode = match mode.to_lowercase().as_str() {
1039 "simple" => ExplainMode::Simple,
1040 "extended" => ExplainMode::Extended,
1041 "codegen" => ExplainMode::Codegen,
1042 "cost" => ExplainMode::Cost,
1043 "formatted" => ExplainMode::Formatted,
1044 other => {
1045 return Err(SparkError::value(
1046 "UNSUPPORTED_EXPLAIN_MODE",
1047 &[("mode", other)],
1048 ))
1049 }
1050 };
1051 let mut relation = self.plan.to_proto();
1052 assign_plan_ids(&mut relation, &self.session)?;
1053 let mut plan = proto::Plan::default();
1054 plan.op_type = Some(proto::plan::OpType::Root(relation));
1055 let mut ex = proto::analyze_plan_request::Explain::default();
1056 ex.plan = Some(plan);
1057 ex.explain_mode = explain_mode as i32;
1058 let mut request = proto::AnalyzePlanRequest::default();
1059 request.session_id = self.session.client().session_id().to_string();
1060 request.user_context = Some(proto::UserContext::default());
1061 request.analyze = Some(proto::analyze_plan_request::Analyze::Explain(ex));
1062 let response = block_on(self.session.client().analyze_plan(request))?;
1063 if let Some(proto::analyze_plan_response::Result::Explain(e)) = response.result {
1064 println!("{}", e.explain_string);
1065 }
1066 Ok(())
1067 }
1068
1069 pub fn with_watermark(&self, time_column: &str, delay_threshold: &str) -> DataFrame {
1071 let plan = LogicalPlan::WithWatermark {
1072 input: Box::new(self.plan.clone()),
1073 time_column: time_column.to_string(),
1074 delay_threshold: delay_threshold.to_string(),
1075 };
1076 DataFrame::new(self.session.clone(), plan)
1077 }
1078
1079 pub fn repartition_by_range(&self, num_partitions: i32, columns: Vec<Expression>) -> DataFrame {
1081 let plan = LogicalPlan::RepartitionByRange {
1082 input: Box::new(self.plan.clone()),
1083 num_partitions: Some(num_partitions),
1084 partition_exprs: columns,
1085 };
1086 DataFrame::new(self.session.clone(), plan)
1087 }
1088
1089 pub fn repartition_by_expressions(
1092 &self,
1093 num_partitions: i32,
1094 columns: Vec<Expression>,
1095 ) -> DataFrame {
1096 let plan = LogicalPlan::RepartitionByExpression {
1097 input: Box::new(self.plan.clone()),
1098 num_partitions,
1099 expressions: columns,
1100 };
1101 DataFrame::new(self.session.clone(), plan)
1102 }
1103
1104 pub fn to_schema(&self, column_names: Vec<&str>) -> DataFrame {
1106 self.to_df(column_names)
1107 }
1108
1109 pub fn melt(
1111 &self,
1112 id_vars: Vec<&str>,
1113 value_vars: Option<Vec<&str>>,
1114 var_name: &str,
1115 value_name: &str,
1116 ) -> DataFrame {
1117 use crate::column::col;
1118 let ids: Vec<Column> = id_vars.iter().map(|name| col(name)).collect();
1119 let vals: Option<Vec<Column>> =
1120 value_vars.map(|v| v.iter().map(|name| col(name)).collect());
1121
1122 let plan = LogicalPlan::Unpivot {
1123 input: Box::new(self.plan.clone()),
1124 ids,
1125 values: vals,
1126 variable_column_name: var_name.to_string(),
1127 value_column_name: value_name.to_string(),
1128 };
1129 DataFrame::new(self.session.clone(), plan)
1130 }
1131
1132 pub fn input_files(&self) -> Result<Vec<String>> {
1134 let mut relation = self.plan.to_proto();
1135 assign_plan_ids(&mut relation, &self.session)?;
1136 let mut plan = proto::Plan::default();
1137 plan.op_type = Some(proto::plan::OpType::Root(relation));
1138 let mut inp = proto::analyze_plan_request::InputFiles::default();
1139 inp.plan = Some(plan);
1140 let mut request = proto::AnalyzePlanRequest::default();
1141 request.session_id = self.session.client().session_id().to_string();
1142 request.user_context = Some(proto::UserContext::default());
1143 request.analyze = Some(proto::analyze_plan_request::Analyze::InputFiles(inp));
1144 let response = block_on(self.session.client().analyze_plan(request))?;
1145 match response.result {
1146 Some(proto::analyze_plan_response::Result::InputFiles(f)) => Ok(f.files),
1147 _ => Ok(vec![]),
1148 }
1149 }
1150
1151 pub fn observe(&self, name: &str, exprs: Vec<Expression>) -> DataFrame {
1153 let plan = LogicalPlan::Observe {
1154 input: Box::new(self.plan.clone()),
1155 name: name.to_string(),
1156 exprs,
1157 };
1158 DataFrame::new(self.session.clone(), plan)
1159 }
1160
1161 pub fn stat(&self) -> crate::group::StatFunctions {
1163 crate::group::StatFunctions::new(self.clone())
1164 }
1165
1166 pub fn na(&self) -> crate::group::NaFunctions {
1170 crate::group::NaFunctions::new(self.clone())
1171 }
1172
1173 pub fn agg(&self, expressions: Vec<Expression>) -> DataFrame {
1175 let plan = LogicalPlan::Aggregate {
1176 input: Box::new(self.plan.clone()),
1177 group_type: AggregateGroupType::GroupBy,
1178 grouping_expressions: vec![],
1179 aggregate_expressions: expressions,
1180 pivot_col: None,
1181 pivot_values: vec![],
1182 grouping_sets: vec![],
1183 };
1184 DataFrame::new(self.session.clone(), plan)
1185 }
1186
1187 pub fn select_expr(&self, exprs: Vec<&str>) -> DataFrame {
1194 let cols: Vec<Column> = exprs.iter().map(|e| crate::functions::expr(e)).collect();
1195 self.select(cols)
1196 }
1197
1198 pub fn fillna(&self, value: i64, subset: Option<Vec<&str>>) -> DataFrame {
1200 self.fillna_value(crate::row::Value::Long(value), subset)
1201 }
1202
1203 pub fn fillna_double(&self, value: f64, subset: Option<Vec<&str>>) -> DataFrame {
1205 self.fillna_value(crate::row::Value::Double(value), subset)
1206 }
1207
1208 pub fn fillna_string(&self, value: &str, subset: Option<Vec<&str>>) -> DataFrame {
1210 self.fillna_value(crate::row::Value::String(value.to_string()), subset)
1211 }
1212
1213 pub fn fillna_bool(&self, value: bool, subset: Option<Vec<&str>>) -> DataFrame {
1215 self.fillna_value(crate::row::Value::Bool(value), subset)
1216 }
1217
1218 pub fn fillna_value(&self, value: crate::row::Value, subset: Option<Vec<&str>>) -> DataFrame {
1220 let columns = subset
1221 .map(|v| v.iter().map(|s| s.to_string()).collect())
1222 .unwrap_or_default();
1223 let plan = LogicalPlan::NAFill {
1224 input: Box::new(self.plan.clone()),
1225 fill_value: value,
1226 columns,
1227 };
1228 DataFrame::new(self.session.clone(), plan)
1229 }
1230
1231 pub fn fillna_map(&self, pairs: Vec<(String, crate::row::Value)>) -> DataFrame {
1234 let (cols, values): (Vec<String>, Vec<crate::row::Value>) = pairs.into_iter().unzip();
1235 let plan = LogicalPlan::NAFillColumns {
1236 input: Box::new(self.plan.clone()),
1237 cols,
1238 values,
1239 };
1240 DataFrame::new(self.session.clone(), plan)
1241 }
1242
1243 pub fn dropna(
1245 &self,
1246 how: Option<&str>,
1247 thresh: Option<i32>,
1248 subset: Option<Vec<&str>>,
1249 ) -> DataFrame {
1250 let how_str = how.unwrap_or("any").to_string();
1251 let columns = subset
1252 .map(|v| v.iter().map(|s| s.to_string()).collect())
1253 .unwrap_or_default();
1254 let plan = LogicalPlan::NADrop {
1257 input: Box::new(self.plan.clone()),
1258 how: how_str,
1259 min_non_null: thresh,
1260 columns,
1261 };
1262 DataFrame::new(self.session.clone(), plan)
1263 }
1264
1265 pub fn replace(
1267 &self,
1268 to_replace: Vec<(String, String)>,
1269 subset: Option<Vec<&str>>,
1270 ) -> DataFrame {
1271 let columns = subset
1272 .map(|v| v.iter().map(|s| s.to_string()).collect())
1273 .unwrap_or_default();
1274 let plan = LogicalPlan::NAReplace {
1275 input: Box::new(self.plan.clone()),
1276 replacements: to_replace,
1277 columns,
1278 };
1279 DataFrame::new(self.session.clone(), plan)
1280 }
1281
1282 pub fn describe(&self, columns: Vec<&str>) -> DataFrame {
1284 let col_names = columns.iter().map(|s| s.to_string()).collect();
1285 let plan = LogicalPlan::Describe {
1286 input: Box::new(self.plan.clone()),
1287 columns: col_names,
1288 };
1289 DataFrame::new(self.session.clone(), plan)
1290 }
1291
1292 pub fn summary(&self, percentiles: Vec<&str>) -> DataFrame {
1294 let percs = percentiles.iter().map(|s| s.to_string()).collect();
1295 let plan = LogicalPlan::Summary {
1296 input: Box::new(self.plan.clone()),
1297 percentiles: percs,
1298 };
1299 DataFrame::new(self.session.clone(), plan)
1300 }
1301
1302 pub fn col_regex(&self, col_name: &str) -> DataFrame {
1304 let plan = LogicalPlan::ColRegex {
1305 input: Box::new(self.plan.clone()),
1306 col_name: col_name.to_string(),
1307 };
1308 DataFrame::new(self.session.clone(), plan)
1309 }
1310
1311 pub fn metadata_column(&self, name: &str) -> Column {
1313 Column::new(Expression::ColumnReference(
1314 crate::expression::ColumnReference::new(name).metadata(),
1315 ))
1316 }
1317
1318 pub fn rollup<C: Into<Column>>(
1320 &self,
1321 group_cols: impl IntoIterator<Item = C>,
1322 ) -> crate::group::GroupedData {
1323 let group_cols: Vec<Column> = group_cols.into_iter().map(Into::into).collect();
1324 crate::group::GroupedData::new(self.clone(), group_cols, AggregateGroupType::Rollup)
1325 }
1326
1327 pub fn cube<C: Into<Column>>(
1329 &self,
1330 group_cols: impl IntoIterator<Item = C>,
1331 ) -> crate::group::GroupedData {
1332 let group_cols: Vec<Column> = group_cols.into_iter().map(Into::into).collect();
1333 crate::group::GroupedData::new(self.clone(), group_cols, AggregateGroupType::Cube)
1334 }
1335
1336 pub fn grouping_sets(&self, group_cols: Vec<Vec<Column>>) -> crate::group::GroupedData {
1340 crate::group::GroupedData::new_grouping_sets(self.clone(), group_cols)
1341 }
1342
1343 pub fn sort_within_partitions(&self, columns: Vec<Expression>) -> DataFrame {
1345 let plan = LogicalPlan::Sort {
1346 input: Box::new(self.plan.clone()),
1347 order: columns,
1348 is_global: false,
1349 };
1350 DataFrame::new(self.session.clone(), plan)
1351 }
1352
1353 pub fn drop_duplicates_within_watermark(&self, column_names: Option<Vec<&str>>) -> DataFrame {
1355 let all_cols = column_names.is_none();
1356 let cols = column_names
1357 .map(|c| c.iter().map(|s| s.to_string()).collect())
1358 .unwrap_or_default();
1359
1360 let plan = LogicalPlan::Deduplicate {
1361 input: Box::new(self.plan.clone()),
1362 all_columns_as_keys: all_cols,
1363 column_names: cols,
1364 within_watermark: true,
1365 };
1366 DataFrame::new(self.session.clone(), plan)
1367 }
1368
1369 pub fn transform<F>(&self, f: F) -> DataFrame
1371 where
1372 F: Fn(&DataFrame) -> DataFrame,
1373 {
1374 f(self)
1375 }
1376
1377 pub fn random_split(&self, weights: Vec<f64>, seed: Option<i64>) -> Vec<DataFrame> {
1379 let total: f64 = weights.iter().sum();
1380 let normalized: Vec<f64> = weights.iter().map(|w| w / total).collect();
1381
1382 let mut results = vec![];
1383 let mut cumulative = 0.0;
1384
1385 for weight in normalized {
1386 let upper = cumulative + weight;
1387 let plan = LogicalPlan::Sample {
1388 input: Box::new(self.plan.clone()),
1389 lower_bound: cumulative,
1390 upper_bound: upper,
1391 with_replacement: false,
1392 seed,
1393 };
1394 results.push(DataFrame::new(self.session.clone(), plan));
1395 cumulative = upper;
1396 }
1397
1398 results
1399 }
1400
1401 pub fn print_schema(&self) -> Result<()> {
1403 let schema = self.schema()?;
1404 println!("{}", schema);
1405 Ok(())
1406 }
1407
1408 pub fn storage_level(&self) -> Result<proto::StorageLevel> {
1410 let get = proto::analyze_plan_request::GetStorageLevel {
1411 relation: Some(self.analyze_relation()?),
1412 };
1413 let request =
1414 self.analyze_request(proto::analyze_plan_request::Analyze::GetStorageLevel(get));
1415 let response = block_on(self.session.client().analyze_plan(request))?;
1416 match response.result {
1417 Some(proto::analyze_plan_response::Result::GetStorageLevel(g)) => {
1418 Ok(g.storage_level.unwrap_or_default())
1419 }
1420 _ => Ok(proto::StorageLevel::default()),
1421 }
1422 }
1423
1424 pub fn is_cached(&self) -> Result<bool> {
1429 let level = self.storage_level()?;
1430 Ok(level.use_memory || level.use_disk)
1431 }
1432
1433 pub fn dtypes(&self) -> Result<Vec<(String, String)>> {
1435 let schema = self.schema()?;
1436 match schema {
1437 DataType::Struct { fields } => {
1438 let dtypes = fields
1439 .iter()
1440 .map(|f| (f.name.clone(), f.data_type.to_string()))
1441 .collect();
1442 Ok(dtypes)
1443 }
1444 _ => Err(SparkError::connect_msg("Schema is not a struct type")),
1445 }
1446 }
1447
1448 pub fn semantic_hash(&self) -> Result<i32> {
1451 let mut relation = self.plan.to_proto();
1452 assign_plan_ids(&mut relation, &self.session)?;
1453 let mut plan = proto::Plan::default();
1454 plan.op_type = Some(proto::plan::OpType::Root(relation));
1455 let mut request = proto::AnalyzePlanRequest::default();
1456 request.session_id = self.session.client().session_id().to_string();
1457 request.user_context = Some(proto::UserContext::default());
1458 request.analyze = Some(proto::analyze_plan_request::Analyze::SemanticHash(
1459 proto::analyze_plan_request::SemanticHash { plan: Some(plan) },
1460 ));
1461 let resp = block_on(self.session.client().analyze_plan(request))?;
1462 match resp.result {
1463 Some(proto::analyze_plan_response::Result::SemanticHash(h)) => Ok(h.result),
1464 _ => Err(SparkError::connect_msg(
1465 "AnalyzePlan response did not contain a semantic hash",
1466 )),
1467 }
1468 }
1469
1470 pub fn same_semantics(&self, other: &DataFrame) -> Result<bool> {
1473 let mut self_rel = self.plan.to_proto();
1474 assign_plan_ids(&mut self_rel, &self.session)?;
1475 let mut other_rel = other.plan.to_proto();
1476 assign_plan_ids(&mut other_rel, &other.session)?;
1477 let mut target_plan = proto::Plan::default();
1478 target_plan.op_type = Some(proto::plan::OpType::Root(self_rel));
1479 let mut other_plan = proto::Plan::default();
1480 other_plan.op_type = Some(proto::plan::OpType::Root(other_rel));
1481 let mut request = proto::AnalyzePlanRequest::default();
1482 request.session_id = self.session.client().session_id().to_string();
1483 request.user_context = Some(proto::UserContext::default());
1484 request.analyze = Some(proto::analyze_plan_request::Analyze::SameSemantics(
1485 proto::analyze_plan_request::SameSemantics {
1486 target_plan: Some(target_plan),
1487 other_plan: Some(other_plan),
1488 },
1489 ));
1490 let resp = block_on(self.session.client().analyze_plan(request))?;
1491 match resp.result {
1492 Some(proto::analyze_plan_response::Result::SameSemantics(r)) => Ok(r.result),
1493 _ => Err(SparkError::connect_msg(
1494 "AnalyzePlan response did not contain a sameSemantics result",
1495 )),
1496 }
1497 }
1498
1499 pub fn to_json(&self) -> Result<Vec<String>> {
1505 let cols: Vec<Column> = self
1506 .columns()?
1507 .iter()
1508 .map(|c| crate::column::col(c))
1509 .collect();
1510 let json_col = crate::functions::to_json(crate::functions::r#struct(cols));
1511 let rows = self.select(vec![json_col]).collect()?;
1512 Ok(rows
1513 .iter()
1514 .map(|r| {
1515 r.get(0)
1516 .and_then(|v| v.as_str())
1517 .unwrap_or_default()
1518 .to_string()
1519 })
1520 .collect())
1521 }
1522
1523 pub fn union_all(&self, other: &DataFrame) -> DataFrame {
1525 let plan = LogicalPlan::SetOperation {
1526 left: Box::new(self.plan.clone()),
1527 right: Box::new(other.plan.clone()),
1528 set_op_type: SetOpType::Union,
1529 is_all: true,
1530 by_name: false,
1531 allow_missing_columns: false,
1532 };
1533 DataFrame::new(self.session.clone(), plan)
1534 }
1535
1536 pub fn except_all(&self, other: &DataFrame) -> DataFrame {
1538 let plan = LogicalPlan::SetOperation {
1539 left: Box::new(self.plan.clone()),
1540 right: Box::new(other.plan.clone()),
1541 set_op_type: SetOpType::Except,
1542 is_all: true,
1543 by_name: false,
1544 allow_missing_columns: false,
1545 };
1546 DataFrame::new(self.session.clone(), plan)
1547 }
1548
1549 pub fn intersect_all(&self, other: &DataFrame) -> DataFrame {
1551 let plan = LogicalPlan::SetOperation {
1552 left: Box::new(self.plan.clone()),
1553 right: Box::new(other.plan.clone()),
1554 set_op_type: SetOpType::Intersect,
1555 is_all: true,
1556 by_name: false,
1557 allow_missing_columns: false,
1558 };
1559 DataFrame::new(self.session.clone(), plan)
1560 }
1561
1562 pub fn unpivot<C: Into<Column>, D: Into<Column>>(
1564 &self,
1565 ids: impl IntoIterator<Item = C>,
1566 values: Option<impl IntoIterator<Item = D>>,
1567 variable_column_name: &str,
1568 value_column_name: &str,
1569 ) -> DataFrame {
1570 let ids: Vec<Column> = ids.into_iter().map(Into::into).collect();
1571 let values: Option<Vec<Column>> = values.map(|v| v.into_iter().map(Into::into).collect());
1572 let plan = LogicalPlan::Unpivot {
1573 input: Box::new(self.plan.clone()),
1574 ids,
1575 values,
1576 variable_column_name: variable_column_name.to_string(),
1577 value_column_name: value_column_name.to_string(),
1578 };
1579 DataFrame::new(self.session.clone(), plan)
1580 }
1581
1582 pub fn with_metadata(&self, column_name: &str, metadata: HashMap<String, String>) -> DataFrame {
1587 let metadata_json = serde_json::to_string(&metadata).unwrap_or_else(|_| "{}".to_string());
1588 let plan = LogicalPlan::WithColumnMetadata {
1589 input: Box::new(self.plan.clone()),
1590 column_name: column_name.to_string(),
1591 metadata_json,
1592 };
1593 DataFrame::new(self.session.clone(), plan)
1594 }
1595
1596 pub fn spark_session(&self) -> SparkSession {
1598 self.session.clone()
1599 }
1600
1601 pub fn is_local(&self) -> bool {
1603 matches!(self.plan, LogicalPlan::LocalRelation { .. })
1604 }
1605
1606 pub fn is_streaming(&self) -> bool {
1608 matches!(
1610 self.plan,
1611 LogicalPlan::Read {
1612 is_streaming: true,
1613 ..
1614 }
1615 )
1616 }
1617
1618 pub fn to_arrow(&self) -> Result<Vec<u8>> {
1624 record_batches_to_ipc(&self.collect_record_batches()?)
1625 }
1626
1627 #[cfg(feature = "datafusion")]
1643 pub fn to_datafusion(
1644 &self,
1645 ctx: &datafusion::prelude::SessionContext,
1646 ) -> Result<datafusion::dataframe::DataFrame> {
1647 record_batches_to_datafusion(ctx, self.collect_record_batches()?)
1648 }
1649
1650 #[cfg(feature = "polars")]
1662 pub fn to_polars(&self) -> Result<polars::frame::DataFrame> {
1663 record_batches_to_polars(&self.collect_record_batches()?)
1664 }
1665
1666 pub fn repartition_by_id(&self, num_partitions: i32, partition_id_col: Column) -> DataFrame {
1671 let direct =
1672 Expression::DirectShufflePartitionId(Box::new(partition_id_col.expression().clone()));
1673 self.repartition_by_expressions(num_partitions, vec![direct])
1674 }
1675
1676 pub fn zip_with_index(&self, index_col_name: &str) -> DataFrame {
1680 let star = Column::new(Expression::UnresolvedStar(None));
1681 let seq = Column::new(Expression::UnresolvedFunction(
1682 crate::expression::UnresolvedFunction::new("distributed_sequence_id", vec![]),
1683 ))
1684 .alias(index_col_name);
1685 self.select(vec![star, seq])
1686 }
1687
1688 pub fn to(&self, schema: DataType) -> DataFrame {
1693 let plan = LogicalPlan::ToSchema {
1694 input: Box::new(self.plan.clone()),
1695 schema,
1696 };
1697 DataFrame::new(self.session.clone(), plan)
1698 }
1699
1700 pub fn exists(&self) -> Result<bool> {
1702 self.limit(1).count().map(|c| c > 0)
1703 }
1704
1705 pub fn scalar(&self) -> Result<Option<Value>> {
1707 let rows = self.limit(1).collect()?;
1708 if rows.is_empty() {
1709 return Ok(None);
1710 }
1711 let row = &rows[0];
1712 Ok(row.get(0).cloned())
1713 }
1714
1715 pub fn transpose(&self) -> Result<DataFrame> {
1719 let plan = LogicalPlan::Transpose {
1720 input: Box::new(self.plan.clone()),
1721 index_columns: vec![],
1722 };
1723 Ok(DataFrame::new(self.session.clone(), plan))
1724 }
1725
1726 pub fn transpose_with_index(&self, index_column: Column) -> Result<DataFrame> {
1729 let plan = LogicalPlan::Transpose {
1730 input: Box::new(self.plan.clone()),
1731 index_columns: vec![index_column.expression().clone()],
1732 };
1733 Ok(DataFrame::new(self.session.clone(), plan))
1734 }
1735
1736 pub fn zip(&self, other: &DataFrame) -> Result<DataFrame> {
1738 let plan = LogicalPlan::Zip {
1739 left: Box::new(self.plan.clone()),
1740 right: Box::new(other.plan.clone()),
1741 };
1742 Ok(DataFrame {
1743 plan,
1744 session: self.session.clone(),
1745 })
1746 }
1747
1748 pub fn register_temp_table(&self, name: &str) -> Result<()> {
1750 self.create_temp_view(name)?;
1751 Ok(())
1752 }
1753
1754 pub fn as_table(&self, alias: &str) -> DataFrame {
1756 self.alias(alias)
1757 }
1758}
1759
1760pub(crate) fn build_input_relation(
1765 plan: &LogicalPlan,
1766 session: &SparkSession,
1767) -> Result<proto::Relation> {
1768 let mut relation = plan.to_proto();
1769 assign_plan_ids(&mut relation, session)?;
1770 Ok(relation)
1771}
1772
1773pub(crate) fn execute_command(
1779 session: &SparkSession,
1780 command_type: proto::command::CommandType,
1781) -> Result<()> {
1782 execute_command_collect(session, command_type).map(|_| ())
1783}
1784
1785pub(crate) fn execute_command_collect(
1788 session: &SparkSession,
1789 command_type: proto::command::CommandType,
1790) -> Result<Vec<proto::ExecutePlanResponse>> {
1791 let mut command = proto::Command::default();
1792 command.command_type = Some(command_type);
1793
1794 let mut plan = proto::Plan::default();
1795 plan.op_type = Some(proto::plan::OpType::Command(command));
1796
1797 let mut request = proto::ExecutePlanRequest::default();
1798 request.session_id = session.client().session_id().to_string();
1799 request.user_context = Some(proto::UserContext::default());
1800 request.tags = session.tags();
1801 request.plan = Some(plan);
1802
1803 let mut stream = block_on(session.client().execute_plan_reattachable(request))?;
1804 let mut info = ExecutionInfo::default();
1807 let mut responses = Vec::new();
1808 while let Some(mut resp) = block_on(stream.message())? {
1809 capture_execution(&mut resp, &mut info, session);
1810 responses.push(resp);
1811 }
1812 session.record_execution(info);
1813 Ok(responses)
1814}
1815
1816fn capture_execution(
1820 resp: &mut proto::ExecutePlanResponse,
1821 info: &mut ExecutionInfo,
1822 session: &SparkSession,
1823) {
1824 if let Some(metrics) = resp.metrics.take() {
1825 info.metrics = Some(metrics);
1826 }
1827 if !resp.observed_metrics.is_empty() {
1828 let metrics = std::mem::take(&mut resp.observed_metrics);
1829 session.profiler().accumulate_observed_metrics(&metrics);
1831 info.observed_metrics.extend(metrics);
1832 }
1833 if let Some(proto::execute_plan_response::ResponseType::ExecutionProgress(progress)) =
1834 &resp.response_type
1835 {
1836 session.notify_progress(progress);
1837 }
1838}
1839
1840pub(crate) fn assign_plan_ids(
1842 relation: &mut proto::Relation,
1843 session: &SparkSession,
1844) -> Result<()> {
1845 if let Some(rel_type) = &mut relation.rel_type {
1846 use proto::relation::RelType;
1847 match rel_type {
1848 RelType::Range(_) => {}
1849 RelType::Sql(_) => {}
1850 RelType::LocalRelation(_) => {}
1851 RelType::CachedRemoteRelation(_) => {}
1852 RelType::Project(proj) => {
1853 if let Some(input) = &mut proj.input {
1854 assign_plan_ids(input, session)?;
1855 }
1856 }
1857 RelType::Filter(filter) => {
1858 if let Some(input) = &mut filter.input {
1859 assign_plan_ids(input, session)?;
1860 }
1861 }
1862 RelType::Join(join) => {
1863 if let Some(left) = &mut join.left {
1864 assign_plan_ids(left, session)?;
1865 }
1866 if let Some(right) = &mut join.right {
1867 assign_plan_ids(right, session)?;
1868 }
1869 }
1870 RelType::SetOp(set_op) => {
1871 if let Some(left) = &mut set_op.left_input {
1872 assign_plan_ids(left, session)?;
1873 }
1874 if let Some(right) = &mut set_op.right_input {
1875 assign_plan_ids(right, session)?;
1876 }
1877 }
1878 RelType::Aggregate(agg) => {
1879 if let Some(input) = &mut agg.input {
1880 assign_plan_ids(input, session)?;
1881 }
1882 }
1883 RelType::Sort(sort) => {
1884 if let Some(input) = &mut sort.input {
1885 assign_plan_ids(input, session)?;
1886 }
1887 }
1888 RelType::Limit(limit) => {
1889 if let Some(input) = &mut limit.input {
1890 assign_plan_ids(input, session)?;
1891 }
1892 }
1893 RelType::Offset(offset) => {
1894 if let Some(input) = &mut offset.input {
1895 assign_plan_ids(input, session)?;
1896 }
1897 }
1898 RelType::Tail(tail) => {
1899 if let Some(input) = &mut tail.input {
1900 assign_plan_ids(input, session)?;
1901 }
1902 }
1903 RelType::Deduplicate(dedup) => {
1904 if let Some(input) = &mut dedup.input {
1905 assign_plan_ids(input, session)?;
1906 }
1907 }
1908 RelType::Repartition(repartition) => {
1909 if let Some(input) = &mut repartition.input {
1910 assign_plan_ids(input, session)?;
1911 }
1912 }
1913 RelType::RepartitionByExpression(repart_expr) => {
1914 if let Some(input) = &mut repart_expr.input {
1915 assign_plan_ids(input, session)?;
1916 }
1917 }
1918 RelType::WithColumns(with_cols) => {
1919 if let Some(input) = &mut with_cols.input {
1920 assign_plan_ids(input, session)?;
1921 }
1922 }
1923 RelType::WithColumnsRenamed(with_renamed) => {
1924 if let Some(input) = &mut with_renamed.input {
1925 assign_plan_ids(input, session)?;
1926 }
1927 }
1928 RelType::Drop(drop) => {
1929 if let Some(input) = &mut drop.input {
1930 assign_plan_ids(input, session)?;
1931 }
1932 }
1933 RelType::ToDf(to_df) => {
1934 if let Some(input) = &mut to_df.input {
1935 assign_plan_ids(input, session)?;
1936 }
1937 }
1938 RelType::ToSchema(to_schema) => {
1939 if let Some(input) = &mut to_schema.input {
1940 assign_plan_ids(input, session)?;
1941 }
1942 }
1943 RelType::Hint(hint) => {
1944 if let Some(input) = &mut hint.input {
1945 assign_plan_ids(input, session)?;
1946 }
1947 }
1948 RelType::Unpivot(unpivot) => {
1949 if let Some(input) = &mut unpivot.input {
1950 assign_plan_ids(input, session)?;
1951 }
1952 }
1953 RelType::Sample(sample) => {
1954 if let Some(input) = &mut sample.input {
1955 assign_plan_ids(input, session)?;
1956 }
1957 }
1958 RelType::FillNa(fill_na) => {
1959 if let Some(input) = &mut fill_na.input {
1960 assign_plan_ids(input, session)?;
1961 }
1962 }
1963 RelType::DropNa(drop_na) => {
1964 if let Some(input) = &mut drop_na.input {
1965 assign_plan_ids(input, session)?;
1966 }
1967 }
1968 RelType::Replace(replace) => {
1969 if let Some(input) = &mut replace.input {
1970 assign_plan_ids(input, session)?;
1971 }
1972 }
1973 RelType::Describe(describe) => {
1974 if let Some(input) = &mut describe.input {
1975 assign_plan_ids(input, session)?;
1976 }
1977 }
1978 RelType::Summary(summary) => {
1979 if let Some(input) = &mut summary.input {
1980 assign_plan_ids(input, session)?;
1981 }
1982 }
1983 RelType::SubqueryAlias(sq_alias) => {
1984 if let Some(input) = &mut sq_alias.input {
1985 assign_plan_ids(input, session)?;
1986 }
1987 }
1988 RelType::CachedLocalRelation(_cached) => {
1989 }
1991 RelType::WithWatermark(watermark) => {
1992 if let Some(input) = &mut watermark.input {
1993 assign_plan_ids(input, session)?;
1994 }
1995 }
1996 RelType::Crosstab(stat) => {
1997 if let Some(input) = &mut stat.input {
1998 assign_plan_ids(input, session)?;
1999 }
2000 }
2001 RelType::FreqItems(stat) => {
2002 if let Some(input) = &mut stat.input {
2003 assign_plan_ids(input, session)?;
2004 }
2005 }
2006 RelType::ApproxQuantile(stat) => {
2007 if let Some(input) = &mut stat.input {
2008 assign_plan_ids(input, session)?;
2009 }
2010 }
2011 RelType::Corr(stat) => {
2012 if let Some(input) = &mut stat.input {
2013 assign_plan_ids(input, session)?;
2014 }
2015 }
2016 RelType::Cov(stat) => {
2017 if let Some(input) = &mut stat.input {
2018 assign_plan_ids(input, session)?;
2019 }
2020 }
2021 RelType::SampleBy(stat) => {
2022 if let Some(input) = &mut stat.input {
2023 assign_plan_ids(input, session)?;
2024 }
2025 }
2026 RelType::CollectMetrics(metrics) => {
2027 if let Some(input) = &mut metrics.input {
2028 assign_plan_ids(input, session)?;
2029 }
2030 }
2031 _ => {
2032 }
2034 }
2035 }
2036
2037 if relation.common.is_none() {
2039 relation.common = Some(proto::RelationCommon::default());
2040 }
2041 if let Some(common) = &mut relation.common {
2042 common.plan_id = Some(session.next_plan_id());
2043 }
2044
2045 Ok(())
2046}
2047
2048fn decode_arrow_batch(batch: &proto::execute_plan_response::ArrowBatch) -> Result<Vec<Row>> {
2050 use arrow::ipc::reader::StreamReader;
2051 use std::io::Cursor;
2052
2053 if batch.data.is_empty() {
2054 return Ok(vec![]);
2055 }
2056
2057 let cursor = Cursor::new(&batch.data);
2058 let mut reader = StreamReader::try_new(cursor, None).map_err(|e| {
2059 SparkError::connect_msg(format!("Failed to create Arrow stream reader: {}", e))
2060 })?;
2061
2062 let mut rows = vec![];
2063
2064 while let Some(record_batch) = reader
2065 .next()
2066 .transpose()
2067 .map_err(|e| SparkError::connect_msg(format!("Failed to decode Arrow batch: {}", e)))?
2068 {
2069 let schema = record_batch.schema();
2070 let num_rows = record_batch.num_rows();
2071 let num_cols = record_batch.num_columns();
2072
2073 for row_idx in 0..num_rows {
2074 let mut field_names = vec![];
2075 let mut values = vec![];
2076
2077 for col_idx in 0..num_cols {
2078 let field_name = schema.field(col_idx).name().clone();
2079 let column = record_batch.column(col_idx);
2080
2081 let value = arrow_value_at(column.as_ref(), row_idx)?;
2082 field_names.push(field_name);
2083 values.push(value);
2084 }
2085
2086 rows.push(Row::new(field_names, values));
2087 }
2088 }
2089
2090 Ok(rows)
2091}
2092
2093fn decode_arrow_record_batches(
2098 batch: &proto::execute_plan_response::ArrowBatch,
2099) -> Result<Vec<arrow::record_batch::RecordBatch>> {
2100 use arrow::ipc::reader::StreamReader;
2101 use std::io::Cursor;
2102
2103 if batch.data.is_empty() {
2104 return Ok(vec![]);
2105 }
2106
2107 let cursor = Cursor::new(&batch.data);
2108 let mut reader = StreamReader::try_new(cursor, None).map_err(|e| {
2109 SparkError::connect_msg(format!("Failed to create Arrow stream reader: {}", e))
2110 })?;
2111
2112 let mut batches = vec![];
2113
2114 while let Some(record_batch) = reader
2115 .next()
2116 .transpose()
2117 .map_err(|e| SparkError::connect_msg(format!("Failed to decode Arrow batch: {}", e)))?
2118 {
2119 batches.push(record_batch);
2120 }
2121
2122 Ok(batches)
2123}
2124
2125fn i128_to_decimal_string(unscaled: i128, scale: i32) -> String {
2128 if scale <= 0 {
2129 return unscaled.to_string();
2130 }
2131 let scale = scale as usize;
2132 let neg = unscaled < 0;
2133 let mut digits = unscaled.unsigned_abs().to_string();
2134 if digits.len() <= scale {
2135 digits = format!("{}{}", "0".repeat(scale - digits.len() + 1), digits);
2136 }
2137 let point = digits.len() - scale;
2138 let s = format!("{}.{}", &digits[..point], &digits[point..]);
2139 if neg {
2140 format!("-{s}")
2141 } else {
2142 s
2143 }
2144}
2145
2146fn record_batches_to_ipc(batches: &[arrow::record_batch::RecordBatch]) -> Result<Vec<u8>> {
2150 use arrow::ipc::writer::FileWriter;
2151 let schema = match batches.first() {
2152 Some(b) => b.schema(),
2153 None => std::sync::Arc::new(arrow::datatypes::Schema::empty()),
2154 };
2155 let mut buf: Vec<u8> = Vec::new();
2156 {
2157 let mut writer = FileWriter::try_new(&mut buf, schema.as_ref())
2158 .map_err(|e| SparkError::connect_msg(format!("Arrow IPC writer init failed: {e}")))?;
2159 for batch in batches {
2160 writer
2161 .write(batch)
2162 .map_err(|e| SparkError::connect_msg(format!("Arrow IPC write failed: {e}")))?;
2163 }
2164 writer
2165 .finish()
2166 .map_err(|e| SparkError::connect_msg(format!("Arrow IPC finish failed: {e}")))?;
2167 }
2168 Ok(buf)
2169}
2170
2171#[cfg(feature = "datafusion")]
2175fn record_batches_to_datafusion(
2176 ctx: &datafusion::prelude::SessionContext,
2177 batches: Vec<arrow::record_batch::RecordBatch>,
2178) -> Result<datafusion::dataframe::DataFrame> {
2179 if batches.is_empty() {
2180 return Err(SparkError::connect_msg(
2181 "Cannot create DataFusion DataFrame from empty result",
2182 ));
2183 }
2184 ctx.read_batches(batches)
2185 .map_err(|e| SparkError::connect_msg(format!("Failed to create DataFusion DataFrame: {e}")))
2186}
2187
2188#[cfg(feature = "polars")]
2192fn record_batches_to_polars(
2193 batches: &[arrow::record_batch::RecordBatch],
2194) -> Result<polars::frame::DataFrame> {
2195 use polars::prelude::{IpcReader, SerReader};
2196 use std::io::Cursor;
2197 if batches.is_empty() {
2198 return Ok(polars::frame::DataFrame::empty());
2199 }
2200 let buf = record_batches_to_ipc(batches)?;
2201 IpcReader::new(Cursor::new(buf))
2202 .finish()
2203 .map_err(|e| SparkError::connect_msg(format!("Failed to create Polars DataFrame: {e}")))
2204}
2205
2206fn map_key_to_string(v: Value) -> String {
2212 match v {
2213 Value::String(s) => s,
2214 Value::Bool(b) => b.to_string(),
2215 Value::Byte(x) => x.to_string(),
2216 Value::Short(x) => x.to_string(),
2217 Value::Integer(x) => x.to_string(),
2218 Value::Long(x) => x.to_string(),
2219 Value::Float(x) => x.to_string(),
2220 Value::Double(x) => x.to_string(),
2221 Value::Date(d) => d.to_string(),
2222 Value::Timestamp(t) => t.to_string(),
2223 Value::Decimal { value, .. } => value,
2224 other => format!("{other:?}"),
2225 }
2226}
2227
2228pub(crate) fn arrow_value_at(array: &dyn arrow::array::Array, index: usize) -> Result<Value> {
2229 use arrow::array::*;
2230
2231 if array.is_null(index) {
2232 return Ok(Value::Null);
2233 }
2234
2235 if array.as_any().downcast_ref::<NullArray>().is_some() {
2237 return Ok(Value::Null);
2238 }
2239
2240 if let Some(arr) = array.as_any().downcast_ref::<BooleanArray>() {
2242 return Ok(Value::Bool(arr.value(index)));
2243 }
2244 if let Some(arr) = array.as_any().downcast_ref::<Int8Array>() {
2245 return Ok(Value::Byte(arr.value(index)));
2246 }
2247 if let Some(arr) = array.as_any().downcast_ref::<Int16Array>() {
2248 return Ok(Value::Short(arr.value(index)));
2249 }
2250 if let Some(arr) = array.as_any().downcast_ref::<Int32Array>() {
2251 return Ok(Value::Integer(arr.value(index)));
2252 }
2253 if let Some(arr) = array.as_any().downcast_ref::<Int64Array>() {
2254 return Ok(Value::Long(arr.value(index)));
2255 }
2256 if let Some(arr) = array.as_any().downcast_ref::<Float32Array>() {
2257 return Ok(Value::Float(arr.value(index)));
2258 }
2259 if let Some(arr) = array.as_any().downcast_ref::<Float64Array>() {
2260 return Ok(Value::Double(arr.value(index)));
2261 }
2262 if let Some(arr) = array.as_any().downcast_ref::<StringArray>() {
2263 return Ok(Value::String(arr.value(index).to_string()));
2264 }
2265 if let Some(arr) = array.as_any().downcast_ref::<BinaryArray>() {
2266 return Ok(Value::Binary(arr.value(index).to_vec()));
2267 }
2268 if let Some(arr) = array.as_any().downcast_ref::<Date32Array>() {
2269 return Ok(Value::Date(arr.value(index)));
2270 }
2271 if let Some(arr) = array.as_any().downcast_ref::<TimestampMicrosecondArray>() {
2272 return Ok(Value::Timestamp(arr.value(index)));
2273 }
2274 if let Some(arr) = array.as_any().downcast_ref::<UInt8Array>() {
2276 return Ok(Value::Short(arr.value(index) as i16));
2277 }
2278 if let Some(arr) = array.as_any().downcast_ref::<UInt16Array>() {
2279 return Ok(Value::Integer(arr.value(index) as i32));
2280 }
2281 if let Some(arr) = array.as_any().downcast_ref::<UInt32Array>() {
2282 return Ok(Value::Long(arr.value(index) as i64));
2283 }
2284 if let Some(arr) = array.as_any().downcast_ref::<UInt64Array>() {
2285 let val = arr.value(index);
2286 let i64_val = i64::try_from(val).map_err(|_| {
2287 SparkError::connect_msg(format!("UInt64 value {} exceeds i64 range", val))
2288 })?;
2289 return Ok(Value::Long(i64_val));
2290 }
2291 if let Some(arr) = array.as_any().downcast_ref::<Decimal128Array>() {
2293 let scale = arr.scale() as i32;
2294 return Ok(Value::Decimal {
2295 value: i128_to_decimal_string(arr.value(index), scale),
2296 precision: Some(arr.precision() as i32),
2297 scale: Some(scale),
2298 });
2299 }
2300 if let Some(arr) = array.as_any().downcast_ref::<LargeStringArray>() {
2302 return Ok(Value::String(arr.value(index).to_string()));
2303 }
2304 if let Some(arr) = array.as_any().downcast_ref::<LargeBinaryArray>() {
2305 return Ok(Value::Binary(arr.value(index).to_vec()));
2306 }
2307 if let Some(arr) = array.as_any().downcast_ref::<StringViewArray>() {
2308 return Ok(Value::String(arr.value(index).to_string()));
2309 }
2310 if let Some(arr) = array.as_any().downcast_ref::<BinaryViewArray>() {
2311 return Ok(Value::Binary(arr.value(index).to_vec()));
2312 }
2313 if let Some(arr) = array.as_any().downcast_ref::<TimestampSecondArray>() {
2315 return Ok(Value::Timestamp(arr.value(index) * 1_000_000));
2316 }
2317 if let Some(arr) = array.as_any().downcast_ref::<TimestampMillisecondArray>() {
2318 return Ok(Value::Timestamp(arr.value(index) * 1_000));
2319 }
2320 if let Some(arr) = array.as_any().downcast_ref::<TimestampNanosecondArray>() {
2321 return Ok(Value::Timestamp(arr.value(index) / 1_000));
2322 }
2323 if let Some(arr) = array.as_any().downcast_ref::<Date64Array>() {
2324 return Ok(Value::Date((arr.value(index) / 86_400_000) as i32));
2325 }
2326 if let Some(arr) = array.as_any().downcast_ref::<ListArray>() {
2328 let child = arr.value(index);
2329 let mut items = Vec::with_capacity(child.len());
2330 for i in 0..child.len() {
2331 items.push(arrow_value_at(child.as_ref(), i)?);
2332 }
2333 return Ok(Value::List(items));
2334 }
2335 if let Some(arr) = array.as_any().downcast_ref::<StructArray>() {
2336 let is_variant = arr.fields().iter().any(|f| {
2341 f.metadata()
2342 .get("variant")
2343 .map(|v| v == "true")
2344 .unwrap_or(false)
2345 });
2346 if is_variant {
2347 let bin_field = |name: &str| -> Result<Vec<u8>> {
2348 match arr.column_by_name(name) {
2349 Some(col) => match arrow_value_at(col.as_ref(), index)? {
2350 Value::Binary(b) => Ok(b),
2351 Value::Null => Ok(vec![]),
2352 _ => Err(SparkError::connect_msg("variant field is not binary")),
2353 },
2354 None => Ok(vec![]),
2355 }
2356 };
2357 return Ok(Value::Variant {
2358 value: bin_field("value")?,
2359 metadata: bin_field("metadata")?,
2360 });
2361 }
2362 let mut fields = Vec::new();
2363 for (f, col) in arr.fields().iter().zip(arr.columns()) {
2364 fields.push((f.name().clone(), arrow_value_at(col.as_ref(), index)?));
2365 }
2366 return Ok(Value::Struct(fields));
2367 }
2368 if let Some(arr) = array.as_any().downcast_ref::<MapArray>() {
2369 let entries = arr.value(index);
2370 let keys = entries.column(0);
2371 let vals = entries.column(1);
2372 let mut map = std::collections::BTreeMap::new();
2373 for i in 0..entries.len() {
2374 let k = map_key_to_string(arrow_value_at(keys.as_ref(), i)?);
2378 map.insert(k, arrow_value_at(vals.as_ref(), i)?);
2379 }
2380 return Ok(Value::Map(map));
2381 }
2382 if let Some(arr) = array.as_any().downcast_ref::<Decimal256Array>() {
2384 return Ok(Value::Decimal {
2385 value: arr.value_as_string(index),
2386 precision: Some(arr.precision() as i32),
2387 scale: Some(arr.scale() as i32),
2388 });
2389 }
2390 if let Some(arr) = array.as_any().downcast_ref::<FixedSizeBinaryArray>() {
2391 return Ok(Value::Binary(arr.value(index).to_vec()));
2392 }
2393 if let Some(arr) = array.as_any().downcast_ref::<Time64MicrosecondArray>() {
2395 return Ok(Value::String(micros_to_time_string(arr.value(index))));
2396 }
2397 if let Some(arr) = array.as_any().downcast_ref::<Time64NanosecondArray>() {
2398 return Ok(Value::String(micros_to_time_string(
2399 arr.value(index) / 1_000,
2400 )));
2401 }
2402 if let Some(arr) = array.as_any().downcast_ref::<Time32MillisecondArray>() {
2403 return Ok(Value::String(micros_to_time_string(
2404 arr.value(index) as i64 * 1_000,
2405 )));
2406 }
2407 if let Some(arr) = array.as_any().downcast_ref::<Time32SecondArray>() {
2408 return Ok(Value::String(micros_to_time_string(
2409 arr.value(index) as i64 * 1_000_000,
2410 )));
2411 }
2412 if let Some(arr) = array.as_any().downcast_ref::<IntervalYearMonthArray>() {
2414 let months = arr.value(index);
2415 return Ok(Value::String(format!(
2416 "{}-{}",
2417 months / 12,
2418 (months % 12).abs()
2419 )));
2420 }
2421 if let Some(arr) = array.as_any().downcast_ref::<IntervalDayTimeArray>() {
2422 let v = arr.value(index);
2423 return Ok(Value::String(format!(
2424 "{} days {} ms",
2425 v.days, v.milliseconds
2426 )));
2427 }
2428 if let Some(arr) = array.as_any().downcast_ref::<IntervalMonthDayNanoArray>() {
2429 let v = arr.value(index);
2430 return Ok(Value::String(format!(
2431 "{} months {} days {} ns",
2432 v.months, v.days, v.nanoseconds
2433 )));
2434 }
2435
2436 Err(SparkError::connect_msg(format!(
2437 "Unsupported Arrow type {:?} - cannot convert to Value",
2438 array.data_type()
2439 )))
2440}
2441
2442fn micros_to_time_string(micros: i64) -> String {
2444 let total_secs = micros.div_euclid(1_000_000);
2445 let us = micros.rem_euclid(1_000_000);
2446 let (h, m, s) = (total_secs / 3600, (total_secs % 3600) / 60, total_secs % 60);
2447 if us == 0 {
2448 format!("{h:02}:{m:02}:{s:02}")
2449 } else {
2450 format!("{h:02}:{m:02}:{s:02}.{us:06}")
2451 }
2452}
2453
2454#[cfg(test)]
2455mod cache_tests {
2456 use super::*;
2457 use prost::Message;
2458
2459 #[test]
2460 fn cache_default_is_memory_and_disk_deser() {
2461 let sl = DataFrame::memory_and_disk_deser();
2462 assert!(sl.use_memory && sl.use_disk && sl.deserialized);
2463 assert!(!sl.use_off_heap);
2464 assert_eq!(sl.replication, 1);
2465 }
2466
2467 #[test]
2468 fn persist_request_carries_storage_level_over_the_wire() {
2469 let persist = proto::analyze_plan_request::Persist {
2472 relation: None,
2473 storage_level: Some(DataFrame::memory_and_disk_deser()),
2474 };
2475 let decoded =
2476 proto::analyze_plan_request::Persist::decode(persist.encode_to_vec().as_slice())
2477 .unwrap();
2478 let sl = decoded
2479 .storage_level
2480 .expect("storage_level must be present");
2481 assert!(sl.use_memory && sl.use_disk && sl.deserialized && sl.replication == 1);
2482 }
2483
2484 #[test]
2485 fn get_storage_level_response_maps_to_is_cached() {
2486 let cached = proto::StorageLevel {
2488 use_memory: true,
2489 ..Default::default()
2490 };
2491 let uncached = proto::StorageLevel::default();
2492 assert!(cached.use_memory || cached.use_disk);
2493 assert!(!(uncached.use_memory || uncached.use_disk));
2494 }
2495
2496 #[test]
2497 fn to_local_iterator_builds_same_plan_as_collect() {
2498 let _iter: LocalRowIterator;
2502 }
2505}
2506
2507#[cfg(test)]
2513mod conversion_tests {
2514 use super::*;
2515 use arrow::array::{Int64Array, StringArray};
2516 use arrow::datatypes::{DataType as ArrowDataType, Field, Schema};
2517 use arrow::record_batch::RecordBatch;
2518 use std::sync::Arc;
2519
2520 fn sample_batch() -> RecordBatch {
2521 let schema = Arc::new(Schema::new(vec![
2522 Field::new("id", ArrowDataType::Int64, false),
2523 Field::new("name", ArrowDataType::Utf8, false),
2524 ]));
2525 RecordBatch::try_new(
2526 schema,
2527 vec![
2528 Arc::new(Int64Array::from(vec![1, 2, 3])),
2529 Arc::new(StringArray::from(vec!["a", "b", "c"])),
2530 ],
2531 )
2532 .unwrap()
2533 }
2534
2535 #[test]
2536 fn to_arrow_ipc_round_trips() {
2537 use arrow::ipc::reader::FileReader;
2538 use std::io::Cursor;
2539
2540 let ipc = record_batches_to_ipc(&[sample_batch()]).expect("ipc encode");
2541 let reader = FileReader::try_new(Cursor::new(ipc), None).expect("ipc decode");
2542 let batches: Vec<_> = reader.map(|b| b.unwrap()).collect();
2543 let total: usize = batches.iter().map(|b| b.num_rows()).sum();
2544 assert_eq!(total, 3, "round-trip must preserve all rows");
2545 assert_eq!(batches[0].num_columns(), 2);
2546 let ids = batches[0]
2547 .column(0)
2548 .as_any()
2549 .downcast_ref::<Int64Array>()
2550 .unwrap();
2551 assert_eq!(ids.values(), &[1, 2, 3]);
2552 }
2553
2554 #[test]
2555 fn to_arrow_ipc_empty_is_valid() {
2556 use arrow::ipc::reader::FileReader;
2558 use std::io::Cursor;
2559 let ipc = record_batches_to_ipc(&[]).expect("empty ipc");
2560 let reader = FileReader::try_new(Cursor::new(ipc), None).expect("empty ipc decode");
2561 assert_eq!(reader.map(|b| b.unwrap().num_rows()).sum::<usize>(), 0);
2562 }
2563
2564 #[cfg(feature = "datafusion")]
2565 #[test]
2566 fn to_datafusion_preserves_rows_and_columns() {
2567 use datafusion::prelude::SessionContext;
2568 use spark_connect_core::runtime::block_on;
2569
2570 let ctx = SessionContext::new();
2571 let df = record_batches_to_datafusion(&ctx, vec![sample_batch()]).expect("to datafusion");
2572 let collected = block_on(df.collect()).expect("collect datafusion");
2575 assert_eq!(collected.iter().map(|b| b.num_rows()).sum::<usize>(), 3);
2576 assert_eq!(collected[0].num_columns(), 2);
2577 }
2578
2579 #[cfg(feature = "datafusion")]
2580 #[test]
2581 fn to_datafusion_empty_errors() {
2582 use datafusion::prelude::SessionContext;
2583 let ctx = SessionContext::new();
2584 assert!(record_batches_to_datafusion(&ctx, vec![]).is_err());
2585 }
2586
2587 #[cfg(feature = "polars")]
2588 #[test]
2589 fn to_polars_preserves_shape() {
2590 let pdf = record_batches_to_polars(&[sample_batch()]).expect("to polars");
2593 assert_eq!(
2594 pdf.height(),
2595 3,
2596 "row count must survive the Arrow-IPC bridge"
2597 );
2598 assert_eq!(
2599 pdf.width(),
2600 2,
2601 "column count must survive the Arrow-IPC bridge"
2602 );
2603 }
2604
2605 #[cfg(feature = "polars")]
2606 #[test]
2607 fn to_polars_empty_is_empty() {
2608 let pdf = record_batches_to_polars(&[]).expect("empty polars");
2609 assert_eq!(pdf.height(), 0);
2610 }
2611}
2612
2613#[cfg(test)]
2614mod plan_construction_tests {
2615 use super::*;
2616 use crate::session::SparkSession;
2617
2618 fn session() -> SparkSession {
2619 SparkSession::builder()
2620 .remote("sc://localhost:15002")
2621 .get_or_create()
2622 .expect("failed to build session")
2623 }
2624
2625 #[test]
2626 fn with_watermark_plan() {
2627 let spark = session();
2628 let df = spark.range(3).unwrap();
2629 let result = df.with_watermark("timestamp", "1 minute");
2630 match &result.plan {
2631 LogicalPlan::WithWatermark {
2632 time_column,
2633 delay_threshold,
2634 ..
2635 } => {
2636 assert_eq!(time_column, "timestamp");
2637 assert_eq!(delay_threshold, "1 minute");
2638 }
2639 _ => panic!("expected WithWatermark plan"),
2640 }
2641 }
2642
2643 #[test]
2644 fn with_metadata_plan() {
2645 let spark = session();
2646 let df = spark.range(3).unwrap();
2647 let mut metadata = std::collections::HashMap::new();
2648 metadata.insert("key".to_string(), "value".to_string());
2649 let result = df.with_metadata("col", metadata);
2650 match &result.plan {
2651 LogicalPlan::WithColumnMetadata {
2652 column_name,
2653 metadata_json,
2654 ..
2655 } => {
2656 assert_eq!(column_name, "col");
2657 assert!(!metadata_json.is_empty());
2658 }
2659 _ => panic!("expected WithColumnMetadata plan"),
2660 }
2661 }
2662
2663 #[test]
2664 fn random_split_plan() {
2665 let spark = session();
2666 let df = spark.range(10).unwrap();
2667 let dfs = df.random_split(vec![0.7, 0.3], None);
2668 assert_eq!(dfs.len(), 2);
2669 for split_df in &dfs {
2671 match &split_df.plan {
2672 LogicalPlan::Sample {
2673 with_replacement: false,
2674 ..
2675 } => {
2676 }
2678 _ => panic!("expected Sample plan"),
2679 }
2680 }
2681 }
2682
2683 #[test]
2684 fn replace_plan() {
2685 let spark = session();
2686 let df = spark.range(3).unwrap();
2687 let replacements = vec![("old".to_string(), "new".to_string())];
2688 let result = df.replace(replacements, Some(vec!["col"]));
2689 match &result.plan {
2690 LogicalPlan::NAReplace { replacements, .. } => {
2691 assert_eq!(replacements.len(), 1);
2692 }
2693 _ => panic!("expected NAReplace plan"),
2694 }
2695 }
2696
2697 #[test]
2701 fn builders_construct_and_serialize() {
2702 use crate::functions::col;
2703 use crate::types::{DataType, StructField};
2704
2705 let spark = session();
2706 let df = spark.range(5).unwrap();
2707 let df2 = spark.range(5).unwrap();
2708 let e = || col("id").expression().clone();
2709 let ser = |d: &DataFrame| {
2710 build_input_relation(d.plan(), &spark).expect("plan serializes to a relation");
2711 };
2712
2713 ser(&df.select(vec![col("id")]));
2714 ser(&df.filter(col("id")));
2715 ser(&df.where_(col("id")));
2716 ser(&df.with_column("x", col("id")));
2717 ser(&df.with_column_renamed("id", "y"));
2718 ser(&df.drop(vec!["id"]));
2719 ser(&df.limit(3));
2720 ser(&df.offset(1));
2721 ser(&df.distinct());
2722 ser(&df.drop_duplicates(Some(vec!["id"])));
2723 ser(&df.sort(vec![e()]));
2724 ser(&df.order_by(vec![e()]));
2725 ser(&df.sort_within_partitions(vec![e()]));
2726 ser(&df.cross_join(&df2));
2727 ser(&df.union(&df2));
2728 ser(&df.union_all(&df2));
2729 ser(&df.union_by_name(&df2));
2730 ser(&df.intersect(&df2));
2731 ser(&df.intersect_all(&df2));
2732 ser(&df.subtract(&df2));
2733 ser(&df.except_all(&df2));
2734 ser(&df.repartition(4));
2735 ser(&df.coalesce(2));
2736 ser(&df.repartition_by_range(3, vec![e()]));
2737 ser(&df.hint("broadcast", Vec::<String>::new()));
2738 ser(&df.to_df(vec!["a"]));
2739 ser(&df.alias("t"));
2740 ser(&df.sample(0.5, Some(1)));
2741 ser(&df.select_expr(vec!["id + 1"]));
2742 ser(&df.col_regex("id"));
2743 ser(&df.describe(vec!["id"]));
2744 ser(&df.summary(vec!["count"]));
2745 ser(&df.as_table("t2"));
2746 ser(&df.to(DataType::Struct {
2747 fields: vec![StructField {
2748 name: "id".to_string(),
2749 data_type: DataType::Long,
2750 nullable: true,
2751 metadata: std::collections::BTreeMap::new(),
2752 }],
2753 }));
2754 ser(&df.unpivot(vec![col("id")], None::<Vec<Column>>, "var", "val"));
2755 ser(&df.melt(vec!["id"], None, "var", "val"));
2756 ser(&df.group_by(vec![col("id")]).agg(vec![e()]));
2757 ser(&df.rollup(vec![col("id")]).agg(vec![e()]));
2758 ser(&df.cube(vec![col("id")]).agg(vec![e()]));
2759 ser(&df.grouping_sets(vec![vec![col("id")]]).agg(vec![e()]));
2760 ser(&df.with_watermark("id", "1 minute"));
2761 let mut md = std::collections::HashMap::new();
2762 md.insert("k".to_string(), "v".to_string());
2763 ser(&df.with_metadata("id", md));
2764 ser(&df.replace(vec![("a".to_string(), "b".to_string())], None));
2765 ser(&df.stat().crosstab("id", "id"));
2766 ser(&df.stat().freq_items(vec!["id"], 0.5));
2767 }
2768
2769 #[test]
2772 fn streaming_reader_and_writer_builders() {
2773 use crate::streaming::Trigger;
2774 let spark = session();
2775 let ser = |d: &DataFrame| {
2776 build_input_relation(d.plan(), &spark).expect("stream plan serializes");
2777 };
2778 ser(&spark
2779 .read_stream()
2780 .format("rate")
2781 .option("rowsPerSecond", "5")
2782 .load(None));
2783 ser(&spark.read_stream().schema("value long").json("/tmp/in"));
2784 ser(&spark.read_stream().parquet("/tmp/in"));
2785 ser(&spark.read_stream().csv("/tmp/in"));
2786 ser(&spark.read_stream().orc("/tmp/in"));
2787 ser(&spark.read_stream().text("/tmp/in"));
2788 ser(&spark.read_stream().format("rate").table("t"));
2789
2790 let base = spark.range(3).unwrap();
2791 for trig in [
2792 Trigger::ProcessingTime("1 second".to_string()),
2793 Trigger::Once,
2794 Trigger::AvailableNow,
2795 Trigger::Continuous("1 second".to_string()),
2796 ] {
2797 let _w = base
2798 .write_stream()
2799 .output_mode("append")
2800 .format("console")
2801 .option("k", "v")
2802 .partition_by(vec!["id"])
2803 .cluster_by(vec!["id"])
2804 .query_name("q")
2805 .trigger(trig);
2806 }
2807 }
2808
2809 #[test]
2812 fn column_operations_and_expressions() {
2813 use crate::functions::col;
2814 let a = || col("a");
2815 let b = || col("b");
2816 let exprs = vec![
2817 a().add(b()),
2818 a().sub(b()),
2819 a().mul(b()),
2820 a().div(b()),
2821 a().modulo(b()),
2822 a().and(b()),
2823 a().or(b()),
2824 a().not(),
2825 a().neg(),
2826 a().eq(b()),
2827 a().ne(b()),
2828 a().gt(b()),
2829 a().lt(b()),
2830 a().ge(b()),
2831 a().le(b()),
2832 a().bitwise_and(b()),
2833 a().bitwise_or(b()),
2834 a().bitwise_xor(b()),
2835 a().eq_null_safe(b()),
2836 a().is_null(),
2837 a().is_not_null(),
2838 a().is_nan(),
2839 a().like("x%"),
2840 a().rlike("x.*"),
2841 a().ilike("x%"),
2842 a().contains(b()),
2843 a().startswith(b()),
2844 a().endswith(b()),
2845 a().substr(b(), b()),
2846 a().between(b(), b()),
2847 a().isin(vec![b()]),
2848 a().get_field("f"),
2849 a().get_item(b()),
2850 a().with_field("f", b()),
2851 a().drop_fields(vec!["f"]),
2852 a().asc(),
2853 a().asc_nulls_first(),
2854 a().asc_nulls_last(),
2855 a().desc(),
2856 a().desc_nulls_first(),
2857 a().desc_nulls_last(),
2858 a().alias("x"),
2859 a().name("y"),
2860 a().cast_str("int"),
2861 a().try_cast_str("int"),
2862 a().astype(crate::types::DataType::Integer),
2863 a().when(b(), b()).otherwise(b()),
2864 ];
2865 for e in &exprs {
2866 let _ = e.to_proto();
2867 }
2868 }
2869
2870 #[test]
2874 fn exotic_plan_variants_serialize() {
2875 use crate::functions::col;
2876 use crate::types::DataType;
2877 use crate::udf::{CommonInlineUserDefinedFunctionExpression, PythonUDFPayload};
2878
2879 let spark = session();
2880 let ser = |d: &DataFrame| {
2881 build_input_relation(d.plan(), &spark).expect("exotic plan serializes");
2882 };
2883 let df = spark.range(5).unwrap();
2884 let df2 = spark.range(5).unwrap();
2885
2886 ser(&df.zip(&df2).unwrap());
2887 ser(&df.transpose().unwrap());
2888 ser(&df.transpose_with_index(col("id")).unwrap());
2889 ser(&df.nearest_by_join(&df2, col("id"), 5, "inner", "asc", "inner"));
2890
2891 let udf = || {
2892 CommonInlineUserDefinedFunctionExpression::new(
2893 "f".to_string(),
2894 true,
2895 vec![],
2896 PythonUDFPayload::new(DataType::Integer, 200, vec![1, 2, 3], "3.11".to_string()),
2897 )
2898 };
2899 ser(&df.map_in_pandas(udf(), false));
2900 ser(&df.map_in_arrow(udf(), false));
2901 ser(&df.group_by(vec![col("id")]).apply_in_pandas(udf()));
2902 ser(&df.group_by(vec![col("id")]).apply_in_arrow(udf()));
2903 let g1 = df.group_by(vec![col("id")]);
2904 let g2 = df2.group_by(vec![col("id")]);
2905 ser(&g1.cogroup(&g2).apply_in_pandas(udf()));
2906
2907 let udtf_df = spark.tvf().udtf(
2908 "myudtf",
2909 vec![],
2910 Some(DataType::Integer),
2911 300,
2912 vec![1, 2],
2913 "3.11".to_string(),
2914 true,
2915 );
2916 ser(&udtf_df);
2917 }
2918}
2919
2920#[cfg(test)]
2926mod arrow_value_tests {
2927 use super::*;
2928 use arrow::array::*;
2929 use arrow::datatypes::{
2930 i256, DataType as ArrowDataType, Field, Int32Type, IntervalDayTime, IntervalMonthDayNano,
2931 };
2932 use std::sync::Arc;
2933
2934 #[test]
2935 fn primitives_and_signed_ints() {
2936 assert!(matches!(
2937 arrow_value_at(&BooleanArray::from(vec![true]), 0).unwrap(),
2938 Value::Bool(true)
2939 ));
2940 assert!(matches!(
2941 arrow_value_at(&Int8Array::from(vec![1i8]), 0).unwrap(),
2942 Value::Byte(1)
2943 ));
2944 assert!(matches!(
2945 arrow_value_at(&Int16Array::from(vec![1i16]), 0).unwrap(),
2946 Value::Short(1)
2947 ));
2948 assert!(matches!(
2949 arrow_value_at(&Int32Array::from(vec![1i32]), 0).unwrap(),
2950 Value::Integer(1)
2951 ));
2952 assert!(matches!(
2953 arrow_value_at(&Int64Array::from(vec![1i64]), 0).unwrap(),
2954 Value::Long(1)
2955 ));
2956 assert!(matches!(
2957 arrow_value_at(&Float32Array::from(vec![1.0f32]), 0).unwrap(),
2958 Value::Float(_)
2959 ));
2960 assert!(matches!(
2961 arrow_value_at(&Float64Array::from(vec![1.0f64]), 0).unwrap(),
2962 Value::Double(_)
2963 ));
2964 assert!(matches!(
2965 arrow_value_at(&StringArray::from(vec!["x"]), 0).unwrap(),
2966 Value::String(_)
2967 ));
2968 assert!(matches!(
2969 arrow_value_at(&BinaryArray::from_iter_values([b"x".as_ref()]), 0).unwrap(),
2970 Value::Binary(_)
2971 ));
2972 assert!(matches!(
2973 arrow_value_at(&Date32Array::from(vec![1i32]), 0).unwrap(),
2974 Value::Date(1)
2975 ));
2976 assert!(matches!(
2977 arrow_value_at(&TimestampMicrosecondArray::from(vec![1i64]), 0).unwrap(),
2978 Value::Timestamp(1)
2979 ));
2980 }
2981
2982 #[test]
2983 fn unsigned_ints() {
2984 assert!(matches!(
2985 arrow_value_at(&UInt8Array::from(vec![1u8]), 0).unwrap(),
2986 Value::Short(1)
2987 ));
2988 assert!(matches!(
2989 arrow_value_at(&UInt16Array::from(vec![1u16]), 0).unwrap(),
2990 Value::Integer(1)
2991 ));
2992 assert!(matches!(
2993 arrow_value_at(&UInt32Array::from(vec![1u32]), 0).unwrap(),
2994 Value::Long(1)
2995 ));
2996 assert!(matches!(
2997 arrow_value_at(&UInt64Array::from(vec![1u64]), 0).unwrap(),
2998 Value::Long(1)
2999 ));
3000 }
3001
3002 #[test]
3003 fn decimals_128_and_256() {
3004 let d128 = Decimal128Array::from(vec![12345i128])
3005 .with_precision_and_scale(10, 2)
3006 .unwrap();
3007 assert!(matches!(
3008 arrow_value_at(&d128, 0).unwrap(),
3009 Value::Decimal { .. }
3010 ));
3011 let d256 = Decimal256Array::from(vec![i256::from_i128(12345)])
3012 .with_precision_and_scale(10, 2)
3013 .unwrap();
3014 assert!(matches!(
3015 arrow_value_at(&d256, 0).unwrap(),
3016 Value::Decimal { .. }
3017 ));
3018 }
3019
3020 #[test]
3021 fn large_and_view_bytes() {
3022 assert!(matches!(
3023 arrow_value_at(&LargeStringArray::from_iter_values(["x"]), 0).unwrap(),
3024 Value::String(_)
3025 ));
3026 assert!(matches!(
3027 arrow_value_at(&LargeBinaryArray::from_iter_values([b"x".as_ref()]), 0).unwrap(),
3028 Value::Binary(_)
3029 ));
3030 assert!(matches!(
3031 arrow_value_at(&StringViewArray::from_iter_values(["x"]), 0).unwrap(),
3032 Value::String(_)
3033 ));
3034 assert!(matches!(
3035 arrow_value_at(&BinaryViewArray::from_iter_values([b"x".as_ref()]), 0).unwrap(),
3036 Value::Binary(_)
3037 ));
3038 }
3039
3040 #[test]
3041 fn timestamps_and_date64() {
3042 assert!(matches!(
3043 arrow_value_at(&TimestampSecondArray::from(vec![1i64]), 0).unwrap(),
3044 Value::Timestamp(_)
3045 ));
3046 assert!(matches!(
3047 arrow_value_at(&TimestampMillisecondArray::from(vec![1i64]), 0).unwrap(),
3048 Value::Timestamp(_)
3049 ));
3050 assert!(matches!(
3051 arrow_value_at(&TimestampNanosecondArray::from(vec![1000i64]), 0).unwrap(),
3052 Value::Timestamp(_)
3053 ));
3054 assert!(matches!(
3055 arrow_value_at(&Date64Array::from(vec![86_400_000i64]), 0).unwrap(),
3056 Value::Date(_)
3057 ));
3058 }
3059
3060 #[test]
3061 fn time_types_render_as_string() {
3062 assert!(matches!(
3063 arrow_value_at(&Time64MicrosecondArray::from(vec![1i64]), 0).unwrap(),
3064 Value::String(_)
3065 ));
3066 assert!(matches!(
3067 arrow_value_at(&Time64NanosecondArray::from(vec![1000i64]), 0).unwrap(),
3068 Value::String(_)
3069 ));
3070 assert!(matches!(
3071 arrow_value_at(&Time32MillisecondArray::from(vec![1i32]), 0).unwrap(),
3072 Value::String(_)
3073 ));
3074 assert!(matches!(
3075 arrow_value_at(&Time32SecondArray::from(vec![1i32]), 0).unwrap(),
3076 Value::String(_)
3077 ));
3078 }
3079
3080 #[test]
3081 fn interval_types_render_as_string() {
3082 assert!(matches!(
3083 arrow_value_at(&IntervalYearMonthArray::from(vec![13i32]), 0).unwrap(),
3084 Value::String(_)
3085 ));
3086 let dt = IntervalDayTimeArray::from(vec![IntervalDayTime::new(1, 100)]);
3087 assert!(matches!(arrow_value_at(&dt, 0).unwrap(), Value::String(_)));
3088 let mdn = IntervalMonthDayNanoArray::from(vec![IntervalMonthDayNano::new(1, 2, 3)]);
3089 assert!(matches!(arrow_value_at(&mdn, 0).unwrap(), Value::String(_)));
3090 }
3091
3092 #[test]
3093 fn fixed_size_binary() {
3094 let arr = FixedSizeBinaryArray::try_from_iter(vec![vec![1u8, 2u8]].into_iter()).unwrap();
3095 assert!(matches!(arrow_value_at(&arr, 0).unwrap(), Value::Binary(_)));
3096 }
3097
3098 #[test]
3099 fn nested_list_struct_map() {
3100 let list =
3101 ListArray::from_iter_primitive::<Int32Type, _, _>(vec![Some(vec![Some(1), Some(2)])]);
3102 assert!(matches!(arrow_value_at(&list, 0).unwrap(), Value::List(_)));
3103
3104 let field = Arc::new(Field::new("a", ArrowDataType::Int32, false));
3105 let col: ArrayRef = Arc::new(Int32Array::from(vec![1]));
3106 let s = StructArray::from(vec![(field, col)]);
3107 assert!(matches!(arrow_value_at(&s, 0).unwrap(), Value::Struct(_)));
3108
3109 let mut b = MapBuilder::new(None, StringBuilder::new(), Int32Builder::new());
3110 b.keys().append_value("k");
3111 b.values().append_value(1);
3112 b.append(true).unwrap();
3113 let m = b.finish();
3114 assert!(matches!(arrow_value_at(&m, 0).unwrap(), Value::Map(_)));
3115 }
3116
3117 #[test]
3118 fn null_element_and_unsupported_type() {
3119 let with_null = Int32Array::from(vec![None as Option<i32>]);
3120 assert!(matches!(
3121 arrow_value_at(&with_null, 0).unwrap(),
3122 Value::Null
3123 ));
3124 let dur = DurationSecondArray::from(vec![1i64]);
3126 assert!(arrow_value_at(&dur, 0).is_err());
3127 }
3128
3129 #[test]
3130 fn map_key_to_string_covers_scalar_arms() {
3131 assert_eq!(map_key_to_string(Value::String("x".to_string())), "x");
3132 assert_eq!(map_key_to_string(Value::Bool(true)), "true");
3133 assert_eq!(map_key_to_string(Value::Byte(1)), "1");
3134 assert_eq!(map_key_to_string(Value::Short(2)), "2");
3135 assert_eq!(map_key_to_string(Value::Integer(3)), "3");
3136 assert_eq!(map_key_to_string(Value::Long(4)), "4");
3137 assert_eq!(map_key_to_string(Value::Float(1.5)), "1.5");
3138 assert_eq!(map_key_to_string(Value::Double(2.5)), "2.5");
3139 assert_eq!(map_key_to_string(Value::Date(5)), "5");
3140 assert_eq!(map_key_to_string(Value::Timestamp(6)), "6");
3141 assert_eq!(
3142 map_key_to_string(Value::Decimal {
3143 value: "7.5".to_string(),
3144 precision: None,
3145 scale: None,
3146 }),
3147 "7.5"
3148 );
3149 let _ = map_key_to_string(Value::List(vec![]));
3151 }
3152
3153 #[test]
3154 fn i128_to_decimal_string_branches() {
3155 assert_eq!(i128_to_decimal_string(12345, 0), "12345");
3156 assert_eq!(i128_to_decimal_string(12345, 2), "123.45");
3157 assert_eq!(i128_to_decimal_string(5, 4), "0.0005");
3158 assert_eq!(i128_to_decimal_string(-5, 4), "-0.0005");
3159 }
3160
3161 #[test]
3162 fn micros_to_time_string_branches() {
3163 assert_eq!(micros_to_time_string(0), "00:00:00");
3164 assert!(micros_to_time_string(1).contains('.'));
3165 }
3166}