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