1use std::collections::{BTreeSet, HashSet};
16use std::sync::Arc;
17use std::time::Duration;
18
19use arrow_schema::DataType;
20use async_recursion::async_recursion;
21use catalog::table_source::DfTableSourceProvider;
22use chrono::{DateTime, Utc};
23use common_function::aggrs::aggr_wrapper::get_aggr_func;
24use common_time::interval::{MS_PER_DAY, NANOS_PER_MILLI};
25use common_time::timestamp::TimeUnit;
26use common_time::{IntervalDayTime, IntervalMonthDayNano, IntervalYearMonth, Timestamp, Timezone};
27use datafusion::datasource::DefaultTableSource;
28use datafusion::prelude::Column;
29use datafusion::scalar::ScalarValue;
30use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRecursion, TreeNodeRewriter};
31use datafusion_common::{DFSchema, DataFusionError, Result as DFResult};
32use datafusion_expr::expr::WildcardOptions;
33use datafusion_expr::simplify::SimplifyContext;
34use datafusion_expr::utils::expr_to_columns;
35use datafusion_expr::{
36 Aggregate, Analyze, Cast, Distinct, DistinctOn, Explain, Expr, ExprSchemable, Extension,
37 Literal, LogicalPlan, LogicalPlanBuilder, Projection,
38};
39use datafusion_optimizer::simplify_expressions::ExprSimplifier;
40use datatypes::prelude::ConcreteDataType;
41use datatypes::schema::TIME_INDEX_KEY;
42use promql_parser::util::parse_duration;
43use session::context::QueryContextRef;
44use snafu::{OptionExt, ResultExt, ensure};
45use table::table::adapter::DfTableProviderAdapter;
46
47use crate::error::{
48 CatalogSnafu, RangeQuerySnafu, Result, TimeIndexNotFoundSnafu, UnknownTableSnafu,
49};
50use crate::plan::ExtractExpr;
51use crate::range_select::plan::{Fill, RangeFn, RangeSelect};
52
53pub struct RangeExprRewriter<'a> {
57 input_plan: &'a Arc<LogicalPlan>,
58 align: Duration,
59 align_to: i64,
60 by: Vec<Expr>,
61 range_fn: BTreeSet<RangeFn>,
63 sub_aggr: &'a Aggregate,
64 query_ctx: &'a QueryContextRef,
65}
66
67impl RangeExprRewriter<'_> {
68 pub fn get_range_expr(&self, args: &[Expr], i: usize) -> DFResult<Expr> {
69 match args.get(i) {
70 Some(Expr::Column(column)) => {
71 let index = self.sub_aggr.schema.index_of_column(column)?;
72 let len = self.sub_aggr.group_expr.len();
73 self.sub_aggr
74 .aggr_expr
75 .get(index - len)
76 .cloned()
77 .ok_or(DataFusionError::Plan(
78 "Range expr not found in underlying Aggregate Plan".into(),
79 ))
80 }
81 Some(Expr::Alias(alias)) => {
82 self.get_range_expr(std::slice::from_ref(alias.expr.as_ref()), 0)
83 }
84 other => Err(dispose_parse_error(other)),
85 }
86 }
87}
88
89#[inline]
90fn dispose_parse_error(expr: Option<&Expr>) -> DataFusionError {
91 DataFusionError::Plan(
92 expr.map(|x| {
93 format!(
94 "Illegal argument `{}` in range select query",
95 x.schema_name()
96 )
97 })
98 .unwrap_or("Missing argument in range select query".into()),
99 )
100}
101
102fn parse_str_expr(args: &[Expr], i: usize) -> DFResult<&str> {
103 match args.get(i) {
104 Some(Expr::Literal(ScalarValue::Utf8(Some(str)), _)) => Ok(str.as_str()),
105 other => Err(dispose_parse_error(other)),
106 }
107}
108
109fn parse_expr_to_string(args: &[Expr], i: usize) -> DFResult<String> {
110 match args.get(i) {
111 Some(Expr::Literal(ScalarValue::Utf8(Some(str)), _)) => Ok(str.clone()),
112 Some(expr) => Ok(expr.schema_name().to_string()),
113 None => Err(dispose_parse_error(None)),
114 }
115}
116
117fn time_millisecond_overflow(expr: &Expr) -> DataFusionError {
118 DataFusionError::Plan(format!(
119 "overflow converting `{}` to milliseconds in range select query",
120 expr.schema_name()
121 ))
122}
123
124fn seconds_to_millisecond(seconds: i64, expr: &Expr) -> DFResult<i64> {
125 seconds
126 .checked_mul(1_000)
127 .ok_or_else(|| time_millisecond_overflow(expr))
128}
129
130fn parse_duration_expr(args: &[Expr], i: usize) -> DFResult<Duration> {
135 match args.get(i) {
136 Some(Expr::Literal(ScalarValue::Utf8(Some(str)), _)) => {
137 parse_duration(str).map_err(DataFusionError::Plan)
138 }
139 Some(expr) => {
140 let ms = evaluate_expr_to_millisecond(args, i, true, None)?;
141 if ms <= 0 {
142 return Err(dispose_parse_error(Some(expr)));
143 }
144 Ok(Duration::from_millis(ms as u64))
145 }
146 None => Err(dispose_parse_error(None)),
147 }
148}
149
150fn evaluate_expr_to_millisecond(
158 args: &[Expr],
159 i: usize,
160 interval_only: bool,
161 scheduled_time: Option<DateTime<Utc>>,
162) -> DFResult<i64> {
163 let Some(expr) = args.get(i) else {
164 return Err(dispose_parse_error(None));
165 };
166 if interval_only && !interval_only_in_expr(expr) {
167 return Err(dispose_parse_error(Some(expr)));
168 }
169 let info = match scheduled_time {
170 Some(dt) => SimplifyContext::default().with_query_execution_start_time(Some(dt)),
171 None => SimplifyContext::default().with_current_time(),
172 };
173 let simplify_expr = ExprSimplifier::new(info).simplify(expr.clone())?;
174 match simplify_expr {
175 Expr::Literal(ScalarValue::TimestampNanosecond(ts_nanos, _), _) => {
176 ts_nanos.map(|v| v.div_euclid(NANOS_PER_MILLI))
177 }
178 Expr::Literal(ScalarValue::DurationNanosecond(ts_nanos), _) => {
179 ts_nanos.map(|v| v / NANOS_PER_MILLI)
180 }
181 Expr::Literal(ScalarValue::TimestampMicrosecond(ts_micros, _), _) => {
182 ts_micros.map(|v| v.div_euclid(1_000))
183 }
184 Expr::Literal(ScalarValue::DurationMicrosecond(ts_micros), _) => {
185 ts_micros.map(|v| v / 1_000)
186 }
187 Expr::Literal(ScalarValue::TimestampMillisecond(ts_millis, _), _)
188 | Expr::Literal(ScalarValue::DurationMillisecond(ts_millis), _) => ts_millis,
189 Expr::Literal(ScalarValue::TimestampSecond(ts_secs, _), _)
190 | Expr::Literal(ScalarValue::DurationSecond(ts_secs), _) => ts_secs
191 .map(|v| seconds_to_millisecond(v, expr))
192 .transpose()?,
193 Expr::Literal(ScalarValue::IntervalYearMonth(interval), _) => interval
195 .map(|v| {
196 let interval = IntervalYearMonth::from_i32(v);
197 if interval.months != 0 {
198 return Err(DataFusionError::Plan(format!(
199 "Year or month interval is not allowed in range query: {}",
200 expr.schema_name()
201 )));
202 }
203
204 Ok(0)
205 })
206 .transpose()?,
207 Expr::Literal(ScalarValue::IntervalDayTime(interval), _) => interval.map(|v| {
208 let interval = IntervalDayTime::from(v);
209 interval.as_millis()
210 }),
211 Expr::Literal(ScalarValue::IntervalMonthDayNano(interval), _) => interval
212 .map(|v| {
213 let interval = IntervalMonthDayNano::from(v);
214 if interval.months != 0 {
215 return Err(DataFusionError::Plan(format!(
216 "Year or month interval is not allowed in range query: {}",
217 expr.schema_name()
218 )));
219 }
220
221 let day_millis = (interval.days as i64)
222 .checked_mul(MS_PER_DAY)
223 .ok_or_else(|| time_millisecond_overflow(expr))?;
224 let nanosecond_millis = interval.nanoseconds.div_euclid(NANOS_PER_MILLI);
225 day_millis
226 .checked_add(nanosecond_millis)
227 .ok_or_else(|| time_millisecond_overflow(expr))
228 })
229 .transpose()?,
230 _ => None,
231 }
232 .ok_or_else(|| {
233 DataFusionError::Plan(format!(
234 "{} is not a expr can be evaluate and use in range query",
235 expr.schema_name()
236 ))
237 })
238}
239
240fn parse_align_to(
247 args: &[Expr],
248 i: usize,
249 timezone: Option<&Timezone>,
250 scheduled_time: Option<DateTime<Utc>>,
251) -> DFResult<i64> {
252 let Ok(s) = parse_str_expr(args, i) else {
253 return evaluate_expr_to_millisecond(args, i, false, scheduled_time);
254 };
255 let upper = s.to_uppercase();
256 match upper.as_str() {
257 "NOW" => {
258 return Ok(scheduled_time
259 .map(|dt| dt.timestamp_millis())
260 .unwrap_or_else(|| Timestamp::current_millis().value()));
261 }
262 "" => return Ok(timezone.map(|tz| tz.local_minus_utc() * 1000).unwrap_or(0)),
264 _ => (),
265 }
266
267 Timestamp::from_str(s, timezone)
268 .map_err(|e| {
269 DataFusionError::Plan(format!(
270 "Illegal `align to` argument `{}` in range select query, can't be parse as NOW/CALENDAR/Timestamp, error: {}",
271 s, e
272 ))
273 })?.convert_to(TimeUnit::Millisecond).map(|x|x.value()).ok_or(DataFusionError::Plan(format!(
274 "Illegal `align to` argument `{}` in range select query, can't be convert to a valid Timestamp",
275 s
276 ))
277 )
278}
279
280fn parse_expr_list(args: &[Expr], start: usize, len: usize) -> DFResult<Vec<Expr>> {
281 let mut outs = Vec::with_capacity(len);
282 for i in start..start + len {
283 outs.push(match &args.get(i) {
284 Some(
285 Expr::Column(_)
286 | Expr::Literal(_, _)
287 | Expr::BinaryExpr(_)
288 | Expr::ScalarFunction(_),
289 ) => args[i].clone(),
290 Some(Expr::Alias(alias)) if matches!(*alias.expr, Expr::ScalarFunction(_)) => {
291 args[i].clone()
292 }
293 other => {
294 return Err(dispose_parse_error(*other));
295 }
296 });
297 }
298 Ok(outs)
299}
300
301macro_rules! inconsistent_check {
302 ($self: ident.$name: ident, $cond: expr) => {
303 if $cond && $self.$name != $name {
304 return Err(DataFusionError::Plan(
305 concat!(
306 "Inconsistent ",
307 stringify!($name),
308 " given in Range Function Rewrite"
309 )
310 .into(),
311 ));
312 } else {
313 $self.$name = $name;
314 }
315 };
316}
317
318impl TreeNodeRewriter for RangeExprRewriter<'_> {
319 type Node = Expr;
320
321 fn f_down(&mut self, node: Expr) -> DFResult<Transformed<Expr>> {
322 if let Expr::ScalarFunction(func) = &node
323 && func.name() == "range_fn"
324 {
325 let range_expr = self.get_range_expr(&func.args, 0)?;
328 let range = parse_duration_expr(&func.args, 1)?;
329 let byc = str::parse::<usize>(parse_str_expr(&func.args, 3)?)
330 .map_err(|e| DataFusionError::Plan(e.to_string()))?;
331 let by = parse_expr_list(&func.args, 4, byc)?;
332 let align = parse_duration_expr(&func.args, byc + 4)?;
333 let scheduled_time =
334 crate::options::parse_scheduled_time_datetime(&self.query_ctx.extensions())
335 .map_err(|err| DataFusionError::Plan(err.to_string()))?;
336 let align_to = parse_align_to(
337 &func.args,
338 byc + 5,
339 Some(&self.query_ctx.timezone()),
340 scheduled_time,
341 )?;
342 let mut data_type = range_expr.get_type(self.input_plan.schema())?;
343 let mut need_cast = false;
344 let fill = Fill::try_from_str(parse_str_expr(&func.args, 2)?, &data_type)?;
345 if matches!(fill, Some(Fill::Linear)) && data_type.is_integer() {
346 data_type = DataType::Float64;
347 need_cast = true;
348 }
349 inconsistent_check!(self.by, !self.by.is_empty());
350 inconsistent_check!(self.align, self.align != Duration::default());
351 inconsistent_check!(self.align_to, self.align_to != 0);
352 let range_fn = RangeFn {
353 name: if let Some(fill) = &fill {
354 format!(
355 "{} RANGE {} FILL {}",
356 range_expr.schema_name(),
357 parse_expr_to_string(&func.args, 1)?,
358 fill
359 )
360 } else {
361 format!(
362 "{} RANGE {}",
363 range_expr.schema_name(),
364 parse_expr_to_string(&func.args, 1)?,
365 )
366 },
367 data_type,
368 expr: range_expr,
369 range,
370 fill,
371 need_cast,
372 };
373 let alias = Expr::Column(Column::from_name(range_fn.name.clone()));
374 self.range_fn.insert(range_fn);
375 return Ok(Transformed::yes(alias));
376 }
377 Ok(Transformed::no(node))
378 }
379}
380
381pub struct RangePlanRewriter {
388 table_provider: DfTableSourceProvider,
389 query_ctx: QueryContextRef,
390}
391
392impl RangePlanRewriter {
393 pub fn new(table_provider: DfTableSourceProvider, query_ctx: QueryContextRef) -> Self {
394 Self {
395 table_provider,
396 query_ctx,
397 }
398 }
399
400 pub async fn rewrite(&mut self, plan: LogicalPlan) -> Result<LogicalPlan> {
401 match self.rewrite_logical_plan(&plan).await? {
402 Some(new_plan) => Ok(new_plan),
403 None => Ok(plan),
404 }
405 }
406
407 #[async_recursion]
408 async fn rewrite_logical_plan(&mut self, plan: &LogicalPlan) -> Result<Option<LogicalPlan>> {
409 let inputs = plan.inputs();
410 let mut new_inputs = Vec::with_capacity(inputs.len());
411 for input in &inputs {
412 new_inputs.push(self.rewrite_logical_plan(input).await?)
413 }
414 match plan {
415 LogicalPlan::Projection(Projection { expr, input, .. })
416 if have_range_in_exprs(expr) =>
417 {
418 let (aggr_plan, input) = if let LogicalPlan::Aggregate(aggr) = input.as_ref() {
419 if have_range_in_exprs(&aggr.aggr_expr) {
421 return RangeQuerySnafu {
422 msg: "Nest Range Query is not allowed",
423 }
424 .fail();
425 }
426 (aggr, aggr.input.clone())
427 } else {
428 return RangeQuerySnafu {
429 msg: "Window functions is not allowed in Range Query",
430 }
431 .fail();
432 };
433 let query_ctx = self.query_ctx.clone();
434 let mut range_rewriter = RangeExprRewriter {
435 input_plan: &input,
436 align: Duration::default(),
437 align_to: 0,
438 by: vec![],
439 range_fn: BTreeSet::new(),
440 sub_aggr: aggr_plan,
441 query_ctx: &query_ctx,
442 };
443 let new_expr = expr
444 .iter()
445 .map(|expr| expr.clone().rewrite(&mut range_rewriter).map(|x| x.data))
446 .collect::<DFResult<Vec<_>>>()?;
447 let need_default_by = range_rewriter.by.is_empty();
448 let (time_index, default_by) =
449 self.get_index_by(input.schema(), need_default_by).await?;
450 if need_default_by {
451 range_rewriter.by = default_by;
452 }
453 let range_exprs = range_rewriter.range_fn.into_iter().collect::<Vec<_>>();
454 let input = Arc::new(build_range_input_projection(
455 input.as_ref(),
456 &range_exprs,
457 &time_index,
458 &range_rewriter.by,
459 )?);
460 let range_select = RangeSelect::try_new(
461 input,
462 range_exprs,
463 range_rewriter.align,
464 range_rewriter.align_to,
465 time_index,
466 range_rewriter.by,
467 &new_expr,
468 )?;
469 let no_additional_project = range_select.schema_project.is_some();
470 let range_plan = LogicalPlan::Extension(Extension {
471 node: Arc::new(range_select),
472 });
473 if no_additional_project {
474 Ok(Some(range_plan))
475 } else {
476 let project_plan = LogicalPlanBuilder::from(range_plan)
477 .project(new_expr)
478 .and_then(|x| x.build())?;
479 Ok(Some(project_plan))
480 }
481 }
482 _ => {
483 if new_inputs.iter().any(|x| x.is_some()) {
484 let inputs: Vec<LogicalPlan> = new_inputs
485 .into_iter()
486 .zip(inputs)
487 .map(|(x, y)| match x {
488 Some(plan) => plan,
489 None => y.clone(),
490 })
491 .collect();
492 let plan = match plan {
496 LogicalPlan::Analyze(Analyze { verbose, .. }) => {
497 ensure!(
498 inputs.len() == 1,
499 RangeQuerySnafu {
500 msg: "Illegal subplan nums when rewrite Analyze logical plan",
501 }
502 );
503 LogicalPlanBuilder::from(inputs[0].clone())
504 .explain(*verbose, true)?
505 .build()
506 }
507 LogicalPlan::Explain(Explain { verbose, .. }) => {
508 ensure!(
509 inputs.len() == 1,
510 RangeQuerySnafu {
511 msg: "Illegal subplan nums when rewrite Explain logical plan",
512 }
513 );
514 LogicalPlanBuilder::from(inputs[0].clone())
515 .explain(*verbose, false)?
516 .build()
517 }
518 LogicalPlan::Distinct(Distinct::On(DistinctOn {
519 on_expr,
520 select_expr,
521 sort_expr,
522 ..
523 })) => {
524 ensure!(
525 inputs.len() == 1,
526 RangeQuerySnafu {
527 msg: "Illegal subplan nums when rewrite DistinctOn logical plan",
528 }
529 );
530 LogicalPlanBuilder::from(inputs[0].clone())
531 .distinct_on(
532 on_expr.clone(),
533 select_expr.clone(),
534 sort_expr.clone(),
535 )?
536 .build()
537 }
538 _ => plan.with_new_exprs(plan.expressions_consider_join(), inputs),
539 }?;
540 Ok(Some(plan))
541 } else {
542 Ok(None)
543 }
544 }
545 }
546 }
547
548 async fn get_index_by(
558 &mut self,
559 schema: &Arc<DFSchema>,
560 need_default_by: bool,
561 ) -> Result<(Expr, Vec<Expr>)> {
562 #[allow(deprecated)]
563 let mut time_index_expr = Expr::Wildcard {
564 qualifier: None,
565 options: Box::new(WildcardOptions::default()),
566 };
567 let mut default_by = vec![];
568 let metadata_time_index_expr = (0..schema.fields().len()).find_map(|i| {
569 let (qualifier, field) = schema.qualified_field(i);
570 if field.metadata().contains_key(TIME_INDEX_KEY)
571 && matches!(field.data_type(), DataType::Timestamp(_, _))
572 {
573 Some(Expr::Column(Column::new(
574 qualifier.cloned(),
575 field.name().clone(),
576 )))
577 } else {
578 None
579 }
580 });
581 for i in 0..schema.fields().len() {
582 let (qualifier, _) = schema.qualified_field(i);
583 if let Some(table_ref) = qualifier {
584 let table_source = match self.table_provider.resolve_table(table_ref.clone()).await
585 {
586 Ok(table_source) => table_source,
587 Err(error) => {
588 if matches!(&error, catalog::error::Error::TableNotExist { .. })
592 && metadata_time_index_expr.is_some()
593 {
594 continue;
595 }
596 return Err(error).context(CatalogSnafu);
597 }
598 };
599 let table = table_source
600 .as_any()
601 .downcast_ref::<DefaultTableSource>()
602 .context(UnknownTableSnafu)?
603 .table_provider
604 .as_any()
605 .downcast_ref::<DfTableProviderAdapter>()
606 .context(UnknownTableSnafu)?
607 .table();
608 let schema = table.schema();
609 let time_index_column =
610 schema
611 .timestamp_column()
612 .with_context(|| TimeIndexNotFoundSnafu {
613 table: table_ref.to_string(),
614 })?;
615 if let ConcreteDataType::Timestamp(_) = time_index_column.data_type {
617 default_by = table
618 .table_info()
619 .meta
620 .row_key_column_names()
621 .map(|key| Expr::Column(Column::new(Some(table_ref.clone()), key)))
622 .collect();
623 if default_by.is_empty() {
627 default_by = vec![1.lit()];
628 }
629 time_index_expr = Expr::Column(Column::new(
630 Some(table_ref.clone()),
631 time_index_column.name.clone(),
632 ));
633 }
634 }
635 }
636 #[allow(deprecated)]
637 if matches!(time_index_expr, Expr::Wildcard { .. })
638 && let Some(expr) = metadata_time_index_expr
639 {
640 common_telemetry::debug!(
641 "Range query falling back to time-index metadata for derived input schema: {}",
642 schema
643 );
644 ensure!(
645 !need_default_by,
646 RangeQuerySnafu {
647 msg: "Cannot infer default BY columns from derived range query input"
648 }
649 );
650 time_index_expr = expr;
651 }
652 #[allow(deprecated)]
653 if matches!(time_index_expr, Expr::Wildcard { .. }) {
654 TimeIndexNotFoundSnafu {
655 table: schema.to_string(),
656 }
657 .fail()
658 } else {
659 Ok((time_index_expr, default_by))
660 }
661 }
662}
663
664fn build_range_input_projection(
669 input: &LogicalPlan,
670 range_exprs: &[RangeFn],
671 time_expr: &Expr,
672 by_exprs: &[Expr],
673) -> DFResult<LogicalPlan> {
674 let mut required_columns = HashSet::new();
675 for range_expr in range_exprs {
676 let range_expr = match &range_expr.expr {
677 Expr::Alias(alias) => alias.expr.as_ref(),
678 expr => expr,
679 };
680 let Some(aggr) = get_aggr_func(range_expr) else {
681 return Err(DataFusionError::Plan(format!(
682 "Unexpected Expr: {} in RangeSelect",
683 range_expr
684 )));
685 };
686 if aggr.params.filter.is_some() {
687 return Err(DataFusionError::NotImplemented(
688 "Range aggregate FILTER is unsupported".to_string(),
689 ));
690 }
691 for expr in &aggr.params.args {
692 expr_to_columns(expr, &mut required_columns)?;
693 }
694 for sort_expr in &aggr.params.order_by {
695 expr_to_columns(&sort_expr.expr, &mut required_columns)?;
696 }
697 }
698 expr_to_columns(time_expr, &mut required_columns)?;
699 for by_expr in by_exprs {
700 expr_to_columns(by_expr, &mut required_columns)?;
701 }
702
703 let required_indices = required_columns
704 .iter()
705 .map(|column| input.schema().index_of_column(column))
706 .collect::<DFResult<BTreeSet<_>>>()?;
707 let projection = required_indices
708 .into_iter()
709 .map(|index| {
710 let (qualifier, field) = input.schema().qualified_field(index);
711 Expr::Column(Column::new(qualifier.cloned(), field.name()))
712 })
713 .collect::<Vec<_>>();
714 LogicalPlanBuilder::from(input.clone())
715 .project(projection)?
716 .build()
717}
718
719fn have_range_in_exprs(exprs: &[Expr]) -> bool {
720 exprs.iter().any(|expr| {
721 let mut find_range = false;
722 let _ = expr.apply(|expr| {
723 Ok(match expr {
724 Expr::ScalarFunction(func) if func.name() == "range_fn" => {
725 find_range = true;
726 TreeNodeRecursion::Stop
727 }
728 _ => TreeNodeRecursion::Continue,
729 })
730 });
731 find_range
732 })
733}
734
735fn interval_only_in_expr(expr: &Expr) -> bool {
736 let mut all_interval = true;
737 let _ = expr.apply(|expr| {
738 if matches!(
740 expr,
741 Expr::Cast(Cast{
742 expr,
743 data_type: DataType::Interval(_)
744 }) if matches!(&**expr, Expr::Literal(ScalarValue::Utf8(_), _))
745 ) {
746 return Ok(TreeNodeRecursion::Stop);
749 }
750
751 if !matches!(
752 expr,
753 Expr::Literal(ScalarValue::IntervalDayTime(_), _)
754 | Expr::Literal(ScalarValue::IntervalMonthDayNano(_), _)
755 | Expr::Literal(ScalarValue::IntervalYearMonth(_), _)
756 | Expr::BinaryExpr(_)
757 | Expr::Cast(Cast {
758 data_type: DataType::Interval(_),
759 ..
760 })
761 ) {
762 all_interval = false;
763 Ok(TreeNodeRecursion::Stop)
764 } else {
765 Ok(TreeNodeRecursion::Continue)
766 }
767 });
768
769 all_interval
770}
771
772#[cfg(test)]
773mod test {
774
775 use arrow::datatypes::{IntervalUnit, TimeUnit};
776 use catalog::RegisterTableRequest;
777 use catalog::memory::MemoryCatalogManager;
778 use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
779 use common_time::IntervalYearMonth;
780 use datafusion_expr::{BinaryExpr, Literal, Operator, UserDefinedLogicalNodeCore};
781 use datatypes::prelude::ConcreteDataType;
782 use datatypes::schema::{ColumnSchema, Schema};
783 use session::context::{QueryContext, QueryContextBuilder};
784 use table::metadata::{TableInfoBuilder, TableMetaBuilder};
785 use table::table::TableRef;
786 use table::test_util::EmptyTable;
787
788 use super::*;
789 use crate::options::QueryOptions;
790 use crate::parser::QueryLanguageParser;
791 use crate::{QueryEngineFactory, QueryEngineRef};
792
793 async fn create_test_engine() -> QueryEngineRef {
794 create_test_engine_with_tables(&["test"], false).await
795 }
796
797 async fn create_union_test_engine() -> QueryEngineRef {
798 create_test_engine_with_tables(&["test_0", "test_1"], true).await
799 }
800
801 async fn create_test_engine_with_tables(
802 table_names: &[&str],
803 with_extra_timestamp: bool,
804 ) -> QueryEngineRef {
805 let catalog_list = MemoryCatalogManager::with_default_setup();
806 for (i, table_name) in table_names.iter().enumerate() {
807 let table = create_test_table(table_name, with_extra_timestamp);
808 assert!(
809 catalog_list
810 .register_table_sync(RegisterTableRequest {
811 catalog: DEFAULT_CATALOG_NAME.to_string(),
812 schema: DEFAULT_SCHEMA_NAME.to_string(),
813 table_name: (*table_name).to_string(),
814 table_id: 1024 + i as u32,
815 table,
816 })
817 .is_ok()
818 );
819 }
820 QueryEngineFactory::new(
821 catalog_list,
822 None,
823 None,
824 None,
825 None,
826 false,
827 QueryOptions::default(),
828 )
829 .query_engine()
830 }
831
832 fn create_test_table(table_name: &str, with_extra_timestamp: bool) -> TableRef {
833 let mut columns = vec![];
834 for i in 0..5 {
835 columns.push(ColumnSchema::new(
836 format!("tag_{i}"),
837 ConcreteDataType::string_datatype(),
838 false,
839 ));
840 }
841 columns.push(
842 ColumnSchema::new(
843 "timestamp".to_string(),
844 ConcreteDataType::timestamp_millisecond_datatype(),
845 false,
846 )
847 .with_time_index(true),
848 );
849 if with_extra_timestamp {
850 columns.push(ColumnSchema::new(
851 "timestamp_2".to_string(),
852 ConcreteDataType::timestamp_millisecond_datatype(),
853 true,
854 ));
855 }
856 for i in 0..5 {
857 columns.push(ColumnSchema::new(
858 format!("field_{i}"),
859 ConcreteDataType::float64_datatype(),
860 true,
861 ));
862 }
863 let schema = Arc::new(Schema::new(columns));
864 let table_meta = TableMetaBuilder::empty()
865 .schema(schema)
866 .primary_key_indices((0..5).collect())
867 .value_indices(if with_extra_timestamp {
868 (6..12).collect()
869 } else {
870 (6..11).collect()
871 })
872 .next_column_id(1024)
873 .build()
874 .unwrap();
875 let table_info = TableInfoBuilder::default()
876 .name(table_name)
877 .meta(table_meta)
878 .build()
879 .unwrap();
880 EmptyTable::from_table_info(&table_info)
881 }
882
883 async fn do_query(sql: &str) -> Result<LogicalPlan> {
884 let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
885 let engine = create_test_engine().await;
886 engine.planner().plan(&stmt, QueryContext::arc()).await
887 }
888
889 async fn do_query_with_ctx(sql: &str, query_ctx: QueryContextRef) -> Result<LogicalPlan> {
890 let stmt = QueryLanguageParser::parse_sql(sql, &query_ctx).unwrap();
891 let engine = create_test_engine().await;
892 engine.planner().plan(&stmt, query_ctx).await
893 }
894
895 async fn do_union_query(sql: &str) -> Result<LogicalPlan> {
896 let stmt = QueryLanguageParser::parse_sql(sql, &QueryContext::arc()).unwrap();
897 let engine = create_union_test_engine().await;
898 engine.planner().plan(&stmt, QueryContext::arc()).await
899 }
900
901 async fn query_plan_compare(sql: &str, expected: String) {
902 let plan = do_query(sql).await.unwrap();
903 assert_eq!(plan.display_indent_schema().to_string(), expected);
904 }
905
906 #[tokio::test]
907 async fn range_align_to_now_uses_scheduled_time_extension() {
908 let query_ctx = Arc::new(
909 QueryContextBuilder::default()
910 .set_extension(
911 crate::options::FLOW_SCHEDULED_TIME_MILLIS.to_string(),
912 "1700000000123".to_string(),
913 )
914 .build(),
915 );
916 let query = r#"SELECT timestamp, tag_0, tag_1, avg(field_0) RANGE '5m' FROM test ALIGN '1h' TO NOW by (tag_0,tag_1);"#;
917 let plan = do_query_with_ctx(query, query_ctx).await.unwrap();
918
919 assert!(
920 plan.display_indent_schema()
921 .to_string()
922 .contains("align_to=1700000000123ms")
923 );
924 }
925
926 #[tokio::test]
927 async fn range_no_project() {
928 let query = r#"SELECT timestamp, tag_0, tag_1, avg(field_0 + field_1) RANGE '5m' FROM test ALIGN '1h' by (tag_0,tag_1);"#;
929 let expected = String::from(
930 "RangeSelect: range_exprs=[avg(test.field_0 + test.field_1) RANGE 5m], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1], time_index=timestamp [timestamp:Timestamp(ms), tag_0:Utf8, tag_1:Utf8, avg(test.field_0 + test.field_1) RANGE 5m:Float64;N]\
931 \n Projection: test.tag_0, test.tag_1, test.timestamp, test.field_0, test.field_1 [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
932 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
933 );
934 query_plan_compare(query, expected).await;
935 }
936
937 #[tokio::test]
938 async fn range_select_rewrite_projects_required_input_columns() {
939 let query = r#"SELECT timestamp, tag_0, tag_1, avg(field_0 + field_1) RANGE '5m' FROM test ALIGN '1h' by (tag_0,tag_1);"#;
940 let plan = do_query(query).await.unwrap();
941 let LogicalPlan::Extension(extension) = plan else {
942 panic!("expected RangeSelect rewrite output, got: {plan}");
943 };
944 let range_select = extension
945 .node
946 .as_any()
947 .downcast_ref::<RangeSelect>()
948 .expect("expected RangeSelect extension");
949
950 let LogicalPlan::Projection(projection) = range_select.input.as_ref() else {
951 panic!(
952 "expected a narrow Projection below RangeSelect, got: {}",
953 range_select.input
954 );
955 };
956 assert_eq!(
957 projection
958 .schema
959 .fields()
960 .iter()
961 .map(|field| field.name().as_str())
962 .collect::<Vec<_>>(),
963 vec!["tag_0", "tag_1", "timestamp", "field_0", "field_1",]
964 );
965 assert_eq!(
966 range_select
967 .schema
968 .fields()
969 .iter()
970 .map(|field| (
971 field.name().as_str(),
972 field.data_type().clone(),
973 field.is_nullable()
974 ))
975 .collect::<Vec<_>>(),
976 vec![
977 (
978 "timestamp",
979 DataType::Timestamp(TimeUnit::Millisecond, None),
980 false
981 ),
982 ("tag_0", DataType::Utf8, false),
983 ("tag_1", DataType::Utf8, false),
984 (
985 "avg(test.field_0 + test.field_1) RANGE 5m",
986 DataType::Float64,
987 true,
988 ),
989 ]
990 );
991 assert_eq!(
992 range_select.schema.qualified_field(0).0,
993 projection.schema.qualified_field(2).0
994 );
995 assert_eq!(
996 range_select.schema.qualified_field(1).0,
997 projection.schema.qualified_field(0).0
998 );
999 }
1000
1001 async fn assert_range_select_input_columns(sql: &str, expected: &[&str]) {
1002 let plan = do_query(sql).await.unwrap();
1003 let LogicalPlan::Extension(extension) = plan else {
1004 panic!("expected RangeSelect rewrite output, got: {plan}");
1005 };
1006 let range_select = extension
1007 .node
1008 .as_any()
1009 .downcast_ref::<RangeSelect>()
1010 .expect("expected RangeSelect extension");
1011 let LogicalPlan::Projection(projection) = range_select.input.as_ref() else {
1012 panic!("expected narrow Projection, got: {}", range_select.input);
1013 };
1014 assert_eq!(
1015 projection
1016 .schema
1017 .fields()
1018 .iter()
1019 .map(|field| field.name().as_str())
1020 .collect::<Vec<_>>(),
1021 expected
1022 );
1023 }
1024
1025 #[tokio::test]
1026 async fn range_select_input_projection_collects_range_dependencies() {
1027 assert_range_select_input_columns(
1028 r#"SELECT timestamp, tag_0, avg(field_0 + field_1) RANGE '5m', sum(field_2) RANGE '5m' FROM test ALIGN '1h' BY (tag_0);"#,
1029 &["tag_0", "timestamp", "field_0", "field_1", "field_2"],
1030 )
1031 .await;
1032 assert_range_select_input_columns(
1033 r#"SELECT timestamp, tag_0, last_value(field_0 ORDER BY field_2) RANGE '5m' FROM test ALIGN '1h' BY (tag_0);"#,
1034 &["tag_0", "timestamp", "field_0", "field_2"],
1035 )
1036 .await;
1037 assert_range_select_input_columns(
1038 r#"SELECT timestamp, count(*) RANGE '5m' FILL NULL FROM test ALIGN '1h';"#,
1039 &["tag_0", "tag_1", "tag_2", "tag_3", "tag_4", "timestamp"],
1040 )
1041 .await;
1042 assert_range_select_input_columns(
1043 r#"SELECT timestamp, count(1) RANGE '5m' FILL PREV FROM test ALIGN '1h' BY (tag_0);"#,
1044 &["tag_0", "timestamp"],
1045 )
1046 .await;
1047 }
1048
1049 #[tokio::test]
1050 async fn range_select_input_projection_collects_nested_alias_dependencies() {
1051 let plan = do_query(
1052 r#"SELECT timestamp, tag_0, avg(field_0 + field_1) RANGE '5m' FROM test ALIGN '1h' BY (tag_0);"#,
1053 )
1054 .await
1055 .unwrap();
1056 let LogicalPlan::Extension(extension) = plan else {
1057 panic!("expected RangeSelect rewrite output, got: {plan}");
1058 };
1059 let range_select = extension
1060 .node
1061 .as_any()
1062 .downcast_ref::<RangeSelect>()
1063 .expect("expected RangeSelect extension");
1064 let mut range_exprs = range_select.range_expr.clone();
1065 range_exprs[0].expr = range_exprs[0]
1066 .expr
1067 .clone()
1068 .alias("inner_alias")
1069 .alias("outer_alias");
1070
1071 let input = build_range_input_projection(
1072 range_select.input.as_ref(),
1073 &range_exprs,
1074 &range_select.time_expr,
1075 &range_select.by,
1076 )
1077 .unwrap();
1078 let LogicalPlan::Projection(projection) = input else {
1079 panic!("expected narrow Projection, got: {input}");
1080 };
1081 assert_eq!(
1082 projection
1083 .schema
1084 .fields()
1085 .iter()
1086 .map(|field| field.name().as_str())
1087 .collect::<Vec<_>>(),
1088 ["tag_0", "timestamp", "field_0", "field_1"],
1089 );
1090 }
1091
1092 #[tokio::test]
1093 async fn range_select_physical_plan_accepts_nested_aggregate_aliases() {
1094 let query_ctx = QueryContext::arc();
1095 let stmt = QueryLanguageParser::parse_sql(
1096 r#"SELECT timestamp, tag_0, avg(field_0) RANGE '5m' FROM test ALIGN '1h' BY (tag_0);"#,
1097 &query_ctx,
1098 )
1099 .unwrap();
1100 let engine = create_test_engine().await;
1101 let plan = engine
1102 .planner()
1103 .plan(&stmt, query_ctx.clone())
1104 .await
1105 .unwrap();
1106 let LogicalPlan::Extension(extension) = plan else {
1107 panic!("expected RangeSelect rewrite output, got: {plan}");
1108 };
1109 let range_select = extension
1110 .node
1111 .as_any()
1112 .downcast_ref::<RangeSelect>()
1113 .expect("expected RangeSelect extension");
1114 let mut exprs = range_select.expressions();
1115 exprs[0] = exprs[0].clone().alias("inner_alias").alias("outer_alias");
1116 let plan = LogicalPlan::Extension(Extension {
1117 node: Arc::new(
1118 range_select
1119 .with_exprs_and_inputs(exprs, vec![range_select.input.as_ref().clone()])
1120 .unwrap(),
1121 ),
1122 });
1123
1124 engine.execute(plan, query_ctx).await.unwrap();
1125 }
1126
1127 #[tokio::test]
1128 async fn range_select_input_projection_resolves_derived_aliases() {
1129 let plan = do_query(
1130 r#"SELECT d.ts, d.group_tag, avg(d.value) RANGE '5m' FROM (SELECT timestamp AS ts, tag_0 AS group_tag, field_0 AS value, field_4 AS ignored FROM test) AS d ALIGN '1h' BY (d.group_tag);"#,
1131 )
1132 .await
1133 .unwrap();
1134 let LogicalPlan::Extension(extension) = plan else {
1135 panic!("expected RangeSelect rewrite output, got: {plan}");
1136 };
1137 let range_select = extension
1138 .node
1139 .as_any()
1140 .downcast_ref::<RangeSelect>()
1141 .expect("expected RangeSelect extension");
1142 let LogicalPlan::Projection(projection) = range_select.input.as_ref() else {
1143 panic!("expected narrow Projection, got: {}", range_select.input);
1144 };
1145 assert_eq!(
1146 projection
1147 .schema
1148 .fields()
1149 .iter()
1150 .map(|field| field.name().as_str())
1151 .collect::<Vec<_>>(),
1152 ["ts", "group_tag", "value"],
1153 );
1154 assert!(
1155 projection
1156 .schema
1157 .fields()
1158 .iter()
1159 .enumerate()
1160 .all(|(index, _)| projection
1161 .schema
1162 .qualified_field(index)
1163 .0
1164 .unwrap()
1165 .to_string()
1166 == "d")
1167 );
1168 }
1169
1170 #[tokio::test]
1171 async fn range_select_rejects_aggregate_filter() {
1172 let error = do_query(
1173 r#"SELECT timestamp, tag_0, avg(field_0) FILTER (WHERE field_1 > 0) RANGE '5m' FROM test ALIGN '1h' BY (tag_0);"#,
1174 )
1175 .await
1176 .unwrap_err()
1177 .to_string();
1178 assert_eq!(
1179 error,
1180 "This feature is not implemented: Range aggregate FILTER is unsupported"
1181 );
1182 }
1183
1184 #[tokio::test]
1185 async fn range_expr_calculation() {
1186 let query = r#"SELECT (avg(field_0 + field_1)/4) RANGE '5m' FROM test ALIGN '1h' by (tag_0,tag_1);"#;
1187 let expected = String::from(
1188 "Projection: avg(test.field_0 + test.field_1) RANGE 5m / Int64(4) [avg(test.field_0 + test.field_1) RANGE 5m / Int64(4):Float64;N]\
1189 \n RangeSelect: range_exprs=[avg(test.field_0 + test.field_1) RANGE 5m], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1], time_index=timestamp [avg(test.field_0 + test.field_1) RANGE 5m:Float64;N, timestamp:Timestamp(ms), tag_0:Utf8, tag_1:Utf8]\
1190 \n Projection: test.tag_0, test.tag_1, test.timestamp, test.field_0, test.field_1 [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
1191 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1192 );
1193 query_plan_compare(query, expected).await;
1194 }
1195
1196 #[tokio::test]
1197 async fn range_multi_args() {
1198 let query =
1199 r#"SELECT (covar(field_0 + field_1, field_1)/4) RANGE '5m' FROM test ALIGN '1h';"#;
1200 let expected = String::from(
1201 "Projection: covar_samp(test.field_0 + test.field_1,test.field_1) RANGE 5m / Int64(4) [covar_samp(test.field_0 + test.field_1,test.field_1) RANGE 5m / Int64(4):Float64;N]\
1202 \n RangeSelect: range_exprs=[covar_samp(test.field_0 + test.field_1,test.field_1) RANGE 5m], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1, test.tag_2, test.tag_3, test.tag_4], time_index=timestamp [covar_samp(test.field_0 + test.field_1,test.field_1) RANGE 5m:Float64;N, timestamp:Timestamp(ms), tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8]\
1203 \n Projection: test.tag_0, test.tag_1, test.tag_2, test.tag_3, test.tag_4, test.timestamp, test.field_0, test.field_1 [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
1204 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1205 );
1206 query_plan_compare(query, expected).await;
1207 }
1208
1209 #[tokio::test]
1210 async fn range_calculation() {
1211 let query = r#"SELECT ((avg(field_0)+sum(field_1))/4) RANGE '5m' FROM test ALIGN '1h' by (tag_0,tag_1) FILL NULL;"#;
1212 let expected = String::from(
1213 "Projection: (avg(test.field_0) RANGE 5m FILL NULL + sum(test.field_1) RANGE 5m FILL NULL) / Int64(4) [avg(test.field_0) RANGE 5m FILL NULL + sum(test.field_1) RANGE 5m FILL NULL / Int64(4):Float64;N]\
1214 \n RangeSelect: range_exprs=[avg(test.field_0) RANGE 5m FILL NULL, sum(test.field_1) RANGE 5m FILL NULL], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1], time_index=timestamp [avg(test.field_0) RANGE 5m FILL NULL:Float64;N, sum(test.field_1) RANGE 5m FILL NULL:Float64;N, timestamp:Timestamp(ms), tag_0:Utf8, tag_1:Utf8]\
1215 \n Projection: test.tag_0, test.tag_1, test.timestamp, test.field_0, test.field_1 [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
1216 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1217 );
1218 query_plan_compare(query, expected).await;
1219 }
1220
1221 #[tokio::test]
1222 async fn range_as_sub_query() {
1223 let query = r#"SELECT foo + 1 from (SELECT ((avg(field_0)+sum(field_1))/4) RANGE '5m' as foo FROM test ALIGN '1h' by (tag_0,tag_1) FILL NULL) where foo > 1;"#;
1224 let expected = String::from(
1225 "Projection: foo + Int64(1) [foo + Int64(1):Float64;N]\
1226 \n Filter: foo > Int64(1) [foo:Float64;N]\
1227 \n Projection: (avg(test.field_0) RANGE 5m FILL NULL + sum(test.field_1) RANGE 5m FILL NULL) / Int64(4) AS foo [foo:Float64;N]\
1228 \n RangeSelect: range_exprs=[avg(test.field_0) RANGE 5m FILL NULL, sum(test.field_1) RANGE 5m FILL NULL], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1], time_index=timestamp [avg(test.field_0) RANGE 5m FILL NULL:Float64;N, sum(test.field_1) RANGE 5m FILL NULL:Float64;N, timestamp:Timestamp(ms), tag_0:Utf8, tag_1:Utf8]\
1229 \n Projection: test.tag_0, test.tag_1, test.timestamp, test.field_0, test.field_1 [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
1230 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1231 );
1232 query_plan_compare(query, expected).await;
1233 }
1234
1235 #[tokio::test]
1236 async fn range_from_nest_query() {
1237 let query = r#"SELECT ((avg(a)+sum(b))/4) RANGE '5m' FROM (SELECT field_0 as a, field_1 as b, tag_0 as c, tag_1 as d, timestamp from test where field_0 > 1.0) ALIGN '1h' by (c, d) FILL NULL;"#;
1238 let expected = String::from(
1239 "Projection: (avg(a) RANGE 5m FILL NULL + sum(b) RANGE 5m FILL NULL) / Int64(4) [avg(a) RANGE 5m FILL NULL + sum(b) RANGE 5m FILL NULL / Int64(4):Float64;N]\
1240 \n RangeSelect: range_exprs=[avg(a) RANGE 5m FILL NULL, sum(b) RANGE 5m FILL NULL], align=3600000ms, align_to=0ms, align_by=[c, d], time_index=timestamp [avg(a) RANGE 5m FILL NULL:Float64;N, sum(b) RANGE 5m FILL NULL:Float64;N, timestamp:Timestamp(ms), c:Utf8, d:Utf8]\
1241 \n Projection: a, b, c, d, test.timestamp [a:Float64;N, b:Float64;N, c:Utf8, d:Utf8, timestamp:Timestamp(ms)]\
1242 \n Projection: test.field_0 AS a, test.field_1 AS b, test.tag_0 AS c, test.tag_1 AS d, test.timestamp [a:Float64;N, b:Float64;N, c:Utf8, d:Utf8, timestamp:Timestamp(ms)]\
1243 \n Filter: test.field_0 > Float64(1) [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]\
1244 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1245 );
1246 query_plan_compare(query, expected).await;
1247 }
1248
1249 #[tokio::test]
1250 async fn range_from_union_query() {
1251 let queries = [
1252 r#"SELECT timestamp, tag_0, avg(field_0) RANGE '5m'
1253 FROM (
1254 SELECT timestamp, tag_0, field_0, timestamp_2 FROM test_0
1255 UNION ALL
1256 SELECT timestamp, tag_0, field_0, timestamp_2 FROM test_1
1257 )
1258 WHERE timestamp >= '1970-01-01 00:00:00'
1259 ALIGN '1h' by (tag_0)"#,
1260 r#"SELECT tmp.timestamp, tmp.tag_0, avg(tmp.field_0) RANGE '5m'
1261 FROM (
1262 SELECT timestamp, tag_0, field_0, timestamp_2 FROM test_0
1263 UNION ALL
1264 SELECT timestamp, tag_0, field_0, timestamp_2 FROM test_1
1265 ) AS tmp
1266 WHERE tmp.timestamp >= '1970-01-01 00:00:00'
1267 ALIGN '1h' by (tmp.tag_0)"#,
1268 ];
1269
1270 for query in queries {
1271 let plan = do_union_query(query)
1272 .await
1273 .unwrap()
1274 .display_indent_schema()
1275 .to_string();
1276
1277 assert!(plan.contains("RangeSelect"));
1278 assert!(plan.contains("Union"));
1279 assert!(plan.contains("time_index=timestamp"));
1280 }
1281 }
1282
1283 #[tokio::test]
1284 async fn range_from_derived_query_without_by_err() {
1285 let queries = [
1286 r#"SELECT timestamp, tag_0, avg(field_0) RANGE '5m'
1287 FROM (
1288 SELECT timestamp, tag_0, field_0, timestamp_2 FROM test_0
1289 UNION ALL
1290 SELECT timestamp, tag_0, field_0, timestamp_2 FROM test_1
1291 )
1292 WHERE timestamp >= '1970-01-01 00:00:00'
1293 ALIGN '1h'"#,
1294 r#"SELECT tmp.timestamp, tmp.tag_0, avg(tmp.field_0) RANGE '5m'
1295 FROM (
1296 SELECT timestamp, tag_0, field_0, timestamp_2 FROM test_0
1297 UNION ALL
1298 SELECT timestamp, tag_0, field_0, timestamp_2 FROM test_1
1299 ) AS tmp
1300 WHERE tmp.timestamp >= '1970-01-01 00:00:00'
1301 ALIGN '1h'"#,
1302 ];
1303
1304 for query in queries {
1305 assert_eq!(
1306 do_union_query(query).await.unwrap_err().to_string(),
1307 "Range Query: Cannot infer default BY columns from derived range query input"
1308 );
1309 }
1310 }
1311
1312 #[tokio::test]
1313 async fn range_in_expr() {
1314 let query = r#"SELECT sin(avg(field_0 + field_1) RANGE '5m' + 1) FROM test ALIGN '1h' by (tag_0,tag_1);"#;
1315 let expected = String::from(
1316 "Projection: sin(avg(test.field_0 + test.field_1) RANGE 5m + Int64(1)) [sin(avg(test.field_0 + test.field_1) RANGE 5m + Int64(1)):Float64;N]\
1317 \n RangeSelect: range_exprs=[avg(test.field_0 + test.field_1) RANGE 5m], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1], time_index=timestamp [avg(test.field_0 + test.field_1) RANGE 5m:Float64;N, timestamp:Timestamp(ms), tag_0:Utf8, tag_1:Utf8]\
1318 \n Projection: test.tag_0, test.tag_1, test.timestamp, test.field_0, test.field_1 [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
1319 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1320 );
1321 query_plan_compare(query, expected).await;
1322 }
1323
1324 #[tokio::test]
1325 async fn duplicate_range_expr() {
1326 let query = r#"SELECT avg(field_0) RANGE '5m' FILL 6.0 + avg(field_0) RANGE '5m' FILL 6.0 FROM test ALIGN '1h' by (tag_0,tag_1);"#;
1327 let expected = String::from(
1328 "Projection: avg(test.field_0) RANGE 5m FILL 6 + avg(test.field_0) RANGE 5m FILL 6 [avg(test.field_0) RANGE 5m FILL 6 + avg(test.field_0) RANGE 5m FILL 6:Float64]\
1329 \n RangeSelect: range_exprs=[avg(test.field_0) RANGE 5m FILL 6], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1], time_index=timestamp [avg(test.field_0) RANGE 5m FILL 6:Float64, timestamp:Timestamp(ms), tag_0:Utf8, tag_1:Utf8]\
1330 \n Projection: test.tag_0, test.tag_1, test.timestamp, test.field_0 [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
1331 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1332 );
1333 query_plan_compare(query, expected).await;
1334 }
1335
1336 #[tokio::test]
1337 async fn deep_nest_range_expr() {
1338 let query = r#"SELECT round(sin(avg(field_0 + field_1) RANGE '5m' + 1)) FROM test ALIGN '1h' by (tag_0,tag_1);"#;
1339 let expected = String::from(
1340 "Projection: round(sin(avg(test.field_0 + test.field_1) RANGE 5m + Int64(1))) [round(sin(avg(test.field_0 + test.field_1) RANGE 5m + Int64(1))):Float64;N]\
1341 \n RangeSelect: range_exprs=[avg(test.field_0 + test.field_1) RANGE 5m], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1], time_index=timestamp [avg(test.field_0 + test.field_1) RANGE 5m:Float64;N, timestamp:Timestamp(ms), tag_0:Utf8, tag_1:Utf8]\
1342 \n Projection: test.tag_0, test.tag_1, test.timestamp, test.field_0, test.field_1 [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
1343 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1344 );
1345 query_plan_compare(query, expected).await;
1346 }
1347
1348 #[tokio::test]
1349 async fn complex_range_expr() {
1350 let query = r#"SELECT gcd(CAST(max(field_0 + 1) Range '5m' FILL NULL AS Int64), CAST(tag_0 AS Int64)) + round(max(field_2+1) Range '6m' FILL NULL + 1) + max(field_2+3) Range '10m' FILL NULL * CAST(tag_1 AS Float64) + 1 FROM test ALIGN '1h' by (tag_0, tag_1);"#;
1351 let expected = String::from(
1352 "Projection: gcd(arrow_cast(max(test.field_0 + Int64(1)) RANGE 5m FILL NULL, Utf8(\"Int64\")), arrow_cast(test.tag_0, Utf8(\"Int64\"))) + round(max(test.field_2 + Int64(1)) RANGE 6m FILL NULL + Int64(1)) + max(test.field_2 + Int64(3)) RANGE 10m FILL NULL * arrow_cast(test.tag_1, Utf8(\"Float64\")) + Int64(1) [gcd(arrow_cast(max(test.field_0 + Int64(1)) RANGE 5m FILL NULL,Utf8(\"Int64\")),arrow_cast(test.tag_0,Utf8(\"Int64\"))) + round(max(test.field_2 + Int64(1)) RANGE 6m FILL NULL + Int64(1)) + max(test.field_2 + Int64(3)) RANGE 10m FILL NULL * arrow_cast(test.tag_1,Utf8(\"Float64\")) + Int64(1):Float64;N]\
1353 \n RangeSelect: range_exprs=[max(test.field_0 + Int64(1)) RANGE 5m FILL NULL, max(test.field_2 + Int64(1)) RANGE 6m FILL NULL, max(test.field_2 + Int64(3)) RANGE 10m FILL NULL], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1], time_index=timestamp [max(test.field_0 + Int64(1)) RANGE 5m FILL NULL:Float64;N, max(test.field_2 + Int64(1)) RANGE 6m FILL NULL:Float64;N, max(test.field_2 + Int64(3)) RANGE 10m FILL NULL:Float64;N, timestamp:Timestamp(ms), tag_0:Utf8, tag_1:Utf8]\
1354 \n Projection: test.tag_0, test.tag_1, test.timestamp, test.field_0, test.field_2 [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_2:Float64;N]\
1355 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1356 );
1357 query_plan_compare(query, expected).await;
1358 }
1359
1360 #[tokio::test]
1361 async fn range_linear_on_integer() {
1362 let query = r#"SELECT min(CAST(field_0 AS Int64) + CAST(field_1 AS Int64)) RANGE '5m' FILL LINEAR FROM test ALIGN '1h' by (tag_0,tag_1);"#;
1363 let expected = String::from(
1364 "RangeSelect: range_exprs=[min(arrow_cast(test.field_0,Utf8(\"Int64\")) + arrow_cast(test.field_1,Utf8(\"Int64\"))) RANGE 5m FILL LINEAR], align=3600000ms, align_to=0ms, align_by=[test.tag_0, test.tag_1], time_index=timestamp [min(arrow_cast(test.field_0,Utf8(\"Int64\")) + arrow_cast(test.field_1,Utf8(\"Int64\"))) RANGE 5m FILL LINEAR:Float64;N]\
1365 \n Projection: test.tag_0, test.tag_1, test.timestamp, test.field_0, test.field_1 [tag_0:Utf8, tag_1:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N]\
1366 \n TableScan: test [tag_0:Utf8, tag_1:Utf8, tag_2:Utf8, tag_3:Utf8, tag_4:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, field_1:Float64;N, field_2:Float64;N, field_3:Float64;N, field_4:Float64;N]",
1367 );
1368 query_plan_compare(query, expected).await;
1369 }
1370
1371 #[tokio::test]
1372 async fn range_nest_range_err() {
1373 let query = r#"SELECT sum(avg(field_0 + field_1) RANGE '5m' + 1) RANGE '5m' + 1 FROM test ALIGN '1h' by (tag_0,tag_1);"#;
1374 assert_eq!(
1375 do_query(query).await.unwrap_err().to_string(),
1376 "Range Query: Nest Range Query is not allowed"
1377 )
1378 }
1379
1380 #[tokio::test]
1381 async fn range_argument_err_1() {
1384 let query = r#"SELECT range_fn('5m', avg(field_0), 'NULL', '1', tag_0, '1h') FROM test group by tag_0;"#;
1385 let error = do_query(query).await.unwrap_err().to_string();
1386 assert_eq!(
1387 error,
1388 "Error during planning: Illegal argument `Utf8(\"5m\")` in range select query"
1389 )
1390 }
1391
1392 #[tokio::test]
1393 async fn range_argument_err_2() {
1394 let query = r#"SELECT range_fn(avg(field_0), 5, 'NULL', '1', tag_0, '1h') FROM test group by tag_0;"#;
1395 let error = do_query(query).await.unwrap_err().to_string();
1396 assert_eq!(
1397 error,
1398 "Error during planning: Illegal argument `Int64(5)` in range select query"
1399 )
1400 }
1401
1402 #[test]
1403 fn qbs_timestamp_submillisecond_uses_floor() {
1404 let assert_timestamp_millis = |timestamp: ScalarValue, expected: i64| {
1405 let args = vec![timestamp.lit()];
1406 assert_eq!(
1407 evaluate_expr_to_millisecond(&args, 0, false, None).unwrap(),
1408 expected
1409 );
1410 assert_eq!(parse_align_to(&args, 0, None, None).unwrap(), expected);
1411 };
1412
1413 assert_timestamp_millis(ScalarValue::TimestampNanosecond(Some(-1), None), -1);
1414 assert_timestamp_millis(ScalarValue::TimestampMicrosecond(Some(-1), None), -1);
1415 assert_timestamp_millis(ScalarValue::TimestampNanosecond(Some(999_999), None), 0);
1416 assert_timestamp_millis(ScalarValue::TimestampMicrosecond(Some(999), None), 0);
1417 assert_timestamp_millis(
1418 ScalarValue::TimestampNanosecond(Some(-NANOS_PER_MILLI), None),
1419 -1,
1420 );
1421 assert_timestamp_millis(ScalarValue::TimestampMicrosecond(Some(-1_000), None), -1);
1422 }
1423
1424 #[test]
1425 fn qbs_second_to_millisecond_safe_thresholds() {
1426 let max_safe_seconds = i64::MAX / 1_000;
1427 let min_safe_seconds = i64::MIN / 1_000;
1428
1429 for seconds in [min_safe_seconds, max_safe_seconds] {
1430 let expected = seconds * 1_000;
1431 let args = vec![ScalarValue::TimestampSecond(Some(seconds), None).lit()];
1432 assert_eq!(
1433 evaluate_expr_to_millisecond(&args, 0, false, None).unwrap(),
1434 expected
1435 );
1436
1437 let args = vec![ScalarValue::DurationSecond(Some(seconds)).lit()];
1438 assert_eq!(
1439 evaluate_expr_to_millisecond(&args, 0, false, None).unwrap(),
1440 expected
1441 );
1442 }
1443 }
1444
1445 fn assert_millisecond_overflow(result: DFResult<i64>, source_expr: &Expr) {
1446 let error = result.unwrap_err();
1447 let DataFusionError::Plan(message) = error else {
1448 panic!("expected a plan error");
1449 };
1450 assert!(message.contains("overflow"));
1451 let source_expr_name = source_expr.schema_name().to_string();
1452 assert!(message.contains(&source_expr_name));
1453 }
1454
1455 #[test]
1456 fn qbs_second_to_millisecond_timestamp_positive_overflow_is_error() {
1457 let args = vec![ScalarValue::TimestampSecond(Some(i64::MAX / 1_000 + 1), None).lit()];
1458 assert_millisecond_overflow(
1459 evaluate_expr_to_millisecond(&args, 0, false, None),
1460 &args[0],
1461 );
1462 }
1463
1464 #[test]
1465 fn qbs_second_to_millisecond_timestamp_negative_overflow_is_error() {
1466 let args = vec![ScalarValue::TimestampSecond(Some(i64::MIN / 1_000 - 1), None).lit()];
1467 assert_millisecond_overflow(
1468 evaluate_expr_to_millisecond(&args, 0, false, None),
1469 &args[0],
1470 );
1471 }
1472
1473 #[test]
1474 fn qbs_second_to_millisecond_duration_positive_overflow_is_error() {
1475 let args = vec![ScalarValue::DurationSecond(Some(i64::MAX / 1_000 + 1)).lit()];
1476 assert_millisecond_overflow(
1477 evaluate_expr_to_millisecond(&args, 0, false, None),
1478 &args[0],
1479 );
1480 }
1481
1482 #[test]
1483 fn qbs_second_to_millisecond_duration_negative_overflow_is_error() {
1484 let args = vec![ScalarValue::DurationSecond(Some(i64::MIN / 1_000 - 1)).lit()];
1485 assert_millisecond_overflow(
1486 evaluate_expr_to_millisecond(&args, 0, false, None),
1487 &args[0],
1488 );
1489 }
1490
1491 #[test]
1492 fn qbs_duration_submillisecond_contract() {
1493 let args = vec![ScalarValue::DurationNanosecond(Some(-1)).lit()];
1495 assert!(parse_duration_expr(&args, 0).is_err());
1496 let args = vec![ScalarValue::DurationNanosecond(Some(999_999)).lit()];
1497 assert!(parse_duration_expr(&args, 0).is_err());
1498 let args = vec![ScalarValue::DurationNanosecond(Some(NANOS_PER_MILLI)).lit()];
1499 assert!(parse_duration_expr(&args, 0).is_err());
1500
1501 let args = vec!["1ms".lit()];
1502 assert_eq!(
1503 parse_duration_expr(&args, 0).unwrap(),
1504 Duration::from_millis(1)
1505 );
1506
1507 let args = vec![
1508 ScalarValue::IntervalMonthDayNano(Some(IntervalMonthDayNano::new(0, 0, -1).into()))
1509 .lit(),
1510 ];
1511 assert!(parse_duration_expr(&args, 0).is_err());
1512 let args = vec![
1513 ScalarValue::IntervalMonthDayNano(Some(
1514 IntervalMonthDayNano::new(0, 0, 999_999).into(),
1515 ))
1516 .lit(),
1517 ];
1518 assert!(parse_duration_expr(&args, 0).is_err());
1519 let args = vec![
1520 ScalarValue::IntervalMonthDayNano(Some(
1521 IntervalMonthDayNano::new(0, 0, NANOS_PER_MILLI).into(),
1522 ))
1523 .lit(),
1524 ];
1525 assert_eq!(
1526 parse_duration_expr(&args, 0).unwrap(),
1527 Duration::from_millis(1)
1528 );
1529
1530 let args = vec![
1531 ScalarValue::IntervalMonthDayNano(Some(IntervalMonthDayNano::new(0, 1, -1).into()))
1532 .lit(),
1533 ];
1534 assert_eq!(
1535 parse_duration_expr(&args, 0).unwrap(),
1536 Duration::from_millis(MS_PER_DAY as u64 - 1)
1537 );
1538 let args = vec![
1539 ScalarValue::IntervalMonthDayNano(Some(
1540 IntervalMonthDayNano::new(0, 1, 999_999).into(),
1541 ))
1542 .lit(),
1543 ];
1544 assert_eq!(
1545 parse_duration_expr(&args, 0).unwrap(),
1546 Duration::from_millis(MS_PER_DAY as u64)
1547 );
1548 }
1549
1550 #[test]
1551 fn test_parse_duration_expr() {
1552 let interval = IntervalYearMonth::new(10);
1554 let args = vec![ScalarValue::IntervalYearMonth(Some(interval.to_i32())).lit()];
1555 assert!(parse_duration_expr(&args, 0).is_err(),);
1556 let interval = IntervalDayTime::new(10, 10);
1558 let args = vec![ScalarValue::IntervalDayTime(Some(interval.into())).lit()];
1559 assert_eq!(
1560 parse_duration_expr(&args, 0).unwrap().as_millis() as i64,
1561 interval.as_millis()
1562 );
1563 let interval = IntervalMonthDayNano::new(0, 10, 10);
1565 let args = vec![ScalarValue::IntervalMonthDayNano(Some(interval.into())).lit()];
1566 assert_eq!(
1567 parse_duration_expr(&args, 0).unwrap().as_millis() as i64,
1568 interval.days as i64 * MS_PER_DAY + interval.nanoseconds.div_euclid(NANOS_PER_MILLI),
1569 );
1570 let args = vec!["1y4w".lit()];
1572 assert_eq!(
1573 parse_duration_expr(&args, 0).unwrap(),
1574 parse_duration("1y4w").unwrap()
1575 );
1576 let args = vec![Expr::Cast(Cast {
1578 expr: Box::new("15 minutes".lit()),
1579 data_type: DataType::Interval(IntervalUnit::MonthDayNano),
1580 })];
1581 assert_eq!(
1582 parse_duration_expr(&args, 0).unwrap(),
1583 parse_duration("15m").unwrap()
1584 );
1585 assert!(parse_duration_expr(&args, 10).is_err());
1587 let args = vec![Expr::BinaryExpr(BinaryExpr {
1589 left: Box::new(
1590 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(0, 10).into())).lit(),
1591 ),
1592 op: Operator::Plus,
1593 right: Box::new(
1594 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(0, 10).into())).lit(),
1595 ),
1596 })];
1597 assert_eq!(
1598 parse_duration_expr(&args, 0).unwrap(),
1599 Duration::from_millis(20)
1600 );
1601 let args = vec![Expr::BinaryExpr(BinaryExpr {
1602 left: Box::new(
1603 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(0, 10).into())).lit(),
1604 ),
1605 op: Operator::Minus,
1606 right: Box::new(
1607 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(0, 10).into())).lit(),
1608 ),
1609 })];
1610 assert!(parse_duration_expr(&args, 0).is_err());
1612 let args = vec![Expr::BinaryExpr(BinaryExpr {
1614 left: Box::new(
1615 ScalarValue::IntervalYearMonth(Some(IntervalYearMonth::new(10).to_i32())).lit(),
1616 ),
1617 op: Operator::Minus,
1618 right: Box::new(ScalarValue::Time64Microsecond(Some(0)).lit()),
1619 })];
1620 assert!(parse_duration_expr(&args, 0).is_err());
1621 }
1622
1623 #[test]
1624 fn test_parse_align_to() {
1625 let args = vec!["NOW".lit()];
1627 let epsinon =
1628 parse_align_to(&args, 0, None, None).unwrap() - Timestamp::current_millis().value();
1629 assert!(epsinon.abs() < 100);
1630 let scheduled_time = DateTime::from_timestamp(1_700_000_000, 123_000_000).unwrap();
1631 assert_eq!(
1632 scheduled_time.timestamp_millis(),
1633 parse_align_to(&args, 0, None, Some(scheduled_time)).unwrap()
1634 );
1635 let args = vec!["".lit()];
1637 assert_eq!(0, parse_align_to(&args, 0, None, None).unwrap());
1638 let args = vec!["".lit()];
1640 assert_eq!(
1641 -36000 * 1000,
1642 parse_align_to(
1643 &args,
1644 0,
1645 Some(&Timezone::from_tz_string("HST").unwrap()),
1646 None
1647 )
1648 .unwrap()
1649 );
1650 assert_eq!(
1651 28800 * 1000,
1652 parse_align_to(
1653 &args,
1654 0,
1655 Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap()),
1656 None
1657 )
1658 .unwrap()
1659 );
1660
1661 let args = vec!["1970-01-01T00:00:00+08:00".lit()];
1663 assert_eq!(
1664 parse_align_to(&args, 0, None, None).unwrap(),
1665 -8 * 60 * 60 * 1000
1666 );
1667 let args = vec!["1970-01-01T00:00:00".lit()];
1669 assert_eq!(
1670 parse_align_to(
1671 &args,
1672 0,
1673 Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap()),
1674 None
1675 )
1676 .unwrap(),
1677 -8 * 60 * 60 * 1000
1678 );
1679 let args = vec![Expr::BinaryExpr(BinaryExpr {
1681 left: Box::new(
1682 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(0, 10).into())).lit(),
1683 ),
1684 op: Operator::Plus,
1685 right: Box::new(
1686 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(0, 10).into())).lit(),
1687 ),
1688 })];
1689 assert_eq!(parse_align_to(&args, 0, None, None).unwrap(), 20);
1690 }
1691
1692 #[test]
1693 fn test_interval_only() {
1694 let expr = Expr::BinaryExpr(BinaryExpr {
1695 left: Box::new(ScalarValue::DurationMillisecond(Some(20)).lit()),
1696 op: Operator::Minus,
1697 right: Box::new(
1698 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(10, 0).into())).lit(),
1699 ),
1700 });
1701 assert!(!interval_only_in_expr(&expr));
1702 let expr = Expr::BinaryExpr(BinaryExpr {
1703 left: Box::new(
1704 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(10, 0).into())).lit(),
1705 ),
1706 op: Operator::Minus,
1707 right: Box::new(
1708 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(10, 0).into())).lit(),
1709 ),
1710 });
1711 assert!(interval_only_in_expr(&expr));
1712
1713 let expr = Expr::BinaryExpr(BinaryExpr {
1714 left: Box::new(Expr::Cast(Cast {
1715 expr: Box::new("15 minute".lit()),
1716 data_type: DataType::Interval(IntervalUnit::MonthDayNano),
1717 })),
1718 op: Operator::Minus,
1719 right: Box::new(
1720 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(10, 0).into())).lit(),
1721 ),
1722 });
1723 assert!(interval_only_in_expr(&expr));
1724
1725 let expr = Expr::Cast(Cast {
1726 expr: Box::new(Expr::BinaryExpr(BinaryExpr {
1727 left: Box::new(Expr::Cast(Cast {
1728 expr: Box::new("15 minute".lit()),
1729 data_type: DataType::Interval(IntervalUnit::MonthDayNano),
1730 })),
1731 op: Operator::Minus,
1732 right: Box::new(
1733 ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(10, 0).into())).lit(),
1734 ),
1735 })),
1736 data_type: DataType::Interval(IntervalUnit::MonthDayNano),
1737 });
1738
1739 assert!(interval_only_in_expr(&expr));
1740 }
1741}