1use 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 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 response_type == SAMPLES_RESPONSE_TYPE
111}
112
113fn 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 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 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(), ×tamp_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, ×tamp_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 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 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 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 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 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 ×tamp_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 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
824pub 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 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}