1use std::collections::HashMap;
16use std::ops::Div;
17use std::pin::Pin;
18use std::sync::Arc;
19use std::task::{Context, Poll};
20
21use datafusion::arrow::array::ArrayRef;
22use datafusion::arrow::datatypes::{DataType, TimeUnit};
23use datafusion::catalog::Session;
24use datafusion::common::arrow::datatypes::Field;
25use datafusion::common::stats::Precision;
26use datafusion::common::tree_node::TreeNodeRecursion;
27use datafusion::common::{
28 DFSchema, DFSchemaRef, Result as DataFusionResult, Statistics, TableReference,
29};
30use datafusion::datasource::{MemTable, provider_as_source};
31use datafusion::error::DataFusionError;
32use datafusion::execution::context::TaskContext;
33use datafusion::logical_expr::physical_planning_context::PhysicalPlanningContext;
34use datafusion::logical_expr::{ExprSchemable, LogicalPlan, UserDefinedLogicalNodeCore};
35use datafusion::physical_expr::{EquivalenceProperties, PhysicalExpr, PhysicalExprRef};
36use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
37use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
38use datafusion::physical_plan::{
39 DisplayAs, DisplayFormatType, ExecutionPlan, Partitioning, PlanProperties, RecordBatchStream,
40 SendableRecordBatchStream, StatisticsArgs,
41};
42use datafusion::physical_planner::PhysicalPlanner;
43use datafusion::prelude::{Expr, ident, lit};
44use datafusion_expr::LogicalPlanBuilder;
45use datatypes::arrow::array::TimestampMillisecondArray;
46use datatypes::arrow::datatypes::SchemaRef;
47use datatypes::arrow::record_batch::RecordBatch;
48use futures::Stream;
49
50use crate::extension_plan::Millisecond;
51
52#[derive(Debug, Clone, PartialEq, Eq, Hash)]
57pub struct EmptyMetric {
58 start: Millisecond,
59 end: Millisecond,
60 interval: Millisecond,
61 expr: Option<Expr>,
62 time_index_schema: DFSchemaRef,
65 result_schema: DFSchemaRef,
67 dummy_input: LogicalPlan,
73}
74
75impl EmptyMetric {
76 pub fn new(
77 start: Millisecond,
78 end: Millisecond,
79 interval: Millisecond,
80 time_index_column_name: String,
81 field_column_name: String,
82 field_expr: Option<Expr>,
83 ) -> DataFusionResult<Self> {
84 let qualifier = Some(TableReference::bare(""));
85 let ts_only_schema = build_ts_only_schema(&time_index_column_name);
86 let mut fields = vec![(qualifier.clone(), ts_only_schema.field(0).clone())];
87 if let Some(field_expr) = &field_expr {
88 let field_data_type = field_expr.get_type(&ts_only_schema)?;
89 fields.push((
90 qualifier.clone(),
91 Arc::new(Field::new(field_column_name, field_data_type, true)),
92 ));
93 }
94 let schema = Arc::new(DFSchema::new_with_metadata(fields, HashMap::new())?);
95
96 let table = MemTable::try_new(Arc::new(schema.as_arrow().clone()), vec![vec![]])?;
97 let source = provider_as_source(Arc::new(table));
98 let dummy_input =
99 LogicalPlanBuilder::scan("dummy", source, None).and_then(|x| x.build())?;
100
101 Ok(Self {
102 start,
103 end,
104 interval,
105 time_index_schema: Arc::new(ts_only_schema),
106 result_schema: schema,
107 expr: field_expr,
108 dummy_input,
109 })
110 }
111
112 pub fn expr(&self) -> Option<&Expr> {
114 self.expr.as_ref()
115 }
116
117 pub const fn name() -> &'static str {
118 "EmptyMetric"
119 }
120
121 pub fn to_execution_plan(
122 &self,
123 session: &dyn Session,
124 physical_planner: &dyn PhysicalPlanner,
125 planning_ctx: &PhysicalPlanningContext,
126 ) -> DataFusionResult<Arc<dyn ExecutionPlan>> {
127 let physical_expr = self
128 .expr
129 .as_ref()
130 .map(|expr| {
131 physical_planner.create_physical_expr(
132 expr,
133 &self.time_index_schema,
134 session,
135 planning_ctx,
136 )
137 })
138 .transpose()?;
139 let result_schema: SchemaRef = self.result_schema.inner().clone();
140 let properties = Arc::new(PlanProperties::new(
141 EquivalenceProperties::new(result_schema.clone()),
142 Partitioning::UnknownPartitioning(1),
143 EmissionType::Incremental,
144 Boundedness::Bounded,
145 ));
146 Ok(Arc::new(EmptyMetricExec {
147 start: self.start,
148 end: self.end,
149 interval: self.interval,
150 time_index_schema: self.time_index_schema.inner().clone(),
151 result_schema,
152 expr: physical_expr,
153 properties,
154 metric: ExecutionPlanMetricsSet::new(),
155 }))
156 }
157}
158
159impl UserDefinedLogicalNodeCore for EmptyMetric {
160 fn name(&self) -> &str {
161 Self::name()
162 }
163
164 fn inputs(&self) -> Vec<&LogicalPlan> {
165 vec![&self.dummy_input]
166 }
167
168 fn schema(&self) -> &DFSchemaRef {
169 &self.result_schema
170 }
171
172 fn expressions(&self) -> Vec<Expr> {
173 if let Some(expr) = &self.expr {
174 vec![expr.clone()]
175 } else {
176 vec![]
177 }
178 }
179
180 fn fmt_for_explain(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
181 write!(
182 f,
183 "EmptyMetric: range=[{}..{}], interval=[{}]",
184 self.start, self.end, self.interval,
185 )
186 }
187
188 fn with_exprs_and_inputs(
189 &self,
190 exprs: Vec<Expr>,
191 _inputs: Vec<LogicalPlan>,
192 ) -> DataFusionResult<Self> {
193 Ok(Self {
194 start: self.start,
195 end: self.end,
196 interval: self.interval,
197 expr: exprs.into_iter().next(),
198 time_index_schema: self.time_index_schema.clone(),
199 result_schema: self.result_schema.clone(),
200 dummy_input: self.dummy_input.clone(),
201 })
202 }
203}
204
205impl PartialOrd for EmptyMetric {
206 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
207 match self.start.partial_cmp(&other.start) {
209 Some(core::cmp::Ordering::Equal) => {}
210 ord => return ord,
211 }
212 match self.end.partial_cmp(&other.end) {
213 Some(core::cmp::Ordering::Equal) => {}
214 ord => return ord,
215 }
216 match self.interval.partial_cmp(&other.interval) {
217 Some(core::cmp::Ordering::Equal) => {}
218 ord => return ord,
219 }
220 self.expr.partial_cmp(&other.expr)
221 }
222}
223
224#[derive(Debug, Clone)]
225pub struct EmptyMetricExec {
226 start: Millisecond,
227 end: Millisecond,
228 interval: Millisecond,
229 time_index_schema: SchemaRef,
232 result_schema: SchemaRef,
234 expr: Option<PhysicalExprRef>,
235 properties: Arc<PlanProperties>,
236 metric: ExecutionPlanMetricsSet,
237}
238
239impl ExecutionPlan for EmptyMetricExec {
240 fn apply_expressions(
241 &self,
242 f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
243 ) -> DataFusionResult<TreeNodeRecursion> {
244 datafusion::physical_plan::apply_expression_roots(self.expr.iter(), f)
245 }
246
247 fn schema(&self) -> SchemaRef {
248 self.result_schema.clone()
249 }
250
251 fn properties(&self) -> &Arc<PlanProperties> {
252 &self.properties
253 }
254
255 fn maintains_input_order(&self) -> Vec<bool> {
256 vec![]
257 }
258
259 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
260 vec![]
261 }
262
263 fn with_new_children(
264 self: Arc<Self>,
265 _children: Vec<Arc<dyn ExecutionPlan>>,
266 ) -> DataFusionResult<Arc<dyn ExecutionPlan>> {
267 Ok(Arc::new(self.as_ref().clone()))
268 }
269
270 fn execute(
271 &self,
272 partition: usize,
273 _context: Arc<TaskContext>,
274 ) -> DataFusionResult<SendableRecordBatchStream> {
275 let baseline_metric = BaselineMetrics::new(&self.metric, partition);
276 Ok(Box::pin(EmptyMetricStream {
277 start: self.start,
278 end: self.end,
279 interval: self.interval,
280 expr: self.expr.clone(),
281 is_first_poll: true,
282 time_index_schema: self.time_index_schema.clone(),
283 result_schema: self.result_schema.clone(),
284 metric: baseline_metric,
285 }))
286 }
287
288 fn metrics(&self) -> Option<MetricsSet> {
289 Some(self.metric.clone_inner())
290 }
291
292 fn statistics_from_inputs(
293 &self,
294 _input_stats: &[Arc<Statistics>],
295 args: &StatisticsArgs,
296 ) -> DataFusionResult<Arc<Statistics>> {
297 let partition = args.partition();
298 if partition.is_some() {
299 return Ok(Arc::new(Statistics::new_unknown(self.schema().as_ref())));
300 }
301
302 let estimated_row_num = if self.end > self.start {
303 (self.end - self.start) as f64 / self.interval as f64
304 } else {
305 0.0
306 };
307 let total_byte_size = estimated_row_num * std::mem::size_of::<Millisecond>() as f64;
308
309 Ok(Arc::new(Statistics {
310 num_rows: Precision::Inexact(estimated_row_num.floor() as _),
311 total_byte_size: Precision::Inexact(total_byte_size.floor() as _),
312 column_statistics: Statistics::unknown_column(&self.schema()),
313 }))
314 }
315
316 fn name(&self) -> &str {
317 "EmptyMetricExec"
318 }
319}
320
321impl DisplayAs for EmptyMetricExec {
322 fn fmt_as(&self, t: DisplayFormatType, f: &mut std::fmt::Formatter) -> std::fmt::Result {
323 match t {
324 DisplayFormatType::Default
325 | DisplayFormatType::Verbose
326 | DisplayFormatType::TreeRender => write!(
327 f,
328 "EmptyMetric: range=[{}..{}], interval=[{}]",
329 self.start, self.end, self.interval,
330 ),
331 }
332 }
333}
334
335pub struct EmptyMetricStream {
336 start: Millisecond,
337 end: Millisecond,
338 interval: Millisecond,
339 expr: Option<PhysicalExprRef>,
340 is_first_poll: bool,
342 time_index_schema: SchemaRef,
345 result_schema: SchemaRef,
347 metric: BaselineMetrics,
348}
349
350impl RecordBatchStream for EmptyMetricStream {
351 fn schema(&self) -> SchemaRef {
352 self.result_schema.clone()
353 }
354}
355
356impl Stream for EmptyMetricStream {
357 type Item = DataFusionResult<RecordBatch>;
358
359 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
360 let result = if self.is_first_poll {
361 self.is_first_poll = false;
362 let _timer = self.metric.elapsed_compute().timer();
363
364 let time_array = (self.start..=self.end)
367 .step_by(self.interval as _)
368 .collect::<Vec<_>>();
369 let time_array = Arc::new(TimestampMillisecondArray::from(time_array));
370 let num_rows = time_array.len();
371 let input_record_batch =
372 RecordBatch::try_new(self.time_index_schema.clone(), vec![time_array.clone()])
373 .map_err(|e| DataFusionError::ArrowError(Box::new(e), None))?;
374 let mut result_arrays: Vec<ArrayRef> = vec![time_array];
375
376 if let Some(field_expr) = &self.expr {
378 result_arrays.push(
379 field_expr
380 .evaluate(&input_record_batch)
381 .and_then(|x| x.into_array(num_rows))?,
382 );
383 }
384
385 let batch = RecordBatch::try_new(self.result_schema.clone(), result_arrays)
387 .map_err(|e| DataFusionError::ArrowError(Box::new(e), None));
388
389 Poll::Ready(Some(batch))
390 } else {
391 Poll::Ready(None)
392 };
393 self.metric.record_poll(result)
394 }
395}
396
397fn build_ts_only_schema(column_name: &str) -> DFSchema {
399 let ts_field = Field::new(
400 column_name,
401 DataType::Timestamp(TimeUnit::Millisecond, None),
402 false,
403 );
404 DFSchema::new_with_metadata(
406 vec![(Some(TableReference::bare("")), Arc::new(ts_field))],
407 HashMap::new(),
408 )
409 .unwrap()
410}
411
412pub fn build_special_time_expr(time_index_column_name: &str) -> Expr {
415 let input_schema = build_ts_only_schema(time_index_column_name);
416 ident(time_index_column_name)
418 .cast_to(&DataType::Int64, &input_schema)
419 .unwrap()
420 .cast_to(&DataType::Float64, &input_schema)
421 .unwrap()
422 .div(lit(1000.0)) }
424
425#[cfg(test)]
426mod test {
427 use datafusion::physical_planner::DefaultPhysicalPlanner;
428 use datafusion::prelude::SessionContext;
429
430 use super::*;
431
432 async fn do_empty_metric_test(
433 start: Millisecond,
434 end: Millisecond,
435 interval: Millisecond,
436 time_column_name: String,
437 field_column_name: String,
438 expected: String,
439 ) {
440 let session_context = SessionContext::default();
441 let df_default_physical_planner = DefaultPhysicalPlanner::default();
442 let time_expr = build_special_time_expr(&time_column_name);
443 let empty_metric = EmptyMetric::new(
444 start,
445 end,
446 interval,
447 time_column_name,
448 field_column_name,
449 Some(time_expr),
450 )
451 .unwrap();
452 let empty_metric_exec = empty_metric
453 .to_execution_plan(
454 &session_context.state(),
455 &df_default_physical_planner,
456 &PhysicalPlanningContext::default(),
457 )
458 .unwrap();
459
460 let result =
461 datafusion::physical_plan::collect(empty_metric_exec, session_context.task_ctx())
462 .await
463 .unwrap();
464 let result_literal = datatypes::arrow::util::pretty::pretty_format_batches(&result)
465 .unwrap()
466 .to_string();
467
468 assert_eq!(result_literal, expected);
469 }
470
471 #[tokio::test]
472 async fn normal_empty_metric_test() {
473 do_empty_metric_test(
474 0,
475 100,
476 10,
477 "time".to_string(),
478 "value".to_string(),
479 String::from(
480 "+-------------------------+-------+\
481 \n| time | value |\
482 \n+-------------------------+-------+\
483 \n| 1970-01-01T00:00:00 | 0.0 |\
484 \n| 1970-01-01T00:00:00.010 | 0.01 |\
485 \n| 1970-01-01T00:00:00.020 | 0.02 |\
486 \n| 1970-01-01T00:00:00.030 | 0.03 |\
487 \n| 1970-01-01T00:00:00.040 | 0.04 |\
488 \n| 1970-01-01T00:00:00.050 | 0.05 |\
489 \n| 1970-01-01T00:00:00.060 | 0.06 |\
490 \n| 1970-01-01T00:00:00.070 | 0.07 |\
491 \n| 1970-01-01T00:00:00.080 | 0.08 |\
492 \n| 1970-01-01T00:00:00.090 | 0.09 |\
493 \n| 1970-01-01T00:00:00.100 | 0.1 |\
494 \n+-------------------------+-------+",
495 ),
496 )
497 .await
498 }
499
500 #[tokio::test]
501 async fn unaligned_empty_metric_test() {
502 do_empty_metric_test(
503 0,
504 100,
505 11,
506 "time".to_string(),
507 "value".to_string(),
508 String::from(
509 "+-------------------------+-------+\
510 \n| time | value |\
511 \n+-------------------------+-------+\
512 \n| 1970-01-01T00:00:00 | 0.0 |\
513 \n| 1970-01-01T00:00:00.011 | 0.011 |\
514 \n| 1970-01-01T00:00:00.022 | 0.022 |\
515 \n| 1970-01-01T00:00:00.033 | 0.033 |\
516 \n| 1970-01-01T00:00:00.044 | 0.044 |\
517 \n| 1970-01-01T00:00:00.055 | 0.055 |\
518 \n| 1970-01-01T00:00:00.066 | 0.066 |\
519 \n| 1970-01-01T00:00:00.077 | 0.077 |\
520 \n| 1970-01-01T00:00:00.088 | 0.088 |\
521 \n| 1970-01-01T00:00:00.099 | 0.099 |\
522 \n+-------------------------+-------+",
523 ),
524 )
525 .await
526 }
527
528 #[tokio::test]
529 async fn one_row_empty_metric_test() {
530 do_empty_metric_test(
531 0,
532 100,
533 1000,
534 "time".to_string(),
535 "value".to_string(),
536 String::from(
537 "+---------------------+-------+\
538 \n| time | value |\
539 \n+---------------------+-------+\
540 \n| 1970-01-01T00:00:00 | 0.0 |\
541 \n+---------------------+-------+",
542 ),
543 )
544 .await
545 }
546
547 #[tokio::test]
548 async fn negative_range_empty_metric_test() {
549 do_empty_metric_test(
550 1000,
551 -1000,
552 10,
553 "time".to_string(),
554 "value".to_string(),
555 String::from(
556 "+------+-------+\
557 \n| time | value |\
558 \n+------+-------+\
559 \n+------+-------+",
560 ),
561 )
562 .await
563 }
564
565 #[tokio::test]
566 async fn no_field_expr() {
567 let session_context = SessionContext::default();
568 let df_default_physical_planner = DefaultPhysicalPlanner::default();
569 let empty_metric =
570 EmptyMetric::new(0, 200, 1000, "time".to_string(), "value".to_string(), None).unwrap();
571 let empty_metric_exec = empty_metric
572 .to_execution_plan(
573 &session_context.state(),
574 &df_default_physical_planner,
575 &PhysicalPlanningContext::default(),
576 )
577 .unwrap();
578
579 let result =
580 datafusion::physical_plan::collect(empty_metric_exec, session_context.task_ctx())
581 .await
582 .unwrap();
583 let result_literal = datatypes::arrow::util::pretty::pretty_format_batches(&result)
584 .unwrap()
585 .to_string();
586
587 let expected = String::from(
588 "+---------------------+\
589 \n| time |\
590 \n+---------------------+\
591 \n| 1970-01-01T00:00:00 |\
592 \n+---------------------+",
593 );
594 assert_eq!(result_literal, expected);
595 }
596}