Skip to main content

query/range_select/
plan_rewrite.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
53/// `RangeExprRewriter` will recursively search certain `Expr`, find all `range_fn` scalar udf contained in `Expr`,
54/// and collect the information required by the RangeSelect query,
55/// and finally modify the `range_fn` scalar udf to an ordinary column field.
56pub struct RangeExprRewriter<'a> {
57    input_plan: &'a Arc<LogicalPlan>,
58    align: Duration,
59    align_to: i64,
60    by: Vec<Expr>,
61    /// Use `BTreeSet` to avoid in case like `avg(a) RANGE '5m' + avg(a) RANGE '5m'`, duplicate range expr `avg(a) RANGE '5m'` be calculate twice
62    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
130/// Parse a duration expr:
131/// 1. duration string (e.g. `'1h'`)
132/// 2. Interval expr (e.g. `INTERVAL '1 year 3 hours 20 minutes'`)
133/// 3. An interval expr can be evaluated at the logical plan stage (e.g. `INTERVAL '2' day - INTERVAL '1' day`)
134fn 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
150/// Evaluate a time calculation expr, case like:
151/// 1. `INTERVAL '1' day + INTERVAL '1 year 2 hours 3 minutes'`
152/// 2. `now() - INTERVAL '1' day` (when `interval_only==false`)
153///
154/// Output a millisecond timestamp
155///
156/// if `interval_only==true`, only accept expr with all interval type (case 2 will return a error)
157fn 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        // We don't support interval with months as days in a month is unclear.
194        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
240/// Parse the `align to` clause and return a UTC timestamp with unit of millisecond,
241/// which is used as the basis for dividing time slot during the align operation.
242/// 1. NOW: align to current execute time
243/// 2. Timestamp string: align to specific timestamp
244/// 3. An expr can be evaluated at the logical plan stage (e.g. `now() - INTERVAL '1' day`)
245/// 4. leave empty (as Default Option): align to unix epoch 0 (timezone aware)
246fn 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        // default align to unix epoch 0 (timezone aware)
263        "" => 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            // `range_fn(func, range, fill, byc, [byv], align, to)`
326            // `[byv]` are variadic arguments, byc indicate the length of arguments
327            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
381/// In order to implement RangeSelect query like `avg(field_0) RANGE '5m' FILL NULL`,
382/// All RangeSelect query items are converted into udf scalar function in sql parse stage, with format like `range_fn(avg(field_0), .....)`.
383/// `range_fn` contains all the parameters we need to execute RangeSelect.
384/// In order to correctly execute the query process of range select, we need to modify the query plan generated by datafusion.
385/// We need to recursively find the entire LogicalPlan, and find all `range_fn` scalar udf contained in the project plan,
386/// collecting info we need to generate RangeSelect Query LogicalPlan and rewrite th original LogicalPlan.
387pub 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                    // Expr like `rate(max(a) RANGE '6m') RANGE '6m'` have legal syntax but illegal semantic.
420                    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                    // Due to the limitations of Datafusion, for `LogicalPlan::Analyze` and `LogicalPlan::Explain`,
493                    // directly using the method `with_new_inputs` to rebuild a new `LogicalPlan` will cause an error,
494                    // so here we directly use the `LogicalPlanBuilder` to build a new plan.
495                    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    /// Finds the time index column and default row-key grouping from the input schema.
549    ///
550    /// Returns `(time_index, [row_columns])` to the rewriter. If the user omits `BY`,
551    /// `[row_columns]` is used as the default time-series grouping.
552    ///
553    /// For derived inputs such as subqueries, joins, or set operations, the source table
554    /// qualifier may no longer resolve back to a table provider. In that case we can still
555    /// recover the time index from column metadata, but we cannot safely reconstruct the
556    /// original row-key columns, so omitted `BY` must be rejected by the caller.
557    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                        // `TableNotExist` here usually means the qualifier now refers to a derived
589                        // input instead of a base table. We can still salvage the time index from
590                        // field metadata, but only when such metadata is present.
591                        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                // assert time_index's datatype is timestamp
616                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 the user does not specify a primary key when creating a table,
624                    // then by default all data will be aggregated into one time series,
625                    // which is equivalent to using `by(1)` in SQL
626                    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
664/// Builds the narrow child projection required by [`RangeSelect`].
665///
666/// The physical Range implementation consumes aggregate arguments and aggregate
667/// ordering expressions, but does not support aggregate `FILTER` expressions.
668fn 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        // A cast expression for an interval.
739        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            // Stop checking the sub `expr`,
747            // which is a `Utf8` type and has already been tested above.
748            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    /// Start directly from the rewritten SQL and check whether the error reported by the range expression rewriting is as expected.
1382    /// the right argument is `range_fn(avg(field_0), '5m', 'NULL', '0', '1h')`
1383    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        // Arrow Duration literals are not accepted by the public interval-only parser.
1494        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        // test IntervalYearMonth
1553        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        // test IntervalDayTime
1557        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        // test IntervalMonthDayNano
1564        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        // test Duration
1571        let args = vec!["1y4w".lit()];
1572        assert_eq!(
1573            parse_duration_expr(&args, 0).unwrap(),
1574            parse_duration("1y4w").unwrap()
1575        );
1576        // test cast expression
1577        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        // test index err
1586        assert!(parse_duration_expr(&args, 10).is_err());
1587        // test evaluate expr
1588        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        // test zero interval error
1611        assert!(parse_duration_expr(&args, 0).is_err());
1612        // test must all be interval
1613        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        // test NOW
1626        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        // test default
1636        let args = vec!["".lit()];
1637        assert_eq!(0, parse_align_to(&args, 0, None, None).unwrap());
1638        // test default with timezone
1639        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        // test Timestamp
1662        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        // timezone
1668        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        // test evaluate expr
1680        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}