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