Skip to main content

operator/
insert.rs

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