1use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
6use std::sync::{Arc, Mutex};
7
8use spark_connect_core::channel::ChannelBuilder;
9use spark_connect_core::client::SparkConnectClient;
10use spark_connect_core::error::{Result, SparkError};
11use spark_connect_core::runtime::block_on;
12use spark_connect_proto as proto;
13
14use crate::dataframe::DataFrame;
15use crate::plan::LogicalPlan;
16use crate::profiler::ProfilerCollector;
17use crate::row::{Row, Value};
18use crate::types::DataType;
19
20pub type ProgressHandler =
23 Arc<dyn Fn(&proto::execute_plan_response::ExecutionProgress) + Send + Sync>;
24
25#[derive(Debug, Clone, Default)]
28pub struct ExecutionInfo {
29 pub metrics: Option<proto::execute_plan_response::Metrics>,
31 pub observed_metrics: Vec<proto::execute_plan_response::ObservedMetrics>,
34}
35
36pub struct SparkSession {
40 client: Arc<SparkConnectClient>,
42 plan_id_counter: Arc<AtomicI64>,
44 tags: Arc<Mutex<Vec<String>>>,
47 last_execution: Arc<Mutex<Option<ExecutionInfo>>>,
50 progress_handlers: Arc<Mutex<Vec<(u64, ProgressHandler)>>>,
53 progress_handler_id: Arc<AtomicU64>,
55 profiler: Arc<ProfilerCollector>,
57 stopped: Arc<AtomicBool>,
60}
61
62impl SparkSession {
63 fn new(client: SparkConnectClient) -> Self {
65 SparkSession {
66 client: Arc::new(client),
67 plan_id_counter: Arc::new(AtomicI64::new(1)),
68 tags: Arc::new(Mutex::new(Vec::new())),
69 last_execution: Arc::new(Mutex::new(None)),
70 progress_handlers: Arc::new(Mutex::new(Vec::new())),
71 progress_handler_id: Arc::new(AtomicU64::new(0)),
72 profiler: Arc::new(ProfilerCollector::new()),
73 stopped: Arc::new(AtomicBool::new(false)),
74 }
75 }
76
77 pub fn add_tag(&self, tag: &str) -> Result<()> {
81 if tag.is_empty() {
82 return Err(SparkError::value(
83 "INVALID_TAG",
84 &[("detail", "Tag cannot be empty")],
85 ));
86 }
87 if tag.contains(',') {
88 return Err(SparkError::value(
89 "INVALID_TAG",
90 &[("detail", "Tag cannot contain ','")],
91 ));
92 }
93 let mut tags = self.tags.lock().unwrap();
94 if !tags.iter().any(|t| t == tag) {
95 tags.push(tag.to_string());
96 }
97 Ok(())
98 }
99
100 pub fn remove_tag(&self, tag: &str) {
102 self.tags.lock().unwrap().retain(|t| t != tag);
103 }
104
105 pub fn get_tags(&self) -> Vec<String> {
107 self.tags.lock().unwrap().clone()
108 }
109
110 pub fn clear_tags(&self) {
112 self.tags.lock().unwrap().clear();
113 }
114
115 pub fn register_progress_handler(
119 &self,
120 handler: impl Fn(&proto::execute_plan_response::ExecutionProgress) + Send + Sync + 'static,
121 ) -> u64 {
122 let id = self.progress_handler_id.fetch_add(1, Ordering::SeqCst);
123 self.progress_handlers
124 .lock()
125 .unwrap()
126 .push((id, Arc::new(handler)));
127 id
128 }
129
130 pub fn remove_progress_handler(&self, id: u64) {
133 self.progress_handlers
134 .lock()
135 .unwrap()
136 .retain(|(hid, _)| *hid != id);
137 }
138
139 pub fn clear_progress_handlers(&self) {
141 self.progress_handlers.lock().unwrap().clear();
142 }
143
144 pub(crate) fn notify_progress(
147 &self,
148 progress: &proto::execute_plan_response::ExecutionProgress,
149 ) {
150 let handlers: Vec<ProgressHandler> = {
153 self.progress_handlers
154 .lock()
155 .unwrap()
156 .iter()
157 .map(|(_, h)| Arc::clone(h))
158 .collect()
159 };
160 for h in handlers {
161 h(progress);
162 }
163 }
164
165 pub(crate) fn record_execution(&self, info: ExecutionInfo) {
168 *self.last_execution.lock().unwrap() = Some(info);
169 }
170
171 pub fn last_execution_info(&self) -> Option<ExecutionInfo> {
174 self.last_execution.lock().unwrap().clone()
175 }
176
177 pub fn profile(&self) -> Vec<proto::execute_plan_response::ObservedMetrics> {
183 self.last_execution_info()
184 .map(|i| i.observed_metrics)
185 .unwrap_or_default()
186 }
187
188 pub(crate) fn tags(&self) -> Vec<String> {
190 self.tags.lock().unwrap().clone()
191 }
192
193 pub fn new_session(&self) -> SparkSession {
197 SparkSession {
198 client: Arc::new(self.client.with_new_session_id()),
199 plan_id_counter: Arc::new(AtomicI64::new(1)),
200 tags: Arc::new(Mutex::new(Vec::new())),
201 last_execution: Arc::new(Mutex::new(None)),
202 progress_handlers: Arc::new(Mutex::new(Vec::new())),
203 progress_handler_id: Arc::new(AtomicU64::new(0)),
204 profiler: Arc::new(ProfilerCollector::new()),
205 stopped: Arc::new(AtomicBool::new(false)),
206 }
207 }
208
209 pub fn clone_session(&self) -> SparkSession {
212 self.new_session()
213 }
214
215 pub(crate) fn next_plan_id(&self) -> i64 {
217 self.plan_id_counter.fetch_add(1, Ordering::SeqCst)
218 }
219
220 pub(crate) fn client(&self) -> &Arc<SparkConnectClient> {
222 &self.client
223 }
224
225 pub fn builder() -> SparkSessionBuilder {
227 SparkSessionBuilder::new()
228 }
229
230 pub fn range(&self, end: i64) -> Result<DataFrame> {
234 self.range_full(0, end, 1, None)
235 }
236
237 pub fn range_full(
239 &self,
240 start: i64,
241 end: i64,
242 step: i64,
243 num_partitions: Option<i32>,
244 ) -> Result<DataFrame> {
245 let plan = LogicalPlan::Range {
246 start,
247 end,
248 step,
249 num_partitions,
250 };
251 Ok(DataFrame::new(self.clone(), plan))
252 }
253
254 pub fn sql(&self, query: &str) -> Result<DataFrame> {
258 self.sql_with_args(query, Vec::new(), std::collections::HashMap::new())
259 }
260
261 pub fn sql_with_args(
264 &self,
265 query: &str,
266 pos_args: Vec<crate::expression::Expression>,
267 named_args: std::collections::HashMap<String, crate::expression::Expression>,
268 ) -> Result<DataFrame> {
269 let plan = LogicalPlan::Sql {
270 query: query.to_string(),
271 pos_args,
272 named_args,
273 };
274 Ok(DataFrame::new(self.clone(), plan))
275 }
276
277 pub fn create_dataframe(&self, rows: Vec<Row>, schema: DataType) -> Result<DataFrame> {
281 let data = if rows.is_empty() {
285 None
286 } else {
287 Some(rows_to_arrow_ipc(&rows, &schema)?)
288 };
289 let plan = LogicalPlan::LocalRelation { schema, data };
290 Ok(DataFrame::new(self.clone(), plan))
291 }
292
293 pub fn read(&self) -> crate::readwriter::DataFrameReader {
297 crate::readwriter::DataFrameReader::new(self.clone())
298 }
299
300 pub fn read_stream(&self) -> crate::streaming::DataStreamReader {
304 crate::streaming::DataStreamReader::new(self.clone())
305 }
306
307 pub fn streams(&self) -> crate::streaming::StreamingQueryManager {
311 crate::streaming::StreamingQueryManager::new(self.clone())
312 }
313
314 pub fn catalog(&self) -> crate::catalog::Catalog {
318 crate::catalog::Catalog::new(self.clone())
319 }
320
321 pub fn register_java_function(
328 &self,
329 name: &str,
330 java_class_name: &str,
331 return_type_ddl: Option<&str>,
332 aggregate: bool,
333 ) -> Result<()> {
334 let mut java_udf = proto::JavaUdf::default();
335 java_udf.class_name = java_class_name.to_string();
336 if let Some(ddl) = return_type_ddl {
337 java_udf.output_type = Some(crate::types::DataType::from_ddl(ddl)?.to_proto());
338 } else {
339 java_udf.aggregate = aggregate;
340 }
341 let mut fun = proto::CommonInlineUserDefinedFunction::default();
342 fun.function_name = name.to_string();
343 fun.deterministic = true;
344 fun.function =
345 Some(proto::common_inline_user_defined_function::Function::JavaUdf(java_udf));
346 crate::dataframe::execute_command_collect(
347 self,
348 proto::command::CommandType::RegisterFunction(fun),
349 )?;
350 Ok(())
351 }
352
353 pub fn conf(&self) -> crate::conf::RuntimeConf {
357 crate::conf::RuntimeConf::new(Arc::clone(&self.client))
358 }
359
360 pub fn tvf(&self) -> crate::tvf::TableValuedFunction {
364 crate::tvf::TableValuedFunction::new(self.clone())
365 }
366
367 pub fn session_id(&self) -> &str {
371 self.client.session_id()
372 }
373
374 pub fn version(&self) -> Result<String> {
378 let mut request = proto::AnalyzePlanRequest::default();
379 request.session_id = self.client.session_id().to_string();
380 request.user_context = Some(proto::UserContext::default());
381 request.analyze = Some(proto::analyze_plan_request::Analyze::SparkVersion(
382 proto::analyze_plan_request::SparkVersion::default(),
383 ));
384 let resp = block_on(self.client.analyze_plan(request))?;
385 match resp.result {
386 Some(proto::analyze_plan_response::Result::SparkVersion(v)) => Ok(v.version),
387 _ => Err(SparkError::connect_msg(
388 "AnalyzePlan response did not contain a spark version",
389 )),
390 }
391 }
392
393 pub fn table(&self, table_name: &str) -> Result<DataFrame> {
397 Ok(self.read().table(table_name))
398 }
399
400 pub fn empty_data_frame(&self) -> Result<DataFrame> {
405 let plan = LogicalPlan::LocalRelation {
406 schema: DataType::Struct { fields: vec![] },
407 data: None,
408 };
409 Ok(DataFrame::new(self.clone(), plan))
410 }
411
412 pub fn interrupt_all(&self) -> Result<Vec<String>> {
416 block_on(self.client.interrupt_all())
417 }
418
419 pub fn interrupt_tag(&self, tag: &str) -> Result<Vec<String>> {
423 block_on(self.client.interrupt_tag(tag))
424 }
425
426 pub fn interrupt_operation(&self, operation_id: &str) -> Result<Vec<String>> {
430 block_on(self.client.interrupt_operation(operation_id))
431 }
432
433 pub fn add_artifacts(&self, paths: &[&str]) -> Result<()> {
437 block_on(self.client.add_artifacts(paths, false, false, true))
438 }
439
440 pub fn add_artifact(&self, path: &str) -> Result<()> {
444 block_on(self.client.add_artifacts(&[path], false, false, true))
445 }
446
447 pub fn copy_from_local_to_fs(&self, local_path: &str, dest_path: &str) -> Result<()> {
452 let name = format!("forward_to_fs/{}", dest_path);
453 block_on(self.client.add_named_artifact(&name, local_path))
454 }
455
456 pub fn register_function(
463 &self,
464 udf: crate::udf::CommonInlineUserDefinedFunctionExpression,
465 ) -> Result<()> {
466 crate::dataframe::execute_command(
467 self,
468 proto::command::CommandType::RegisterFunction(udf.to_proto()),
469 )
470 }
471
472 pub fn build_resource_profile(
480 &self,
481 profile: &crate::resource::ResourceProfile,
482 ) -> Result<i32> {
483 let mut cmd = proto::CreateResourceProfileCommand::default();
484 cmd.profile = Some(profile.proto().clone());
485
486 let responses = crate::dataframe::execute_command_collect(
487 self,
488 proto::command::CommandType::CreateResourceProfileCommand(cmd),
489 )?;
490
491 for resp in responses {
492 if let Some(
493 proto::execute_plan_response::ResponseType::CreateResourceProfileCommandResult(res),
494 ) = resp.response_type
495 {
496 return Ok(res.profile_id);
497 }
498 }
499
500 Err(SparkError::connect_msg(
501 "build_resource_profile: server returned no CreateResourceProfileCommandResult",
502 ))
503 }
504
505 pub fn register_data_source(
513 &self,
514 data_source: crate::datasource::CommonInlineUserDefinedDataSourceExpression,
515 ) -> Result<()> {
516 crate::dataframe::execute_command(
517 self,
518 proto::command::CommandType::RegisterDataSource(data_source.to_proto()),
519 )
520 }
521
522 pub fn profiler(&self) -> Arc<ProfilerCollector> {
529 Arc::clone(&self.profiler)
530 }
531
532 pub fn stop(&self) -> Result<()> {
534 block_on(self.client.release_session())?;
535 self.stopped.store(true, Ordering::SeqCst);
536 Ok(())
537 }
538
539 pub fn is_stopped(&self) -> bool {
542 self.stopped.load(Ordering::SeqCst)
543 }
544}
545
546impl Clone for SparkSession {
547 fn clone(&self) -> Self {
548 SparkSession {
549 client: Arc::clone(&self.client),
550 plan_id_counter: Arc::clone(&self.plan_id_counter),
551 tags: Arc::clone(&self.tags),
552 last_execution: Arc::clone(&self.last_execution),
553 progress_handlers: Arc::clone(&self.progress_handlers),
554 progress_handler_id: Arc::clone(&self.progress_handler_id),
555 profiler: Arc::clone(&self.profiler),
556 stopped: Arc::clone(&self.stopped),
557 }
558 }
559}
560
561pub struct SparkSessionBuilder {
563 remote_url: Option<String>,
564}
565
566impl SparkSessionBuilder {
567 pub fn new() -> Self {
569 SparkSessionBuilder { remote_url: None }
570 }
571
572 pub fn remote(mut self, url: &str) -> Self {
574 self.remote_url = Some(url.to_string());
575 self
576 }
577
578 pub fn get_or_create(self) -> Result<SparkSession> {
580 let url = self.remote_url.ok_or_else(|| {
581 SparkError::value(
582 "NO_REMOTE_URL",
583 &[("detail", "Must call .remote(url) before .get_or_create()")],
584 )
585 })?;
586
587 let builder = ChannelBuilder::parse(&url)?;
588 let client = block_on(SparkConnectClient::connect(&builder))?;
589 Ok(SparkSession::new(client))
590 }
591}
592
593impl Default for SparkSessionBuilder {
594 fn default() -> Self {
595 Self::new()
596 }
597}
598
599fn rows_to_arrow_ipc(rows: &[Row], schema: &DataType) -> Result<Vec<u8>> {
602 use arrow::datatypes::Schema as ArrowSchema;
603 use arrow::ipc::writer::StreamWriter;
604 use arrow::record_batch::RecordBatch;
605
606 if rows.is_empty() {
607 return Ok(vec![]);
608 }
609
610 let arrow_schema_vec = schema_to_arrow_fields(schema)?;
612 let arrow_schema = ArrowSchema::new(arrow_schema_vec);
613
614 let mut columns = vec![];
616 let fields = match schema {
617 DataType::Struct { fields } => fields,
618 _ => return Err(SparkError::connect_msg("Schema is not a struct type")),
619 };
620
621 let coerced: Vec<Row> = rows
624 .iter()
625 .map(|r| {
626 let vals: Vec<Value> = fields
627 .iter()
628 .enumerate()
629 .map(|(i, f)| coerce_value(r.get(i), &f.data_type))
630 .collect();
631 Row::new(r.fields().to_vec(), vals)
632 })
633 .collect();
634
635 for field_idx in 0..fields.len() {
636 let array = build_arrow_array(&coerced, field_idx, Some(&fields[field_idx].data_type))?;
637 columns.push(array);
638 }
639
640 let batch = RecordBatch::try_new(Arc::new(arrow_schema), columns)
642 .map_err(|e| SparkError::connect_msg(format!("Failed to create Arrow batch: {}", e)))?;
643
644 let mut buffer = Vec::new();
646 {
647 let mut writer = StreamWriter::try_new(&mut buffer, &batch.schema())
648 .map_err(|e| SparkError::connect_msg(format!("Failed to create IPC writer: {}", e)))?;
649 writer
650 .write(&batch)
651 .map_err(|e| SparkError::connect_msg(format!("Failed to write batch to IPC: {}", e)))?;
652 writer
653 .finish()
654 .map_err(|e| SparkError::connect_msg(format!("Failed to finish IPC writer: {}", e)))?;
655 }
656
657 Ok(buffer)
658}
659
660fn schema_to_arrow_fields(schema: &DataType) -> Result<Vec<arrow::datatypes::Field>> {
662 use arrow::datatypes::Field;
663
664 match schema {
665 DataType::Struct { fields } => {
666 let mut arrow_fields = vec![];
667 for field in fields {
668 arrow_fields.push(Field::new(
669 &field.name,
670 datatype_to_arrow(&field.data_type)?,
671 field.nullable,
672 ));
673 }
674 Ok(arrow_fields)
675 }
676 _ => Err(SparkError::connect_msg("Schema is not a struct")),
677 }
678}
679
680fn datatype_to_arrow(dt: &DataType) -> Result<arrow::datatypes::DataType> {
682 use arrow::datatypes::{DataType as A, Field, Fields, TimeUnit};
683 Ok(match dt {
684 DataType::Null => A::Null,
685 DataType::Boolean => A::Boolean,
686 DataType::Byte => A::Int8,
687 DataType::Short => A::Int16,
688 DataType::Integer => A::Int32,
689 DataType::Long => A::Int64,
690 DataType::Float => A::Float32,
691 DataType::Double => A::Float64,
692 DataType::String { .. } | DataType::Char { .. } | DataType::Varchar { .. } => A::Utf8,
694 DataType::Binary => A::Binary,
695 DataType::Date => A::Date32,
696 DataType::Timestamp => A::Timestamp(TimeUnit::Microsecond, Some("UTC".into())),
698 DataType::TimestampNtz => A::Timestamp(TimeUnit::Microsecond, None),
699 DataType::Time { .. } => A::Time64(TimeUnit::Microsecond),
700 DataType::Decimal { precision, scale } => A::Decimal128(*precision as u8, *scale as i8),
701 DataType::Array {
702 element_type,
703 contains_null,
704 } => A::List(Arc::new(Field::new(
705 "element",
706 datatype_to_arrow(element_type)?,
707 *contains_null,
708 ))),
709 DataType::Map {
710 key_type,
711 value_type,
712 value_contains_null,
713 } => {
714 let entry_fields: Fields = vec![
715 Field::new("key", datatype_to_arrow(key_type)?, false),
716 Field::new(
717 "value",
718 datatype_to_arrow(value_type)?,
719 *value_contains_null,
720 ),
721 ]
722 .into();
723 A::Map(
724 Arc::new(Field::new("entries", A::Struct(entry_fields), false)),
725 false,
726 )
727 }
728 DataType::Struct { fields } => {
729 let f: Fields = fields
730 .iter()
731 .map(|field| {
732 Ok(Field::new(
733 &field.name,
734 datatype_to_arrow(&field.data_type)?,
735 field.nullable,
736 ))
737 })
738 .collect::<Result<Vec<_>>>()?
739 .into();
740 A::Struct(f)
741 }
742 other => {
743 return Err(SparkError::connect_msg(format!(
744 "Unsupported type for Arrow createDataFrame conversion: {other:?}"
745 )))
746 }
747 })
748}
749
750fn coerce_value(v: Option<&Value>, target: &DataType) -> Value {
754 let v = match v {
755 Some(v) => v,
756 None => return Value::Null,
757 };
758 match (target, v) {
759 (_, Value::Null) => Value::Null,
760 (DataType::Byte, Value::Long(n)) => Value::Byte(*n as i8),
761 (DataType::Byte, Value::Integer(n)) => Value::Byte(*n as i8),
762 (DataType::Short, Value::Long(n)) => Value::Short(*n as i16),
763 (DataType::Short, Value::Integer(n)) => Value::Short(*n as i16),
764 (DataType::Integer, Value::Long(n)) => Value::Integer(*n as i32),
765 (DataType::Long, Value::Integer(n)) => Value::Long(*n as i64),
766 (DataType::Float, Value::Double(f)) => Value::Float(*f as f32),
770 (DataType::Float, Value::Long(n)) => Value::Float(*n as f32),
771 (DataType::Float, Value::Integer(n)) => Value::Float(*n as f32),
772 (DataType::Double, Value::Long(n)) => Value::Double(*n as f64),
773 (DataType::Double, Value::Integer(n)) => Value::Double(*n as f64),
774 (DataType::Double, Value::Float(f)) => Value::Double(*f as f64),
775 (DataType::Array { element_type, .. }, Value::List(items)) => Value::List(
778 items
779 .iter()
780 .map(|it| coerce_value(Some(it), element_type))
781 .collect(),
782 ),
783 (DataType::Struct { fields }, Value::Struct(sf)) => Value::Struct(
784 fields
785 .iter()
786 .enumerate()
787 .map(|(i, f)| {
788 let val = sf
789 .iter()
790 .find(|(n, _)| n == &f.name)
791 .map(|(_, v)| v)
792 .or_else(|| sf.get(i).map(|(_, v)| v));
793 (f.name.clone(), coerce_value(val, &f.data_type))
794 })
795 .collect(),
796 ),
797 (DataType::Map { value_type, .. }, Value::Map(m)) => Value::Map(
798 m.iter()
799 .map(|(k, v)| (k.clone(), coerce_value(Some(v), value_type)))
800 .collect(),
801 ),
802 _ => v.clone(),
803 }
804}
805
806fn build_arrow_array(
810 rows: &[Row],
811 field_idx: usize,
812 target: Option<&DataType>,
813) -> Result<Arc<dyn arrow::array::Array>> {
814 use arrow::array::NullArray;
815 if rows.is_empty() {
816 return Ok(Arc::new(NullArray::new(0)));
817 }
818 let column: Vec<Value> = rows
819 .iter()
820 .map(|r| r.get(field_idx).cloned().unwrap_or(Value::Null))
821 .collect();
822 match target {
823 Some(dt) => values_to_arrow(&column, dt),
824 None => build_arrow_array_scalar(rows, field_idx),
827 }
828}
829
830fn values_to_arrow(values: &[Value], dt: &DataType) -> Result<Arc<dyn arrow::array::Array>> {
833 use arrow::array::*;
834 use arrow::buffer::{NullBuffer, OffsetBuffer};
835 use arrow::datatypes::{Field, Fields};
836
837 macro_rules! prim {
838 ($arr:ty, $pat:path) => {{
839 let vals: Result<Vec<_>> = values
840 .iter()
841 .map(|v| match v {
842 Value::Null => Ok(None),
843 $pat(x) => Ok(Some(*x)),
844 _ => Err(SparkError::connect_msg("Type mismatch in row data")),
845 })
846 .collect();
847 Ok(Arc::new(<$arr>::from(vals?)) as Arc<dyn Array>)
848 }};
849 }
850
851 macro_rules! int_prim {
855 ($arr:ty, $t:ty) => {{
856 let vals: Result<Vec<_>> = values
857 .iter()
858 .map(|v| match v {
859 Value::Null => Ok(None),
860 Value::Byte(x) => Ok(Some(*x as $t)),
861 Value::Short(x) => Ok(Some(*x as $t)),
862 Value::Integer(x) => Ok(Some(*x as $t)),
863 Value::Long(x) => Ok(Some(*x as $t)),
864 _ => Err(SparkError::connect_msg("Type mismatch in row data")),
865 })
866 .collect();
867 Ok(Arc::new(<$arr>::from(vals?)) as Arc<dyn Array>)
868 }};
869 }
870 macro_rules! float_prim {
871 ($arr:ty, $t:ty) => {{
872 let vals: Result<Vec<_>> = values
873 .iter()
874 .map(|v| match v {
875 Value::Null => Ok(None),
876 Value::Float(x) => Ok(Some(*x as $t)),
877 Value::Double(x) => Ok(Some(*x as $t)),
878 Value::Integer(x) => Ok(Some(*x as $t)),
879 Value::Long(x) => Ok(Some(*x as $t)),
880 _ => Err(SparkError::connect_msg("Type mismatch in row data")),
881 })
882 .collect();
883 Ok(Arc::new(<$arr>::from(vals?)) as Arc<dyn Array>)
884 }};
885 }
886
887 match dt {
888 DataType::Null => Ok(Arc::new(NullArray::new(values.len()))),
889 DataType::Boolean => prim!(BooleanArray, Value::Bool),
890 DataType::Byte => int_prim!(Int8Array, i8),
891 DataType::Short => int_prim!(Int16Array, i16),
892 DataType::Integer => int_prim!(Int32Array, i32),
893 DataType::Long => int_prim!(Int64Array, i64),
894 DataType::Float => float_prim!(Float32Array, f32),
895 DataType::Double => float_prim!(Float64Array, f64),
896 DataType::String { .. } | DataType::Char { .. } | DataType::Varchar { .. } => {
897 let vals: Result<Vec<Option<&str>>> = values
898 .iter()
899 .map(|v| match v {
900 Value::Null => Ok(None),
901 Value::String(s) => Ok(Some(s.as_str())),
902 _ => Err(SparkError::connect_msg("Type mismatch in row data")),
903 })
904 .collect();
905 Ok(Arc::new(StringArray::from(vals?)))
906 }
907 DataType::Binary => {
908 let vals: Result<Vec<Option<&[u8]>>> = values
909 .iter()
910 .map(|v| match v {
911 Value::Null => Ok(None),
912 Value::Binary(b) => Ok(Some(b.as_slice())),
913 _ => Err(SparkError::connect_msg("Type mismatch in row data")),
914 })
915 .collect();
916 Ok(Arc::new(BinaryArray::from(vals?)))
917 }
918 DataType::Date => prim!(Date32Array, Value::Date),
919 DataType::Timestamp | DataType::TimestampNtz => {
920 let vals: Result<Vec<Option<i64>>> = values
921 .iter()
922 .map(|v| match v {
923 Value::Null => Ok(None),
924 Value::Timestamp(t) => Ok(Some(*t)),
925 _ => Err(SparkError::connect_msg("Type mismatch in row data")),
926 })
927 .collect();
928 let arr = TimestampMicrosecondArray::from(vals?);
929 let arr = if matches!(dt, DataType::TimestampNtz) {
930 arr
931 } else {
932 arr.with_timezone("UTC")
933 };
934 Ok(Arc::new(arr))
935 }
936 DataType::Decimal { precision, scale } => {
937 let col_scale = *scale;
938 let vals: Result<Vec<Option<i128>>> = values
939 .iter()
940 .map(|v| match v {
941 Value::Null => Ok(None),
942 Value::Decimal { value, .. } => {
943 decimal_str_to_unscaled(value, col_scale).map(Some)
944 }
945 _ => Err(SparkError::connect_msg("Type mismatch in row data")),
946 })
947 .collect();
948 let arr = Decimal128Array::from(vals?)
951 .with_precision_and_scale(*precision as u8, col_scale as i8)
952 .map_err(|e| SparkError::connect_msg(format!("decimal build: {e}")))?;
953 Ok(Arc::new(arr))
954 }
955 DataType::Array {
956 element_type,
957 contains_null,
958 } => {
959 let mut child: Vec<Value> = Vec::new();
960 let mut offsets: Vec<i32> = vec![0];
961 let mut valid = arrow::array::builder::BooleanBufferBuilder::new(values.len());
962 for v in values {
963 match v {
964 Value::Null => {
965 valid.append(false);
966 offsets.push(*offsets.last().unwrap());
967 }
968 Value::List(items) => {
969 valid.append(true);
970 child.extend(items.iter().cloned());
971 offsets.push(child.len() as i32);
972 }
973 _ => return Err(SparkError::connect_msg("Type mismatch: expected array")),
974 }
975 }
976 let child_arr = values_to_arrow(&child, element_type)?;
977 let field = Arc::new(Field::new(
978 "element",
979 datatype_to_arrow(element_type)?,
980 *contains_null,
981 ));
982 Ok(Arc::new(ListArray::new(
983 field,
984 OffsetBuffer::new(offsets.into()),
985 child_arr,
986 Some(NullBuffer::new(valid.finish())),
987 )))
988 }
989 DataType::Struct { fields } => {
990 let mut cols: Vec<Vec<Value>> = vec![Vec::with_capacity(values.len()); fields.len()];
991 let mut valid = arrow::array::builder::BooleanBufferBuilder::new(values.len());
992 for v in values {
993 match v {
994 Value::Null => {
995 valid.append(false);
996 for c in cols.iter_mut() {
997 c.push(Value::Null);
998 }
999 }
1000 Value::Struct(sf) => {
1001 valid.append(true);
1002 for (i, f) in fields.iter().enumerate() {
1003 let val = sf
1005 .iter()
1006 .find(|(n, _)| n == &f.name)
1007 .map(|(_, val)| val.clone())
1008 .or_else(|| sf.get(i).map(|(_, val)| val.clone()))
1009 .unwrap_or(Value::Null);
1010 cols[i].push(val);
1011 }
1012 }
1013 Value::Map(m) => {
1016 valid.append(true);
1017 for (i, f) in fields.iter().enumerate() {
1018 cols[i].push(m.get(&f.name).cloned().unwrap_or(Value::Null));
1019 }
1020 }
1021 _ => return Err(SparkError::connect_msg("Type mismatch: expected struct")),
1022 }
1023 }
1024 let arrays: Vec<Arc<dyn Array>> = fields
1025 .iter()
1026 .enumerate()
1027 .map(|(i, f)| values_to_arrow(&cols[i], &f.data_type))
1028 .collect::<Result<_>>()?;
1029 let afields: Fields = fields
1030 .iter()
1031 .map(|f| {
1032 Ok(Field::new(
1033 &f.name,
1034 datatype_to_arrow(&f.data_type)?,
1035 f.nullable,
1036 ))
1037 })
1038 .collect::<Result<Vec<_>>>()?
1039 .into();
1040 Ok(Arc::new(StructArray::new(
1041 afields,
1042 arrays,
1043 Some(NullBuffer::new(valid.finish())),
1044 )))
1045 }
1046 DataType::Map {
1047 key_type,
1048 value_type,
1049 value_contains_null,
1050 } => {
1051 let mut keys: Vec<Value> = Vec::new();
1052 let mut vals: Vec<Value> = Vec::new();
1053 let mut offsets: Vec<i32> = vec![0];
1054 let mut valid = arrow::array::builder::BooleanBufferBuilder::new(values.len());
1055 for v in values {
1056 match v {
1057 Value::Null => {
1058 valid.append(false);
1059 offsets.push(*offsets.last().unwrap());
1060 }
1061 Value::Map(m) => {
1062 valid.append(true);
1063 for (k, val) in m {
1064 keys.push(Value::String(k.clone()));
1065 vals.push(val.clone());
1066 }
1067 offsets.push(keys.len() as i32);
1068 }
1069 _ => return Err(SparkError::connect_msg("Type mismatch: expected map")),
1070 }
1071 }
1072 let key_arr = values_to_arrow(&keys, key_type)?;
1073 let val_arr = values_to_arrow(&vals, value_type)?;
1074 let entry_fields: Fields = vec![
1075 Field::new("key", datatype_to_arrow(key_type)?, false),
1076 Field::new(
1077 "value",
1078 datatype_to_arrow(value_type)?,
1079 *value_contains_null,
1080 ),
1081 ]
1082 .into();
1083 let entries = StructArray::new(entry_fields.clone(), vec![key_arr, val_arr], None);
1084 let entries_field = Arc::new(Field::new(
1085 "entries",
1086 arrow::datatypes::DataType::Struct(entry_fields),
1087 false,
1088 ));
1089 Ok(Arc::new(MapArray::new(
1090 entries_field,
1091 OffsetBuffer::new(offsets.into()),
1092 entries,
1093 Some(NullBuffer::new(valid.finish())),
1094 false,
1095 )))
1096 }
1097 other => Err(SparkError::connect_msg(format!(
1098 "Unsupported type for Arrow createDataFrame conversion: {other:?}"
1099 ))),
1100 }
1101}
1102
1103fn build_arrow_array_scalar(
1105 rows: &[Row],
1106 field_idx: usize,
1107) -> Result<Arc<dyn arrow::array::Array>> {
1108 use arrow::array::*;
1109
1110 if rows.is_empty() {
1111 return Ok(Arc::new(NullArray::new(0)));
1112 }
1113
1114 let first_val = rows[0]
1116 .get(field_idx)
1117 .ok_or_else(|| SparkError::connect_msg("Invalid row index"))?;
1118
1119 match first_val {
1120 Value::Null => Ok(Arc::new(NullArray::new(rows.len()))),
1121 Value::Bool(_) => {
1122 let values: Result<Vec<Option<bool>>> = rows
1123 .iter()
1124 .map(|r| match r.get(field_idx) {
1125 None | Some(Value::Null) => Ok(None),
1126 Some(Value::Bool(b)) => Ok(Some(*b)),
1127 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1128 })
1129 .collect();
1130 Ok(Arc::new(BooleanArray::from(values?)))
1131 }
1132 Value::Byte(_) => {
1133 let values: Result<Vec<_>> = rows
1134 .iter()
1135 .map(|r| match r.get(field_idx) {
1136 None | Some(Value::Null) => Ok(None),
1137 Some(Value::Byte(b)) => Ok(Some(*b)),
1138 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1139 })
1140 .collect();
1141 Ok(Arc::new(Int8Array::from(values?)))
1142 }
1143 Value::Short(_) => {
1144 let values: Result<Vec<_>> = rows
1145 .iter()
1146 .map(|r| match r.get(field_idx) {
1147 None | Some(Value::Null) => Ok(None),
1148 Some(Value::Short(s)) => Ok(Some(*s)),
1149 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1150 })
1151 .collect();
1152 Ok(Arc::new(Int16Array::from(values?)))
1153 }
1154 Value::Integer(_) => {
1155 let values: Result<Vec<_>> = rows
1156 .iter()
1157 .map(|r| match r.get(field_idx) {
1158 None | Some(Value::Null) => Ok(None),
1159 Some(Value::Integer(i)) => Ok(Some(*i)),
1160 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1161 })
1162 .collect();
1163 Ok(Arc::new(Int32Array::from(values?)))
1164 }
1165 Value::Long(_) => {
1166 let values: Result<Vec<_>> = rows
1167 .iter()
1168 .map(|r| match r.get(field_idx) {
1169 None | Some(Value::Null) => Ok(None),
1170 Some(Value::Long(l)) => Ok(Some(*l)),
1171 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1172 })
1173 .collect();
1174 Ok(Arc::new(Int64Array::from(values?)))
1175 }
1176 Value::Float(_) => {
1177 let values: Result<Vec<_>> = rows
1178 .iter()
1179 .map(|r| match r.get(field_idx) {
1180 None | Some(Value::Null) => Ok(None),
1181 Some(Value::Float(f)) => Ok(Some(*f)),
1182 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1183 })
1184 .collect();
1185 Ok(Arc::new(Float32Array::from(values?)))
1186 }
1187 Value::Double(_) => {
1188 let values: Result<Vec<_>> = rows
1189 .iter()
1190 .map(|r| match r.get(field_idx) {
1191 None | Some(Value::Null) => Ok(None),
1192 Some(Value::Double(d)) => Ok(Some(*d)),
1193 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1194 })
1195 .collect();
1196 Ok(Arc::new(Float64Array::from(values?)))
1197 }
1198 Value::String(_) => {
1199 let values: Result<Vec<_>> = rows
1200 .iter()
1201 .map(|r| match r.get(field_idx) {
1202 None | Some(Value::Null) => Ok(None),
1203 Some(Value::String(s)) => Ok(Some(s.as_str())),
1204 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1205 })
1206 .collect();
1207 Ok(Arc::new(StringArray::from(values?)))
1208 }
1209 Value::Binary(_) => {
1210 let values: Result<Vec<_>> = rows
1211 .iter()
1212 .map(|r| match r.get(field_idx) {
1213 None | Some(Value::Null) => Ok(None),
1214 Some(Value::Binary(b)) => Ok(Some(b.as_slice())),
1215 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1216 })
1217 .collect();
1218 Ok(Arc::new(BinaryArray::from(values?)))
1219 }
1220 Value::Date(_) => {
1221 let values: Result<Vec<Option<i32>>> = rows
1222 .iter()
1223 .map(|r| match r.get(field_idx) {
1224 None | Some(Value::Null) => Ok(None),
1225 Some(Value::Date(d)) => Ok(Some(*d)),
1226 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1227 })
1228 .collect();
1229 Ok(Arc::new(Date32Array::from(values?)))
1230 }
1231 Value::Timestamp(_) => {
1232 let values: Result<Vec<Option<i64>>> = rows
1233 .iter()
1234 .map(|r| match r.get(field_idx) {
1235 None | Some(Value::Null) => Ok(None),
1236 Some(Value::Timestamp(t)) => Ok(Some(*t)),
1237 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1238 })
1239 .collect();
1240 let arr = TimestampMicrosecondArray::from(values?).with_timezone("UTC");
1242 Ok(Arc::new(arr))
1243 }
1244 Value::Decimal { scale, .. } => {
1245 let col_scale = scale.unwrap_or(0);
1249 let values: Result<Vec<Option<i128>>> = rows
1250 .iter()
1251 .map(|r| match r.get(field_idx) {
1252 None | Some(Value::Null) => Ok(None),
1253 Some(Value::Decimal { value, .. }) => {
1254 decimal_str_to_unscaled(value, col_scale).map(Some)
1255 }
1256 Some(_) => Err(SparkError::connect_msg("Type mismatch in row data")),
1257 })
1258 .collect();
1259 let arr = Decimal128Array::from(values?)
1260 .with_precision_and_scale(38, col_scale as i8)
1261 .map_err(|e| SparkError::connect_msg(format!("decimal build: {e}")))?;
1262 Ok(Arc::new(arr))
1263 }
1264 _ => Err(SparkError::connect_msg("Unsupported value type")),
1265 }
1266}
1267
1268fn decimal_str_to_unscaled(s: &str, scale: i32) -> Result<i128> {
1271 let s = s.trim();
1272 let (neg, s) = match s.strip_prefix('-') {
1273 Some(rest) => (true, rest),
1274 None => (false, s.strip_prefix('+').unwrap_or(s)),
1275 };
1276 let (int_part, frac_part) = match s.split_once('.') {
1277 Some((i, f)) => (i, f),
1278 None => (s, ""),
1279 };
1280 let scale = scale.max(0) as usize;
1281 let mut digits = String::with_capacity(int_part.len() + scale);
1282 digits.push_str(int_part);
1283 let frac: String = frac_part
1285 .chars()
1286 .chain(std::iter::repeat('0'))
1287 .take(scale)
1288 .collect();
1289 digits.push_str(&frac);
1290 let digits = digits.trim_start_matches('0');
1291 let mag: i128 = if digits.is_empty() {
1292 0
1293 } else {
1294 digits
1295 .parse::<i128>()
1296 .map_err(|e| SparkError::connect_msg(format!("invalid decimal '{s}': {e}")))?
1297 };
1298 Ok(if neg { -mag } else { mag })
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303 use super::*;
1304
1305 fn session() -> SparkSession {
1306 SparkSession::builder()
1307 .remote("sc://localhost:15002")
1308 .get_or_create()
1309 .expect("failed to build session")
1310 }
1311
1312 #[test]
1313 fn session_is_not_stopped_initially() {
1314 let spark = session();
1315 assert!(!spark.is_stopped());
1316 }
1317
1318 #[test]
1319 fn session_id_is_set() {
1320 let spark = session();
1321 let session_id = spark.session_id();
1322 assert!(!session_id.is_empty());
1323 }
1324
1325 #[test]
1326 fn session_builder_without_remote_fails() {
1327 let result = SparkSessionBuilder::new().get_or_create();
1328 assert!(result.is_err());
1329 }
1330
1331 #[test]
1332 fn session_tags_add_and_remove() {
1333 let spark = session();
1334 spark.add_tag("test_tag").unwrap();
1335 let tags = spark.get_tags();
1336 assert!(tags.contains(&"test_tag".to_string()));
1337
1338 spark.remove_tag("test_tag");
1339 let tags = spark.get_tags();
1340 assert!(!tags.contains(&"test_tag".to_string()));
1341 }
1342
1343 #[test]
1344 fn session_tags_cannot_be_empty() {
1345 let spark = session();
1346 let result = spark.add_tag("");
1347 assert!(result.is_err());
1348 }
1349
1350 #[test]
1351 fn session_tags_cannot_contain_comma() {
1352 let spark = session();
1353 let result = spark.add_tag("tag,with,comma");
1354 assert!(result.is_err());
1355 }
1356
1357 #[test]
1358 fn session_tags_clear() {
1359 let spark = session();
1360 spark.add_tag("tag1").unwrap();
1361 spark.add_tag("tag2").unwrap();
1362 spark.clear_tags();
1363 assert!(spark.get_tags().is_empty());
1364 }
1365
1366 #[test]
1367 fn session_tags_no_duplicates() {
1368 let spark = session();
1369 spark.add_tag("tag").unwrap();
1370 spark.add_tag("tag").unwrap();
1371 let tags = spark.get_tags();
1372 assert_eq!(tags.len(), 1);
1373 }
1374
1375 #[test]
1376 fn session_clone_shares_state() {
1377 let spark1 = session();
1378 spark1.add_tag("test").unwrap();
1379 let spark2 = spark1.clone();
1380 let tags = spark2.get_tags();
1382 assert!(tags.contains(&"test".to_string()));
1383 }
1384}