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, COMMENT_KEY, DDL_TIMEOUT, DDL_WAIT, REPARTITION_COLUMN_HINT_KEY,
102 TableOptions, parse_entity_columns, parse_entity_option_key,
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 req = SubmitDdlTaskRequest::new(DdlTask::new_alter_logical_tables(vec![expr]));
1886
1887 let mut invalidate_keys = vec![
1888 CacheIdent::TableId(physical_table_id),
1889 CacheIdent::TableId(table_id),
1890 CacheIdent::TableName(TableName::new(catalog_name, schema_name, table_name)),
1891 ];
1892
1893 let physical_table = self
1894 .table_metadata_manager
1895 .table_info_manager()
1896 .get(physical_table_id)
1897 .await
1898 .context(TableMetadataManagerSnafu)?
1899 .map(|x| x.into_inner());
1900 if let Some(physical_table) = physical_table {
1901 let physical_table_name = TableName::new(
1902 physical_table.table_info.catalog_name,
1903 physical_table.table_info.schema_name,
1904 physical_table.table_info.name,
1905 );
1906 invalidate_keys.push(CacheIdent::TableName(physical_table_name));
1907 }
1908
1909 (req, invalidate_keys)
1910 };
1911
1912 self.procedure_executor
1913 .submit_ddl_task(executor_context, req)
1914 .await
1915 .context(error::ExecuteDdlSnafu)?;
1916
1917 self.cache_invalidator
1919 .invalidate(&Context::default(), &invalidate_keys)
1920 .await
1921 .context(error::InvalidateTableCacheSnafu)?;
1922
1923 Ok(Output::new_with_affected_rows(0))
1924 }
1925
1926 #[cfg(feature = "enterprise")]
1927 #[tracing::instrument(skip_all)]
1928 pub async fn alter_trigger(
1929 &self,
1930 _alter_expr: AlterTrigger,
1931 _query_context: QueryContextRef,
1932 ) -> Result<Output> {
1933 crate::error::NotSupportedSnafu {
1934 feat: "alter trigger",
1935 }
1936 .fail()
1937 }
1938
1939 #[tracing::instrument(skip_all)]
1940 pub async fn alter_database(
1941 &self,
1942 alter_expr: AlterDatabase,
1943 query_context: QueryContextRef,
1944 ) -> Result<Output> {
1945 let alter_expr = expr_helper::to_alter_database_expr(alter_expr, &query_context)?;
1946 self.alter_database_inner(alter_expr, query_context).await
1947 }
1948
1949 #[tracing::instrument(skip_all)]
1950 pub async fn alter_database_inner(
1951 &self,
1952 alter_expr: AlterDatabaseExpr,
1953 query_context: QueryContextRef,
1954 ) -> Result<Output> {
1955 ensure!(
1956 !is_readonly_schema(&alter_expr.schema_name),
1957 SchemaReadOnlySnafu {
1958 name: query_context.current_schema().clone()
1959 }
1960 );
1961
1962 let exists = self
1963 .catalog_manager
1964 .schema_exists(&alter_expr.catalog_name, &alter_expr.schema_name, None)
1965 .await
1966 .context(CatalogSnafu)?;
1967 ensure!(
1968 exists,
1969 SchemaNotFoundSnafu {
1970 schema_info: alter_expr.schema_name,
1971 }
1972 );
1973
1974 let cache_ident = [CacheIdent::SchemaName(SchemaName {
1975 catalog_name: alter_expr.catalog_name.clone(),
1976 schema_name: alter_expr.schema_name.clone(),
1977 })];
1978
1979 self.alter_database_procedure(alter_expr, query_context)
1980 .await?;
1981
1982 self.cache_invalidator
1984 .invalidate(&Context::default(), &cache_ident)
1985 .await
1986 .context(error::InvalidateTableCacheSnafu)?;
1987
1988 Ok(Output::new_with_affected_rows(0))
1989 }
1990
1991 async fn create_table_procedure(
1992 &self,
1993 create_table: CreateTableExpr,
1994 partitions: Vec<PartitionExpr>,
1995 table_info: TableInfo,
1996 query_context: QueryContextRef,
1997 trigger_reason: TriggerReason,
1998 ) -> Result<SubmitDdlTaskResponse> {
1999 let partitions = partitions
2000 .into_iter()
2001 .map(|expr| expr.as_pb_partition().context(PartitionExprToPbSnafu))
2002 .collect::<Result<Vec<_>>>()?;
2003
2004 let executor_context = to_executor_context_with_origin_frontend(
2005 query_context,
2006 &self.origin_frontend_addr,
2007 trigger_reason,
2008 );
2009 let request = SubmitDdlTaskRequest::new(DdlTask::new_create_table(
2010 create_table,
2011 partitions,
2012 table_info,
2013 ));
2014
2015 self.procedure_executor
2016 .submit_ddl_task(executor_context, request)
2017 .await
2018 .context(error::ExecuteDdlSnafu)
2019 }
2020
2021 async fn create_logical_tables_procedure(
2022 &self,
2023 tables_data: Vec<(CreateTableExpr, TableInfo)>,
2024 query_context: QueryContextRef,
2025 trigger_reason: TriggerReason,
2026 ) -> Result<SubmitDdlTaskResponse> {
2027 let executor_context = to_executor_context_with_origin_frontend(
2028 query_context,
2029 &self.origin_frontend_addr,
2030 trigger_reason,
2031 );
2032 let request = SubmitDdlTaskRequest::new(DdlTask::new_create_logical_tables(tables_data));
2033
2034 self.procedure_executor
2035 .submit_ddl_task(executor_context, request)
2036 .await
2037 .context(error::ExecuteDdlSnafu)
2038 }
2039
2040 async fn alter_logical_tables_procedure(
2041 &self,
2042 tables_data: Vec<AlterTableExpr>,
2043 query_context: QueryContextRef,
2044 trigger_reason: TriggerReason,
2045 ) -> Result<SubmitDdlTaskResponse> {
2046 let executor_context = to_executor_context(query_context, trigger_reason);
2047 let request = SubmitDdlTaskRequest::new(DdlTask::new_alter_logical_tables(tables_data));
2048
2049 self.procedure_executor
2050 .submit_ddl_task(executor_context, request)
2051 .await
2052 .context(error::ExecuteDdlSnafu)
2053 }
2054
2055 async fn drop_table_procedure(
2056 &self,
2057 table_name: &TableName,
2058 table_id: TableId,
2059 drop_if_exists: bool,
2060 query_context: QueryContextRef,
2061 ) -> Result<SubmitDdlTaskResponse> {
2062 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2063 let request = SubmitDdlTaskRequest::new(DdlTask::new_drop_table(
2064 table_name.catalog_name.clone(),
2065 table_name.schema_name.clone(),
2066 table_name.table_name.clone(),
2067 table_id,
2068 drop_if_exists,
2069 ));
2070
2071 self.procedure_executor
2072 .submit_ddl_task(executor_context, request)
2073 .await
2074 .context(error::ExecuteDdlSnafu)
2075 }
2076
2077 async fn drop_database_procedure(
2078 &self,
2079 catalog: String,
2080 schema: String,
2081 drop_if_exists: bool,
2082 query_context: QueryContextRef,
2083 ) -> Result<SubmitDdlTaskResponse> {
2084 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2085 let request =
2086 SubmitDdlTaskRequest::new(DdlTask::new_drop_database(catalog, schema, drop_if_exists));
2087
2088 self.procedure_executor
2089 .submit_ddl_task(executor_context, request)
2090 .await
2091 .context(error::ExecuteDdlSnafu)
2092 }
2093
2094 async fn alter_database_procedure(
2095 &self,
2096 alter_expr: AlterDatabaseExpr,
2097 query_context: QueryContextRef,
2098 ) -> Result<SubmitDdlTaskResponse> {
2099 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2100 let request = SubmitDdlTaskRequest::new(DdlTask::new_alter_database(alter_expr));
2101
2102 self.procedure_executor
2103 .submit_ddl_task(executor_context, request)
2104 .await
2105 .context(error::ExecuteDdlSnafu)
2106 }
2107
2108 async fn truncate_table_procedure(
2109 &self,
2110 table_name: &TableName,
2111 table_id: TableId,
2112 time_ranges: Vec<(Timestamp, Timestamp)>,
2113 query_context: QueryContextRef,
2114 ) -> Result<SubmitDdlTaskResponse> {
2115 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2116 let request = SubmitDdlTaskRequest::new(DdlTask::new_truncate_table(
2117 table_name.catalog_name.clone(),
2118 table_name.schema_name.clone(),
2119 table_name.table_name.clone(),
2120 table_id,
2121 time_ranges,
2122 ));
2123
2124 self.procedure_executor
2125 .submit_ddl_task(executor_context, request)
2126 .await
2127 .context(error::ExecuteDdlSnafu)
2128 }
2129
2130 #[tracing::instrument(skip_all)]
2131 pub async fn create_database(
2132 &self,
2133 database: &str,
2134 create_if_not_exists: bool,
2135 options: HashMap<String, String>,
2136 query_context: QueryContextRef,
2137 ) -> Result<Output> {
2138 let catalog = query_context.current_catalog();
2139 ensure!(
2140 NAME_PATTERN_REG.is_match(catalog),
2141 error::UnexpectedSnafu {
2142 violated: format!("Invalid catalog name: {}", catalog)
2143 }
2144 );
2145
2146 ensure!(
2147 NAME_PATTERN_REG.is_match(database),
2148 error::UnexpectedSnafu {
2149 violated: format!("Invalid database name: {}", database)
2150 }
2151 );
2152
2153 #[cfg(feature = "enterprise")]
2154 let creator = match &self.create_database_handler {
2155 Some(handler) => handler.creator(&query_context).context(ExternalSnafu)?,
2156 None => None,
2157 };
2158 #[cfg(not(feature = "enterprise"))]
2159 let creator = None;
2160
2161 let output = if !self
2162 .catalog_manager
2163 .schema_exists(catalog, database, None)
2164 .await
2165 .context(CatalogSnafu)?
2166 && !self.catalog_manager.is_reserved_schema_name(database)
2167 {
2168 self.create_database_procedure(
2169 catalog.to_string(),
2170 database.to_string(),
2171 create_if_not_exists,
2172 options,
2173 query_context.clone(),
2174 creator,
2175 )
2176 .await?;
2177
2178 Output::new_with_affected_rows(1)
2179 } else if create_if_not_exists {
2180 Output::new_with_affected_rows(1)
2181 } else {
2182 return error::SchemaExistsSnafu { name: database }.fail();
2183 };
2184
2185 #[cfg(feature = "enterprise")]
2186 if let Some(handler) = &self.create_database_handler {
2187 handler
2188 .refresh_current_user(&query_context)
2189 .await
2190 .context(ExternalSnafu)?;
2191 }
2192
2193 Ok(output)
2194 }
2195
2196 async fn create_database_procedure(
2197 &self,
2198 catalog: String,
2199 database: String,
2200 create_if_not_exists: bool,
2201 options: HashMap<String, String>,
2202 query_context: QueryContextRef,
2203 creator: Option<CreatorGrantIntent>,
2204 ) -> Result<SubmitDdlTaskResponse> {
2205 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2206 let request = SubmitDdlTaskRequest::new(DdlTask::new_create_database(
2207 catalog,
2208 database,
2209 create_if_not_exists,
2210 options,
2211 creator,
2212 ));
2213
2214 self.procedure_executor
2215 .submit_ddl_task(executor_context, request)
2216 .await
2217 .context(error::ExecuteDdlSnafu)
2218 }
2219}
2220
2221pub fn parse_partitions(
2223 create_table: &CreateTableExpr,
2224 partitions: Option<Partitions>,
2225 query_ctx: &QueryContextRef,
2226) -> Result<(Vec<PartitionExpr>, Vec<String>)> {
2227 let partition_columns = find_partition_columns(&partitions)?;
2230 let partition_exprs =
2231 find_partition_entries(create_table, &partitions, &partition_columns, query_ctx)?;
2232
2233 let exprs = partition_exprs.clone();
2235 MultiDimPartitionRule::try_new(partition_columns.clone(), vec![], exprs, true)
2236 .context(InvalidPartitionSnafu)?;
2237
2238 Ok((partition_exprs, partition_columns))
2239}
2240
2241fn parse_partitions_for_logical_validation(
2242 create_table: &CreateTableExpr,
2243 partitions: &Partitions,
2244 query_ctx: &QueryContextRef,
2245) -> Result<(Vec<String>, Vec<PartitionExpr>)> {
2246 let partition_columns = partitions
2247 .column_list
2248 .iter()
2249 .map(|ident| ident.value.clone())
2250 .collect::<Vec<_>>();
2251
2252 let column_name_and_type = partition_columns
2253 .iter()
2254 .map(|pc| {
2255 let column = create_table
2256 .column_defs
2257 .iter()
2258 .find(|c| &c.name == pc)
2259 .context(ColumnNotFoundSnafu { msg: pc.clone() })?;
2260 let column_name = &column.name;
2261 let data_type = ConcreteDataType::from(
2262 ColumnDataTypeWrapper::try_new(column.data_type, column.datatype_extension.clone())
2263 .context(ColumnDataTypeSnafu)?,
2264 );
2265 Ok((column_name, data_type))
2266 })
2267 .collect::<Result<HashMap<_, _>>>()?;
2268
2269 let mut partition_exprs = Vec::with_capacity(partitions.exprs.len());
2270 for expr in &partitions.exprs {
2271 let partition_expr = convert_one_expr(expr, &column_name_and_type, &query_ctx.timezone())?;
2272 partition_exprs.push(partition_expr);
2273 }
2274
2275 MultiDimPartitionRule::try_new(
2276 partition_columns.clone(),
2277 vec![],
2278 partition_exprs.clone(),
2279 true,
2280 )
2281 .context(InvalidPartitionSnafu)?;
2282
2283 Ok((partition_columns, partition_exprs))
2284}
2285
2286pub fn verify_alter(
2292 table_id: TableId,
2293 table_info: Arc<TableInfo>,
2294 expr: AlterTableExpr,
2295) -> Result<bool> {
2296 let request: AlterTableRequest =
2297 common_grpc_expr::alter_expr_to_request(table_id, expr, Some(&table_info.meta))
2298 .context(AlterExprToRequestSnafu)?;
2299
2300 let AlterTableRequest {
2301 table_name,
2302 alter_kind,
2303 ..
2304 } = &request;
2305
2306 if let AlterKind::RenameTable { new_table_name } = alter_kind {
2307 ensure!(
2308 NAME_PATTERN_REG.is_match(new_table_name),
2309 error::UnexpectedSnafu {
2310 violated: format!("Invalid table name: {}", new_table_name)
2311 }
2312 );
2313 ensure_table_definition_writable(&table_info.schema_name, new_table_name)?;
2316 } else if let AlterKind::AddColumns { columns } = alter_kind {
2317 let column_names: HashSet<_> = table_info
2320 .meta
2321 .schema
2322 .column_schemas()
2323 .iter()
2324 .map(|schema| &schema.name)
2325 .collect();
2326 if columns.iter().all(|column| {
2327 column_names.contains(&column.column_schema.name) && column.add_if_not_exists
2328 }) {
2329 return Ok(false);
2330 }
2331 }
2332
2333 let new_meta = table_info
2334 .meta
2335 .builder_with_alter_kind(table_name, &request.alter_kind)
2336 .context(error::TableSnafu)?
2337 .build()
2338 .context(error::BuildTableMetaSnafu { table_name })?;
2339
2340 validate_json2_columns_append_mode(&new_meta.schema, &new_meta.options)?;
2341 for (key, value) in &new_meta.options.extra_options {
2345 if parse_entity_option_key(key).is_none() {
2346 continue;
2347 }
2348 for col in &parse_entity_columns(value) {
2349 if let Some(column) = new_meta.schema.column_schema_by_name(col) {
2350 ensure_entity_column_renders_as_string(key, col, &column.data_type)?;
2351 }
2352 }
2353 }
2354
2355 Ok(true)
2356}
2357
2358pub fn create_table_info(
2359 create_table: &CreateTableExpr,
2360 partition_columns: Vec<String>,
2361) -> Result<TableInfo> {
2362 let mut column_schemas = Vec::with_capacity(create_table.column_defs.len());
2363 let mut column_name_to_index_map = HashMap::new();
2364
2365 for (idx, column) in create_table.column_defs.iter().enumerate() {
2366 let schema =
2367 column_def::try_as_column_schema(column).context(error::InvalidColumnDefSnafu {
2368 column: &column.name,
2369 })?;
2370 let schema = schema.with_time_index(column.name == create_table.time_index);
2371
2372 column_schemas.push(schema);
2373 let _ = column_name_to_index_map.insert(column.name.clone(), idx);
2374 }
2375
2376 let next_column_id = column_schemas.len() as u32;
2377 let schema = Arc::new(Schema::try_new(column_schemas).context(ConvertSchemaSnafu)?);
2378
2379 let primary_key_indices = create_table
2380 .primary_keys
2381 .iter()
2382 .map(|name| {
2383 column_name_to_index_map
2384 .get(name)
2385 .cloned()
2386 .context(ColumnNotFoundSnafu { msg: name })
2387 })
2388 .collect::<Result<Vec<_>>>()?;
2389
2390 let partition_key_indices = partition_columns
2391 .into_iter()
2392 .map(|col_name| {
2393 column_name_to_index_map
2394 .get(&col_name)
2395 .cloned()
2396 .context(ColumnNotFoundSnafu { msg: col_name })
2397 })
2398 .collect::<Result<Vec<_>>>()?;
2399
2400 let mut table_options = TableOptions::try_from_iter(&create_table.table_options)
2401 .context(UnrecognizedTableOptionSnafu)?;
2402
2403 validate_json2_columns_append_mode(&schema, &table_options)?;
2404
2405 validate_repartition_column_hint(
2406 &mut table_options,
2407 &column_name_to_index_map,
2408 &partition_key_indices,
2409 &create_table.time_index,
2410 )?;
2411
2412 validate_entity_semantic_options(&table_options, &schema)?;
2413
2414 let meta = TableMeta {
2415 schema,
2416 primary_key_indices,
2417 value_indices: vec![],
2418 engine: create_table.engine.clone(),
2419 next_column_id,
2420 options: table_options,
2421 created_on: Utc::now(),
2422 updated_on: Utc::now(),
2423 partition_key_indices,
2424 column_ids: vec![],
2425 };
2426
2427 let desc = if create_table.desc.is_empty() {
2428 create_table.table_options.get(COMMENT_KEY).cloned()
2429 } else {
2430 Some(create_table.desc.clone())
2431 };
2432
2433 let table_info = TableInfo {
2434 ident: metadata::TableIdent {
2435 table_id: 0,
2437 version: 0,
2438 },
2439 name: create_table.table_name.clone(),
2440 desc,
2441 catalog_name: create_table.catalog_name.clone(),
2442 schema_name: create_table.schema_name.clone(),
2443 meta,
2444 table_type: TableType::Base,
2445 };
2446 Ok(table_info)
2447}
2448
2449fn validate_json2_columns_append_mode(schema: &Schema, table_options: &TableOptions) -> Result<()> {
2450 let append_mode = table_options
2451 .extra_options
2452 .get(APPEND_MODE_KEY)
2453 .is_some_and(|value| value == "true");
2454
2455 for column in schema.column_schemas() {
2456 if column.data_type.is_json2() {
2457 ensure!(
2458 append_mode,
2459 InvalidSqlSnafu {
2460 err_msg: format!(
2461 "JSON2 column `{}` requires {}='true'",
2462 column.name, APPEND_MODE_KEY
2463 ),
2464 }
2465 );
2466 }
2467 }
2468
2469 Ok(())
2470}
2471
2472fn validate_repartition_column_hint(
2473 table_options: &mut TableOptions,
2474 column_name_to_index_map: &HashMap<String, usize>,
2475 partition_key_indices: &[usize],
2476 time_index: &str,
2477) -> Result<()> {
2478 let Some(column_name) = table_options
2479 .extra_options
2480 .get(REPARTITION_COLUMN_HINT_KEY)
2481 .map(|value| value.trim().to_string())
2482 else {
2483 return Ok(());
2484 };
2485
2486 ensure!(
2487 !column_name.is_empty(),
2488 InvalidPartitionRuleSnafu {
2489 reason: format!("{REPARTITION_COLUMN_HINT_KEY} expects exactly one column name"),
2490 }
2491 );
2492
2493 ensure!(
2494 !column_name.contains(','),
2495 InvalidPartitionRuleSnafu {
2496 reason: format!("{REPARTITION_COLUMN_HINT_KEY} expects exactly one column name"),
2497 }
2498 );
2499
2500 ensure!(
2501 partition_key_indices.is_empty(),
2502 InvalidPartitionRuleSnafu {
2503 reason: format!(
2504 "cannot set {REPARTITION_COLUMN_HINT_KEY} on a table with partition metadata"
2505 ),
2506 }
2507 );
2508
2509 column_name_to_index_map
2510 .get(&column_name)
2511 .context(ColumnNotFoundSnafu { msg: &column_name })?;
2512
2513 ensure!(
2514 column_name != time_index,
2515 InvalidPartitionRuleSnafu {
2516 reason: format!("cannot set {REPARTITION_COLUMN_HINT_KEY} to the time index column"),
2517 }
2518 );
2519
2520 table_options
2521 .extra_options
2522 .insert(REPARTITION_COLUMN_HINT_KEY.to_string(), column_name);
2523
2524 Ok(())
2525}
2526
2527fn ensure_table_writable(schema: &str, table: &str) -> Result<()> {
2529 ensure!(
2530 !is_readonly_schema(schema),
2531 SchemaReadOnlySnafu {
2532 name: schema.to_string()
2533 }
2534 );
2535 ensure!(
2536 !is_readonly_table(schema, table),
2537 TableReadOnlySnafu {
2538 name: table.to_string()
2539 }
2540 );
2541 Ok(())
2542}
2543
2544fn ensure_table_definition_writable(schema: &str, table: &str) -> Result<()> {
2550 ensure_table_writable(schema, table)?;
2551 ensure!(
2552 !is_ddl_reserved_table(schema, table),
2553 TableDdlReservedSnafu {
2554 name: table.to_string()
2555 }
2556 );
2557 Ok(())
2558}
2559
2560fn has_stable_string_form(data_type: &datatypes::prelude::ConcreteDataType) -> bool {
2564 use datatypes::prelude::ConcreteDataType;
2565 !matches!(
2566 data_type,
2567 ConcreteDataType::Binary(_)
2568 | ConcreteDataType::Json(_)
2569 | ConcreteDataType::Vector(_)
2570 | ConcreteDataType::List(_)
2571 | ConcreteDataType::Struct(_)
2572 | ConcreteDataType::Dictionary(_)
2573 | ConcreteDataType::Null(_)
2574 )
2575}
2576
2577fn ensure_entity_column_renders_as_string(
2578 key: &str,
2579 col: &str,
2580 data_type: &datatypes::prelude::ConcreteDataType,
2581) -> Result<()> {
2582 ensure!(
2583 has_stable_string_form(data_type),
2584 InvalidSqlSnafu {
2585 err_msg: format!(
2586 "entity column `{col}` (option `{key}`) has type `{data_type}`, which cannot \
2587 render as a string",
2588 ),
2589 }
2590 );
2591 Ok(())
2592}
2593
2594fn validate_entity_semantic_options(table_options: &TableOptions, schema: &Schema) -> Result<()> {
2599 for (key, value) in &table_options.extra_options {
2600 if parse_entity_option_key(key).is_none() {
2601 continue;
2602 };
2603 for col in &parse_entity_columns(value) {
2604 let column = schema
2605 .column_schema_by_name(col)
2606 .context(ColumnNotFoundSnafu { msg: col })?;
2607 ensure_entity_column_renders_as_string(key, col, &column.data_type)?;
2608 }
2609 }
2610 Ok(())
2611}
2612
2613fn find_partition_columns(partitions: &Option<Partitions>) -> Result<Vec<String>> {
2614 let columns = if let Some(partitions) = partitions {
2615 partitions
2616 .column_list
2617 .iter()
2618 .map(|x| x.value.clone())
2619 .collect::<Vec<_>>()
2620 } else {
2621 vec![]
2622 };
2623 Ok(columns)
2624}
2625
2626fn find_partition_entries(
2630 create_table: &CreateTableExpr,
2631 partitions: &Option<Partitions>,
2632 partition_columns: &[String],
2633 query_ctx: &QueryContextRef,
2634) -> Result<Vec<PartitionExpr>> {
2635 let Some(partitions) = partitions else {
2636 return Ok(vec![]);
2637 };
2638
2639 let column_name_and_type = partition_columns
2641 .iter()
2642 .map(|pc| {
2643 let column = create_table
2644 .column_defs
2645 .iter()
2646 .find(|c| &c.name == pc)
2647 .unwrap();
2649 let column_name = &column.name;
2650 let data_type = ConcreteDataType::from(
2651 ColumnDataTypeWrapper::try_new(column.data_type, column.datatype_extension.clone())
2652 .context(ColumnDataTypeSnafu)?,
2653 );
2654 Ok((column_name, data_type))
2655 })
2656 .collect::<Result<HashMap<_, _>>>()?;
2657
2658 let mut partition_exprs = Vec::with_capacity(partitions.exprs.len());
2660 for partition in &partitions.exprs {
2661 let partition_expr =
2662 convert_one_expr(partition, &column_name_and_type, &query_ctx.timezone())?;
2663 partition_exprs.push(partition_expr);
2664 }
2665
2666 Ok(partition_exprs)
2667}
2668
2669fn column_name_and_type<'a>(
2670 partition_columns: &'a [&'a ColumnSchema],
2671) -> HashMap<&'a String, ConcreteDataType> {
2672 partition_columns
2673 .iter()
2674 .map(|column| (&column.name, column.data_type.clone()))
2675 .collect()
2676}
2677
2678fn validate_and_collect_partition_columns<'a>(
2679 column_names: &[String],
2680 column_schemas: &'a [ColumnSchema],
2681) -> Result<Vec<&'a ColumnSchema>> {
2682 let mut seen = HashSet::with_capacity(column_names.len());
2683 column_names
2684 .iter()
2685 .map(|column_name| {
2686 ensure!(
2687 seen.insert(column_name),
2688 InvalidPartitionRuleSnafu {
2689 reason: format!("duplicate partition column '{}'", column_name)
2690 }
2691 );
2692 column_schemas
2693 .iter()
2694 .find(|column| &column.name == column_name)
2695 .with_context(|| ColumnNotFoundSnafu { msg: column_name })
2696 })
2697 .collect()
2698}
2699
2700fn ensure_partition_expr_columns_in_target(
2701 partition_exprs: &[PartitionExpr],
2702 target_partition_columns: &HashSet<&String>,
2703) -> Result<()> {
2704 for expr in partition_exprs {
2705 ensure_partition_operand_columns_in_target(&expr.lhs, target_partition_columns)?;
2706 ensure_partition_operand_columns_in_target(&expr.rhs, target_partition_columns)?;
2707 }
2708
2709 Ok(())
2710}
2711
2712fn ensure_partition_operand_columns_in_target(
2713 operand: &Operand,
2714 target_partition_columns: &HashSet<&String>,
2715) -> Result<()> {
2716 match operand {
2717 Operand::Column(column) => ensure!(
2718 target_partition_columns.contains(column),
2719 InvalidPartitionRuleSnafu {
2720 reason: format!(
2721 "partition expression references column '{}' that is not in target partition columns",
2722 column
2723 )
2724 }
2725 ),
2726 Operand::Expr(expr) => {
2727 ensure_partition_operand_columns_in_target(&expr.lhs, target_partition_columns)?;
2728 ensure_partition_operand_columns_in_target(&expr.rhs, target_partition_columns)?;
2729 }
2730 Operand::Value(_) => {}
2731 }
2732
2733 Ok(())
2734}
2735
2736fn convert_one_expr(
2737 expr: &Expr,
2738 column_name_and_type: &HashMap<&String, ConcreteDataType>,
2739 timezone: &Timezone,
2740) -> Result<PartitionExpr> {
2741 let Expr::BinaryOp { left, op, right } = expr else {
2742 return InvalidPartitionRuleSnafu {
2743 reason: "partition rule must be a binary expression",
2744 }
2745 .fail();
2746 };
2747
2748 let op =
2749 RestrictedOp::try_from_parser(&op.clone()).with_context(|| InvalidPartitionRuleSnafu {
2750 reason: format!("unsupported operator in partition expr {op}"),
2751 })?;
2752
2753 let (lhs, op, rhs) = match (left.as_ref(), right.as_ref()) {
2755 (Expr::Identifier(ident), Expr::Value(value)) => {
2757 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2758 let value = convert_value(&value.value, data_type, timezone, None)?;
2759 (Operand::Column(column_name), op, Operand::Value(value))
2760 }
2761 (Expr::Identifier(ident), Expr::UnaryOp { op: unary_op, expr })
2762 if let Expr::Value(v) = &**expr =>
2763 {
2764 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2765 let value = convert_value(&v.value, data_type, timezone, Some(*unary_op))?;
2766 (Operand::Column(column_name), op, Operand::Value(value))
2767 }
2768 (Expr::Value(value), Expr::Identifier(ident)) => {
2770 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2771 let value = convert_value(&value.value, data_type, timezone, None)?;
2772 (Operand::Value(value), op, Operand::Column(column_name))
2773 }
2774 (Expr::UnaryOp { op: unary_op, expr }, Expr::Identifier(ident))
2775 if let Expr::Value(v) = &**expr =>
2776 {
2777 let (column_name, data_type) = convert_identifier(ident, column_name_and_type)?;
2778 let value = convert_value(&v.value, data_type, timezone, Some(*unary_op))?;
2779 (Operand::Value(value), op, Operand::Column(column_name))
2780 }
2781 (Expr::BinaryOp { .. }, Expr::BinaryOp { .. }) => {
2782 let lhs = convert_one_expr(left, column_name_and_type, timezone)?;
2784 let rhs = convert_one_expr(right, column_name_and_type, timezone)?;
2785 (Operand::Expr(lhs), op, Operand::Expr(rhs))
2786 }
2787 _ => {
2788 return InvalidPartitionRuleSnafu {
2789 reason: format!("invalid partition expr {expr}"),
2790 }
2791 .fail();
2792 }
2793 };
2794
2795 Ok(PartitionExpr::new(lhs, op, rhs))
2796}
2797
2798fn convert_identifier(
2799 ident: &Ident,
2800 column_name_and_type: &HashMap<&String, ConcreteDataType>,
2801) -> Result<(String, ConcreteDataType)> {
2802 let column_name = ident.value.clone();
2803 let data_type = column_name_and_type
2804 .get(&column_name)
2805 .cloned()
2806 .with_context(|| ColumnNotFoundSnafu { msg: &column_name })?;
2807 Ok((column_name, data_type))
2808}
2809
2810fn convert_value(
2811 value: &ParserValue,
2812 data_type: ConcreteDataType,
2813 timezone: &Timezone,
2814 unary_op: Option<UnaryOperator>,
2815) -> Result<Value> {
2816 sql_value_to_value(
2817 &ColumnSchema::new("<NONAME>", data_type, true),
2818 value,
2819 Some(timezone),
2820 unary_op,
2821 false,
2822 )
2823 .context(error::SqlCommonSnafu)
2824}
2825
2826#[cfg(feature = "enterprise")]
2827async fn execute_undrop_table(
2828 table_metadata_manager: &TableMetadataManagerRef,
2829 procedure_executor: &ProcedureExecutorRef,
2830 cache_invalidator: &CacheInvalidatorRef,
2831 table_name: TableName,
2832 query_context: QueryContextRef,
2833) -> Result<Output> {
2834 ensure_table_definition_writable(&table_name.schema_name, &table_name.table_name)?;
2837
2838 let dropped = table_metadata_manager
2839 .get_dropped_table(&table_name)
2840 .await
2841 .context(TableMetadataManagerSnafu)?
2842 .with_context(|| TableNotFoundSnafu {
2843 table_name: table_name.to_string(),
2844 })?;
2845
2846 let executor_context = to_executor_context(query_context, TriggerReason::Manual);
2847 let request = SubmitDdlTaskRequest::new(DdlTask::new_undrop_table(dropped.table_id));
2848 procedure_executor
2849 .submit_ddl_task(executor_context, request)
2850 .await
2851 .context(error::ExecuteDdlSnafu)?;
2852
2853 if let Err(err) = cache_invalidator
2854 .invalidate(
2855 &Context::default(),
2856 &[
2857 CacheIdent::TableId(dropped.table_id),
2858 CacheIdent::TableName(table_name),
2859 ],
2860 )
2861 .await
2862 {
2863 warn!(
2864 "Failed to invalidate cache after restoring table '{}' (id={}): {}",
2865 dropped.table_name, dropped.table_id, err
2866 );
2867 }
2868
2869 Ok(Output::new_with_affected_rows(0))
2870}
2871
2872#[cfg(test)]
2873mod test {
2874 #[cfg(feature = "enterprise")]
2875 use std::sync::{Arc, Mutex};
2876 use std::time::Duration;
2877
2878 #[cfg(feature = "enterprise")]
2879 use api::v1::meta::{ProcedureDetailResponse, ReconcileRequest, ReconcileResponse};
2880 #[cfg(feature = "enterprise")]
2881 use common_meta::cache_invalidator::{CacheInvalidator, CacheInvalidatorRef};
2882 #[cfg(feature = "enterprise")]
2883 use common_meta::instruction::CacheIdent;
2884 #[cfg(feature = "enterprise")]
2885 use common_meta::key::table_route::TableRouteValue;
2886 #[cfg(feature = "enterprise")]
2887 use common_meta::key::test_utils::new_test_table_info_with_name;
2888 #[cfg(feature = "enterprise")]
2889 use common_meta::key::{TableMetadataManager, TableMetadataManagerRef};
2890 #[cfg(feature = "enterprise")]
2891 use common_meta::kv_backend::memory::MemoryKvBackend;
2892 #[cfg(feature = "enterprise")]
2893 use common_meta::procedure_executor::{
2894 ExecutorContext, ProcedureExecutor, ProcedureExecutorRef,
2895 };
2896 #[cfg(feature = "enterprise")]
2897 use common_meta::rpc::ddl::{DdlTask, SubmitDdlTaskRequest, SubmitDdlTaskResponse};
2898 #[cfg(feature = "enterprise")]
2899 use common_meta::rpc::procedure::{
2900 MigrateRegionRequest, MigrateRegionResponse, ProcedureStateResponse,
2901 };
2902 use session::context::{QueryContext, QueryContextBuilder};
2903 use sql::dialect::GreptimeDbDialect;
2904 use sql::parser::{ParseOptions, ParserContext};
2905 use sql::statements::statement::Statement;
2906 use sqlparser::parser::Parser;
2907
2908 use super::*;
2909 use crate::expr_helper;
2910
2911 #[cfg(feature = "enterprise")]
2912 #[derive(Default)]
2913 struct RecordingProcedureExecutor {
2914 requests: Mutex<Vec<SubmitDdlTaskRequest>>,
2915 contexts: Mutex<Vec<ExecutorContext>>,
2916 fail: bool,
2917 }
2918
2919 #[cfg(feature = "enterprise")]
2920 #[async_trait::async_trait]
2921 impl ProcedureExecutor for RecordingProcedureExecutor {
2922 async fn submit_ddl_task(
2923 &self,
2924 ctx: ExecutorContext,
2925 request: SubmitDdlTaskRequest,
2926 ) -> common_meta::error::Result<SubmitDdlTaskResponse> {
2927 self.contexts.lock().unwrap().push(ctx);
2928 self.requests.lock().unwrap().push(request);
2929 if self.fail {
2930 return common_meta::error::UnsupportedSnafu {
2931 operation: "test submit failure",
2932 }
2933 .fail();
2934 }
2935 Ok(SubmitDdlTaskResponse::default())
2936 }
2937
2938 async fn migrate_region(
2939 &self,
2940 _: &ExecutorContext,
2941 _: MigrateRegionRequest,
2942 ) -> common_meta::error::Result<MigrateRegionResponse> {
2943 unimplemented!()
2944 }
2945 async fn reconcile(
2946 &self,
2947 _: &ExecutorContext,
2948 _: ReconcileRequest,
2949 ) -> common_meta::error::Result<ReconcileResponse> {
2950 unimplemented!()
2951 }
2952 async fn query_procedure_state(
2953 &self,
2954 _: &ExecutorContext,
2955 _: &str,
2956 ) -> common_meta::error::Result<ProcedureStateResponse> {
2957 unimplemented!()
2958 }
2959 async fn list_procedures(
2960 &self,
2961 _: &ExecutorContext,
2962 ) -> common_meta::error::Result<ProcedureDetailResponse> {
2963 unimplemented!()
2964 }
2965 }
2966
2967 #[cfg(feature = "enterprise")]
2968 #[derive(Default)]
2969 struct RecordingCacheInvalidator {
2970 invalidations: Mutex<Vec<Vec<CacheIdent>>>,
2971 fail: bool,
2972 }
2973
2974 #[cfg(feature = "enterprise")]
2975 #[async_trait::async_trait]
2976 impl CacheInvalidator for RecordingCacheInvalidator {
2977 async fn invalidate(
2978 &self,
2979 _: &Context,
2980 caches: &[CacheIdent],
2981 ) -> common_meta::error::Result<()> {
2982 self.invalidations.lock().unwrap().push(caches.to_vec());
2983 if self.fail {
2984 return common_meta::error::UnsupportedSnafu {
2985 operation: "test cache failure",
2986 }
2987 .fail();
2988 }
2989 Ok(())
2990 }
2991
2992 fn invalidate_all(&self) -> common_meta::error::Result<()> {
2993 Ok(())
2994 }
2995 }
2996
2997 #[cfg(feature = "enterprise")]
2998 async fn dropped_table_manager(table_id: TableId, name: &TableName) -> TableMetadataManagerRef {
2999 let backend = Arc::new(MemoryKvBackend::default());
3000 let manager = Arc::new(TableMetadataManager::new(backend));
3001 let mut info = new_test_table_info_with_name(table_id, &name.table_name);
3002 info.catalog_name = name.catalog_name.clone();
3003 info.schema_name = name.schema_name.clone();
3004 let route = TableRouteValue::physical(vec![]);
3005 manager
3006 .create_table_metadata(info, route.clone(), HashMap::new())
3007 .await
3008 .unwrap();
3009 manager
3010 .delete_table_metadata(table_id, name, &route, &HashMap::new(), None)
3011 .await
3012 .unwrap();
3013 manager
3014 }
3015
3016 #[cfg(feature = "enterprise")]
3017 #[tokio::test]
3018 async fn test_undrop_table_execution_path() {
3019 let name = TableName::new("greptime", "public", "metrics");
3020 let manager = dropped_table_manager(42, &name).await;
3021 let mut live = new_test_table_info_with_name(99, "metrics");
3023 live.catalog_name = "greptime".to_string();
3024 live.schema_name = "public".to_string();
3025 manager
3026 .create_table_metadata(live, TableRouteValue::physical(vec![]), HashMap::new())
3027 .await
3028 .unwrap();
3029 let procedure = Arc::new(RecordingProcedureExecutor::default());
3030 let cache = Arc::new(RecordingCacheInvalidator::default());
3031
3032 execute_undrop_table(
3033 &manager,
3034 &(procedure.clone() as ProcedureExecutorRef),
3035 &(cache.clone() as CacheInvalidatorRef),
3036 name.clone(),
3037 QueryContext::arc(),
3038 )
3039 .await
3040 .unwrap();
3041
3042 let requests = procedure.requests.lock().unwrap();
3043 assert!(matches!(&requests[0].task, DdlTask::UndropTable(task) if task.table_id == 42));
3044 let contexts = procedure.contexts.lock().unwrap();
3045 assert_eq!(
3046 contexts[0].event_input.as_ref().map(|input| input.reason),
3047 Some(TriggerReason::Manual)
3048 );
3049 assert_eq!(
3050 cache.invalidations.lock().unwrap()[0],
3051 vec![CacheIdent::TableId(42), CacheIdent::TableName(name)]
3052 );
3053 }
3054
3055 #[cfg(feature = "enterprise")]
3056 #[tokio::test]
3057 async fn test_undrop_table_missing_tombstone_submits_nothing() {
3058 let manager = Arc::new(TableMetadataManager::new(Arc::new(
3059 MemoryKvBackend::default(),
3060 )));
3061 let procedure = Arc::new(RecordingProcedureExecutor::default());
3062 let cache = Arc::new(RecordingCacheInvalidator::default());
3063 let err = execute_undrop_table(
3064 &manager,
3065 &(procedure.clone() as ProcedureExecutorRef),
3066 &(cache as CacheInvalidatorRef),
3067 TableName::new("greptime", "public", "missing"),
3068 QueryContext::arc(),
3069 )
3070 .await
3071 .unwrap_err();
3072 assert!(matches!(err, crate::error::Error::TableNotFound { .. }));
3073 assert!(procedure.requests.lock().unwrap().is_empty());
3074 }
3075
3076 #[cfg(feature = "enterprise")]
3077 #[tokio::test]
3078 async fn test_undrop_table_submit_failure_does_not_invalidate() {
3079 let name = TableName::new("greptime", "public", "metrics");
3080 let manager = dropped_table_manager(42, &name).await;
3081 let procedure = Arc::new(RecordingProcedureExecutor {
3082 fail: true,
3083 ..Default::default()
3084 });
3085 let cache = Arc::new(RecordingCacheInvalidator::default());
3086 execute_undrop_table(
3087 &manager,
3088 &(procedure as ProcedureExecutorRef),
3089 &(cache.clone() as CacheInvalidatorRef),
3090 name,
3091 QueryContext::arc(),
3092 )
3093 .await
3094 .unwrap_err();
3095 assert!(cache.invalidations.lock().unwrap().is_empty());
3096 }
3097
3098 #[cfg(feature = "enterprise")]
3099 #[tokio::test]
3100 async fn test_undrop_table_cache_failure_returns_success_after_submit() {
3101 let name = TableName::new("greptime", "public", "metrics");
3102 let manager = dropped_table_manager(42, &name).await;
3103 let procedure = Arc::new(RecordingProcedureExecutor::default());
3104 let cache = Arc::new(RecordingCacheInvalidator {
3105 fail: true,
3106 ..Default::default()
3107 });
3108 execute_undrop_table(
3109 &manager,
3110 &(procedure.clone() as ProcedureExecutorRef),
3111 &(cache.clone() as CacheInvalidatorRef),
3112 name.clone(),
3113 QueryContext::arc(),
3114 )
3115 .await
3116 .unwrap();
3117 assert_eq!(procedure.requests.lock().unwrap().len(), 1);
3118 assert_eq!(
3119 cache.invalidations.lock().unwrap()[0],
3120 vec![CacheIdent::TableId(42), CacheIdent::TableName(name)]
3121 );
3122 }
3123
3124 #[test]
3125 fn test_parse_ddl_options() {
3126 let options = OptionMap::from([
3127 ("timeout".to_string(), "5m".to_string()),
3128 ("wait".to_string(), "false".to_string()),
3129 ]);
3130 let ddl_options = parse_ddl_options(&options).unwrap();
3131 assert!(!ddl_options.wait);
3132 assert_eq!(Duration::from_secs(300), ddl_options.timeout);
3133 }
3134
3135 #[test]
3136 fn test_validate_entity_semantic_options() {
3137 let schema = Schema::new(vec![
3138 ColumnSchema::new("service_name", ConcreteDataType::string_datatype(), true),
3139 ColumnSchema::new("host_id", ConcreteDataType::string_datatype(), true),
3140 ColumnSchema::new("value", ConcreteDataType::float64_datatype(), true),
3141 ColumnSchema::new("payload", ConcreteDataType::binary_datatype(), true),
3142 ]);
3143 let opts = |pairs: &[(&str, &str)]| TableOptions {
3144 extra_options: pairs
3145 .iter()
3146 .map(|(k, v)| (k.to_string(), v.to_string()))
3147 .collect(),
3148 ..Default::default()
3149 };
3150
3151 for pairs in [
3153 &[(
3154 "greptime.semantic.entity.process.id",
3155 "service_name,host_id",
3156 )][..],
3157 &[("greptime.semantic.entity.service.id", "value")][..],
3158 ] {
3159 assert!(validate_entity_semantic_options(&opts(pairs), &schema).is_ok());
3160 }
3161
3162 let missing = validate_entity_semantic_options(
3163 &opts(&[("greptime.semantic.entity.service.id", "nope")]),
3164 &schema,
3165 )
3166 .unwrap_err();
3167 assert!(matches!(missing, error::Error::ColumnNotFound { .. }));
3168
3169 let binary_id = validate_entity_semantic_options(
3170 &opts(&[("greptime.semantic.entity.service.id", "payload")]),
3171 &schema,
3172 )
3173 .unwrap_err();
3174 assert!(matches!(binary_id, error::Error::InvalidSql { .. }));
3175 }
3176
3177 #[test]
3178 fn test_validate_and_normalize_flow_options_empty() {
3179 assert!(
3180 validate_and_normalize_flow_options(HashMap::new(), None)
3181 .unwrap()
3182 .is_empty()
3183 );
3184 }
3185
3186 #[test]
3187 fn test_validate_and_normalize_flow_options_valid() {
3188 let options = HashMap::from([
3189 (DEFER_ON_MISSING_SOURCE_KEY.to_string(), "TRUE".to_string()),
3190 (
3191 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY.to_string(),
3192 "FALSE".to_string(),
3193 ),
3194 ]);
3195
3196 assert_eq!(
3197 validate_and_normalize_flow_options(options, None).unwrap(),
3198 HashMap::from([
3199 (DEFER_ON_MISSING_SOURCE_KEY.to_string(), "true".to_string(),),
3200 (
3201 FLOW_EXPERIMENTAL_ENABLE_INCREMENTAL_READ_KEY.to_string(),
3202 "false".to_string(),
3203 )
3204 ])
3205 );
3206 }
3207
3208 #[test]
3209 fn test_validate_and_normalize_flow_options_unknown_option() {
3210 let err = validate_and_normalize_flow_options(
3211 HashMap::from([("foo".to_string(), "bar".to_string())]),
3212 None,
3213 )
3214 .unwrap_err();
3215
3216 assert!(
3217 err.to_string()
3218 .contains("unknown flow option 'foo', supported options:")
3219 );
3220 }
3221
3222 #[test]
3223 fn test_validate_and_normalize_flow_options_reserved_option() {
3224 let err = validate_and_normalize_flow_options(
3225 HashMap::from([(
3226 FlowType::FLOW_TYPE_KEY.to_string(),
3227 FlowType::BATCHING.to_string(),
3228 )]),
3229 None,
3230 )
3231 .unwrap_err();
3232
3233 assert!(
3234 err.to_string()
3235 .contains("flow option 'flow_type' is reserved for internal use")
3236 );
3237 }
3238
3239 #[test]
3240 fn test_validate_and_normalize_flow_options_invalid_bool() {
3241 let err = validate_and_normalize_flow_options(
3242 HashMap::from([(
3243 DEFER_ON_MISSING_SOURCE_KEY.to_string(),
3244 "not-a-bool".to_string(),
3245 )]),
3246 None,
3247 )
3248 .unwrap_err();
3249
3250 assert!(
3251 err.to_string()
3252 .contains("invalid flow option 'defer_on_missing_source': 'not-a-bool'")
3253 );
3254 }
3255
3256 #[test]
3257 fn test_validate_and_normalize_flow_options_rejects_redacted_invalid_input() {
3258 let sql = r"
3259CREATE FLOW task_6
3260SINK TO schema_1.table_1
3261WITH (access_key_id = [true])
3262AS
3263SELECT max(c1), min(c2) FROM schema_2.table_2;";
3264 let stmt =
3265 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3266 .unwrap()
3267 .pop()
3268 .unwrap();
3269
3270 let Statement::CreateFlow(create_flow) = stmt else {
3271 unreachable!()
3272 };
3273 let expr =
3274 expr_helper::to_create_flow_task_expr(create_flow, &QueryContext::arc()).unwrap();
3275 let err = validate_and_normalize_flow_options(expr.flow_options, None).unwrap_err();
3276
3277 assert!(
3278 err.to_string()
3279 .contains("unknown flow option 'access_key_id'")
3280 );
3281 }
3282
3283 #[test]
3286 fn test_eval_interval_rejected_non_positive() {
3287 let err = validate_and_normalize_flow_options(HashMap::new(), Some(0)).unwrap_err();
3289 assert!(err.to_string().contains("EVAL INTERVAL must be positive"));
3290
3291 let err = validate_and_normalize_flow_options(HashMap::new(), Some(-5)).unwrap_err();
3293 assert!(err.to_string().contains("EVAL INTERVAL must be positive"));
3294
3295 let result = validate_and_normalize_flow_options(HashMap::new(), Some(300));
3297 assert!(result.is_ok());
3298 }
3299
3300 #[test]
3301 fn test_schedule_and_internal_keys_rejected_as_unknown_options() {
3302 for key in [
3303 "eval_interval_anchor",
3304 "eval_interval_start",
3305 "eval_interval_missed_tick_policy",
3306 "eval_interval_catchup_max_runs",
3307 "eval_interval_catchup_max_lag",
3308 "__greptime_internal_eval_schedule",
3309 ] {
3310 let err = validate_and_normalize_flow_options(
3311 HashMap::from([(key.to_string(), "value".to_string())]),
3312 Some(300),
3313 )
3314 .unwrap_err();
3315
3316 assert!(
3317 err.to_string()
3318 .contains(&format!("unknown flow option '{key}'")),
3319 "unexpected error for {key}: {err}"
3320 );
3321 }
3322 }
3323
3324 #[test]
3325 fn test_determine_flow_type_for_source_state_missing_sources_require_opt_in() {
3326 let err = determine_flow_type_for_source_state("my_flow", &HashMap::new(), true, false)
3327 .unwrap_err();
3328
3329 assert!(err.to_string().contains(
3330 "missing source tables for flow 'my_flow'; use WITH (defer_on_missing_source = true) to create a pending flow"
3331 ));
3332 }
3333
3334 #[test]
3335 fn test_determine_flow_type_for_source_state_missing_sources_prefer_batching() {
3336 let flow_options =
3337 HashMap::from([(DEFER_ON_MISSING_SOURCE_KEY.to_string(), "true".to_string())]);
3338
3339 assert_eq!(
3340 determine_flow_type_for_source_state("my_flow", &flow_options, true, true).unwrap(),
3341 Some(FlowType::Batching)
3342 );
3343 }
3344
3345 #[test]
3346 fn test_determine_flow_type_for_source_state_instant_ttl_without_missing_sources() {
3347 assert_eq!(
3348 determine_flow_type_for_source_state("my_flow", &HashMap::new(), false, true).unwrap(),
3349 Some(FlowType::Streaming)
3350 );
3351 }
3352
3353 #[test]
3354 fn test_name_is_match() {
3355 assert!(!NAME_PATTERN_REG.is_match("/adaf"));
3356 assert!(!NAME_PATTERN_REG.is_match("🈲"));
3357 assert!(NAME_PATTERN_REG.is_match("hello"));
3358 assert!(NAME_PATTERN_REG.is_match("test@"));
3359 assert!(!NAME_PATTERN_REG.is_match("@test"));
3360 assert!(NAME_PATTERN_REG.is_match("test#"));
3361 assert!(!NAME_PATTERN_REG.is_match("#test"));
3362 assert!(!NAME_PATTERN_REG.is_match("@"));
3363 assert!(!NAME_PATTERN_REG.is_match("#"));
3364 }
3365
3366 #[test]
3367 fn test_partition_expr_equivalence_with_swapped_operands() {
3368 let column_name = "device_id".to_string();
3369 let column_name_and_type =
3370 HashMap::from([(&column_name, ConcreteDataType::int32_datatype())]);
3371 let timezone = Timezone::from_tz_string("UTC").unwrap();
3372 let dialect = GreptimeDbDialect {};
3373
3374 let mut parser = Parser::new(&dialect)
3375 .try_with_sql("device_id < 100")
3376 .unwrap();
3377 let expr_left = parser.parse_expr().unwrap();
3378
3379 let mut parser = Parser::new(&dialect)
3380 .try_with_sql("100 > device_id")
3381 .unwrap();
3382 let expr_right = parser.parse_expr().unwrap();
3383
3384 let partition_left =
3385 convert_one_expr(&expr_left, &column_name_and_type, &timezone).unwrap();
3386 let partition_right =
3387 convert_one_expr(&expr_right, &column_name_and_type, &timezone).unwrap();
3388
3389 assert_eq!(partition_left, partition_right);
3390 assert!([partition_left.clone()].contains(&partition_right));
3391
3392 let mut physical_partition_exprs = vec![partition_left];
3393 let mut logical_partition_exprs = vec![partition_right];
3394 physical_partition_exprs.sort_unstable();
3395 logical_partition_exprs.sort_unstable();
3396 assert_eq!(physical_partition_exprs, logical_partition_exprs);
3397 }
3398
3399 #[test]
3400 fn test_repartition_target_partition_columns_are_overwrite_context() {
3401 let device_id = ColumnSchema::new("device_id", ConcreteDataType::int32_datatype(), true);
3402 let area = ColumnSchema::new("area", ConcreteDataType::string_datatype(), true);
3403 let existing_partition_columns = vec![&device_id];
3404 let target_partition_columns = vec![&device_id, &area];
3405 let existing_column_name_and_type = column_name_and_type(&existing_partition_columns);
3406 let target_column_name_and_type = column_name_and_type(&target_partition_columns);
3407 let timezone = Timezone::from_tz_string("UTC").unwrap();
3408 let dialect = GreptimeDbDialect {};
3409
3410 let mut parser = Parser::new(&dialect)
3411 .try_with_sql("device_id < 100 AND area < 'South'")
3412 .unwrap();
3413 let expr = parser.parse_expr().unwrap();
3414
3415 let err = convert_one_expr(&expr, &existing_column_name_and_type, &timezone).unwrap_err();
3416 assert!(err.to_string().contains("area"));
3417
3418 let partition_expr = convert_one_expr(&expr, &target_column_name_and_type, &timezone)
3419 .expect("target columns should overwrite the conversion context");
3420 let partition_expr = partition_expr.to_string();
3421 assert!(partition_expr.contains("device_id"));
3422 assert!(partition_expr.contains("area"));
3423 assert!(partition_expr.contains("South"));
3424 }
3425
3426 #[test]
3427 fn test_repartition_rejects_remaining_expr_outside_target_columns() {
3428 let device_id = "device_id".to_string();
3429 let area = "area".to_string();
3430 let timezone = Timezone::from_tz_string("UTC").unwrap();
3431 let column_name_and_type = HashMap::from([
3432 (&device_id, ConcreteDataType::int32_datatype()),
3433 (&area, ConcreteDataType::string_datatype()),
3434 ]);
3435 let dialect = GreptimeDbDialect {};
3436 let mut parser = Parser::new(&dialect)
3437 .try_with_sql("device_id >= 100")
3438 .unwrap();
3439 let remaining_old_expr = convert_one_expr(
3440 &parser.parse_expr().unwrap(),
3441 &column_name_and_type,
3442 &timezone,
3443 )
3444 .unwrap();
3445 let target_partition_columns = HashSet::from([&area]);
3446
3447 let err = ensure_partition_expr_columns_in_target(
3448 &[remaining_old_expr],
3449 &target_partition_columns,
3450 )
3451 .unwrap_err();
3452
3453 assert!(err.to_string().contains("device_id"));
3454 assert!(err.to_string().contains("target partition columns"));
3455 }
3456
3457 #[test]
3458 fn test_repartition_rejects_duplicate_target_partition_columns() {
3459 let device_id = ColumnSchema::new("device_id", ConcreteDataType::int32_datatype(), true);
3460 let column_schemas = vec![device_id];
3461 let target_partition_columns = vec!["device_id".to_string(), "device_id".to_string()];
3462
3463 let err =
3464 validate_and_collect_partition_columns(&target_partition_columns, &column_schemas)
3465 .unwrap_err();
3466
3467 assert!(err.to_string().contains("duplicate partition column"));
3468 assert!(err.to_string().contains("device_id"));
3469 }
3470
3471 fn create_expr_from_sql(sql: &str) -> CreateTableExpr {
3472 let result =
3473 ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
3474 .unwrap();
3475
3476 match &result[0] {
3477 Statement::CreateTable(create) => {
3478 expr_helper::create_to_expr(create, &QueryContext::arc()).unwrap()
3479 }
3480 _ => unreachable!(),
3481 }
3482 }
3483
3484 #[test]
3485 fn test_create_table_with_repartition_column_hint() {
3486 let expr = create_expr_from_sql(
3487 r"
3488CREATE TABLE metrics (
3489 host STRING,
3490 ts TIMESTAMP TIME INDEX,
3491 cpu DOUBLE,
3492 PRIMARY KEY(host)
3493)
3494WITH ('repartition.column.hint' = ' host ')",
3495 );
3496
3497 let table_info = create_table_info(&expr, vec![]).unwrap();
3498 assert_eq!(
3499 table_info
3500 .meta
3501 .options
3502 .extra_options
3503 .get(REPARTITION_COLUMN_HINT_KEY),
3504 Some(&"host".to_string())
3505 );
3506 }
3507
3508 #[test]
3509 fn test_create_table_info_rejects_non_timestamp_time_index() {
3510 let expr = CreateTableExpr {
3511 catalog_name: "greptime".to_string(),
3512 schema_name: "public".to_string(),
3513 table_name: "demo".to_string(),
3514 desc: String::new(),
3515 column_defs: vec![api::v1::ColumnDef {
3516 name: "host".to_string(),
3517 data_type: api::v1::ColumnDataType::String as i32,
3518 is_nullable: true,
3519 default_constraint: vec![],
3520 semantic_type: 0,
3521 comment: String::new(),
3522 datatype_extension: None,
3523 options: None,
3524 }],
3525 time_index: "host".to_string(),
3526 primary_keys: vec![],
3527 create_if_not_exists: false,
3528 table_options: HashMap::new(),
3529 table_id: None,
3530 engine: "mito".to_string(),
3531 };
3532
3533 let err = create_table_info(&expr, vec![]).unwrap_err();
3534 assert_eq!(
3535 common_error::ext::ErrorExt::status_code(&err),
3536 common_error::status_code::StatusCode::InvalidArguments
3537 );
3538 }
3539
3540 #[test]
3541 fn test_verify_alter_guards_entity_column_types() {
3542 let expr = create_expr_from_sql(
3543 "CREATE TABLE t (svc STRING, ts TIMESTAMP TIME INDEX) \
3544 WITH ('greptime.semantic.entity.service.id'='svc');",
3545 );
3546 let table_info = Arc::new(create_table_info(&expr, vec![]).unwrap());
3547 let alter = |kind| AlterTableExpr {
3548 catalog_name: "greptime".to_string(),
3549 schema_name: "public".to_string(),
3550 table_name: "t".to_string(),
3551 kind: Some(kind),
3552 };
3553
3554 let modify = alter(Kind::ModifyColumnTypes(api::v1::ModifyColumnTypes {
3555 modify_column_types: vec![api::v1::ModifyColumnType {
3556 column_name: "svc".to_string(),
3557 target_type: api::v1::ColumnDataType::Binary as i32,
3558 target_type_extension: None,
3559 }],
3560 }));
3561 let err = verify_alter(1, table_info.clone(), modify).unwrap_err();
3562 assert!(
3563 err.to_string().contains("cannot render as a string"),
3564 "{err}"
3565 );
3566
3567 let drop = alter(Kind::DropColumns(api::v1::DropColumns {
3570 drop_columns: vec![api::v1::DropColumn {
3571 name: "svc".to_string(),
3572 }],
3573 }));
3574 assert!(verify_alter(1, table_info, drop).unwrap());
3575 }
3576
3577 #[test]
3578 fn test_json2_requires_append_mode() {
3579 let cases = [
3580 "CREATE TABLE monitor (payload JSON2, ts TIMESTAMP TIME INDEX);",
3581 "CREATE TABLE monitor (payload JSON2, ts TIMESTAMP TIME INDEX) WITH (append_mode='false');",
3582 ];
3583
3584 for sql in cases {
3585 let expr = create_expr_from_sql(sql);
3586 let err = create_table_info(&expr, vec![]).unwrap_err();
3587 assert!(
3588 err.to_string()
3589 .contains("JSON2 column `payload` requires append_mode='true'"),
3590 "{err}"
3591 );
3592 }
3593
3594 let expr = create_expr_from_sql(
3595 "CREATE TABLE monitor (payload JSON2, ts TIMESTAMP TIME INDEX) WITH (append_mode='true');",
3596 );
3597 create_table_info(&expr, vec![]).unwrap();
3598 }
3599
3600 #[test]
3601 fn test_create_table_with_empty_repartition_column_hint() {
3602 let expr = create_expr_from_sql(
3603 r"
3604CREATE TABLE metrics (
3605 host STRING,
3606 ts TIMESTAMP TIME INDEX,
3607 cpu DOUBLE,
3608 PRIMARY KEY(host)
3609)
3610WITH ('repartition.column.hint' = '')",
3611 );
3612
3613 let err = create_table_info(&expr, vec![]).unwrap_err();
3614 assert!(
3615 err.to_string()
3616 .contains("repartition.column.hint expects exactly one column name")
3617 );
3618 }
3619
3620 #[test]
3621 fn test_create_table_with_multiple_repartition_column_hints() {
3622 let expr = create_expr_from_sql(
3623 r"
3624CREATE TABLE metrics (
3625 host STRING,
3626 region_id STRING,
3627 ts TIMESTAMP TIME INDEX,
3628 cpu DOUBLE,
3629 PRIMARY KEY(host)
3630)
3631WITH ('repartition.column.hint' = 'host,region_id')",
3632 );
3633
3634 let err = create_table_info(&expr, vec![]).unwrap_err();
3635 assert!(
3636 err.to_string()
3637 .contains("repartition.column.hint expects exactly one column name")
3638 );
3639 }
3640
3641 #[test]
3642 fn test_create_table_with_missing_repartition_column_hint() {
3643 let expr = create_expr_from_sql(
3644 r"
3645CREATE TABLE metrics (
3646 host STRING,
3647 ts TIMESTAMP TIME INDEX,
3648 cpu DOUBLE,
3649 PRIMARY KEY(host)
3650)
3651WITH ('repartition.column.hint' = 'region_id')",
3652 );
3653
3654 let err = create_table_info(&expr, vec![]).unwrap_err();
3655 assert!(
3656 err.to_string()
3657 .contains("Cannot find column by name: region")
3658 );
3659 }
3660
3661 #[test]
3662 fn test_create_table_with_time_index_repartition_column_hint() {
3663 let expr = create_expr_from_sql(
3664 r"
3665CREATE TABLE metrics (
3666 host STRING,
3667 ts TIMESTAMP TIME INDEX,
3668 cpu DOUBLE,
3669 PRIMARY KEY(host)
3670)
3671WITH ('repartition.column.hint' = 'ts')",
3672 );
3673
3674 let err = create_table_info(&expr, vec![]).unwrap_err();
3675 assert!(
3676 err.to_string()
3677 .contains("cannot set repartition.column.hint to the time index column")
3678 );
3679 }
3680
3681 #[test]
3682 fn test_create_partitioned_table_with_repartition_column_hint() {
3683 let expr = create_expr_from_sql(
3684 r"
3685CREATE TABLE metrics (
3686 host STRING,
3687 ts TIMESTAMP TIME INDEX,
3688 cpu DOUBLE,
3689 PRIMARY KEY(host)
3690)
3691WITH ('repartition.column.hint' = 'host')",
3692 );
3693
3694 let err = create_table_info(&expr, vec!["host".to_string()]).unwrap_err();
3695 assert!(
3696 err.to_string()
3697 .contains("cannot set repartition.column.hint on a table with partition metadata")
3698 );
3699 }
3700
3701 #[tokio::test]
3702 #[ignore = "TODO(ruihang): WIP new partition rule"]
3703 async fn test_parse_partitions() {
3704 common_telemetry::init_default_ut_logging();
3705 let cases = [
3706 (
3707 r"
3708CREATE TABLE rcx ( a INT, b STRING, c TIMESTAMP, TIME INDEX (c) )
3709PARTITION ON COLUMNS (b) (
3710 b < 'hz',
3711 b >= 'hz' AND b < 'sh',
3712 b >= 'sh'
3713)
3714ENGINE=mito",
3715 r#"[{"column_list":["b"],"value_list":["{\"Value\":{\"String\":\"hz\"}}"]},{"column_list":["b"],"value_list":["{\"Value\":{\"String\":\"sh\"}}"]},{"column_list":["b"],"value_list":["\"MaxValue\""]}]"#,
3716 ),
3717 (
3718 r"
3719CREATE TABLE rcx ( a INT, b STRING, c TIMESTAMP, TIME INDEX (c) )
3720PARTITION BY RANGE COLUMNS (b, a) (
3721 PARTITION r0 VALUES LESS THAN ('hz', 10),
3722 b < 'hz' AND a < 10,
3723 b >= 'hz' AND b < 'sh' AND a >= 10 AND a < 20,
3724 b >= 'sh' AND a >= 20
3725)
3726ENGINE=mito",
3727 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\""]}]"#,
3728 ),
3729 ];
3730 let ctx = QueryContextBuilder::default().build().into();
3731 for (sql, expected) in cases {
3732 let result = ParserContext::create_with_dialect(
3733 sql,
3734 &GreptimeDbDialect {},
3735 ParseOptions::default(),
3736 )
3737 .unwrap();
3738 match &result[0] {
3739 Statement::CreateTable(c) => {
3740 let expr = expr_helper::create_to_expr(c, &QueryContext::arc()).unwrap();
3741 let (partitions, _) =
3742 parse_partitions(&expr, c.partitions.clone(), &ctx).unwrap();
3743 let json = serde_json::to_string(&partitions).unwrap();
3744 assert_eq!(json, expected);
3745 }
3746 _ => unreachable!(),
3747 }
3748 }
3749 }
3750}