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