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