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