Skip to main content

frontend/instance/
prom_store.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::HashMap;
16use std::sync::Arc;
17
18use api::prom_store::remote::read_request::ResponseType;
19use api::prom_store::remote::{Query, QueryResult, ReadRequest, ReadResponse};
20use api::v1::alter_table_expr::Kind;
21use api::v1::{
22    AddColumn, AddColumns, AlterTableExpr, ColumnDataType, ColumnDef, CreateTableExpr,
23    RowInsertRequests, SemanticType,
24};
25use async_trait::async_trait;
26use auth::{
27    PROM_STORE_READ, PROM_STORE_WRITE, PermissionChecker, PermissionCheckerRef, PermissionReq,
28    PermissionTableTarget, PermissionTableTargets,
29};
30use client::OutputData;
31use common_catalog::{format_full_table_name, parse_optional_catalog_and_schema_from_db_string};
32use common_error::ext::BoxedError;
33use common_meta::rpc::ddl::TriggerReason;
34use common_query::Output;
35use common_query::prelude::{GREPTIME_PHYSICAL_TABLE, greptime_value};
36use common_recordbatch::RecordBatches;
37use common_telemetry::{debug, tracing};
38use datatypes::data_type::ConcreteDataType;
39use datatypes::schema::Schema;
40use operator::insert::{
41    AutoCreateTableType, InserterRef, PerTableSemanticIndex, apply_per_table_semantic_options,
42    build_create_table_expr, fill_table_options_for_create, parse_per_table_semantic_index,
43};
44use operator::statement::StatementExecutor;
45use prost::Message;
46use query::query_engine::options::{QueryOptions, validate_catalog_and_schema};
47use servers::batcher::logical_table::PendingRowsSchemaAlterer;
48use servers::error::{self, AuthSnafu, Result as ServerResult};
49use servers::http::header::{CONTENT_ENCODING_SNAPPY, CONTENT_TYPE_PROTOBUF, collect_plan_metrics};
50use servers::http::prom_store::PHYSICAL_TABLE_PARAM;
51use servers::interceptor::{PromStoreProtocolInterceptor, PromStoreProtocolInterceptorRef};
52use servers::prom_store;
53use servers::query_handler::{
54    PromStoreProtocolHandler, PromStoreProtocolHandlerRef, PromStoreResponse,
55};
56use session::context::QueryContextRef;
57use snafu::{OptionExt, ResultExt, ensure};
58use store_api::metric_engine_consts::{METRIC_ENGINE_NAME, PHYSICAL_TABLE_METADATA_KEY};
59use store_api::mito_engine_options::SST_FORMAT_KEY;
60use table::TableRef;
61use table::table_reference::TableReference;
62use tracing::instrument;
63
64use crate::error::{
65    AmbiguousValueColumnSnafu, CatalogSnafu, ColumnNotFoundSnafu, ExecLogicalPlanSnafu,
66    InvalidPromRemoteReadSchemaSnafu, PromStoreRemoteQueryPlanSnafu, ReadTableSnafu, Result,
67};
68use crate::instance::Instance;
69
70const SAMPLES_RESPONSE_TYPE: i32 = ResponseType::Samples as i32;
71
72struct RemoteQueryOutput {
73    table_name: String,
74    timestamp_column_name: String,
75    value_column_name: String,
76    output: Output,
77}
78
79fn auto_create_table_type_for_prom_remote_write(
80    ctx: &QueryContextRef,
81    with_metric_engine: bool,
82) -> AutoCreateTableType {
83    if with_metric_engine {
84        let physical_table = ctx
85            .extension(PHYSICAL_TABLE_PARAM)
86            .unwrap_or(GREPTIME_PHYSICAL_TABLE)
87            .to_string();
88        AutoCreateTableType::Logical(physical_table)
89    } else {
90        AutoCreateTableType::Physical
91    }
92}
93
94fn required_physical_table_for_create_type(create_type: &AutoCreateTableType) -> Option<&str> {
95    match create_type {
96        AutoCreateTableType::Logical(physical_table) => Some(physical_table.as_str()),
97        _ => None,
98    }
99}
100
101fn fill_metric_physical_table_options(table_options: &mut HashMap<String, String>) {
102    // We always enforce flat format in this ingestion path.
103    table_options.insert(SST_FORMAT_KEY.to_string(), "flat".to_string());
104    table_options.insert(PHYSICAL_TABLE_METADATA_KEY.to_string(), "true".to_string());
105}
106
107#[inline]
108fn is_supported(response_type: i32) -> bool {
109    // Only supports samples response right now
110    response_type == SAMPLES_RESPONSE_TYPE
111}
112
113/// Negotiating the content type of the remote read response.
114///
115/// Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
116/// implemented by server, error is returned.
117/// For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
118fn negotiate_response_type(accepted_response_types: &[i32]) -> ServerResult<ResponseType> {
119    if accepted_response_types.is_empty() {
120        return Ok(ResponseType::Samples);
121    }
122
123    let response_type = accepted_response_types
124        .iter()
125        .find(|t| is_supported(**t))
126        .with_context(|| error::NotSupportedSnafu {
127            feat: format!(
128                "server does not support any of the requested response types: {accepted_response_types:?}",
129            ),
130        })?;
131
132    // It's safe to unwrap here, we known that it should be SAMPLES_RESPONSE_TYPE
133    Ok(ResponseType::try_from(*response_type).unwrap())
134}
135
136fn resolve_remote_query_target(
137    ctx: &QueryContextRef,
138    query: &Query,
139    table_name: &str,
140) -> PermissionTableTarget {
141    match prom_store::extract_schema_from_query(query) {
142        Some(database) => {
143            let (catalog, schema) = parse_optional_catalog_and_schema_from_db_string(&database);
144            PermissionTableTarget::new(
145                catalog.unwrap_or_else(|| ctx.current_catalog().to_string()),
146                schema,
147                table_name,
148            )
149        }
150        None => PermissionTableTarget::new(ctx.current_catalog(), ctx.current_schema(), table_name),
151    }
152}
153
154#[instrument(skip_all, fields(table_name))]
155async fn to_query_result(
156    table_name: &str,
157    timestamp_column_name: &str,
158    value_column_name: &str,
159    output: Output,
160) -> ServerResult<QueryResult> {
161    let OutputData::Stream(stream) = output.data else {
162        unreachable!()
163    };
164    let recordbatches = RecordBatches::try_collect(stream)
165        .await
166        .context(error::CollectRecordbatchSnafu)?;
167    Ok(QueryResult {
168        timeseries: prom_store::recordbatches_to_timeseries(
169            table_name,
170            timestamp_column_name,
171            value_column_name,
172            recordbatches,
173        )?,
174    })
175}
176
177fn resolve_value_column_name(table_name: &str, table: &TableRef) -> Result<String> {
178    let columns = table
179        .field_columns()
180        .map(|column| column.name)
181        .collect::<Vec<_>>();
182
183    match columns.as_slice() {
184        [] => ColumnNotFoundSnafu {
185            msg: format!("value field in table '{table_name}'"),
186        }
187        .fail(),
188
189        [only] => Ok(only.clone()),
190
191        columns if columns.iter().any(|name| name == greptime_value()) => {
192            Ok(greptime_value().to_string())
193        }
194
195        columns => AmbiguousValueColumnSnafu {
196            table_name: table_name.to_string(),
197            field_columns: columns.to_vec(),
198        }
199        .fail(),
200    }
201}
202
203fn ensure_string_label_columns(
204    schema: &Schema,
205    timestamp_column_name: &str,
206    value_column_name: &str,
207) -> Result<()> {
208    for column in schema.column_schemas() {
209        if column.name == timestamp_column_name || column.name == value_column_name {
210            continue;
211        }
212        // All remaining columns are emitted as labels, including non-key columns.
213        ensure!(
214            matches!(column.data_type, ConcreteDataType::String(_)),
215            InvalidPromRemoteReadSchemaSnafu {
216                detail: format!(
217                    "Prometheus remote read only supports string labels: column '{}' has type {:?}",
218                    column.name, column.data_type
219                ),
220            }
221        );
222    }
223    Ok(())
224}
225
226impl Instance {
227    #[tracing::instrument(skip_all)]
228    async fn handle_remote_query(
229        &self,
230        ctx: &QueryContextRef,
231        catalog_name: &str,
232        schema_name: &str,
233        table_name: &str,
234        query: &Query,
235    ) -> Result<Option<RemoteQueryOutput>> {
236        let Some(table) = self
237            .catalog_manager
238            .table(catalog_name, schema_name, table_name, Some(ctx))
239            .await
240            .context(CatalogSnafu)?
241        else {
242            debug!(
243                catalog = catalog_name,
244                schema = schema_name,
245                table = table_name,
246                "Table not found during remote read, returning empty result"
247            );
248            return Ok(None);
249        };
250
251        let timestamp_column_name = table
252            .schema()
253            .timestamp_column()
254            .with_context(|| ColumnNotFoundSnafu {
255                msg: format!("time index in table '{table_name}'"),
256            })?
257            .name
258            .clone();
259
260        let value_column_name = resolve_value_column_name(table_name, &table)?;
261
262        ensure_string_label_columns(&table.schema(), &timestamp_column_name, &value_column_name)?;
263
264        let dataframe = self
265            .query_engine
266            .read_table(table)
267            .with_context(|_| ReadTableSnafu {
268                table_name: format_full_table_name(catalog_name, schema_name, table_name),
269            })?;
270
271        let logical_plan = prom_store::query_to_plan(dataframe, query, &timestamp_column_name)
272            .context(PromStoreRemoteQueryPlanSnafu)?;
273
274        debug!(
275            "Prometheus remote read, table: {}, logical plan: {}",
276            table_name,
277            logical_plan.display_indent(),
278        );
279
280        let output = self
281            .query_engine
282            .execute(logical_plan, ctx.clone())
283            .await
284            .context(ExecLogicalPlanSnafu)?;
285
286        Ok(Some(RemoteQueryOutput {
287            table_name: table_name.to_string(),
288            timestamp_column_name,
289            value_column_name,
290            output,
291        }))
292    }
293
294    #[tracing::instrument(skip_all)]
295    async fn handle_remote_queries(
296        &self,
297        ctx: QueryContextRef,
298        queries: &[Query],
299        query_targets: &[PermissionTableTarget],
300    ) -> ServerResult<Vec<Option<RemoteQueryOutput>>> {
301        let mut results = Vec::with_capacity(queries.len());
302
303        for (query, target) in queries.iter().zip(query_targets) {
304            let result = self
305                .handle_remote_query(&ctx, &target.catalog, &target.schema, &target.table, query)
306                .await
307                .map_err(BoxedError::new)
308                .context(error::ExecuteQuerySnafu)?;
309
310            results.push(result);
311        }
312        Ok(results)
313    }
314}
315
316#[async_trait]
317impl PendingRowsSchemaAlterer for Instance {
318    async fn create_tables_if_missing_batch(
319        &self,
320        catalog: &str,
321        schema: &str,
322        tables: &[(&str, &[api::v1::ColumnSchema])],
323        with_metric_engine: bool,
324        ctx: QueryContextRef,
325    ) -> ServerResult<()> {
326        if tables.is_empty() {
327            return Ok(());
328        }
329
330        let create_type = auto_create_table_type_for_prom_remote_write(&ctx, with_metric_engine);
331        if let Some(physical_table) = required_physical_table_for_create_type(&create_type) {
332            self.create_metric_physical_table_if_missing(
333                catalog,
334                schema,
335                physical_table,
336                ctx.clone(),
337            )
338            .await?;
339        }
340
341        let engine = if matches!(create_type, AutoCreateTableType::Logical(_)) {
342            METRIC_ENGINE_NAME
343        } else {
344            common_catalog::consts::default_engine()
345        };
346
347        // Check which tables actually still need to be created (may have been
348        // concurrently created by another request).
349        let mut create_exprs: Vec<CreateTableExpr> = Vec::with_capacity(tables.len());
350        let mut per_table_semantics: Option<Option<PerTableSemanticIndex>> = None;
351        for &(table_name, request_schema) in tables {
352            let existing = self
353                .catalog_manager()
354                .table(catalog, schema, table_name, Some(ctx.as_ref()))
355                .await
356                .map_err(BoxedError::new)
357                .context(error::ExecuteGrpcQuerySnafu)?;
358            if existing.is_some() {
359                continue;
360            }
361
362            let table_ref = TableReference::full(catalog, schema, table_name);
363            let mut create_table_expr = build_create_table_expr(&table_ref, request_schema, engine)
364                .map_err(BoxedError::new)
365                .context(error::ExecuteGrpcQuerySnafu)?;
366
367            let mut table_options = std::collections::HashMap::with_capacity(4);
368            fill_table_options_for_create(&mut table_options, &create_type, &ctx);
369            // The batched create path bypasses the operator's auto-create, so
370            // fold the per-table semantic index in here too.
371            let semantic_index = per_table_semantics
372                .get_or_insert_with(|| parse_per_table_semantic_index(&ctx))
373                .as_ref();
374            apply_per_table_semantic_options(
375                &mut table_options,
376                semantic_index,
377                schema,
378                table_name,
379            );
380            create_table_expr.table_options.extend(table_options);
381            create_exprs.push(create_table_expr);
382        }
383
384        if create_exprs.is_empty() {
385            return Ok(());
386        }
387
388        match create_type {
389            AutoCreateTableType::Logical(_) => {
390                // Use the batch API for logical tables.
391                self.statement_executor
392                    .create_logical_tables(&create_exprs, ctx, TriggerReason::AutoCreate)
393                    .await
394                    .map_err(BoxedError::new)
395                    .context(error::ExecuteGrpcQuerySnafu)?;
396            }
397            AutoCreateTableType::Physical => {
398                // Physical tables don't have a batch DDL path; create one at a time.
399                for mut expr in create_exprs {
400                    expr.table_options
401                        .insert(SST_FORMAT_KEY.to_string(), "flat".to_string());
402                    self.statement_executor
403                        .create_table_inner(&mut expr, None, ctx.clone(), TriggerReason::AutoCreate)
404                        .await
405                        .map_err(BoxedError::new)
406                        .context(error::ExecuteGrpcQuerySnafu)?;
407                }
408            }
409            create_type => {
410                return error::InvalidPromRemoteRequestSnafu {
411                    msg: format!(
412                        "prom remote write only supports logical or physical auto-create: {}",
413                        create_type.as_str()
414                    ),
415                }
416                .fail();
417            }
418        }
419
420        Ok(())
421    }
422
423    async fn add_missing_prom_tag_columns_batch(
424        &self,
425        catalog: &str,
426        schema: &str,
427        tables: &[(&str, &[String])],
428        ctx: QueryContextRef,
429    ) -> ServerResult<()> {
430        if tables.is_empty() {
431            return Ok(());
432        }
433
434        let alter_exprs: Vec<AlterTableExpr> = tables
435            .iter()
436            .filter(|(_, columns)| !columns.is_empty())
437            .map(|&(table_name, columns)| {
438                let add_columns = AddColumns {
439                    add_columns: columns
440                        .iter()
441                        .map(|column_name| AddColumn {
442                            column_def: Some(ColumnDef {
443                                name: column_name.clone(),
444                                data_type: ColumnDataType::String as i32,
445                                is_nullable: true,
446                                semantic_type: SemanticType::Tag as i32,
447                                comment: String::new(),
448                                ..Default::default()
449                            }),
450                            location: None,
451                            add_if_not_exists: true,
452                        })
453                        .collect(),
454                };
455
456                AlterTableExpr {
457                    catalog_name: catalog.to_string(),
458                    schema_name: schema.to_string(),
459                    table_name: table_name.to_string(),
460                    kind: Some(Kind::AddColumns(add_columns)),
461                }
462            })
463            .collect();
464
465        if alter_exprs.is_empty() {
466            return Ok(());
467        }
468
469        self.statement_executor
470            .alter_logical_tables(alter_exprs, ctx, TriggerReason::AutoAlter)
471            .await
472            .map_err(BoxedError::new)
473            .context(error::ExecuteGrpcQuerySnafu)?;
474
475        Ok(())
476    }
477}
478
479impl Instance {
480    async fn prepare_prom_store_write(
481        &self,
482        request: RowInsertRequests,
483        ctx: QueryContextRef,
484    ) -> ServerResult<(RowInsertRequests, QueryContextRef)> {
485        PromStoreProtocolHandler::pre_write(self, &request, ctx.clone()).await?;
486        Ok((request, Arc::new(ctx.fork())))
487    }
488
489    async fn execute_prom_store_write(
490        &self,
491        request: RowInsertRequests,
492        ctx: QueryContextRef,
493        with_metric_engine: bool,
494    ) -> ServerResult<Output> {
495        let output = if with_metric_engine {
496            let physical_table = ctx
497                .extension(PHYSICAL_TABLE_PARAM)
498                .unwrap_or(GREPTIME_PHYSICAL_TABLE)
499                .to_string();
500            self.handle_metric_row_inserts(request, ctx.clone(), physical_table.clone())
501                .await
502                .map_err(BoxedError::new)
503                .context(error::ExecuteGrpcQuerySnafu)?
504        } else {
505            self.handle_row_inserts(request, ctx.clone(), true, true)
506                .await
507                .map_err(BoxedError::new)
508                .context(error::ExecuteGrpcQuerySnafu)?
509        };
510
511        Ok(output)
512    }
513}
514
515#[async_trait]
516impl PromStoreProtocolHandler for Instance {
517    async fn prepare_metric_write(
518        &self,
519        request: RowInsertRequests,
520        ctx: QueryContextRef,
521    ) -> ServerResult<operator::insert::PreparedMetricInserts> {
522        let physical_table = ctx
523            .extension(PHYSICAL_TABLE_PARAM)
524            .unwrap_or(GREPTIME_PHYSICAL_TABLE)
525            .to_string();
526        self.inserter
527            .prepare_metric_row_inserts(request, ctx, &self.statement_executor, physical_table)
528            .await
529            .map_err(BoxedError::new)
530            .context(error::ExecuteGrpcQuerySnafu)
531    }
532
533    async fn write_prepared_metric(
534        &self,
535        request: operator::insert::PreparedMetricInserts,
536        ctx: QueryContextRef,
537    ) -> ServerResult<Output> {
538        self.inserter
539            .handle_prepared_metric_inserts(request, ctx)
540            .await
541            .map_err(BoxedError::new)
542            .context(error::ExecuteGrpcQuerySnafu)
543    }
544
545    async fn check_exemplar_sources(
546        &self,
547        sources: &std::collections::BTreeSet<String>,
548        ctx: QueryContextRef,
549    ) -> ServerResult<()> {
550        let targets = sources
551            .iter()
552            .map(|name| {
553                PermissionTableTarget::new(ctx.current_catalog(), ctx.current_schema(), name)
554            })
555            .collect();
556        self.check_table_permission(
557            &ctx,
558            PermissionReq::Action(PROM_STORE_WRITE),
559            PermissionTableTargets::resolved(targets),
560        )
561        .context(AuthSnafu)?;
562        Ok(())
563    }
564
565    async fn write_exemplars_prepared(
566        &self,
567        request: RowInsertRequests,
568        ctx: QueryContextRef,
569    ) -> ServerResult<Output> {
570        self.write_exemplars_with_sources(request, ctx, &[]).await
571    }
572
573    async fn write_exemplars_with_sources(
574        &self,
575        request: RowInsertRequests,
576        ctx: QueryContextRef,
577        sources: &[table::metadata::TableInfoRef],
578    ) -> ServerResult<Output> {
579        self.inserter
580            .handle_exemplar_inserts(request, ctx, &self.statement_executor, sources)
581            .await
582            .map_err(BoxedError::new)
583            .context(error::ExecuteGrpcQuerySnafu)
584    }
585
586    async fn pre_write(
587        &self,
588        request: &RowInsertRequests,
589        ctx: QueryContextRef,
590    ) -> ServerResult<()> {
591        self.plugins
592            .get::<PermissionCheckerRef>()
593            .as_ref()
594            .check_permission(ctx.current_user(), PermissionReq::Action(PROM_STORE_WRITE))
595            .context(AuthSnafu)?;
596        let interceptor_ref = self
597            .plugins
598            .get::<PromStoreProtocolInterceptorRef<servers::error::Error>>();
599        interceptor_ref.pre_write(request, ctx.clone())?;
600        self.check_row_insert_permission(request, &ctx, PermissionReq::Action(PROM_STORE_WRITE))
601            .context(AuthSnafu)?;
602        Ok(())
603    }
604
605    async fn write_prepared(
606        &self,
607        request: RowInsertRequests,
608        ctx: QueryContextRef,
609        with_metric_engine: bool,
610    ) -> ServerResult<Output> {
611        self.execute_prom_store_write(request, ctx, with_metric_engine)
612            .await
613    }
614
615    async fn write(
616        &self,
617        request: RowInsertRequests,
618        ctx: QueryContextRef,
619        with_metric_engine: bool,
620    ) -> ServerResult<Output> {
621        let (request, ctx) = self.prepare_prom_store_write(request, ctx).await?;
622        self.write_prepared(request, ctx, with_metric_engine).await
623    }
624
625    async fn write_all(
626        &self,
627        requests: Vec<(QueryContextRef, RowInsertRequests)>,
628        with_metric_engine: bool,
629    ) -> ServerResult<Vec<ServerResult<Output>>> {
630        let mut prepared = Vec::with_capacity(requests.len());
631        for (ctx, request) in requests {
632            let (request, ctx) = self.prepare_prom_store_write(request, ctx).await?;
633            prepared.push((ctx, request));
634        }
635        operator::insert::admit_row_insert_batches(&mut prepared)
636            .await
637            .map_err(BoxedError::new)
638            .context(error::ExecuteGrpcQuerySnafu)?;
639
640        let mut outputs = Vec::with_capacity(prepared.len());
641        for (ctx, request) in prepared {
642            let output = self.write_prepared(request, ctx, with_metric_engine).await;
643            let failed = output.is_err();
644            outputs.push(output);
645            if failed {
646                break;
647            }
648        }
649        Ok(outputs)
650    }
651
652    #[instrument(skip_all, fields(table_name))]
653    async fn read(
654        &self,
655        request: ReadRequest,
656        ctx: QueryContextRef,
657    ) -> ServerResult<PromStoreResponse> {
658        self.plugins
659            .get::<PermissionCheckerRef>()
660            .as_ref()
661            .check_permission(ctx.current_user(), PermissionReq::Action(PROM_STORE_READ))
662            .context(AuthSnafu)?;
663
664        let interceptor_ref = self
665            .plugins
666            .get::<PromStoreProtocolInterceptorRef<servers::error::Error>>();
667        interceptor_ref.pre_read(&request, ctx.clone())?;
668        let ctx = Arc::new(ctx.fork());
669
670        let table_names = request
671            .queries
672            .iter()
673            .map(prom_store::table_name)
674            .collect::<ServerResult<Vec<_>>>()?;
675        let query_targets = request
676            .queries
677            .iter()
678            .zip(&table_names)
679            .map(|(query, table_name)| resolve_remote_query_target(&ctx, query, table_name))
680            .collect::<Vec<_>>();
681        let disallow_cross_catalog_query = self
682            .plugins
683            .get::<QueryOptions>()
684            .map(|opts| opts.disallow_cross_catalog_query)
685            .unwrap_or_default();
686        if disallow_cross_catalog_query {
687            for target in &query_targets {
688                validate_catalog_and_schema(&target.catalog, &target.schema, &ctx)
689                    .map_err(BoxedError::new)
690                    .context(error::ExecuteQuerySnafu)?;
691            }
692        }
693        let targets = self
694            .resolve_query_permission_targets(
695                PermissionTableTargets::resolved(query_targets.clone()),
696                &ctx,
697            )
698            .await?;
699        self.check_table_permission(&ctx, PermissionReq::Action(PROM_STORE_READ), targets)
700            .context(AuthSnafu)?;
701
702        let response_type = negotiate_response_type(&request.accepted_response_types)?;
703
704        for query in &request.queries {
705            prom_store::validate_matchers(&query.matchers)?;
706        }
707
708        // query_to_plan intersects valid time hints with each query's outer range.
709        // Other hints do not change raw samples or labels.
710        let results = self
711            .handle_remote_queries(ctx, &request.queries, &query_targets)
712            .await?;
713
714        match response_type {
715            ResponseType::Samples => {
716                let mut query_results = Vec::with_capacity(results.len());
717                let mut map = HashMap::new();
718                for result in results {
719                    let Some(result) = result else {
720                        query_results.push(QueryResult { timeseries: vec![] });
721                        continue;
722                    };
723                    let RemoteQueryOutput {
724                        table_name,
725                        timestamp_column_name,
726                        value_column_name,
727                        output,
728                    } = result;
729                    let plan = output.meta.plan.clone();
730                    query_results.push(
731                        to_query_result(
732                            &table_name,
733                            &timestamp_column_name,
734                            &value_column_name,
735                            output,
736                        )
737                        .await?,
738                    );
739                    if let Some(ref plan) = plan {
740                        collect_plan_metrics(plan, &mut [&mut map]);
741                    }
742                }
743
744                let response = ReadResponse {
745                    results: query_results,
746                };
747
748                let resp_metrics = map
749                    .into_iter()
750                    .map(|(k, v)| (k, v.into()))
751                    .collect::<HashMap<_, _>>();
752
753                // TODO(dennis): may consume too much memory, adds flow control
754                Ok(PromStoreResponse {
755                    content_type: CONTENT_TYPE_PROTOBUF.clone(),
756                    content_encoding: CONTENT_ENCODING_SNAPPY.clone(),
757                    resp_metrics,
758                    body: prom_store::snappy_compress(&response.encode_to_vec())?,
759                })
760            }
761            ResponseType::StreamedXorChunks => error::NotSupportedSnafu {
762                feat: "streamed remote read",
763            }
764            .fail(),
765        }
766    }
767}
768
769impl Instance {
770    async fn create_metric_physical_table_if_missing(
771        &self,
772        catalog: &str,
773        schema: &str,
774        physical_table: &str,
775        ctx: QueryContextRef,
776    ) -> ServerResult<()> {
777        let table = self
778            .catalog_manager()
779            .table(catalog, schema, physical_table, Some(ctx.as_ref()))
780            .await
781            .map_err(BoxedError::new)
782            .context(error::ExecuteGrpcQuerySnafu)?;
783        if table.is_some() {
784            return Ok(());
785        }
786
787        let table_ref = TableReference::full(catalog, schema, physical_table);
788        let default_schema = vec![
789            api::v1::ColumnSchema {
790                column_name: common_query::prelude::greptime_timestamp().to_string(),
791                datatype: api::v1::ColumnDataType::TimestampMillisecond as i32,
792                semantic_type: api::v1::SemanticType::Timestamp as i32,
793                datatype_extension: None,
794                options: None,
795            },
796            api::v1::ColumnSchema {
797                column_name: common_query::prelude::greptime_value().to_string(),
798                datatype: api::v1::ColumnDataType::Float64 as i32,
799                semantic_type: api::v1::SemanticType::Field as i32,
800                datatype_extension: None,
801                options: None,
802            },
803        ];
804        let mut create_table_expr = build_create_table_expr(
805            &table_ref,
806            &default_schema,
807            common_catalog::consts::default_engine(),
808        )
809        .map_err(BoxedError::new)
810        .context(error::ExecuteGrpcQuerySnafu)?;
811        create_table_expr.engine = METRIC_ENGINE_NAME.to_string();
812        fill_metric_physical_table_options(&mut create_table_expr.table_options);
813
814        self.statement_executor
815            .create_table_inner(&mut create_table_expr, None, ctx, TriggerReason::AutoCreate)
816            .await
817            .map_err(BoxedError::new)
818            .context(error::ExecuteGrpcQuerySnafu)?;
819
820        Ok(())
821    }
822}
823
824/// This handler is mainly used for `frontend` or `standalone` to directly import
825/// the metrics collected by itself, thereby avoiding importing metrics through the network,
826/// thus reducing compression and network transmission overhead,
827/// so only implement `PromStoreProtocolHandler::write` method.
828pub struct ExportMetricHandler {
829    inserter: InserterRef,
830    statement_executor: Arc<StatementExecutor>,
831}
832
833impl ExportMetricHandler {
834    pub fn new_handler(
835        inserter: InserterRef,
836        statement_executor: Arc<StatementExecutor>,
837    ) -> PromStoreProtocolHandlerRef {
838        Arc::new(Self {
839            inserter,
840            statement_executor,
841        })
842    }
843}
844
845#[async_trait]
846impl PromStoreProtocolHandler for ExportMetricHandler {
847    async fn pre_write(
848        &self,
849        _request: &RowInsertRequests,
850        _ctx: QueryContextRef,
851    ) -> ServerResult<()> {
852        Ok(())
853    }
854
855    async fn write_prepared(
856        &self,
857        request: RowInsertRequests,
858        ctx: QueryContextRef,
859        _: bool,
860    ) -> ServerResult<Output> {
861        self.inserter
862            .handle_metric_row_inserts(
863                request,
864                ctx,
865                &self.statement_executor,
866                GREPTIME_PHYSICAL_TABLE.to_string(),
867            )
868            .await
869            .map_err(BoxedError::new)
870            .context(error::ExecuteGrpcQuerySnafu)
871    }
872
873    async fn write(
874        &self,
875        request: RowInsertRequests,
876        ctx: QueryContextRef,
877        with_metric_engine: bool,
878    ) -> ServerResult<Output> {
879        self.write_prepared(request, ctx, with_metric_engine).await
880    }
881
882    async fn write_all(
883        &self,
884        mut requests: Vec<(QueryContextRef, RowInsertRequests)>,
885        with_metric_engine: bool,
886    ) -> ServerResult<Vec<ServerResult<Output>>> {
887        operator::insert::admit_row_insert_batches(&mut requests)
888            .await
889            .map_err(BoxedError::new)
890            .context(error::ExecuteGrpcQuerySnafu)?;
891        let mut outputs = Vec::with_capacity(requests.len());
892        for (ctx, request) in requests {
893            let output = self.write_prepared(request, ctx, with_metric_engine).await;
894            let failed = output.is_err();
895            outputs.push(output);
896            if failed {
897                break;
898            }
899        }
900        Ok(outputs)
901    }
902
903    async fn read(
904        &self,
905        _request: ReadRequest,
906        _ctx: QueryContextRef,
907    ) -> ServerResult<PromStoreResponse> {
908        unreachable!();
909    }
910}
911
912#[cfg(test)]
913mod tests {
914    use std::sync::Arc;
915
916    use api::prom_store::remote::LabelMatcher;
917    use common_error::ext::ErrorExt;
918    use common_error::status_code::StatusCode;
919    use session::context::QueryContext;
920
921    use super::*;
922
923    #[test]
924    fn test_native_histogram_remote_read_response_negotiation() {
925        for accepted in [
926            vec![],
927            vec![ResponseType::Samples as i32],
928            vec![
929                ResponseType::StreamedXorChunks as i32,
930                ResponseType::Samples as i32,
931            ],
932        ] {
933            assert_eq!(
934                negotiate_response_type(&accepted).unwrap(),
935                ResponseType::Samples
936            );
937        }
938        assert!(negotiate_response_type(&[ResponseType::StreamedXorChunks as i32]).is_err());
939    }
940
941    #[test]
942    fn test_remote_read_label_types() {
943        use datatypes::schema::ColumnSchema;
944
945        for (label_type, supported) in [
946            (ConcreteDataType::string_datatype(), true),
947            (ConcreteDataType::large_string_datatype(), true),
948            (ConcreteDataType::utf8_view_datatype(), true),
949            (ConcreteDataType::binary_datatype(), false),
950            (ConcreteDataType::binary_view_datatype(), false),
951            (ConcreteDataType::json_datatype(), false),
952            (ConcreteDataType::vector_datatype(3), false),
953            (ConcreteDataType::int64_datatype(), false),
954            (ConcreteDataType::boolean_datatype(), false),
955            (
956                common_query::native_histogram::native_histogram_value_type().clone(),
957                false,
958            ),
959        ] {
960            let schema = Schema::new(vec![
961                ColumnSchema::new(
962                    "ts",
963                    ConcreteDataType::timestamp_millisecond_datatype(),
964                    false,
965                ),
966                ColumnSchema::new("value", label_type.clone(), true),
967                ColumnSchema::new("label", ConcreteDataType::string_datatype(), true),
968            ]);
969            // Value columns are validated separately, regardless of their type.
970            ensure_string_label_columns(&schema, "ts", "value").unwrap();
971
972            let result = ensure_string_label_columns(&schema, "ts", "label");
973            assert_eq!(supported, result.is_ok(), "{label_type:?}");
974            if let Err(err) = result {
975                assert!(matches!(
976                    err,
977                    crate::error::Error::InvalidPromRemoteReadSchema { .. }
978                ));
979                assert_eq!(err.status_code(), StatusCode::InvalidArguments);
980                assert!(
981                    err.to_string()
982                        .starts_with("Invalid Prometheus remote read schema: ")
983                );
984                assert!(
985                    err.to_string()
986                        .contains("only supports string labels: column 'value'")
987                );
988            }
989        }
990    }
991
992    #[test]
993    fn test_auto_create_table_type_for_prom_remote_write_metric_engine() {
994        let mut query_ctx = QueryContext::with(
995            common_catalog::consts::DEFAULT_CATALOG_NAME,
996            common_catalog::consts::DEFAULT_SCHEMA_NAME,
997        );
998        query_ctx.set_extension(PHYSICAL_TABLE_PARAM, "metric_physical".to_string());
999        let ctx = Arc::new(query_ctx);
1000
1001        let create_type = auto_create_table_type_for_prom_remote_write(&ctx, true);
1002        match create_type {
1003            AutoCreateTableType::Logical(physical) => assert_eq!(physical, "metric_physical"),
1004            _ => panic!("expected logical table create type"),
1005        }
1006    }
1007
1008    #[test]
1009    fn test_auto_create_table_type_for_prom_remote_write_without_metric_engine() {
1010        let ctx = Arc::new(QueryContext::with(
1011            common_catalog::consts::DEFAULT_CATALOG_NAME,
1012            common_catalog::consts::DEFAULT_SCHEMA_NAME,
1013        ));
1014
1015        let create_type = auto_create_table_type_for_prom_remote_write(&ctx, false);
1016        match create_type {
1017            AutoCreateTableType::Physical => {}
1018            _ => panic!("expected physical table create type"),
1019        }
1020    }
1021
1022    #[test]
1023    fn test_required_physical_table_for_create_type() {
1024        let logical = AutoCreateTableType::Logical("phy_table".to_string());
1025        assert_eq!(
1026            Some("phy_table"),
1027            required_physical_table_for_create_type(&logical)
1028        );
1029
1030        let physical = AutoCreateTableType::Physical;
1031        assert_eq!(None, required_physical_table_for_create_type(&physical));
1032    }
1033
1034    #[test]
1035    fn test_metric_physical_table_options_forces_flat_sst_format() {
1036        let mut table_options = HashMap::new();
1037
1038        fill_metric_physical_table_options(&mut table_options);
1039
1040        assert_eq!(
1041            Some("flat"),
1042            table_options.get(SST_FORMAT_KEY).map(String::as_str)
1043        );
1044        assert_eq!(
1045            Some("true"),
1046            table_options
1047                .get(PHYSICAL_TABLE_METADATA_KEY)
1048                .map(String::as_str)
1049        );
1050    }
1051
1052    fn query_with_database(database: &str) -> Query {
1053        Query {
1054            matchers: vec![LabelMatcher {
1055                name: servers::prom_store::DATABASE_LABEL.to_string(),
1056                value: database.to_string(),
1057                r#type: api::prom_store::remote::label_matcher::Type::Eq as i32,
1058            }],
1059            ..Default::default()
1060        }
1061    }
1062
1063    #[test]
1064    fn test_resolve_remote_query_target() {
1065        let ctx = Arc::new(QueryContext::with("request_catalog", "request_schema"));
1066
1067        assert_eq!(
1068            PermissionTableTarget::new("request_catalog", "request_schema", "fallback_metric"),
1069            resolve_remote_query_target(&ctx, &Query::default(), "fallback_metric")
1070        );
1071        assert_eq!(
1072            PermissionTableTarget::new("request_catalog", "selected_schema", "schema_metric"),
1073            resolve_remote_query_target(
1074                &ctx,
1075                &query_with_database("selected_schema"),
1076                "schema_metric"
1077            )
1078        );
1079        assert_eq!(
1080            PermissionTableTarget::new("selected_catalog", "selected_schema", "catalog_metric"),
1081            resolve_remote_query_target(
1082                &ctx,
1083                &query_with_database("selected_catalog-selected_schema"),
1084                "catalog_metric"
1085            )
1086        );
1087    }
1088
1089    #[test]
1090    fn test_resolve_remote_query_target_preserves_context() {
1091        let ctx = Arc::new(QueryContext::with("request_catalog", "request_schema"));
1092
1093        resolve_remote_query_target(
1094            &ctx,
1095            &query_with_database("selected_catalog-selected_schema"),
1096            "metric",
1097        );
1098
1099        assert_eq!("request_catalog", ctx.current_catalog());
1100        assert_eq!("request_schema", ctx.current_schema());
1101    }
1102}