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