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 Ok(CreateFlowExpr {
1102 catalog_name: query_ctx.current_catalog().to_string(),
1103 flow_name: sanitize_flow_name(create_flow.flow_name)?,
1104 source_table_names,
1105 sink_table_name: Some(sink_table_name),
1106 or_replace: create_flow.or_replace,
1107 create_if_not_exists: create_flow.if_not_exists,
1108 expire_after: create_flow.expire_after.map(|value| ExpireAfter { value }),
1109 eval_interval: eval_interval.map(|seconds| api::v1::EvalInterval { seconds }),
1110 comment: create_flow.comment.unwrap_or_default(),
1111 sql: create_flow.query.to_string(),
1112 flow_options: stringify_flow_options(create_flow.flow_options)?,
1113 })
1114}
1115
1116fn stringify_flow_options(flow_options: OptionMap) -> Result<HashMap<String, String>> {
1117 let options_len = flow_options.len();
1118 let flow_options = flow_options.into_map();
1119 ensure!(
1120 flow_options.len() == options_len,
1121 InvalidSqlSnafu {
1122 err_msg: "flow options only support scalar string-compatible values".to_string(),
1123 }
1124 );
1125 Ok(flow_options)
1126}
1127
1128fn sanitize_flow_name(mut flow_name: ObjectName) -> Result<String> {
1130 ensure!(
1131 flow_name.0.len() == 1,
1132 InvalidFlowNameSnafu {
1133 name: flow_name.to_string(),
1134 }
1135 );
1136 Ok(flow_name.0.swap_remove(0).to_string_unquoted())
1138}
1139
1140#[cfg(test)]
1141mod tests {
1142 use std::collections::HashMap;
1143
1144 use api::v1::{SetDatabaseOptions, UnsetDatabaseOptions};
1145 use datatypes::value::Value;
1146 use session::context::{QueryContext, QueryContextBuilder};
1147 use sql::dialect::GreptimeDbDialect;
1148 use sql::parser::{ParseOptions, ParserContext};
1149 use sql::statements::statement::Statement;
1150 use store_api::storage::ColumnDefaultConstraint;
1151
1152 use super::*;
1153
1154 #[test]
1155 fn test_create_flow_tql_expr() {
1156 let sql = r#"
1157CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1158TQL EVAL (0, 15, '5s') count_values("status_code", http_requests);"#;
1159 let stmt =
1160 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default());
1161
1162 assert!(
1163 stmt.is_err(),
1164 "Expected error for invalid TQL EVAL parameters: {:#?}",
1165 stmt
1166 );
1167
1168 let sql = r#"
1169CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1170TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests);"#;
1171 let stmt =
1172 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1173 .unwrap()
1174 .pop()
1175 .unwrap();
1176
1177 let Statement::CreateFlow(create_flow) = stmt else {
1178 unreachable!()
1179 };
1180 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1181
1182 let to_dot_sep =
1183 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1184 assert_eq!("calc_reqs", expr.flow_name);
1185 assert_eq!("greptime", expr.catalog_name);
1186 assert_eq!(
1187 "greptime.public.cnt_reqs",
1188 expr.sink_table_name.map(to_dot_sep).unwrap()
1189 );
1190 assert_eq!(1, expr.source_table_names.len());
1191 assert_eq!(
1192 "greptime.public.http_requests",
1193 to_dot_sep(expr.source_table_names[0].clone())
1194 );
1195 assert_eq!(
1196 r#"TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests)"#,
1197 expr.sql
1198 );
1199
1200 let sql = r#"
1201CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1202TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__schema__="greptime_private"});"#;
1203 let stmt =
1204 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1205 .unwrap()
1206 .pop()
1207 .unwrap();
1208 let Statement::CreateFlow(create_flow) = stmt else {
1209 unreachable!()
1210 };
1211 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1212 assert_eq!(1, expr.source_table_names.len());
1213 assert_eq!(
1214 "greptime.greptime_private.http_requests",
1215 to_dot_sep(expr.source_table_names[0].clone())
1216 );
1217
1218 let sql = r#"
1219CREATE FLOW calc_reqs SINK TO cnt_reqs AS
1220TQL EVAL (now() - '15s'::interval, now(), '5s') count_values("status_code", http_requests{__database__="greptime_private"});"#;
1221 let stmt =
1222 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1223 .unwrap()
1224 .pop()
1225 .unwrap();
1226 let Statement::CreateFlow(create_flow) = stmt else {
1227 unreachable!()
1228 };
1229 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1230 assert_eq!(1, expr.source_table_names.len());
1231 assert_eq!(
1232 "greptime.greptime_private.http_requests",
1233 to_dot_sep(expr.source_table_names[0].clone())
1234 );
1235 }
1236
1237 #[test]
1238 fn test_create_flow_tql_cte_source_tables() {
1239 let sql = r#"
1240CREATE FLOW calc_cte
1241SINK TO metric_cte_sink
1242EVAL INTERVAL '1m'
1243AS
1244WITH tql(ts, the_value) AS (
1245 TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1246)
1247SELECT * FROM tql;
1248"#;
1249
1250 let stmt =
1251 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1252 .unwrap()
1253 .pop()
1254 .unwrap();
1255
1256 let Statement::CreateFlow(create_flow) = stmt else {
1257 unreachable!()
1258 };
1259 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1260
1261 let to_dot_sep =
1262 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1263 assert_eq!(1, expr.source_table_names.len());
1264 assert_eq!(
1265 "greptime.public.metric_cte",
1266 to_dot_sep(expr.source_table_names[0].clone())
1267 );
1268 }
1269
1270 #[test]
1271 fn test_create_flow_tql_cte_source_tables_quoted_cte_name() {
1272 let sql = r#"
1273CREATE FLOW calc_cte
1274SINK TO metric_cte_sink
1275EVAL INTERVAL '1m'
1276AS
1277WITH "TQL"(ts, the_value) AS (
1278 TQL EVAL (now() - '1m'::interval, now(), '5s') metric_cte
1279)
1280SELECT * FROM "TQL";
1281"#;
1282
1283 let stmt =
1284 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1285 .unwrap()
1286 .pop()
1287 .unwrap();
1288
1289 let Statement::CreateFlow(create_flow) = stmt else {
1290 unreachable!()
1291 };
1292 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1293
1294 let to_dot_sep =
1295 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1296 assert_eq!(1, expr.source_table_names.len());
1297 assert_eq!(
1298 "greptime.public.metric_cte",
1299 to_dot_sep(expr.source_table_names[0].clone())
1300 );
1301 }
1302
1303 #[test]
1304 fn test_create_flow_tql_cte_source_tables_same_name() {
1305 let sql = r#"
1306CREATE FLOW calc_cte
1307SINK TO metric_cte_sink
1308EVAL INTERVAL '1m'
1309AS
1310WITH tql(ts, the_value) AS (
1311 TQL EVAL (now() - '1m'::interval, now(), '5s') tql
1312)
1313SELECT * FROM tql;
1314"#;
1315
1316 let stmt =
1317 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1318 .unwrap()
1319 .pop()
1320 .unwrap();
1321
1322 let Statement::CreateFlow(create_flow) = stmt else {
1323 unreachable!()
1324 };
1325 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1326
1327 let to_dot_sep =
1328 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1329 assert_eq!(1, expr.source_table_names.len());
1330 assert_eq!(
1331 "greptime.public.tql",
1332 to_dot_sep(expr.source_table_names[0].clone())
1333 );
1334 }
1335
1336 #[test]
1337 fn test_create_flow_expr() {
1338 let sql = r"
1339CREATE FLOW test_distinct_basic SINK TO out_distinct_basic AS
1340SELECT
1341 DISTINCT number as dis
1342FROM
1343 distinct_basic;";
1344 let stmt =
1345 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1346 .unwrap()
1347 .pop()
1348 .unwrap();
1349
1350 let Statement::CreateFlow(create_flow) = stmt else {
1351 unreachable!()
1352 };
1353 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1354
1355 let to_dot_sep =
1356 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1357 assert_eq!("test_distinct_basic", expr.flow_name);
1358 assert_eq!("greptime", expr.catalog_name);
1359 assert_eq!(
1360 "greptime.public.out_distinct_basic",
1361 expr.sink_table_name.map(to_dot_sep).unwrap()
1362 );
1363 assert_eq!(1, expr.source_table_names.len());
1364 assert_eq!(
1365 "greptime.public.distinct_basic",
1366 to_dot_sep(expr.source_table_names[0].clone())
1367 );
1368 assert_eq!(
1369 r"SELECT
1370 DISTINCT number as dis
1371FROM
1372 distinct_basic",
1373 expr.sql
1374 );
1375
1376 let sql = r"
1377CREATE FLOW `task_2`
1378SINK TO schema_1.table_1
1379AS
1380SELECT max(c1), min(c2) FROM schema_2.table_2;";
1381 let stmt =
1382 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1383 .unwrap()
1384 .pop()
1385 .unwrap();
1386
1387 let Statement::CreateFlow(create_flow) = stmt else {
1388 unreachable!()
1389 };
1390 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1391
1392 let to_dot_sep =
1393 |c: TableName| format!("{}.{}.{}", c.catalog_name, c.schema_name, c.table_name);
1394 assert_eq!("task_2", expr.flow_name);
1395 assert_eq!("greptime", expr.catalog_name);
1396 assert_eq!(
1397 "greptime.schema_1.table_1",
1398 expr.sink_table_name.map(to_dot_sep).unwrap()
1399 );
1400 assert_eq!(1, expr.source_table_names.len());
1401 assert_eq!(
1402 "greptime.schema_2.table_2",
1403 to_dot_sep(expr.source_table_names[0].clone())
1404 );
1405 assert_eq!("SELECT max(c1), min(c2) FROM schema_2.table_2", expr.sql);
1406 assert!(expr.flow_options.is_empty());
1407
1408 let sql = r"
1409CREATE FLOW task_3
1410SINK TO schema_1.table_1
1411WITH (defer_on_missing_source = 'true', foo = 'bar')
1412AS
1413SELECT max(c1), min(c2) FROM schema_2.table_2;";
1414 let stmt =
1415 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1416 .unwrap()
1417 .pop()
1418 .unwrap();
1419
1420 let Statement::CreateFlow(create_flow) = stmt else {
1421 unreachable!()
1422 };
1423 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1424 assert_eq!(
1425 expr.flow_options,
1426 HashMap::from([
1427 ("defer_on_missing_source".to_string(), "true".to_string()),
1428 ("foo".to_string(), "bar".to_string()),
1429 ])
1430 );
1431
1432 let sql = r"
1433CREATE FLOW task_4
1434SINK TO schema_1.table_1
1435WITH (defer_on_missing_source = true)
1436AS
1437SELECT max(c1), min(c2) FROM schema_2.table_2;";
1438 let stmt =
1439 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1440 .unwrap()
1441 .pop()
1442 .unwrap();
1443
1444 let Statement::CreateFlow(create_flow) = stmt else {
1445 unreachable!()
1446 };
1447 let expr = to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
1448 assert_eq!(
1449 expr.flow_options,
1450 HashMap::from([("defer_on_missing_source".to_string(), "true".to_string(),)])
1451 );
1452
1453 let sql = r"
1454CREATE FLOW task_5
1455SINK TO schema_1.table_1
1456WITH (defer_on_missing_source = [true])
1457AS
1458SELECT max(c1), min(c2) FROM schema_2.table_2;";
1459 let stmt =
1460 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1461 .unwrap()
1462 .pop()
1463 .unwrap();
1464
1465 let Statement::CreateFlow(create_flow) = stmt else {
1466 unreachable!()
1467 };
1468 let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1469 assert!(res.is_err());
1470 assert!(
1471 res.unwrap_err()
1472 .to_string()
1473 .contains("flow options only support scalar string-compatible values")
1474 );
1475
1476 let sql = r"
1477CREATE FLOW abc.`task_2`
1478SINK TO schema_1.table_1
1479AS
1480SELECT max(c1), min(c2) FROM schema_2.table_2;";
1481 let stmt =
1482 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1483 .unwrap()
1484 .pop()
1485 .unwrap();
1486
1487 let Statement::CreateFlow(create_flow) = stmt else {
1488 unreachable!()
1489 };
1490 let res = to_create_flow_task_expr(create_flow, &QueryContext::arc());
1491
1492 assert!(res.is_err());
1493 assert!(
1494 res.unwrap_err()
1495 .to_string()
1496 .contains("Invalid flow name: abc.`task_2`")
1497 );
1498 }
1499
1500 #[test]
1501 fn test_create_to_expr() {
1502 let sql = "CREATE TABLE monitor (host STRING,ts TIMESTAMP,TIME INDEX (ts),PRIMARY KEY(host)) ENGINE=mito WITH(ttl='3days', write_buffer_size='1024KB');";
1503 let stmt =
1504 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1505 .unwrap()
1506 .pop()
1507 .unwrap();
1508
1509 let Statement::CreateTable(create_table) = stmt else {
1510 unreachable!()
1511 };
1512 let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1513 assert_eq!("3days", expr.table_options.get("ttl").unwrap());
1514 assert_eq!(
1515 "1.0MiB",
1516 expr.table_options.get("write_buffer_size").unwrap()
1517 );
1518
1519 let sql = "CREATE TABLE monitor (ts TIMESTAMP TIME INDEX) WITH(skip_wal='false');";
1520 let stmt =
1521 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1522 .unwrap()
1523 .pop()
1524 .unwrap();
1525 let Statement::CreateTable(create_table) = stmt else {
1526 unreachable!()
1527 };
1528 let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1529 assert_eq!(
1530 Some("false"),
1531 expr.table_options
1532 .get(store_api::mito_engine_options::SKIP_WAL_KEY)
1533 .map(String::as_str)
1534 );
1535 }
1536
1537 #[test]
1538 fn test_invalid_create_to_expr() {
1539 let cases = [
1540 "CREATE TABLE monitor (host STRING primary key, ts TIMESTAMP TIME INDEX, some_column text, some_column string);",
1542 "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, some_column STRING, PRIMARY KEY (some_column, host, some_column));",
1544 "CREATE TABLE monitor (host STRING, ts TIMESTAMP TIME INDEX, PRIMARY KEY (host, ts));",
1546 ];
1547
1548 for sql in cases {
1549 let stmt = ParserContext::create_with_dialect(
1550 sql,
1551 &GreptimeDbDialect {},
1552 ParseOptions::default(),
1553 )
1554 .unwrap()
1555 .pop()
1556 .unwrap();
1557 let Statement::CreateTable(create_table) = stmt else {
1558 unreachable!()
1559 };
1560 create_to_expr(&create_table, &QueryContext::arc()).unwrap_err();
1561 }
1562 }
1563
1564 #[test]
1565 fn test_create_to_expr_with_default_timestamp_value() {
1566 let sql = "CREATE TABLE monitor (v double,ts TIMESTAMP default '2024-01-30T00:01:01',TIME INDEX (ts)) engine=mito;";
1567 let stmt =
1568 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1569 .unwrap()
1570 .pop()
1571 .unwrap();
1572
1573 let Statement::CreateTable(create_table) = stmt else {
1574 unreachable!()
1575 };
1576
1577 let expr = create_to_expr(&create_table, &QueryContext::arc()).unwrap();
1579 let ts_column = &expr.column_defs[1];
1580 let constraint = assert_ts_column(ts_column);
1581 assert!(
1582 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1583 if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1584 );
1585
1586 let ctx = QueryContextBuilder::default()
1588 .timezone(Timezone::from_tz_string("+08:00").unwrap())
1589 .build()
1590 .into();
1591 let expr = create_to_expr(&create_table, &ctx).unwrap();
1592 let ts_column = &expr.column_defs[1];
1593 let constraint = assert_ts_column(ts_column);
1594 assert!(
1595 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1596 if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1597 );
1598 }
1599
1600 fn assert_ts_column(ts_column: &api::v1::ColumnDef) -> ColumnDefaultConstraint {
1601 assert_eq!("ts", ts_column.name);
1602 assert_eq!(
1603 ColumnDataType::TimestampMillisecond as i32,
1604 ts_column.data_type
1605 );
1606 assert!(!ts_column.default_constraint.is_empty());
1607
1608 ColumnDefaultConstraint::try_from(&ts_column.default_constraint[..]).unwrap()
1609 }
1610
1611 #[test]
1612 fn test_to_alter_expr() {
1613 let sql = "ALTER DATABASE greptime SET key1='value1', key2='value2';";
1614 let stmt =
1615 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1616 .unwrap()
1617 .pop()
1618 .unwrap();
1619
1620 let Statement::AlterDatabase(alter_database) = stmt else {
1621 unreachable!()
1622 };
1623
1624 let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1625 let kind = expr.kind.unwrap();
1626
1627 let AlterDatabaseKind::SetDatabaseOptions(SetDatabaseOptions {
1628 set_database_options,
1629 }) = kind
1630 else {
1631 unreachable!()
1632 };
1633
1634 assert_eq!(2, set_database_options.len());
1635 assert_eq!("key1", set_database_options[0].key);
1636 assert_eq!("value1", set_database_options[0].value);
1637 assert_eq!("key2", set_database_options[1].key);
1638 assert_eq!("value2", set_database_options[1].value);
1639
1640 let sql = "ALTER DATABASE greptime UNSET key1, key2;";
1641 let stmt =
1642 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1643 .unwrap()
1644 .pop()
1645 .unwrap();
1646
1647 let Statement::AlterDatabase(alter_database) = stmt else {
1648 unreachable!()
1649 };
1650
1651 let expr = to_alter_database_expr(alter_database, &QueryContext::arc()).unwrap();
1652 let kind = expr.kind.unwrap();
1653
1654 let AlterDatabaseKind::UnsetDatabaseOptions(UnsetDatabaseOptions { keys }) = kind else {
1655 unreachable!()
1656 };
1657
1658 assert_eq!(2, keys.len());
1659 assert!(keys.contains(&"key1".to_string()));
1660 assert!(keys.contains(&"key2".to_string()));
1661
1662 let sql = "ALTER TABLE monitor add column ts TIMESTAMP default '2024-01-30T00:01:01';";
1663 let stmt =
1664 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1665 .unwrap()
1666 .pop()
1667 .unwrap();
1668
1669 let Statement::AlterTable(alter_table) = stmt else {
1670 unreachable!()
1671 };
1672
1673 let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1675 let kind = expr.kind.unwrap();
1676
1677 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1678 unreachable!()
1679 };
1680
1681 assert_eq!(1, add_columns.len());
1682 let ts_column = add_columns[0].column_def.clone().unwrap();
1683 let constraint = assert_ts_column(&ts_column);
1684 assert!(
1685 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1686 if ts.to_iso8601_string() == "2024-01-30 00:01:01+0000")
1687 );
1688
1689 let ctx = QueryContextBuilder::default()
1692 .timezone(Timezone::from_tz_string("+08:00").unwrap())
1693 .build()
1694 .into();
1695 let expr = to_alter_table_expr(alter_table, &ctx).unwrap();
1696 let kind = expr.kind.unwrap();
1697
1698 let AlterTableKind::AddColumns(AddColumns { add_columns, .. }) = kind else {
1699 unreachable!()
1700 };
1701
1702 assert_eq!(1, add_columns.len());
1703 let ts_column = add_columns[0].column_def.clone().unwrap();
1704 let constraint = assert_ts_column(&ts_column);
1705 assert!(
1706 matches!(constraint, ColumnDefaultConstraint::Value(Value::Timestamp(ts))
1707 if ts.to_iso8601_string() == "2024-01-29 16:01:01+0000")
1708 );
1709 }
1710
1711 #[test]
1712 fn test_to_alter_modify_column_type_expr() {
1713 let sql = "ALTER TABLE monitor MODIFY COLUMN mem_usage STRING;";
1714 let stmt =
1715 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1716 .unwrap()
1717 .pop()
1718 .unwrap();
1719
1720 let Statement::AlterTable(alter_table) = stmt else {
1721 unreachable!()
1722 };
1723
1724 let expr = to_alter_table_expr(alter_table.clone(), &QueryContext::arc()).unwrap();
1726 let kind = expr.kind.unwrap();
1727
1728 let AlterTableKind::ModifyColumnTypes(ModifyColumnTypes {
1729 modify_column_types,
1730 }) = kind
1731 else {
1732 unreachable!()
1733 };
1734
1735 assert_eq!(1, modify_column_types.len());
1736 let modify_column_type = &modify_column_types[0];
1737
1738 assert_eq!("mem_usage", modify_column_type.column_name);
1739 assert_eq!(
1740 ColumnDataType::String as i32,
1741 modify_column_type.target_type
1742 );
1743 assert!(modify_column_type.target_type_extension.is_none());
1744 }
1745
1746 #[test]
1747 fn test_to_repartition_request() {
1748 let sql = r#"
1749ALTER TABLE metrics REPARTITION (
1750 device_id < 100
1751) INTO (
1752 device_id < 100 AND area < 'South',
1753 device_id < 100 AND area >= 'South'
1754);"#;
1755 let stmt =
1756 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1757 .unwrap()
1758 .pop()
1759 .unwrap();
1760
1761 let Statement::AlterTable(alter_table) = stmt else {
1762 unreachable!()
1763 };
1764
1765 let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1766 assert_eq!("greptime", request.catalog_name);
1767 assert_eq!("public", request.schema_name);
1768 assert_eq!("metrics", request.table_name);
1769 let RepartitionSource::Partitions {
1770 from_exprs,
1771 target_partition_columns,
1772 } = request.source
1773 else {
1774 unreachable!()
1775 };
1776 assert!(target_partition_columns.is_none());
1777 assert_eq!(
1778 from_exprs
1779 .into_iter()
1780 .map(|x| x.to_string())
1781 .collect::<Vec<_>>(),
1782 vec!["device_id < 100".to_string()]
1783 );
1784 assert_eq!(
1785 request
1786 .into_exprs
1787 .into_iter()
1788 .map(|x| x.to_string())
1789 .collect::<Vec<_>>(),
1790 vec![
1791 "device_id < 100 AND area < 'South'".to_string(),
1792 "device_id < 100 AND area >= 'South'".to_string()
1793 ]
1794 );
1795 }
1796
1797 #[test]
1798 fn test_to_repartition_request_with_target_partition_columns() {
1799 let sql = r#"
1800ALTER TABLE metrics REPARTITION (
1801 device_id < 100
1802) ON COLUMNS (device_id, area) INTO (
1803 device_id < 100 AND area < 'South',
1804 device_id < 100 AND area >= 'South'
1805);"#;
1806 let stmt =
1807 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1808 .unwrap()
1809 .pop()
1810 .unwrap();
1811
1812 let Statement::AlterTable(alter_table) = stmt else {
1813 unreachable!()
1814 };
1815
1816 let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1817 let RepartitionSource::Partitions {
1818 target_partition_columns,
1819 ..
1820 } = request.source
1821 else {
1822 unreachable!()
1823 };
1824
1825 assert_eq!(
1826 target_partition_columns,
1827 Some(vec!["device_id".to_string(), "area".to_string()])
1828 );
1829 }
1830
1831 #[test]
1832 fn test_to_repartition_request_with_unpartitioned_source() {
1833 let sql = r#"
1834ALTER TABLE metrics PARTITION ON COLUMNS (device_id, area) (
1835 device_id < 100 AND area < 'South',
1836 device_id < 100 AND area >= 'South'
1837);"#;
1838 let stmt =
1839 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1840 .unwrap()
1841 .pop()
1842 .unwrap();
1843
1844 let Statement::AlterTable(alter_table) = stmt else {
1845 unreachable!()
1846 };
1847
1848 let request = to_repartition_request(alter_table, &QueryContext::arc()).unwrap();
1849 assert_eq!("greptime", request.catalog_name);
1850 assert_eq!("public", request.schema_name);
1851 assert_eq!("metrics", request.table_name);
1852 let RepartitionSource::Unpartitioned { partition_columns } = request.source else {
1853 unreachable!()
1854 };
1855 assert_eq!(partition_columns, vec!["device_id", "area"]);
1856 assert_eq!(
1857 request
1858 .into_exprs
1859 .into_iter()
1860 .map(|x| x.to_string())
1861 .collect::<Vec<_>>(),
1862 vec![
1863 "device_id < 100 AND area < 'South'".to_string(),
1864 "device_id < 100 AND area >= 'South'".to_string()
1865 ]
1866 );
1867 }
1868
1869 fn new_test_table_names() -> Vec<TableName> {
1870 vec![
1871 TableName {
1872 catalog_name: "greptime".to_string(),
1873 schema_name: "public".to_string(),
1874 table_name: "a_table".to_string(),
1875 },
1876 TableName {
1877 catalog_name: "greptime".to_string(),
1878 schema_name: "public".to_string(),
1879 table_name: "b_table".to_string(),
1880 },
1881 ]
1882 }
1883
1884 #[test]
1885 fn test_to_create_view_expr() {
1886 let sql = "CREATE VIEW test AS SELECT * FROM NUMBERS";
1887 let stmt =
1888 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1889 .unwrap()
1890 .pop()
1891 .unwrap();
1892
1893 let Statement::CreateView(stmt) = stmt else {
1894 unreachable!()
1895 };
1896
1897 let logical_plan = vec![1, 2, 3];
1898 let table_names = new_test_table_names();
1899 let columns = vec!["a".to_string()];
1900 let plan_columns = vec!["number".to_string()];
1901
1902 let expr = to_create_view_expr(
1903 stmt,
1904 logical_plan.clone(),
1905 table_names.clone(),
1906 columns.clone(),
1907 plan_columns.clone(),
1908 sql.to_string(),
1909 QueryContext::arc(),
1910 )
1911 .unwrap();
1912
1913 assert_eq!("greptime", expr.catalog_name);
1914 assert_eq!("public", expr.schema_name);
1915 assert_eq!("test", expr.view_name);
1916 assert!(!expr.create_if_not_exists);
1917 assert!(!expr.or_replace);
1918 assert_eq!(logical_plan, expr.logical_plan);
1919 assert_eq!(table_names, expr.table_names);
1920 assert_eq!(sql, expr.definition);
1921 assert_eq!(columns, expr.columns);
1922 assert_eq!(plan_columns, expr.plan_columns);
1923 }
1924
1925 #[test]
1926 fn test_to_create_view_expr_complex() {
1927 let sql = "CREATE OR REPLACE VIEW IF NOT EXISTS test.test_view AS SELECT * FROM NUMBERS";
1928 let stmt =
1929 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1930 .unwrap()
1931 .pop()
1932 .unwrap();
1933
1934 let Statement::CreateView(stmt) = stmt else {
1935 unreachable!()
1936 };
1937
1938 let logical_plan = vec![1, 2, 3];
1939 let table_names = new_test_table_names();
1940 let columns = vec!["a".to_string()];
1941 let plan_columns = vec!["number".to_string()];
1942
1943 let expr = to_create_view_expr(
1944 stmt,
1945 logical_plan.clone(),
1946 table_names.clone(),
1947 columns.clone(),
1948 plan_columns.clone(),
1949 sql.to_string(),
1950 QueryContext::arc(),
1951 )
1952 .unwrap();
1953
1954 assert_eq!("greptime", expr.catalog_name);
1955 assert_eq!("test", expr.schema_name);
1956 assert_eq!("test_view", expr.view_name);
1957 assert!(expr.create_if_not_exists);
1958 assert!(expr.or_replace);
1959 assert_eq!(logical_plan, expr.logical_plan);
1960 assert_eq!(table_names, expr.table_names);
1961 assert_eq!(sql, expr.definition);
1962 assert_eq!(columns, expr.columns);
1963 assert_eq!(plan_columns, expr.plan_columns);
1964 }
1965
1966 #[test]
1967 fn test_expr_to_create() {
1968 let sql = r#"CREATE TABLE IF NOT EXISTS `tt` (
1969 `timestamp` TIMESTAMP(9) NOT NULL,
1970 `ip_address` STRING NULL SKIPPING INDEX WITH(false_positive_rate = '0.01', granularity = '10240', type = 'BLOOM'),
1971 `username` STRING NULL,
1972 `http_method` STRING NULL INVERTED INDEX,
1973 `request_line` STRING NULL FULLTEXT INDEX WITH(analyzer = 'English', backend = 'bloom', case_sensitive = 'false', false_positive_rate = '0.01', granularity = '10240'),
1974 `protocol` STRING NULL,
1975 `status_code` INT NULL INVERTED INDEX,
1976 `response_size` BIGINT NULL,
1977 `message` STRING NULL,
1978 TIME INDEX (`timestamp`),
1979 PRIMARY KEY (`username`, `status_code`)
1980)
1981ENGINE=mito
1982WITH(
1983 append_mode = 'true'
1984)"#;
1985 let stmt =
1986 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
1987 .unwrap()
1988 .pop()
1989 .unwrap();
1990
1991 let Statement::CreateTable(original_create) = stmt else {
1992 unreachable!()
1993 };
1994
1995 let expr = create_to_expr(&original_create, &QueryContext::arc()).unwrap();
1997
1998 let create_table = expr_to_create(&expr, Some('`')).unwrap();
1999 let new_sql = format!("{:#}", create_table);
2000 assert_eq!(sql, new_sql);
2001 }
2002}