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