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