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