1use std::sync::Arc;
16
17use ahash::{HashMap, HashMapExt, HashSet, HashSetExt};
18use api::v1::alter_table_expr::Kind;
19use api::v1::column_def::options_from_skipping;
20use api::v1::region::{
21 InsertRequest as RegionInsertRequest, InsertRequests as RegionInsertRequests,
22 RegionRequestHeader,
23};
24use api::v1::{
25 AlterTableExpr, ColumnDataType, ColumnSchema, CreateTableExpr, InsertRequests,
26 RowInsertRequest, RowInsertRequests, SemanticType,
27};
28use catalog::CatalogManagerRef;
29use client::{OutputData, OutputMeta};
30use common_catalog::consts::{
31 PARENT_SPAN_ID_COLUMN, SERVICE_NAME_COLUMN, TRACE_ID_COLUMN, TRACE_TABLE_NAME,
32 TRACE_TABLE_NAME_SESSION_KEY, default_engine, trace_operations_table_name,
33 trace_services_table_name,
34};
35use common_grpc_expr::util::ColumnExpr;
36use common_meta::cache::TableFlownodeSetCacheRef;
37use common_meta::node_manager::{AffectedRows, NodeManagerRef};
38use common_meta::peer::Peer;
39use common_query::Output;
40use common_query::native_histogram::{is_native_histogram_value_type, native_histogram_value_type};
41use common_query::prelude::{greptime_timestamp, greptime_value};
42use common_telemetry::tracing_context::TracingContext;
43use common_telemetry::{error, info, warn};
44use datatypes::schema::SkippingIndexOptions;
45use futures_util::future;
46use meter_macros::write_meter;
47use partition::manager::PartitionRuleManagerRef;
48use session::context::QueryContextRef;
49use snafu::ResultExt;
50use snafu::prelude::*;
51use sql::partition::partition_rule_for_hexstring;
52use sql::statements::create::Partitions;
53use sql::statements::insert::Insert;
54use store_api::metric_engine_consts::{
55 LOGICAL_TABLE_METADATA_KEY, METRIC_ENGINE_NAME, PHYSICAL_TABLE_METADATA_KEY,
56};
57use store_api::mito_engine_options::{
58 APPEND_MODE_KEY, COMPACTION_TYPE, COMPACTION_TYPE_TWCS, MERGE_MODE_KEY, TTL_KEY,
59 TWCS_TIME_WINDOW,
60};
61use store_api::storage::{RegionId, TableId};
62use table::TableRef;
63use table::metadata::TableInfo;
64use table::requests::{
65 AUTO_CREATE_TABLE_KEY, InsertRequest as TableInsertRequest, SEMANTIC_PER_TABLE_INDEX_KEY,
66 TABLE_DATA_MODEL, TABLE_DATA_MODEL_TRACE_V1, TRACE_TABLE_PARTITIONS_HINT_KEY,
67 VALID_TABLE_OPTION_KEYS, is_semantic_option_key, validate_semantic_option,
68};
69use table::table_reference::TableReference;
70
71use crate::error::{
72 CatalogSnafu, ColumnOptionsSnafu, CreatePartitionRulesSnafu, FindRegionLeaderSnafu,
73 InvalidInsertRequestSnafu, JoinTaskSnafu, RequestInsertsSnafu, Result, TableNotFoundSnafu,
74};
75use crate::expr_helper;
76use crate::region_req_factory::RegionRequestFactory;
77use crate::req_convert::common::preprocess_row_insert_requests;
78use crate::req_convert::insert::{
79 ColumnToRow, RowToRegion, StatementToRegion, TableToRegion, fill_reqs_with_impure_default,
80};
81use crate::statement::StatementExecutor;
82
83pub struct Inserter {
84 catalog_manager: CatalogManagerRef,
85 pub(crate) partition_manager: PartitionRuleManagerRef,
86 pub(crate) node_manager: NodeManagerRef,
87 pub(crate) table_flownode_set_cache: TableFlownodeSetCacheRef,
88 auto_create_table: bool,
92}
93
94pub type InserterRef = Arc<Inserter>;
95
96#[derive(Clone)]
98pub enum AutoCreateTableType {
99 Logical(String),
101 Physical,
103 Log,
105 LastNonNull,
107 Trace { alter_existing: bool },
109}
110
111impl AutoCreateTableType {
112 pub fn as_str(&self) -> &'static str {
113 match self {
114 AutoCreateTableType::Logical(_) => "logical",
115 AutoCreateTableType::Physical => "physical",
116 AutoCreateTableType::Log => "log",
117 AutoCreateTableType::LastNonNull => "last_non_null",
118 AutoCreateTableType::Trace { .. } => "trace",
119 }
120 }
121
122 fn alter_existing(&self) -> bool {
123 !matches!(
124 self,
125 Self::Trace {
126 alter_existing: false
127 }
128 )
129 }
130}
131
132#[derive(Clone)]
139pub struct InstantAndNormalInsertRequests {
140 pub normal_requests: RegionInsertRequests,
142 pub instant_requests: RegionInsertRequests,
145}
146
147impl Inserter {
148 pub fn new(
149 catalog_manager: CatalogManagerRef,
150 partition_manager: PartitionRuleManagerRef,
151 node_manager: NodeManagerRef,
152 table_flownode_set_cache: TableFlownodeSetCacheRef,
153 auto_create_table: bool,
154 ) -> Self {
155 Self {
156 catalog_manager,
157 partition_manager,
158 node_manager,
159 table_flownode_set_cache,
160 auto_create_table,
161 }
162 }
163
164 pub async fn handle_column_inserts(
165 &self,
166 requests: InsertRequests,
167 ctx: QueryContextRef,
168 statement_executor: &StatementExecutor,
169 ) -> Result<Output> {
170 let row_inserts = ColumnToRow::convert(requests)?;
171 self.handle_row_inserts(row_inserts, ctx, statement_executor, false, false)
172 .await
173 }
174
175 pub async fn handle_row_inserts(
177 &self,
178 mut requests: RowInsertRequests,
179 ctx: QueryContextRef,
180 statement_executor: &StatementExecutor,
181 accommodate_existing_schema: bool,
182 is_single_value: bool,
183 ) -> Result<Output> {
184 preprocess_row_insert_requests(&mut requests.inserts)?;
185 self.handle_row_inserts_with_create_type(
186 requests,
187 ctx,
188 statement_executor,
189 AutoCreateTableType::Physical,
190 accommodate_existing_schema,
191 is_single_value,
192 )
193 .await
194 }
195
196 pub async fn handle_log_inserts(
198 &self,
199 requests: RowInsertRequests,
200 ctx: QueryContextRef,
201 statement_executor: &StatementExecutor,
202 ) -> Result<Output> {
203 self.handle_row_inserts_with_create_type(
204 requests,
205 ctx,
206 statement_executor,
207 AutoCreateTableType::Log,
208 false,
209 false,
210 )
211 .await
212 }
213
214 pub async fn handle_trace_inserts(
215 &self,
216 requests: RowInsertRequests,
217 ctx: QueryContextRef,
218 statement_executor: &StatementExecutor,
219 ) -> Result<Output> {
220 self.handle_row_inserts_with_create_type(
221 requests,
222 ctx,
223 statement_executor,
224 AutoCreateTableType::Trace {
225 alter_existing: true,
226 },
227 false,
228 false,
229 )
230 .await
231 }
232
233 pub async fn handle_last_non_null_inserts(
235 &self,
236 requests: RowInsertRequests,
237 ctx: QueryContextRef,
238 statement_executor: &StatementExecutor,
239 accommodate_existing_schema: bool,
240 is_single_value: bool,
241 ) -> Result<Output> {
242 self.handle_row_inserts_with_create_type(
243 requests,
244 ctx,
245 statement_executor,
246 AutoCreateTableType::LastNonNull,
247 accommodate_existing_schema,
248 is_single_value,
249 )
250 .await
251 }
252
253 async fn handle_row_inserts_with_create_type(
255 &self,
256 mut requests: RowInsertRequests,
257 ctx: QueryContextRef,
258 statement_executor: &StatementExecutor,
259 create_type: AutoCreateTableType,
260 accommodate_existing_schema: bool,
261 is_single_value: bool,
262 ) -> Result<Output> {
263 requests.inserts.retain(|req| {
265 req.rows
266 .as_ref()
267 .map(|r| !r.rows.is_empty())
268 .unwrap_or_default()
269 });
270 validate_column_count_match(&requests)?;
271
272 let CreateAlterTableResult {
273 instant_table_ids,
274 table_infos,
275 } = self
276 .create_or_alter_tables_on_demand(
277 &mut requests,
278 &ctx,
279 create_type,
280 statement_executor,
281 accommodate_existing_schema,
282 is_single_value,
283 )
284 .await?;
285
286 let name_to_info = table_infos
287 .values()
288 .map(|info| (info.name.clone(), info.clone()))
289 .collect::<HashMap<_, _>>();
290 let inserts = RowToRegion::new(
291 name_to_info,
292 instant_table_ids,
293 self.partition_manager.as_ref(),
294 )
295 .convert(requests)
296 .await?;
297
298 self.do_request(inserts, &table_infos, &ctx).await
299 }
300
301 pub async fn handle_metric_row_inserts(
303 &self,
304 mut requests: RowInsertRequests,
305 ctx: QueryContextRef,
306 statement_executor: &StatementExecutor,
307 physical_table: String,
308 ) -> Result<Output> {
309 requests.inserts.retain(|req| {
311 req.rows
312 .as_ref()
313 .map(|r| !r.rows.is_empty())
314 .unwrap_or_default()
315 });
316 validate_column_count_match(&requests)?;
317
318 self.create_physical_table_on_demand(&ctx, physical_table.clone(), statement_executor)
320 .await?;
321
322 let CreateAlterTableResult {
324 instant_table_ids,
325 table_infos,
326 } = self
327 .create_or_alter_tables_on_demand(
328 &mut requests,
329 &ctx,
330 AutoCreateTableType::Logical(physical_table.clone()),
331 statement_executor,
332 true,
333 true,
334 )
335 .await?;
336 let name_to_info = table_infos
337 .values()
338 .map(|info| (info.name.clone(), info.clone()))
339 .collect::<HashMap<_, _>>();
340 let inserts = RowToRegion::new(name_to_info, instant_table_ids, &self.partition_manager)
341 .convert(requests)
342 .await?;
343
344 self.do_request(inserts, &table_infos, &ctx).await
345 }
346
347 pub async fn handle_table_insert(
348 &self,
349 request: TableInsertRequest,
350 ctx: QueryContextRef,
351 ) -> Result<Output> {
352 let catalog = request.catalog_name.as_str();
353 let schema = request.schema_name.as_str();
354 let table_name = request.table_name.as_str();
355 let table = self.get_table(catalog, schema, table_name).await?;
356 let table = table.with_context(|| TableNotFoundSnafu {
357 table_name: common_catalog::format_full_table_name(catalog, schema, table_name),
358 })?;
359 let table_info = table.table_info();
360
361 let inserts = TableToRegion::new(&table_info, &self.partition_manager)
362 .convert(request)
363 .await?;
364
365 let table_infos = HashMap::from_iter([(table_info.table_id(), table_info.clone())]);
366
367 self.do_request(inserts, &table_infos, &ctx).await
368 }
369
370 pub async fn handle_statement_insert(
371 &self,
372 insert: &Insert,
373 ctx: &QueryContextRef,
374 ) -> Result<Output> {
375 let (inserts, table_info) =
376 StatementToRegion::new(self.catalog_manager.as_ref(), &self.partition_manager, ctx)
377 .convert(insert, ctx)
378 .await?;
379
380 let table_infos = HashMap::from_iter([(table_info.table_id(), table_info.clone())]);
381
382 self.do_request(inserts, &table_infos, ctx).await
383 }
384}
385
386impl Inserter {
387 async fn do_request(
388 &self,
389 requests: InstantAndNormalInsertRequests,
390 table_infos: &HashMap<TableId, Arc<TableInfo>>,
391 ctx: &QueryContextRef,
392 ) -> Result<Output> {
393 let requests = fill_reqs_with_impure_default(table_infos, requests)?;
395
396 let write_cost = write_meter!(
397 ctx.current_catalog(),
398 ctx.current_schema(),
399 requests,
400 ctx.channel() as u8
401 );
402 let request_factory = RegionRequestFactory::new(RegionRequestHeader {
403 tracing_context: TracingContext::from_current_span().to_w3c(),
404 dbname: ctx.get_db_string(),
405 ..Default::default()
406 });
407
408 let InstantAndNormalInsertRequests {
409 normal_requests,
410 instant_requests,
411 } = requests;
412
413 let flow_mirror_task = FlowMirrorTask::new(
415 &self.table_flownode_set_cache,
416 normal_requests
417 .requests
418 .iter()
419 .chain(instant_requests.requests.iter()),
420 )
421 .await?;
422 flow_mirror_task.detach(self.node_manager.clone())?;
423
424 let write_tasks = self
426 .group_requests_by_peer(normal_requests)
427 .await?
428 .into_iter()
429 .map(|(peer, inserts)| {
430 let node_manager = self.node_manager.clone();
431 let request = request_factory.build_insert(inserts);
432 common_runtime::spawn_global(async move {
433 node_manager
434 .datanode(&peer)
435 .await
436 .handle(request)
437 .await
438 .context(RequestInsertsSnafu)
439 })
440 });
441 let results = future::try_join_all(write_tasks)
442 .await
443 .context(JoinTaskSnafu)?;
444 let affected_rows = results
445 .into_iter()
446 .map(|resp| resp.map(|r| r.affected_rows))
447 .sum::<Result<AffectedRows>>()?;
448 crate::metrics::DIST_INGEST_ROW_COUNT
449 .with_label_values(&[ctx.get_db_string().as_str()])
450 .inc_by(affected_rows as u64);
451 Ok(Output::new(
452 OutputData::AffectedRows(affected_rows),
453 OutputMeta::new_with_cost(write_cost as _),
454 ))
455 }
456
457 async fn group_requests_by_peer(
458 &self,
459 requests: RegionInsertRequests,
460 ) -> Result<HashMap<Peer, RegionInsertRequests>> {
461 let mut requests_per_region: HashMap<RegionId, RegionInsertRequests> = HashMap::new();
464 for req in requests.requests {
465 let region_id = RegionId::from_u64(req.region_id);
466 requests_per_region
467 .entry(region_id)
468 .or_default()
469 .requests
470 .push(req);
471 }
472
473 let mut inserts: HashMap<Peer, RegionInsertRequests> = HashMap::new();
474
475 for (region_id, reqs) in requests_per_region {
476 let peer = self
477 .partition_manager
478 .find_region_leader(region_id)
479 .await
480 .context(FindRegionLeaderSnafu)?;
481 inserts
482 .entry(peer)
483 .or_default()
484 .requests
485 .extend(reqs.requests);
486 }
487
488 Ok(inserts)
489 }
490
491 fn auto_create_disabled_reason(&self, ctx: &QueryContextRef) -> Result<Option<&'static str>> {
495 let auto_create_table_hint = ctx
496 .extension(AUTO_CREATE_TABLE_KEY)
497 .map(|v| v.parse::<bool>())
498 .transpose()
499 .map_err(|_| {
500 InvalidInsertRequestSnafu {
501 reason: "`auto_create_table` hint must be a boolean",
502 }
503 .build()
504 })?
505 .unwrap_or(true);
506 Ok(if !self.auto_create_table {
507 Some("auto-create table is disabled by frontend config")
508 } else if !auto_create_table_hint {
509 Some("`auto_create_table` hint is disabled")
510 } else {
511 None
512 })
513 }
514
515 pub async fn ensure_trace_table_on_demand(
520 &self,
521 table_name: &str,
522 request_schema: Vec<ColumnSchema>,
523 alter_existing: bool,
524 ctx: &QueryContextRef,
525 statement_executor: &StatementExecutor,
526 ) -> Result<()> {
527 let mut requests = RowInsertRequests {
528 inserts: vec![RowInsertRequest {
529 table_name: table_name.to_string(),
530 rows: Some(api::v1::Rows {
531 schema: request_schema,
532 rows: Vec::new(),
533 }),
534 }],
535 };
536 self.create_or_alter_tables_on_demand(
537 &mut requests,
538 ctx,
539 AutoCreateTableType::Trace { alter_existing },
540 statement_executor,
541 false,
542 false,
543 )
544 .await?;
545 Ok(())
546 }
547
548 async fn create_or_alter_tables_on_demand(
561 &self,
562 requests: &mut RowInsertRequests,
563 ctx: &QueryContextRef,
564 auto_create_table_type: AutoCreateTableType,
565 statement_executor: &StatementExecutor,
566 accommodate_existing_schema: bool,
567 is_single_value: bool,
568 ) -> Result<CreateAlterTableResult> {
569 let _timer = crate::metrics::CREATE_ALTER_ON_DEMAND
570 .with_label_values(&[auto_create_table_type.as_str()])
571 .start_timer();
572
573 let catalog = ctx.current_catalog();
574 let schema = ctx.current_schema();
575
576 let mut table_infos = HashMap::new();
577 if let Some(disabled_reason) = self.auto_create_disabled_reason(ctx)? {
578 let mut instant_table_ids = HashSet::new();
579 for req in &requests.inserts {
580 let table = self
581 .get_table(catalog, &schema, &req.table_name)
582 .await?
583 .context(InvalidInsertRequestSnafu {
584 reason: format!(
585 "Table `{}` does not exist, and {}",
586 req.table_name, disabled_reason
587 ),
588 })?;
589 let table_info = table.table_info();
590 if table_info.is_ttl_instant_table() {
591 instant_table_ids.insert(table_info.table_id());
592 }
593 table_infos.insert(table_info.table_id(), table.table_info());
594 }
595 let ret = CreateAlterTableResult {
596 instant_table_ids,
597 table_infos,
598 };
599 return Ok(ret);
600 }
601
602 let mut create_tables = vec![];
603 let mut alter_tables = vec![];
604 let mut need_refresh_table_infos = HashSet::new();
605 let mut instant_table_ids = HashSet::new();
606
607 for req in &mut requests.inserts {
608 match self.get_table(catalog, &schema, &req.table_name).await? {
609 Some(table) => {
610 let table_info = table.table_info();
611 if table_info.is_ttl_instant_table() {
612 instant_table_ids.insert(table_info.table_id());
613 }
614 if let Some(alter_expr) = self.get_alter_table_expr_on_demand(
615 req,
616 &table,
617 ctx,
618 accommodate_existing_schema,
619 is_single_value,
620 auto_create_table_type.alter_existing(),
621 )? {
622 alter_tables.push(alter_expr);
623 need_refresh_table_infos.insert((
624 catalog.to_string(),
625 schema.clone(),
626 req.table_name.clone(),
627 ));
628 } else {
629 table_infos.insert(table_info.table_id(), table.table_info());
630 }
631 }
632 None => {
633 let create_expr =
634 self.get_create_table_expr_on_demand(req, &auto_create_table_type, ctx)?;
635 create_tables.push(create_expr);
636 }
637 }
638 }
639
640 match auto_create_table_type {
641 AutoCreateTableType::Logical(_) => {
642 if !create_tables.is_empty() {
643 let tables = self
645 .create_logical_tables(create_tables, ctx, statement_executor)
646 .await?;
647
648 for table in tables {
649 let table_info = table.table_info();
650 if table_info.is_ttl_instant_table() {
651 instant_table_ids.insert(table_info.table_id());
652 }
653 table_infos.insert(table_info.table_id(), table.table_info());
654 }
655 }
656 if !alter_tables.is_empty() {
657 statement_executor
659 .alter_logical_tables(alter_tables, ctx.clone())
660 .await?;
661 }
662 }
663 AutoCreateTableType::Physical
664 | AutoCreateTableType::Log
665 | AutoCreateTableType::LastNonNull => {
666 for create_table in create_tables {
669 let table = self
670 .create_physical_table(create_table, None, ctx, statement_executor)
671 .await?;
672 let table_info = table.table_info();
673 if table_info.is_ttl_instant_table() {
674 instant_table_ids.insert(table_info.table_id());
675 }
676 table_infos.insert(table_info.table_id(), table.table_info());
677 }
678 for alter_expr in alter_tables.into_iter() {
679 statement_executor
680 .alter_table_inner(alter_expr, ctx.clone())
681 .await?;
682 }
683 }
684
685 AutoCreateTableType::Trace { .. } => {
686 let trace_table_name = ctx
687 .extension(TRACE_TABLE_NAME_SESSION_KEY)
688 .unwrap_or(TRACE_TABLE_NAME);
689
690 let trace_table_partitions = if let Some(trace_table_partitions) =
691 ctx.extension(TRACE_TABLE_PARTITIONS_HINT_KEY)
692 {
693 let p = trace_table_partitions.parse::<u32>().map_err(|_| {
694 InvalidInsertRequestSnafu {
695 reason: format!(
696 "Failed to parse trace_table_partitions: {}",
697 trace_table_partitions
698 ),
699 }
700 .build()
701 })?;
702 Some(p)
703 } else {
704 None
705 };
706
707 for mut create_table in create_tables {
710 if create_table.table_name == trace_services_table_name(trace_table_name)
711 || create_table.table_name == trace_operations_table_name(trace_table_name)
712 {
713 create_table
715 .table_options
716 .insert(APPEND_MODE_KEY.to_string(), "false".to_string());
717 create_table.table_options.remove(TTL_KEY);
719
720 let table = self
721 .create_physical_table(create_table, None, ctx, statement_executor)
722 .await?;
723 let table_info = table.table_info();
724 if table_info.is_ttl_instant_table() {
725 instant_table_ids.insert(table_info.table_id());
726 }
727 table_infos.insert(table_info.table_id(), table.table_info());
728 } else {
729 let partitions = if matches!(trace_table_partitions, Some(0) | Some(1)) {
732 None
734 } else {
735 let p = partition_rule_for_hexstring(
736 TRACE_ID_COLUMN,
737 trace_table_partitions,
738 )
739 .context(CreatePartitionRulesSnafu)?;
740 Some(p)
741 };
742
743 let index_columns =
748 [TRACE_ID_COLUMN, PARENT_SPAN_ID_COLUMN, SERVICE_NAME_COLUMN];
749 for index_column in index_columns {
750 if let Some(col) = create_table
751 .column_defs
752 .iter_mut()
753 .find(|c| c.name == index_column)
754 {
755 col.options =
756 options_from_skipping(&SkippingIndexOptions::default())
757 .context(ColumnOptionsSnafu)?;
758 } else {
759 warn!(
760 "Column {} not found when creating index for trace table: {}.",
761 index_column, create_table.table_name
762 );
763 }
764 }
765
766 create_table.table_options.insert(
768 TABLE_DATA_MODEL.to_string(),
769 TABLE_DATA_MODEL_TRACE_V1.to_string(),
770 );
771
772 let table = self
773 .create_physical_table(
774 create_table,
775 partitions,
776 ctx,
777 statement_executor,
778 )
779 .await?;
780 let table_info = table.table_info();
781 if table_info.is_ttl_instant_table() {
782 instant_table_ids.insert(table_info.table_id());
783 }
784 table_infos.insert(table_info.table_id(), table.table_info());
785 }
786 }
787 for alter_expr in alter_tables.into_iter() {
788 statement_executor
789 .alter_table_inner(alter_expr, ctx.clone())
790 .await?;
791 }
792 }
793 }
794
795 for (catalog, schema, table_name) in need_refresh_table_infos {
797 let table = self
798 .get_table(&catalog, &schema, &table_name)
799 .await?
800 .context(TableNotFoundSnafu {
801 table_name: common_catalog::format_full_table_name(
802 &catalog,
803 &schema,
804 &table_name,
805 ),
806 })?;
807 let table_info = table.table_info();
808 table_infos.insert(table_info.table_id(), table.table_info());
809 }
810
811 Ok(CreateAlterTableResult {
812 instant_table_ids,
813 table_infos,
814 })
815 }
816
817 async fn create_physical_table_on_demand(
818 &self,
819 ctx: &QueryContextRef,
820 physical_table: String,
821 statement_executor: &StatementExecutor,
822 ) -> Result<()> {
823 let catalog_name = ctx.current_catalog();
824 let schema_name = ctx.current_schema();
825
826 if self
828 .get_table(catalog_name, &schema_name, &physical_table)
829 .await?
830 .is_some()
831 {
832 return Ok(());
833 }
834
835 if let Some(disabled_reason) = self.auto_create_disabled_reason(ctx)? {
837 return InvalidInsertRequestSnafu {
838 reason: format!(
839 "Physical table `{physical_table}` does not exist, and {disabled_reason}"
840 ),
841 }
842 .fail();
843 }
844
845 let table_reference = TableReference::full(catalog_name, &schema_name, &physical_table);
846 info!("Physical metric table `{table_reference}` does not exist, try creating table");
847
848 let default_schema = vec![
850 ColumnSchema {
851 column_name: greptime_timestamp().to_string(),
852 datatype: ColumnDataType::TimestampMillisecond as _,
853 semantic_type: SemanticType::Timestamp as _,
854 datatype_extension: None,
855 options: None,
856 },
857 ColumnSchema {
858 column_name: greptime_value().to_string(),
859 datatype: ColumnDataType::Float64 as _,
860 semantic_type: SemanticType::Field as _,
861 datatype_extension: None,
862 options: None,
863 },
864 ];
865 let create_table_expr =
866 &mut build_create_table_expr(&table_reference, &default_schema, default_engine())?;
867
868 create_table_expr.engine = METRIC_ENGINE_NAME.to_string();
869 create_table_expr
870 .table_options
871 .insert(PHYSICAL_TABLE_METADATA_KEY.to_string(), "true".to_string());
872
873 let res = statement_executor
875 .create_table_inner(create_table_expr, None, ctx.clone())
876 .await;
877
878 match res {
879 Ok(_) => {
880 info!("Successfully created table {table_reference}",);
881 Ok(())
882 }
883 Err(err) => {
884 error!(err; "Failed to create table {table_reference}");
885 Err(err)
886 }
887 }
888 }
889
890 async fn get_table(
891 &self,
892 catalog: &str,
893 schema: &str,
894 table: &str,
895 ) -> Result<Option<TableRef>> {
896 self.catalog_manager
897 .table(catalog, schema, table, None)
898 .await
899 .context(CatalogSnafu)
900 }
901
902 fn get_create_table_expr_on_demand(
903 &self,
904 req: &RowInsertRequest,
905 create_type: &AutoCreateTableType,
906 ctx: &QueryContextRef,
907 ) -> Result<CreateTableExpr> {
908 let mut table_options = std::collections::HashMap::with_capacity(4);
909 fill_table_options_for_create(&mut table_options, create_type, ctx);
910 apply_per_table_semantic_options(&mut table_options, ctx, &req.table_name);
911
912 let engine_name = if let AutoCreateTableType::Logical(_) = create_type {
913 METRIC_ENGINE_NAME
915 } else {
916 default_engine()
917 };
918
919 let schema = ctx.current_schema();
920 let table_ref = TableReference::full(ctx.current_catalog(), &schema, &req.table_name);
921 let request_schema = req.rows.as_ref().unwrap().schema.as_slice();
923 let mut create_table_expr =
924 build_create_table_expr(&table_ref, request_schema, engine_name)?;
925
926 if ctx.extension(SPLUNK_PK_METADATA_ORDER_KEY).is_some() {
928 reorder_splunk_primary_keys(&mut create_table_expr.primary_keys);
929 }
930
931 info!("Table `{table_ref}` does not exist, try creating table");
932 create_table_expr.table_options.extend(table_options);
933 Ok(create_table_expr)
934 }
935
936 fn get_alter_table_expr_on_demand(
944 &self,
945 req: &mut RowInsertRequest,
946 table: &TableRef,
947 ctx: &QueryContextRef,
948 accommodate_existing_schema: bool,
949 is_single_value: bool,
950 alter_existing: bool,
951 ) -> Result<Option<AlterTableExpr>> {
952 if !alter_existing {
953 return Ok(None);
954 }
955
956 let catalog_name = ctx.current_catalog();
957 let schema_name = ctx.current_schema();
958 let table_name = table.table_info().name.clone();
959
960 let request_schema = req.rows.as_ref().unwrap().schema.as_slice();
961 let request_field_count = request_schema
962 .iter()
963 .filter(|col| col.semantic_type == SemanticType::Field as i32)
964 .count();
965 let column_exprs = ColumnExpr::from_column_schemas(request_schema);
966 let add_columns = expr_helper::extract_add_columns_expr(&table.schema(), column_exprs)?;
967 let Some(mut add_columns) = add_columns else {
968 return Ok(None);
969 };
970
971 if accommodate_existing_schema {
973 let request_is_native_histogram = request_is_native_histogram(request_schema);
974 let table_is_native_histogram = table_is_native_histogram(table);
975 ensure!(
976 request_is_native_histogram == table_is_native_histogram,
977 InvalidInsertRequestSnafu {
978 reason: format!(
979 "Table `{table_name}` cannot mix native histogram and float sample fields"
980 ),
981 }
982 );
983 let table_schema = table.schema();
984 let ts_col_name = table_schema.timestamp_column().map(|c| c.name.clone());
986 let mut field_col_name = None;
988 if is_single_value && request_field_count <= 1 {
989 let mut multiple_field_cols = false;
990 table.field_columns().for_each(|col| {
991 if field_col_name.is_none() {
992 field_col_name = Some(col.name.clone());
993 } else {
994 multiple_field_cols = true;
995 }
996 });
997 if multiple_field_cols {
998 field_col_name = None;
999 }
1000 }
1001
1002 if let Some(rows) = req.rows.as_mut() {
1004 for col in &mut rows.schema {
1005 match col.semantic_type {
1006 x if x == SemanticType::Timestamp as i32 => {
1007 if let Some(ref ts_name) = ts_col_name
1008 && col.column_name != *ts_name
1009 {
1010 col.column_name = ts_name.clone();
1011 }
1012 }
1013 x if x == SemanticType::Field as i32 => {
1014 if let Some(ref field_name) = field_col_name
1015 && col.column_name != *field_name
1016 {
1017 col.column_name = field_name.clone();
1018 }
1019 }
1020 _ => {}
1021 }
1022 }
1023 }
1024
1025 add_columns.add_columns.retain(|col| {
1027 let def = col.column_def.as_ref().unwrap();
1028 def.semantic_type == SemanticType::Tag as i32
1029 || (def.semantic_type == SemanticType::Field as i32 && field_col_name.is_none())
1030 });
1031
1032 if add_columns.add_columns.is_empty() {
1033 return Ok(None);
1034 }
1035 }
1036
1037 Ok(Some(AlterTableExpr {
1038 catalog_name: catalog_name.to_string(),
1039 schema_name: schema_name.clone(),
1040 table_name: table_name.clone(),
1041 kind: Some(Kind::AddColumns(add_columns)),
1042 }))
1043 }
1044
1045 async fn create_physical_table(
1047 &self,
1048 mut create_table_expr: CreateTableExpr,
1049 partitions: Option<Partitions>,
1050 ctx: &QueryContextRef,
1051 statement_executor: &StatementExecutor,
1052 ) -> Result<TableRef> {
1053 {
1054 let table_ref = TableReference::full(
1055 &create_table_expr.catalog_name,
1056 &create_table_expr.schema_name,
1057 &create_table_expr.table_name,
1058 );
1059
1060 info!("Table `{table_ref}` does not exist, try creating table");
1061 }
1062 let res = statement_executor
1063 .create_table_inner(&mut create_table_expr, partitions, ctx.clone())
1064 .await;
1065
1066 let table_ref = TableReference::full(
1067 &create_table_expr.catalog_name,
1068 &create_table_expr.schema_name,
1069 &create_table_expr.table_name,
1070 );
1071
1072 match res {
1073 Ok(table) => {
1074 info!(
1075 "Successfully created table {} with options: {:?}",
1076 table_ref, create_table_expr.table_options,
1077 );
1078 Ok(table)
1079 }
1080 Err(err) => {
1081 error!(err; "Failed to create table {}", table_ref);
1082 Err(err)
1083 }
1084 }
1085 }
1086
1087 async fn create_logical_tables(
1088 &self,
1089 create_table_exprs: Vec<CreateTableExpr>,
1090 ctx: &QueryContextRef,
1091 statement_executor: &StatementExecutor,
1092 ) -> Result<Vec<TableRef>> {
1093 let res = statement_executor
1094 .create_logical_tables(&create_table_exprs, ctx.clone())
1095 .await;
1096
1097 match res {
1098 Ok(res) => {
1099 info!("Successfully created logical tables");
1100 Ok(res)
1101 }
1102 Err(err) => {
1103 let failed_tables = create_table_exprs
1104 .into_iter()
1105 .map(|expr| {
1106 format!(
1107 "{}.{}.{}",
1108 expr.catalog_name, expr.schema_name, expr.table_name
1109 )
1110 })
1111 .collect::<Vec<_>>();
1112 error!(
1113 err;
1114 "Failed to create logical tables {:?}",
1115 failed_tables
1116 );
1117 Err(err)
1118 }
1119 }
1120 }
1121
1122 pub fn node_manager(&self) -> &NodeManagerRef {
1123 &self.node_manager
1124 }
1125
1126 pub fn partition_manager(&self) -> &PartitionRuleManagerRef {
1127 &self.partition_manager
1128 }
1129
1130 pub fn table_flownode_set_cache(&self) -> &TableFlownodeSetCacheRef {
1131 &self.table_flownode_set_cache
1132 }
1133}
1134
1135fn request_is_native_histogram(request_schema: &[ColumnSchema]) -> bool {
1136 let mut fields = request_schema
1137 .iter()
1138 .filter(|col| col.semantic_type == SemanticType::Field as i32);
1139 let Some(col) = fields.next() else {
1140 return false;
1141 };
1142
1143 fields.next().is_none()
1144 && api::helper::is_column_type_value_eq(
1145 col.datatype,
1146 col.datatype_extension.clone(),
1147 native_histogram_value_type(),
1148 )
1149}
1150
1151fn table_is_native_histogram(table: &TableRef) -> bool {
1152 let mut fields = table.field_columns();
1153 let Some(col) = fields.next() else {
1154 return false;
1155 };
1156
1157 fields.next().is_none() && is_native_histogram_value_type(&col.data_type)
1158}
1159
1160fn validate_column_count_match(requests: &RowInsertRequests) -> Result<()> {
1161 for request in &requests.inserts {
1162 let rows = request.rows.as_ref().unwrap();
1163 let column_count = rows.schema.len();
1164 rows.rows.iter().try_for_each(|r| {
1165 ensure!(
1166 r.values.len() == column_count,
1167 InvalidInsertRequestSnafu {
1168 reason: format!(
1169 "column count mismatch, columns: {}, values: {}",
1170 column_count,
1171 r.values.len()
1172 )
1173 }
1174 );
1175 Ok(())
1176 })?;
1177 }
1178 Ok(())
1179}
1180
1181pub fn fill_table_options_for_create(
1183 table_options: &mut std::collections::HashMap<String, String>,
1184 create_type: &AutoCreateTableType,
1185 ctx: &QueryContextRef,
1186) {
1187 for key in VALID_TABLE_OPTION_KEYS {
1188 if let Some(value) = ctx.extension(key) {
1189 table_options.insert(key.to_string(), value.to_string());
1190 }
1191 }
1192
1193 for (key, value) in ctx.extensions() {
1195 if is_semantic_option_key(&key) && validate_semantic_option(&key, &value) {
1196 table_options.insert(key, value);
1197 }
1198 }
1199
1200 match create_type {
1201 AutoCreateTableType::Logical(physical_table) => {
1202 table_options.insert(
1203 LOGICAL_TABLE_METADATA_KEY.to_string(),
1204 physical_table.clone(),
1205 );
1206 }
1207 AutoCreateTableType::Physical => {
1208 if let Some(append_mode) = ctx.extension(APPEND_MODE_KEY) {
1209 table_options.insert(APPEND_MODE_KEY.to_string(), append_mode.to_string());
1210 }
1211 if let Some(merge_mode) = ctx.extension(MERGE_MODE_KEY) {
1212 table_options.insert(MERGE_MODE_KEY.to_string(), merge_mode.to_string());
1213 }
1214 if let Some(time_window) = ctx.extension(TWCS_TIME_WINDOW) {
1215 table_options.insert(TWCS_TIME_WINDOW.to_string(), time_window.to_string());
1216 table_options.insert(
1218 COMPACTION_TYPE.to_string(),
1219 COMPACTION_TYPE_TWCS.to_string(),
1220 );
1221 }
1222 }
1223 AutoCreateTableType::Log => {
1226 table_options.insert(APPEND_MODE_KEY.to_string(), "true".to_string());
1227 }
1228 AutoCreateTableType::LastNonNull => {
1229 if ctx
1230 .extension(APPEND_MODE_KEY)
1231 .is_some_and(|value| value.eq_ignore_ascii_case("true"))
1232 {
1233 table_options.insert(APPEND_MODE_KEY.to_string(), "true".to_string());
1234 table_options.insert(MERGE_MODE_KEY.to_string(), "last_row".to_string());
1235 } else if let Some(merge_mode) = ctx.extension(MERGE_MODE_KEY) {
1236 table_options.insert(MERGE_MODE_KEY.to_string(), merge_mode.to_string());
1237 } else {
1238 table_options.insert(MERGE_MODE_KEY.to_string(), "last_non_null".to_string());
1239 }
1240 }
1241 AutoCreateTableType::Trace { .. } => {
1242 table_options.insert(APPEND_MODE_KEY.to_string(), "true".to_string());
1243 }
1244 }
1245}
1246
1247fn apply_per_table_semantic_options(
1259 table_options: &mut std::collections::HashMap<String, String>,
1260 ctx: &QueryContextRef,
1261 table_name: &str,
1262) {
1263 let Some(raw) = ctx.extension(SEMANTIC_PER_TABLE_INDEX_KEY) else {
1264 return;
1265 };
1266 let Ok(index) = serde_json::from_str::<
1267 std::collections::BTreeMap<String, std::collections::BTreeMap<String, String>>,
1268 >(raw) else {
1269 warn!("failed to parse semantic per-table index, skipping per-table options");
1270 return;
1271 };
1272 let Some(entry) = index.get(table_name) else {
1273 return;
1274 };
1275 for (key, value) in entry {
1276 if is_semantic_option_key(key) && validate_semantic_option(key, value) {
1277 table_options.insert(key.clone(), value.clone());
1278 }
1279 }
1280}
1281
1282pub fn build_create_table_expr(
1283 table: &TableReference,
1284 request_schema: &[ColumnSchema],
1285 engine: &str,
1286) -> Result<CreateTableExpr> {
1287 expr_helper::create_table_expr_by_column_schemas(table, request_schema, engine, None)
1288}
1289
1290pub const SPLUNK_PK_METADATA_ORDER_KEY: &str = "splunk_pk_metadata_order";
1294
1295fn reorder_splunk_primary_keys(primary_keys: &mut [String]) {
1298 const LEAD: [&str; 3] = ["host", "source", "sourcetype"];
1299 primary_keys.sort_by_key(|name| {
1302 LEAD.iter()
1303 .position(|&lead| lead == name.as_str())
1304 .unwrap_or(LEAD.len())
1305 });
1306}
1307
1308struct CreateAlterTableResult {
1310 instant_table_ids: HashSet<TableId>,
1312 table_infos: HashMap<TableId, Arc<TableInfo>>,
1314}
1315
1316struct FlowMirrorTask {
1317 requests: HashMap<Peer, RegionInsertRequests>,
1318 num_rows: usize,
1319}
1320
1321impl FlowMirrorTask {
1322 async fn new(
1323 cache: &TableFlownodeSetCacheRef,
1324 requests: impl Iterator<Item = &RegionInsertRequest>,
1325 ) -> Result<Self> {
1326 let mut src_table_reqs: HashMap<TableId, Option<(Vec<Peer>, RegionInsertRequests)>> =
1327 HashMap::new();
1328 let mut num_rows = 0;
1329
1330 for req in requests {
1331 let table_id = RegionId::from_u64(req.region_id).table_id();
1332 match src_table_reqs.get_mut(&table_id) {
1333 Some(Some((_peers, reqs))) => reqs.requests.push(req.clone()),
1334 Some(None) => continue,
1336 _ => {
1337 let peers = cache
1339 .get(table_id)
1340 .await
1341 .context(RequestInsertsSnafu)?
1342 .unwrap_or_default()
1343 .values()
1344 .cloned()
1345 .collect::<HashSet<_>>()
1346 .into_iter()
1347 .collect::<Vec<_>>();
1348
1349 if !peers.is_empty() {
1350 let mut reqs = RegionInsertRequests::default();
1351 reqs.requests.push(req.clone());
1352 num_rows += reqs
1353 .requests
1354 .iter()
1355 .map(|r| r.rows.as_ref().unwrap().rows.len())
1356 .sum::<usize>();
1357 src_table_reqs.insert(table_id, Some((peers, reqs)));
1358 } else {
1359 src_table_reqs.insert(table_id, None);
1361 }
1362 }
1363 }
1364 }
1365
1366 let mut inserts: HashMap<Peer, RegionInsertRequests> = HashMap::new();
1367
1368 for (_table_id, (peers, reqs)) in src_table_reqs
1369 .into_iter()
1370 .filter_map(|(k, v)| v.map(|v| (k, v)))
1371 {
1372 if peers.len() == 1 {
1373 inserts
1375 .entry(peers[0].clone())
1376 .or_default()
1377 .requests
1378 .extend(reqs.requests);
1379 continue;
1380 } else {
1381 for flownode in peers {
1383 inserts
1384 .entry(flownode.clone())
1385 .or_default()
1386 .requests
1387 .extend(reqs.requests.clone());
1388 }
1389 }
1390 }
1391
1392 Ok(Self {
1393 requests: inserts,
1394 num_rows,
1395 })
1396 }
1397
1398 fn detach(self, node_manager: NodeManagerRef) -> Result<()> {
1399 crate::metrics::DIST_MIRROR_PENDING_ROW_COUNT.add(self.num_rows as i64);
1400 for (peer, inserts) in self.requests {
1401 let node_manager = node_manager.clone();
1402 common_runtime::spawn_global(async move {
1403 let result = node_manager
1404 .flownode(&peer)
1405 .await
1406 .handle_inserts(inserts)
1407 .await
1408 .context(RequestInsertsSnafu);
1409
1410 match result {
1411 Ok(resp) => {
1412 let affected_rows = resp.affected_rows;
1413 crate::metrics::DIST_MIRROR_ROW_COUNT.inc_by(affected_rows);
1414 crate::metrics::DIST_MIRROR_PENDING_ROW_COUNT.sub(affected_rows as _);
1415 }
1416 Err(err) => {
1417 error!(err; "Failed to insert data into flownode {}", peer);
1418 }
1419 }
1420 });
1421 }
1422
1423 Ok(())
1424 }
1425}
1426
1427#[cfg(test)]
1428mod tests {
1429 use std::sync::Arc;
1430
1431 use api::helper::ColumnDataTypeWrapper;
1432 use api::v1::helper::{field_column_schema, time_index_column_schema};
1433 use api::v1::{RowInsertRequest, Rows, Value};
1434 use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
1435 use common_meta::cache::new_table_flownode_set_cache;
1436 use common_meta::ddl::test_util::datanode_handler::NaiveDatanodeHandler;
1437 use common_meta::test_util::MockDatanodeManager;
1438 use common_query::native_histogram::NATIVE_HISTOGRAM_FIELD;
1439 use common_query::prelude::{greptime_native_histogram, set_default_prefix};
1440 use datatypes::data_type::ConcreteDataType;
1441 use datatypes::schema::ColumnSchema;
1442 use moka::future::Cache;
1443 use session::context::QueryContext;
1444 use table::TableRef;
1445 use table::dist_table::DummyDataSource;
1446 use table::metadata::{TableInfoBuilder, TableMetaBuilder, TableType};
1447
1448 use super::*;
1449 use crate::tests::{create_partition_rule_manager, prepare_mocked_backend};
1450
1451 fn make_table_ref_with_schema(
1452 ts_name: &str,
1453 field_name: &str,
1454 field_type: ConcreteDataType,
1455 ) -> TableRef {
1456 let schema = datatypes::schema::SchemaBuilder::try_from_columns(vec![
1457 ColumnSchema::new(
1458 ts_name,
1459 ConcreteDataType::timestamp_millisecond_datatype(),
1460 false,
1461 )
1462 .with_time_index(true),
1463 ColumnSchema::new(field_name, field_type, true),
1464 ])
1465 .unwrap()
1466 .build()
1467 .unwrap();
1468 let meta = TableMetaBuilder::empty()
1469 .schema(Arc::new(schema))
1470 .primary_key_indices(vec![])
1471 .value_indices(vec![1])
1472 .engine("mito")
1473 .next_column_id(0)
1474 .options(Default::default())
1475 .created_on(Default::default())
1476 .build()
1477 .unwrap();
1478 let info = Arc::new(
1479 TableInfoBuilder::default()
1480 .table_id(1)
1481 .table_version(0)
1482 .name("test_table")
1483 .schema_name(DEFAULT_SCHEMA_NAME)
1484 .catalog_name(DEFAULT_CATALOG_NAME)
1485 .desc(None)
1486 .table_type(TableType::Base)
1487 .meta(meta)
1488 .build()
1489 .unwrap(),
1490 );
1491 Arc::new(table::Table::new(
1492 info,
1493 table::metadata::FilterPushDownType::Unsupported,
1494 Arc::new(DummyDataSource),
1495 ))
1496 }
1497
1498 #[tokio::test]
1499 async fn test_accommodate_existing_schema_logic() {
1500 let ts_name = "my_ts";
1501 let field_name = "my_field";
1502 let table =
1503 make_table_ref_with_schema(ts_name, field_name, ConcreteDataType::float64_datatype());
1504
1505 let mut req = RowInsertRequest {
1507 table_name: "test_table".to_string(),
1508 rows: Some(Rows {
1509 schema: vec![
1510 time_index_column_schema("ts_wrong", ColumnDataType::TimestampMillisecond),
1511 field_column_schema("field_wrong", ColumnDataType::Float64),
1512 ],
1513 rows: vec![api::v1::Row {
1514 values: vec![Value::default(), Value::default()],
1515 }],
1516 }),
1517 };
1518 let ctx = Arc::new(QueryContext::with(
1519 DEFAULT_CATALOG_NAME,
1520 DEFAULT_SCHEMA_NAME,
1521 ));
1522
1523 let kv_backend = prepare_mocked_backend().await;
1524 let inserter = Inserter::new(
1525 catalog::memory::MemoryCatalogManager::new(),
1526 create_partition_rule_manager(kv_backend.clone()).await,
1527 Arc::new(MockDatanodeManager::new(NaiveDatanodeHandler)),
1528 Arc::new(new_table_flownode_set_cache(
1529 String::new(),
1530 Cache::new(100),
1531 kv_backend.clone(),
1532 )),
1533 true,
1534 );
1535 assert!(
1537 inserter
1538 .get_alter_table_expr_on_demand(&mut req, &table, &ctx, false, false, false)
1539 .unwrap()
1540 .is_none()
1541 );
1542 let alter_expr = inserter
1543 .get_alter_table_expr_on_demand(&mut req, &table, &ctx, true, true, true)
1544 .unwrap();
1545 assert!(alter_expr.is_none());
1546
1547 let req_schema = req.rows.as_ref().unwrap().schema.clone();
1549 assert_eq!(req_schema[0].column_name, ts_name);
1550 assert_eq!(req_schema[1].column_name, field_name);
1551 }
1552
1553 #[test]
1554 fn test_native_histogram_detection_survives_prefix_change() {
1555 set_default_prefix(Some("custom")).unwrap();
1556 let table = make_table_ref_with_schema(
1557 "custom_timestamp",
1558 NATIVE_HISTOGRAM_FIELD,
1559 native_histogram_value_type().clone(),
1560 );
1561 let (datatype, datatype_extension) =
1562 ColumnDataTypeWrapper::try_from(native_histogram_value_type().clone())
1563 .unwrap()
1564 .into_parts();
1565 let request_schema = [api::v1::ColumnSchema {
1566 column_name: greptime_native_histogram().to_string(),
1567 datatype: datatype as i32,
1568 semantic_type: SemanticType::Field as i32,
1569 datatype_extension,
1570 options: None,
1571 }];
1572
1573 assert!(request_is_native_histogram(&request_schema));
1574 assert!(table_is_native_histogram(&table));
1575 }
1576
1577 #[test]
1578 fn test_last_non_null_create_options_preserve_default_without_append_mode() {
1579 let ctx = Arc::new(QueryContext::with(
1580 DEFAULT_CATALOG_NAME,
1581 DEFAULT_SCHEMA_NAME,
1582 ));
1583 let mut table_options = Default::default();
1584
1585 fill_table_options_for_create(&mut table_options, &AutoCreateTableType::LastNonNull, &ctx);
1586
1587 assert_eq!(
1588 Some("last_non_null"),
1589 table_options.get(MERGE_MODE_KEY).map(String::as_str)
1590 );
1591 assert!(!table_options.contains_key(APPEND_MODE_KEY));
1592 }
1593
1594 #[test]
1595 fn test_fill_table_options_copies_semantic_extensions() {
1596 use table::requests::{
1597 SEMANTIC_METRIC_TYPE, SEMANTIC_PER_TABLE_INDEX_KEY, SEMANTIC_SIGNAL_TYPE,
1598 SEMANTIC_SOURCE, SEMANTIC_SOURCE_VERSION, SIGNAL_TYPE_METRIC, SOURCE_OPENTELEMETRY,
1599 };
1600
1601 let mut ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
1602 ctx.set_extension(SEMANTIC_SIGNAL_TYPE, SIGNAL_TYPE_METRIC);
1603 ctx.set_extension(SEMANTIC_SOURCE, SOURCE_OPENTELEMETRY);
1604 ctx.set_extension(SEMANTIC_SOURCE_VERSION, "2.0");
1605 ctx.set_extension(SEMANTIC_METRIC_TYPE, "bogus");
1606 ctx.set_extension(SEMANTIC_PER_TABLE_INDEX_KEY, "{}");
1608 let ctx = Arc::new(ctx);
1609 let mut table_options = Default::default();
1610
1611 fill_table_options_for_create(&mut table_options, &AutoCreateTableType::Physical, &ctx);
1612
1613 assert_eq!(
1614 Some(SIGNAL_TYPE_METRIC),
1615 table_options.get(SEMANTIC_SIGNAL_TYPE).map(String::as_str)
1616 );
1617 assert_eq!(
1618 Some(SOURCE_OPENTELEMETRY),
1619 table_options.get(SEMANTIC_SOURCE).map(String::as_str)
1620 );
1621 assert_eq!(
1622 Some("2.0"),
1623 table_options
1624 .get(SEMANTIC_SOURCE_VERSION)
1625 .map(String::as_str)
1626 );
1627 assert!(!table_options.contains_key(SEMANTIC_METRIC_TYPE));
1628 assert!(!table_options.contains_key(SEMANTIC_PER_TABLE_INDEX_KEY));
1629 }
1630
1631 #[test]
1632 fn test_apply_per_table_semantic_options() {
1633 use table::requests::{
1634 SEMANTIC_METRIC_TYPE, SEMANTIC_METRIC_UNIT, SEMANTIC_PER_TABLE_INDEX_KEY,
1635 };
1636
1637 let index = r#"{
1638 "http_requests_total": {
1639 "greptime.semantic.metric.type": "counter",
1640 "greptime.semantic.metric.unit": "By",
1641 "greptime.semantic.metric.type_BOGUS": "x"
1642 },
1643 "other_table": {
1644 "greptime.semantic.metric.type": "gauge"
1645 }
1646 }"#;
1647 let mut ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
1648 ctx.set_extension(SEMANTIC_PER_TABLE_INDEX_KEY, index);
1649 let ctx = Arc::new(ctx);
1650
1651 let mut table_options = std::collections::HashMap::new();
1652 apply_per_table_semantic_options(&mut table_options, &ctx, "http_requests_total");
1653 assert_eq!(
1654 table_options.get(SEMANTIC_METRIC_TYPE).map(String::as_str),
1655 Some("counter")
1656 );
1657 assert_eq!(
1658 table_options.get(SEMANTIC_METRIC_UNIT).map(String::as_str),
1659 Some("By")
1660 );
1661 assert!(!table_options.contains_key("greptime.semantic.metric.type_BOGUS"));
1664 assert_eq!(table_options.len(), 2);
1665
1666 let mut empty = std::collections::HashMap::new();
1667 apply_per_table_semantic_options(&mut empty, &ctx, "not_in_index");
1668 assert!(empty.is_empty());
1669
1670 let bare = Arc::new(QueryContext::with(
1672 DEFAULT_CATALOG_NAME,
1673 DEFAULT_SCHEMA_NAME,
1674 ));
1675 let mut opts = std::collections::HashMap::new();
1676 apply_per_table_semantic_options(&mut opts, &bare, "http_requests_total");
1677 assert!(opts.is_empty());
1678 }
1679
1680 #[test]
1681 fn test_last_non_null_create_options_preserve_default_with_append_mode_false() {
1682 let mut ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
1683 ctx.set_extension(APPEND_MODE_KEY, "false");
1684 let ctx = Arc::new(ctx);
1685 let mut table_options = Default::default();
1686
1687 fill_table_options_for_create(&mut table_options, &AutoCreateTableType::LastNonNull, &ctx);
1688
1689 assert!(!table_options.contains_key(APPEND_MODE_KEY));
1690 assert_eq!(
1691 Some("last_non_null"),
1692 table_options.get(MERGE_MODE_KEY).map(String::as_str)
1693 );
1694 }
1695
1696 #[test]
1697 fn test_last_non_null_create_options_use_configured_merge_mode() {
1698 let mut ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
1699 ctx.set_extension(MERGE_MODE_KEY, "last_row");
1700 let ctx = Arc::new(ctx);
1701 let mut table_options = Default::default();
1702
1703 fill_table_options_for_create(&mut table_options, &AutoCreateTableType::LastNonNull, &ctx);
1704
1705 assert_eq!(
1706 Some("last_row"),
1707 table_options.get(MERGE_MODE_KEY).map(String::as_str)
1708 );
1709 assert!(!table_options.contains_key(APPEND_MODE_KEY));
1710 }
1711
1712 #[test]
1713 fn test_last_non_null_create_options_use_last_row_with_append_mode_true() {
1714 let mut ctx = QueryContext::with(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
1715 ctx.set_extension(APPEND_MODE_KEY, "true");
1716 let ctx = Arc::new(ctx);
1717 let mut table_options = Default::default();
1718
1719 fill_table_options_for_create(&mut table_options, &AutoCreateTableType::LastNonNull, &ctx);
1720
1721 assert_eq!(
1722 Some("true"),
1723 table_options.get(APPEND_MODE_KEY).map(String::as_str)
1724 );
1725 assert_eq!(
1726 Some("last_row"),
1727 table_options.get(MERGE_MODE_KEY).map(String::as_str)
1728 );
1729 }
1730}