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