Skip to main content

sql/parsers/
utils.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::HashMap;
16use std::sync::Arc;
17
18use chrono::{DateTime, Utc};
19use datafusion::config::ConfigOptions;
20use datafusion::error::Result as DfResult;
21use datafusion::execution::SessionStateBuilder;
22use datafusion::execution::context::SessionState;
23use datafusion::optimizer::simplify_expressions::ExprSimplifier;
24use datafusion_common::tree_node::{TreeNode, TreeNodeVisitor};
25use datafusion_common::{DFSchema, ScalarValue, TableReference};
26use datafusion_expr::simplify::SimplifyContext;
27use datafusion_expr::{AggregateUDF, Expr, HigherOrderUDF, ScalarUDF, TableSource, WindowUDF};
28use datafusion_sql::planner::{ContextProvider, SqlToRel};
29use datatypes::arrow::datatypes::DataType;
30use datatypes::schema::{
31    COLUMN_FULLTEXT_OPT_KEY_ANALYZER, COLUMN_FULLTEXT_OPT_KEY_BACKEND,
32    COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE, COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE,
33    COLUMN_FULLTEXT_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE,
34    COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE,
35    COLUMN_VECTOR_INDEX_OPT_KEY_CONNECTIVITY, COLUMN_VECTOR_INDEX_OPT_KEY_ENGINE,
36    COLUMN_VECTOR_INDEX_OPT_KEY_EXPANSION_ADD, COLUMN_VECTOR_INDEX_OPT_KEY_EXPANSION_SEARCH,
37    COLUMN_VECTOR_INDEX_OPT_KEY_METRIC,
38};
39use snafu::{ResultExt, ensure};
40use sqlparser::dialect::Dialect;
41use sqlparser::keywords::Keyword;
42use sqlparser::parser::Parser;
43use table::requests::{SEMANTIC_PREFIX, validate_semantic_option, validate_table_option};
44
45use crate::error::{
46    ConvertToLogicalExpressionSnafu, InvalidSqlSnafu, InvalidTableOptionSnafu, ParseSqlValueSnafu,
47    Result, SimplificationSnafu, SyntaxSnafu,
48};
49use crate::parser::{ParseOptions, ParserContext};
50use crate::parsers::with_tql_parser::CteContent;
51use crate::statements::OptionMap;
52use crate::statements::query::Query;
53use crate::statements::statement::Statement;
54use crate::util::{OptionValue, parse_option_string};
55
56/// Check if the given SQL query is a TQL statement. Simple tql cte query is also considered as TQL statement.
57pub fn is_tql(dialect: &dyn Dialect, sql: &str) -> Result<bool> {
58    let stmts = ParserContext::create_with_dialect(sql, dialect, ParseOptions::default())?;
59
60    ensure!(
61        stmts.len() == 1,
62        InvalidSqlSnafu {
63            msg: format!("Expect only one statement, found {}", stmts.len())
64        }
65    );
66    let stmt = &stmts[0];
67    match stmt {
68        Statement::Tql(_) => Ok(true),
69        Statement::Query(query) => Ok(is_simple_tql_cte_query(query)),
70        _ => Ok(false),
71    }
72}
73
74pub(crate) fn is_simple_tql_cte_query(query: &Query) -> bool {
75    use crate::parser::ParserContext;
76
77    let Some(hybrid_cte) = &query.hybrid_cte else {
78        return false;
79    };
80
81    if !has_only_hybrid_tql_cte(query) {
82        return false;
83    }
84
85    let Some(cte) = hybrid_cte.cte_tables.first() else {
86        return false;
87    };
88    if hybrid_cte.cte_tables.len() != 1 || !matches!(cte.content, CteContent::Tql(_)) {
89        return false;
90    }
91
92    let Some(reference) = extract_simple_select_star_reference(query) else {
93        return false;
94    };
95
96    let reference = ParserContext::canonicalize_identifier(reference).value;
97    let cte_name = ParserContext::canonicalize_identifier(cte.name.clone()).value;
98    reference == cte_name
99}
100
101pub(crate) fn has_tql_cte(query: &Query) -> bool {
102    query.hybrid_cte.as_ref().is_some_and(|with| {
103        with.cte_tables
104            .iter()
105            .any(|cte| matches!(cte.content, CteContent::Tql(_)))
106    })
107}
108
109fn has_only_hybrid_tql_cte(query: &Query) -> bool {
110    query
111        .inner
112        .with
113        .as_ref()
114        .is_none_or(|with| with.cte_tables.is_empty())
115}
116
117fn extract_simple_select_star_reference(query: &Query) -> Option<sqlparser::ast::Ident> {
118    use sqlparser::ast::{SetExpr, TableFactor};
119
120    if !is_plain_query_root(&query.inner) {
121        return None;
122    }
123
124    let SetExpr::Select(select) = &*query.inner.body else {
125        return None;
126    };
127    if !is_plain_select(select) || !is_plain_wildcard_projection(select.projection.as_slice()) {
128        return None;
129    }
130
131    let [table_with_joins] = select.from.as_slice() else {
132        return None;
133    };
134    if !table_with_joins.joins.is_empty() {
135        return None;
136    }
137
138    let TableFactor::Table { name, .. } = &table_with_joins.relation else {
139        return None;
140    };
141    if name.0.len() != 1 {
142        return None;
143    }
144
145    name.0[0].as_ident().cloned()
146}
147
148fn is_plain_query_root(query: &sqlparser::ast::Query) -> bool {
149    query.order_by.is_none()
150        && query.limit_clause.is_none()
151        && query.fetch.is_none()
152        && query.locks.is_empty()
153        && query.for_clause.is_none()
154        && query.settings.is_none()
155        && query.format_clause.is_none()
156        && query.pipe_operators.is_empty()
157}
158
159fn is_plain_select(select: &sqlparser::ast::Select) -> bool {
160    use sqlparser::ast::GroupByExpr;
161
162    select.distinct.is_none()
163        && select.top.is_none()
164        && select.exclude.is_none()
165        && select.into.is_none()
166        && select.lateral_views.is_empty()
167        && select.prewhere.is_none()
168        && select.selection.is_none()
169        && matches!(select.group_by, GroupByExpr::Expressions(ref exprs, _) if exprs.is_empty())
170        && select.cluster_by.is_empty()
171        && select.distribute_by.is_empty()
172        && select.sort_by.is_empty()
173        && select.having.is_none()
174        && select.named_window.is_empty()
175        && select.qualify.is_none()
176        && select.value_table_mode.is_none()
177        && select.connect_by.is_empty()
178}
179
180fn is_plain_wildcard_projection(projection: &[sqlparser::ast::SelectItem]) -> bool {
181    use sqlparser::ast::SelectItem;
182
183    matches!(
184        projection,
185        [SelectItem::Wildcard(options)]
186            if options.opt_ilike.is_none()
187                && options.opt_exclude.is_none()
188                && options.opt_except.is_none()
189                && options.opt_replace.is_none()
190                && options.opt_rename.is_none()
191    )
192}
193
194/// Convert a parser expression to a scalar value. This function will try the
195/// best to resolve and reduce constants. Exprs like `1 + 1` or `now()` can be
196/// handled properly.
197///
198/// if `require_now_expr` is true, it will ensure that the expression contains a `now()` function.
199/// If the expression does not contain `now()`, it will return an error.
200///
201pub fn parser_expr_to_scalar_value_literal(
202    expr: sqlparser::ast::Expr,
203    require_now_expr: bool,
204) -> Result<ScalarValue> {
205    parser_expr_to_scalar_value_literal_at(expr, require_now_expr, None)
206}
207
208/// Same as [`parser_expr_to_scalar_value_literal`] but uses the provided
209/// `scheduled_time` for evaluating `now()`. If `scheduled_time` is
210/// `Some`, `now()` will be simplified to the given timestamp instead of
211/// the current wall-clock time.
212pub fn parser_expr_to_scalar_value_literal_at(
213    expr: sqlparser::ast::Expr,
214    require_now_expr: bool,
215    scheduled_time: Option<DateTime<Utc>>,
216) -> Result<ScalarValue> {
217    // 1. convert parser expr to logical expr
218    let empty_df_schema = DFSchema::empty();
219    let logical_expr = SqlToRel::new(&StubContextProvider::default())
220        .sql_to_expr(expr, &empty_df_schema, &mut Default::default())
221        .context(ConvertToLogicalExpressionSnafu)?;
222
223    struct FindNow {
224        found: bool,
225    }
226
227    impl TreeNodeVisitor<'_> for FindNow {
228        type Node = Expr;
229        fn f_down(
230            &mut self,
231            node: &Self::Node,
232        ) -> DfResult<datafusion_common::tree_node::TreeNodeRecursion> {
233            if let Expr::ScalarFunction(func) = node
234                && func.name().to_lowercase() == "now"
235            {
236                if !func.args.is_empty() {
237                    return Err(datafusion_common::DataFusionError::Plan(
238                        "now() function should not have arguments".to_string(),
239                    ));
240                }
241                self.found = true;
242                return Ok(datafusion_common::tree_node::TreeNodeRecursion::Stop);
243            }
244            Ok(datafusion_common::tree_node::TreeNodeRecursion::Continue)
245        }
246    }
247
248    if require_now_expr {
249        let have_now = {
250            let mut visitor = FindNow { found: false };
251            logical_expr.visit(&mut visitor).unwrap();
252            visitor.found
253        };
254        if !have_now {
255            return ParseSqlValueSnafu {
256                msg: format!(
257                    "expected now() expression, but not found in {}",
258                    logical_expr
259                ),
260            }
261            .fail();
262        }
263    }
264
265    // 2. simplify logical expr — use scheduled time if provided, else wall-clock
266    let info = match scheduled_time {
267        Some(dt) => SimplifyContext::builder()
268            .with_query_execution_start_time(Some(dt))
269            .build(),
270        None => SimplifyContext::builder().with_current_time().build(),
271    };
272    let simplifier = ExprSimplifier::new(info);
273
274    // Coerce the logical expression so simplifier can handle it correctly. This is necessary for const eval with possible type mismatch. i.e.: `now() - now() + '15s'::interval` which is `TimestampNanosecond - TimestampNanosecond + IntervalMonthDayNano`.
275    let logical_expr = simplifier
276        .coerce(logical_expr, &empty_df_schema)
277        .context(SimplificationSnafu)?;
278
279    let simplified_expr = simplifier
280        .simplify(logical_expr)
281        .context(SimplificationSnafu)?;
282
283    if let datafusion::logical_expr::Expr::Literal(lit, _) = simplified_expr {
284        Ok(lit)
285    } else {
286        // Err(ParseSqlValue)
287        ParseSqlValueSnafu {
288            msg: format!("expected literal value, but found {:?}", simplified_expr),
289        }
290        .fail()
291    }
292}
293
294/// Helper struct for [`parser_expr_to_scalar_value`].
295struct StubContextProvider {
296    state: SessionState,
297}
298
299impl Default for StubContextProvider {
300    fn default() -> Self {
301        Self {
302            state: SessionStateBuilder::new()
303                .with_config(Default::default())
304                .with_runtime_env(Default::default())
305                .with_default_features()
306                .build(),
307        }
308    }
309}
310
311impl ContextProvider for StubContextProvider {
312    fn get_table_source(&self, _name: TableReference) -> DfResult<Arc<dyn TableSource>> {
313        unimplemented!()
314    }
315
316    fn get_function_meta(&self, name: &str) -> Option<Arc<ScalarUDF>> {
317        self.state.scalar_functions().get(name).cloned()
318    }
319
320    fn get_higher_order_meta(&self, name: &str) -> Option<Arc<HigherOrderUDF>> {
321        self.state.higher_order_functions().get(name).cloned()
322    }
323
324    fn get_aggregate_meta(&self, name: &str) -> Option<Arc<AggregateUDF>> {
325        self.state.aggregate_functions().get(name).cloned()
326    }
327
328    fn get_window_meta(&self, _name: &str) -> Option<Arc<WindowUDF>> {
329        unimplemented!()
330    }
331
332    fn get_variable_type(&self, _variable_names: &[String]) -> Option<DataType> {
333        unimplemented!()
334    }
335
336    fn options(&self) -> &ConfigOptions {
337        self.state.config_options()
338    }
339
340    fn udf_names(&self) -> Vec<String> {
341        self.state.scalar_functions().keys().cloned().collect()
342    }
343
344    fn higher_order_function_names(&self) -> Vec<String> {
345        self.state
346            .higher_order_functions()
347            .keys()
348            .cloned()
349            .collect()
350    }
351
352    fn udaf_names(&self) -> Vec<String> {
353        self.state.aggregate_functions().keys().cloned().collect()
354    }
355
356    fn udwf_names(&self) -> Vec<String> {
357        self.state.window_functions().keys().cloned().collect()
358    }
359}
360
361pub fn validate_column_fulltext_create_option(key: &str) -> bool {
362    [
363        COLUMN_FULLTEXT_OPT_KEY_ANALYZER,
364        COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE,
365        COLUMN_FULLTEXT_OPT_KEY_BACKEND,
366        COLUMN_FULLTEXT_OPT_KEY_GRANULARITY,
367        COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE,
368    ]
369    .contains(&key)
370}
371
372pub fn validate_column_skipping_index_create_option(key: &str) -> bool {
373    [
374        COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY,
375        COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE,
376        COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE,
377    ]
378    .contains(&key)
379}
380
381pub fn validate_column_vector_index_create_option(key: &str) -> bool {
382    [
383        COLUMN_VECTOR_INDEX_OPT_KEY_ENGINE,
384        COLUMN_VECTOR_INDEX_OPT_KEY_METRIC,
385        COLUMN_VECTOR_INDEX_OPT_KEY_CONNECTIVITY,
386        COLUMN_VECTOR_INDEX_OPT_KEY_EXPANSION_ADD,
387        COLUMN_VECTOR_INDEX_OPT_KEY_EXPANSION_SEARCH,
388    ]
389    .contains(&key)
390}
391
392/// Convert an [`IntervalMonthDayNano`] to a [`Duration`].
393#[cfg(feature = "enterprise")]
394pub fn convert_month_day_nano_to_duration(
395    interval: arrow_buffer::IntervalMonthDayNano,
396) -> Result<std::time::Duration> {
397    let months: i64 = interval.months.into();
398    let days: i64 = interval.days.into();
399    let months_in_seconds: i64 = months * 60 * 60 * 24 * 3044 / 1000;
400    let days_in_seconds: i64 = days * 60 * 60 * 24;
401    let seconds_from_nanos = interval.nanoseconds / 1_000_000_000;
402    let total_seconds = months_in_seconds + days_in_seconds + seconds_from_nanos;
403
404    let mut nanos_remainder = interval.nanoseconds % 1_000_000_000;
405    let mut adjusted_seconds = total_seconds;
406
407    if nanos_remainder < 0 {
408        nanos_remainder += 1_000_000_000;
409        adjusted_seconds -= 1;
410    }
411
412    snafu::ensure!(
413        adjusted_seconds >= 0,
414        crate::error::InvalidIntervalSnafu {
415            reason: "must be a positive interval",
416        }
417    );
418
419    // Cast safety: `adjusted_seconds` is guaranteed to be non-negative before.
420    let adjusted_seconds = adjusted_seconds as u64;
421    // Cast safety: `nanos_remainder` is smaller than 1_000_000_000 which
422    // is checked above.
423    let nanos_remainder = nanos_remainder as u32;
424
425    Ok(std::time::Duration::new(adjusted_seconds, nanos_remainder))
426}
427
428pub fn parse_with_options(parser: &mut Parser) -> Result<OptionMap> {
429    let options = parser
430        .parse_options(Keyword::WITH)
431        .context(SyntaxSnafu)?
432        .into_iter()
433        .map(parse_option_string)
434        .collect::<Result<HashMap<String, OptionValue>>>()?;
435    for (key, value) in &options {
436        if key.starts_with(SEMANTIC_PREFIX) {
437            // Semantic keys are whitelisted and value-checked against their domain,
438            // so a user cannot set an unknown key or an out-of-range value.
439            let value = value.as_string().unwrap_or_default();
440            ensure!(
441                validate_semantic_option(key, value),
442                InvalidTableOptionSnafu { key }
443            );
444        } else {
445            ensure!(validate_table_option(key), InvalidTableOptionSnafu { key });
446        }
447    }
448    Ok(OptionMap::new(options))
449}
450
451#[cfg(test)]
452mod tests {
453    use chrono::DateTime;
454    use datafusion::functions::datetime::expr_fn::now;
455    use datafusion_expr::lit;
456    use datatypes::arrow::datatypes::TimestampNanosecondType;
457
458    use super::*;
459    use crate::dialect::GreptimeDbDialect;
460    use crate::parser::{ParseOptions, ParserContext};
461    use crate::statements::statement::Statement;
462
463    #[test]
464    fn test_is_tql() {
465        let dialect = GreptimeDbDialect {};
466
467        assert!(is_tql(&dialect, "TQL EVAL (0, 10, '1s') cpu_usage_total").unwrap());
468        assert!(!is_tql(&dialect, "SELECT 1").unwrap());
469
470        let tql_cte = r#"
471WITH tql_cte(ts, val) AS (
472    TQL EVAL (0, 15, '5s') metric
473)
474SELECT * FROM tql_cte
475"#;
476        assert!(is_tql(&dialect, tql_cte).unwrap());
477
478        let rename_cols = r#"
479WITH tql (the_timestamp, the_value) AS (
480    TQL EVAL (0, 40, '10s') metric
481)
482SELECT * FROM tql
483"#;
484        assert!(is_tql(&dialect, rename_cols).unwrap());
485        let stmts =
486            ParserContext::create_with_dialect(rename_cols, &dialect, ParseOptions::default())
487                .unwrap();
488        let Statement::Query(q) = &stmts[0] else {
489            panic!("Expected Query statement");
490        };
491        let hybrid = q.hybrid_cte.as_ref().expect("Expected hybrid cte");
492        assert_eq!(hybrid.cte_tables.len(), 1);
493        assert_eq!(hybrid.cte_tables[0].columns.len(), 2);
494        assert_eq!(hybrid.cte_tables[0].columns[0].to_string(), "the_timestamp");
495        assert_eq!(hybrid.cte_tables[0].columns[1].to_string(), "the_value");
496
497        let sql_cte = r#"
498WITH cte AS (SELECT 1)
499SELECT * FROM cte
500"#;
501        assert!(!is_tql(&dialect, sql_cte).unwrap());
502
503        let extra_sql_cte = r#"
504WITH sql_cte AS (SELECT 1), tql_cte(ts, val) AS (
505    TQL EVAL (0, 15, '5s') metric
506)
507SELECT * FROM tql_cte
508"#;
509        assert!(!is_tql(&dialect, extra_sql_cte).unwrap());
510
511        let not_select_star = r#"
512WITH tql_cte(ts, val) AS (
513    TQL EVAL (0, 15, '5s') metric
514)
515SELECT ts FROM tql_cte
516"#;
517        assert!(!is_tql(&dialect, not_select_star).unwrap());
518
519        let with_filter = r#"
520WITH tql_cte(ts, val) AS (
521    TQL EVAL (0, 15, '5s') metric
522)
523SELECT * FROM tql_cte WHERE ts > 0
524"#;
525        assert!(!is_tql(&dialect, with_filter).unwrap());
526    }
527
528    /// Keep this test to make sure we are using datafusion's `ExprSimplifier` correctly.
529    #[test]
530    fn test_simplifier() {
531        let now_time = DateTime::from_timestamp(61, 0).unwrap();
532        let lit_now = lit(ScalarValue::new_timestamp::<TimestampNanosecondType>(
533            now_time.timestamp_nanos_opt(),
534            None,
535        ));
536        let testcases = vec![
537            (now(), lit_now),
538            (now() - now(), lit(ScalarValue::DurationNanosecond(Some(0)))),
539            (
540                now() + lit(ScalarValue::new_interval_dt(0, 1500)),
541                lit(ScalarValue::new_timestamp::<TimestampNanosecondType>(
542                    Some(62500000000),
543                    None,
544                )),
545            ),
546            (
547                now() - (now() + lit(ScalarValue::new_interval_dt(0, 1500))),
548                lit(ScalarValue::DurationNanosecond(Some(-1500000000))),
549            ),
550            // this one failed if type is not coerced
551            (
552                now() - now() + lit(ScalarValue::new_interval_dt(0, 1500)),
553                lit(ScalarValue::new_interval_mdn(0, 0, 1500000000)),
554            ),
555            (
556                lit(ScalarValue::new_interval_mdn(
557                    0,
558                    0,
559                    61 * 86400 * 1_000_000_000,
560                )),
561                lit(ScalarValue::new_interval_mdn(
562                    0,
563                    0,
564                    61 * 86400 * 1_000_000_000,
565                )),
566            ),
567        ];
568
569        let info = SimplifyContext::builder()
570            .with_query_execution_start_time(Some(now_time))
571            .build();
572        let simplifier = ExprSimplifier::new(info);
573        for (expr, expected) in testcases {
574            let expr_name = expr.schema_name().to_string();
575            let expr = simplifier.coerce(expr, &DFSchema::empty()).unwrap();
576
577            let simplified_expr = simplifier.simplify(expr).unwrap();
578            assert_eq!(
579                simplified_expr, expected,
580                "Failed to simplify expression: {expr_name}"
581            );
582        }
583    }
584}