Skip to main content

sql/statements/
create.rs

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