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 })
809 })
810 .collect::<Result<Vec<_>>>()?;
811
812 Ok(PbJsonSettings {
813 type_hints,
814 max_auto_expanded_paths,
815 })
816}
817
818pub(crate) fn to_alter_table_expr(
820 alter_table: AlterTable,
821 query_ctx: &QueryContextRef,
822) -> Result<AlterTableExpr> {
823 let (catalog_name, schema_name, table_name) =
824 table_idents_to_full_name(alter_table.table_name(), query_ctx)
825 .map_err(BoxedError::new)
826 .context(ExternalSnafu)?;
827
828 let kind = match alter_table.alter_operation {
829 AlterTableOperation::AddConstraint(_) => {
830 return NotSupportedSnafu {
831 feat: "ADD CONSTRAINT",
832 }
833 .fail();
834 }
835 AlterTableOperation::AddColumns { add_columns } => AlterTableKind::AddColumns(AddColumns {
836 add_columns: add_columns
837 .into_iter()
838 .map(|add_column| {
839 let column_def =
840 sql_col_to_grpc_col_def(&add_column.column, Some(&query_ctx.timezone()))
841 .map_err(BoxedError::new)
842 .context(ExternalSnafu)?;
843 if is_interval_type(&column_def.data_type()) {
844 return NotSupportedSnafu {
845 feat: "Add column with interval type",
846 }
847 .fail();
848 }
849 Ok(AddColumn {
850 column_def: Some(column_def),
851 location: add_column.location.as_ref().map(From::from),
852 add_if_not_exists: add_column.add_if_not_exists,
853 })
854 })
855 .collect::<Result<Vec<AddColumn>>>()?,
856 }),
857 AlterTableOperation::ModifyColumnType {
858 column_name,
859 target_type,
860 ..
861 } => {
862 let target_type =
863 sql_data_type_to_concrete_data_type(&target_type).context(ParseSqlSnafu)?;
864
865 if target_type.is_json2() {
867 return NotSupportedSnafu {
868 feat: "ALTER TABLE MODIFY COLUMN to JSON2 type",
869 }
870 .fail();
871 }
872
873 let (target_type, target_type_extension) = ColumnDataTypeWrapper::try_from(target_type)
874 .map(|w| w.to_parts())
875 .context(ColumnDataTypeSnafu)?;
876 if is_interval_type(&target_type) {
877 return NotSupportedSnafu {
878 feat: "Modify column type to interval type",
879 }
880 .fail();
881 }
882 AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
883 modify_column_types: vec![ModifyColumnType {
884 column_name: column_name.value,
885 target_type: target_type as i32,
886 target_type_extension,
887 }],
888 })
889 }
890 AlterTableOperation::SetJsonSettings {
891 column_name,
892 json2_options,
893 } => {
894 let settings = match json2_options {
895 Some(options) => options.build_json_settings().context(ParseSqlSnafu)?,
896 None => datatypes::json::JsonSettings::new_v2(),
897 };
898
899 AlterTableKind::SetJsonSettings(SetJsonSettings {
900 column_name: column_name.value,
901 settings: Some(json_settings_to_proto(settings)?),
902 })
903 }
904 AlterTableOperation::DropColumn { name } => AlterTableKind::DropColumns(DropColumns {
905 drop_columns: vec![DropColumn {
906 name: name.value.clone(),
907 }],
908 }),
909 AlterTableOperation::RenameTable { new_table_name } => {
910 AlterTableKind::RenameTable(RenameTable {
911 new_table_name: new_table_name.clone(),
912 })
913 }
914 AlterTableOperation::SetTableOptions { options } => {
915 AlterTableKind::SetTableOptions(SetTableOptions {
916 table_options: options.into_iter().map(Into::into).collect(),
917 })
918 }
919 AlterTableOperation::UnsetTableOptions { keys } => {
920 AlterTableKind::UnsetTableOptions(UnsetTableOptions { keys })
921 }
922 AlterTableOperation::Repartition { .. } => {
923 return NotSupportedSnafu {
924 feat: "ALTER TABLE ... REPARTITION",
925 }
926 .fail();
927 }
928 AlterTableOperation::Partition { .. } => {
929 return NotSupportedSnafu {
930 feat: "ALTER TABLE ... PARTITION ON COLUMNS",
931 }
932 .fail();
933 }
934 AlterTableOperation::SetIndex { options } => {
935 let option = match options {
936 sql::statements::alter::SetIndexOperation::Fulltext {
937 column_name,
938 options,
939 } => SetIndex {
940 options: Some(set_index::Options::Fulltext(SetFulltext {
941 column_name: column_name.value,
942 enable: options.enable,
943 analyzer: match options.analyzer {
944 FulltextAnalyzer::English => Analyzer::English.into(),
945 FulltextAnalyzer::Chinese => Analyzer::Chinese.into(),
946 },
947 case_sensitive: options.case_sensitive,
948 backend: match options.backend {
949 FulltextBackend::Bloom => PbFulltextBackend::Bloom.into(),
950 FulltextBackend::Tantivy => PbFulltextBackend::Tantivy.into(),
951 },
952 granularity: options.granularity as u64,
953 false_positive_rate: options.false_positive_rate(),
954 })),
955 },
956 sql::statements::alter::SetIndexOperation::Inverted { column_name } => SetIndex {
957 options: Some(set_index::Options::Inverted(SetInverted {
958 column_name: column_name.value,
959 })),
960 },
961 sql::statements::alter::SetIndexOperation::Skipping {
962 column_name,
963 options,
964 } => SetIndex {
965 options: Some(set_index::Options::Skipping(SetSkipping {
966 column_name: column_name.value,
967 enable: true,
968 granularity: options.granularity as u64,
969 false_positive_rate: options.false_positive_rate(),
970 skipping_index_type: match options.index_type {
971 SkippingIndexType::BloomFilter => {
972 PbSkippingIndexType::BloomFilter.into()
973 }
974 },
975 })),
976 },
977 };
978 AlterTableKind::SetIndexes(SetIndexes {
979 set_indexes: vec![option],
980 })
981 }
982 AlterTableOperation::UnsetIndex { options } => {
983 let option = match options {
984 sql::statements::alter::UnsetIndexOperation::Fulltext { column_name } => {
985 UnsetIndex {
986 options: Some(unset_index::Options::Fulltext(UnsetFulltext {
987 column_name: column_name.value,
988 })),
989 }
990 }
991 sql::statements::alter::UnsetIndexOperation::Inverted { column_name } => {
992 UnsetIndex {
993 options: Some(unset_index::Options::Inverted(UnsetInverted {
994 column_name: column_name.value,
995 })),
996 }
997 }
998 sql::statements::alter::UnsetIndexOperation::Skipping { column_name } => {
999 UnsetIndex {
1000 options: Some(unset_index::Options::Skipping(UnsetSkipping {
1001 column_name: column_name.value,
1002 })),
1003 }
1004 }
1005 };
1006
1007 AlterTableKind::UnsetIndexes(UnsetIndexes {
1008 unset_indexes: vec![option],
1009 })
1010 }
1011 AlterTableOperation::DropDefaults { columns } => {
1012 AlterTableKind::DropDefaults(DropDefaults {
1013 drop_defaults: columns
1014 .into_iter()
1015 .map(|col| {
1016 let column_name = col.0.to_string();
1017 Ok(api::v1::DropDefault { column_name })
1018 })
1019 .collect::<Result<Vec<_>>>()?,
1020 })
1021 }
1022 AlterTableOperation::SetDefaults { defaults } => AlterTableKind::SetDefaults(SetDefaults {
1023 set_defaults: defaults
1024 .into_iter()
1025 .map(|col| {
1026 let column_name = col.column_name.to_string();
1027 let default_constraint = serde_json::to_string(&col.default_constraint)
1028 .context(EncodeJsonSnafu)?
1029 .into_bytes();
1030 Ok(api::v1::SetDefault {
1031 column_name,
1032 default_constraint,
1033 })
1034 })
1035 .collect::<Result<Vec<_>>>()?,
1036 }),
1037 };
1038
1039 Ok(AlterTableExpr {
1040 catalog_name,
1041 schema_name,
1042 table_name,
1043 kind: Some(kind),
1044 })
1045}
1046
1047pub fn to_alter_database_expr(
1049 alter_database: AlterDatabase,
1050 query_ctx: &QueryContextRef,
1051) -> Result<AlterDatabaseExpr> {
1052 let catalog = query_ctx.current_catalog();
1053 let schema = alter_database.database_name;
1054
1055 let kind = match alter_database.alter_operation {
1056 AlterDatabaseOperation::SetDatabaseOption { options } => {
1057 let options = options.into_iter().map(Into::into).collect();
1058 AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1059 set_database_options: options,
1060 })
1061 }
1062 AlterDatabaseOperation::UnsetDatabaseOption { keys } => {
1063 AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys })
1064 }
1065 };
1066
1067 Ok(AlterDatabaseExpr {
1068 catalog_name: catalog.to_string(),
1069 schema_name: schema.to_string(),
1070 kind: Some(kind),
1071 })
1072}
1073
1074pub fn to_create_view_expr(
1076 stmt: CreateView,
1077 logical_plan: Vec<u8>,
1078 table_names: Vec<TableName>,
1079 columns: Vec<String>,
1080 plan_columns: Vec<String>,
1081 definition: String,
1082 query_ctx: QueryContextRef,
1083) -> Result<CreateViewExpr> {
1084 let (catalog_name, schema_name, view_name) = table_idents_to_full_name(&stmt.name, &query_ctx)
1085 .map_err(BoxedError::new)
1086 .context(ExternalSnafu)?;
1087
1088 let expr = CreateViewExpr {
1089 catalog_name,
1090 schema_name,
1091 view_name,
1092 logical_plan,
1093 create_if_not_exists: stmt.if_not_exists,
1094 or_replace: stmt.or_replace,
1095 table_names,
1096 columns,
1097 plan_columns,
1098 definition,
1099 };
1100
1101 Ok(expr)
1102}
1103
1104pub fn to_create_flow_task_expr(
1105 create_flow: CreateFlow,
1106 query_ctx: &QueryContextRef,
1107) -> Result<CreateFlowExpr> {
1108 let sink_table_ref = object_name_to_table_reference(create_flow.sink_table_name.clone(), true)
1110 .with_context(|_| ConvertIdentifierSnafu {
1111 ident: create_flow.sink_table_name.to_string(),
1112 })?;
1113 let catalog = sink_table_ref
1114 .catalog()
1115 .unwrap_or(query_ctx.current_catalog())
1116 .to_string();
1117 let schema = sink_table_ref
1118 .schema()
1119 .map(|s| s.to_owned())
1120 .unwrap_or(query_ctx.current_schema());
1121
1122 let sink_table_name = TableName {
1123 catalog_name: catalog,
1124 schema_name: schema,
1125 table_name: sink_table_ref.table().to_string(),
1126 };
1127
1128 let source_table_names = extract_tables_from_query(&create_flow.query)
1129 .map(|name| {
1130 let reference =
1131 object_name_to_table_reference(name.clone(), true).with_context(|_| {
1132 ConvertIdentifierSnafu {
1133 ident: name.to_string(),
1134 }
1135 })?;
1136 let catalog = reference
1137 .catalog()
1138 .unwrap_or(query_ctx.current_catalog())
1139 .to_string();
1140 let schema = reference
1141 .schema()
1142 .map(|s| s.to_string())
1143 .unwrap_or(query_ctx.current_schema());
1144
1145 let table_name = TableName {
1146 catalog_name: catalog,
1147 schema_name: schema,
1148 table_name: reference.table().to_string(),
1149 };
1150 Ok(table_name)
1151 })
1152 .collect::<Result<Vec<_>>>()?;
1153
1154 let eval_interval = create_flow.eval_interval;
1155
1156 let flow_options = stringify_flow_options(create_flow.flow_options)?;
1157 Ok(CreateFlowExpr {
1158 catalog_name: query_ctx.current_catalog().to_string(),
1159 flow_name: sanitize_flow_name(create_flow.flow_name)?,
1160 source_table_names,
1161 sink_table_name: Some(sink_table_name),
1162 or_replace: create_flow.or_replace,
1163 create_if_not_exists: create_flow.if_not_exists,
1164 expire_after: create_flow.expire_after.map(|value| ExpireAfter { value }),
1165 eval_interval: eval_interval.map(|seconds| api::v1::EvalInterval { seconds }),
1166 comment: create_flow.comment.unwrap_or_default(),
1167 sql: create_flow.query.to_string(),
1168 flow_options,
1169 })
1170}
1171
1172fn stringify_flow_options(flow_options: OptionMap) -> Result<HashMap<String, String>> {
1173 let options_len = flow_options.len();
1174 let flow_options = flow_options.into_map();
1175 ensure!(
1176 flow_options.len() == options_len,
1177 InvalidSqlSnafu {
1178 err_msg: "flow options only support scalar string-compatible values".to_string(),
1179 }
1180 );
1181 Ok(flow_options)
1182}
1183
1184fn sanitize_flow_name(mut flow_name: ObjectName) -> Result<String> {
1186 ensure!(
1187 flow_name.0.len() == 1,
1188 InvalidFlowNameSnafu {
1189 name: flow_name.to_string(),
1190 }
1191 );
1192 Ok(flow_name.0.swap_remove(0).to_string_unquoted())
1194}
1195
1196#[cfg(test)]
1197mod tests {
1198 use std::collections::HashMap;
1199
1200 use api::v1::{SetDatabaseOptions, UnsetDatabaseOptions};
1201 use datatypes::extension::json::is_json2_extension_type;
1202 use datatypes::value::Value;
1203 use session::context::{QueryContext, QueryContextBuilder};
1204 use sql::dialect::GreptimeDbDialect;
1205 use sql::parser::{ParseOptions, ParserContext};
1206 use sql::statements::statement::Statement;
1207 use store_api::storage::ColumnDefaultConstraint;
1208
1209 use super::*;
1210
1211 #[test]
1212 fn test_to_alter_add_json2_column_preserves_extension() {
1213 let sql = "ALTER TABLE monitor ADD COLUMN payload JSON2;";
1214 let stmt =
1215 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1216 .unwrap()
1217 .pop()
1218 .unwrap();
1219 let Statement::AlterTable(alter_table) = stmt else {
1220 unreachable!()
1221 };
1222
1223 let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1224 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = expr.kind.unwrap() else {
1225 unreachable!()
1226 };
1227 let column_def = add_columns[0].column_def.as_ref().unwrap();
1228 let column_schema = try_as_column_schema(column_def).unwrap();
1229 let schema = Schema::new(vec![column_schema]);
1230
1231 assert!(is_json2_extension_type(&schema.arrow_schema().fields()[0]));
1232 }
1233
1234 #[test]
1235 fn test_to_alter_add_json2_column_preserves_settings() {
1236 let sql = "ALTER TABLE monitor ADD COLUMN payload JSON2(max_auto_expanded_paths = 0, service STRING, nested.value BIGINT)";
1237 let stmt =
1238 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1239 .unwrap()
1240 .pop()
1241 .unwrap();
1242 let Statement::AlterTable(alter_table) = stmt else {
1243 unreachable!()
1244 };
1245 let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1246 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = expr.kind.unwrap() else {
1247 unreachable!()
1248 };
1249 let column_schema =
1250 try_as_column_schema(add_columns[0].column_def.as_ref().unwrap()).unwrap();
1251 let metadata: datatypes::extension::json::JsonMetadata =
1252 serde_json::from_str(&column_schema.metadata()["ARROW:extension:metadata"]).unwrap();
1253 let settings = metadata.json_settings();
1254 assert_eq!(Some(0), settings.max_auto_expanded_paths());
1255 assert_eq!(2, settings.type_hints().len());
1256 assert_eq!(vec!["service"], settings.type_hints()[0].path);
1257 assert_eq!(vec!["nested", "value"], settings.type_hints()[1].path);
1258 }
1259
1260 #[test]
1261 fn test_create_flow_tql_expr() {
1262 let sql = r#"
1263CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1264TQL EVAL (0, 15, '5s') count_values("status_code", http_requests);"#;
1265 let stmt =
1266 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1267
1268 assert!(
1269 stmt.is_err(),
1270 "Expected error for invalid TQL EVAL parameters: {:#?}",
1271 stmt
1272 );
1273
1274 let sql = r#"
1275CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1276TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests);"#;
1277 let stmt =
1278 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1279 .unwrap()
1280 .pop()
1281 .unwrap();
1282
1283 let Statement::CreateFlow(create_flow) = stmt else {
1284 unreachable!()
1285 };
1286 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1287
1288 let to_dot_sep =
1289 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1290 assert_eq!("calc_reqs", expr.flow_name);
1291 assert_eq!("greptime", expr.catalog_name);
1292 assert_eq!(
1293 "greptime.public.cnt_reqs",
1294 expr.sink_table_name.map(to_dot_sep).unwrap()
1295 );
1296 assert_eq!(1, expr.source_table_names.len());
1297 assert_eq!(
1298 "greptime.public.http_requests",
1299 to_dot_sep(expr.source_table_names[0].clone())
1300 );
1301 assert_eq!(
1302 r#"TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests)"#,
1303 expr.sql
1304 );
1305
1306 let sql = r#"
1307CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1308TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__schema__="greptime_private"});"#;
1309 let stmt =
1310 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1311 .unwrap()
1312 .pop()
1313 .unwrap();
1314 let Statement::CreateFlow(create_flow) = stmt else {
1315 unreachable!()
1316 };
1317 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1318 assert_eq!(1, expr.source_table_names.len());
1319 assert_eq!(
1320 "greptime.greptime_private.http_requests",
1321 to_dot_sep(expr.source_table_names[0].clone())
1322 );
1323
1324 let sql = r#"
1325CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1326TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__database__="greptime_private"});"#;
1327 let stmt =
1328 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1329 .unwrap()
1330 .pop()
1331 .unwrap();
1332 let Statement::CreateFlow(create_flow) = stmt else {
1333 unreachable!()
1334 };
1335 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1336 assert_eq!(1, expr.source_table_names.len());
1337 assert_eq!(
1338 "greptime.greptime_private.http_requests",
1339 to_dot_sep(expr.source_table_names[0].clone())
1340 );
1341 }
1342
1343 #[test]
1344 fn test_create_flow_tql_cte_source_tables() {
1345 let sql = r#"
1346CREATE FLOW calc_cte
1347SINK TO metric_cte_sink
1348EVAL INTERVAL '1m'
1349AS
1350WITH tql(ts, the_value) AS (
1351 TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1352)
1353SELECT * FROM tql;
1354"#;
1355
1356 let stmt =
1357 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1358 .unwrap()
1359 .pop()
1360 .unwrap();
1361
1362 let Statement::CreateFlow(create_flow) = stmt else {
1363 unreachable!()
1364 };
1365 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1366
1367 let to_dot_sep =
1368 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1369 assert_eq!(1, expr.source_table_names.len());
1370 assert_eq!(
1371 "greptime.public.metric_cte",
1372 to_dot_sep(expr.source_table_names[0].clone())
1373 );
1374 }
1375
1376 #[test]
1377 fn test_create_flow_tql_cte_source_tables_quoted_cte_name() {
1378 let sql = r#"
1379CREATE FLOW calc_cte
1380SINK TO metric_cte_sink
1381EVAL INTERVAL '1m'
1382AS
1383WITH "TQL"(ts, the_value) AS (
1384 TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1385)
1386SELECT * FROM "TQL";
1387"#;
1388
1389 let stmt =
1390 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1391 .unwrap()
1392 .pop()
1393 .unwrap();
1394
1395 let Statement::CreateFlow(create_flow) = stmt else {
1396 unreachable!()
1397 };
1398 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1399
1400 let to_dot_sep =
1401 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1402 assert_eq!(1, expr.source_table_names.len());
1403 assert_eq!(
1404 "greptime.public.metric_cte",
1405 to_dot_sep(expr.source_table_names[0].clone())
1406 );
1407 }
1408
1409 #[test]
1410 fn test_create_flow_tql_cte_source_tables_same_name() {
1411 let sql = r#"
1412CREATE FLOW calc_cte
1413SINK TO metric_cte_sink
1414EVAL INTERVAL '1m'
1415AS
1416WITH tql(ts, the_value) AS (
1417 TQL EVAL (now() - '1m'::interval, now(), '5s') tql
1418)
1419SELECT * FROM tql;
1420"#;
1421
1422 let stmt =
1423 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1424 .unwrap()
1425 .pop()
1426 .unwrap();
1427
1428 let Statement::CreateFlow(create_flow) = stmt else {
1429 unreachable!()
1430 };
1431 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1432
1433 let to_dot_sep =
1434 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1435 assert_eq!(1, expr.source_table_names.len());
1436 assert_eq!(
1437 "greptime.public.tql",
1438 to_dot_sep(expr.source_table_names[0].clone())
1439 );
1440 }
1441
1442 #[test]
1443 fn test_create_flow_expr() {
1444 let sql = r"
1445CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS
1446SELECT
1447 DISTINCT number as dis
1448FROM
1449 distinct_basic;";
1450 let stmt =
1451 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1452 .unwrap()
1453 .pop()
1454 .unwrap();
1455
1456 let Statement::CreateFlow(create_flow) = stmt else {
1457 unreachable!()
1458 };
1459 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1460
1461 let to_dot_sep =
1462 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1463 assert_eq!("test_distinct_basic", expr.flow_name);
1464 assert_eq!("greptime", expr.catalog_name);
1465 assert_eq!(
1466 "greptime.public.out_distinct_basic",
1467 expr.sink_table_name.map(to_dot_sep).unwrap()
1468 );
1469 assert_eq!(1, expr.source_table_names.len());
1470 assert_eq!(
1471 "greptime.public.distinct_basic",
1472 to_dot_sep(expr.source_table_names[0].clone())
1473 );
1474 assert_eq!(
1475 r"SELECT
1476 DISTINCT number as dis
1477FROM
1478 distinct_basic",
1479 expr.sql
1480 );
1481
1482 let sql = r"
1483CREATE FLOW `task_2`
1484SINK TO schema_1.table_1
1485AS
1486SELECT max(c1), min(c2) FROM schema_2.table_2;";
1487 let stmt =
1488 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1489 .unwrap()
1490 .pop()
1491 .unwrap();
1492
1493 let Statement::CreateFlow(create_flow) = stmt else {
1494 unreachable!()
1495 };
1496 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1497
1498 let to_dot_sep =
1499 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1500 assert_eq!("task_2", expr.flow_name);
1501 assert_eq!("greptime", expr.catalog_name);
1502 assert_eq!(
1503 "greptime.schema_1.table_1",
1504 expr.sink_table_name.map(to_dot_sep).unwrap()
1505 );
1506 assert_eq!(1, expr.source_table_names.len());
1507 assert_eq!(
1508 "greptime.schema_2.table_2",
1509 to_dot_sep(expr.source_table_names[0].clone())
1510 );
1511 assert_eq!("SELECT max(c1), min(c2) FROM schema_2.table_2", expr.sql);
1512 assert!(expr.flow_options.is_empty());
1513
1514 let sql = r"
1515CREATE FLOW task_3
1516SINK TO schema_1.table_1
1517WITH (defer_on_missing_source = 'true', foo = 'bar')
1518AS
1519SELECT max(c1), min(c2) FROM schema_2.table_2;";
1520 let stmt =
1521 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1522 .unwrap()
1523 .pop()
1524 .unwrap();
1525
1526 let Statement::CreateFlow(create_flow) = stmt else {
1527 unreachable!()
1528 };
1529 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1530 assert_eq!(
1531 expr.flow_options,
1532 HashMap::from([
1533 ("defer_on_missing_source".to_string(), "true".to_string()),
1534 ("foo".to_string(), "bar".to_string()),
1535 ])
1536 );
1537
1538 let sql = r"
1539CREATE FLOW task_4
1540SINK TO schema_1.table_1
1541WITH (defer_on_missing_source = true)
1542AS
1543SELECT max(c1), min(c2) FROM schema_2.table_2;";
1544 let stmt =
1545 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1546 .unwrap()
1547 .pop()
1548 .unwrap();
1549
1550 let Statement::CreateFlow(create_flow) = stmt else {
1551 unreachable!()
1552 };
1553 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1554 assert_eq!(
1555 expr.flow_options,
1556 HashMap::from([("defer_on_missing_source".to_string(), "true".to_string(),)])
1557 );
1558
1559 let sql = r"
1560CREATE FLOW task_5
1561SINK TO schema_1.table_1
1562WITH (defer_on_missing_source = [true])
1563AS
1564SELECT max(c1), min(c2) FROM schema_2.table_2;";
1565 let stmt =
1566 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1567 .unwrap()
1568 .pop()
1569 .unwrap();
1570
1571 let Statement::CreateFlow(create_flow) = stmt else {
1572 unreachable!()
1573 };
1574 let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1575 assert!(res.is_err());
1576 assert!(
1577 res.unwrap_err()
1578 .to_string()
1579 .contains("flow options only support scalar string-compatible values")
1580 );
1581
1582 let sql = r"
1583CREATE FLOW abc.`task_2`
1584SINK TO schema_1.table_1
1585AS
1586SELECT max(c1), min(c2) FROM schema_2.table_2;";
1587 let stmt =
1588 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1589 .unwrap()
1590 .pop()
1591 .unwrap();
1592
1593 let Statement::CreateFlow(create_flow) = stmt else {
1594 unreachable!()
1595 };
1596 let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1597
1598 assert!(res.is_err());
1599 assert!(
1600 res.unwrap_err()
1601 .to_string()
1602 .contains("Invalid flow name: abc.`task_2`")
1603 );
1604 }
1605
1606 #[test]
1607 fn test_create_to_expr() {
1608 let sql = "CREATE TABLE monitor (host STRING,ts TIMESTAMP,TIME INDEX (ts),PRIMARY KEY(host)) ENGINE=mito WITH(ttl='3days', write_buffer_size='1024KB');";
1609 let stmt =
1610 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1611 .unwrap()
1612 .pop()
1613 .unwrap();
1614
1615 let Statement::CreateTable(create_table) = stmt else {
1616 unreachable!()
1617 };
1618 let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1619 assert_eq!("3days", expr.table_options.get("ttl").unwrap());
1620 assert_eq!(
1621 "1.0MiB",
1622 expr.table_options.get("write_buffer_size").unwrap()
1623 );
1624
1625 let sql = "CREATE TABLE monitor (ts TIMESTAMP TIME INDEX) WITH(skip_wal='false');";
1626 let stmt =
1627 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1628 .unwrap()
1629 .pop()
1630 .unwrap();
1631 let Statement::CreateTable(create_table) = stmt else {
1632 unreachable!()
1633 };
1634 let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1635 assert_eq!(
1636 Some("false"),
1637 expr.table_options
1638 .get(store_api::mito_engine_options::SKIP_WAL_KEY)
1639 .map(String::as_str)
1640 );
1641 }
1642
1643 #[test]
1644 fn test_invalid_create_to_expr() {
1645 let cases = [
1646 "CREATE TABLE monitor (host STRING primary key, ts TIMESTAMP TIME INDEX, some_column text, some_column string);",
1648 "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, some_column STRING, PRIMARY KEY (some_column, host, some_column));",
1650 "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, PRIMARY KEY (host, ts));",
1652 ];
1653
1654 for sql in cases {
1655 let stmt = ParserContext::create_with_dialect(
1656 sql,
1657 &GreptimeDbDialect {},
1658 ParseOptions::default(),
1659 )
1660 .unwrap()
1661 .pop()
1662 .unwrap();
1663 let Statement::CreateTable(create_table) = stmt else {
1664 unreachable!()
1665 };
1666 create_to_expr(&create_table, &QueryContext::arc()).unwrap_err();
1667 }
1668 }
1669
1670 #[test]
1671 fn test_create_to_expr_with_default_timestamp_value() {
1672 let sql = "CREATE TABLE monitor (v double,ts TIMESTAMP default '2024-01-30T00:01:01',TIME INDEX (ts)) engine=mito;";
1673 let stmt =
1674 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1675 .unwrap()
1676 .pop()
1677 .unwrap();
1678
1679 let Statement::CreateTable(create_table) = stmt else {
1680 unreachable!()
1681 };
1682
1683 let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1685 let ts_column = &expr.column_defs[1];
1686 let constraint = assert_ts_column(ts_column);
1687 assert!(
1688 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1689 if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1690 );
1691
1692 let ctx = QueryContextBuilder::default()
1694 .timezone(Timezone::from_tz_string("+08:00").unwrap())
1695 .build()
1696 .into();
1697 let expr = create_to_expr(&create_table, &ctx).unwrap();
1698 let ts_column = &expr.column_defs[1];
1699 let constraint = assert_ts_column(ts_column);
1700 assert!(
1701 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1702 if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1703 );
1704 }
1705
1706 fn assert_ts_column(ts_column: &api::v1::ColumnDef) -> ColumnDefaultConstraint {
1707 assert_eq!("ts", ts_column.name);
1708 assert_eq!(
1709 ColumnDataType::TimestampMillisecond as i32,
1710 ts_column.data_type
1711 );
1712 assert!(!ts_column.default_constraint.is_empty());
1713
1714 ColumnDefaultConstraint::try_from(&ts_column.default_constraint[..]).unwrap()
1715 }
1716
1717 #[test]
1718 fn test_to_alter_expr() {
1719 let sql = "ALTER DATABASE greptime SET key1='value1', key2='value2';";
1720 let stmt =
1721 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1722 .unwrap()
1723 .pop()
1724 .unwrap();
1725
1726 let Statement::AlterDatabase(alter_database) = stmt else {
1727 unreachable!()
1728 };
1729
1730 let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1731 let kind = expr.kind.unwrap();
1732
1733 let AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1734 set_database_options,
1735 }) = kind
1736 else {
1737 unreachable!()
1738 };
1739
1740 assert_eq!(2, set_database_options.len());
1741 assert_eq!("key1", set_database_options[0].key);
1742 assert_eq!("value1", set_database_options[0].value);
1743 assert_eq!("key2", set_database_options[1].key);
1744 assert_eq!("value2", set_database_options[1].value);
1745
1746 let sql = "ALTER DATABASE greptime UNSET key1, key2;";
1747 let stmt =
1748 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1749 .unwrap()
1750 .pop()
1751 .unwrap();
1752
1753 let Statement::AlterDatabase(alter_database) = stmt else {
1754 unreachable!()
1755 };
1756
1757 let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1758 let kind = expr.kind.unwrap();
1759
1760 let AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys }) = kind else {
1761 unreachable!()
1762 };
1763
1764 assert_eq!(2, keys.len());
1765 assert!(keys.contains(&"key1".to_string()));
1766 assert!(keys.contains(&"key2".to_string()));
1767
1768 let sql = "ALTER TABLE monitor add column ts TIMESTAMP default '2024-01-30T00:01:01';";
1769 let stmt =
1770 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1771 .unwrap()
1772 .pop()
1773 .unwrap();
1774
1775 let Statement::AlterTable(alter_table) = stmt else {
1776 unreachable!()
1777 };
1778
1779 let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1781 let kind = expr.kind.unwrap();
1782
1783 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1784 unreachable!()
1785 };
1786
1787 assert_eq!(1, add_columns.len());
1788 let ts_column = add_columns[0].column_def.clone().unwrap();
1789 let constraint = assert_ts_column(&ts_column);
1790 assert!(
1791 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1792 if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1793 );
1794
1795 let ctx = QueryContextBuilder::default()
1798 .timezone(Timezone::from_tz_string("+08:00").unwrap())
1799 .build()
1800 .into();
1801 let expr = to_alter_table_expr(alter_table, &ctx).unwrap();
1802 let kind = expr.kind.unwrap();
1803
1804 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1805 unreachable!()
1806 };
1807
1808 assert_eq!(1, add_columns.len());
1809 let ts_column = add_columns[0].column_def.clone().unwrap();
1810 let constraint = assert_ts_column(&ts_column);
1811 assert!(
1812 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1813 if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1814 );
1815 }
1816
1817 #[test]
1818 fn test_to_alter_modify_column_type_expr() {
1819 let sql = "ALTER TABLE monitor MODIFY COLUMN mem_usage STRING;";
1820 let stmt =
1821 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1822 .unwrap()
1823 .pop()
1824 .unwrap();
1825
1826 let Statement::AlterTable(alter_table) = stmt else {
1827 unreachable!()
1828 };
1829
1830 let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1832 let kind = expr.kind.unwrap();
1833
1834 let AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
1835 modify_column_types,
1836 }) = kind
1837 else {
1838 unreachable!()
1839 };
1840
1841 assert_eq!(1, modify_column_types.len());
1842 let modify_column_type = &modify_column_types[0];
1843
1844 assert_eq!("mem_usage", modify_column_type.column_name);
1845 assert_eq!(
1846 ColumnDataType::String as i32,
1847 modify_column_type.target_type
1848 );
1849 assert!(modify_column_type.target_type_extension.is_none());
1850 }
1851
1852 #[test]
1853 fn test_to_alter_set_json_settings_expr() {
1854 let sql = "ALTER TABLE monitor MODIFY COLUMN payload JSON2 (service STRING);";
1855 let stmt =
1856 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1857 .unwrap()
1858 .pop()
1859 .unwrap();
1860
1861 let Statement::AlterTable(alter_table) = stmt else {
1862 unreachable!()
1863 };
1864 let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1865 let kind = expr.kind.unwrap();
1866
1867 let AlterTableKind::SetJsonSettings(modify) = kind else {
1868 unreachable!()
1869 };
1870 assert_eq!("payload", modify.column_name);
1871 let settings = modify.settings.as_ref().unwrap();
1872 assert_eq!(Some(100), settings.max_auto_expanded_paths);
1873 assert_eq!(1, settings.type_hints.len());
1874 assert_eq!(["service"], &settings.type_hints[0].path[..]);
1875
1876 let sql = "ALTER TABLE monitor MODIFY COLUMN payload JSON2;";
1877 let stmt =
1878 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1879 .unwrap()
1880 .pop()
1881 .unwrap();
1882
1883 let Statement::AlterTable(alter_table) = stmt else {
1884 unreachable!()
1885 };
1886 let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
1887 let kind = expr.kind.unwrap();
1888
1889 let AlterTableKind::SetJsonSettings(modify) = kind else {
1890 unreachable!()
1891 };
1892 let settings = modify.settings.as_ref().unwrap();
1893 assert_eq!(Some(100), settings.max_auto_expanded_paths);
1894 assert!(settings.type_hints.is_empty());
1895 }
1896
1897 #[test]
1898 fn test_to_repartition_request() {
1899 let sql = r#"
1900ALTER TABLE metrics REPARTITION (
1901 device_id < 100
1902) INTO (
1903 device_id < 100 AND area < 'South',
1904 device_id < 100 AND area >= 'South'
1905);"#;
1906 let stmt =
1907 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1908 .unwrap()
1909 .pop()
1910 .unwrap();
1911
1912 let Statement::AlterTable(alter_table) = stmt else {
1913 unreachable!()
1914 };
1915
1916 let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1917 assert_eq!("greptime", request.catalog_name);
1918 assert_eq!("public", request.schema_name);
1919 assert_eq!("metrics", request.table_name);
1920 let RepartitionSource::Partitions {
1921 from_exprs,
1922 target_partition_columns,
1923 } = request.source
1924 else {
1925 unreachable!()
1926 };
1927 assert!(target_partition_columns.is_none());
1928 assert_eq!(
1929 from_exprs
1930 .into_iter()
1931 .map(|x| x.to_string())
1932 .collect::<Vec<_>>(),
1933 vec!["device_id < 100".to_string()]
1934 );
1935 assert_eq!(
1936 request
1937 .into_exprs
1938 .into_iter()
1939 .map(|x| x.to_string())
1940 .collect::<Vec<_>>(),
1941 vec![
1942 "device_id < 100 AND area < 'South'".to_string(),
1943 "device_id < 100 AND area >= 'South'".to_string()
1944 ]
1945 );
1946 }
1947
1948 #[test]
1949 fn test_to_repartition_request_with_target_partition_columns() {
1950 let sql = r#"
1951ALTER TABLE metrics REPARTITION (
1952 device_id < 100
1953) ON COLUMNS (device_id, area) INTO (
1954 device_id < 100 AND area < 'South',
1955 device_id < 100 AND area >= 'South'
1956);"#;
1957 let stmt =
1958 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1959 .unwrap()
1960 .pop()
1961 .unwrap();
1962
1963 let Statement::AlterTable(alter_table) = stmt else {
1964 unreachable!()
1965 };
1966
1967 let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1968 let RepartitionSource::Partitions {
1969 target_partition_columns,
1970 ..
1971 } = request.source
1972 else {
1973 unreachable!()
1974 };
1975
1976 assert_eq!(
1977 target_partition_columns,
1978 Some(vec!["device_id".to_string(), "area".to_string()])
1979 );
1980 }
1981
1982 #[test]
1983 fn test_to_repartition_request_with_unpartitioned_source() {
1984 let sql = r#"
1985ALTER TABLE metrics PARTITION ON COLUMNS (device_id, area) (
1986 device_id < 100 AND area < 'South',
1987 device_id < 100 AND area >= 'South'
1988);"#;
1989 let stmt =
1990 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1991 .unwrap()
1992 .pop()
1993 .unwrap();
1994
1995 let Statement::AlterTable(alter_table) = stmt else {
1996 unreachable!()
1997 };
1998
1999 let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
2000 assert_eq!("greptime", request.catalog_name);
2001 assert_eq!("public", request.schema_name);
2002 assert_eq!("metrics", request.table_name);
2003 let RepartitionSource::Unpartitioned { partition_columns } = request.source else {
2004 unreachable!()
2005 };
2006 assert_eq!(partition_columns, vec!["device_id", "area"]);
2007 assert_eq!(
2008 request
2009 .into_exprs
2010 .into_iter()
2011 .map(|x| x.to_string())
2012 .collect::<Vec<_>>(),
2013 vec![
2014 "device_id < 100 AND area < 'South'".to_string(),
2015 "device_id < 100 AND area >= 'South'".to_string()
2016 ]
2017 );
2018 }
2019
2020 fn new_test_table_names() -> Vec<TableName> {
2021 vec![
2022 TableName {
2023 catalog_name: "greptime".to_string(),
2024 schema_name: "public".to_string(),
2025 table_name: "a_table".to_string(),
2026 },
2027 TableName {
2028 catalog_name: "greptime".to_string(),
2029 schema_name: "public".to_string(),
2030 table_name: "b_table".to_string(),
2031 },
2032 ]
2033 }
2034
2035 #[test]
2036 fn test_to_create_view_expr() {
2037 let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
2038 let stmt =
2039 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2040 .unwrap()
2041 .pop()
2042 .unwrap();
2043
2044 let Statement::CreateView(stmt) = stmt else {
2045 unreachable!()
2046 };
2047
2048 let logical_plan = vec![1, 2, 3];
2049 let table_names = new_test_table_names();
2050 let columns = vec!["a".to_string()];
2051 let plan_columns = vec!["number".to_string()];
2052
2053 let expr = to_create_view_expr(
2054 stmt,
2055 logical_plan.clone(),
2056 table_names.clone(),
2057 columns.clone(),
2058 plan_columns.clone(),
2059 sql.to_string(),
2060 QueryContext::arc(),
2061 )
2062 .unwrap();
2063
2064 assert_eq!("greptime", expr.catalog_name);
2065 assert_eq!("public", expr.schema_name);
2066 assert_eq!("test", expr.view_name);
2067 assert!(!expr.create_if_not_exists);
2068 assert!(!expr.or_replace);
2069 assert_eq!(logical_plan, expr.logical_plan);
2070 assert_eq!(table_names, expr.table_names);
2071 assert_eq!(sql, expr.definition);
2072 assert_eq!(columns, expr.columns);
2073 assert_eq!(plan_columns, expr.plan_columns);
2074 }
2075
2076 #[test]
2077 fn test_to_create_view_expr_complex() {
2078 let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test.test_view AS SELECT * FROM NUMBERS";
2079 let stmt =
2080 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2081 .unwrap()
2082 .pop()
2083 .unwrap();
2084
2085 let Statement::CreateView(stmt) = stmt else {
2086 unreachable!()
2087 };
2088
2089 let logical_plan = vec![1, 2, 3];
2090 let table_names = new_test_table_names();
2091 let columns = vec!["a".to_string()];
2092 let plan_columns = vec!["number".to_string()];
2093
2094 let expr = to_create_view_expr(
2095 stmt,
2096 logical_plan.clone(),
2097 table_names.clone(),
2098 columns.clone(),
2099 plan_columns.clone(),
2100 sql.to_string(),
2101 QueryContext::arc(),
2102 )
2103 .unwrap();
2104
2105 assert_eq!("greptime", expr.catalog_name);
2106 assert_eq!("test", expr.schema_name);
2107 assert_eq!("test_view", expr.view_name);
2108 assert!(expr.create_if_not_exists);
2109 assert!(expr.or_replace);
2110 assert_eq!(logical_plan, expr.logical_plan);
2111 assert_eq!(table_names, expr.table_names);
2112 assert_eq!(sql, expr.definition);
2113 assert_eq!(columns, expr.columns);
2114 assert_eq!(plan_columns, expr.plan_columns);
2115 }
2116
2117 #[test]
2118 fn test_expr_to_create() {
2119 let sql = r#"CREATE TABLE IF NOT EXISTS `tt` (
2120 `timestamp` TIMESTAMP(9) NOT NULL,
2121 `ip_address` STRING NULL SKIPPING INDEX WITH(false_positive_rate = '0.01', granularity = '10240', type = 'BLOOM'),
2122 `username` STRING NULL,
2123 `http_method` STRING NULL INVERTED INDEX,
2124 `request_line` STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false', false_positive_rate = '0.01', granularity = '10240'),
2125 `protocol` STRING NULL,
2126 `status_code` INT NULL INVERTED INDEX,
2127 `response_size` BIGINT NULL,
2128 `message` STRING NULL COMPRESSION WITH(level = '3', page_size = '64KiB', type = 'zstd'),
2129 TIME INDEX (`timestamp`),
2130 PRIMARY KEY (`username`, `status_code`)
2131)
2132ENGINE=mito
2133WITH(
2134 append_mode = 'true'
2135)"#;
2136 let stmt =
2137 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
2138 .unwrap()
2139 .pop()
2140 .unwrap();
2141
2142 let Statement::CreateTable(original_create) = stmt else {
2143 unreachable!()
2144 };
2145
2146 let expr = create_to_expr(&original_create, &QueryContext::arc()).unwrap();
2148
2149 let create_table = expr_to_create(&expr, Some('`')).unwrap();
2150 let new_sql = format!("{:#}", create_table);
2151 assert_eq!(sql, new_sql);
2152 }
2153}