micromegas_analytics/dfext/
task_log_exec_plan.rs1use datafusion::arrow::datatypes::DataType;
2use datafusion::arrow::datatypes::Field;
3use datafusion::arrow::datatypes::Schema;
4use datafusion::arrow::datatypes::SchemaRef;
5use datafusion::arrow::datatypes::TimeUnit;
6use datafusion::common::Statistics;
7use datafusion::common::internal_err;
8use datafusion::error::DataFusionError;
9use datafusion::execution::SendableRecordBatchStream;
10use datafusion::execution::TaskContext;
11use datafusion::physical_expr::EquivalenceProperties;
12use datafusion::physical_plan::DisplayAs;
13use datafusion::physical_plan::DisplayFormatType;
14use datafusion::physical_plan::ExecutionPlan;
15use datafusion::physical_plan::Partitioning;
16use datafusion::physical_plan::PlanProperties;
17use datafusion::physical_plan::execution_plan::Boundedness;
18use datafusion::physical_plan::execution_plan::EmissionType;
19use std::sync::Arc;
20use tokio::sync::mpsc;
21
22use super::async_log_stream::AsyncLogStream;
23
24pub type TaskSpawner = dyn FnOnce() -> mpsc::Receiver<Result<(chrono::DateTime<chrono::Utc>, String), String>>
31 + Sync
32 + Send;
33
34pub struct TaskLogExecPlan {
36 schema: SchemaRef,
37 cache: Arc<PlanProperties>,
38 spawner: std::sync::Mutex<Option<Box<TaskSpawner>>>,
39}
40
41impl DisplayAs for TaskLogExecPlan {
42 fn fmt_as(&self, t: DisplayFormatType, f: &mut std::fmt::Formatter) -> std::fmt::Result {
43 match t {
44 DisplayFormatType::Default
45 | DisplayFormatType::Verbose
46 | DisplayFormatType::TreeRender => {
47 write!(f, "TaskLogExecPlan")
48 }
49 }
50 }
51}
52
53impl std::fmt::Debug for TaskLogExecPlan {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(f, "TaskLogExecPlan")
56 }
57}
58
59impl TaskLogExecPlan {
60 pub fn new(spawner: Box<TaskSpawner>) -> Self {
61 let schema = Arc::new(Schema::new(vec![
62 Field::new(
63 "time",
64 DataType::Timestamp(TimeUnit::Nanosecond, Some("+00:00".into())),
65 false,
66 ),
67 Field::new("msg", DataType::Utf8, false),
68 ]));
69
70 let cache = PlanProperties::new(
71 EquivalenceProperties::new(Arc::clone(&schema)),
72 Partitioning::RoundRobinBatch(1),
73 EmissionType::Incremental,
74 Boundedness::Unbounded {
75 requires_infinite_memory: false,
76 },
77 );
78
79 Self {
80 schema,
81 cache: Arc::new(cache),
82 spawner: std::sync::Mutex::new(Some(spawner)),
83 }
84 }
85}
86
87impl ExecutionPlan for TaskLogExecPlan {
88 fn name(&self) -> &'static str {
89 "LogExecPlan"
90 }
91
92 fn schema(&self) -> SchemaRef {
93 self.schema.clone()
94 }
95
96 fn properties(&self) -> &Arc<PlanProperties> {
97 &self.cache
98 }
99
100 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
101 vec![]
102 }
103
104 fn with_new_children(
105 self: Arc<Self>,
106 children: Vec<Arc<dyn ExecutionPlan>>,
107 ) -> datafusion::error::Result<Arc<dyn ExecutionPlan>> {
108 if children.is_empty() {
109 Ok(self)
110 } else {
111 internal_err!("Children cannot be replaced in LogExecPlan")
112 }
113 }
114
115 fn execute(
116 &self,
117 partition: usize,
118 _context: Arc<TaskContext>,
119 ) -> datafusion::error::Result<SendableRecordBatchStream> {
120 if partition >= 1 {
121 return internal_err!("Invalid partition {partition} for LogExecPlan");
122 }
123
124 let mut spawner = self.spawner.lock().map_err(|_| {
125 DataFusionError::Execution("Error locking mutex in LogExecPlan".to_owned())
126 })?;
127 if let Some(fun) = spawner.take() {
128 drop(spawner);
129 Ok(Box::pin(AsyncLogStream::new(self.schema.clone(), fun())))
130 } else {
131 internal_err!("Spawner already taken in LogExecPlan")
132 }
133 }
134
135 fn partition_statistics(
136 &self,
137 _partition: Option<usize>,
138 ) -> datafusion::error::Result<Arc<Statistics>> {
139 Ok(Arc::new(Statistics::new_unknown(&self.schema)))
140 }
141}