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