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