1use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17use std::time::Duration;
18
19use api::helper::ColumnDataTypeWrapper;
20use api::v1::alter_table_expr::Kind;
21use api::v1::meta::CreateFlowTask as PbCreateFlowTask;
22use api::v1::repartition::Source;
23use api::v1::{
24 AlterDatabaseExpr, AlterTableExpr, CreateFlowExpr, CreateTableExpr, CreateViewExpr,
25 PartitionedSource, Repartition, TargetPartitionColumns, UnpartitionedSource, column_def,
26};
27#[cfg(feature = "enterprise")]
28use api::v1::{
29 CreateTriggerExpr as PbCreateTriggerExpr, meta::CreateTriggerTask as PbCreateTriggerTask,
30};
31use catalog::CatalogManagerRef;
32use chrono::Utc;
33use common_base::regex_pattern::NAME_PATTERN_REG;
34use common_catalog::consts::{
35 DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, is_ddl_reserved_table, is_readonly_schema,
36 is_readonly_table,
37};
38use common_catalog::{format_full_flow_name, format_full_table_name};
39use common_error::ext::BoxedError;
40#[cfg(feature = "enterprise")]
41use common_meta::cache_invalidator::CacheInvalidatorRef;
42use common_meta::cache_invalidator::Context;
43use common_meta::ddl::create_flow::{
44 DEFER_ON_MISSING_SOURCE_KEY, FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY, FlowType,
45};
46use common_meta::instruction::CacheIdent;
47#[cfg(feature = "enterprise")]
48use common_meta::key::TableMetadataManagerRef;
49use common_meta::key::schema_name::{SchemaName, SchemaNameKey};
50#[cfg(feature = "enterprise")]
51use common_meta::procedure_executor::ProcedureExecutorRef;
52#[cfg(feature = "enterprise")]
53use common_meta::rpc::ddl::trigger::CreateTriggerTask;
54#[cfg(feature = "enterprise")]
55use common_meta::rpc::ddl::trigger::DropTriggerTask;
56use common_meta::rpc::ddl::{
57 CreateFlowTask, CreatorGrantIntent, DdlTask, DropFlowTask, DropViewTask, SubmitDdlTaskRequest,
58 SubmitDdlTaskResponse, TriggerReason,
59};
60use common_query::Output;
61use common_recordbatch::{RecordBatch, RecordBatches};
62use common_sql::convert::sql_value_to_value;
63use common_telemetry::{debug, info, tracing, warn};
64use common_time::{Timestamp, Timezone};
65use datafusion_common::tree_node::TreeNodeVisitor;
66use datafusion_expr::LogicalPlan;
67use datatypes::prelude::ConcreteDataType;
68use datatypes::schema::{ColumnSchema, Schema};
69use datatypes::value::Value;
70use datatypes::vectors::{StringVector, VectorRef};
71use humantime::parse_duration;
72use partition::expr::{Operand, PartitionExpr, RestrictedOp};
73use partition::multi_dim::MultiDimPartitionRule;
74use query::parser::QueryStatement;
75use query::plan::extract_and_rewrite_full_table_names;
76use query::query_engine::DefaultSerializer;
77use query::sql::create_table_stmt;
78use session::context::QueryContextRef;
79use session::table_name::table_idents_to_full_name;
80use snafu::{OptionExt, ResultExt, ensure};
81use sql::parser::{ParseOptions, ParserContext};
82use sql::parsers::utils::is_tql;
83use sql::statements::OptionMap;
84#[cfg(feature = "enterprise")]
85use sql::statements::alter::trigger::AlterTrigger;
86use sql::statements::alter::{AlterDatabase, AlterTable, AlterTableOperation};
87#[cfg(feature = "enterprise")]
88use sql::statements::create::trigger::CreateTrigger;
89use sql::statements::create::{
90 CreateExternalTable, CreateFlow, CreateTable, CreateTableLike, CreateView, Partitions,
91};
92use sql::statements::statement::Statement;
93use sqlparser::ast::{Expr, Ident, UnaryOperator, Value as ParserValue};
94use store_api::metric_engine_consts::{LOGICAL_TABLE_METADATA_KEY, METRIC_ENGINE_NAME};
95use store_api::mito_engine_options::APPEND_MODE_KEY;
96use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan};
97use table::TableRef;
98use table::dist_table::DistTable;
99use table::metadata::{self, TableId, TableInfo, TableMeta, TableType};
100use table::requests::{
101 AlterKind, AlterTableRequest, AnnotationContext, COMMENT_KEY, DDL_TIMEOUT, DDL_WAIT,
102 TableOptions, validate_and_normalize_annotation_options,
103};
104use table::table_name::TableName;
105use table::table_reference::TableReference;
106
107use crate::error::{
108 self, AlterExprToRequestSnafu, BuildDfLogicalPlanSnafu, CatalogSnafu, ColumnDataTypeSnafu,
109 ColumnNotFoundSnafu, ConvertSchemaSnafu, CreateLogicalTablesSnafu,
110 DeserializePartitionExprSnafu, EmptyDdlExprSnafu, ExternalSnafu, ExtractTableNamesSnafu,
111 FlowNotFoundSnafu, InvalidPartitionRuleSnafu, InvalidPartitionSnafu, InvalidSqlSnafu,
112 InvalidTableNameSnafu, InvalidViewNameSnafu, InvalidViewStmtSnafu, NotSupportedSnafu,
113 PartitionExprToPbSnafu, Result, SchemaInUseSnafu, SchemaNotFoundSnafu, SchemaReadOnlySnafu,
114 SerializePartitionExprSnafu, SubstraitCodecSnafu, TableAlreadyExistsSnafu,
115 TableDdlReservedSnafu, TableMetadataManagerSnafu, TableNotFoundSnafu, TableReadOnlySnafu,
116 UnrecognizedTableOptionSnafu, ViewAlreadyExistsSnafu,
117};
118use crate::expr_helper::{self, RepartitionRequest, RepartitionSource};
119use crate::statement::StatementExecutor;
120use crate::statement::show::create_partitions_stmt;
121use crate::utils::{to_executor_context, to_executor_context_with_origin_frontend};
122
123#[derive(Debug, Clone, Copy)]
124struct DdlSubmitOptions {
125 wait: bool,
126 timeout: Duration,
127}
128
129const ALLOWED_FLOW_OPTIONS: [&str; 2] = [
130 DEFER_ON_MISSING_SOURCE_KEY,
131 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY,
132];
133
134fn build_procedure_id_output(procedure_id: Vec<u8>) -> Result<Output> {
135 let procedure_id = String::from_utf8_lossy(&procedure_id).to_string();
136 let vector: VectorRef = Arc::new(StringVector::from(vec![procedure_id]));
137 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
138 "Procedure ID",
139 vector.data_type(),
140 false,
141 )]));
142 let batch =
143 RecordBatch::new(schema.clone(), vec![vector]).context(error::BuildRecordBatchSnafu)?;
144 let batches =
145 RecordBatches::try_new(schema, vec![batch]).context(error::BuildRecordBatchSnafu)?;
146 Ok(Output::new_with_record_batches(batches))
147}
148
149fn parse_ddl_options(options: &OptionMap) -> Result<DdlSubmitOptions> {
150 let wait = match options.get(DDL_WAIT) {
151 Some(value) => value.parse::<bool>().map_err(|_| {
152 InvalidSqlSnafu {
153 err_msg: format!("invalid DDL option '{DDL_WAIT}': '{value}'"),
154 }
155 .build()
156 })?,
157 None => SubmitDdlTaskRequest::default_wait(),
158 };
159
160 let timeout = match options.get(DDL_TIMEOUT) {
161 Some(value) => parse_duration(value).map_err(|err| {
162 InvalidSqlSnafu {
163 err_msg: format!("invalid DDL option '{DDL_TIMEOUT}': '{value}': {err}"),
164 }
165 .build()
166 })?,
167 None => SubmitDdlTaskRequest::default_timeout(),
168 };
169
170 Ok(DdlSubmitOptions { wait, timeout })
171}
172
173fn supported_flow_options() -> String {
174 ALLOWED_FLOW_OPTIONS.join(", ")
175}
176
177fn normalize_flow_bool_option(key: &str, value: &str) -> Result<String> {
178 value
179 .trim()
180 .to_ascii_lowercase()
181 .parse::<bool>()
182 .map(|value| value.to_string())
183 .map_err(|_| {
184 InvalidSqlSnafu {
185 err_msg: format!("invalid flow option '{key}': '{value}'"),
186 }
187 .build()
188 })
189}
190
191fn validate_and_normalize_flow_options(
192 options: HashMap<String, String>,
193 eval_interval: Option<i64>,
194) -> Result<HashMap<String, String>> {
195 if let Some(secs) = eval_interval
197 && secs <= 0
198 {
199 return InvalidSqlSnafu {
200 err_msg: format!("EVAL INTERVAL must be positive, got {secs} seconds"),
201 }
202 .fail();
203 }
204
205 options
206 .into_iter()
207 .map(|(key, value)| {
208 if key == FlowType::FLOW_TYPE_KEY {
209 return InvalidSqlSnafu {
210 err_msg: format!("flow option '{key}' is reserved for internal use"),
211 }
212 .fail();
213 }
214
215 let normalized_value = match key.as_str() {
216 DEFER_ON_MISSING_SOURCE_KEY | FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY => {
217 normalize_flow_bool_option(&key, &value)?
218 }
219 _ => {
220 return InvalidSqlSnafu {
221 err_msg: format!(
222 "unknown flow option '{key}', supported options: {}",
223 supported_flow_options()
224 ),
225 }
226 .fail();
227 }
228 };
229
230 Ok((key, normalized_value))
231 })
232 .collect()
233}
234
235fn determine_flow_type_for_source_state(
236 flow_name: &str,
237 flow_options: &HashMap<String, String>,
238 has_missing_source_table: bool,
239 has_instant_ttl_source_table: bool,
240) -> Result<Option<FlowType>> {
241 if has_missing_source_table {
242 let defer_on_missing_source = flow_options
243 .get(DEFER_ON_MISSING_SOURCE_KEY)
244 .is_some_and(|value| value == "true");
245 ensure!(
246 defer_on_missing_source,
247 InvalidSqlSnafu {
248 err_msg: format!(
249 "missing source tables for flow '{}'; use WITH ({DEFER_ON_MISSING_SOURCE_KEY} = true) to create a pending flow",
250 flow_name
251 )
252 }
253 );
254 info!(
255 "Flow `{}` is created as a pending batching flow because source tables are missing and defer_on_missing_source=true",
256 flow_name
257 );
258 return Ok(Some(FlowType::Batching));
259 }
260
261 if has_instant_ttl_source_table {
262 return Ok(Some(FlowType::Streaming));
263 }
264
265 Ok(None)
266}
267
268impl StatementExecutor {
269 pub fn catalog_manager(&self) -> CatalogManagerRef {
270 self.catalog_manager.clone()
271 }
272
273 #[tracing::instrument(skip_all)]
274 pub async fn create_table(&self, stmt: CreateTable, ctx: QueryContextRef) -> Result<TableRef> {
275 let (catalog, schema, _table) = table_idents_to_full_name(&stmt.name, &ctx)
276 .map_err(BoxedError::new)
277 .context(error::ExternalSnafu)?;
278
279 let schema_options = self
280 .table_metadata_manager
281 .schema_manager()
282 .get(SchemaNameKey {
283 catalog: &catalog,
284 schema: &schema,
285 })
286 .await
287 .context(TableMetadataManagerSnafu)?
288 .map(|v| v.into_inner());
289
290 let create_expr = &mut expr_helper::create_to_expr(&stmt, &ctx)?;
291 if let Some(schema_options) = schema_options {
294 for (key, value) in schema_options.extra_options.iter() {
295 if key.starts_with("compaction.") {
296 continue;
297 }
298 create_expr
299 .table_options
300 .entry(key.clone())
301 .or_insert(value.clone());
302 }
303 }
304
305 self.create_table_inner(create_expr, stmt.partitions, ctx, TriggerReason::Manual)
306 .await
307 }
308
309 #[tracing::instrument(skip_all)]
310 pub async fn create_table_like(
311 &self,
312 stmt: CreateTableLike,
313 ctx: QueryContextRef,
314 ) -> Result<TableRef> {
315 let (catalog, schema, table) = table_idents_to_full_name(&stmt.source_name, &ctx)
316 .map_err(BoxedError::new)
317 .context(error::ExternalSnafu)?;
318 let table_ref = self
319 .catalog_manager
320 .table(&catalog, &schema, &table, Some(&ctx))
321 .await
322 .context(CatalogSnafu)?
323 .context(TableNotFoundSnafu { table_name: &table })?;
324 let partition_info = self
325 .partition_manager
326 .find_physical_partition_info(table_ref.table_info().table_id())
327 .await
328 .context(error::FindTablePartitionRuleSnafu { table_name: table })?;
329
330 let schema_options = self
332 .table_metadata_manager
333 .schema_manager()
334 .get(SchemaNameKey {
335 catalog: &catalog,
336 schema: &schema,
337 })
338 .await
339 .context(TableMetadataManagerSnafu)?
340 .map(|v| v.into_inner());
341
342 let quote_style = ctx.quote_style();
343 let mut create_stmt =
344 create_table_stmt(&table_ref.table_info(), schema_options, quote_style)
345 .context(error::ParseQuerySnafu)?;
346 create_stmt.name = stmt.table_name;
347 create_stmt.if_not_exists = false;
348
349 let table_info = table_ref.table_info();
350 let partitions = create_partitions_stmt(&table_info, &partition_info.partitions)?.and_then(
351 |mut partitions| {
352 if !partitions.column_list.is_empty() {
353 partitions.set_quote(quote_style);
354 Some(partitions)
355 } else {
356 None
357 }
358 },
359 );
360
361 let create_expr = &mut expr_helper::create_to_expr(&create_stmt, &ctx)?;
362 self.create_table_inner(create_expr, partitions, ctx, TriggerReason::Manual)
363 .await
364 }
365
366 #[tracing::instrument(skip_all)]
367 pub async fn create_external_table(
368 &self,
369 create_expr: CreateExternalTable,
370 ctx: QueryContextRef,
371 ) -> Result<TableRef> {
372 let create_expr =
373 &mut expr_helper::create_external_expr(create_expr, &ctx, &self.local_file_access)
374 .await?;
375 self.create_table_inner(create_expr, None, ctx, TriggerReason::Manual)
376 .await
377 }
378
379 pub async fn create_declared_relationships_table(
383 &self,
384 catalog: &str,
385 query_ctx: QueryContextRef,
386 ) -> Result<TableRef> {
387 let mut expr = super::semantic_graph::build_declared_relationships_expr(catalog);
388 self.create_non_logic_table(&mut expr, None, query_ctx, TriggerReason::AutoCreate)
389 .await
390 }
391
392 #[tracing::instrument(skip_all)]
393 pub async fn create_table_inner(
394 &self,
395 create_table: &mut CreateTableExpr,
396 partitions: Option<Partitions>,
397 query_ctx: QueryContextRef,
398 trigger_reason: TriggerReason,
399 ) -> Result<TableRef> {
400 ensure_table_definition_writable(&create_table.schema_name, &create_table.table_name)?;
401
402 if create_table.engine == METRIC_ENGINE_NAME
403 && create_table
404 .table_options
405 .contains_key(LOGICAL_TABLE_METADATA_KEY)
406 {
407 if let Some(partitions) = partitions.as_ref()
408 && !partitions.exprs.is_empty()
409 {
410 self.validate_logical_table_partition_rule(create_table, partitions, &query_ctx)
411 .await?;
412 }
413 self.create_logical_tables(
415 std::slice::from_ref(create_table),
416 query_ctx,
417 trigger_reason,
418 )
419 .await?
420 .into_iter()
421 .next()
422 .context(error::UnexpectedSnafu {
423 violated: "expected to create logical tables",
424 })
425 } else {
426 self.create_non_logic_table(create_table, partitions, query_ctx, trigger_reason)
428 .await
429 }
430 }
431
432 #[tracing::instrument(skip_all)]
433 pub async fn create_non_logic_table(
434 &self,
435 create_table: &mut CreateTableExpr,
436 partitions: Option<Partitions>,
437 query_ctx: QueryContextRef,
438 trigger_reason: TriggerReason,
439 ) -> Result<TableRef> {
440 let _timer = crate::metrics::DIST_CREATE_TABLE.start_timer();
441
442 let schema = self
444 .table_metadata_manager
445 .schema_manager()
446 .get(SchemaNameKey::new(
447 &create_table.catalog_name,
448 &create_table.schema_name,
449 ))
450 .await
451 .context(TableMetadataManagerSnafu)?;
452 ensure!(
453 schema.is_some(),
454 SchemaNotFoundSnafu {
455 schema_info: &create_table.schema_name,
456 }
457 );
458
459 if let Some(table) = self
461 .catalog_manager
462 .table(
463 &create_table.catalog_name,
464 &create_table.schema_name,
465 &create_table.table_name,
466 Some(&query_ctx),
467 )
468 .await
469 .context(CatalogSnafu)?
470 {
471 return if create_table.create_if_not_exists {
472 Ok(table)
473 } else {
474 TableAlreadyExistsSnafu {
475 table: format_full_table_name(
476 &create_table.catalog_name,
477 &create_table.schema_name,
478 &create_table.table_name,
479 ),
480 }
481 .fail()
482 };
483 }
484
485 ensure!(
486 NAME_PATTERN_REG.is_match(&create_table.table_name),
487 InvalidTableNameSnafu {
488 table_name: &create_table.table_name,
489 }
490 );
491
492 let table_name = TableName::new(
493 &create_table.catalog_name,
494 &create_table.schema_name,
495 &create_table.table_name,
496 );
497
498 let (partitions, partition_cols) = parse_partitions(create_table, partitions, &query_ctx)?;
499 let mut table_info = create_table_info(create_table, partition_cols)?;
500
501 let resp = self
502 .create_table_procedure(
503 create_table.clone(),
504 partitions,
505 table_info.clone(),
506 query_ctx,
507 trigger_reason,
508 )
509 .await?;
510
511 let table_id = resp
512 .table_ids
513 .into_iter()
514 .next()
515 .context(error::UnexpectedSnafu {
516 violated: "expected table_id",
517 })?;
518 info!("Successfully created table '{table_name}' with table id {table_id}");
519
520 table_info.ident.table_id = table_id;
521
522 let table_info = Arc::new(table_info);
523 create_table.table_id = Some(api::v1::TableId { id: table_id });
524
525 let table = DistTable::table(table_info);
526
527 Ok(table)
528 }
529
530 #[tracing::instrument(skip_all)]
531 pub async fn create_logical_tables(
532 &self,
533 create_table_exprs: &[CreateTableExpr],
534 query_context: QueryContextRef,
535 trigger_reason: TriggerReason,
536 ) -> Result<Vec<TableRef>> {
537 let _timer = crate::metrics::DIST_CREATE_TABLES.start_timer();
538 ensure!(
539 !create_table_exprs.is_empty(),
540 EmptyDdlExprSnafu {
541 name: "create logic tables"
542 }
543 );
544
545 for create_table in create_table_exprs {
547 ensure!(
548 NAME_PATTERN_REG.is_match(&create_table.table_name),
549 InvalidTableNameSnafu {
550 table_name: &create_table.table_name,
551 }
552 );
553 }
554
555 let raw_tables_info = create_table_exprs
556 .iter()
557 .map(|create| create_table_info(create, vec![]))
558 .collect::<Result<Vec<_>>>()?;
559 let tables_data = create_table_exprs
560 .iter()
561 .cloned()
562 .zip(raw_tables_info.iter().cloned())
563 .collect::<Vec<_>>();
564
565 let resp = self
566 .create_logical_tables_procedure(tables_data, query_context.clone(), trigger_reason)
567 .await?;
568
569 let table_ids = resp.table_ids;
570 ensure!(
571 table_ids.len() == raw_tables_info.len(),
572 CreateLogicalTablesSnafu {
573 reason: format!(
574 "The number of tables is inconsistent with the expected number to be created, expected: {}, actual: {}",
575 raw_tables_info.len(),
576 table_ids.len()
577 )
578 }
579 );
580 info!("Successfully created logical tables: {:?}", table_ids);
581
582 let mut tables_info = Vec::with_capacity(table_ids.len());
586 for (table_id, create_table) in table_ids.iter().zip(create_table_exprs.iter()) {
587 let table = self
588 .catalog_manager
589 .table(
590 &create_table.catalog_name,
591 &create_table.schema_name,
592 &create_table.table_name,
593 Some(&query_context),
594 )
595 .await
596 .context(CatalogSnafu)?
597 .with_context(|| TableNotFoundSnafu {
598 table_name: format_full_table_name(
599 &create_table.catalog_name,
600 &create_table.schema_name,
601 &create_table.table_name,
602 ),
603 })?;
604
605 let table_info = table.table_info();
606 ensure!(
608 table_info.table_id() == *table_id,
609 CreateLogicalTablesSnafu {
610 reason: format!(
611 "Table id mismatch after creation, expected {}, got {} for table {}",
612 table_id,
613 table_info.table_id(),
614 format_full_table_name(
615 &create_table.catalog_name,
616 &create_table.schema_name,
617 &create_table.table_name
618 )
619 )
620 }
621 );
622
623 tables_info.push(table_info);
624 }
625
626 Ok(tables_info.into_iter().map(DistTable::table).collect())
627 }
628
629 async fn validate_logical_table_partition_rule(
630 &self,
631 create_table: &CreateTableExpr,
632 partitions: &Partitions,
633 query_ctx: &QueryContextRef,
634 ) -> Result<()> {
635 let (_, mut logical_partition_exprs) =
636 parse_partitions_for_logical_validation(create_table, partitions, query_ctx)?;
637
638 let physical_table_name = create_table
639 .table_options
640 .get(LOGICAL_TABLE_METADATA_KEY)
641 .with_context(|| CreateLogicalTablesSnafu {
642 reason: format!(
643 "expect `{LOGICAL_TABLE_METADATA_KEY}` option on creating logical table"
644 ),
645 })?;
646
647 let physical_table = self
648 .catalog_manager
649 .table(
650 &create_table.catalog_name,
651 &create_table.schema_name,
652 physical_table_name,
653 Some(query_ctx),
654 )
655 .await
656 .context(CatalogSnafu)?
657 .context(TableNotFoundSnafu {
658 table_name: physical_table_name.clone(),
659 })?;
660
661 let physical_table_info = physical_table.table_info();
662 let (partition_rule, _) = self
663 .partition_manager
664 .find_table_partition_rule(&physical_table_info)
665 .await
666 .context(error::FindTablePartitionRuleSnafu {
667 table_name: physical_table_name.clone(),
668 })?;
669
670 let multi_dim_rule = partition_rule
671 .as_ref()
672 .as_any()
673 .downcast_ref::<MultiDimPartitionRule>()
674 .context(InvalidPartitionRuleSnafu {
675 reason: "physical table partition rule is not range-based",
676 })?;
677
678 let mut physical_partition_exprs = multi_dim_rule.exprs().to_vec();
680 logical_partition_exprs.sort_unstable();
681 physical_partition_exprs.sort_unstable();
682
683 ensure!(
684 physical_partition_exprs == logical_partition_exprs,
685 InvalidPartitionRuleSnafu {
686 reason: format!(
687 "logical table partition rule must match the corresponding physical table's\n logical table partition exprs:\t\t {:?}\n physical table partition exprs:\t {:?}",
688 logical_partition_exprs, physical_partition_exprs
689 ),
690 }
691 );
692
693 Ok(())
694 }
695
696 #[cfg(feature = "enterprise")]
697 #[tracing::instrument(skip_all)]
698 pub async fn create_trigger(
699 &self,
700 stmt: CreateTrigger,
701 query_context: QueryContextRef,
702 ) -> Result<Output> {
703 let expr = expr_helper::to_create_trigger_task_expr(stmt, &query_context)?;
704 self.create_trigger_inner(expr, query_context).await
705 }
706
707 #[cfg(feature = "enterprise")]
708 pub async fn create_trigger_inner(
709 &self,
710 expr: PbCreateTriggerExpr,
711 query_context: QueryContextRef,
712 ) -> Result<Output> {
713 self.create_trigger_procedure(expr, query_context).await?;
714 Ok(Output::new_with_affected_rows(0))
715 }
716
717 #[cfg(feature = "enterprise")]
718 async fn create_trigger_procedure(
719 &self,
720 expr: PbCreateTriggerExpr,
721 query_context: QueryContextRef,
722 ) -> Result<SubmitDdlTaskResponse> {
723 let task = CreateTriggerTask::try_from(PbCreateTriggerTask {
724 create_trigger: Some(expr),
725 })
726 .context(error::InvalidExprSnafu)?;
727
728 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
729 let request = SubmitDdlTaskRequest::new(DdlTask::new_create_trigger(task));
730
731 self.procedure_executor
732 .submit_ddl_task(executor_context, request)
733 .await
734 .context(error::ExecuteDdlSnafu)
735 }
736
737 #[tracing::instrument(skip_all)]
738 pub async fn create_flow(
739 &self,
740 stmt: CreateFlow,
741 query_context: QueryContextRef,
742 ) -> Result<Output> {
743 let expr = expr_helper::to_create_flow_task_expr(stmt, &query_context)?;
745
746 self.create_flow_inner(expr, query_context).await
747 }
748
749 pub async fn create_flow_inner(
750 &self,
751 expr: CreateFlowExpr,
752 query_context: QueryContextRef,
753 ) -> Result<Output> {
754 self.create_flow_procedure(expr, query_context).await?;
755 Ok(Output::new_with_affected_rows(0))
756 }
757
758 async fn create_flow_procedure(
759 &self,
760 mut expr: CreateFlowExpr,
761 query_context: QueryContextRef,
762 ) -> Result<SubmitDdlTaskResponse> {
763 let eval_interval_secs = expr.eval_interval.as_ref().map(|e| e.seconds);
764
765 if let Some(secs) = eval_interval_secs
767 && secs <= 0
768 {
769 return InvalidSqlSnafu {
770 err_msg: format!("EVAL INTERVAL must be positive, got {secs} seconds"),
771 }
772 .fail();
773 }
774
775 expr.flow_options =
776 validate_and_normalize_flow_options(expr.flow_options, eval_interval_secs)?;
777
778 let flow_type = self
779 .determine_flow_type(&expr, query_context.clone())
780 .await?;
781 info!("determined flow={} type: {:#?}", expr.flow_name, flow_type);
782
783 expr.flow_options
784 .insert(FlowType::FLOW_TYPE_KEY.to_string(), flow_type.to_string());
785
786 let task = CreateFlowTask::try_from(PbCreateFlowTask {
787 create_flow: Some(expr),
788 })
789 .context(error::InvalidExprSnafu)?;
790 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
791 let request = SubmitDdlTaskRequest::new(DdlTask::new_create_flow(task));
792
793 self.procedure_executor
794 .submit_ddl_task(executor_context, request)
795 .await
796 .context(error::ExecuteDdlSnafu)
797 }
798
799 async fn determine_flow_type(
803 &self,
804 expr: &CreateFlowExpr,
805 query_ctx: QueryContextRef,
806 ) -> Result<FlowType> {
807 let mut has_missing_source_table = false;
808 let mut has_instant_ttl_source_table = false;
809
810 for src_table_name in &expr.source_table_names {
811 let table = self
812 .catalog_manager()
813 .table(
814 &src_table_name.catalog_name,
815 &src_table_name.schema_name,
816 &src_table_name.table_name,
817 Some(&query_ctx),
818 )
819 .await
820 .map_err(BoxedError::new)
821 .context(ExternalSnafu)?;
822
823 let Some(table) = table else {
824 has_missing_source_table = true;
825 continue;
826 };
827
828 if table.table_info().meta.options.ttl == Some(common_time::TimeToLive::Instant) {
829 warn!(
830 "Source table `{}` for flow `{}`'s ttl=instant, fallback to streaming mode",
831 format_full_table_name(
832 &src_table_name.catalog_name,
833 &src_table_name.schema_name,
834 &src_table_name.table_name
835 ),
836 expr.flow_name
837 );
838 has_instant_ttl_source_table = true;
839 }
840 }
841
842 if let Some(flow_type) = determine_flow_type_for_source_state(
843 &expr.flow_name,
844 &expr.flow_options,
845 has_missing_source_table,
846 has_instant_ttl_source_table,
847 )? {
848 return Ok(flow_type);
849 }
850
851 let engine = &self.query_engine;
852 let stmts = ParserContext::create_with_dialect(
853 &expr.sql,
854 query_ctx.sql_dialect(),
855 ParseOptions::default(),
856 )
857 .map_err(BoxedError::new)
858 .context(ExternalSnafu)?;
859
860 ensure!(
861 stmts.len() == 1,
862 InvalidSqlSnafu {
863 err_msg: format!("Expect only one statement, found {}", stmts.len())
864 }
865 );
866 let stmt = &stmts[0];
867
868 if is_tql(query_ctx.sql_dialect(), &expr.sql)
869 .map_err(BoxedError::new)
870 .context(ExternalSnafu)?
871 {
872 return Ok(FlowType::Batching);
873 }
874
875 let plan = match stmt {
877 Statement::Tql(_) => return Ok(FlowType::Batching),
879 _ => engine
880 .planner()
881 .plan(&QueryStatement::Sql(stmt.clone()), query_ctx)
882 .await
883 .map_err(BoxedError::new)
884 .context(ExternalSnafu)?,
885 };
886
887 struct FindAggr {
889 is_aggr: bool,
890 }
891
892 impl TreeNodeVisitor<'_> for FindAggr {
893 type Node = LogicalPlan;
894 fn f_down(
895 &mut self,
896 node: &Self::Node,
897 ) -> datafusion_common::Result<datafusion_common::tree_node::TreeNodeRecursion>
898 {
899 match node {
900 LogicalPlan::Aggregate(_) | LogicalPlan::Distinct(_) => {
901 self.is_aggr = true;
902 return Ok(datafusion_common::tree_node::TreeNodeRecursion::Stop);
903 }
904 _ => (),
905 }
906 Ok(datafusion_common::tree_node::TreeNodeRecursion::Continue)
907 }
908 }
909
910 let mut find_aggr = FindAggr { is_aggr: false };
911
912 plan.visit_with_subqueries(&mut find_aggr)
913 .context(BuildDfLogicalPlanSnafu)?;
914 if find_aggr.is_aggr {
915 Ok(FlowType::Batching)
916 } else {
917 Ok(FlowType::Streaming)
918 }
919 }
920
921 #[tracing::instrument(skip_all)]
922 pub async fn create_view(
923 &self,
924 create_view: CreateView,
925 ctx: QueryContextRef,
926 ) -> Result<TableRef> {
927 let logical_plan = match &*create_view.query {
929 Statement::Query(query) => {
930 self.plan(
931 &QueryStatement::Sql(Statement::Query(query.clone())),
932 ctx.clone(),
933 )
934 .await?
935 }
936 Statement::Tql(query) => self.plan_tql(query.clone(), &ctx).await?,
937 _ => {
938 return InvalidViewStmtSnafu {}.fail();
939 }
940 };
941 let definition = create_view.to_string();
943
944 let schema: Schema = logical_plan
947 .schema()
948 .clone()
949 .try_into()
950 .context(ConvertSchemaSnafu)?;
951 let plan_columns: Vec<_> = schema
952 .column_schemas()
953 .iter()
954 .map(|c| c.name.clone())
955 .collect();
956
957 let columns: Vec<_> = create_view
958 .columns
959 .iter()
960 .map(|ident| ident.to_string())
961 .collect();
962
963 if !columns.is_empty() {
965 ensure!(
966 columns.len() == plan_columns.len(),
967 error::ViewColumnsMismatchSnafu {
968 view_name: create_view.name.to_string(),
969 expected: plan_columns.len(),
970 actual: columns.len(),
971 }
972 );
973 }
974
975 let (table_names, plan) = extract_and_rewrite_full_table_names(logical_plan, ctx.clone())
978 .context(ExtractTableNamesSnafu)?;
979
980 let table_names = table_names.into_iter().map(|t| t.into()).collect();
981
982 let encoded_plan = DFLogicalSubstraitConvertor
989 .encode(&plan, DefaultSerializer)
990 .context(SubstraitCodecSnafu)?;
991
992 let expr = expr_helper::to_create_view_expr(
993 create_view,
994 encoded_plan.to_vec(),
995 table_names,
996 columns,
997 plan_columns,
998 definition,
999 ctx.clone(),
1000 )?;
1001
1002 self.create_view_by_expr(expr, ctx).await
1004 }
1005
1006 pub async fn create_view_by_expr(
1007 &self,
1008 expr: CreateViewExpr,
1009 ctx: QueryContextRef,
1010 ) -> Result<TableRef> {
1011 ensure_table_definition_writable(&expr.schema_name, &expr.view_name)?;
1014 ensure! {
1015 !(expr.create_if_not_exists & expr.or_replace),
1016 InvalidSqlSnafu {
1017 err_msg: "syntax error Create Or Replace and If Not Exist cannot be used together",
1018 }
1019 };
1020 let _timer = crate::metrics::DIST_CREATE_VIEW.start_timer();
1021
1022 let schema_exists = self
1023 .table_metadata_manager
1024 .schema_manager()
1025 .exists(SchemaNameKey::new(&expr.catalog_name, &expr.schema_name))
1026 .await
1027 .context(TableMetadataManagerSnafu)?;
1028
1029 ensure!(
1030 schema_exists,
1031 SchemaNotFoundSnafu {
1032 schema_info: &expr.schema_name,
1033 }
1034 );
1035
1036 if let Some(table) = self
1038 .catalog_manager
1039 .table(
1040 &expr.catalog_name,
1041 &expr.schema_name,
1042 &expr.view_name,
1043 Some(&ctx),
1044 )
1045 .await
1046 .context(CatalogSnafu)?
1047 {
1048 let table_type = table.table_info().table_type;
1049
1050 match (table_type, expr.create_if_not_exists, expr.or_replace) {
1051 (TableType::View, true, false) => {
1052 return Ok(table);
1053 }
1054 (TableType::View, false, false) => {
1055 return ViewAlreadyExistsSnafu {
1056 name: format_full_table_name(
1057 &expr.catalog_name,
1058 &expr.schema_name,
1059 &expr.view_name,
1060 ),
1061 }
1062 .fail();
1063 }
1064 (TableType::View, _, true) => {
1065 }
1067 _ => {
1068 return TableAlreadyExistsSnafu {
1069 table: format_full_table_name(
1070 &expr.catalog_name,
1071 &expr.schema_name,
1072 &expr.view_name,
1073 ),
1074 }
1075 .fail();
1076 }
1077 }
1078 }
1079
1080 ensure!(
1081 NAME_PATTERN_REG.is_match(&expr.view_name),
1082 InvalidViewNameSnafu {
1083 name: expr.view_name.clone(),
1084 }
1085 );
1086
1087 let view_name = TableName::new(&expr.catalog_name, &expr.schema_name, &expr.view_name);
1088
1089 let mut view_info = TableInfo {
1090 ident: metadata::TableIdent {
1091 table_id: 0,
1093 version: 0,
1094 },
1095 name: expr.view_name.clone(),
1096 desc: None,
1097 catalog_name: expr.catalog_name.clone(),
1098 schema_name: expr.schema_name.clone(),
1099 meta: TableMeta::empty(),
1101 table_type: TableType::View,
1102 };
1103
1104 let executor_context = to_executor_context(ctx, TriggerReason::Manual);
1105 let request = SubmitDdlTaskRequest::new(DdlTask::new_create_view(expr, view_info.clone()));
1106
1107 let resp = self
1108 .procedure_executor
1109 .submit_ddl_task(executor_context, request)
1110 .await
1111 .context(error::ExecuteDdlSnafu)?;
1112
1113 debug!(
1114 "Submit creating view '{view_name}' task response: {:?}",
1115 resp
1116 );
1117
1118 let view_id = resp
1119 .table_ids
1120 .into_iter()
1121 .next()
1122 .context(error::UnexpectedSnafu {
1123 violated: "expected table_id",
1124 })?;
1125 info!("Successfully created view '{view_name}' with view id {view_id}");
1126
1127 view_info.ident.table_id = view_id;
1128
1129 let view_info = Arc::new(view_info);
1130
1131 let table = DistTable::table(view_info);
1132
1133 self.cache_invalidator
1135 .invalidate(
1136 &Context::default(),
1137 &[
1138 CacheIdent::TableId(view_id),
1139 CacheIdent::TableName(view_name.clone()),
1140 ],
1141 )
1142 .await
1143 .context(error::InvalidateTableCacheSnafu)?;
1144
1145 Ok(table)
1146 }
1147
1148 #[tracing::instrument(skip_all)]
1149 pub async fn drop_flow(
1150 &self,
1151 catalog_name: String,
1152 flow_name: String,
1153 drop_if_exists: bool,
1154 query_context: QueryContextRef,
1155 ) -> Result<Output> {
1156 if let Some(flow) = self
1157 .flow_metadata_manager
1158 .flow_name_manager()
1159 .get(&catalog_name, &flow_name)
1160 .await
1161 .context(error::TableMetadataManagerSnafu)?
1162 {
1163 let flow_id = flow.flow_id();
1164 let task = DropFlowTask {
1165 catalog_name,
1166 flow_name,
1167 flow_id,
1168 drop_if_exists,
1169 };
1170 self.drop_flow_procedure(task, query_context).await?;
1171
1172 Ok(Output::new_with_affected_rows(0))
1173 } else if drop_if_exists {
1174 Ok(Output::new_with_affected_rows(0))
1175 } else {
1176 FlowNotFoundSnafu {
1177 flow_name: format_full_flow_name(&catalog_name, &flow_name),
1178 }
1179 .fail()
1180 }
1181 }
1182
1183 async fn drop_flow_procedure(
1184 &self,
1185 expr: DropFlowTask,
1186 query_context: QueryContextRef,
1187 ) -> Result<SubmitDdlTaskResponse> {
1188 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
1189 let request = SubmitDdlTaskRequest::new(DdlTask::new_drop_flow(expr));
1190
1191 self.procedure_executor
1192 .submit_ddl_task(executor_context, request)
1193 .await
1194 .context(error::ExecuteDdlSnafu)
1195 }
1196
1197 #[cfg(feature = "enterprise")]
1198 #[tracing::instrument(skip_all)]
1199 pub(super) async fn drop_trigger(
1200 &self,
1201 catalog_name: String,
1202 trigger_name: String,
1203 drop_if_exists: bool,
1204 query_context: QueryContextRef,
1205 ) -> Result<Output> {
1206 let task = DropTriggerTask {
1207 catalog_name,
1208 trigger_name,
1209 drop_if_exists,
1210 };
1211 self.drop_trigger_procedure(task, query_context).await?;
1212 Ok(Output::new_with_affected_rows(0))
1213 }
1214
1215 #[cfg(feature = "enterprise")]
1216 async fn drop_trigger_procedure(
1217 &self,
1218 expr: DropTriggerTask,
1219 query_context: QueryContextRef,
1220 ) -> Result<SubmitDdlTaskResponse> {
1221 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
1222 let request = SubmitDdlTaskRequest::new(DdlTask::new_drop_trigger(expr));
1223
1224 self.procedure_executor
1225 .submit_ddl_task(executor_context, request)
1226 .await
1227 .context(error::ExecuteDdlSnafu)
1228 }
1229
1230 #[tracing::instrument(skip_all)]
1232 pub async fn drop_view(
1233 &self,
1234 catalog: String,
1235 schema: String,
1236 view: String,
1237 drop_if_exists: bool,
1238 query_context: QueryContextRef,
1239 ) -> Result<Output> {
1240 let view_info = if let Some(view) = self
1241 .catalog_manager
1242 .table(&catalog, &schema, &view, None)
1243 .await
1244 .context(CatalogSnafu)?
1245 {
1246 view.table_info()
1247 } else if drop_if_exists {
1248 return Ok(Output::new_with_affected_rows(0));
1250 } else {
1251 return TableNotFoundSnafu {
1252 table_name: format_full_table_name(&catalog, &schema, &view),
1253 }
1254 .fail();
1255 };
1256
1257 ensure!(
1259 view_info.table_type == TableType::View,
1260 error::InvalidViewSnafu {
1261 msg: "not a view",
1262 view_name: format_full_table_name(&catalog, &schema, &view),
1263 }
1264 );
1265
1266 let view_id = view_info.table_id();
1267 let view_name = TableName::new(&catalog, &schema, &view);
1268
1269 let task = DropViewTask {
1270 catalog,
1271 schema,
1272 view,
1273 view_id,
1274 drop_if_exists,
1275 };
1276
1277 self.drop_view_procedure(task, query_context).await?;
1278
1279 self.cache_invalidator
1281 .invalidate(
1282 &Context::default(),
1283 &[
1284 CacheIdent::TableId(view_id),
1285 CacheIdent::TableName(view_name),
1286 ],
1287 )
1288 .await
1289 .context(error::InvalidateTableCacheSnafu)?;
1290
1291 Ok(Output::new_with_affected_rows(0))
1292 }
1293
1294 async fn drop_view_procedure(
1296 &self,
1297 expr: DropViewTask,
1298 query_context: QueryContextRef,
1299 ) -> Result<SubmitDdlTaskResponse> {
1300 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
1301 let request = SubmitDdlTaskRequest::new(DdlTask::new_drop_view(expr));
1302
1303 self.procedure_executor
1304 .submit_ddl_task(executor_context, request)
1305 .await
1306 .context(error::ExecuteDdlSnafu)
1307 }
1308
1309 #[tracing::instrument(skip_all)]
1310 pub async fn alter_logical_tables(
1311 &self,
1312 alter_table_exprs: Vec<AlterTableExpr>,
1313 query_context: QueryContextRef,
1314 trigger_reason: TriggerReason,
1315 ) -> Result<Output> {
1316 let _timer = crate::metrics::DIST_ALTER_TABLES.start_timer();
1317 ensure!(
1318 !alter_table_exprs.is_empty(),
1319 EmptyDdlExprSnafu {
1320 name: "alter logical tables"
1321 }
1322 );
1323
1324 let mut groups: HashMap<TableId, Vec<AlterTableExpr>> = HashMap::new();
1326 for expr in alter_table_exprs {
1327 let catalog = if expr.catalog_name.is_empty() {
1329 query_context.current_catalog()
1330 } else {
1331 &expr.catalog_name
1332 };
1333 let schema = if expr.schema_name.is_empty() {
1334 query_context.current_schema()
1335 } else {
1336 expr.schema_name.clone()
1337 };
1338 let table_name = &expr.table_name;
1339 let table = self
1340 .catalog_manager
1341 .table(catalog, &schema, table_name, Some(&query_context))
1342 .await
1343 .context(CatalogSnafu)?
1344 .with_context(|| TableNotFoundSnafu {
1345 table_name: format_full_table_name(catalog, &schema, table_name),
1346 })?;
1347 let table_id = table.table_info().ident.table_id;
1348 let physical_table_id = self
1349 .table_metadata_manager
1350 .table_route_manager()
1351 .get_physical_table_id(table_id)
1352 .await
1353 .context(TableMetadataManagerSnafu)?;
1354 groups.entry(physical_table_id).or_default().push(expr);
1355 }
1356
1357 let mut handles = Vec::with_capacity(groups.len());
1359 for (_physical_table_id, exprs) in groups {
1360 let fut =
1361 self.alter_logical_tables_procedure(exprs, query_context.clone(), trigger_reason);
1362 handles.push(fut);
1363 }
1364 let _results = futures::future::try_join_all(handles).await?;
1365
1366 Ok(Output::new_with_affected_rows(0))
1367 }
1368
1369 #[tracing::instrument(skip_all)]
1370 pub async fn drop_table(
1371 &self,
1372 table_name: TableName,
1373 drop_if_exists: bool,
1374 query_context: QueryContextRef,
1375 ) -> Result<Output> {
1376 self.drop_tables(&[table_name], drop_if_exists, query_context)
1378 .await
1379 }
1380
1381 #[tracing::instrument(skip_all)]
1382 pub async fn drop_tables(
1383 &self,
1384 table_names: &[TableName],
1385 drop_if_exists: bool,
1386 query_context: QueryContextRef,
1387 ) -> Result<Output> {
1388 let mut tables = Vec::with_capacity(table_names.len());
1389 for table_name in table_names {
1390 ensure_table_writable(&table_name.schema_name, &table_name.table_name)?;
1391
1392 if let Some(table) = self
1393 .catalog_manager
1394 .table(
1395 &table_name.catalog_name,
1396 &table_name.schema_name,
1397 &table_name.table_name,
1398 Some(&query_context),
1399 )
1400 .await
1401 .context(CatalogSnafu)?
1402 {
1403 tables.push(table.table_info().table_id());
1404 } else if drop_if_exists {
1405 continue;
1407 } else {
1408 return TableNotFoundSnafu {
1409 table_name: table_name.to_string(),
1410 }
1411 .fail();
1412 }
1413 }
1414
1415 for (table_name, table_id) in table_names.iter().zip(tables.into_iter()) {
1416 self.drop_table_procedure(table_name, table_id, drop_if_exists, query_context.clone())
1417 .await?;
1418
1419 self.cache_invalidator
1421 .invalidate(
1422 &Context::default(),
1423 &[
1424 CacheIdent::TableId(table_id),
1425 CacheIdent::TableName(table_name.clone()),
1426 ],
1427 )
1428 .await
1429 .context(error::InvalidateTableCacheSnafu)?;
1430 }
1431 Ok(Output::new_with_affected_rows(0))
1432 }
1433
1434 #[cfg(feature = "enterprise")]
1435 #[tracing::instrument(skip_all)]
1436 pub async fn undrop_table(
1437 &self,
1438 table_name: TableName,
1439 query_context: QueryContextRef,
1440 ) -> Result<Output> {
1441 execute_undrop_table(
1442 &self.table_metadata_manager,
1443 &self.procedure_executor,
1444 &self.cache_invalidator,
1445 table_name,
1446 query_context,
1447 )
1448 .await
1449 }
1450
1451 #[tracing::instrument(skip_all)]
1452 pub async fn drop_database(
1453 &self,
1454 catalog: String,
1455 schema: String,
1456 drop_if_exists: bool,
1457 query_context: QueryContextRef,
1458 ) -> Result<Output> {
1459 ensure!(
1460 !is_readonly_schema(&schema),
1461 SchemaReadOnlySnafu { name: schema }
1462 );
1463
1464 if self
1465 .catalog_manager
1466 .schema_exists(&catalog, &schema, None)
1467 .await
1468 .context(CatalogSnafu)?
1469 {
1470 if schema == query_context.current_schema() {
1471 SchemaInUseSnafu { name: schema }.fail()
1472 } else {
1473 self.drop_database_procedure(catalog, schema, drop_if_exists, query_context)
1474 .await?;
1475
1476 Ok(Output::new_with_affected_rows(0))
1477 }
1478 } else if drop_if_exists {
1479 Ok(Output::new_with_affected_rows(0))
1481 } else {
1482 SchemaNotFoundSnafu {
1483 schema_info: schema,
1484 }
1485 .fail()
1486 }
1487 }
1488
1489 #[tracing::instrument(skip_all)]
1490 pub async fn truncate_table(
1491 &self,
1492 table_name: TableName,
1493 time_ranges: Vec<(Timestamp, Timestamp)>,
1494 query_context: QueryContextRef,
1495 ) -> Result<Output> {
1496 ensure_table_writable(&table_name.schema_name, &table_name.table_name)?;
1497
1498 let table = self
1499 .catalog_manager
1500 .table(
1501 &table_name.catalog_name,
1502 &table_name.schema_name,
1503 &table_name.table_name,
1504 Some(&query_context),
1505 )
1506 .await
1507 .context(CatalogSnafu)?
1508 .with_context(|| TableNotFoundSnafu {
1509 table_name: table_name.to_string(),
1510 })?;
1511 let table_id = table.table_info().table_id();
1512 self.truncate_table_procedure(&table_name, table_id, time_ranges, query_context)
1513 .await?;
1514
1515 Ok(Output::new_with_affected_rows(0))
1516 }
1517
1518 #[tracing::instrument(skip_all)]
1519 pub async fn alter_table(
1520 &self,
1521 alter_table: AlterTable,
1522 query_context: QueryContextRef,
1523 ) -> Result<Output> {
1524 if matches!(
1525 alter_table.alter_operation(),
1526 AlterTableOperation::Repartition { .. } | AlterTableOperation::Partition { .. }
1527 ) {
1528 let request = expr_helper::to_repartition_request(alter_table, &query_context)?;
1529 return self.repartition_table(request, &query_context).await;
1530 }
1531
1532 let expr = expr_helper::to_alter_table_expr(alter_table, &query_context)?;
1533 self.alter_table_inner(expr, query_context, TriggerReason::Manual)
1534 .await
1535 }
1536
1537 #[tracing::instrument(skip_all)]
1538 pub async fn repartition_table(
1539 &self,
1540 request: RepartitionRequest,
1541 query_context: &QueryContextRef,
1542 ) -> Result<Output> {
1543 ensure_table_definition_writable(&request.schema_name, &request.table_name)?;
1545
1546 let table_ref = TableReference::full(
1547 &request.catalog_name,
1548 &request.schema_name,
1549 &request.table_name,
1550 );
1551 let table = self
1553 .catalog_manager
1554 .table(
1555 &request.catalog_name,
1556 &request.schema_name,
1557 &request.table_name,
1558 Some(query_context),
1559 )
1560 .await
1561 .context(CatalogSnafu)?
1562 .with_context(|| TableNotFoundSnafu {
1563 table_name: table_ref.to_string(),
1564 })?;
1565 let table_id = table.table_info().ident.table_id;
1566 let (physical_table_id, physical_table_route) = self
1568 .table_metadata_manager
1569 .table_route_manager()
1570 .get_physical_table_route(table_id)
1571 .await
1572 .context(TableMetadataManagerSnafu)?;
1573
1574 ensure!(
1575 physical_table_id == table_id,
1576 NotSupportedSnafu {
1577 feat: "REPARTITION on logical tables"
1578 }
1579 );
1580
1581 let table_info = table.table_info();
1582 let existing_partition_columns = table_info.meta.partition_columns().collect::<Vec<_>>();
1583 let column_schemas = table_info.meta.schema.column_schemas();
1584 let target_partition_columns = match &request.source {
1590 RepartitionSource::Partitions {
1591 target_partition_columns,
1592 ..
1593 } => {
1594 ensure!(
1595 !existing_partition_columns.is_empty(),
1596 InvalidPartitionRuleSnafu {
1597 reason: format!(
1598 "table {} does not have partition columns, cannot repartition",
1599 table_ref
1600 )
1601 }
1602 );
1603
1604 if let Some(target_partition_columns) = target_partition_columns {
1605 ensure!(
1606 !target_partition_columns.is_empty(),
1607 InvalidPartitionRuleSnafu {
1608 reason: "ON COLUMNS requires at least one partition column"
1609 }
1610 );
1611 validate_and_collect_partition_columns(
1612 target_partition_columns,
1613 column_schemas,
1614 )?
1615 } else {
1616 existing_partition_columns.clone()
1617 }
1618 }
1619 RepartitionSource::Unpartitioned { partition_columns } => {
1620 ensure!(
1621 !partition_columns.is_empty(),
1622 InvalidPartitionRuleSnafu {
1623 reason: "PARTITION ON COLUMNS requires at least one partition column"
1624 }
1625 );
1626 ensure!(
1627 existing_partition_columns.is_empty(),
1628 InvalidPartitionRuleSnafu {
1629 reason: format!("table {} already has partition columns", table_ref)
1630 }
1631 );
1632 partition_columns
1633 .iter()
1634 .map(|column_name| {
1635 column_schemas
1636 .iter()
1637 .find(|column| &column.name == column_name)
1638 .with_context(|| ColumnNotFoundSnafu { msg: column_name })
1639 })
1640 .collect::<Result<Vec<_>>>()?
1641 }
1642 };
1643
1644 let from_column_name_and_type = column_name_and_type(&existing_partition_columns);
1645 let target_column_name_and_type = column_name_and_type(&target_partition_columns);
1646 let target_partition_column_names = target_partition_columns
1647 .iter()
1648 .map(|column| column.name.clone())
1649 .collect::<Vec<_>>();
1650 let timezone = query_context.timezone();
1651 let from_partition_exprs = match &request.source {
1653 RepartitionSource::Partitions { from_exprs, .. } => from_exprs
1654 .iter()
1655 .map(|expr| convert_one_expr(expr, &from_column_name_and_type, &timezone))
1656 .collect::<Result<Vec<_>>>()?,
1657 RepartitionSource::Unpartitioned { .. } => vec![],
1658 };
1659
1660 let mut into_partition_exprs = request
1661 .into_exprs
1662 .iter()
1663 .map(|expr| convert_one_expr(expr, &target_column_name_and_type, &timezone))
1664 .collect::<Result<Vec<_>>>()?;
1665
1666 if matches!(&request.source, RepartitionSource::Partitions { .. })
1669 && from_partition_exprs.len() > 1
1670 && into_partition_exprs.len() == 1
1671 && let Some(expr) = into_partition_exprs.pop()
1672 {
1673 into_partition_exprs.push(partition::simplify::simplify_merged_partition_expr(expr));
1674 }
1675
1676 let mut existing_partition_exprs =
1678 Vec::with_capacity(physical_table_route.region_routes.len());
1679 for route in &physical_table_route.region_routes {
1680 let expr_json = route.region.partition_expr();
1681 if !expr_json.is_empty() {
1682 match PartitionExpr::from_json_str(&expr_json) {
1683 Ok(Some(expr)) => existing_partition_exprs.push(expr),
1684 Ok(None) => {
1685 }
1687 Err(e) => {
1688 return Err(e).context(DeserializePartitionExprSnafu);
1689 }
1690 }
1691 }
1692 }
1693
1694 if matches!(&request.source, RepartitionSource::Partitions { .. }) {
1697 for from_expr in &from_partition_exprs {
1698 ensure!(
1699 existing_partition_exprs.contains(from_expr),
1700 InvalidPartitionRuleSnafu {
1701 reason: format!(
1702 "partition expression '{}' does not exist in table {}",
1703 from_expr, table_ref
1704 )
1705 }
1706 );
1707 }
1708 }
1709
1710 let new_partition_exprs: Vec<PartitionExpr> = match &request.source {
1713 RepartitionSource::Partitions { .. } => existing_partition_exprs
1714 .into_iter()
1715 .filter(|expr| !from_partition_exprs.contains(expr))
1716 .chain(into_partition_exprs.clone().into_iter())
1717 .collect(),
1718 RepartitionSource::Unpartitioned { .. } => into_partition_exprs.clone(),
1719 };
1720 ensure_partition_expr_columns_in_target(
1721 &new_partition_exprs,
1722 &target_partition_column_names.iter().collect(),
1723 )?;
1724 let new_partition_exprs_len = new_partition_exprs.len();
1725 let from_partition_exprs_len = from_partition_exprs.len();
1726
1727 let _ = MultiDimPartitionRule::try_new(
1729 target_partition_column_names,
1730 vec![],
1731 new_partition_exprs,
1732 true,
1733 )
1734 .context(InvalidPartitionSnafu)?;
1735
1736 let ddl_options = parse_ddl_options(&request.options)?;
1737 let serialize_exprs = |exprs: Vec<PartitionExpr>| -> Result<Vec<String>> {
1738 let mut json_exprs = Vec::with_capacity(exprs.len());
1739 for expr in exprs {
1740 json_exprs.push(expr.as_json_str().context(SerializePartitionExprSnafu)?);
1741 }
1742 Ok(json_exprs)
1743 };
1744 let from_partition_exprs_json = serialize_exprs(from_partition_exprs)?;
1745 let into_partition_exprs_json = serialize_exprs(into_partition_exprs)?;
1746 let source = match &request.source {
1747 RepartitionSource::Partitions {
1748 target_partition_columns,
1749 ..
1750 } => Source::PartitionExprs(PartitionedSource {
1751 exprs: from_partition_exprs_json,
1752 target_partition_columns: target_partition_columns
1753 .clone()
1754 .map(|columns| TargetPartitionColumns { columns }),
1755 }),
1756 RepartitionSource::Unpartitioned { partition_columns } => {
1757 Source::Unpartitioned(UnpartitionedSource {
1758 partition_columns: partition_columns.clone(),
1759 })
1760 }
1761 };
1762 let repartition = Repartition {
1763 into_partition_exprs: into_partition_exprs_json,
1764 source: Some(source),
1765 ..Default::default()
1766 };
1767 let executor_context = to_executor_context(query_context.clone(), TriggerReason::Manual);
1768 let mut req = SubmitDdlTaskRequest::new(DdlTask::new_alter_table(AlterTableExpr {
1769 catalog_name: request.catalog_name.clone(),
1770 schema_name: request.schema_name.clone(),
1771 table_name: request.table_name.clone(),
1772 kind: Some(Kind::Repartition(repartition)),
1773 }));
1774 req.wait = ddl_options.wait;
1775 req.timeout = ddl_options.timeout;
1776
1777 info!(
1778 "Submitting repartition task for table {} (table_id={}), from {} to {} partitions, timeout: {:?}, wait: {}",
1779 table_ref,
1780 table_id,
1781 from_partition_exprs_len,
1782 new_partition_exprs_len,
1783 ddl_options.timeout,
1784 ddl_options.wait
1785 );
1786
1787 let response = self
1788 .procedure_executor
1789 .submit_ddl_task(executor_context, req)
1790 .await
1791 .context(error::ExecuteDdlSnafu)?;
1792
1793 if !ddl_options.wait {
1794 return build_procedure_id_output(response.key);
1795 }
1796
1797 let invalidate_keys = vec![
1799 CacheIdent::TableId(table_id),
1800 CacheIdent::TableName(TableName::new(
1801 request.catalog_name,
1802 request.schema_name,
1803 request.table_name,
1804 )),
1805 ];
1806
1807 self.cache_invalidator
1809 .invalidate(&Context::default(), &invalidate_keys)
1810 .await
1811 .context(error::InvalidateTableCacheSnafu)?;
1812
1813 Ok(Output::new_with_affected_rows(0))
1814 }
1815
1816 #[tracing::instrument(skip_all)]
1817 pub async fn alter_table_inner(
1818 &self,
1819 expr: AlterTableExpr,
1820 query_context: QueryContextRef,
1821 trigger_reason: TriggerReason,
1822 ) -> Result<Output> {
1823 ensure_table_definition_writable(&expr.schema_name, &expr.table_name)?;
1824
1825 let catalog_name = if expr.catalog_name.is_empty() {
1826 DEFAULT_CATALOG_NAME.to_string()
1827 } else {
1828 expr.catalog_name.clone()
1829 };
1830
1831 let schema_name = if expr.schema_name.is_empty() {
1832 DEFAULT_SCHEMA_NAME.to_string()
1833 } else {
1834 expr.schema_name.clone()
1835 };
1836
1837 let table_name = expr.table_name.clone();
1838
1839 let table = self
1840 .catalog_manager
1841 .table(
1842 &catalog_name,
1843 &schema_name,
1844 &table_name,
1845 Some(&query_context),
1846 )
1847 .await
1848 .context(CatalogSnafu)?
1849 .with_context(|| TableNotFoundSnafu {
1850 table_name: format_full_table_name(&catalog_name, &schema_name, &table_name),
1851 })?;
1852
1853 let table_id = table.table_info().ident.table_id;
1854 let need_alter = verify_alter(table_id, table.table_info(), expr.clone())?;
1855 if !need_alter {
1856 return Ok(Output::new_with_affected_rows(0));
1857 }
1858 info!(
1859 "Table info before alter is {:?}, expr: {:?}",
1860 table.table_info(),
1861 expr
1862 );
1863
1864 let physical_table_id = self
1865 .table_metadata_manager
1866 .table_route_manager()
1867 .get_physical_table_id(table_id)
1868 .await
1869 .context(TableMetadataManagerSnafu)?;
1870
1871 let executor_context = to_executor_context(query_context, trigger_reason);
1872
1873 let (req, invalidate_keys) = if physical_table_id == table_id {
1874 let req = SubmitDdlTaskRequest::new(DdlTask::new_alter_table(expr));
1876
1877 let invalidate_keys = vec![
1878 CacheIdent::TableId(table_id),
1879 CacheIdent::TableName(TableName::new(catalog_name, schema_name, table_name)),
1880 ];
1881
1882 (req, invalidate_keys)
1883 } else {
1884 let annotation_alter = match expr.kind.as_ref() {
1887 Some(kind) => common_grpc_expr::annotation_alter_family(kind)
1888 .context(AlterExprToRequestSnafu)?
1889 .is_some_and(|family| family.allows_logical_tables()),
1890 None => false,
1891 };
1892 let task = if annotation_alter {
1893 DdlTask::new_alter_table(expr)
1894 } else {
1895 DdlTask::new_alter_logical_tables(vec![expr])
1896 };
1897 let req = SubmitDdlTaskRequest::new(task);
1898
1899 let mut invalidate_keys = vec![
1900 CacheIdent::TableId(physical_table_id),
1901 CacheIdent::TableId(table_id),
1902 CacheIdent::TableName(TableName::new(catalog_name, schema_name, table_name)),
1903 ];
1904
1905 let physical_table = self
1906 .table_metadata_manager
1907 .table_info_manager()
1908 .get(physical_table_id)
1909 .await
1910 .context(TableMetadataManagerSnafu)?
1911 .map(|x| x.into_inner());
1912 if let Some(physical_table) = physical_table {
1913 let physical_table_name = TableName::new(
1914 physical_table.table_info.catalog_name,
1915 physical_table.table_info.schema_name,
1916 physical_table.table_info.name,
1917 );
1918 invalidate_keys.push(CacheIdent::TableName(physical_table_name));
1919 }
1920
1921 (req, invalidate_keys)
1922 };
1923
1924 self.procedure_executor
1925 .submit_ddl_task(executor_context, req)
1926 .await
1927 .context(error::ExecuteDdlSnafu)?;
1928
1929 self.cache_invalidator
1931 .invalidate(&Context::default(), &invalidate_keys)
1932 .await
1933 .context(error::InvalidateTableCacheSnafu)?;
1934
1935 Ok(Output::new_with_affected_rows(0))
1936 }
1937
1938 #[cfg(feature = "enterprise")]
1939 #[tracing::instrument(skip_all)]
1940 pub async fn alter_trigger(
1941 &self,
1942 _alter_expr: AlterTrigger,
1943 _query_context: QueryContextRef,
1944 ) -> Result<Output> {
1945 crate::error::NotSupportedSnafu {
1946 feat: "alter trigger",
1947 }
1948 .fail()
1949 }
1950
1951 #[tracing::instrument(skip_all)]
1952 pub async fn alter_database(
1953 &self,
1954 alter_expr: AlterDatabase,
1955 query_context: QueryContextRef,
1956 ) -> Result<Output> {
1957 let alter_expr = expr_helper::to_alter_database_expr(alter_expr, &query_context)?;
1958 self.alter_database_inner(alter_expr, query_context).await
1959 }
1960
1961 #[tracing::instrument(skip_all)]
1962 pub async fn alter_database_inner(
1963 &self,
1964 alter_expr: AlterDatabaseExpr,
1965 query_context: QueryContextRef,
1966 ) -> Result<Output> {
1967 ensure!(
1968 !is_readonly_schema(&alter_expr.schema_name),
1969 SchemaReadOnlySnafu {
1970 name: query_context.current_schema().clone()
1971 }
1972 );
1973
1974 let exists = self
1975 .catalog_manager
1976 .schema_exists(&alter_expr.catalog_name, &alter_expr.schema_name, None)
1977 .await
1978 .context(CatalogSnafu)?;
1979 ensure!(
1980 exists,
1981 SchemaNotFoundSnafu {
1982 schema_info: alter_expr.schema_name,
1983 }
1984 );
1985
1986 let cache_ident = [CacheIdent::SchemaName(SchemaName {
1987 catalog_name: alter_expr.catalog_name.clone(),
1988 schema_name: alter_expr.schema_name.clone(),
1989 })];
1990
1991 self.alter_database_procedure(alter_expr, query_context)
1992 .await?;
1993
1994 self.cache_invalidator
1996 .invalidate(&Context::default(), &cache_ident)
1997 .await
1998 .context(error::InvalidateTableCacheSnafu)?;
1999
2000 Ok(Output::new_with_affected_rows(0))
2001 }
2002
2003 async fn create_table_procedure(
2004 &self,
2005 create_table: CreateTableExpr,
2006 partitions: Vec<PartitionExpr>,
2007 table_info: TableInfo,
2008 query_context: QueryContextRef,
2009 trigger_reason: TriggerReason,
2010 ) -> Result<SubmitDdlTaskResponse> {
2011 let partitions = partitions
2012 .into_iter()
2013 .map(|expr| expr.as_pb_partition().context(PartitionExprToPbSnafu))
2014 .collect::<Result<Vec<_>>>()?;
2015
2016 let executor_context = to_executor_context_with_origin_frontend(
2017 query_context,
2018 &self.origin_frontend_addr,
2019 trigger_reason,
2020 );
2021 let request = SubmitDdlTaskRequest::new(DdlTask::new_create_table(
2022 create_table,
2023 partitions,
2024 table_info,
2025 ));
2026
2027 self.procedure_executor
2028 .submit_ddl_task(executor_context, request)
2029 .await
2030 .context(error::ExecuteDdlSnafu)
2031 }
2032
2033 async fn create_logical_tables_procedure(
2034 &self,
2035 tables_data: Vec<(CreateTableExpr, TableInfo)>,
2036 query_context: QueryContextRef,
2037 trigger_reason: TriggerReason,
2038 ) -> Result<SubmitDdlTaskResponse> {
2039 let executor_context = to_executor_context_with_origin_frontend(
2040 query_context,
2041 &self.origin_frontend_addr,
2042 trigger_reason,
2043 );
2044 let request = SubmitDdlTaskRequest::new(DdlTask::new_create_logical_tables(tables_data));
2045
2046 self.procedure_executor
2047 .submit_ddl_task(executor_context, request)
2048 .await
2049 .context(error::ExecuteDdlSnafu)
2050 }
2051
2052 async fn alter_logical_tables_procedure(
2053 &self,
2054 tables_data: Vec<AlterTableExpr>,
2055 query_context: QueryContextRef,
2056 trigger_reason: TriggerReason,
2057 ) -> Result<SubmitDdlTaskResponse> {
2058 let executor_context = to_executor_context(query_context, trigger_reason);
2059 let request = SubmitDdlTaskRequest::new(DdlTask::new_alter_logical_tables(tables_data));
2060
2061 self.procedure_executor
2062 .submit_ddl_task(executor_context, request)
2063 .await
2064 .context(error::ExecuteDdlSnafu)
2065 }
2066
2067 async fn drop_table_procedure(
2068 &self,
2069 table_name: &TableName,
2070 table_id: TableId,
2071 drop_if_exists: bool,
2072 query_context: QueryContextRef,
2073 ) -> Result<SubmitDdlTaskResponse> {
2074 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2075 let request = SubmitDdlTaskRequest::new(DdlTask::new_drop_table(
2076 table_name.catalog_name.clone(),
2077 table_name.schema_name.clone(),
2078 table_name.table_name.clone(),
2079 table_id,
2080 drop_if_exists,
2081 ));
2082
2083 self.procedure_executor
2084 .submit_ddl_task(executor_context, request)
2085 .await
2086 .context(error::ExecuteDdlSnafu)
2087 }
2088
2089 async fn drop_database_procedure(
2090 &self,
2091 catalog: String,
2092 schema: String,
2093 drop_if_exists: bool,
2094 query_context: QueryContextRef,
2095 ) -> Result<SubmitDdlTaskResponse> {
2096 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2097 let request =
2098 SubmitDdlTaskRequest::new(DdlTask::new_drop_database(catalog, schema, drop_if_exists));
2099
2100 self.procedure_executor
2101 .submit_ddl_task(executor_context, request)
2102 .await
2103 .context(error::ExecuteDdlSnafu)
2104 }
2105
2106 async fn alter_database_procedure(
2107 &self,
2108 alter_expr: AlterDatabaseExpr,
2109 query_context: QueryContextRef,
2110 ) -> Result<SubmitDdlTaskResponse> {
2111 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2112 let request = SubmitDdlTaskRequest::new(DdlTask::new_alter_database(alter_expr));
2113
2114 self.procedure_executor
2115 .submit_ddl_task(executor_context, request)
2116 .await
2117 .context(error::ExecuteDdlSnafu)
2118 }
2119
2120 async fn truncate_table_procedure(
2121 &self,
2122 table_name: &TableName,
2123 table_id: TableId,
2124 time_ranges: Vec<(Timestamp, Timestamp)>,
2125 query_context: QueryContextRef,
2126 ) -> Result<SubmitDdlTaskResponse> {
2127 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2128 let request = SubmitDdlTaskRequest::new(DdlTask::new_truncate_table(
2129 table_name.catalog_name.clone(),
2130 table_name.schema_name.clone(),
2131 table_name.table_name.clone(),
2132 table_id,
2133 time_ranges,
2134 ));
2135
2136 self.procedure_executor
2137 .submit_ddl_task(executor_context, request)
2138 .await
2139 .context(error::ExecuteDdlSnafu)
2140 }
2141
2142 #[tracing::instrument(skip_all)]
2143 pub async fn create_database(
2144 &self,
2145 database: &str,
2146 create_if_not_exists: bool,
2147 options: HashMap<String, String>,
2148 query_context: QueryContextRef,
2149 ) -> Result<Output> {
2150 let catalog = query_context.current_catalog();
2151 ensure!(
2152 NAME_PATTERN_REG.is_match(catalog),
2153 error::UnexpectedSnafu {
2154 violated: format!("Invalid catalog name: {}", catalog)
2155 }
2156 );
2157
2158 ensure!(
2159 NAME_PATTERN_REG.is_match(database),
2160 error::UnexpectedSnafu {
2161 violated: format!("Invalid database name: {}", database)
2162 }
2163 );
2164
2165 #[cfg(feature = "enterprise")]
2166 let creator = match &self.create_database_handler {
2167 Some(handler) => handler.creator(&query_context).context(ExternalSnafu)?,
2168 None => None,
2169 };
2170 #[cfg(not(feature = "enterprise"))]
2171 let creator = None;
2172
2173 let output = if !self
2174 .catalog_manager
2175 .schema_exists(catalog, database, None)
2176 .await
2177 .context(CatalogSnafu)?
2178 && !self.catalog_manager.is_reserved_schema_name(database)
2179 {
2180 self.create_database_procedure(
2181 catalog.to_string(),
2182 database.to_string(),
2183 create_if_not_exists,
2184 options,
2185 query_context.clone(),
2186 creator,
2187 )
2188 .await?;
2189
2190 Output::new_with_affected_rows(1)
2191 } else if create_if_not_exists {
2192 Output::new_with_affected_rows(1)
2193 } else {
2194 return error::SchemaExistsSnafu { name: database }.fail();
2195 };
2196
2197 #[cfg(feature = "enterprise")]
2198 if let Some(handler) = &self.create_database_handler {
2199 handler
2200 .refresh_current_user(&query_context)
2201 .await
2202 .context(ExternalSnafu)?;
2203 }
2204
2205 Ok(output)
2206 }
2207
2208 async fn create_database_procedure(
2209 &self,
2210 catalog: String,
2211 database: String,
2212 create_if_not_exists: bool,
2213 options: HashMap<String, String>,
2214 query_context: QueryContextRef,
2215 creator: Option<CreatorGrantIntent>,
2216 ) -> Result<SubmitDdlTaskResponse> {
2217 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2218 let request = SubmitDdlTaskRequest::new(DdlTask::new_create_database(
2219 catalog,
2220 database,
2221 create_if_not_exists,
2222 options,
2223 creator,
2224 ));
2225
2226 self.procedure_executor
2227 .submit_ddl_task(executor_context, request)
2228 .await
2229 .context(error::ExecuteDdlSnafu)
2230 }
2231}
2232
2233pub fn parse_partitions(
2235 create_table: &CreateTableExpr,
2236 partitions: Option<Partitions>,
2237 query_ctx: &QueryContextRef,
2238) -> Result<(Vec<PartitionExpr>, Vec<String>)> {
2239 let partition_columns = find_partition_columns(&partitions)?;
2242 let partition_exprs =
2243 find_partition_entries(create_table, &partitions, &partition_columns, query_ctx)?;
2244
2245 let exprs = partition_exprs.clone();
2247 MultiDimPartitionRule::try_new(partition_columns.clone(), vec![], exprs, true)
2248 .context(InvalidPartitionSnafu)?;
2249
2250 Ok((partition_exprs, partition_columns))
2251}
2252
2253fn parse_partitions_for_logical_validation(
2254 create_table: &CreateTableExpr,
2255 partitions: &Partitions,
2256 query_ctx: &QueryContextRef,
2257) -> Result<(Vec<String>, Vec<PartitionExpr>)> {
2258 let partition_columns = partitions
2259 .column_list
2260 .iter()
2261 .map(|ident| ident.value.clone())
2262 .collect::<Vec<_>>();
2263
2264 let column_name_and_type = partition_columns
2265 .iter()
2266 .map(|pc| {
2267 let column = create_table
2268 .column_defs
2269 .iter()
2270 .find(|c| &c.name == pc)
2271 .context(ColumnNotFoundSnafu { msg: pc.clone() })?;
2272 let column_name = &column.name;
2273 let data_type = ConcreteDataType::from(
2274 ColumnDataTypeWrapper::try_new(column.data_type, column.datatype_extension.clone())
2275 .context(ColumnDataTypeSnafu)?,
2276 );
2277 Ok((column_name, data_type))
2278 })
2279 .collect::<Result<HashMap<_, _>>>()?;
2280
2281 let mut partition_exprs = Vec::with_capacity(partitions.exprs.len());
2282 for expr in &partitions.exprs {
2283 let partition_expr = convert_one_expr(expr, &column_name_and_type, &query_ctx.timezone())?;
2284 partition_exprs.push(partition_expr);
2285 }
2286
2287 MultiDimPartitionRule::try_new(
2288 partition_columns.clone(),
2289 vec![],
2290 partition_exprs.clone(),
2291 true,
2292 )
2293 .context(InvalidPartitionSnafu)?;
2294
2295 Ok((partition_columns, partition_exprs))
2296}
2297
2298pub fn verify_alter(
2304 table_id: TableId,
2305 table_info: Arc<TableInfo>,
2306 expr: AlterTableExpr,
2307) -> Result<bool> {
2308 let request: AlterTableRequest =
2309 common_grpc_expr::alter_expr_to_request(table_id, expr, Some(&table_info.meta))
2310 .context(AlterExprToRequestSnafu)?;
2311
2312 let AlterTableRequest {
2313 table_name,
2314 alter_kind,
2315 ..
2316 } = &request;
2317
2318 if let AlterKind::RenameTable { new_table_name } = alter_kind {
2319 ensure!(
2320 NAME_PATTERN_REG.is_match(new_table_name),
2321 error::UnexpectedSnafu {
2322 violated: format!("Invalid table name: {}", new_table_name)
2323 }
2324 );
2325 ensure_table_definition_writable(&table_info.schema_name, new_table_name)?;
2328 } else if let AlterKind::AddColumns { columns } = alter_kind {
2329 let column_names: HashSet<_> = table_info
2332 .meta
2333 .schema
2334 .column_schemas()
2335 .iter()
2336 .map(|schema| &schema.name)
2337 .collect();
2338 if columns.iter().all(|column| {
2339 column_names.contains(&column.column_schema.name) && column.add_if_not_exists
2340 }) {
2341 return Ok(false);
2342 }
2343 }
2344
2345 let new_meta = table_info
2346 .meta
2347 .builder_with_alter_kind(table_name, &request.alter_kind)
2348 .context(error::TableSnafu)?
2349 .build()
2350 .context(error::BuildTableMetaSnafu { table_name })?;
2351
2352 validate_json2_columns_append_mode(&new_meta.schema, &new_meta.options)?;
2353
2354 Ok(true)
2355}
2356
2357pub fn create_table_info(
2358 create_table: &CreateTableExpr,
2359 partition_columns: Vec<String>,
2360) -> Result<TableInfo> {
2361 let mut column_schemas = Vec::with_capacity(create_table.column_defs.len());
2362 let mut column_name_to_index_map = HashMap::new();
2363
2364 for (idx, column) in create_table.column_defs.iter().enumerate() {
2365 let schema =
2366 column_def::try_as_column_schema(column).context(error::InvalidColumnDefSnafu {
2367 column: &column.name,
2368 })?;
2369 let schema = schema.with_time_index(column.name == create_table.time_index);
2370
2371 column_schemas.push(schema);
2372 let _ = column_name_to_index_map.insert(column.name.clone(), idx);
2373 }
2374
2375 let next_column_id = column_schemas.len() as u32;
2376 let schema = Arc::new(Schema::try_new(column_schemas).context(ConvertSchemaSnafu)?);
2377
2378 let primary_key_indices = create_table
2379 .primary_keys
2380 .iter()
2381 .map(|name| {
2382 column_name_to_index_map
2383 .get(name)
2384 .cloned()
2385 .context(ColumnNotFoundSnafu { msg: name })
2386 })
2387 .collect::<Result<Vec<_>>>()?;
2388
2389 let partition_key_indices = partition_columns
2390 .into_iter()
2391 .map(|col_name| {
2392 column_name_to_index_map
2393 .get(&col_name)
2394 .cloned()
2395 .context(ColumnNotFoundSnafu { msg: col_name })
2396 })
2397 .collect::<Result<Vec<_>>>()?;
2398
2399 let mut table_options = TableOptions::try_from_iter(&create_table.table_options)
2400 .context(UnrecognizedTableOptionSnafu)?;
2401
2402 validate_json2_columns_append_mode(&schema, &table_options)?;
2403
2404 validate_and_normalize_annotations(&mut table_options, &schema, &partition_key_indices)?;
2405
2406 let meta = TableMeta {
2407 schema,
2408 primary_key_indices,
2409 value_indices: vec![],
2410 engine: create_table.engine.clone(),
2411 next_column_id,
2412 options: table_options,
2413 created_on: Utc::now(),
2414 updated_on: Utc::now(),
2415 partition_key_indices,
2416 column_ids: vec![],
2417 };
2418
2419 let desc = if create_table.desc.is_empty() {
2420 create_table.table_options.get(COMMENT_KEY).cloned()
2421 } else {
2422 Some(create_table.desc.clone())
2423 };
2424
2425 let table_info = TableInfo {
2426 ident: metadata::TableIdent {
2427 table_id: 0,
2429 version: 0,
2430 },
2431 name: create_table.table_name.clone(),
2432 desc,
2433 catalog_name: create_table.catalog_name.clone(),
2434 schema_name: create_table.schema_name.clone(),
2435 meta,
2436 table_type: TableType::Base,
2437 };
2438 Ok(table_info)
2439}
2440
2441fn validate_json2_columns_append_mode(schema: &Schema, table_options: &TableOptions) -> Result<()> {
2442 let append_mode = table_options
2443 .extra_options
2444 .get(APPEND_MODE_KEY)
2445 .is_some_and(|value| value == "true");
2446
2447 for column in schema.column_schemas() {
2448 if column.data_type.is_json2() {
2449 ensure!(
2450 append_mode,
2451 InvalidSqlSnafu {
2452 err_msg: format!(
2453 "JSON2 column `{}` requires {}='true'",
2454 column.name, APPEND_MODE_KEY
2455 ),
2456 }
2457 );
2458 }
2459 }
2460
2461 Ok(())
2462}
2463
2464fn ensure_table_writable(schema: &str, table: &str) -> Result<()> {
2466 ensure!(
2467 !is_readonly_schema(schema),
2468 SchemaReadOnlySnafu {
2469 name: schema.to_string()
2470 }
2471 );
2472 ensure!(
2473 !is_readonly_table(schema, table),
2474 TableReadOnlySnafu {
2475 name: table.to_string()
2476 }
2477 );
2478 Ok(())
2479}
2480
2481fn ensure_table_definition_writable(schema: &str, table: &str) -> Result<()> {
2487 ensure_table_writable(schema, table)?;
2488 ensure!(
2489 !is_ddl_reserved_table(schema, table),
2490 TableDdlReservedSnafu {
2491 name: table.to_string()
2492 }
2493 );
2494 Ok(())
2495}
2496
2497fn validate_and_normalize_annotations(
2501 options: &mut TableOptions,
2502 schema: &Schema,
2503 partition_key_indices: &[usize],
2504) -> Result<()> {
2505 use table::requests::AnnotationValidationError as CheckError;
2506 let cx = AnnotationContext {
2507 schema,
2508 partition_key_indices,
2509 };
2510 validate_and_normalize_annotation_options(options, &cx).map_err(|e| match e {
2511 CheckError::ColumnNotFound { column } => ColumnNotFoundSnafu { msg: column }.build(),
2512 e @ (CheckError::UnknownKey { .. }
2513 | CheckError::InvalidValue { .. }
2514 | CheckError::ColumnNotStringForm { .. }) => InvalidSqlSnafu {
2515 err_msg: e.to_string(),
2516 }
2517 .build(),
2518 e @ (CheckError::NotSingleColumn
2519 | CheckError::PartitionMetadataConflict
2520 | CheckError::TimeIndexConflict) => InvalidPartitionRuleSnafu {
2521 reason: e.to_string(),
2522 }
2523 .build(),
2524 })
2525}
2526
2527fn find_partition_columns(partitions: &Option<Partitions>) -> Result<Vec<String>> {
2528 let columns = if let Some(partitions) = partitions {
2529 partitions
2530 .column_list
2531 .iter()
2532 .map(|x| x.value.clone())
2533 .collect::<Vec<_>>()
2534 } else {
2535 vec![]
2536 };
2537 Ok(columns)
2538}
2539
2540fn find_partition_entries(
2544 create_table: &CreateTableExpr,
2545 partitions: &Option<Partitions>,
2546 partition_columns: &[String],
2547 query_ctx: &QueryContextRef,
2548) -> Result<Vec<PartitionExpr>> {
2549 let Some(partitions) = partitions else {
2550 return Ok(vec![]);
2551 };
2552
2553 let column_name_and_type = partition_columns
2555 .iter()
2556 .map(|pc| {
2557 let column = create_table
2558 .column_defs
2559 .iter()
2560 .find(|c| &c.name == pc)
2561 .unwrap();
2563 let column_name = &column.name;
2564 let data_type = ConcreteDataType::from(
2565 ColumnDataTypeWrapper::try_new(column.data_type, column.datatype_extension.clone())
2566 .context(ColumnDataTypeSnafu)?,
2567 );
2568 Ok((column_name, data_type))
2569 })
2570 .collect::<Result<HashMap<_, _>>>()?;
2571
2572 let mut partition_exprs = Vec::with_capacity(partitions.exprs.len());
2574 for partition in &partitions.exprs {
2575 let partition_expr =
2576 convert_one_expr(partition, &column_name_and_type, &query_ctx.timezone())?;
2577 partition_exprs.push(partition_expr);
2578 }
2579
2580 Ok(partition_exprs)
2581}
2582
2583fn column_name_and_type<'a>(
2584 partition_columns: &'a [&'a ColumnSchema],
2585) -> HashMap<&'a String, ConcreteDataType> {
2586 partition_columns
2587 .iter()
2588 .map(|column| (&column.name, column.data_type.clone()))
2589 .collect()
2590}
2591
2592fn validate_and_collect_partition_columns<'a>(
2593 column_names: &[String],
2594 column_schemas: &'a [ColumnSchema],
2595) -> Result<Vec<&'a ColumnSchema>> {
2596 let mut seen = HashSet::with_capacity(column_names.len());
2597 column_names
2598 .iter()
2599 .map(|column_name| {
2600 ensure!(
2601 seen.insert(column_name),
2602 InvalidPartitionRuleSnafu {
2603 reason: format!("duplicate partition column '{}'", column_name)
2604 }
2605 );
2606 column_schemas
2607 .iter()
2608 .find(|column| &column.name == column_name)
2609 .with_context(|| ColumnNotFoundSnafu { msg: column_name })
2610 })
2611 .collect()
2612}
2613
2614fn ensure_partition_expr_columns_in_target(
2615 partition_exprs: &[PartitionExpr],
2616 target_partition_columns: &HashSet<&String>,
2617) -> Result<()> {
2618 for expr in partition_exprs {
2619 ensure_partition_operand_columns_in_target(&expr.lhs, target_partition_columns)?;
2620 ensure_partition_operand_columns_in_target(&expr.rhs, target_partition_columns)?;
2621 }
2622
2623 Ok(())
2624}
2625
2626fn ensure_partition_operand_columns_in_target(
2627 operand: &Operand,
2628 target_partition_columns: &HashSet<&String>,
2629) -> Result<()> {
2630 match operand {
2631 Operand::Column(column) => ensure!(
2632 target_partition_columns.contains(column),
2633 InvalidPartitionRuleSnafu {
2634 reason: format!(
2635 "partition expression references column '{}' that is not in target partition columns",
2636 column
2637 )
2638 }
2639 ),
2640 Operand::Expr(expr) => {
2641 ensure_partition_operand_columns_in_target(&expr.lhs, target_partition_columns)?;
2642 ensure_partition_operand_columns_in_target(&expr.rhs, target_partition_columns)?;
2643 }
2644 Operand::Value(_) => {}
2645 }
2646
2647 Ok(())
2648}
2649
2650fn convert_one_expr(
2651 expr: &Expr,
2652 column_name_and_type: &HashMap<&String, ConcreteDataType>,
2653 timezone: &Timezone,
2654) -> Result<PartitionExpr> {
2655 let Expr::BinaryOp { left, op, right } = expr else {
2656 return InvalidPartitionRuleSnafu {
2657 reason: "partition rule must be a binary expression",
2658 }
2659 .fail();
2660 };
2661
2662 let op =
2663 RestrictedOp::try_from_parser(&op.clone()).with_context(|| InvalidPartitionRuleSnafu {
2664 reason: format!("unsupported operator in partition expr {op}"),
2665 })?;
2666
2667 let (lhs, op, rhs) = match (left.as_ref(), right.as_ref()) {
2669 (Expr::Identifier(ident), Expr::Value(value)) => {
2671 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2672 let value = convert_value(&value.value, data_type, timezone, None)?;
2673 (Operand::Column(column_name), op, Operand::Value(value))
2674 }
2675 (Expr::Identifier(ident), Expr::UnaryOp { op: unary_op, expr })
2676 if let Expr::Value(v) = &**expr =>
2677 {
2678 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2679 let value = convert_value(&v.value, data_type, timezone, Some(*unary_op))?;
2680 (Operand::Column(column_name), op, Operand::Value(value))
2681 }
2682 (Expr::Value(value), Expr::Identifier(ident)) => {
2684 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2685 let value = convert_value(&value.value, data_type, timezone, None)?;
2686 (Operand::Value(value), op, Operand::Column(column_name))
2687 }
2688 (Expr::UnaryOp { op: unary_op, expr }, Expr::Identifier(ident))
2689 if let Expr::Value(v) = &**expr =>
2690 {
2691 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2692 let value = convert_value(&v.value, data_type, timezone, Some(*unary_op))?;
2693 (Operand::Value(value), op, Operand::Column(column_name))
2694 }
2695 (Expr::BinaryOp { .. }, Expr::BinaryOp { .. }) => {
2696 let lhs = convert_one_expr(left, column_name_and_type, timezone)?;
2698 let rhs = convert_one_expr(right, column_name_and_type, timezone)?;
2699 (Operand::Expr(lhs), op, Operand::Expr(rhs))
2700 }
2701 _ => {
2702 return InvalidPartitionRuleSnafu {
2703 reason: format!("invalid partition expr {expr}"),
2704 }
2705 .fail();
2706 }
2707 };
2708
2709 Ok(PartitionExpr::new(lhs, op, rhs))
2710}
2711
2712fn convert_identifier(
2713 ident: &Ident,
2714 column_name_and_type: &HashMap<&String, ConcreteDataType>,
2715) -> Result<(String, ConcreteDataType)> {
2716 let column_name = ident.value.clone();
2717 let data_type = column_name_and_type
2718 .get(&column_name)
2719 .cloned()
2720 .with_context(|| ColumnNotFoundSnafu { msg: &column_name })?;
2721 Ok((column_name, data_type))
2722}
2723
2724fn convert_value(
2725 value: &ParserValue,
2726 data_type: ConcreteDataType,
2727 timezone: &Timezone,
2728 unary_op: Option<UnaryOperator>,
2729) -> Result<Value> {
2730 sql_value_to_value(
2731 &ColumnSchema::new("<NONAME>", data_type, true),
2732 value,
2733 Some(timezone),
2734 unary_op,
2735 false,
2736 )
2737 .context(error::SqlCommonSnafu)
2738}
2739
2740#[cfg(feature = "enterprise")]
2741async fn execute_undrop_table(
2742 table_metadata_manager: &TableMetadataManagerRef,
2743 procedure_executor: &ProcedureExecutorRef,
2744 cache_invalidator: &CacheInvalidatorRef,
2745 table_name: TableName,
2746 query_context: QueryContextRef,
2747) -> Result<Output> {
2748 ensure_table_definition_writable(&table_name.schema_name, &table_name.table_name)?;
2751
2752 let dropped = table_metadata_manager
2753 .get_dropped_table(&table_name)
2754 .await
2755 .context(TableMetadataManagerSnafu)?
2756 .with_context(|| TableNotFoundSnafu {
2757 table_name: table_name.to_string(),
2758 })?;
2759
2760 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2761 let request = SubmitDdlTaskRequest::new(DdlTask::new_undrop_table(dropped.table_id));
2762 procedure_executor
2763 .submit_ddl_task(executor_context, request)
2764 .await
2765 .context(error::ExecuteDdlSnafu)?;
2766
2767 if let Err(err) = cache_invalidator
2768 .invalidate(
2769 &Context::default(),
2770 &[
2771 CacheIdent::TableId(dropped.table_id),
2772 CacheIdent::TableName(table_name),
2773 ],
2774 )
2775 .await
2776 {
2777 warn!(
2778 "Failed to invalidate cache after restoring table '{}' (id={}): {}",
2779 dropped.table_name, dropped.table_id, err
2780 );
2781 }
2782
2783 Ok(Output::new_with_affected_rows(0))
2784}
2785
2786#[cfg(test)]
2787mod test {
2788 #[cfg(feature = "enterprise")]
2789 use std::sync::{Arc, Mutex};
2790 use std::time::Duration;
2791
2792 #[cfg(feature = "enterprise")]
2793 use api::v1::meta::{ProcedureDetailResponse, ReconcileRequest, ReconcileResponse};
2794 #[cfg(feature = "enterprise")]
2795 use common_meta::cache_invalidator::{CacheInvalidator, CacheInvalidatorRef};
2796 #[cfg(feature = "enterprise")]
2797 use common_meta::instruction::CacheIdent;
2798 #[cfg(feature = "enterprise")]
2799 use common_meta::key::table_route::TableRouteValue;
2800 #[cfg(feature = "enterprise")]
2801 use common_meta::key::test_utils::new_test_table_info_with_name;
2802 #[cfg(feature = "enterprise")]
2803 use common_meta::key::{TableMetadataManager, TableMetadataManagerRef};
2804 #[cfg(feature = "enterprise")]
2805 use common_meta::kv_backend::memory::MemoryKvBackend;
2806 #[cfg(feature = "enterprise")]
2807 use common_meta::procedure_executor::{
2808 ExecutorContext, ProcedureExecutor, ProcedureExecutorRef,
2809 };
2810 #[cfg(feature = "enterprise")]
2811 use common_meta::rpc::ddl::{DdlTask, SubmitDdlTaskRequest, SubmitDdlTaskResponse};
2812 #[cfg(feature = "enterprise")]
2813 use common_meta::rpc::procedure::{
2814 MigrateRegionRequest, MigrateRegionResponse, ProcedureStateResponse,
2815 };
2816 use session::context::{QueryContext, QueryContextBuilder};
2817 use sql::dialect::GreptimeDbDialect;
2818 use sql::parser::{ParseOptions, ParserContext};
2819 use sql::statements::statement::Statement;
2820 use sqlparser::parser::Parser;
2821 use table::requests::REPARTITION_COLUMN_HINT_KEY;
2822
2823 use super::*;
2824 use crate::expr_helper;
2825
2826 #[cfg(feature = "enterprise")]
2827 #[derive(Default)]
2828 struct RecordingProcedureExecutor {
2829 requests: Mutex<Vec<SubmitDdlTaskRequest>>,
2830 contexts: Mutex<Vec<ExecutorContext>>,
2831 fail: bool,
2832 }
2833
2834 #[cfg(feature = "enterprise")]
2835 #[async_trait::async_trait]
2836 impl ProcedureExecutor for RecordingProcedureExecutor {
2837 async fn submit_ddl_task(
2838 &self,
2839 ctx: ExecutorContext,
2840 request: SubmitDdlTaskRequest,
2841 ) -> common_meta::error::Result<SubmitDdlTaskResponse> {
2842 self.contexts.lock().unwrap().push(ctx);
2843 self.requests.lock().unwrap().push(request);
2844 if self.fail {
2845 return common_meta::error::UnsupportedSnafu {
2846 operation: "test submit failure",
2847 }
2848 .fail();
2849 }
2850 Ok(SubmitDdlTaskResponse::default())
2851 }
2852
2853 async fn migrate_region(
2854 &self,
2855 _: &ExecutorContext,
2856 _: MigrateRegionRequest,
2857 ) -> common_meta::error::Result<MigrateRegionResponse> {
2858 unimplemented!()
2859 }
2860 async fn reconcile(
2861 &self,
2862 _: &ExecutorContext,
2863 _: ReconcileRequest,
2864 ) -> common_meta::error::Result<ReconcileResponse> {
2865 unimplemented!()
2866 }
2867 async fn query_procedure_state(
2868 &self,
2869 _: &ExecutorContext,
2870 _: &str,
2871 ) -> common_meta::error::Result<ProcedureStateResponse> {
2872 unimplemented!()
2873 }
2874 async fn list_procedures(
2875 &self,
2876 _: &ExecutorContext,
2877 ) -> common_meta::error::Result<ProcedureDetailResponse> {
2878 unimplemented!()
2879 }
2880 }
2881
2882 #[cfg(feature = "enterprise")]
2883 #[derive(Default)]
2884 struct RecordingCacheInvalidator {
2885 invalidations: Mutex<Vec<Vec<CacheIdent>>>,
2886 fail: bool,
2887 }
2888
2889 #[cfg(feature = "enterprise")]
2890 #[async_trait::async_trait]
2891 impl CacheInvalidator for RecordingCacheInvalidator {
2892 async fn invalidate(
2893 &self,
2894 _: &Context,
2895 caches: &[CacheIdent],
2896 ) -> common_meta::error::Result<()> {
2897 self.invalidations.lock().unwrap().push(caches.to_vec());
2898 if self.fail {
2899 return common_meta::error::UnsupportedSnafu {
2900 operation: "test cache failure",
2901 }
2902 .fail();
2903 }
2904 Ok(())
2905 }
2906
2907 fn invalidate_all(&self) -> common_meta::error::Result<()> {
2908 Ok(())
2909 }
2910 }
2911
2912 #[cfg(feature = "enterprise")]
2913 async fn dropped_table_manager(table_id: TableId, name: &TableName) -> TableMetadataManagerRef {
2914 let backend = Arc::new(MemoryKvBackend::default());
2915 let manager = Arc::new(TableMetadataManager::new(backend));
2916 let mut info = new_test_table_info_with_name(table_id, &name.table_name);
2917 info.catalog_name = name.catalog_name.clone();
2918 info.schema_name = name.schema_name.clone();
2919 let route = TableRouteValue::physical(vec![]);
2920 manager
2921 .create_table_metadata(info, route.clone(), HashMap::new())
2922 .await
2923 .unwrap();
2924 manager
2925 .delete_table_metadata(table_id, name, &route, &HashMap::new(), None)
2926 .await
2927 .unwrap();
2928 manager
2929 }
2930
2931 #[cfg(feature = "enterprise")]
2932 #[tokio::test]
2933 async fn test_undrop_table_execution_path() {
2934 let name = TableName::new("greptime", "public", "metrics");
2935 let manager = dropped_table_manager(42, &name).await;
2936 let mut live = new_test_table_info_with_name(99, "metrics");
2938 live.catalog_name = "greptime".to_string();
2939 live.schema_name = "public".to_string();
2940 manager
2941 .create_table_metadata(live, TableRouteValue::physical(vec![]), HashMap::new())
2942 .await
2943 .unwrap();
2944 let procedure = Arc::new(RecordingProcedureExecutor::default());
2945 let cache = Arc::new(RecordingCacheInvalidator::default());
2946
2947 execute_undrop_table(
2948 &manager,
2949 &(procedure.clone() as ProcedureExecutorRef),
2950 &(cache.clone() as CacheInvalidatorRef),
2951 name.clone(),
2952 QueryContext::arc(),
2953 )
2954 .await
2955 .unwrap();
2956
2957 let requests = procedure.requests.lock().unwrap();
2958 assert!(matches!(&requests[0].task, DdlTask::UndropTable(task) if task.table_id == 42));
2959 let contexts = procedure.contexts.lock().unwrap();
2960 assert_eq!(
2961 contexts[0].event_input.as_ref().map(|input| input.reason),
2962 Some(TriggerReason::Manual)
2963 );
2964 assert_eq!(
2965 cache.invalidations.lock().unwrap()[0],
2966 vec![CacheIdent::TableId(42), CacheIdent::TableName(name)]
2967 );
2968 }
2969
2970 #[cfg(feature = "enterprise")]
2971 #[tokio::test]
2972 async fn test_undrop_table_missing_tombstone_submits_nothing() {
2973 let manager = Arc::new(TableMetadataManager::new(Arc::new(
2974 MemoryKvBackend::default(),
2975 )));
2976 let procedure = Arc::new(RecordingProcedureExecutor::default());
2977 let cache = Arc::new(RecordingCacheInvalidator::default());
2978 let err = execute_undrop_table(
2979 &manager,
2980 &(procedure.clone() as ProcedureExecutorRef),
2981 &(cache as CacheInvalidatorRef),
2982 TableName::new("greptime", "public", "missing"),
2983 QueryContext::arc(),
2984 )
2985 .await
2986 .unwrap_err();
2987 assert!(matches!(err, crate::error::Error::TableNotFound { .. }));
2988 assert!(procedure.requests.lock().unwrap().is_empty());
2989 }
2990
2991 #[cfg(feature = "enterprise")]
2992 #[tokio::test]
2993 async fn test_undrop_table_submit_failure_does_not_invalidate() {
2994 let name = TableName::new("greptime", "public", "metrics");
2995 let manager = dropped_table_manager(42, &name).await;
2996 let procedure = Arc::new(RecordingProcedureExecutor {
2997 fail: true,
2998 ..Default::default()
2999 });
3000 let cache = Arc::new(RecordingCacheInvalidator::default());
3001 execute_undrop_table(
3002 &manager,
3003 &(procedure as ProcedureExecutorRef),
3004 &(cache.clone() as CacheInvalidatorRef),
3005 name,
3006 QueryContext::arc(),
3007 )
3008 .await
3009 .unwrap_err();
3010 assert!(cache.invalidations.lock().unwrap().is_empty());
3011 }
3012
3013 #[cfg(feature = "enterprise")]
3014 #[tokio::test]
3015 async fn test_undrop_table_cache_failure_returns_success_after_submit() {
3016 let name = TableName::new("greptime", "public", "metrics");
3017 let manager = dropped_table_manager(42, &name).await;
3018 let procedure = Arc::new(RecordingProcedureExecutor::default());
3019 let cache = Arc::new(RecordingCacheInvalidator {
3020 fail: true,
3021 ..Default::default()
3022 });
3023 execute_undrop_table(
3024 &manager,
3025 &(procedure.clone() as ProcedureExecutorRef),
3026 &(cache.clone() as CacheInvalidatorRef),
3027 name.clone(),
3028 QueryContext::arc(),
3029 )
3030 .await
3031 .unwrap();
3032 assert_eq!(procedure.requests.lock().unwrap().len(), 1);
3033 assert_eq!(
3034 cache.invalidations.lock().unwrap()[0],
3035 vec![CacheIdent::TableId(42), CacheIdent::TableName(name)]
3036 );
3037 }
3038
3039 #[test]
3040 fn test_parse_ddl_options() {
3041 let options = OptionMap::from([
3042 ("timeout".to_string(), "5m".to_string()),
3043 ("wait".to_string(), "false".to_string()),
3044 ]);
3045 let ddl_options = parse_ddl_options(&options).unwrap();
3046 assert!(!ddl_options.wait);
3047 assert_eq!(Duration::from_secs(300), ddl_options.timeout);
3048 }
3049
3050 #[test]
3051 fn test_validate_and_normalize_annotations() {
3052 let schema = Schema::new(vec![
3053 ColumnSchema::new("service_name", ConcreteDataType::string_datatype(), true),
3054 ColumnSchema::new("host_id", ConcreteDataType::string_datatype(), true),
3055 ColumnSchema::new("value", ConcreteDataType::float64_datatype(), true),
3056 ColumnSchema::new("payload", ConcreteDataType::binary_datatype(), true),
3057 ]);
3058 let opts = |pairs: &[(&str, &str)]| TableOptions {
3059 extra_options: pairs
3060 .iter()
3061 .map(|(k, v)| (k.to_string(), v.to_string()))
3062 .collect(),
3063 ..Default::default()
3064 };
3065 let check = |pairs: &[(&str, &str)]| {
3066 let mut options = opts(pairs);
3067 validate_and_normalize_annotations(&mut options, &schema, &[]).map(|()| options)
3068 };
3069
3070 for pairs in [
3072 &[(
3073 "greptime.semantic.entity.process.id",
3074 "service_name,host_id",
3075 )][..],
3076 &[("greptime.semantic.entity.service.id", "value")][..],
3077 ] {
3078 assert!(check(pairs).is_ok());
3079 }
3080
3081 let missing = check(&[("greptime.semantic.entity.service.id", "nope")]).unwrap_err();
3083 assert!(matches!(missing, error::Error::ColumnNotFound { .. }));
3084 assert_eq!(
3085 common_error::status_code::StatusCode::InvalidArguments,
3086 common_error::ext::ErrorExt::status_code(&missing)
3087 );
3088
3089 let binary_id = check(&[("greptime.semantic.entity.service.id", "payload")]).unwrap_err();
3090 assert!(matches!(binary_id, error::Error::InvalidSql { .. }));
3091
3092 let bad_value = check(&[("greptime.semantic.signal_type", "garbage")]).unwrap_err();
3095 assert!(matches!(bad_value, error::Error::InvalidSql { .. }));
3096
3097 let unknown_key = check(&[("greptime.semantic.nonsense", "x")]).unwrap_err();
3098 assert!(matches!(unknown_key, error::Error::InvalidSql { .. }));
3099 }
3100
3101 #[test]
3102 fn test_validate_and_normalize_flow_options_empty() {
3103 assert!(
3104 validate_and_normalize_flow_options(HashMap::new(), None)
3105 .unwrap()
3106 .is_empty()
3107 );
3108 }
3109
3110 #[test]
3111 fn test_validate_and_normalize_flow_options_valid() {
3112 let options = HashMap::from([
3113 (DEFER_ON_MISSING_SOURCE_KEY.to_string(), "TRUE".to_string()),
3114 (
3115 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY.to_string(),
3116 "FALSE".to_string(),
3117 ),
3118 ]);
3119
3120 assert_eq!(
3121 validate_and_normalize_flow_options(options, None).unwrap(),
3122 HashMap::from([
3123 (DEFER_ON_MISSING_SOURCE_KEY.to_string(), "true".to_string(),),
3124 (
3125 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY.to_string(),
3126 "false".to_string(),
3127 )
3128 ])
3129 );
3130 }
3131
3132 #[test]
3133 fn test_validate_and_normalize_flow_options_unknown_option() {
3134 let err = validate_and_normalize_flow_options(
3135 HashMap::from([("foo".to_string(), "bar".to_string())]),
3136 None,
3137 )
3138 .unwrap_err();
3139
3140 assert!(
3141 err.to_string()
3142 .contains("unknown flow option 'foo', supported options:")
3143 );
3144 }
3145
3146 #[test]
3147 fn test_validate_and_normalize_flow_options_reserved_option() {
3148 let err = validate_and_normalize_flow_options(
3149 HashMap::from([(
3150 FlowType::FLOW_TYPE_KEY.to_string(),
3151 FlowType::BATCHING.to_string(),
3152 )]),
3153 None,
3154 )
3155 .unwrap_err();
3156
3157 assert!(
3158 err.to_string()
3159 .contains("flow option 'flow_type' is reserved for internal use")
3160 );
3161 }
3162
3163 #[test]
3164 fn test_validate_and_normalize_flow_options_invalid_bool() {
3165 let err = validate_and_normalize_flow_options(
3166 HashMap::from([(
3167 DEFER_ON_MISSING_SOURCE_KEY.to_string(),
3168 "not-a-bool".to_string(),
3169 )]),
3170 None,
3171 )
3172 .unwrap_err();
3173
3174 assert!(
3175 err.to_string()
3176 .contains("invalid flow option 'defer_on_missing_source': 'not-a-bool'")
3177 );
3178 }
3179
3180 #[test]
3181 fn test_validate_and_normalize_flow_options_rejects_redacted_invalid_input() {
3182 let sql = r"
3183CREATE FLOW task_6
3184SINK TO schema_1.table_1
3185WITH (access_key_id = [true])
3186AS
3187SELECT max(c1), min(c2) FROM schema_2.table_2;";
3188 let stmt =
3189 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3190 .unwrap()
3191 .pop()
3192 .unwrap();
3193
3194 let Statement::CreateFlow(create_flow) = stmt else {
3195 unreachable!()
3196 };
3197 let expr =
3198 expr_helper::to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
3199 let err = validate_and_normalize_flow_options(expr.flow_options, None).unwrap_err();
3200
3201 assert!(
3202 err.to_string()
3203 .contains("unknown flow option 'access_key_id'")
3204 );
3205 }
3206
3207 #[test]
3210 fn test_eval_interval_rejected_non_positive() {
3211 let err = validate_and_normalize_flow_options(HashMap::new(), Some(0)).unwrap_err();
3213 assert!(err.to_string().contains("EVAL INTERVAL must be positive"));
3214
3215 let err = validate_and_normalize_flow_options(HashMap::new(), Some(-5)).unwrap_err();
3217 assert!(err.to_string().contains("EVAL INTERVAL must be positive"));
3218
3219 let result = validate_and_normalize_flow_options(HashMap::new(), Some(300));
3221 assert!(result.is_ok());
3222 }
3223
3224 #[test]
3225 fn test_schedule_and_internal_keys_rejected_as_unknown_options() {
3226 for key in [
3227 "eval_interval_anchor",
3228 "eval_interval_start",
3229 "eval_interval_missed_tick_policy",
3230 "eval_interval_catchup_max_runs",
3231 "eval_interval_catchup_max_lag",
3232 "__greptime_internal_eval_schedule",
3233 ] {
3234 let err = validate_and_normalize_flow_options(
3235 HashMap::from([(key.to_string(), "value".to_string())]),
3236 Some(300),
3237 )
3238 .unwrap_err();
3239
3240 assert!(
3241 err.to_string()
3242 .contains(&format!("unknown flow option '{key}'")),
3243 "unexpected error for {key}: {err}"
3244 );
3245 }
3246 }
3247
3248 #[test]
3249 fn test_determine_flow_type_for_source_state_missing_sources_require_opt_in() {
3250 let err = determine_flow_type_for_source_state("my_flow", &HashMap::new(), true, false)
3251 .unwrap_err();
3252
3253 assert!(err.to_string().contains(
3254 "missing source tables for flow 'my_flow'; use WITH (defer_on_missing_source = true) to create a pending flow"
3255 ));
3256 }
3257
3258 #[test]
3259 fn test_determine_flow_type_for_source_state_missing_sources_prefer_batching() {
3260 let flow_options =
3261 HashMap::from([(DEFER_ON_MISSING_SOURCE_KEY.to_string(), "true".to_string())]);
3262
3263 assert_eq!(
3264 determine_flow_type_for_source_state("my_flow", &flow_options, true, true).unwrap(),
3265 Some(FlowType::Batching)
3266 );
3267 }
3268
3269 #[test]
3270 fn test_determine_flow_type_for_source_state_instant_ttl_without_missing_sources() {
3271 assert_eq!(
3272 determine_flow_type_for_source_state("my_flow", &HashMap::new(), false, true).unwrap(),
3273 Some(FlowType::Streaming)
3274 );
3275 }
3276
3277 #[test]
3278 fn test_name_is_match() {
3279 assert!(!NAME_PATTERN_REG.is_match("/adaf"));
3280 assert!(!NAME_PATTERN_REG.is_match("🈲"));
3281 assert!(NAME_PATTERN_REG.is_match("hello"));
3282 assert!(NAME_PATTERN_REG.is_match("test@"));
3283 assert!(!NAME_PATTERN_REG.is_match("@test"));
3284 assert!(NAME_PATTERN_REG.is_match("test#"));
3285 assert!(!NAME_PATTERN_REG.is_match("#test"));
3286 assert!(!NAME_PATTERN_REG.is_match("@"));
3287 assert!(!NAME_PATTERN_REG.is_match("#"));
3288 }
3289
3290 #[test]
3291 fn test_partition_expr_equivalence_with_swapped_operands() {
3292 let column_name = "device_id".to_string();
3293 let column_name_and_type =
3294 HashMap::from([(&column_name, ConcreteDataType::int32_datatype())]);
3295 let timezone = Timezone::from_tz_string("UTC").unwrap();
3296 let dialect = GreptimeDbDialect {};
3297
3298 let mut parser = Parser::new(&dialect)
3299 .try_with_sql("device_id < 100")
3300 .unwrap();
3301 let expr_left = parser.parse_expr().unwrap();
3302
3303 let mut parser = Parser::new(&dialect)
3304 .try_with_sql("100 > device_id")
3305 .unwrap();
3306 let expr_right = parser.parse_expr().unwrap();
3307
3308 let partition_left =
3309 convert_one_expr(&expr_left, &column_name_and_type, &timezone).unwrap();
3310 let partition_right =
3311 convert_one_expr(&expr_right, &column_name_and_type, &timezone).unwrap();
3312
3313 assert_eq!(partition_left, partition_right);
3314 assert!([partition_left.clone()].contains(&partition_right));
3315
3316 let mut physical_partition_exprs = vec![partition_left];
3317 let mut logical_partition_exprs = vec![partition_right];
3318 physical_partition_exprs.sort_unstable();
3319 logical_partition_exprs.sort_unstable();
3320 assert_eq!(physical_partition_exprs, logical_partition_exprs);
3321 }
3322
3323 #[test]
3324 fn test_repartition_target_partition_columns_are_overwrite_context() {
3325 let device_id = ColumnSchema::new("device_id", ConcreteDataType::int32_datatype(), true);
3326 let area = ColumnSchema::new("area", ConcreteDataType::string_datatype(), true);
3327 let existing_partition_columns = vec![&device_id];
3328 let target_partition_columns = vec![&device_id, &area];
3329 let existing_column_name_and_type = column_name_and_type(&existing_partition_columns);
3330 let target_column_name_and_type = column_name_and_type(&target_partition_columns);
3331 let timezone = Timezone::from_tz_string("UTC").unwrap();
3332 let dialect = GreptimeDbDialect {};
3333
3334 let mut parser = Parser::new(&dialect)
3335 .try_with_sql("device_id < 100 AND area < 'South'")
3336 .unwrap();
3337 let expr = parser.parse_expr().unwrap();
3338
3339 let err = convert_one_expr(&expr, &existing_column_name_and_type, &timezone).unwrap_err();
3340 assert!(err.to_string().contains("area"));
3341
3342 let partition_expr = convert_one_expr(&expr, &target_column_name_and_type, &timezone)
3343 .expect("target columns should overwrite the conversion context");
3344 let partition_expr = partition_expr.to_string();
3345 assert!(partition_expr.contains("device_id"));
3346 assert!(partition_expr.contains("area"));
3347 assert!(partition_expr.contains("South"));
3348 }
3349
3350 #[test]
3351 fn test_repartition_rejects_remaining_expr_outside_target_columns() {
3352 let device_id = "device_id".to_string();
3353 let area = "area".to_string();
3354 let timezone = Timezone::from_tz_string("UTC").unwrap();
3355 let column_name_and_type = HashMap::from([
3356 (&device_id, ConcreteDataType::int32_datatype()),
3357 (&area, ConcreteDataType::string_datatype()),
3358 ]);
3359 let dialect = GreptimeDbDialect {};
3360 let mut parser = Parser::new(&dialect)
3361 .try_with_sql("device_id >= 100")
3362 .unwrap();
3363 let remaining_old_expr = convert_one_expr(
3364 &parser.parse_expr().unwrap(),
3365 &column_name_and_type,
3366 &timezone,
3367 )
3368 .unwrap();
3369 let target_partition_columns = HashSet::from([&area]);
3370
3371 let err = ensure_partition_expr_columns_in_target(
3372 &[remaining_old_expr],
3373 &target_partition_columns,
3374 )
3375 .unwrap_err();
3376
3377 assert!(err.to_string().contains("device_id"));
3378 assert!(err.to_string().contains("target partition columns"));
3379 }
3380
3381 #[test]
3382 fn test_repartition_rejects_duplicate_target_partition_columns() {
3383 let device_id = ColumnSchema::new("device_id", ConcreteDataType::int32_datatype(), true);
3384 let column_schemas = vec![device_id];
3385 let target_partition_columns = vec!["device_id".to_string(), "device_id".to_string()];
3386
3387 let err =
3388 validate_and_collect_partition_columns(&target_partition_columns, &column_schemas)
3389 .unwrap_err();
3390
3391 assert!(err.to_string().contains("duplicate partition column"));
3392 assert!(err.to_string().contains("device_id"));
3393 }
3394
3395 fn create_expr_from_sql(sql: &str) -> CreateTableExpr {
3396 let result =
3397 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3398 .unwrap();
3399
3400 match &result[0] {
3401 Statement::CreateTable(create) => {
3402 expr_helper::create_to_expr(create, &QueryContext::arc()).unwrap()
3403 }
3404 _ => unreachable!(),
3405 }
3406 }
3407
3408 #[test]
3409 fn test_create_table_with_repartition_column_hint() {
3410 let expr = create_expr_from_sql(
3411 r"
3412CREATE TABLE metrics (
3413 host STRING,
3414 ts TIMESTAMP TIME INDEX,
3415 cpu DOUBLE,
3416 PRIMARY KEY(host)
3417)
3418WITH ('repartition.column.hint' = ' host ')",
3419 );
3420
3421 let table_info = create_table_info(&expr, vec![]).unwrap();
3422 assert_eq!(
3423 table_info
3424 .meta
3425 .options
3426 .extra_options
3427 .get(REPARTITION_COLUMN_HINT_KEY),
3428 Some(&"host".to_string())
3429 );
3430 }
3431
3432 #[test]
3433 fn test_create_table_info_rejects_non_timestamp_time_index() {
3434 let expr = CreateTableExpr {
3435 catalog_name: "greptime".to_string(),
3436 schema_name: "public".to_string(),
3437 table_name: "demo".to_string(),
3438 desc: String::new(),
3439 column_defs: vec![api::v1::ColumnDef {
3440 name: "host".to_string(),
3441 data_type: api::v1::ColumnDataType::String as i32,
3442 is_nullable: true,
3443 default_constraint: vec![],
3444 semantic_type: 0,
3445 comment: String::new(),
3446 datatype_extension: None,
3447 options: None,
3448 }],
3449 time_index: "host".to_string(),
3450 primary_keys: vec![],
3451 create_if_not_exists: false,
3452 table_options: HashMap::new(),
3453 table_id: None,
3454 engine: "mito".to_string(),
3455 };
3456
3457 let err = create_table_info(&expr, vec![]).unwrap_err();
3458 assert_eq!(
3459 common_error::ext::ErrorExt::status_code(&err),
3460 common_error::status_code::StatusCode::InvalidArguments
3461 );
3462 }
3463
3464 #[test]
3465 fn test_verify_alter_guards_entity_column_types() {
3466 let expr = create_expr_from_sql(
3467 "CREATE TABLE t (svc STRING, ts TIMESTAMP TIME INDEX) \
3468 WITH ('greptime.semantic.entity.service.id'='svc');",
3469 );
3470 let table_info = Arc::new(create_table_info(&expr, vec![]).unwrap());
3471 let alter = |kind| AlterTableExpr {
3472 catalog_name: "greptime".to_string(),
3473 schema_name: "public".to_string(),
3474 table_name: "t".to_string(),
3475 kind: Some(kind),
3476 };
3477
3478 let modify = alter(Kind::ModifyColumnTypes(api::v1::ModifyColumnTypes {
3479 modify_column_types: vec![api::v1::ModifyColumnType {
3480 column_name: "svc".to_string(),
3481 target_type: api::v1::ColumnDataType::Binary as i32,
3482 target_type_extension: None,
3483 }],
3484 }));
3485 let err = verify_alter(1, table_info.clone(), modify).unwrap_err();
3486 let msg = common_error::ext::ErrorExt::output_msg(&err);
3487 assert!(
3488 msg.contains("must keep a type that renders as a string"),
3489 "{msg}"
3490 );
3491
3492 let drop = alter(Kind::DropColumns(api::v1::DropColumns {
3495 drop_columns: vec![api::v1::DropColumn {
3496 name: "svc".to_string(),
3497 }],
3498 }));
3499 assert!(verify_alter(1, table_info, drop).unwrap());
3500 }
3501
3502 #[test]
3503 fn test_json2_requires_append_mode() {
3504 let cases = [
3505 "CREATE TABLE monitor (payload JSON2, ts TIMESTAMP TIME INDEX);",
3506 "CREATE TABLE monitor (payload JSON2, ts TIMESTAMP TIME INDEX) WITH (append_mode='false');",
3507 ];
3508
3509 for sql in cases {
3510 let expr = create_expr_from_sql(sql);
3511 let err = create_table_info(&expr, vec![]).unwrap_err();
3512 assert!(
3513 err.to_string()
3514 .contains("JSON2 column `payload` requires append_mode='true'"),
3515 "{err}"
3516 );
3517 }
3518
3519 let expr = create_expr_from_sql(
3520 "CREATE TABLE monitor (payload JSON2, ts TIMESTAMP TIME INDEX) WITH (append_mode='true');",
3521 );
3522 create_table_info(&expr, vec![]).unwrap();
3523 }
3524
3525 #[test]
3526 fn test_create_table_with_empty_repartition_column_hint() {
3527 let expr = create_expr_from_sql(
3528 r"
3529CREATE TABLE metrics (
3530 host STRING,
3531 ts TIMESTAMP TIME INDEX,
3532 cpu DOUBLE,
3533 PRIMARY KEY(host)
3534)
3535WITH ('repartition.column.hint' = '')",
3536 );
3537
3538 let err = create_table_info(&expr, vec![]).unwrap_err();
3539 assert!(
3540 err.to_string()
3541 .contains("repartition.column.hint expects exactly one column name")
3542 );
3543 }
3544
3545 #[test]
3546 fn test_create_table_with_multiple_repartition_column_hints() {
3547 let expr = create_expr_from_sql(
3548 r"
3549CREATE TABLE metrics (
3550 host STRING,
3551 region_id STRING,
3552 ts TIMESTAMP TIME INDEX,
3553 cpu DOUBLE,
3554 PRIMARY KEY(host)
3555)
3556WITH ('repartition.column.hint' = 'host,region_id')",
3557 );
3558
3559 let err = create_table_info(&expr, vec![]).unwrap_err();
3560 assert!(
3561 err.to_string()
3562 .contains("repartition.column.hint expects exactly one column name")
3563 );
3564 }
3565
3566 #[test]
3567 fn test_create_table_with_missing_repartition_column_hint() {
3568 let expr = create_expr_from_sql(
3569 r"
3570CREATE TABLE metrics (
3571 host STRING,
3572 ts TIMESTAMP TIME INDEX,
3573 cpu DOUBLE,
3574 PRIMARY KEY(host)
3575)
3576WITH ('repartition.column.hint' = 'region_id')",
3577 );
3578
3579 let err = create_table_info(&expr, vec![]).unwrap_err();
3580 assert!(
3581 err.to_string()
3582 .contains("Cannot find column by name: region")
3583 );
3584 }
3585
3586 #[test]
3587 fn test_create_table_with_time_index_repartition_column_hint() {
3588 let expr = create_expr_from_sql(
3589 r"
3590CREATE TABLE metrics (
3591 host STRING,
3592 ts TIMESTAMP TIME INDEX,
3593 cpu DOUBLE,
3594 PRIMARY KEY(host)
3595)
3596WITH ('repartition.column.hint' = 'ts')",
3597 );
3598
3599 let err = create_table_info(&expr, vec![]).unwrap_err();
3600 assert!(
3601 err.to_string()
3602 .contains("cannot set repartition.column.hint to the time index column")
3603 );
3604 }
3605
3606 #[test]
3607 fn test_create_partitioned_table_with_repartition_column_hint() {
3608 let expr = create_expr_from_sql(
3609 r"
3610CREATE TABLE metrics (
3611 host STRING,
3612 ts TIMESTAMP TIME INDEX,
3613 cpu DOUBLE,
3614 PRIMARY KEY(host)
3615)
3616WITH ('repartition.column.hint' = 'host')",
3617 );
3618
3619 let err = create_table_info(&expr, vec!["host".to_string()]).unwrap_err();
3620 assert!(
3621 err.to_string()
3622 .contains("cannot set repartition.column.hint on a table with partition metadata")
3623 );
3624 }
3625
3626 #[tokio::test]
3627 #[ignore = "TODO(ruihang): WIP new partition rule"]
3628 async fn test_parse_partitions() {
3629 common_telemetry::init_default_ut_logging();
3630 let cases = [
3631 (
3632 r"
3633CREATE TABLE rcx ( a INT, b STRING, c TIMESTAMP, TIME INDEX (c) )
3634PARTITION ON COLUMNS (b) (
3635 b < 'hz',
3636 b >= 'hz' AND b < 'sh',
3637 b >= 'sh'
3638)
3639ENGINE=mito",
3640 r#"[{"column_list":["b"],"value_list":["{\"Value\":{\"String\":\"hz\"}}"]},{"column_list":["b"],"value_list":["{\"Value\":{\"String\":\"sh\"}}"]},{"column_list":["b"],"value_list":["\"MaxValue\""]}]"#,
3641 ),
3642 (
3643 r"
3644CREATE TABLE rcx ( a INT, b STRING, c TIMESTAMP, TIME INDEX (c) )
3645PARTITION BY RANGE COLUMNS (b, a) (
3646 PARTITION r0 VALUES LESS THAN ('hz', 10),
3647 b < 'hz' AND a < 10,
3648 b >= 'hz' AND b < 'sh' AND a >= 10 AND a < 20,
3649 b >= 'sh' AND a >= 20
3650)
3651ENGINE=mito",
3652 r#"[{"column_list":["b","a"],"value_list":["{\"Value\":{\"String\":\"hz\"}}","{\"Value\":{\"Int32\":10}}"]},{"column_list":["b","a"],"value_list":["{\"Value\":{\"String\":\"sh\"}}","{\"Value\":{\"Int32\":20}}"]},{"column_list":["b","a"],"value_list":["\"MaxValue\"","\"MaxValue\""]}]"#,
3653 ),
3654 ];
3655 let ctx = QueryContextBuilder::default().build().into();
3656 for (sql, expected) in cases {
3657 let result = ParserContext::create_with_dialect(
3658 sql,
3659 &GreptimeDbDialect {},
3660 ParseOptions::default(),
3661 )
3662 .unwrap();
3663 match &result[0] {
3664 Statement::CreateTable(c) => {
3665 let expr = expr_helper::create_to_expr(c, &QueryContext::arc()).unwrap();
3666 let (partitions, _) =
3667 parse_partitions(&expr, c.partitions.clone(), &ctx).unwrap();
3668 let json = serde_json::to_string(&partitions).unwrap();
3669 assert_eq!(json, expected);
3670 }
3671 _ => unreachable!(),
3672 }
3673 }
3674 }
3675}