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