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