Skip to main content

sql/statements/
create.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::fmt::{Display, Formatter};
17
18use common_catalog::consts::FILE_ENGINE;
19use datatypes::json::{JSON2_DEFAULT_MAX_AUTO_EXPANDED_PATHS, JsonSettings};
20use datatypes::prelude::ConcreteDataType;
21use datatypes::schema::{FulltextOptions, SkippingIndexOptions};
22use itertools::Itertools;
23use serde::Serialize;
24use snafu::ResultExt;
25use sqlparser::ast::{ColumnOptionDef, DataType, Expr};
26use sqlparser_derive::{Visit, VisitMut};
27
28use crate::ast::{ColumnDef, Ident, ObjectName};
29use crate::error::{
30    InvalidFlowQuerySnafu, InvalidSqlSnafu, Result, SetFulltextOptionSnafu,
31    SetSkippingIndexOptionSnafu,
32};
33use crate::statements::query::Query as GtQuery;
34use crate::statements::statement::Statement;
35use crate::statements::tql::Tql;
36use crate::statements::{OptionMap, sql_data_type_to_concrete_data_type};
37
38const LINE_SEP: &str = ",\n";
39const COMMA_SEP: &str = ", ";
40const INDENT: usize = 2;
41pub const VECTOR_OPT_DIM: &str = "dim";
42
43macro_rules! format_indent {
44    ($fmt: expr, $arg: expr) => {
45        format!($fmt, format_args!("{: >1$}", "", INDENT), $arg)
46    };
47    ($arg: expr) => {
48        format_indent!("{}{}", $arg)
49    };
50}
51
52macro_rules! format_list_indent {
53    ($list: expr) => {
54        $list.iter().map(|e| format_indent!(e)).join(LINE_SEP)
55    };
56}
57
58macro_rules! format_list_comma {
59    ($list: expr) => {
60        $list.iter().map(|e| format!("{}", e)).join(COMMA_SEP)
61    };
62}
63
64#[cfg(feature = "enterprise")]
65pub mod trigger;
66
67fn format_table_constraint(constraints: &[TableConstraint]) -> String {
68    constraints.iter().map(|c| format_indent!(c)).join(LINE_SEP)
69}
70
71/// Table constraint for create table statement.
72#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
73pub enum TableConstraint {
74    /// Primary key constraint.
75    PrimaryKey { columns: Vec<Ident> },
76    /// Time index constraint.
77    TimeIndex { column: Ident },
78}
79
80impl Display for TableConstraint {
81    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82        match self {
83            TableConstraint::PrimaryKey { columns } => {
84                write!(f, "PRIMARY KEY ({})", format_list_comma!(columns))
85            }
86            TableConstraint::TimeIndex { column } => {
87                write!(f, "TIME INDEX ({})", column)
88            }
89        }
90    }
91}
92
93#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
94pub struct CreateTable {
95    /// Create if not exists
96    pub if_not_exists: bool,
97    pub table_id: u32,
98    /// Table name
99    pub name: ObjectName,
100    pub columns: Vec<Column>,
101    pub engine: String,
102    pub constraints: Vec<TableConstraint>,
103    /// Table options in `WITH`. All keys are lowercase.
104    pub options: OptionMap,
105    pub partitions: Option<Partitions>,
106}
107
108/// Column definition in `CREATE TABLE` statement.
109#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
110pub struct Column {
111    /// `ColumnDef` from `sqlparser::ast`
112    pub column_def: ColumnDef,
113    /// Column extensions for greptimedb dialect.
114    pub extensions: ColumnExtensions,
115}
116
117/// Column extensions for greptimedb dialect.
118#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Default, Serialize)]
119pub struct ColumnExtensions {
120    /// Vector type options.
121    pub vector_options: Option<OptionMap>,
122
123    /// Fulltext index options.
124    pub fulltext_index_options: Option<OptionMap>,
125    /// Skipping index options.
126    pub skipping_index_options: Option<OptionMap>,
127    /// Inverted index options.
128    ///
129    /// Inverted index doesn't have options at present. There won't be any options in that map.
130    pub inverted_index_options: Option<OptionMap>,
131    /// JSON2-specific column options.
132    pub json2_options: Option<Json2Options>,
133}
134
135/// JSON2-specific options represented in the SQL AST.
136#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Default, Serialize)]
137pub struct Json2Options {
138    /// Maximum number of unhinted JSON2 paths expanded into Arrow fields.
139    pub(crate) max_auto_expanded_paths: Option<u32>,
140    /// Paths stored as explicitly typed JSON2 fields.
141    pub(crate) type_hints: Vec<JsonTypeHint>,
142}
143
144impl Display for Json2Options {
145    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
146        let mut options = Vec::with_capacity(self.type_hints.len() + 1);
147        if let Some(max) = self.max_auto_expanded_paths {
148            options.push(format!("max_auto_expanded_paths = {max}"));
149        }
150        options.extend(self.type_hints.iter().map(format_json_type_hint));
151        write!(f, "(\n    {}\n  )", options.iter().join(",\n    "))
152    }
153}
154
155impl Json2Options {
156    pub fn build_json_settings(&self) -> Result<JsonSettings> {
157        let type_hints = self
158            .type_hints
159            .iter()
160            .map(|hint| {
161                Ok(datatypes::json::JsonTypeHint {
162                    path: hint.path.clone(),
163                    data_type: sql_data_type_to_concrete_data_type(&hint.data_type)?,
164                    inverted_index: hint.inverted_index,
165                })
166            })
167            .collect::<Result<Vec<_>>>()?;
168        let max_auto_expanded_paths = self
169            .max_auto_expanded_paths
170            .or(Some(JSON2_DEFAULT_MAX_AUTO_EXPANDED_PATHS));
171        JsonSettings::try_new(type_hints, max_auto_expanded_paths).map_err(Into::into)
172    }
173}
174
175#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
176pub struct JsonTypeHint {
177    pub path: Vec<String>,
178    pub data_type: DataType,
179    pub inverted_index: bool,
180}
181
182impl Column {
183    pub fn name(&self) -> &Ident {
184        &self.column_def.name
185    }
186
187    pub fn data_type(&self) -> &DataType {
188        &self.column_def.data_type
189    }
190
191    pub fn mut_data_type(&mut self) -> &mut DataType {
192        &mut self.column_def.data_type
193    }
194
195    pub fn options(&self) -> &[ColumnOptionDef] {
196        &self.column_def.options
197    }
198
199    pub fn mut_options(&mut self) -> &mut Vec<ColumnOptionDef> {
200        &mut self.column_def.options
201    }
202}
203
204impl Display for Column {
205    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
206        if let Some(vector_options) = &self.extensions.vector_options
207            && let Some(dim) = vector_options.get(VECTOR_OPT_DIM)
208        {
209            write!(f, "{} VECTOR({})", self.column_def.name, dim)?;
210            return Ok(());
211        }
212
213        write!(f, "{} {}", self.column_def.name, self.column_def.data_type)?;
214        if let Some(options) = &self.extensions.json2_options {
215            write!(f, "{options}")?;
216        }
217        for option in &self.column_def.options {
218            write!(f, " {option}")?;
219        }
220
221        if let Some(fulltext_options) = &self.extensions.fulltext_index_options {
222            if !fulltext_options.is_empty() {
223                let options = fulltext_options.kv_pairs();
224                write!(f, " FULLTEXT INDEX WITH({})", format_list_comma!(options))?;
225            } else {
226                write!(f, " FULLTEXT INDEX")?;
227            }
228        }
229
230        if let Some(skipping_index_options) = &self.extensions.skipping_index_options {
231            if !skipping_index_options.is_empty() {
232                let options = skipping_index_options.kv_pairs();
233                write!(f, " SKIPPING INDEX WITH({})", format_list_comma!(options))?;
234            } else {
235                write!(f, " SKIPPING INDEX")?;
236            }
237        }
238
239        if let Some(inverted_index_options) = &self.extensions.inverted_index_options {
240            if !inverted_index_options.is_empty() {
241                let options = inverted_index_options.kv_pairs();
242                write!(f, " INVERTED INDEX WITH({})", format_list_comma!(options))?;
243            } else {
244                write!(f, " INVERTED INDEX")?;
245            }
246        }
247
248        Ok(())
249    }
250}
251
252impl ColumnExtensions {
253    pub fn build_fulltext_options(&self) -> Result<Option<FulltextOptions>> {
254        let Some(options) = self.fulltext_index_options.as_ref() else {
255            return Ok(None);
256        };
257
258        let options: HashMap<String, String> = options.clone().into_map();
259        Ok(Some(options.try_into().context(SetFulltextOptionSnafu)?))
260    }
261
262    pub fn build_skipping_index_options(&self) -> Result<Option<SkippingIndexOptions>> {
263        let Some(options) = self.skipping_index_options.as_ref() else {
264            return Ok(None);
265        };
266
267        let options: HashMap<String, String> = options.clone().into_map();
268        Ok(Some(
269            options.try_into().context(SetSkippingIndexOptionSnafu)?,
270        ))
271    }
272
273    pub fn build_json_settings(&self) -> Result<Option<JsonSettings>> {
274        let Some(options) = &self.json2_options else {
275            return Ok(None);
276        };
277
278        options.build_json_settings().map(Some)
279    }
280
281    pub fn set_json_settings(&mut self, settings: JsonSettings) -> Result<()> {
282        let (type_hints, max_auto_expanded_paths) = settings.into_parts();
283        let type_hints = type_hints
284            .into_iter()
285            .map(|hint| {
286                let data_type = json_type_hint_sql_data_type(&hint.data_type)?;
287                Ok(JsonTypeHint {
288                    path: hint.path,
289                    data_type,
290                    inverted_index: hint.inverted_index,
291                })
292            })
293            .collect::<Result<Vec<_>>>()?;
294        self.json2_options = (max_auto_expanded_paths.is_some() || !type_hints.is_empty())
295            .then_some(Json2Options {
296                max_auto_expanded_paths,
297                type_hints,
298            });
299        Ok(())
300    }
301}
302
303fn json_type_hint_sql_data_type(data_type: &ConcreteDataType) -> Result<DataType> {
304    let sql_type = match data_type {
305        ConcreteDataType::String(_) => DataType::String(None),
306        ConcreteDataType::Int64(_) => DataType::BigInt(None),
307        ConcreteDataType::UInt64(_) => DataType::BigIntUnsigned(None),
308        ConcreteDataType::Float64(_) => DataType::Double(sqlparser::ast::ExactNumberInfo::None),
309        ConcreteDataType::Boolean(_) => DataType::Boolean,
310        _ => {
311            return InvalidSqlSnafu {
312                msg: format!("unsupported JSON2 type hint data type: {data_type}"),
313            }
314            .fail();
315        }
316    };
317    Ok(sql_type)
318}
319
320fn format_json_type_hint(hint: &JsonTypeHint) -> String {
321    let path = hint
322        .path
323        .iter()
324        .map(|segment| format_json_path_segment(segment))
325        .join(".");
326    let inverted_index = if hint.inverted_index {
327        " INVERTED INDEX"
328    } else {
329        ""
330    };
331    format!("{} {}{}", path, hint.data_type, inverted_index)
332}
333
334fn format_json_path_segment(segment: &str) -> String {
335    format!("\"{}\"", segment.replace('"', "\"\""))
336}
337
338/// Partition on columns or values.
339///
340/// - `column_list` is the list of columns in `PARTITION ON COLUMNS` clause.
341/// - `exprs` is the list of expressions in `PARTITION ON VALUES` clause, like
342///   `host <= 'host1'`, `host > 'host1' and host <= 'host2'` or `host > 'host2'`.
343///   Each expression stands for a partition.
344#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
345pub struct Partitions {
346    pub column_list: Vec<Ident>,
347    pub exprs: Vec<Expr>,
348}
349
350impl Partitions {
351    /// set quotes to all [Ident]s from column list
352    pub fn set_quote(&mut self, quote_style: char) {
353        self.column_list
354            .iter_mut()
355            .for_each(|c| c.quote_style = Some(quote_style));
356    }
357}
358
359impl Display for Partitions {
360    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
361        if !self.column_list.is_empty() {
362            write!(
363                f,
364                "PARTITION ON COLUMNS ({}) (\n{}\n)",
365                format_list_comma!(self.column_list),
366                format_list_indent!(self.exprs),
367            )?;
368        }
369        Ok(())
370    }
371}
372
373impl Display for CreateTable {
374    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
375        write!(f, "CREATE ")?;
376        if self.engine == FILE_ENGINE {
377            write!(f, "EXTERNAL ")?;
378        }
379        write!(f, "TABLE ")?;
380        if self.if_not_exists {
381            write!(f, "IF NOT EXISTS ")?;
382        }
383        writeln!(f, "{} (", &self.name)?;
384        writeln!(f, "{},", format_list_indent!(self.columns))?;
385        writeln!(f, "{}", format_table_constraint(&self.constraints))?;
386        writeln!(f, ")")?;
387        if let Some(partitions) = &self.partitions {
388            writeln!(f, "{partitions}")?;
389        }
390        writeln!(f, "ENGINE={}", &self.engine)?;
391        if !self.options.is_empty() {
392            let options = self.options.kv_pairs();
393            write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
394        }
395        Ok(())
396    }
397}
398
399#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
400pub struct CreateDatabase {
401    pub name: ObjectName,
402    /// Create if not exists
403    pub if_not_exists: bool,
404    pub options: OptionMap,
405}
406
407impl CreateDatabase {
408    /// Creates a statement for `CREATE DATABASE`
409    pub fn new(name: ObjectName, if_not_exists: bool, options: OptionMap) -> Self {
410        Self {
411            name,
412            if_not_exists,
413            options,
414        }
415    }
416}
417
418impl Display for CreateDatabase {
419    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
420        write!(f, "CREATE DATABASE ")?;
421        if self.if_not_exists {
422            write!(f, "IF NOT EXISTS ")?;
423        }
424        write!(f, "{}", &self.name)?;
425        if !self.options.is_empty() {
426            let options = self.options.kv_pairs();
427            write!(f, "\nWITH(\n{}\n)", format_list_indent!(options))?;
428        }
429        Ok(())
430    }
431}
432
433#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
434pub struct CreateExternalTable {
435    /// Table name
436    pub name: ObjectName,
437    pub columns: Vec<Column>,
438    pub constraints: Vec<TableConstraint>,
439    /// Table options in `WITH`. All keys are lowercase.
440    pub options: OptionMap,
441    pub if_not_exists: bool,
442    pub engine: String,
443}
444
445impl Display for CreateExternalTable {
446    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
447        write!(f, "CREATE EXTERNAL TABLE ")?;
448        if self.if_not_exists {
449            write!(f, "IF NOT EXISTS ")?;
450        }
451        writeln!(f, "{} (", &self.name)?;
452        writeln!(f, "{},", format_list_indent!(self.columns))?;
453        writeln!(f, "{}", format_table_constraint(&self.constraints))?;
454        writeln!(f, ")")?;
455        writeln!(f, "ENGINE={}", &self.engine)?;
456        if !self.options.is_empty() {
457            let options = self.options.kv_pairs();
458            write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
459        }
460        Ok(())
461    }
462}
463
464#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
465pub struct CreateTableLike {
466    /// Table name
467    pub table_name: ObjectName,
468    /// The table that is designated to be imitated by `Like`
469    pub source_name: ObjectName,
470}
471
472impl Display for CreateTableLike {
473    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
474        let table_name = &self.table_name;
475        let source_name = &self.source_name;
476        write!(f, r#"CREATE TABLE {table_name} LIKE {source_name}"#)
477    }
478}
479
480#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
481pub struct CreateFlow {
482    /// Flow name
483    pub flow_name: ObjectName,
484    /// Output (sink) table name
485    pub sink_table_name: ObjectName,
486    /// Whether to replace existing task
487    pub or_replace: bool,
488    /// Create if not exist
489    pub if_not_exists: bool,
490    /// `EXPIRE AFTER`
491    /// Duration in second as `i64`
492    pub expire_after: Option<i64>,
493    /// Duration for flow evaluation interval
494    /// Duration in seconds as `i64`
495    /// If not set, flow will be evaluated based on time window size and other args.
496    pub eval_interval: Option<i64>,
497    /// Phase offset of the flow evaluation schedule within `eval_interval`.
498    /// Duration in seconds as `i64`.
499    /// Must be in range `[0, eval_interval)`. Only legal together with
500    /// `eval_interval`. A value of zero (the default) means the schedule is
501    /// anchored to the Unix epoch, i.e. phases at `k * eval_interval`.
502    pub eval_offset: Option<i64>,
503    /// Comment string
504    pub comment: Option<String>,
505    /// Flow creation options from `WITH (...)`
506    pub flow_options: OptionMap,
507    /// SQL statement
508    pub query: Box<SqlOrTql>,
509}
510
511/// Either a sql query or a tql query
512#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
513pub enum SqlOrTql {
514    Sql(GtQuery, String),
515    Tql(Tql, String),
516}
517
518impl std::fmt::Display for SqlOrTql {
519    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
520        match self {
521            Self::Sql(_, s) => write!(f, "{}", s),
522            Self::Tql(_, s) => write!(f, "{}", s),
523        }
524    }
525}
526
527impl SqlOrTql {
528    pub fn try_from_statement(
529        value: Statement,
530        original_query: &str,
531    ) -> std::result::Result<Self, crate::error::Error> {
532        match value {
533            Statement::Query(query) => Ok(Self::Sql(*query, original_query.to_string())),
534            Statement::Tql(tql) => Ok(Self::Tql(tql, original_query.to_string())),
535            _ => InvalidFlowQuerySnafu {
536                reason: format!("Expect either sql query or promql query, found {:?}", value),
537            }
538            .fail(),
539        }
540    }
541}
542
543impl Display for CreateFlow {
544    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
545        write!(f, "CREATE ")?;
546        if self.or_replace {
547            write!(f, "OR REPLACE ")?;
548        }
549        write!(f, "FLOW ")?;
550        if self.if_not_exists {
551            write!(f, "IF NOT EXISTS ")?;
552        }
553        writeln!(f, "{}", &self.flow_name)?;
554        writeln!(f, "SINK TO {}", &self.sink_table_name)?;
555        if let Some(expire_after) = &self.expire_after {
556            writeln!(f, "EXPIRE AFTER '{} s'", expire_after)?;
557        }
558        if let Some(eval_interval) = &self.eval_interval {
559            writeln!(f, "EVAL INTERVAL '{} s'", eval_interval)?;
560        }
561        // Canonical display: omit a zero offset (equivalent to the default
562        // epoch-anchored schedule). Non-zero offsets are always emitted.
563        if let Some(eval_offset) = &self.eval_offset
564            && *eval_offset != 0
565        {
566            writeln!(f, "EVAL OFFSET '{} s'", eval_offset)?;
567        }
568        if let Some(comment) = &self.comment {
569            writeln!(f, "COMMENT '{}'", comment)?;
570        }
571        if !self.flow_options.is_empty() {
572            let options = self.flow_options.kv_pairs();
573            writeln!(f, "WITH ({})", format_list_comma!(options))?;
574        }
575        write!(f, "AS {}", &self.query)
576    }
577}
578
579/// Create SQL view statement.
580#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
581pub struct CreateView {
582    /// View name
583    pub name: ObjectName,
584    /// An optional list of names to be used for columns of the view
585    pub columns: Vec<Ident>,
586    /// The clause after `As` that defines the VIEW.
587    /// Can only be either [Statement::Query] or [Statement::Tql].
588    pub query: Box<Statement>,
589    /// Whether to replace existing VIEW
590    pub or_replace: bool,
591    /// Create VIEW only when it doesn't exists
592    pub if_not_exists: bool,
593}
594
595impl Display for CreateView {
596    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
597        write!(f, "CREATE ")?;
598        if self.or_replace {
599            write!(f, "OR REPLACE ")?;
600        }
601        write!(f, "VIEW ")?;
602        if self.if_not_exists {
603            write!(f, "IF NOT EXISTS ")?;
604        }
605        write!(f, "{} ", &self.name)?;
606        if !self.columns.is_empty() {
607            write!(f, "({}) ", format_list_comma!(self.columns))?;
608        }
609        write!(f, "AS {}", &self.query)
610    }
611}
612
613#[cfg(test)]
614mod tests {
615    use std::assert_matches;
616
617    use datatypes::json::{JsonSettings, JsonTypeHint as DatatypeJsonTypeHint};
618    use datatypes::prelude::ConcreteDataType;
619
620    use super::*;
621    use crate::dialect::GreptimeDbDialect;
622    use crate::error::Error;
623    use crate::parser::{ParseOptions, ParserContext};
624    use crate::statements::statement::Statement;
625
626    #[test]
627    fn test_display_create_table() {
628        let sql = r"create table if not exists demo(
629                             host string,
630                             ts timestamp,
631                             cpu double default 0,
632                             memory double,
633                             TIME INDEX (ts),
634                             PRIMARY KEY(host)
635                       )
636                       PARTITION ON COLUMNS (host) (
637                            host = 'a',
638                            host > 'a',
639                       )
640                       engine=mito
641                       with(ttl='7d', storage='File');
642         ";
643        let result =
644            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
645                .unwrap();
646        assert_eq!(1, result.len());
647
648        match &result[0] {
649            Statement::CreateTable(c) => {
650                let new_sql = format!("\n{}", c);
651                assert_eq!(
652                    r#"
653CREATE TABLE IF NOT EXISTS demo (
654  host STRING,
655  ts TIMESTAMP,
656  cpu DOUBLE DEFAULT 0,
657  memory DOUBLE,
658  TIME INDEX (ts),
659  PRIMARY KEY (host)
660)
661PARTITION ON COLUMNS (host) (
662  host = 'a',
663  host > 'a'
664)
665ENGINE=mito
666WITH(
667  storage = 'File',
668  ttl = '7d'
669)"#,
670                    &new_sql
671                );
672
673                let new_result = ParserContext::create_with_dialect(
674                    &new_sql,
675                    &GreptimeDbDialect {},
676                    ParseOptions::default(),
677                )
678                .unwrap();
679                assert_eq!(result, new_result);
680            }
681            _ => unreachable!(),
682        }
683    }
684
685    #[test]
686    fn test_display_empty_partition_column() {
687        let sql = r"create table if not exists demo(
688            host string,
689            ts timestamp,
690            cpu double default 0,
691            memory double,
692            TIME INDEX (ts),
693            PRIMARY KEY(ts, host)
694            );
695        ";
696        let result =
697            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
698                .unwrap();
699        assert_eq!(1, result.len());
700
701        match &result[0] {
702            Statement::CreateTable(c) => {
703                let new_sql = format!("\n{}", c);
704                assert_eq!(
705                    r#"
706CREATE TABLE IF NOT EXISTS demo (
707  host STRING,
708  ts TIMESTAMP,
709  cpu DOUBLE DEFAULT 0,
710  memory DOUBLE,
711  TIME INDEX (ts),
712  PRIMARY KEY (ts, host)
713)
714ENGINE=mito
715"#,
716                    &new_sql
717                );
718
719                let new_result = ParserContext::create_with_dialect(
720                    &new_sql,
721                    &GreptimeDbDialect {},
722                    ParseOptions::default(),
723                )
724                .unwrap();
725                assert_eq!(result, new_result);
726            }
727            _ => unreachable!(),
728        }
729    }
730
731    #[test]
732    fn test_validate_table_options() {
733        let sql = r"create table if not exists demo(
734            host string,
735            ts timestamp,
736            cpu double default 0,
737            memory double,
738            TIME INDEX (ts),
739            PRIMARY KEY(host)
740      )
741      PARTITION ON COLUMNS (host) ()
742      engine=mito
743      with(ttl='7d', 'compaction.type'='world');
744";
745        let result =
746            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
747                .unwrap();
748        match &result[0] {
749            Statement::CreateTable(c) => {
750                assert_eq!(2, c.options.len());
751            }
752            _ => unreachable!(),
753        }
754
755        let sql = r"create table if not exists demo(
756            host string,
757            ts timestamp,
758            cpu double default 0,
759            memory double,
760            TIME INDEX (ts),
761            PRIMARY KEY(host)
762      )
763      PARTITION ON COLUMNS (host) ()
764      engine=mito
765      with(ttl='7d', hello='world');
766";
767        let result =
768            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
769        assert_matches!(result, Err(Error::InvalidTableOption { .. }));
770
771        // A whitelisted semantic key with an in-domain value is accepted.
772        let semantic = |with: &str| {
773            let sql =
774                format!("create table demo(host string, ts timestamp time index) with({with});");
775            ParserContext::create_with_dialect(&sql, &GreptimeDbDialect {}, ParseOptions::default())
776        };
777        assert!(semantic("'greptime.semantic.signal_type'='metric'").is_ok());
778        // An out-of-domain value is rejected.
779        assert_matches!(
780            semantic("'greptime.semantic.signal_type'='spans'"),
781            Err(Error::InvalidTableOption { .. })
782        );
783        // An unknown key under the semantic prefix is rejected.
784        assert_matches!(
785            semantic("'greptime.semantic.bogus'='x'"),
786            Err(Error::InvalidTableOption { .. })
787        );
788    }
789
790    #[test]
791    fn test_display_json2_type_hints_quotes_path_segments() {
792        let sql = r#"CREATE TABLE traces (
793            log_json_data JSON2 (
794                "service.name" STRING,
795                "a.b"."c" BIGINT,
796                a."b.c" STRING
797            ),
798            ts TIMESTAMP TIME INDEX
799        )"#;
800        let result =
801            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
802                .unwrap();
803
804        match &result[0] {
805            Statement::CreateTable(c) => {
806                let new_sql = format!("\n{}", c);
807                assert_eq!(
808                    r#"
809CREATE TABLE traces (
810  log_json_data JSON2(
811    "service.name" STRING,
812    "a.b"."c" BIGINT,
813    "a"."b.c" STRING
814  ),
815  ts TIMESTAMP NOT NULL,
816  TIME INDEX (ts)
817)
818ENGINE=mito
819"#,
820                    &new_sql
821                );
822
823                let new_result = ParserContext::create_with_dialect(
824                    &new_sql,
825                    &GreptimeDbDialect {},
826                    ParseOptions::default(),
827                )
828                .unwrap();
829                assert_eq!(result, new_result);
830            }
831            _ => unreachable!(),
832        }
833    }
834
835    #[test]
836    fn test_parse_json2_max_auto_expanded_paths_option() -> Result<()> {
837        let sql = r#"CREATE TABLE traces (
838            log_json_data JSON2 (
839                status_code BIGINT,
840                max_auto_expanded_paths = 1
841            ),
842            ts TIMESTAMP TIME INDEX
843        )"#;
844        let result = ParserContext::create_with_dialect(
845            sql,
846            &GreptimeDbDialect {},
847            ParseOptions::default(),
848        )?;
849        let Statement::CreateTable(create_table) = &result[0] else {
850            unreachable!()
851        };
852        let settings = create_table.columns[0]
853            .extensions
854            .build_json_settings()?
855            .unwrap();
856        assert_eq!(settings.max_auto_expanded_paths(), Some(1));
857        Ok(())
858    }
859
860    #[test]
861    fn test_display_json2_type_hints_quotes_numeric_segments() {
862        let sql = r#"CREATE TABLE traces (
863            log_json_data JSON2 (
864                "1abc" STRING,
865                a."2b" BIGINT
866            ),
867            ts TIMESTAMP TIME INDEX
868        )"#;
869        let result =
870            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
871                .unwrap();
872
873        match &result[0] {
874            Statement::CreateTable(c) => {
875                let new_sql = format!("\n{}", c);
876                assert_eq!(
877                    r#"
878CREATE TABLE traces (
879  log_json_data JSON2(
880    "1abc" STRING,
881    "a"."2b" BIGINT
882  ),
883  ts TIMESTAMP NOT NULL,
884  TIME INDEX (ts)
885)
886ENGINE=mito
887"#,
888                    &new_sql
889                );
890
891                let new_result = ParserContext::create_with_dialect(
892                    &new_sql,
893                    &GreptimeDbDialect {},
894                    ParseOptions::default(),
895                )
896                .unwrap();
897                assert_eq!(result, new_result);
898            }
899            _ => unreachable!(),
900        }
901    }
902
903    #[test]
904    fn test_json2_type_hint_rejects_default() {
905        let sql = r#"CREATE TABLE traces (
906            log_json_data JSON2 (
907                status_code BIGINT DEFAULT -5,
908                duration DOUBLE DEFAULT +1.5,
909                error BOOLEAN DEFAULT false,
910                message STRING DEFAULT 'unknown'
911            ),
912            ts TIMESTAMP TIME INDEX
913        )"#;
914        let err =
915            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
916                .unwrap_err();
917        assert!(err.to_string().contains("DEFAULT is not supported"));
918    }
919
920    #[test]
921    fn test_json2_type_hint_rejects_not_null() {
922        let sql = r#"CREATE TABLE traces (
923            log_json_data JSON2 (
924                status_code BIGINT NOT NULL DEFAULT NULL
925            ),
926            ts TIMESTAMP TIME INDEX
927        )"#;
928        let err =
929            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
930                .unwrap_err();
931        assert!(err.to_string().contains("NULL/NOT NULL is not supported"));
932    }
933
934    #[test]
935    fn test_set_json_settings_preserves_type_hint_sql_types() -> Result<()> {
936        let mut extensions = super::ColumnExtensions::default();
937        let settings = JsonSettings::try_new(
938            vec![
939                DatatypeJsonTypeHint {
940                    path: vec!["i".to_string()],
941                    data_type: ConcreteDataType::int64_datatype(),
942                    inverted_index: false,
943                },
944                DatatypeJsonTypeHint {
945                    path: vec!["f".to_string()],
946                    data_type: ConcreteDataType::float64_datatype(),
947                    inverted_index: false,
948                },
949                DatatypeJsonTypeHint {
950                    path: vec!["u".to_string()],
951                    data_type: ConcreteDataType::uint64_datatype(),
952                    inverted_index: false,
953                },
954                DatatypeJsonTypeHint {
955                    path: vec!["s".to_string()],
956                    data_type: ConcreteDataType::string_datatype(),
957                    inverted_index: false,
958                },
959                DatatypeJsonTypeHint {
960                    path: vec!["b".to_string()],
961                    data_type: ConcreteDataType::boolean_datatype(),
962                    inverted_index: false,
963                },
964            ],
965            None,
966        )?;
967        extensions.set_json_settings(settings)?;
968
969        assert_eq!(
970            extensions
971                .json2_options
972                .unwrap()
973                .type_hints
974                .iter()
975                .map(|hint| hint.data_type.to_string())
976                .collect::<Vec<_>>(),
977            vec!["BIGINT", "DOUBLE", "BIGINT UNSIGNED", "STRING", "BOOLEAN"]
978        );
979        Ok(())
980    }
981
982    #[test]
983    fn test_set_json_settings_rejects_unsupported_type_hint_type() -> Result<()> {
984        let err = JsonSettings::try_new(
985            vec![DatatypeJsonTypeHint {
986                path: vec!["u".to_string()],
987                data_type: ConcreteDataType::date_datatype(),
988                inverted_index: false,
989            }],
990            None,
991        )
992        .unwrap_err();
993
994        assert!(
995            err.to_string()
996                .contains("unsupported JSON2 type hint data type")
997        );
998        Ok(())
999    }
1000
1001    #[test]
1002    fn test_set_empty_json_settings_omits_json2_options() -> Result<()> {
1003        let mut extensions = ColumnExtensions::default();
1004        extensions.set_json_settings(JsonSettings::default())?;
1005        assert!(extensions.json2_options.is_none());
1006        Ok(())
1007    }
1008
1009    #[test]
1010    fn test_display_create_database() {
1011        let sql = r"create database test;";
1012        let stmts =
1013            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1014                .unwrap();
1015        assert_eq!(1, stmts.len());
1016        assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1017
1018        match &stmts[0] {
1019            Statement::CreateDatabase(set) => {
1020                let new_sql = format!("\n{}", set);
1021                assert_eq!(
1022                    r#"
1023CREATE DATABASE test"#,
1024                    &new_sql
1025                );
1026            }
1027            _ => {
1028                unreachable!();
1029            }
1030        }
1031
1032        let sql = r"create database if not exists test;";
1033        let stmts =
1034            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1035                .unwrap();
1036        assert_eq!(1, stmts.len());
1037        assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1038
1039        match &stmts[0] {
1040            Statement::CreateDatabase(set) => {
1041                let new_sql = format!("\n{}", set);
1042                assert_eq!(
1043                    r#"
1044CREATE DATABASE IF NOT EXISTS test"#,
1045                    &new_sql
1046                );
1047            }
1048            _ => {
1049                unreachable!();
1050            }
1051        }
1052
1053        let sql = r#"CREATE DATABASE IF NOT EXISTS test WITH (ttl='1h');"#;
1054        let stmts =
1055            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1056                .unwrap();
1057        assert_eq!(1, stmts.len());
1058        assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1059
1060        match &stmts[0] {
1061            Statement::CreateDatabase(set) => {
1062                let new_sql = format!("\n{}", set);
1063                assert_eq!(
1064                    r#"
1065CREATE DATABASE IF NOT EXISTS test
1066WITH(
1067  ttl = '1h'
1068)"#,
1069                    &new_sql
1070                );
1071            }
1072            _ => {
1073                unreachable!();
1074            }
1075        }
1076    }
1077
1078    #[test]
1079    fn test_display_create_table_like() {
1080        let sql = r"create table t2 like t1;";
1081        let stmts =
1082            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1083                .unwrap();
1084        assert_eq!(1, stmts.len());
1085        assert_matches!(&stmts[0], Statement::CreateTableLike { .. });
1086
1087        match &stmts[0] {
1088            Statement::CreateTableLike(create) => {
1089                let new_sql = format!("\n{}", create);
1090                assert_eq!(
1091                    r#"
1092CREATE TABLE t2 LIKE t1"#,
1093                    &new_sql
1094                );
1095            }
1096            _ => {
1097                unreachable!();
1098            }
1099        }
1100    }
1101
1102    #[test]
1103    fn test_display_create_external_table() {
1104        let sql = r#"CREATE EXTERNAL TABLE city (
1105            host string,
1106            ts timestamp,
1107            cpu float64 default 0,
1108            memory float64,
1109            TIME INDEX (ts),
1110            PRIMARY KEY(host)
1111) WITH (location='/var/data/city.csv', format='csv');"#;
1112        let stmts =
1113            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1114                .unwrap();
1115        assert_eq!(1, stmts.len());
1116        assert_matches!(&stmts[0], Statement::CreateExternalTable { .. });
1117
1118        match &stmts[0] {
1119            Statement::CreateExternalTable(create) => {
1120                let new_sql = format!("\n{}", create);
1121                assert_eq!(
1122                    r#"
1123CREATE EXTERNAL TABLE city (
1124  host STRING,
1125  ts TIMESTAMP,
1126  cpu DOUBLE DEFAULT 0,
1127  memory DOUBLE,
1128  TIME INDEX (ts),
1129  PRIMARY KEY (host)
1130)
1131ENGINE=file
1132WITH(
1133  format = 'csv',
1134  location = '/var/data/city.csv'
1135)"#,
1136                    &new_sql
1137                );
1138            }
1139            _ => {
1140                unreachable!();
1141            }
1142        }
1143    }
1144
1145    #[test]
1146    fn test_display_create_flow() {
1147        let sql = r"CREATE FLOW filter_numbers
1148            SINK TO out_num_cnt
1149            AS SELECT number FROM numbers_input where number > 10;";
1150        let result =
1151            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1152                .unwrap();
1153        assert_eq!(1, result.len());
1154
1155        match &result[0] {
1156            Statement::CreateFlow(c) => {
1157                let new_sql = format!("\n{}", c);
1158                assert_eq!(
1159                    r#"
1160CREATE FLOW filter_numbers
1161SINK TO out_num_cnt
1162AS SELECT number FROM numbers_input where number > 10"#,
1163                    &new_sql
1164                );
1165
1166                let new_result = ParserContext::create_with_dialect(
1167                    &new_sql,
1168                    &GreptimeDbDialect {},
1169                    ParseOptions::default(),
1170                )
1171                .unwrap();
1172                assert_eq!(result, new_result);
1173            }
1174            _ => unreachable!(),
1175        }
1176    }
1177}