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