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, Value as SqlValue};
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
359#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut)]
360pub struct PartitionEntry {
361    pub name: Ident,
362    pub value_list: Vec<SqlValue>,
363}
364
365impl Display for PartitionEntry {
366    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
367        write!(
368            f,
369            "PARTITION {} VALUES LESS THAN ({})",
370            self.name,
371            format_list_comma!(self.value_list),
372        )
373    }
374}
375
376impl Display for Partitions {
377    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
378        if !self.column_list.is_empty() {
379            write!(
380                f,
381                "PARTITION ON COLUMNS ({}) (\n{}\n)",
382                format_list_comma!(self.column_list),
383                format_list_indent!(self.exprs),
384            )?;
385        }
386        Ok(())
387    }
388}
389
390impl Display for CreateTable {
391    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
392        write!(f, "CREATE ")?;
393        if self.engine == FILE_ENGINE {
394            write!(f, "EXTERNAL ")?;
395        }
396        write!(f, "TABLE ")?;
397        if self.if_not_exists {
398            write!(f, "IF NOT EXISTS ")?;
399        }
400        writeln!(f, "{} (", &self.name)?;
401        writeln!(f, "{},", format_list_indent!(self.columns))?;
402        writeln!(f, "{}", format_table_constraint(&self.constraints))?;
403        writeln!(f, ")")?;
404        if let Some(partitions) = &self.partitions {
405            writeln!(f, "{partitions}")?;
406        }
407        writeln!(f, "ENGINE={}", &self.engine)?;
408        if !self.options.is_empty() {
409            let options = self.options.kv_pairs();
410            write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
411        }
412        Ok(())
413    }
414}
415
416#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
417pub struct CreateDatabase {
418    pub name: ObjectName,
419    /// Create if not exists
420    pub if_not_exists: bool,
421    pub options: OptionMap,
422}
423
424impl CreateDatabase {
425    /// Creates a statement for `CREATE DATABASE`
426    pub fn new(name: ObjectName, if_not_exists: bool, options: OptionMap) -> Self {
427        Self {
428            name,
429            if_not_exists,
430            options,
431        }
432    }
433}
434
435impl Display for CreateDatabase {
436    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
437        write!(f, "CREATE DATABASE ")?;
438        if self.if_not_exists {
439            write!(f, "IF NOT EXISTS ")?;
440        }
441        write!(f, "{}", &self.name)?;
442        if !self.options.is_empty() {
443            let options = self.options.kv_pairs();
444            write!(f, "\nWITH(\n{}\n)", format_list_indent!(options))?;
445        }
446        Ok(())
447    }
448}
449
450#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
451pub struct CreateExternalTable {
452    /// Table name
453    pub name: ObjectName,
454    pub columns: Vec<Column>,
455    pub constraints: Vec<TableConstraint>,
456    /// Table options in `WITH`. All keys are lowercase.
457    pub options: OptionMap,
458    pub if_not_exists: bool,
459    pub engine: String,
460}
461
462impl Display for CreateExternalTable {
463    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
464        write!(f, "CREATE EXTERNAL TABLE ")?;
465        if self.if_not_exists {
466            write!(f, "IF NOT EXISTS ")?;
467        }
468        writeln!(f, "{} (", &self.name)?;
469        writeln!(f, "{},", format_list_indent!(self.columns))?;
470        writeln!(f, "{}", format_table_constraint(&self.constraints))?;
471        writeln!(f, ")")?;
472        writeln!(f, "ENGINE={}", &self.engine)?;
473        if !self.options.is_empty() {
474            let options = self.options.kv_pairs();
475            write!(f, "WITH(\n{}\n)", format_list_indent!(options))?;
476        }
477        Ok(())
478    }
479}
480
481#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
482pub struct CreateTableLike {
483    /// Table name
484    pub table_name: ObjectName,
485    /// The table that is designated to be imitated by `Like`
486    pub source_name: ObjectName,
487}
488
489impl Display for CreateTableLike {
490    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
491        let table_name = &self.table_name;
492        let source_name = &self.source_name;
493        write!(f, r#"CREATE TABLE {table_name} LIKE {source_name}"#)
494    }
495}
496
497#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
498pub struct CreateFlow {
499    /// Flow name
500    pub flow_name: ObjectName,
501    /// Output (sink) table name
502    pub sink_table_name: ObjectName,
503    /// Whether to replace existing task
504    pub or_replace: bool,
505    /// Create if not exist
506    pub if_not_exists: bool,
507    /// `EXPIRE AFTER`
508    /// Duration in second as `i64`
509    pub expire_after: Option<i64>,
510    /// Duration for flow evaluation interval
511    /// Duration in seconds as `i64`
512    /// If not set, flow will be evaluated based on time window size and other args.
513    pub eval_interval: Option<i64>,
514    /// Phase offset of the flow evaluation schedule within `eval_interval`.
515    /// Duration in seconds as `i64`.
516    /// Must be in range `[0, eval_interval)`. Only legal together with
517    /// `eval_interval`. A value of zero (the default) means the schedule is
518    /// anchored to the Unix epoch, i.e. phases at `k * eval_interval`.
519    pub eval_offset: Option<i64>,
520    /// Comment string
521    pub comment: Option<String>,
522    /// Flow creation options from `WITH (...)`
523    pub flow_options: OptionMap,
524    /// SQL statement
525    pub query: Box<SqlOrTql>,
526}
527
528/// Either a sql query or a tql query
529#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
530pub enum SqlOrTql {
531    Sql(GtQuery, String),
532    Tql(Tql, String),
533}
534
535impl std::fmt::Display for SqlOrTql {
536    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
537        match self {
538            Self::Sql(_, s) => write!(f, "{}", s),
539            Self::Tql(_, s) => write!(f, "{}", s),
540        }
541    }
542}
543
544impl SqlOrTql {
545    pub fn try_from_statement(
546        value: Statement,
547        original_query: &str,
548    ) -> std::result::Result<Self, crate::error::Error> {
549        match value {
550            Statement::Query(query) => Ok(Self::Sql(*query, original_query.to_string())),
551            Statement::Tql(tql) => Ok(Self::Tql(tql, original_query.to_string())),
552            _ => InvalidFlowQuerySnafu {
553                reason: format!("Expect either sql query or promql query, found {:?}", value),
554            }
555            .fail(),
556        }
557    }
558}
559
560impl Display for CreateFlow {
561    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
562        write!(f, "CREATE ")?;
563        if self.or_replace {
564            write!(f, "OR REPLACE ")?;
565        }
566        write!(f, "FLOW ")?;
567        if self.if_not_exists {
568            write!(f, "IF NOT EXISTS ")?;
569        }
570        writeln!(f, "{}", &self.flow_name)?;
571        writeln!(f, "SINK TO {}", &self.sink_table_name)?;
572        if let Some(expire_after) = &self.expire_after {
573            writeln!(f, "EXPIRE AFTER '{} s'", expire_after)?;
574        }
575        if let Some(eval_interval) = &self.eval_interval {
576            writeln!(f, "EVAL INTERVAL '{} s'", eval_interval)?;
577        }
578        // Canonical display: omit a zero offset (equivalent to the default
579        // epoch-anchored schedule). Non-zero offsets are always emitted.
580        if let Some(eval_offset) = &self.eval_offset
581            && *eval_offset != 0
582        {
583            writeln!(f, "EVAL OFFSET '{} s'", eval_offset)?;
584        }
585        if let Some(comment) = &self.comment {
586            writeln!(f, "COMMENT '{}'", comment)?;
587        }
588        if !self.flow_options.is_empty() {
589            let options = self.flow_options.kv_pairs();
590            writeln!(f, "WITH ({})", format_list_comma!(options))?;
591        }
592        write!(f, "AS {}", &self.query)
593    }
594}
595
596/// Create SQL view statement.
597#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)]
598pub struct CreateView {
599    /// View name
600    pub name: ObjectName,
601    /// An optional list of names to be used for columns of the view
602    pub columns: Vec<Ident>,
603    /// The clause after `As` that defines the VIEW.
604    /// Can only be either [Statement::Query] or [Statement::Tql].
605    pub query: Box<Statement>,
606    /// Whether to replace existing VIEW
607    pub or_replace: bool,
608    /// Create VIEW only when it doesn't exists
609    pub if_not_exists: bool,
610}
611
612impl Display for CreateView {
613    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
614        write!(f, "CREATE ")?;
615        if self.or_replace {
616            write!(f, "OR REPLACE ")?;
617        }
618        write!(f, "VIEW ")?;
619        if self.if_not_exists {
620            write!(f, "IF NOT EXISTS ")?;
621        }
622        write!(f, "{} ", &self.name)?;
623        if !self.columns.is_empty() {
624            write!(f, "({}) ", format_list_comma!(self.columns))?;
625        }
626        write!(f, "AS {}", &self.query)
627    }
628}
629
630#[cfg(test)]
631mod tests {
632    use std::assert_matches;
633
634    use datatypes::json::{JsonSettings, JsonTypeHint as DatatypeJsonTypeHint};
635    use datatypes::prelude::ConcreteDataType;
636
637    use super::*;
638    use crate::dialect::GreptimeDbDialect;
639    use crate::error::Error;
640    use crate::parser::{ParseOptions, ParserContext};
641    use crate::statements::statement::Statement;
642
643    #[test]
644    fn test_display_create_table() {
645        let sql = r"create table if not exists demo(
646                             host string,
647                             ts timestamp,
648                             cpu double default 0,
649                             memory double,
650                             TIME INDEX (ts),
651                             PRIMARY KEY(host)
652                       )
653                       PARTITION ON COLUMNS (host) (
654                            host = 'a',
655                            host > 'a',
656                       )
657                       engine=mito
658                       with(ttl='7d', storage='File');
659         ";
660        let result =
661            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
662                .unwrap();
663        assert_eq!(1, result.len());
664
665        match &result[0] {
666            Statement::CreateTable(c) => {
667                let new_sql = format!("\n{}", c);
668                assert_eq!(
669                    r#"
670CREATE TABLE IF NOT EXISTS demo (
671  host STRING,
672  ts TIMESTAMP,
673  cpu DOUBLE DEFAULT 0,
674  memory DOUBLE,
675  TIME INDEX (ts),
676  PRIMARY KEY (host)
677)
678PARTITION ON COLUMNS (host) (
679  host = 'a',
680  host > 'a'
681)
682ENGINE=mito
683WITH(
684  storage = 'File',
685  ttl = '7d'
686)"#,
687                    &new_sql
688                );
689
690                let new_result = ParserContext::create_with_dialect(
691                    &new_sql,
692                    &GreptimeDbDialect {},
693                    ParseOptions::default(),
694                )
695                .unwrap();
696                assert_eq!(result, new_result);
697            }
698            _ => unreachable!(),
699        }
700    }
701
702    #[test]
703    fn test_display_empty_partition_column() {
704        let sql = r"create table if not exists demo(
705            host string,
706            ts timestamp,
707            cpu double default 0,
708            memory double,
709            TIME INDEX (ts),
710            PRIMARY KEY(ts, host)
711            );
712        ";
713        let result =
714            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
715                .unwrap();
716        assert_eq!(1, result.len());
717
718        match &result[0] {
719            Statement::CreateTable(c) => {
720                let new_sql = format!("\n{}", c);
721                assert_eq!(
722                    r#"
723CREATE TABLE IF NOT EXISTS demo (
724  host STRING,
725  ts TIMESTAMP,
726  cpu DOUBLE DEFAULT 0,
727  memory DOUBLE,
728  TIME INDEX (ts),
729  PRIMARY KEY (ts, host)
730)
731ENGINE=mito
732"#,
733                    &new_sql
734                );
735
736                let new_result = ParserContext::create_with_dialect(
737                    &new_sql,
738                    &GreptimeDbDialect {},
739                    ParseOptions::default(),
740                )
741                .unwrap();
742                assert_eq!(result, new_result);
743            }
744            _ => unreachable!(),
745        }
746    }
747
748    #[test]
749    fn test_validate_table_options() {
750        let sql = r"create table if not exists demo(
751            host string,
752            ts timestamp,
753            cpu double default 0,
754            memory double,
755            TIME INDEX (ts),
756            PRIMARY KEY(host)
757      )
758      PARTITION ON COLUMNS (host) ()
759      engine=mito
760      with(ttl='7d', 'compaction.type'='world');
761";
762        let result =
763            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
764                .unwrap();
765        match &result[0] {
766            Statement::CreateTable(c) => {
767                assert_eq!(2, c.options.len());
768            }
769            _ => unreachable!(),
770        }
771
772        let sql = r"create table if not exists demo(
773            host string,
774            ts timestamp,
775            cpu double default 0,
776            memory double,
777            TIME INDEX (ts),
778            PRIMARY KEY(host)
779      )
780      PARTITION ON COLUMNS (host) ()
781      engine=mito
782      with(ttl='7d', hello='world');
783";
784        let result =
785            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
786        assert_matches!(result, Err(Error::InvalidTableOption { .. }));
787
788        // A whitelisted semantic key with an in-domain value is accepted.
789        let semantic = |with: &str| {
790            let sql =
791                format!("create table demo(host string, ts timestamp time index) with({with});");
792            ParserContext::create_with_dialect(&sql, &GreptimeDbDialect {}, ParseOptions::default())
793        };
794        assert!(semantic("'greptime.semantic.signal_type'='metric'").is_ok());
795        // An out-of-domain value is rejected.
796        assert_matches!(
797            semantic("'greptime.semantic.signal_type'='spans'"),
798            Err(Error::InvalidTableOption { .. })
799        );
800        // An unknown key under the semantic prefix is rejected.
801        assert_matches!(
802            semantic("'greptime.semantic.bogus'='x'"),
803            Err(Error::InvalidTableOption { .. })
804        );
805    }
806
807    #[test]
808    fn test_display_json2_type_hints_quotes_path_segments() {
809        let sql = r#"CREATE 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 TIME INDEX
816        )"#;
817        let result =
818            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
819                .unwrap();
820
821        match &result[0] {
822            Statement::CreateTable(c) => {
823                let new_sql = format!("\n{}", c);
824                assert_eq!(
825                    r#"
826CREATE TABLE traces (
827  log_json_data JSON2(
828    "service.name" STRING,
829    "a.b"."c" BIGINT,
830    "a"."b.c" STRING
831  ),
832  ts TIMESTAMP NOT NULL,
833  TIME INDEX (ts)
834)
835ENGINE=mito
836"#,
837                    &new_sql
838                );
839
840                let new_result = ParserContext::create_with_dialect(
841                    &new_sql,
842                    &GreptimeDbDialect {},
843                    ParseOptions::default(),
844                )
845                .unwrap();
846                assert_eq!(result, new_result);
847            }
848            _ => unreachable!(),
849        }
850    }
851
852    #[test]
853    fn test_parse_json2_max_auto_expanded_paths_option() -> Result<()> {
854        let sql = r#"CREATE TABLE traces (
855            log_json_data JSON2 (
856                status_code BIGINT,
857                max_auto_expanded_paths = 1
858            ),
859            ts TIMESTAMP TIME INDEX
860        )"#;
861        let result = ParserContext::create_with_dialect(
862            sql,
863            &GreptimeDbDialect {},
864            ParseOptions::default(),
865        )?;
866        let Statement::CreateTable(create_table) = &result[0] else {
867            unreachable!()
868        };
869        let settings = create_table.columns[0]
870            .extensions
871            .build_json_settings()?
872            .unwrap();
873        assert_eq!(settings.max_auto_expanded_paths(), Some(1));
874        Ok(())
875    }
876
877    #[test]
878    fn test_display_json2_type_hints_quotes_numeric_segments() {
879        let sql = r#"CREATE TABLE traces (
880            log_json_data JSON2 (
881                "1abc" STRING,
882                a."2b" BIGINT
883            ),
884            ts TIMESTAMP TIME INDEX
885        )"#;
886        let result =
887            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
888                .unwrap();
889
890        match &result[0] {
891            Statement::CreateTable(c) => {
892                let new_sql = format!("\n{}", c);
893                assert_eq!(
894                    r#"
895CREATE TABLE traces (
896  log_json_data JSON2(
897    "1abc" STRING,
898    "a"."2b" BIGINT
899  ),
900  ts TIMESTAMP NOT NULL,
901  TIME INDEX (ts)
902)
903ENGINE=mito
904"#,
905                    &new_sql
906                );
907
908                let new_result = ParserContext::create_with_dialect(
909                    &new_sql,
910                    &GreptimeDbDialect {},
911                    ParseOptions::default(),
912                )
913                .unwrap();
914                assert_eq!(result, new_result);
915            }
916            _ => unreachable!(),
917        }
918    }
919
920    #[test]
921    fn test_json2_type_hint_rejects_default() {
922        let sql = r#"CREATE TABLE traces (
923            log_json_data JSON2 (
924                status_code BIGINT DEFAULT -5,
925                duration DOUBLE DEFAULT +1.5,
926                error BOOLEAN DEFAULT false,
927                message STRING DEFAULT 'unknown'
928            ),
929            ts TIMESTAMP TIME INDEX
930        )"#;
931        let err =
932            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
933                .unwrap_err();
934        assert!(err.to_string().contains("DEFAULT is not supported"));
935    }
936
937    #[test]
938    fn test_json2_type_hint_rejects_not_null() {
939        let sql = r#"CREATE TABLE traces (
940            log_json_data JSON2 (
941                status_code BIGINT NOT NULL DEFAULT NULL
942            ),
943            ts TIMESTAMP TIME INDEX
944        )"#;
945        let err =
946            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
947                .unwrap_err();
948        assert!(err.to_string().contains("NULL/NOT NULL is not supported"));
949    }
950
951    #[test]
952    fn test_set_json_settings_preserves_type_hint_sql_types() -> Result<()> {
953        let mut extensions = super::ColumnExtensions::default();
954        let settings = JsonSettings::try_new(
955            vec![
956                DatatypeJsonTypeHint {
957                    path: vec!["i".to_string()],
958                    data_type: ConcreteDataType::int64_datatype(),
959                    inverted_index: false,
960                },
961                DatatypeJsonTypeHint {
962                    path: vec!["f".to_string()],
963                    data_type: ConcreteDataType::float64_datatype(),
964                    inverted_index: false,
965                },
966                DatatypeJsonTypeHint {
967                    path: vec!["u".to_string()],
968                    data_type: ConcreteDataType::uint64_datatype(),
969                    inverted_index: false,
970                },
971                DatatypeJsonTypeHint {
972                    path: vec!["s".to_string()],
973                    data_type: ConcreteDataType::string_datatype(),
974                    inverted_index: false,
975                },
976                DatatypeJsonTypeHint {
977                    path: vec!["b".to_string()],
978                    data_type: ConcreteDataType::boolean_datatype(),
979                    inverted_index: false,
980                },
981            ],
982            None,
983        )?;
984        extensions.set_json_settings(settings)?;
985
986        assert_eq!(
987            extensions
988                .json2_options
989                .unwrap()
990                .type_hints
991                .iter()
992                .map(|hint| hint.data_type.to_string())
993                .collect::<Vec<_>>(),
994            vec!["BIGINT", "DOUBLE", "BIGINT UNSIGNED", "STRING", "BOOLEAN"]
995        );
996        Ok(())
997    }
998
999    #[test]
1000    fn test_set_json_settings_rejects_unsupported_type_hint_type() -> Result<()> {
1001        let err = JsonSettings::try_new(
1002            vec![DatatypeJsonTypeHint {
1003                path: vec!["u".to_string()],
1004                data_type: ConcreteDataType::date_datatype(),
1005                inverted_index: false,
1006            }],
1007            None,
1008        )
1009        .unwrap_err();
1010
1011        assert!(
1012            err.to_string()
1013                .contains("unsupported JSON2 type hint data type")
1014        );
1015        Ok(())
1016    }
1017
1018    #[test]
1019    fn test_set_empty_json_settings_omits_json2_options() -> Result<()> {
1020        let mut extensions = ColumnExtensions::default();
1021        extensions.set_json_settings(JsonSettings::default())?;
1022        assert!(extensions.json2_options.is_none());
1023        Ok(())
1024    }
1025
1026    #[test]
1027    fn test_display_create_database() {
1028        let sql = r"create database test;";
1029        let stmts =
1030            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1031                .unwrap();
1032        assert_eq!(1, stmts.len());
1033        assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1034
1035        match &stmts[0] {
1036            Statement::CreateDatabase(set) => {
1037                let new_sql = format!("\n{}", set);
1038                assert_eq!(
1039                    r#"
1040CREATE DATABASE test"#,
1041                    &new_sql
1042                );
1043            }
1044            _ => {
1045                unreachable!();
1046            }
1047        }
1048
1049        let sql = r"create database if not exists test;";
1050        let stmts =
1051            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1052                .unwrap();
1053        assert_eq!(1, stmts.len());
1054        assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1055
1056        match &stmts[0] {
1057            Statement::CreateDatabase(set) => {
1058                let new_sql = format!("\n{}", set);
1059                assert_eq!(
1060                    r#"
1061CREATE DATABASE IF NOT EXISTS test"#,
1062                    &new_sql
1063                );
1064            }
1065            _ => {
1066                unreachable!();
1067            }
1068        }
1069
1070        let sql = r#"CREATE DATABASE IF NOT EXISTS test WITH (ttl='1h');"#;
1071        let stmts =
1072            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1073                .unwrap();
1074        assert_eq!(1, stmts.len());
1075        assert_matches!(&stmts[0], Statement::CreateDatabase { .. });
1076
1077        match &stmts[0] {
1078            Statement::CreateDatabase(set) => {
1079                let new_sql = format!("\n{}", set);
1080                assert_eq!(
1081                    r#"
1082CREATE DATABASE IF NOT EXISTS test
1083WITH(
1084  ttl = '1h'
1085)"#,
1086                    &new_sql
1087                );
1088            }
1089            _ => {
1090                unreachable!();
1091            }
1092        }
1093    }
1094
1095    #[test]
1096    fn test_display_create_table_like() {
1097        let sql = r"create table t2 like t1;";
1098        let stmts =
1099            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1100                .unwrap();
1101        assert_eq!(1, stmts.len());
1102        assert_matches!(&stmts[0], Statement::CreateTableLike { .. });
1103
1104        match &stmts[0] {
1105            Statement::CreateTableLike(create) => {
1106                let new_sql = format!("\n{}", create);
1107                assert_eq!(
1108                    r#"
1109CREATE TABLE t2 LIKE t1"#,
1110                    &new_sql
1111                );
1112            }
1113            _ => {
1114                unreachable!();
1115            }
1116        }
1117    }
1118
1119    #[test]
1120    fn test_display_create_external_table() {
1121        let sql = r#"CREATE EXTERNAL TABLE city (
1122            host string,
1123            ts timestamp,
1124            cpu float64 default 0,
1125            memory float64,
1126            TIME INDEX (ts),
1127            PRIMARY KEY(host)
1128) WITH (location='/var/data/city.csv', format='csv');"#;
1129        let stmts =
1130            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1131                .unwrap();
1132        assert_eq!(1, stmts.len());
1133        assert_matches!(&stmts[0], Statement::CreateExternalTable { .. });
1134
1135        match &stmts[0] {
1136            Statement::CreateExternalTable(create) => {
1137                let new_sql = format!("\n{}", create);
1138                assert_eq!(
1139                    r#"
1140CREATE EXTERNAL TABLE city (
1141  host STRING,
1142  ts TIMESTAMP,
1143  cpu DOUBLE DEFAULT 0,
1144  memory DOUBLE,
1145  TIME INDEX (ts),
1146  PRIMARY KEY (host)
1147)
1148ENGINE=file
1149WITH(
1150  format = 'csv',
1151  location = '/var/data/city.csv'
1152)"#,
1153                    &new_sql
1154                );
1155            }
1156            _ => {
1157                unreachable!();
1158            }
1159        }
1160    }
1161
1162    #[test]
1163    fn test_display_create_flow() {
1164        let sql = r"CREATE FLOW filter_numbers
1165            SINK TO out_num_cnt
1166            AS SELECT number FROM numbers_input where number > 10;";
1167        let result =
1168            ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1169                .unwrap();
1170        assert_eq!(1, result.len());
1171
1172        match &result[0] {
1173            Statement::CreateFlow(c) => {
1174                let new_sql = format!("\n{}", c);
1175                assert_eq!(
1176                    r#"
1177CREATE FLOW filter_numbers
1178SINK TO out_num_cnt
1179AS SELECT number FROM numbers_input where number > 10"#,
1180                    &new_sql
1181                );
1182
1183                let new_result = ParserContext::create_with_dialect(
1184                    &new_sql,
1185                    &GreptimeDbDialect {},
1186                    ParseOptions::default(),
1187                )
1188                .unwrap();
1189                assert_eq!(result, new_result);
1190            }
1191            _ => unreachable!(),
1192        }
1193    }
1194}