1use crate::blocking::{Arg, Lend};
26use crate::Result;
27
28use apache_spark_connect::catalog::{Catalog, CatalogMetadata, Database, Function, Table, TablePartition};
29use apache_spark_connect::conf::RuntimeConf;
30use apache_spark_connect::group::GroupedData;
31use apache_spark_connect::merge::MergeIntoWriter;
32use apache_spark_connect::readwriter::{DataFrameWriter, DataFrameWriterV2};
33use apache_spark_connect::row::{Row, Value};
34use apache_spark_connect::streaming::{DataStreamWriter, StreamingQuery, StreamingQueryException, StreamingQueryStatus};
35use apache_spark_connect::udf::CommonInlineUserDefinedFunctionExpression;
36use apache_spark_connect::{Column, DataFrame, DataType, StorageLevel};
37use arrow::record_batch::RecordBatch;
38use futures_core::Stream;
39use std::collections::HashMap;
40use std::pin::Pin;
41use std::task::{Context, Poll, ready};
42use tokio::sync::mpsc;
43use tokio::task::JoinHandle;
44
45macro_rules! async_ext {
50 (
51 $(#[$attr:meta])*
52 pub trait $Trait:ident for $Type:ident by ref {
53 $($async_fn:ident => $fn:ident($($arg:ident: $ArgTy:ty),*) -> $Ret:ty;)*
54 }
55 ) => {
56 async_ext!(@emit $(#[$attr])* $Trait $Type [&Self] [clone] {
57 $($async_fn => $fn($($arg: $ArgTy),*) -> $Ret;)*
58 });
59 };
60 (
61 $(#[$attr:meta])*
62 pub trait $Trait:ident for $Type:ident by value {
63 $($async_fn:ident => $fn:ident($($arg:ident: $ArgTy:ty),*) -> $Ret:ty;)*
64 }
65 ) => {
66 async_ext!(@emit $(#[$attr])* $Trait $Type [Self] [move] {
67 $($async_fn => $fn($($arg: $ArgTy),*) -> $Ret;)*
68 });
69 };
70 (@take clone $this:tt) => { $this.clone() };
71 (@take move $this:tt) => { $this };
72 (
73 @emit $(#[$attr:meta])* $Trait:ident $Type:ident [$Recv:ty] [$take:ident] {
74 $($async_fn:ident => $fn:ident($($arg:ident: $ArgTy:ty),*) -> $Ret:ty;)*
75 }
76 ) => {
77 $(#[$attr])*
78 pub trait $Trait {
79 $(
80 #[doc = concat!("Async [`", stringify!($Type), "::", stringify!($fn), "`].")]
81 fn $async_fn(self: $Recv $(, $arg: $ArgTy)*) -> impl Future<Output = Result<$Ret>> + Send + 'static;
82 )*
83 }
84
85 impl $Trait for $Type {
86 $(
87 fn $async_fn(self: $Recv $(, $arg: $ArgTy)*) -> impl Future<Output = Result<$Ret>> + Send + 'static {
88 let this = async_ext!(@take $take self);
89 $(let $arg = Arg::own($arg);)*
90 crate::blocking::blocking(move || this.$fn($(Lend::lend(&$arg)),*))
91 }
92 )*
93 }
94 };
95}
96
97async_ext! {
98 pub trait DataFrameExt for DataFrame by ref {
100 collect_async => collect() -> Vec<Row>;
101 collect_record_batches_async => collect_record_batches() -> Vec<RecordBatch>;
102 count_async => count() -> i64;
103 show_async => show(n: usize) -> ();
104 first_async => first() -> Option<Row>;
105 head_async => head() -> Option<Row>;
106 take_async => take(n: usize) -> Vec<Row>;
107 is_empty_async => is_empty() -> bool;
108 exists_async => exists() -> bool;
109 scalar_async => scalar() -> Option<Value>;
110 schema_async => schema() -> DataType;
111 columns_async => columns() -> Vec<String>;
112 dtypes_async => dtypes() -> Vec<(String, String)>;
113 print_schema_async => print_schema() -> ();
114 explain_async => explain() -> ();
115 explain_mode_async => explain_mode(mode: &str) -> ();
116 input_files_async => input_files() -> Vec<String>;
117 is_cached_async => is_cached() -> bool;
118 cache_async => cache() -> DataFrame;
119 persist_async => persist(storage_level: StorageLevel) -> DataFrame;
120 unpersist_async => unpersist(blocking: bool) -> DataFrame;
121 storage_level_async => storage_level() -> StorageLevel;
122 checkpoint_async => checkpoint() -> DataFrame;
123 local_checkpoint_async => local_checkpoint() -> DataFrame;
124 semantic_hash_async => semantic_hash() -> i32;
125 same_semantics_async => same_semantics(other: &DataFrame) -> bool;
126 create_temp_view_async => create_temp_view(name: &str) -> ();
127 create_or_replace_temp_view_async => create_or_replace_temp_view(name: &str) -> ();
128 create_global_temp_view_async => create_global_temp_view(name: &str) -> ();
129 create_or_replace_global_temp_view_async => create_or_replace_global_temp_view(name: &str) -> ();
130 register_temp_table_async => register_temp_table(name: &str) -> ();
131 to_arrow_async => to_arrow() -> Vec<u8>;
132 to_json_async => to_json() -> Vec<String>;
133 foreach_async => foreach(func: CommonInlineUserDefinedFunctionExpression) -> ();
134 foreach_partition_async => foreach_partition(func: CommonInlineUserDefinedFunctionExpression) -> ();
135 }
136}
137
138async_ext! {
139 pub trait GroupedDataExt for GroupedData by ref {
141 input_columns_async => input_columns() -> Vec<String>;
142 }
143}
144
145async_ext! {
146 pub trait CatalogExt for Catalog by ref {
156 analyze_table_async => analyze_table(table_name: &str, no_scan: bool) -> ();
157 cache_table_async => cache_table(table_name: &str) -> ();
158 cache_table_with_storage_level_async => cache_table_with_storage_level(table_name: &str, storage_level: Option<StorageLevel>) -> ();
159 clear_cache_async => clear_cache() -> ();
160 create_database_async => create_database(db_name: &str, if_not_exists: bool, properties: HashMap<String, String>) -> ();
161 current_catalog_async => current_catalog() -> String;
162 current_database_async => current_database() -> String;
163 database_exists_async => database_exists(db_name: &str) -> bool;
164 drop_database_async => drop_database(db_name: &str, if_exists: bool, cascade: bool) -> ();
165 drop_global_temp_view_async => drop_global_temp_view(view_name: &str) -> bool;
166 drop_table_async => drop_table(table_name: &str, if_exists: bool, purge: bool) -> ();
167 drop_temp_view_async => drop_temp_view(view_name: &str) -> bool;
168 drop_view_async => drop_view(view_name: &str, if_exists: bool) -> ();
169 function_exists_async => function_exists(function_name: &str) -> bool;
170 function_exists_with_database_async => function_exists_with_database(function_name: &str, db_name: Option<&str>) -> bool;
171 get_create_table_string_async => get_create_table_string(table_name: &str, as_serde: bool) -> String;
172 get_database_typed_async => get_database_typed(db_name: &str) -> Database;
173 get_function_typed_async => get_function_typed(function_name: &str) -> Function;
174 get_table_properties_async => get_table_properties(table_name: &str) -> Vec<(String, String)>;
175 get_table_typed_async => get_table_typed(table_name: &str) -> Table;
176 is_cached_async => is_cached(table_name: &str) -> bool;
177 list_catalogs_typed_async => list_catalogs_typed(pattern: Option<&str>) -> Vec<CatalogMetadata>;
178 list_databases_typed_async => list_databases_typed(pattern: Option<&str>) -> Vec<Database>;
179 list_partitions_typed_async => list_partitions_typed(table_name: &str) -> Vec<TablePartition>;
180 list_tables_typed_async => list_tables_typed(db_name: Option<&str>, pattern: Option<&str>) -> Vec<Table>;
181 list_views_async => list_views(db_name: Option<&str>, pattern: Option<&str>) -> DataFrame;
182 list_views_typed_async => list_views_typed(db_name: Option<&str>, pattern: Option<&str>) -> Vec<Table>;
183 recover_partitions_async => recover_partitions(table_name: &str) -> ();
184 refresh_by_path_async => refresh_by_path(path: &str) -> ();
185 refresh_table_async => refresh_table(table_name: &str) -> ();
186 set_current_catalog_async => set_current_catalog(catalog_name: &str) -> ();
187 set_current_database_async => set_current_database(db_name: &str) -> ();
188 table_exists_async => table_exists(table_name: &str) -> bool;
189 table_exists_with_database_async => table_exists_with_database(table_name: &str, db_name: Option<&str>) -> bool;
190 truncate_table_async => truncate_table(table_name: &str) -> ();
191 uncache_table_async => uncache_table(table_name: &str) -> ();
192 }
193}
194
195async_ext! {
196 pub trait RuntimeConfExt for RuntimeConf by ref {
198 get_async => get(key: &str) -> Option<String>;
199 get_all_async => get_all() -> HashMap<String, String>;
200 get_with_default_async => get_with_default(key: &str, default: Option<&str>) -> Option<String>;
201 is_modifiable_async => is_modifiable(key: &str) -> bool;
202 set_async => set(key: &str, value: &str) -> ();
203 unset_async => unset(key: &str) -> ();
204 }
205}
206
207async_ext! {
208 pub trait DataFrameWriterExt for DataFrameWriter by value {
210 save_async => save(path: Option<&str>) -> ();
211 save_as_table_async => save_as_table(table_name: &str) -> ();
212 insert_into_async => insert_into(table_name: &str) -> ();
213 csv_async => csv(path: &str) -> ();
214 json_async => json(path: &str) -> ();
215 orc_async => orc(path: &str) -> ();
216 parquet_async => parquet(path: &str) -> ();
217 text_async => text(path: &str) -> ();
218 xml_async => xml(path: &str) -> ();
219 }
220}
221
222async_ext! {
223 pub trait DataFrameWriterV2Ext for DataFrameWriterV2 by value {
225 append_async => append() -> ();
226 create_async => create() -> ();
227 create_or_replace_async => create_or_replace() -> ();
228 overwrite_async => overwrite(condition: Column) -> ();
229 overwrite_partitions_async => overwrite_partitions() -> ();
230 replace_async => replace() -> ();
231 }
232}
233
234async_ext! {
235 pub trait MergeIntoWriterExt for MergeIntoWriter by value {
237 merge_async => merge() -> ();
238 }
239}
240
241async_ext! {
242 pub trait DataStreamWriterExt for DataStreamWriter by value {
244 start_async => start(path: &str) -> StreamingQuery;
245 to_table_async => to_table(table_name: &str) -> StreamingQuery;
246 }
247}
248
249async_ext! {
250 pub trait StreamingQueryExt for StreamingQuery by ref {
252 is_active_async => is_active() -> bool;
253 status_async => status() -> StreamingQueryStatus;
254 stop_async => stop() -> ();
255 await_termination_async => await_termination(timeout_sec: Option<f64>) -> Option<bool>;
256 exception_async => exception() -> Option<StreamingQueryException>;
257 last_progress_async => last_progress() -> Option<String>;
258 recent_progress_async => recent_progress() -> Vec<String>;
259 process_all_available_async => process_all_available() -> ();
260 explain_async => explain(extended: bool) -> String;
261 }
262}
263
264const ROW_BUFFER: usize = 1024;
266
267pub trait DataFrameStreamExt {
269 fn to_local_iterator_async(&self, prefetch_partitions: bool) -> RowStream;
275}
276
277impl DataFrameStreamExt for DataFrame {
278 fn to_local_iterator_async(&self, prefetch_partitions: bool) -> RowStream {
279 let df = self.clone();
280 let (sender, rows) = mpsc::channel(ROW_BUFFER);
281
282 let task = tokio::task::spawn_blocking(move || {
283 let iterator = match df.to_local_iterator(prefetch_partitions) {
284 Ok(iterator) => iterator,
285 Err(error) => return drop(sender.blocking_send(Err(error))),
286 };
287 for row in iterator {
288 if sender.blocking_send(row).is_err() {
289 break;
290 }
291 }
292 });
293
294 RowStream { rows, task: Some(task) }
295 }
296}
297
298pub struct RowStream {
304 rows: mpsc::Receiver<Result<Row>>,
305 task: Option<JoinHandle<()>>,
306}
307
308impl Stream for RowStream {
309 type Item = Result<Row>;
310
311 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
312 if let Some(row) = ready!(self.rows.poll_recv(cx)) {
313 return Poll::Ready(Some(row));
314 }
315 if let Some(task) = self.task.as_mut() {
316 let finished = ready!(Pin::new(task).poll(cx));
317 self.task = None;
318 if let Err(error) = finished
319 && error.is_panic()
320 {
321 std::panic::resume_unwind(error.into_panic());
322 }
323 }
324 Poll::Ready(None)
325 }
326}