1#[cfg(feature = "enterprise")]
16pub mod trigger;
17
18use std::collections::{HashMap, HashSet};
19
20use api::helper::ColumnDataTypeWrapper;
21use api::v1::alter_database_expr::Kind as AlterDatabaseKind;
22use api::v1::alter_table_expr::Kind as AlterTableKind;
23use api::v1::column_def::{options_from_column_schema, try_as_column_schema};
24use api::v1::{
25 AddColumn, AddColumns, AlterDatabaseExpr, AlterTableExpr, Analyzer, ColumnDataType,
26 ColumnDataTypeExtension, CreateFlowExpr, CreateTableExpr, CreateViewExpr, DropColumn,
27 DropColumns, DropDefaults, ExpireAfter, FulltextBackend as PbFulltextBackend,
28 JsonSettings as PbJsonSettings, JsonTypeHint as PbJsonTypeHint, ModifyColumnType,
29 ModifyColumnTypes, RenameTable, SemanticType, SetDatabaseOptions, SetDefaults, SetFulltext,
30 SetIndex, SetIndexes, SetInverted, SetJsonSettings, SetSkipping, SetTableOptions,
31 SkippingIndexType as PbSkippingIndexType, TableName, UnsetDatabaseOptions, UnsetFulltext,
32 UnsetIndex, UnsetIndexes, UnsetInverted, UnsetSkipping, UnsetTableOptions, set_index,
33 unset_index,
34};
35use common_datasource::object_store::LocalFileAccess;
36use common_error::ext::BoxedError;
37use common_grpc_expr::util::ColumnExpr;
38use common_time::Timezone;
39use datafusion::sql::planner::object_name_to_table_reference;
40use datatypes::json::JsonSettings;
41use datatypes::prelude::ConcreteDataType;
42use datatypes::schema::{
43 COLUMN_FULLTEXT_OPT_KEY_ANALYZER, COLUMN_FULLTEXT_OPT_KEY_BACKEND,
44 COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE, COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE,
45 COLUMN_FULLTEXT_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE,
46 COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY, COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE, COMMENT_KEY,
47 ColumnDefaultConstraint, ColumnSchema, FulltextAnalyzer, FulltextBackend, Schema,
48 SkippingIndexType,
49};
50use file_engine::FileOptions;
51use query::sql::{
52 check_file_to_table_schema_compatibility, file_column_schemas_to_table,
53 infer_file_table_schema, prepare_file_table_files,
54};
55use session::context::QueryContextRef;
56use session::table_name::table_idents_to_full_name;
57use snafu::{OptionExt, ResultExt, ensure};
58use sql::ast::{
59 ColumnDef, ColumnOption, ColumnOptionDef, Expr, Ident, ObjectName, ObjectNamePartExt,
60};
61use sql::dialect::GreptimeDbDialect;
62use sql::parser::ParserContext;
63use sql::statements::alter::{
64 AlterDatabase, AlterDatabaseOperation, AlterTable, AlterTableOperation,
65};
66use sql::statements::create::{
67 Column as SqlColumn, ColumnExtensions, CreateExternalTable, CreateFlow, CreateTable,
68 CreateView, TableConstraint,
69};
70use sql::statements::{
71 OptionMap, column_to_schema, concrete_data_type_to_sql_data_type, sql_col_to_grpc_col_def,
72 sql_data_type_to_concrete_data_type, value_to_sql_value,
73};
74use sql::util::extract_tables_from_query;
75use store_api::mito_engine_options::{COMPACTION_OVERRIDE, COMPACTION_TYPE};
76use table::requests::{FILE_TABLE_META_KEY, TableOptions};
77use table::table_reference::TableReference;
78#[cfg(feature = "enterprise")]
79pub use trigger::to_create_trigger_task_expr;
80
81use crate::error::{
82 BuildCreateExprOnInsertionSnafu, ColumnDataTypeSnafu, ConvertColumnDefaultConstraintSnafu,
83 ConvertIdentifierSnafu, EncodeJsonSnafu, ExternalSnafu, FindNewColumnsOnInsertionSnafu,
84 IllegalPrimaryKeysDefSnafu, InferFileTableSchemaSnafu, InvalidColumnDefSnafu,
85 InvalidFlowNameSnafu, InvalidSqlSnafu, NotSupportedSnafu, ParseSqlSnafu, ParseSqlValueSnafu,
86 PrepareFileTableSnafu, Result, SchemaIncompatibleSnafu, UnrecognizedTableOptionSnafu,
87};
88
89pub fn create_table_expr_by_column_schemas(
90 table_name: &TableReference<'_>,
91 column_schemas: &[api::v1::ColumnSchema],
92 engine: &str,
93 desc: Option<&str>,
94) -> Result<CreateTableExpr> {
95 let column_exprs = ColumnExpr::from_column_schemas(column_schemas);
96 let expr = common_grpc_expr::util::build_create_table_expr(
97 None,
98 table_name,
99 column_exprs,
100 engine,
101 desc.unwrap_or("Created on insertion"),
102 )
103 .context(BuildCreateExprOnInsertionSnafu)?;
104
105 validate_create_expr(&expr)?;
106 Ok(expr)
107}
108
109pub fn extract_add_columns_expr(
110 schema: &Schema,
111 column_exprs: Vec<ColumnExpr>,
112) -> Result<Option<AddColumns>> {
113 let add_columns = common_grpc_expr::util::extract_new_columns(schema, column_exprs)
114 .context(FindNewColumnsOnInsertionSnafu)?;
115 if let Some(add_columns) = &add_columns {
116 validate_add_columns_expr(add_columns)?;
117 }
118 Ok(add_columns)
119}
120
121pub(crate) async fn create_external_expr(
145 create: CreateExternalTable,
146 query_ctx: &QueryContextRef,
147 local_file_access: &LocalFileAccess,
148) -> Result<CreateTableExpr> {
149 let (catalog_name, schema_name, table_name) =
150 table_idents_to_full_name(&create.name, query_ctx)
151 .map_err(BoxedError::new)
152 .context(ExternalSnafu)?;
153
154 let mut table_options = create.options.into_map();
155
156 let (object_store, files) = prepare_file_table_files(&table_options, local_file_access)
157 .await
158 .context(PrepareFileTableSnafu)?;
159
160 let file_column_schemas = infer_file_table_schema(&object_store, &files, &table_options)
161 .await
162 .context(InferFileTableSchemaSnafu)?
163 .column_schemas()
164 .to_vec();
165
166 let (time_index, primary_keys, table_column_schemas) = if !create.columns.is_empty() {
167 let time_index = find_time_index(&create.constraints)?;
169 let primary_keys = find_primary_keys(&create.columns, &create.constraints)?;
170 let column_schemas =
171 columns_to_column_schemas(&create.columns, &time_index, Some(&query_ctx.timezone()))?;
172 (time_index, primary_keys, column_schemas)
173 } else {
174 let (column_schemas, time_index) = file_column_schemas_to_table(&file_column_schemas);
176 let primary_keys = vec![];
177 (time_index, primary_keys, column_schemas)
178 };
179
180 check_file_to_table_schema_compatibility(&file_column_schemas, &table_column_schemas)
181 .context(SchemaIncompatibleSnafu)?;
182
183 let meta = FileOptions {
184 files,
185 file_column_schemas,
186 };
187 table_options.insert(
188 FILE_TABLE_META_KEY.to_string(),
189 serde_json::to_string(&meta).context(EncodeJsonSnafu)?,
190 );
191
192 let column_defs = column_schemas_to_defs(table_column_schemas, &primary_keys)?;
193 let expr = CreateTableExpr {
194 catalog_name,
195 schema_name,
196 table_name,
197 desc: String::default(),
198 column_defs,
199 time_index,
200 primary_keys,
201 create_if_not_exists: create.if_not_exists,
202 table_options,
203 table_id: None,
204 engine: create.engine.clone(),
205 };
206
207 Ok(expr)
208}
209
210pub fn create_to_expr(
212 create: &CreateTable,
213 query_ctx: &QueryContextRef,
214) -> Result<CreateTableExpr> {
215 let (catalog_name, schema_name, table_name) =
216 table_idents_to_full_name(&create.name, query_ctx)
217 .map_err(BoxedError::new)
218 .context(ExternalSnafu)?;
219
220 let time_index = find_time_index(&create.constraints)?;
221 let mut table_options = HashMap::from(
222 &TableOptions::try_from_iter(create.options.to_str_map())
223 .context(UnrecognizedTableOptionSnafu)?,
224 );
225
226 if table_options.contains_key(COMPACTION_TYPE) {
227 table_options.insert(COMPACTION_OVERRIDE.to_string(), "true".to_string());
228 }
229
230 let primary_keys = find_primary_keys(&create.columns, &create.constraints)?;
231
232 let expr = CreateTableExpr {
233 catalog_name,
234 schema_name,
235 table_name,
236 desc: String::default(),
237 column_defs: columns_to_expr(
238 &create.columns,
239 &time_index,
240 &primary_keys,
241 Some(&query_ctx.timezone()),
242 )?,
243 time_index,
244 primary_keys,
245 create_if_not_exists: create.if_not_exists,
246 table_options,
247 table_id: None,
248 engine: create.engine.clone(),
249 };
250
251 validate_create_expr(&expr)?;
252 Ok(expr)
253}
254
255pub fn expr_to_create(expr: &CreateTableExpr, quote_style: Option<char>) -> Result<CreateTable> {
263 let quote_style = quote_style.unwrap_or('`');
264
265 let table_name = ObjectName(vec![sql::ast::ObjectNamePart::Identifier(
267 sql::ast::Ident::with_quote(quote_style, &expr.table_name),
268 )]);
269
270 let mut columns = Vec::with_capacity(expr.column_defs.len());
272 for column_def in &expr.column_defs {
273 let column_schema = try_as_column_schema(column_def).context(InvalidColumnDefSnafu {
274 column: &column_def.name,
275 })?;
276
277 let mut options = Vec::new();
278
279 if column_def.is_nullable {
281 options.push(ColumnOptionDef {
282 name: None,
283 option: ColumnOption::Null,
284 });
285 } else {
286 options.push(ColumnOptionDef {
287 name: None,
288 option: ColumnOption::NotNull,
289 });
290 }
291
292 if let Some(default_constraint) = column_schema.default_constraint() {
294 let expr = match default_constraint {
295 ColumnDefaultConstraint::Value(v) => {
296 Expr::Value(value_to_sql_value(v).context(ParseSqlValueSnafu)?.into())
297 }
298 ColumnDefaultConstraint::Function(func_expr) => {
299 ParserContext::parse_function(func_expr, &GreptimeDbDialect {})
300 .context(ParseSqlSnafu)?
301 }
302 };
303 options.push(ColumnOptionDef {
304 name: None,
305 option: ColumnOption::Default(expr),
306 });
307 }
308
309 if !column_def.comment.is_empty() {
311 options.push(ColumnOptionDef {
312 name: None,
313 option: ColumnOption::Comment(column_def.comment.clone()),
314 });
315 }
316
317 let mut extensions = ColumnExtensions::default();
322
323 if let Ok(Some(opt)) = column_schema.fulltext_options()
325 && opt.enable
326 {
327 let mut map = HashMap::from([
328 (
329 COLUMN_FULLTEXT_OPT_KEY_ANALYZER.to_string(),
330 opt.analyzer.to_string(),
331 ),
332 (
333 COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE.to_string(),
334 opt.case_sensitive.to_string(),
335 ),
336 (
337 COLUMN_FULLTEXT_OPT_KEY_BACKEND.to_string(),
338 opt.backend.to_string(),
339 ),
340 ]);
341 if opt.backend == FulltextBackend::Bloom {
342 map.insert(
343 COLUMN_FULLTEXT_OPT_KEY_GRANULARITY.to_string(),
344 opt.granularity.to_string(),
345 );
346 map.insert(
347 COLUMN_FULLTEXT_OPT_KEY_FALSE_POSITIVE_RATE.to_string(),
348 opt.false_positive_rate().to_string(),
349 );
350 }
351 extensions.fulltext_index_options = Some(map.into());
352 }
353
354 if let Ok(Some(opt)) = column_schema.skipping_index_options() {
356 let map = HashMap::from([
357 (
358 COLUMN_SKIPPING_INDEX_OPT_KEY_GRANULARITY.to_string(),
359 opt.granularity.to_string(),
360 ),
361 (
362 COLUMN_SKIPPING_INDEX_OPT_KEY_FALSE_POSITIVE_RATE.to_string(),
363 opt.false_positive_rate().to_string(),
364 ),
365 (
366 COLUMN_SKIPPING_INDEX_OPT_KEY_TYPE.to_string(),
367 opt.index_type.to_string(),
368 ),
369 ]);
370 extensions.skipping_index_options = Some(map.into());
371 }
372
373 if column_schema.is_inverted_indexed() {
375 extensions.inverted_index_options = Some(HashMap::new().into());
376 }
377
378 let sql_column = SqlColumn {
379 column_def: ColumnDef {
380 name: Ident::with_quote(quote_style, &column_def.name),
381 data_type: concrete_data_type_to_sql_data_type(&column_schema.data_type)
382 .context(ParseSqlSnafu)?,
383 options,
384 },
385 extensions,
386 };
387
388 columns.push(sql_column);
389 }
390
391 let mut constraints = Vec::new();
393
394 constraints.push(TableConstraint::TimeIndex {
396 column: Ident::with_quote(quote_style, &expr.time_index),
397 });
398
399 if !expr.primary_keys.is_empty() {
401 let primary_key_columns: Vec<Ident> = expr
402 .primary_keys
403 .iter()
404 .map(|pk| Ident::with_quote(quote_style, pk))
405 .collect();
406
407 constraints.push(TableConstraint::PrimaryKey {
408 columns: primary_key_columns,
409 });
410 }
411
412 let mut options = OptionMap::default();
414 for (key, value) in &expr.table_options {
415 options.insert(key.clone(), value.clone());
416 }
417
418 Ok(CreateTable {
419 if_not_exists: expr.create_if_not_exists,
420 table_id: expr.table_id.as_ref().map(|tid| tid.id).unwrap_or(0),
421 name: table_name,
422 columns,
423 engine: expr.engine.clone(),
424 constraints,
425 options,
426 partitions: None,
427 })
428}
429
430pub fn validate_create_expr(create: &CreateTableExpr) -> Result<()> {
432 let mut column_to_indices = HashMap::with_capacity(create.column_defs.len());
434 for (idx, column) in create.column_defs.iter().enumerate() {
435 if let Some(indices) = column_to_indices.get(&column.name) {
436 return InvalidSqlSnafu {
437 err_msg: format!(
438 "column name `{}` is duplicated at index {} and {}",
439 column.name, indices, idx
440 ),
441 }
442 .fail();
443 }
444 column_to_indices.insert(&column.name, idx);
445 }
446
447 let time_index_idx =
449 column_to_indices
450 .get(&create.time_index)
451 .with_context(|| InvalidSqlSnafu {
452 err_msg: format!(
453 "column name `{}` is not found in column list",
454 create.time_index
455 ),
456 })?;
457
458 let time_index_column = &create.column_defs[*time_index_idx];
460 let data_type = ConcreteDataType::from(
461 ColumnDataTypeWrapper::try_new(
462 time_index_column.data_type,
463 time_index_column.datatype_extension.clone(),
464 )
465 .context(ColumnDataTypeSnafu)?,
466 );
467 ensure!(
468 data_type.is_timestamp(),
469 InvalidSqlSnafu {
470 err_msg: format!(
471 "column `{}` is not a timestamp type, it can't be used as time index",
472 create.time_index
473 ),
474 }
475 );
476
477 for pk in &create.primary_keys {
479 let _ = column_to_indices
480 .get(&pk)
481 .with_context(|| InvalidSqlSnafu {
482 err_msg: format!("column name `{}` is not found in column list", pk),
483 })?;
484 }
485
486 let mut pk_set = HashSet::new();
488 for pk in &create.primary_keys {
489 if !pk_set.insert(pk) {
490 return InvalidSqlSnafu {
491 err_msg: format!("column name `{}` is duplicated in primary keys", pk),
492 }
493 .fail();
494 }
495 }
496
497 if pk_set.contains(&create.time_index) {
499 return InvalidSqlSnafu {
500 err_msg: format!(
501 "column name `{}` is both primary key and time index",
502 create.time_index
503 ),
504 }
505 .fail();
506 }
507
508 for column in &create.column_defs {
509 if is_interval_type(&column.data_type()) {
511 return InvalidSqlSnafu {
512 err_msg: format!(
513 "column name `{}` is interval type, which is not supported",
514 column.name
515 ),
516 }
517 .fail();
518 }
519 if is_date_time_type(&column.data_type()) {
521 return InvalidSqlSnafu {
522 err_msg: format!(
523 "column name `{}` is datetime type, which is not supported, please use `timestamp` type instead",
524 column.name
525 ),
526 }
527 .fail();
528 }
529 }
530 Ok(())
531}
532
533fn validate_add_columns_expr(add_columns: &AddColumns) -> Result<()> {
534 for add_column in &add_columns.add_columns {
535 let Some(column_def) = &add_column.column_def else {
536 continue;
537 };
538 if is_date_time_type(&column_def.data_type()) {
539 return InvalidSqlSnafu {
540 err_msg: format!("column name `{}` is datetime type, which is not supported, please use `timestamp` type instead", column_def.name),
541 }
542 .fail();
543 }
544 if is_interval_type(&column_def.data_type()) {
545 return InvalidSqlSnafu {
546 err_msg: format!(
547 "column name `{}` is interval type, which is not supported",
548 column_def.name
549 ),
550 }
551 .fail();
552 }
553 }
554 Ok(())
555}
556
557fn is_date_time_type(data_type: &ColumnDataType) -> bool {
558 matches!(data_type, ColumnDataType::Datetime)
559}
560
561fn is_interval_type(data_type: &ColumnDataType) -> bool {
562 matches!(
563 data_type,
564 ColumnDataType::IntervalYearMonth
565 | ColumnDataType::IntervalDayTime
566 | ColumnDataType::IntervalMonthDayNano
567 )
568}
569
570fn find_primary_keys(
571 columns: &[SqlColumn],
572 constraints: &[TableConstraint],
573) -> Result<Vec<String>> {
574 let columns_pk = columns
575 .iter()
576 .filter_map(|x| {
577 if x.options()
578 .iter()
579 .any(|o| matches!(o.option, ColumnOption::PrimaryKey(_)))
580 {
581 Some(x.name().value.clone())
582 } else {
583 None
584 }
585 })
586 .collect::<Vec<String>>();
587
588 ensure!(
589 columns_pk.len() <= 1,
590 IllegalPrimaryKeysDefSnafu {
591 msg: "not allowed to inline multiple primary keys in columns options"
592 }
593 );
594
595 let constraints_pk = constraints
596 .iter()
597 .filter_map(|constraint| match constraint {
598 TableConstraint::PrimaryKey { columns, .. } => {
599 Some(columns.iter().map(|ident| ident.value.clone()))
600 }
601 _ => None,
602 })
603 .flatten()
604 .collect::<Vec<String>>();
605
606 ensure!(
607 columns_pk.is_empty() || constraints_pk.is_empty(),
608 IllegalPrimaryKeysDefSnafu {
609 msg: "found definitions of primary keys in multiple places"
610 }
611 );
612
613 let mut primary_keys = Vec::with_capacity(columns_pk.len() + constraints_pk.len());
614 primary_keys.extend(columns_pk);
615 primary_keys.extend(constraints_pk);
616 Ok(primary_keys)
617}
618
619pub fn find_time_index(constraints: &[TableConstraint]) -> Result<String> {
620 let time_index = constraints
621 .iter()
622 .filter_map(|constraint| match constraint {
623 TableConstraint::TimeIndex { column, .. } => Some(&column.value),
624 _ => None,
625 })
626 .collect::<Vec<&String>>();
627 ensure!(
628 time_index.len() == 1,
629 InvalidSqlSnafu {
630 err_msg: "must have one and only one TimeIndex columns",
631 }
632 );
633 Ok(time_index[0].clone())
634}
635
636fn columns_to_expr(
637 column_defs: &[SqlColumn],
638 time_index: &str,
639 primary_keys: &[String],
640 timezone: Option<&Timezone>,
641) -> Result<Vec<api::v1::ColumnDef>> {
642 let column_schemas = columns_to_column_schemas(column_defs, time_index, timezone)?;
643 column_schemas_to_defs(column_schemas, primary_keys)
644}
645
646fn columns_to_column_schemas(
647 columns: &[SqlColumn],
648 time_index: &str,
649 timezone: Option<&Timezone>,
650) -> Result<Vec<ColumnSchema>> {
651 columns
652 .iter()
653 .map(|c| column_to_schema(c, time_index, timezone).context(ParseSqlSnafu))
654 .collect::<Result<Vec<ColumnSchema>>>()
655}
656
657pub fn column_schemas_to_defs(
659 column_schemas: Vec<ColumnSchema>,
660 primary_keys: &[String],
661) -> Result<Vec<api::v1::ColumnDef>> {
662 let column_datatypes: Vec<(ColumnDataType, Option<ColumnDataTypeExtension>)> = column_schemas
663 .iter()
664 .map(|c| {
665 ColumnDataTypeWrapper::try_from(c.data_type.clone())
666 .map(|w| w.to_parts())
667 .context(ColumnDataTypeSnafu)
668 })
669 .collect::<Result<Vec<_>>>()?;
670
671 column_schemas
672 .iter()
673 .zip(column_datatypes)
674 .map(|(schema, datatype)| {
675 let semantic_type = if schema.is_time_index() {
676 SemanticType::Timestamp
677 } else if primary_keys.contains(&schema.name) {
678 SemanticType::Tag
679 } else {
680 SemanticType::Field
681 } as i32;
682 let comment = schema
683 .metadata()
684 .get(COMMENT_KEY)
685 .cloned()
686 .unwrap_or_default();
687
688 Ok(api::v1::ColumnDef {
689 name: schema.name.clone(),
690 data_type: datatype.0 as i32,
691 is_nullable: schema.is_nullable(),
692 default_constraint: match schema.default_constraint() {
693 None => vec![],
694 Some(v) => {
695 v.clone()
696 .try_into()
697 .context(ConvertColumnDefaultConstraintSnafu {
698 column_name: &schema.name,
699 })?
700 }
701 },
702 semantic_type,
703 comment,
704 datatype_extension: datatype.1,
705 options: options_from_column_schema(schema),
706 })
707 })
708 .collect()
709}
710
711#[derive(Debug, Clone, PartialEq, Eq)]
712pub struct RepartitionRequest {
713 pub catalog_name: String,
714 pub schema_name: String,
715 pub table_name: String,
716 pub source: RepartitionSource,
717 pub into_exprs: Vec<Expr>,
718 pub options: OptionMap,
719}
720
721#[derive(Debug, Clone, PartialEq, Eq)]
722pub enum RepartitionSource {
723 Partitions {
724 from_exprs: Vec<Expr>,
725 target_partition_columns: Option<Vec<String>>,
726 },
727 Unpartitioned {
728 partition_columns: Vec<String>,
729 },
730}
731
732pub(crate) fn to_repartition_request(
733 alter_table: AlterTable,
734 query_ctx: &QueryContextRef,
735) -> Result<RepartitionRequest> {
736 let AlterTable {
737 table_name,
738 alter_operation,
739 options,
740 } = alter_table;
741
742 let (catalog_name, schema_name, table_name) = table_idents_to_full_name(&table_name, query_ctx)
743 .map_err(BoxedError::new)
744 .context(ExternalSnafu)?;
745
746 let (source, into_exprs) = match alter_operation {
747 AlterTableOperation::Repartition { operation } => (
748 RepartitionSource::Partitions {
749 from_exprs: operation.from_exprs,
750 target_partition_columns: operation.partition_columns.map(|columns| {
751 columns
752 .into_iter()
753 .map(|ident| ident.value)
754 .collect::<Vec<_>>()
755 }),
756 },
757 operation.into_exprs,
758 ),
759 AlterTableOperation::Partition { partitions } => (
760 RepartitionSource::Unpartitioned {
761 partition_columns: partitions
762 .column_list
763 .into_iter()
764 .map(|ident| ident.value)
765 .collect(),
766 },
767 partitions.exprs,
768 ),
769 _ => {
770 return InvalidSqlSnafu {
771 err_msg: "expected REPARTITION or PARTITION operation",
772 }
773 .fail();
774 }
775 };
776
777 Ok(RepartitionRequest {
778 catalog_name,
779 schema_name,
780 table_name,
781 source,
782 into_exprs,
783 options,
784 })
785}
786
787fn json_settings_to_proto(settings: JsonSettings) -> Result<PbJsonSettings> {
788 let (type_hints, max_auto_expanded_paths) = settings.into_parts();
789 let type_hints = type_hints
790 .into_iter()
791 .map(|hint| {
792 let (data_type, datatype_extension) = ColumnDataTypeWrapper::try_from(hint.data_type)
793 .map(|w| w.to_parts())
794 .context(ColumnDataTypeSnafu)?;
795
796 Ok(PbJsonTypeHint {
797 path: hint.path,
798 data_type: data_type as i32,
799 datatype_extension,
800 })
801 })
802 .collect::<Result<Vec<_>>>()?;
803
804 Ok(PbJsonSettings {
805 type_hints,
806 max_auto_expanded_paths,
807 })
808}
809
810pub(crate) fn to_alter_table_expr(
812 alter_table: AlterTable,
813 query_ctx: &QueryContextRef,
814) -> Result<AlterTableExpr> {
815 let (catalog_name, schema_name, table_name) =
816 table_idents_to_full_name(alter_table.table_name(), query_ctx)
817 .map_err(BoxedError::new)
818 .context(ExternalSnafu)?;
819
820 let kind = match alter_table.alter_operation {
821 AlterTableOperation::AddConstraint(_) => {
822 return NotSupportedSnafu {
823 feat: "ADD CONSTRAINT",
824 }
825 .fail();
826 }
827 AlterTableOperation::AddColumns { add_columns } => AlterTableKind::AddColumns(AddColumns {
828 add_columns: add_columns
829 .into_iter()
830 .map(|add_column| {
831 let column_def =
832 sql_col_to_grpc_col_def(&add_column.column, Some(&query_ctx.timezone()))
833 .map_err(BoxedError::new)
834 .context(ExternalSnafu)?;
835 if is_interval_type(&column_def.data_type()) {
836 return NotSupportedSnafu {
837 feat: "Add column with interval type",
838 }
839 .fail();
840 }
841 Ok(AddColumn {
842 column_def: Some(column_def),
843 location: add_column.location.as_ref().map(From::from),
844 add_if_not_exists: add_column.add_if_not_exists,
845 })
846 })
847 .collect::<Result<Vec<AddColumn>>>()?,
848 }),
849 AlterTableOperation::ModifyColumnType {
850 column_name,
851 target_type,
852 ..
853 } => {
854 let target_type =
855 sql_data_type_to_concrete_data_type(&target_type).context(ParseSqlSnafu)?;
856
857 if target_type.is_json2() {
859 return NotSupportedSnafu {
860 feat: "ALTER TABLE MODIFY COLUMN to JSON2 type",
861 }
862 .fail();
863 }
864
865 let (target_type, target_type_extension) = ColumnDataTypeWrapper::try_from(target_type)
866 .map(|w| w.to_parts())
867 .context(ColumnDataTypeSnafu)?;
868 if is_interval_type(&target_type) {
869 return NotSupportedSnafu {
870 feat: "Modify column type to interval type",
871 }
872 .fail();
873 }
874 AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
875 modify_column_types: vec![ModifyColumnType {
876 column_name: column_name.value,
877 target_type: target_type as i32,
878 target_type_extension,
879 }],
880 })
881 }
882 AlterTableOperation::SetJsonSettings {
883 column_name,
884 json2_options,
885 } => {
886 let settings = match json2_options {
887 Some(options) => options.build_json_settings().context(ParseSqlSnafu)?,
888 None => datatypes::json::JsonSettings::new_v2(),
889 };
890
891 AlterTableKind::SetJsonSettings(SetJsonSettings {
892 column_name: column_name.value,
893 settings: Some(json_settings_to_proto(settings)?),
894 })
895 }
896 AlterTableOperation::DropColumn { name } => AlterTableKind::DropColumns(DropColumns {
897 drop_columns: vec![DropColumn {
898 name: name.value.clone(),
899 }],
900 }),
901 AlterTableOperation::RenameTable { new_table_name } => {
902 AlterTableKind::RenameTable(RenameTable {
903 new_table_name: new_table_name.clone(),
904 })
905 }
906 AlterTableOperation::SetTableOptions { options } => {
907 AlterTableKind::SetTableOptions(SetTableOptions {
908 table_options: options.into_iter().map(Into::into).collect(),
909 })
910 }
911 AlterTableOperation::UnsetTableOptions { keys } => {
912 AlterTableKind::UnsetTableOptions(UnsetTableOptions { keys })
913 }
914 AlterTableOperation::Repartition { .. } => {
915 return NotSupportedSnafu {
916 feat: "ALTER TABLE ... REPARTITION",
917 }
918 .fail();
919 }
920 AlterTableOperation::Partition { .. } => {
921 return NotSupportedSnafu {
922 feat: "ALTER TABLE ... PARTITION ON COLUMNS",
923 }
924 .fail();
925 }
926 AlterTableOperation::SetIndex { options } => {
927 let option = match options {
928 sql::statements::alter::SetIndexOperation::Fulltext {
929 column_name,
930 options,
931 } => SetIndex {
932 options: Some(set_index::Options::Fulltext(SetFulltext {
933 column_name: column_name.value,
934 enable: options.enable,
935 analyzer: match options.analyzer {
936 FulltextAnalyzer::English => Analyzer::English.into(),
937 FulltextAnalyzer::Chinese => Analyzer::Chinese.into(),
938 },
939 case_sensitive: options.case_sensitive,
940 backend: match options.backend {
941 FulltextBackend::Bloom => PbFulltextBackend::Bloom.into(),
942 FulltextBackend::Tantivy => PbFulltextBackend::Tantivy.into(),
943 },
944 granularity: options.granularity as u64,
945 false_positive_rate: options.false_positive_rate(),
946 })),
947 },
948 sql::statements::alter::SetIndexOperation::Inverted { column_name } => SetIndex {
949 options: Some(set_index::Options::Inverted(SetInverted {
950 column_name: column_name.value,
951 })),
952 },
953 sql::statements::alter::SetIndexOperation::Skipping {
954 column_name,
955 options,
956 } => SetIndex {
957 options: Some(set_index::Options::Skipping(SetSkipping {
958 column_name: column_name.value,
959 enable: true,
960 granularity: options.granularity as u64,
961 false_positive_rate: options.false_positive_rate(),
962 skipping_index_type: match options.index_type {
963 SkippingIndexType::BloomFilter => {
964 PbSkippingIndexType::BloomFilter.into()
965 }
966 },
967 })),
968 },
969 };
970 AlterTableKind::SetIndexes(SetIndexes {
971 set_indexes: vec![option],
972 })
973 }
974 AlterTableOperation::UnsetIndex { options } => {
975 let option = match options {
976 sql::statements::alter::UnsetIndexOperation::Fulltext { column_name } => {
977 UnsetIndex {
978 options: Some(unset_index::Options::Fulltext(UnsetFulltext {
979 column_name: column_name.value,
980 })),
981 }
982 }
983 sql::statements::alter::UnsetIndexOperation::Inverted { column_name } => {
984 UnsetIndex {
985 options: Some(unset_index::Options::Inverted(UnsetInverted {
986 column_name: column_name.value,
987 })),
988 }
989 }
990 sql::statements::alter::UnsetIndexOperation::Skipping { column_name } => {
991 UnsetIndex {
992 options: Some(unset_index::Options::Skipping(UnsetSkipping {
993 column_name: column_name.value,
994 })),
995 }
996 }
997 };
998
999 AlterTableKind::UnsetIndexes(UnsetIndexes {
1000 unset_indexes: vec![option],
1001 })
1002 }
1003 AlterTableOperation::DropDefaults { columns } => {
1004 AlterTableKind::DropDefaults(DropDefaults {
1005 drop_defaults: columns
1006 .into_iter()
1007 .map(|col| {
1008 let column_name = col.0.to_string();
1009 Ok(api::v1::DropDefault { column_name })
1010 })
1011 .collect::<Result<Vec<_>>>()?,
1012 })
1013 }
1014 AlterTableOperation::SetDefaults { defaults } => AlterTableKind::SetDefaults(SetDefaults {
1015 set_defaults: defaults
1016 .into_iter()
1017 .map(|col| {
1018 let column_name = col.column_name.to_string();
1019 let default_constraint = serde_json::to_string(&col.default_constraint)
1020 .context(EncodeJsonSnafu)?
1021 .into_bytes();
1022 Ok(api::v1::SetDefault {
1023 column_name,
1024 default_constraint,
1025 })
1026 })
1027 .collect::<Result<Vec<_>>>()?,
1028 }),
1029 };
1030
1031 Ok(AlterTableExpr {
1032 catalog_name,
1033 schema_name,
1034 table_name,
1035 kind: Some(kind),
1036 })
1037}
1038
1039pub fn to_alter_database_expr(
1041 alter_database: AlterDatabase,
1042 query_ctx: &QueryContextRef,
1043) -> Result<AlterDatabaseExpr> {
1044 let catalog = query_ctx.current_catalog();
1045 let schema = alter_database.database_name;
1046
1047 let kind = match alter_database.alter_operation {
1048 AlterDatabaseOperation::SetDatabaseOption { options } => {
1049 let options = options.into_iter().map(Into::into).collect();
1050 AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1051 set_database_options: options,
1052 })
1053 }
1054 AlterDatabaseOperation::UnsetDatabaseOption { keys } => {
1055 AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys })
1056 }
1057 };
1058
1059 Ok(AlterDatabaseExpr {
1060 catalog_name: catalog.to_string(),
1061 schema_name: schema.to_string(),
1062 kind: Some(kind),
1063 })
1064}
1065
1066pub fn to_create_view_expr(
1068 stmt: CreateView,
1069 logical_plan: Vec<u8>,
1070 table_names: Vec<TableName>,
1071 columns: Vec<String>,
1072 plan_columns: Vec<String>,
1073 definition: String,
1074 query_ctx: QueryContextRef,
1075) -> Result<CreateViewExpr> {
1076 let (catalog_name, schema_name, view_name) = table_idents_to_full_name(&stmt.name, &query_ctx)
1077 .map_err(BoxedError::new)
1078 .context(ExternalSnafu)?;
1079
1080 let expr = CreateViewExpr {
1081 catalog_name,
1082 schema_name,
1083 view_name,
1084 logical_plan,
1085 create_if_not_exists: stmt.if_not_exists,
1086 or_replace: stmt.or_replace,
1087 table_names,
1088 columns,
1089 plan_columns,
1090 definition,
1091 };
1092
1093 Ok(expr)
1094}
1095
1096pub fn to_create_flow_task_expr(
1097 create_flow: CreateFlow,
1098 query_ctx: &QueryContextRef,
1099) -> Result<CreateFlowExpr> {
1100 let sink_table_ref = object_name_to_table_reference(create_flow.sink_table_name.clone(), true)
1102 .with_context(|_| ConvertIdentifierSnafu {
1103 ident: create_flow.sink_table_name.to_string(),
1104 })?;
1105 let catalog = sink_table_ref
1106 .catalog()
1107 .unwrap_or(query_ctx.current_catalog())
1108 .to_string();
1109 let schema = sink_table_ref
1110 .schema()
1111 .map(|s| s.to_owned())
1112 .unwrap_or(query_ctx.current_schema());
1113
1114 let sink_table_name = TableName {
1115 catalog_name: catalog,
1116 schema_name: schema,
1117 table_name: sink_table_ref.table().to_string(),
1118 };
1119
1120 let source_table_names = extract_tables_from_query(&create_flow.query)
1121 .map(|name| {
1122 let reference =
1123 object_name_to_table_reference(name.clone(), true).with_context(|_| {
1124 ConvertIdentifierSnafu {
1125 ident: name.to_string(),
1126 }
1127 })?;
1128 let catalog = reference
1129 .catalog()
1130 .unwrap_or(query_ctx.current_catalog())
1131 .to_string();
1132 let schema = reference
1133 .schema()
1134 .map(|s| s.to_string())
1135 .unwrap_or(query_ctx.current_schema());
1136
1137 let table_name = TableName {
1138 catalog_name: catalog,
1139 schema_name: schema,
1140 table_name: reference.table().to_string(),
1141 };
1142 Ok(table_name)
1143 })
1144 .collect::<Result<Vec<_>>>()?;
1145
1146 let eval_interval = create_flow.eval_interval;
1147
1148 let flow_options = stringify_flow_options(create_flow.flow_options)?;
1149 Ok(CreateFlowExpr {
1150 catalog_name: query_ctx.current_catalog().to_string(),
1151 flow_name: sanitize_flow_name(create_flow.flow_name)?,
1152 source_table_names,
1153 sink_table_name: Some(sink_table_name),
1154 or_replace: create_flow.or_replace,
1155 create_if_not_exists: create_flow.if_not_exists,
1156 expire_after: create_flow.expire_after.map(|value| ExpireAfter { value }),
1157 eval_interval: eval_interval.map(|seconds| api::v1::EvalInterval { seconds }),
1158 comment: create_flow.comment.unwrap_or_default(),
1159 sql: create_flow.query.to_string(),
1160 flow_options,
1161 })
1162}
1163
1164fn stringify_flow_options(flow_options: OptionMap) -> Result<HashMap<String, String>> {
1165 let options_len = flow_options.len();
1166 let flow_options = flow_options.into_map();
1167 ensure!(
1168 flow_options.len() == options_len,
1169 InvalidSqlSnafu {
1170 err_msg: "flow options only support scalar string-compatible values".to_string(),
1171 }
1172 );
1173 Ok(flow_options)
1174}
1175
1176fn sanitize_flow_name(mut flow_name: ObjectName) -> Result<String> {
1178 ensure!(
1179 flow_name.0.len() == 1,
1180 InvalidFlowNameSnafu {
1181 name: flow_name.to_string(),
1182 }
1183 );
1184 Ok(flow_name.0.swap_remove(0).to_string_unquoted())
1186}
1187
1188#[cfg(test)]
1189mod tests {
1190 use std::collections::HashMap;
1191
1192 use api::v1::{SetDatabaseOptions, UnsetDatabaseOptions};
1193 use datatypes::extension::json::is_json2_extension_type;
1194 use datatypes::value::Value;
1195 use session::context::{QueryContext, QueryContextBuilder};
1196 use sql::dialect::GreptimeDbDialect;
1197 use sql::parser::{ParseOptions, ParserContext};
1198 use sql::statements::statement::Statement;
1199 use store_api::storage::ColumnDefaultConstraint;
1200
1201 use super::*;
1202
1203 #[test]
1204 fn test_to_alter_add_json2_column_preserves_extension() {
1205 let sql = "ALTER TABLE monitor ADD COLUMN payload JSON2;";
1206 let stmt =
1207 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1208 .unwrap()
1209 .pop()
1210 .unwrap();
1211 let Statement::AlterTable(alter_table) = stmt else {
1212 unreachable!()
1213 };
1214
1215 let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1216 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = expr.kind.unwrap() else {
1217 unreachable!()
1218 };
1219 let column_def = add_columns[0].column_def.as_ref().unwrap();
1220 let column_schema = try_as_column_schema(column_def).unwrap();
1221 let schema = Schema::new(vec![column_schema]);
1222
1223 assert!(is_json2_extension_type(&schema.arrow_schema().fields()[0]));
1224 }
1225
1226 #[test]
1227 fn test_to_alter_add_json2_column_preserves_settings() {
1228 let sql = "ALTER TABLE monitor ADD COLUMN payload JSON2(max_auto_expanded_paths = 0, service STRING, nested.value BIGINT)";
1229 let stmt =
1230 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1231 .unwrap()
1232 .pop()
1233 .unwrap();
1234 let Statement::AlterTable(alter_table) = stmt else {
1235 unreachable!()
1236 };
1237 let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1238 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = expr.kind.unwrap() else {
1239 unreachable!()
1240 };
1241 let column_schema =
1242 try_as_column_schema(add_columns[0].column_def.as_ref().unwrap()).unwrap();
1243 let metadata: datatypes::extension::json::JsonMetadata =
1244 serde_json::from_str(&column_schema.metadata()["ARROW:extension:metadata"]).unwrap();
1245 let settings = metadata.json_settings();
1246 assert_eq!(Some(0), settings.max_auto_expanded_paths());
1247 assert_eq!(2, settings.type_hints().len());
1248 assert_eq!(vec!["service"], settings.type_hints()[0].path);
1249 assert_eq!(vec!["nested", "value"], settings.type_hints()[1].path);
1250 }
1251
1252 #[test]
1253 fn test_create_flow_tql_expr() {
1254 let sql = r#"
1255CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1256TQL EVAL (0, 15, '5s') count_values("status_code", http_requests);"#;
1257 let stmt =
1258 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1259
1260 assert!(
1261 stmt.is_err(),
1262 "Expected error for invalid TQL EVAL parameters: {:#?}",
1263 stmt
1264 );
1265
1266 let sql = r#"
1267CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1268TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests);"#;
1269 let stmt =
1270 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1271 .unwrap()
1272 .pop()
1273 .unwrap();
1274
1275 let Statement::CreateFlow(create_flow) = stmt else {
1276 unreachable!()
1277 };
1278 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1279
1280 let to_dot_sep =
1281 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1282 assert_eq!("calc_reqs", expr.flow_name);
1283 assert_eq!("greptime", expr.catalog_name);
1284 assert_eq!(
1285 "greptime.public.cnt_reqs",
1286 expr.sink_table_name.map(to_dot_sep).unwrap()
1287 );
1288 assert_eq!(1, expr.source_table_names.len());
1289 assert_eq!(
1290 "greptime.public.http_requests",
1291 to_dot_sep(expr.source_table_names[0].clone())
1292 );
1293 assert_eq!(
1294 r#"TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests)"#,
1295 expr.sql
1296 );
1297
1298 let sql = r#"
1299CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1300TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__schema__="greptime_private"});"#;
1301 let stmt =
1302 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1303 .unwrap()
1304 .pop()
1305 .unwrap();
1306 let Statement::CreateFlow(create_flow) = stmt else {
1307 unreachable!()
1308 };
1309 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1310 assert_eq!(1, expr.source_table_names.len());
1311 assert_eq!(
1312 "greptime.greptime_private.http_requests",
1313 to_dot_sep(expr.source_table_names[0].clone())
1314 );
1315
1316 let sql = r#"
1317CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1318TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__database__="greptime_private"});"#;
1319 let stmt =
1320 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1321 .unwrap()
1322 .pop()
1323 .unwrap();
1324 let Statement::CreateFlow(create_flow) = stmt else {
1325 unreachable!()
1326 };
1327 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1328 assert_eq!(1, expr.source_table_names.len());
1329 assert_eq!(
1330 "greptime.greptime_private.http_requests",
1331 to_dot_sep(expr.source_table_names[0].clone())
1332 );
1333 }
1334
1335 #[test]
1336 fn test_create_flow_tql_cte_source_tables() {
1337 let sql = r#"
1338CREATE FLOW calc_cte
1339SINK TO metric_cte_sink
1340EVAL INTERVAL '1m'
1341AS
1342WITH tql(ts, the_value) AS (
1343 TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1344)
1345SELECT * FROM tql;
1346"#;
1347
1348 let stmt =
1349 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1350 .unwrap()
1351 .pop()
1352 .unwrap();
1353
1354 let Statement::CreateFlow(create_flow) = stmt else {
1355 unreachable!()
1356 };
1357 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1358
1359 let to_dot_sep =
1360 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1361 assert_eq!(1, expr.source_table_names.len());
1362 assert_eq!(
1363 "greptime.public.metric_cte",
1364 to_dot_sep(expr.source_table_names[0].clone())
1365 );
1366 }
1367
1368 #[test]
1369 fn test_create_flow_tql_cte_source_tables_quoted_cte_name() {
1370 let sql = r#"
1371CREATE FLOW calc_cte
1372SINK TO metric_cte_sink
1373EVAL INTERVAL '1m'
1374AS
1375WITH "TQL"(ts, the_value) AS (
1376 TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1377)
1378SELECT * FROM "TQL";
1379"#;
1380
1381 let stmt =
1382 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1383 .unwrap()
1384 .pop()
1385 .unwrap();
1386
1387 let Statement::CreateFlow(create_flow) = stmt else {
1388 unreachable!()
1389 };
1390 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1391
1392 let to_dot_sep =
1393 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1394 assert_eq!(1, expr.source_table_names.len());
1395 assert_eq!(
1396 "greptime.public.metric_cte",
1397 to_dot_sep(expr.source_table_names[0].clone())
1398 );
1399 }
1400
1401 #[test]
1402 fn test_create_flow_tql_cte_source_tables_same_name() {
1403 let sql = r#"
1404CREATE FLOW calc_cte
1405SINK TO metric_cte_sink
1406EVAL INTERVAL '1m'
1407AS
1408WITH tql(ts, the_value) AS (
1409 TQL EVAL (now() - '1m'::interval, now(), '5s') tql
1410)
1411SELECT * FROM tql;
1412"#;
1413
1414 let stmt =
1415 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1416 .unwrap()
1417 .pop()
1418 .unwrap();
1419
1420 let Statement::CreateFlow(create_flow) = stmt else {
1421 unreachable!()
1422 };
1423 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1424
1425 let to_dot_sep =
1426 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1427 assert_eq!(1, expr.source_table_names.len());
1428 assert_eq!(
1429 "greptime.public.tql",
1430 to_dot_sep(expr.source_table_names[0].clone())
1431 );
1432 }
1433
1434 #[test]
1435 fn test_create_flow_expr() {
1436 let sql = r"
1437CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS
1438SELECT
1439 DISTINCT number as dis
1440FROM
1441 distinct_basic;";
1442 let stmt =
1443 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1444 .unwrap()
1445 .pop()
1446 .unwrap();
1447
1448 let Statement::CreateFlow(create_flow) = stmt else {
1449 unreachable!()
1450 };
1451 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1452
1453 let to_dot_sep =
1454 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1455 assert_eq!("test_distinct_basic", expr.flow_name);
1456 assert_eq!("greptime", expr.catalog_name);
1457 assert_eq!(
1458 "greptime.public.out_distinct_basic",
1459 expr.sink_table_name.map(to_dot_sep).unwrap()
1460 );
1461 assert_eq!(1, expr.source_table_names.len());
1462 assert_eq!(
1463 "greptime.public.distinct_basic",
1464 to_dot_sep(expr.source_table_names[0].clone())
1465 );
1466 assert_eq!(
1467 r"SELECT
1468 DISTINCT number as dis
1469FROM
1470 distinct_basic",
1471 expr.sql
1472 );
1473
1474 let sql = r"
1475CREATE FLOW `task_2`
1476SINK TO schema_1.table_1
1477AS
1478SELECT max(c1), min(c2) FROM schema_2.table_2;";
1479 let stmt =
1480 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1481 .unwrap()
1482 .pop()
1483 .unwrap();
1484
1485 let Statement::CreateFlow(create_flow) = stmt else {
1486 unreachable!()
1487 };
1488 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1489
1490 let to_dot_sep =
1491 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1492 assert_eq!("task_2", expr.flow_name);
1493 assert_eq!("greptime", expr.catalog_name);
1494 assert_eq!(
1495 "greptime.schema_1.table_1",
1496 expr.sink_table_name.map(to_dot_sep).unwrap()
1497 );
1498 assert_eq!(1, expr.source_table_names.len());
1499 assert_eq!(
1500 "greptime.schema_2.table_2",
1501 to_dot_sep(expr.source_table_names[0].clone())
1502 );
1503 assert_eq!("SELECT max(c1), min(c2) FROM schema_2.table_2", expr.sql);
1504 assert!(expr.flow_options.is_empty());
1505
1506 let sql = r"
1507CREATE FLOW task_3
1508SINK TO schema_1.table_1
1509WITH (defer_on_missing_source = 'true', foo = 'bar')
1510AS
1511SELECT max(c1), min(c2) FROM schema_2.table_2;";
1512 let stmt =
1513 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1514 .unwrap()
1515 .pop()
1516 .unwrap();
1517
1518 let Statement::CreateFlow(create_flow) = stmt else {
1519 unreachable!()
1520 };
1521 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1522 assert_eq!(
1523 expr.flow_options,
1524 HashMap::from([
1525 ("defer_on_missing_source".to_string(), "true".to_string()),
1526 ("foo".to_string(), "bar".to_string()),
1527 ])
1528 );
1529
1530 let sql = r"
1531CREATE FLOW task_4
1532SINK TO schema_1.table_1
1533WITH (defer_on_missing_source = true)
1534AS
1535SELECT max(c1), min(c2) FROM schema_2.table_2;";
1536 let stmt =
1537 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1538 .unwrap()
1539 .pop()
1540 .unwrap();
1541
1542 let Statement::CreateFlow(create_flow) = stmt else {
1543 unreachable!()
1544 };
1545 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1546 assert_eq!(
1547 expr.flow_options,
1548 HashMap::from([("defer_on_missing_source".to_string(), "true".to_string(),)])
1549 );
1550
1551 let sql = r"
1552CREATE FLOW task_5
1553SINK TO schema_1.table_1
1554WITH (defer_on_missing_source = [true])
1555AS
1556SELECT max(c1), min(c2) FROM schema_2.table_2;";
1557 let stmt =
1558 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1559 .unwrap()
1560 .pop()
1561 .unwrap();
1562
1563 let Statement::CreateFlow(create_flow) = stmt else {
1564 unreachable!()
1565 };
1566 let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1567 assert!(res.is_err());
1568 assert!(
1569 res.unwrap_err()
1570 .to_string()
1571 .contains("flow options only support scalar string-compatible values")
1572 );
1573
1574 let sql = r"
1575CREATE FLOW abc.`task_2`
1576SINK TO schema_1.table_1
1577AS
1578SELECT max(c1), min(c2) FROM schema_2.table_2;";
1579 let stmt =
1580 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1581 .unwrap()
1582 .pop()
1583 .unwrap();
1584
1585 let Statement::CreateFlow(create_flow) = stmt else {
1586 unreachable!()
1587 };
1588 let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1589
1590 assert!(res.is_err());
1591 assert!(
1592 res.unwrap_err()
1593 .to_string()
1594 .contains("Invalid flow name: abc.`task_2`")
1595 );
1596 }
1597
1598 #[test]
1599 fn test_create_to_expr() {
1600 let sql = "CREATE TABLE monitor (host STRING,ts TIMESTAMP,TIME INDEX (ts),PRIMARY KEY(host)) ENGINE=mito WITH(ttl='3days', write_buffer_size='1024KB');";
1601 let stmt =
1602 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1603 .unwrap()
1604 .pop()
1605 .unwrap();
1606
1607 let Statement::CreateTable(create_table) = stmt else {
1608 unreachable!()
1609 };
1610 let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1611 assert_eq!("3days", expr.table_options.get("ttl").unwrap());
1612 assert_eq!(
1613 "1.0MiB",
1614 expr.table_options.get("write_buffer_size").unwrap()
1615 );
1616
1617 let sql = "CREATE TABLE monitor (ts TIMESTAMP TIME INDEX) WITH(skip_wal='false');";
1618 let stmt =
1619 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1620 .unwrap()
1621 .pop()
1622 .unwrap();
1623 let Statement::CreateTable(create_table) = stmt else {
1624 unreachable!()
1625 };
1626 let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1627 assert_eq!(
1628 Some("false"),
1629 expr.table_options
1630 .get(store_api::mito_engine_options::SKIP_WAL_KEY)
1631 .map(String::as_str)
1632 );
1633 }
1634
1635 #[test]
1636 fn test_invalid_create_to_expr() {
1637 let cases = [
1638 "CREATE TABLE monitor (host STRING primary key, ts TIMESTAMP TIME INDEX, some_column text, some_column string);",
1640 "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, some_column STRING, PRIMARY KEY (some_column, host, some_column));",
1642 "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, PRIMARY KEY (host, ts));",
1644 ];
1645
1646 for sql in cases {
1647 let stmt = ParserContext::create_with_dialect(
1648 sql,
1649 &GreptimeDbDialect {},
1650 ParseOptions::default(),
1651 )
1652 .unwrap()
1653 .pop()
1654 .unwrap();
1655 let Statement::CreateTable(create_table) = stmt else {
1656 unreachable!()
1657 };
1658 create_to_expr(&create_table, &QueryContext::arc()).unwrap_err();
1659 }
1660 }
1661
1662 #[test]
1663 fn test_create_to_expr_with_default_timestamp_value() {
1664 let sql = "CREATE TABLE monitor (v double,ts TIMESTAMP default '2024-01-30T00:01:01',TIME INDEX (ts)) engine=mito;";
1665 let stmt =
1666 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1667 .unwrap()
1668 .pop()
1669 .unwrap();
1670
1671 let Statement::CreateTable(create_table) = stmt else {
1672 unreachable!()
1673 };
1674
1675 let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1677 let ts_column = &expr.column_defs[1];
1678 let constraint = assert_ts_column(ts_column);
1679 assert!(
1680 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1681 if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1682 );
1683
1684 let ctx = QueryContextBuilder::default()
1686 .timezone(Timezone::from_tz_string("+08:00").unwrap())
1687 .build()
1688 .into();
1689 let expr = create_to_expr(&create_table, &ctx).unwrap();
1690 let ts_column = &expr.column_defs[1];
1691 let constraint = assert_ts_column(ts_column);
1692 assert!(
1693 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1694 if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1695 );
1696 }
1697
1698 fn assert_ts_column(ts_column: &api::v1::ColumnDef) -> ColumnDefaultConstraint {
1699 assert_eq!("ts", ts_column.name);
1700 assert_eq!(
1701 ColumnDataType::TimestampMillisecond as i32,
1702 ts_column.data_type
1703 );
1704 assert!(!ts_column.default_constraint.is_empty());
1705
1706 ColumnDefaultConstraint::try_from(&ts_column.default_constraint[..]).unwrap()
1707 }
1708
1709 #[test]
1710 fn test_to_alter_expr() {
1711 let sql = "ALTER DATABASE greptime SET key1='value1', key2='value2';";
1712 let stmt =
1713 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1714 .unwrap()
1715 .pop()
1716 .unwrap();
1717
1718 let Statement::AlterDatabase(alter_database) = stmt else {
1719 unreachable!()
1720 };
1721
1722 let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1723 let kind = expr.kind.unwrap();
1724
1725 let AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1726 set_database_options,
1727 }) = kind
1728 else {
1729 unreachable!()
1730 };
1731
1732 assert_eq!(2, set_database_options.len());
1733 assert_eq!("key1", set_database_options[0].key);
1734 assert_eq!("value1", set_database_options[0].value);
1735 assert_eq!("key2", set_database_options[1].key);
1736 assert_eq!("value2", set_database_options[1].value);
1737
1738 let sql = "ALTER DATABASE greptime UNSET key1, key2;";
1739 let stmt =
1740 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1741 .unwrap()
1742 .pop()
1743 .unwrap();
1744
1745 let Statement::AlterDatabase(alter_database) = stmt else {
1746 unreachable!()
1747 };
1748
1749 let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1750 let kind = expr.kind.unwrap();
1751
1752 let AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys }) = kind else {
1753 unreachable!()
1754 };
1755
1756 assert_eq!(2, keys.len());
1757 assert!(keys.contains(&"key1".to_string()));
1758 assert!(keys.contains(&"key2".to_string()));
1759
1760 let sql = "ALTER TABLE monitor add column ts TIMESTAMP default '2024-01-30T00:01:01';";
1761 let stmt =
1762 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1763 .unwrap()
1764 .pop()
1765 .unwrap();
1766
1767 let Statement::AlterTable(alter_table) = stmt else {
1768 unreachable!()
1769 };
1770
1771 let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1773 let kind = expr.kind.unwrap();
1774
1775 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1776 unreachable!()
1777 };
1778
1779 assert_eq!(1, add_columns.len());
1780 let ts_column = add_columns[0].column_def.clone().unwrap();
1781 let constraint = assert_ts_column(&ts_column);
1782 assert!(
1783 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1784 if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1785 );
1786
1787 let ctx = QueryContextBuilder::default()
1790 .timezone(Timezone::from_tz_string("+08:00").unwrap())
1791 .build()
1792 .into();
1793 let expr = to_alter_table_expr(alter_table, &ctx).unwrap();
1794 let kind = expr.kind.unwrap();
1795
1796 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1797 unreachable!()
1798 };
1799
1800 assert_eq!(1, add_columns.len());
1801 let ts_column = add_columns[0].column_def.clone().unwrap();
1802 let constraint = assert_ts_column(&ts_column);
1803 assert!(
1804 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1805 if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1806 );
1807 }
1808
1809 #[test]
1810 fn test_to_alter_modify_column_type_expr() {
1811 let sql = "ALTER TABLE monitor MODIFY COLUMN mem_usage STRING;";
1812 let stmt =
1813 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1814 .unwrap()
1815 .pop()
1816 .unwrap();
1817
1818 let Statement::AlterTable(alter_table) = stmt else {
1819 unreachable!()
1820 };
1821
1822 let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1824 let kind = expr.kind.unwrap();
1825
1826 let AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
1827 modify_column_types,
1828 }) = kind
1829 else {
1830 unreachable!()
1831 };
1832
1833 assert_eq!(1, modify_column_types.len());
1834 let modify_column_type = &modify_column_types[0];
1835
1836 assert_eq!("mem_usage", modify_column_type.column_name);
1837 assert_eq!(
1838 ColumnDataType::String as i32,
1839 modify_column_type.target_type
1840 );
1841 assert!(modify_column_type.target_type_extension.is_none());
1842 }
1843
1844 #[test]
1845 fn test_to_alter_set_json_settings_expr() {
1846 let sql = "ALTER TABLE monitor MODIFY COLUMN payload JSON2 (service STRING);";
1847 let stmt =
1848 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1849 .unwrap()
1850 .pop()
1851 .unwrap();
1852
1853 let Statement::AlterTable(alter_table) = stmt else {
1854 unreachable!()
1855 };
1856 let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1857 let kind = expr.kind.unwrap();
1858
1859 let AlterTableKind::SetJsonSettings(modify) = kind else {
1860 unreachable!()
1861 };
1862 assert_eq!("payload", modify.column_name);
1863 let settings = modify.settings.as_ref().unwrap();
1864 assert_eq!(Some(100), settings.max_auto_expanded_paths);
1865 assert_eq!(1, settings.type_hints.len());
1866 assert_eq!(["service"], &settings.type_hints[0].path[..]);
1867
1868 let sql = "ALTER TABLE monitor MODIFY COLUMN payload JSON2;";
1869 let stmt =
1870 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1871 .unwrap()
1872 .pop()
1873 .unwrap();
1874
1875 let Statement::AlterTable(alter_table) = stmt else {
1876 unreachable!()
1877 };
1878 let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1879 let kind = expr.kind.unwrap();
1880
1881 let AlterTableKind::SetJsonSettings(modify) = kind else {
1882 unreachable!()
1883 };
1884 let settings = modify.settings.as_ref().unwrap();
1885 assert_eq!(Some(100), settings.max_auto_expanded_paths);
1886 assert!(settings.type_hints.is_empty());
1887 }
1888
1889 #[test]
1890 fn test_to_repartition_request() {
1891 let sql = r#"
1892ALTER TABLE metrics REPARTITION (
1893 device_id < 100
1894) INTO (
1895 device_id < 100 AND area < 'South',
1896 device_id < 100 AND area >= 'South'
1897);"#;
1898 let stmt =
1899 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1900 .unwrap()
1901 .pop()
1902 .unwrap();
1903
1904 let Statement::AlterTable(alter_table) = stmt else {
1905 unreachable!()
1906 };
1907
1908 let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1909 assert_eq!("greptime", request.catalog_name);
1910 assert_eq!("public", request.schema_name);
1911 assert_eq!("metrics", request.table_name);
1912 let RepartitionSource::Partitions {
1913 from_exprs,
1914 target_partition_columns,
1915 } = request.source
1916 else {
1917 unreachable!()
1918 };
1919 assert!(target_partition_columns.is_none());
1920 assert_eq!(
1921 from_exprs
1922 .into_iter()
1923 .map(|x| x.to_string())
1924 .collect::<Vec<_>>(),
1925 vec!["device_id < 100".to_string()]
1926 );
1927 assert_eq!(
1928 request
1929 .into_exprs
1930 .into_iter()
1931 .map(|x| x.to_string())
1932 .collect::<Vec<_>>(),
1933 vec![
1934 "device_id < 100 AND area < 'South'".to_string(),
1935 "device_id < 100 AND area >= 'South'".to_string()
1936 ]
1937 );
1938 }
1939
1940 #[test]
1941 fn test_to_repartition_request_with_target_partition_columns() {
1942 let sql = r#"
1943ALTER TABLE metrics REPARTITION (
1944 device_id < 100
1945) ON COLUMNS (device_id, area) INTO (
1946 device_id < 100 AND area < 'South',
1947 device_id < 100 AND area >= 'South'
1948);"#;
1949 let stmt =
1950 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1951 .unwrap()
1952 .pop()
1953 .unwrap();
1954
1955 let Statement::AlterTable(alter_table) = stmt else {
1956 unreachable!()
1957 };
1958
1959 let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1960 let RepartitionSource::Partitions {
1961 target_partition_columns,
1962 ..
1963 } = request.source
1964 else {
1965 unreachable!()
1966 };
1967
1968 assert_eq!(
1969 target_partition_columns,
1970 Some(vec!["device_id".to_string(), "area".to_string()])
1971 );
1972 }
1973
1974 #[test]
1975 fn test_to_repartition_request_with_unpartitioned_source() {
1976 let sql = r#"
1977ALTER TABLE metrics PARTITION ON COLUMNS (device_id, area) (
1978 device_id < 100 AND area < 'South',
1979 device_id < 100 AND area >= 'South'
1980);"#;
1981 let stmt =
1982 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1983 .unwrap()
1984 .pop()
1985 .unwrap();
1986
1987 let Statement::AlterTable(alter_table) = stmt else {
1988 unreachable!()
1989 };
1990
1991 let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1992 assert_eq!("greptime", request.catalog_name);
1993 assert_eq!("public", request.schema_name);
1994 assert_eq!("metrics", request.table_name);
1995 let RepartitionSource::Unpartitioned { partition_columns } = request.source else {
1996 unreachable!()
1997 };
1998 assert_eq!(partition_columns, vec!["device_id", "area"]);
1999 assert_eq!(
2000 request
2001 .into_exprs
2002 .into_iter()
2003 .map(|x| x.to_string())
2004 .collect::<Vec<_>>(),
2005 vec![
2006 "device_id < 100 AND area < 'South'".to_string(),
2007 "device_id < 100 AND area >= 'South'".to_string()
2008 ]
2009 );
2010 }
2011
2012 fn new_test_table_names() -> Vec<TableName> {
2013 vec![
2014 TableName {
2015 catalog_name: "greptime".to_string(),
2016 schema_name: "public".to_string(),
2017 table_name: "a_table".to_string(),
2018 },
2019 TableName {
2020 catalog_name: "greptime".to_string(),
2021 schema_name: "public".to_string(),
2022 table_name: "b_table".to_string(),
2023 },
2024 ]
2025 }
2026
2027 #[test]
2028 fn test_to_create_view_expr() {
2029 let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
2030 let stmt =
2031 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2032 .unwrap()
2033 .pop()
2034 .unwrap();
2035
2036 let Statement::CreateView(stmt) = stmt else {
2037 unreachable!()
2038 };
2039
2040 let logical_plan = vec![1, 2, 3];
2041 let table_names = new_test_table_names();
2042 let columns = vec!["a".to_string()];
2043 let plan_columns = vec!["number".to_string()];
2044
2045 let expr = to_create_view_expr(
2046 stmt,
2047 logical_plan.clone(),
2048 table_names.clone(),
2049 columns.clone(),
2050 plan_columns.clone(),
2051 sql.to_string(),
2052 QueryContext::arc(),
2053 )
2054 .unwrap();
2055
2056 assert_eq!("greptime", expr.catalog_name);
2057 assert_eq!("public", expr.schema_name);
2058 assert_eq!("test", expr.view_name);
2059 assert!(!expr.create_if_not_exists);
2060 assert!(!expr.or_replace);
2061 assert_eq!(logical_plan, expr.logical_plan);
2062 assert_eq!(table_names, expr.table_names);
2063 assert_eq!(sql, expr.definition);
2064 assert_eq!(columns, expr.columns);
2065 assert_eq!(plan_columns, expr.plan_columns);
2066 }
2067
2068 #[test]
2069 fn test_to_create_view_expr_complex() {
2070 let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test.test_view AS SELECT * FROM NUMBERS";
2071 let stmt =
2072 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2073 .unwrap()
2074 .pop()
2075 .unwrap();
2076
2077 let Statement::CreateView(stmt) = stmt else {
2078 unreachable!()
2079 };
2080
2081 let logical_plan = vec![1, 2, 3];
2082 let table_names = new_test_table_names();
2083 let columns = vec!["a".to_string()];
2084 let plan_columns = vec!["number".to_string()];
2085
2086 let expr = to_create_view_expr(
2087 stmt,
2088 logical_plan.clone(),
2089 table_names.clone(),
2090 columns.clone(),
2091 plan_columns.clone(),
2092 sql.to_string(),
2093 QueryContext::arc(),
2094 )
2095 .unwrap();
2096
2097 assert_eq!("greptime", expr.catalog_name);
2098 assert_eq!("test", expr.schema_name);
2099 assert_eq!("test_view", expr.view_name);
2100 assert!(expr.create_if_not_exists);
2101 assert!(expr.or_replace);
2102 assert_eq!(logical_plan, expr.logical_plan);
2103 assert_eq!(table_names, expr.table_names);
2104 assert_eq!(sql, expr.definition);
2105 assert_eq!(columns, expr.columns);
2106 assert_eq!(plan_columns, expr.plan_columns);
2107 }
2108
2109 #[test]
2110 fn test_expr_to_create() {
2111 let sql = r#"CREATE TABLE IF NOT EXISTS `tt` (
2112 `timestamp` TIMESTAMP(9) NOT NULL,
2113 `ip_address` STRING NULL SKIPPING INDEX WITH(false_positive_rate = '0.01', granularity = '10240', type = 'BLOOM'),
2114 `username` STRING NULL,
2115 `http_method` STRING NULL INVERTED INDEX,
2116 `request_line` STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false', false_positive_rate = '0.01', granularity = '10240'),
2117 `protocol` STRING NULL,
2118 `status_code` INT NULL INVERTED INDEX,
2119 `response_size` BIGINT NULL,
2120 `message` STRING NULL,
2121 TIME INDEX (`timestamp`),
2122 PRIMARY KEY (`username`, `status_code`)
2123)
2124ENGINE=mito
2125WITH(
2126 append_mode = 'true'
2127)"#;
2128 let stmt =
2129 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2130 .unwrap()
2131 .pop()
2132 .unwrap();
2133
2134 let Statement::CreateTable(original_create) = stmt else {
2135 unreachable!()
2136 };
2137
2138 let expr = create_to_expr(&original_create, &QueryContext::arc()).unwrap();
2140
2141 let create_table = expr_to_create(&expr, Some('`')).unwrap();
2142 let new_sql = format!("{:#}", create_table);
2143 assert_eq!(sql, new_sql);
2144 }
2145}