Skip to main content

servers/http/
handler.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;
17use std::time::{Duration, Instant};
18
19use axum::extract::rejection::FormRejection;
20use axum::extract::{Json, Query, State};
21use axum::response::sse::{Event, KeepAlive, Sse};
22use axum::response::{IntoResponse, Response};
23use axum::{Extension, Form};
24use common_catalog::parse_catalog_and_schema_from_db_string;
25use common_error::ext::ErrorExt;
26use common_error::status_code::StatusCode;
27use common_plugins::GREPTIME_EXEC_WRITE_COST;
28use common_query::{Output, OutputData};
29use common_recordbatch::{RecordBatch, SendableRecordBatchStream, util};
30use common_telemetry::tracing;
31use datafusion::physical_plan::ExecutionPlan;
32use datatypes::schema::SchemaRef;
33use futures::StreamExt;
34use query::parser::{DEFAULT_LOOKBACK_STRING, PromQuery};
35use serde::{Deserialize, Serialize};
36use serde_json::Value;
37use session::context::{Channel, QueryContext, QueryContextRef};
38use snafu::ResultExt;
39use sql::dialect::GreptimeDbDialect;
40use sql::parser::{ParseOptions, ParserContext};
41use sql::statements::statement::Statement;
42
43use crate::error::{FailedToParseQuerySnafu, InvalidQuerySnafu, Result};
44use crate::http::header::collect_plan_metrics;
45use crate::http::result::arrow_result::ArrowResponse;
46use crate::http::result::csv_result::CsvResponse;
47use crate::http::result::error_result::ErrorResponse;
48use crate::http::result::greptime_result_v1::GreptimedbV1Response;
49use crate::http::result::influxdb_result_v1::InfluxdbV1Response;
50use crate::http::result::json_result::JsonResponse;
51use crate::http::result::null_result::NullResponse;
52use crate::http::result::table_result::TableResponse;
53use crate::http::{
54    ApiState, Epoch, GreptimeOptionsConfigState, GreptimeQueryOutput, HttpRecordsOutput,
55    HttpResponse, ResponseFormat,
56};
57use crate::metrics_handler::MetricsHandler;
58use crate::query_handler::sql::ServerSqlQueryHandlerRef;
59
60#[derive(Debug, Default, Serialize, Deserialize)]
61pub struct SqlQuery {
62    pub db: Option<String>,
63    pub sql: Option<String>,
64    // (Optional) result format: [`greptimedb_v1`, `influxdb_v1`, `csv`,
65    // `arrow`],
66    // the default value is `greptimedb_v1`
67    pub format: Option<String>,
68    // Returns epoch timestamps with the specified precision.
69    // Both u and µ indicate microseconds.
70    // epoch = [ns,u,µ,ms,s],
71    //
72    // TODO(jeremy): currently, only InfluxDB result format is supported,
73    // and all columns of the `Timestamp` type will be converted to their
74    // specified time precision. Maybe greptimedb format can support this
75    // param too.
76    pub epoch: Option<String>,
77    pub limit: Option<usize>,
78    // For arrow output
79    pub compression: Option<String>,
80    pub snapshot_interval_ms: Option<u64>,
81}
82
83const DEFAULT_ANALYZE_SNAPSHOT_INTERVAL_MS: u64 = 5000;
84const MIN_ANALYZE_SNAPSHOT_INTERVAL_MS: u64 = 1000;
85const MAX_ANALYZE_SNAPSHOT_INTERVAL_MS: u64 = 60000;
86
87#[derive(Serialize)]
88struct AnalyzeStreamPayload {
89    seq: u64,
90    state: &'static str,
91    partial: bool,
92    elapsed_ms: u64,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    metrics: Option<Value>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    output: Option<GreptimeQueryOutput>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    reason: Option<String>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    code: Option<u32>,
101}
102
103struct AnalyzeStreamState {
104    stream: SendableRecordBatchStream,
105    schema: SchemaRef,
106    plan: Option<Arc<dyn ExecutionPlan>>,
107    batches: Vec<RecordBatch>,
108    seq: u64,
109    start: Instant,
110    requested_interval_ms: u64,
111    current_interval_ms: u64,
112    done: bool,
113}
114
115/// Handler to execute sql
116#[axum_macros::debug_handler]
117#[tracing::instrument(skip_all, fields(protocol = "http", request_type = "sql"))]
118pub async fn sql(
119    State(state): State<ApiState>,
120    Query(query_params): Query<SqlQuery>,
121    Extension(mut query_ctx): Extension<QueryContext>,
122    Form(form_params): Form<SqlQuery>,
123) -> HttpResponse {
124    let start = Instant::now();
125    let sql_handler = &state.sql_handler;
126    if let Some(db) = &query_params.db.or(form_params.db) {
127        let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
128        query_ctx.set_current_catalog(&catalog);
129        query_ctx.set_current_schema(&schema);
130    }
131    let db = query_ctx.get_db_string();
132
133    query_ctx.set_channel(Channel::HttpSql);
134    let query_ctx = Arc::new(query_ctx);
135
136    let _timer = crate::metrics::METRIC_HTTP_SQL_ELAPSED
137        .with_label_values(&[db.as_str()])
138        .start_timer();
139
140    let sql = query_params.sql.or(form_params.sql);
141    let format = query_params
142        .format
143        .or(form_params.format)
144        .map(|s| s.to_lowercase())
145        .map(|s| ResponseFormat::parse(s.as_str()).unwrap_or(ResponseFormat::GreptimedbV1))
146        .unwrap_or(ResponseFormat::GreptimedbV1);
147    let epoch = query_params
148        .epoch
149        .or(form_params.epoch)
150        .map(|s| s.to_lowercase())
151        .map(|s| Epoch::parse(s.as_str()).unwrap_or(Epoch::Millisecond));
152
153    let result = if let Some(sql) = &sql {
154        if let Some((status, msg)) = validate_schema(sql_handler.clone(), query_ctx.clone()).await {
155            Err((status, msg))
156        } else {
157            Ok(sql_handler.do_query(sql, query_ctx.clone()).await)
158        }
159    } else {
160        Err((
161            StatusCode::InvalidArguments,
162            "sql parameter is required.".to_string(),
163        ))
164    };
165
166    let outputs = match result {
167        Err((status, msg)) => {
168            return HttpResponse::Error(
169                ErrorResponse::from_error_message(status, msg)
170                    .with_execution_time(start.elapsed().as_millis() as u64),
171            );
172        }
173        Ok(outputs) => outputs,
174    };
175
176    let mut resp = match format {
177        ResponseFormat::Arrow => {
178            ArrowResponse::from_output(outputs, query_params.compression).await
179        }
180        ResponseFormat::Csv(with_names, with_types) => {
181            CsvResponse::from_output(outputs, with_names, with_types).await
182        }
183        ResponseFormat::Table => TableResponse::from_output(outputs).await,
184        ResponseFormat::GreptimedbV1 => GreptimedbV1Response::from_output(outputs).await,
185        ResponseFormat::InfluxdbV1 => InfluxdbV1Response::from_output(outputs, epoch).await,
186        ResponseFormat::Json => JsonResponse::from_output(outputs).await,
187        ResponseFormat::Null => NullResponse::from_output(outputs).await,
188    };
189
190    if let Some(limit) = query_params.limit {
191        resp = resp.with_limit(limit);
192    }
193    resp.with_execution_time(start.elapsed().as_millis() as u64)
194}
195
196/// Handler to stream partial `EXPLAIN ANALYZE VERBOSE` metrics as SSE.
197///
198/// This experimental endpoint is POST-only SSE, so browser `EventSource` does
199/// not apply. Each `metrics` event carries a complete snapshot (not a delta);
200/// large snapshots are throttled but never truncated. `final`, `canceled`, and
201/// `error` are terminal events. If the client disconnects it won't receive a
202/// `canceled` event, but the production frontend stream is dropped and
203/// best-effort cancels the underlying query.
204#[axum_macros::debug_handler]
205#[tracing::instrument(
206    skip_all,
207    fields(protocol = "http", request_type = "sql_analyze_stream")
208)]
209pub async fn sql_analyze_stream(
210    State(state): State<ApiState>,
211    Query(query_params): Query<SqlQuery>,
212    Extension(mut query_ctx): Extension<QueryContext>,
213    form_params: std::result::Result<Form<SqlQuery>, FormRejection>,
214) -> Response {
215    let start = Instant::now();
216    let form_params = match form_params {
217        Ok(Form(params)) => params,
218        Err(err) => {
219            if err.status() != axum::http::StatusCode::UNSUPPORTED_MEDIA_TYPE {
220                return ErrorResponse::from_error_message(
221                    StatusCode::InvalidArguments,
222                    err.body_text(),
223                )
224                .with_execution_time(start.elapsed().as_millis() as u64)
225                .into_response();
226            }
227            SqlQuery::default()
228        }
229    };
230    let sql_handler = &state.sql_handler;
231    if let Some(db) = &query_params.db.or(form_params.db) {
232        let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
233        query_ctx.set_current_catalog(&catalog);
234        query_ctx.set_current_schema(&schema);
235    }
236    query_ctx.set_channel(Channel::HttpSql);
237    query_ctx.enable_live_analyze_metrics();
238    let query_ctx = Arc::new(query_ctx);
239
240    let Some(sql) = query_params.sql.or(form_params.sql) else {
241        return ErrorResponse::from_error_message(
242            StatusCode::InvalidArguments,
243            "sql parameter is required.".to_string(),
244        )
245        .with_execution_time(start.elapsed().as_millis() as u64)
246        .into_response();
247    };
248    if let Some((status, msg)) = validate_schema(sql_handler.clone(), query_ctx.clone()).await {
249        return ErrorResponse::from_error_message(status, msg)
250            .with_execution_time(start.elapsed().as_millis() as u64)
251            .into_response();
252    }
253
254    let interval_ms = query_params
255        .snapshot_interval_ms
256        .or(form_params.snapshot_interval_ms)
257        .unwrap_or(DEFAULT_ANALYZE_SNAPSHOT_INTERVAL_MS)
258        .clamp(
259            MIN_ANALYZE_SNAPSHOT_INTERVAL_MS,
260            MAX_ANALYZE_SNAPSHOT_INTERVAL_MS,
261        );
262
263    let output = match state
264        .sql_handler
265        .do_analyze_stream_query(&sql, query_ctx.clone())
266        .await
267    {
268        Ok(output) => output,
269        Err(err) => {
270            return ErrorResponse::from_error(err)
271                .with_execution_time(start.elapsed().as_millis() as u64)
272                .into_response();
273        }
274    };
275
276    let plan = output.meta.plan.clone();
277    let OutputData::Stream(stream) = output.data else {
278        return ErrorResponse::from_error_message(
279            StatusCode::InvalidArguments,
280            "analyze stream query must return a stream".to_string(),
281        )
282        .with_execution_time(start.elapsed().as_millis() as u64)
283        .into_response();
284    };
285    let schema = stream.schema();
286
287    let sse_stream = futures::stream::unfold(
288        AnalyzeStreamState {
289            stream,
290            schema,
291            plan,
292            batches: Vec::new(),
293            seq: 0,
294            start,
295            requested_interval_ms: interval_ms,
296            current_interval_ms: interval_ms,
297            done: false,
298        },
299        |mut state| async move {
300            if state.done {
301                return None;
302            }
303            let tick = tokio::time::sleep(Duration::from_millis(state.current_interval_ms));
304            tokio::pin!(tick);
305            loop {
306                tokio::select! {
307                    item = state.stream.next() => {
308                        match item {
309                            Some(Ok(batch)) => state.batches.push(batch),
310                            Some(Err(err)) => {
311                                let status = err.status_code();
312                                let event_name = if status == StatusCode::Cancelled { "canceled" } else { "error" };
313                                let (payload, _) = make_analyze_payload(AnalyzePayloadArgs {
314                                    seq: state.seq,
315                                    state: event_name,
316                                    partial: false,
317                                    start: state.start,
318                                    plan: state.plan.as_ref(),
319                                    output: None,
320                                    reason: Some(err.output_msg()),
321                                    code: Some(status as u32),
322                                });
323                                state.seq += 1;
324                                state.done = true;
325                                return Some((Ok::<Event, std::convert::Infallible>(Event::default().event(event_name).data(payload)), state));
326                            }
327                            None => {
328                                let batches = std::mem::take(&mut state.batches);
329                                let output = HttpRecordsOutput::try_new(state.schema.clone(), batches)
330                                    .map(GreptimeQueryOutput::Records);
331                                let (event_name, payload) = make_final_analyze_event(
332                                    output.map_err(|err| (err.output_msg(), err.status_code() as u32)),
333                                    state.seq,
334                                    state.start,
335                                    state.plan.as_ref(),
336                                );
337                                state.seq += 1;
338                                state.done = true;
339                                return Some((Ok::<Event, std::convert::Infallible>(Event::default().event(event_name).data(payload)), state));
340                            }
341                        }
342                    }
343                    _ = &mut tick => {
344                        if state.plan.is_some() {
345                            let (payload, payload_bytes) = make_analyze_payload(AnalyzePayloadArgs {
346                                seq: state.seq,
347                                state: "metrics",
348                                partial: true,
349                                start: state.start,
350                                plan: state.plan.as_ref(),
351                                output: None,
352                                reason: None,
353                                code: None,
354                            });
355                            state.current_interval_ms = adaptive_interval_ms(payload_bytes, state.requested_interval_ms);
356                            state.seq += 1;
357                            return Some((Ok::<Event, std::convert::Infallible>(Event::default().event("metrics").data(payload)), state));
358                        }
359                        tick.as_mut().reset(tokio::time::Instant::now() + Duration::from_millis(state.current_interval_ms));
360                    }
361                }
362            }
363        },
364    );
365
366    Sse::new(sse_stream)
367        .keep_alive(KeepAlive::new().interval(Duration::from_secs(15)))
368        .into_response()
369}
370
371fn adaptive_interval_ms(payload_bytes: usize, requested_ms: u64) -> u64 {
372    if payload_bytes >= 10 * 1024 * 1024 {
373        requested_ms.max(30_000)
374    } else if payload_bytes >= 1024 * 1024 {
375        requested_ms.max(10_000)
376    } else {
377        requested_ms
378    }
379}
380
381fn make_final_analyze_event(
382    output: std::result::Result<GreptimeQueryOutput, (String, u32)>,
383    seq: u64,
384    start: Instant,
385    plan: Option<&Arc<dyn ExecutionPlan>>,
386) -> (&'static str, String) {
387    match output {
388        Ok(output) => (
389            "final",
390            make_analyze_payload(AnalyzePayloadArgs {
391                seq,
392                state: "final",
393                partial: false,
394                start,
395                plan,
396                output: Some(output),
397                reason: None,
398                code: None,
399            })
400            .0,
401        ),
402        Err((reason, code)) => (
403            "error",
404            make_analyze_payload(AnalyzePayloadArgs {
405                seq,
406                state: "error",
407                partial: false,
408                start,
409                plan,
410                output: None,
411                reason: Some(reason),
412                code: Some(code),
413            })
414            .0,
415        ),
416    }
417}
418
419struct AnalyzePayloadArgs<'a> {
420    seq: u64,
421    state: &'static str,
422    partial: bool,
423    start: Instant,
424    plan: Option<&'a Arc<dyn ExecutionPlan>>,
425    output: Option<GreptimeQueryOutput>,
426    reason: Option<String>,
427    code: Option<u32>,
428}
429
430fn make_analyze_payload(args: AnalyzePayloadArgs<'_>) -> (String, usize) {
431    let AnalyzePayloadArgs {
432        seq,
433        state,
434        partial,
435        start,
436        plan,
437        output,
438        reason,
439        code,
440    } = args;
441    let metrics = plan.and_then(|plan| query::analyze_plan_metrics_to_json_value(plan, true).ok());
442    let payload = AnalyzeStreamPayload {
443        seq,
444        state,
445        partial,
446        elapsed_ms: start.elapsed().as_millis() as u64,
447        metrics,
448        output,
449        reason,
450        code,
451    };
452    let payload_string = serde_json::to_string(&payload).unwrap_or_else(|e| {
453        serde_json::json!({
454            "seq": seq,
455            "state": "error",
456            "partial": false,
457            "reason": format!("Failed to serialize SSE payload: {e}"),
458        })
459        .to_string()
460    });
461    let payload_bytes = payload_string.len();
462    (payload_string, payload_bytes)
463}
464
465/// Handler to parse sql
466#[axum_macros::debug_handler]
467#[tracing::instrument(skip_all, fields(protocol = "http", request_type = "sql"))]
468pub async fn sql_parse(
469    Query(query_params): Query<SqlQuery>,
470    Form(form_params): Form<SqlQuery>,
471) -> Result<Json<Vec<Statement>>> {
472    let Some(sql) = query_params.sql.or(form_params.sql) else {
473        return InvalidQuerySnafu {
474            reason: "sql parameter is required.",
475        }
476        .fail();
477    };
478
479    let stmts =
480        ParserContext::create_with_dialect(&sql, &GreptimeDbDialect {}, ParseOptions::default())
481            .context(FailedToParseQuerySnafu)?;
482
483    Ok(stmts.into())
484}
485
486#[derive(Debug, Serialize, Deserialize)]
487pub struct SqlFormatResponse {
488    pub formatted: String,
489}
490
491/// Handler to format sql string
492#[axum_macros::debug_handler]
493#[tracing::instrument(skip_all, fields(protocol = "http", request_type = "sql_format"))]
494pub async fn sql_format(
495    Query(query_params): Query<SqlQuery>,
496    Form(form_params): Form<SqlQuery>,
497) -> axum::response::Response {
498    let Some(sql) = query_params.sql.or(form_params.sql) else {
499        let resp = ErrorResponse::from_error_message(
500            StatusCode::InvalidArguments,
501            "sql parameter is required.".to_string(),
502        );
503        return HttpResponse::Error(resp).into_response();
504    };
505
506    // Parse using GreptimeDB dialect then reconstruct statements via Display
507    let stmts = match ParserContext::create_with_dialect(
508        &sql,
509        &GreptimeDbDialect {},
510        ParseOptions::default(),
511    ) {
512        Ok(v) => v,
513        Err(e) => return HttpResponse::Error(ErrorResponse::from_error(e)).into_response(),
514    };
515
516    let mut parts: Vec<String> = Vec::with_capacity(stmts.len());
517    for stmt in stmts {
518        let mut s = format!("{stmt}");
519        if !s.trim_end().ends_with(';') {
520            s.push(';');
521        }
522        parts.push(s);
523    }
524
525    let formatted = parts.join("\n");
526    Json(SqlFormatResponse { formatted }).into_response()
527}
528
529/// Create a response from query result
530pub async fn from_output(
531    outputs: Vec<crate::error::Result<Output>>,
532) -> std::result::Result<(Vec<GreptimeQueryOutput>, HashMap<String, Value>), ErrorResponse> {
533    // TODO(sunng87): this api response structure cannot represent error well.
534    //  It hides successful execution results from error response
535    let mut results = Vec::with_capacity(outputs.len());
536    let mut merge_map = HashMap::new();
537
538    for out in outputs {
539        match out {
540            Ok(o) => match o.data {
541                OutputData::AffectedRows(rows) => {
542                    results.push(GreptimeQueryOutput::AffectedRows(rows));
543                    if o.meta.cost > 0 {
544                        merge_map.insert(GREPTIME_EXEC_WRITE_COST.to_string(), o.meta.cost as u64);
545                    }
546                }
547                OutputData::Stream(stream) => {
548                    let schema = stream.schema().clone();
549                    // TODO(sunng87): streaming response
550                    let mut http_record_output = match util::collect(stream).await {
551                        Ok(rows) => match HttpRecordsOutput::try_new(schema, rows) {
552                            Ok(rows) => rows,
553                            Err(err) => {
554                                return Err(ErrorResponse::from_error(err));
555                            }
556                        },
557                        Err(err) => {
558                            return Err(ErrorResponse::from_error(err));
559                        }
560                    };
561                    if let Some(physical_plan) = o.meta.plan {
562                        let mut result_map = HashMap::new();
563
564                        let mut tmp = vec![&mut merge_map, &mut result_map];
565                        collect_plan_metrics(&physical_plan, &mut tmp);
566                        let re = result_map
567                            .into_iter()
568                            .map(|(k, v)| (k, Value::from(v)))
569                            .collect::<HashMap<String, Value>>();
570                        http_record_output.metrics.extend(re);
571                    }
572                    results.push(GreptimeQueryOutput::Records(http_record_output))
573                }
574                OutputData::RecordBatches(rbs) => {
575                    match HttpRecordsOutput::try_new(rbs.schema(), rbs.take()) {
576                        Ok(rows) => {
577                            results.push(GreptimeQueryOutput::Records(rows));
578                        }
579                        Err(err) => {
580                            return Err(ErrorResponse::from_error(err));
581                        }
582                    }
583                }
584            },
585
586            Err(err) => {
587                return Err(ErrorResponse::from_error(err));
588            }
589        }
590    }
591
592    let merge_map = merge_map
593        .into_iter()
594        .map(|(k, v)| (k, Value::from(v)))
595        .collect();
596
597    Ok((results, merge_map))
598}
599
600#[derive(Debug, Default, Serialize, Deserialize)]
601pub struct PromqlQuery {
602    pub query: String,
603    pub start: String,
604    pub end: String,
605    pub step: String,
606    pub lookback: Option<String>,
607    pub db: Option<String>,
608    // (Optional) result format: [`greptimedb_v1`, `influxdb_v1`, `csv`,
609    // `arrow`],
610    // the default value is `greptimedb_v1`
611    pub format: Option<String>,
612    // For arrow output
613    pub compression: Option<String>,
614    // Returns epoch timestamps with the specified precision.
615    // Both u and µ indicate microseconds.
616    // epoch = [ns,u,µ,ms,s],
617    //
618    // For influx output only
619    //
620    // TODO(jeremy): currently, only InfluxDB result format is supported,
621    // and all columns of the `Timestamp` type will be converted to their
622    // specified time precision. Maybe greptimedb format can support this
623    // param too.
624    pub epoch: Option<String>,
625}
626
627impl From<PromqlQuery> for PromQuery {
628    fn from(query: PromqlQuery) -> Self {
629        PromQuery {
630            query: query.query,
631            start: query.start,
632            end: query.end,
633            step: query.step,
634            lookback: query
635                .lookback
636                .unwrap_or_else(|| DEFAULT_LOOKBACK_STRING.to_string()),
637            // TODO(dennis): support alias from http params or parse from query.query
638            alias: None,
639        }
640    }
641}
642
643/// Handler to execute promql
644#[axum_macros::debug_handler]
645#[tracing::instrument(skip_all, fields(protocol = "http", request_type = "promql"))]
646pub async fn promql(
647    State(state): State<ApiState>,
648    Query(params): Query<PromqlQuery>,
649    Extension(mut query_ctx): Extension<QueryContext>,
650) -> Response {
651    let sql_handler = &state.sql_handler;
652    let exec_start = Instant::now();
653    let db = query_ctx.get_db_string();
654
655    query_ctx.set_channel(Channel::Promql);
656    let query_ctx = Arc::new(query_ctx);
657
658    let _timer = crate::metrics::METRIC_HTTP_PROMQL_ELAPSED
659        .with_label_values(&[db.as_str()])
660        .start_timer();
661
662    let resp = if let Some((status, msg)) =
663        validate_schema(sql_handler.clone(), query_ctx.clone()).await
664    {
665        let resp = ErrorResponse::from_error_message(status, msg);
666        HttpResponse::Error(resp)
667    } else {
668        let format = params
669            .format
670            .as_ref()
671            .map(|s| s.to_lowercase())
672            .map(|s| ResponseFormat::parse(s.as_str()).unwrap_or(ResponseFormat::GreptimedbV1))
673            .unwrap_or(ResponseFormat::GreptimedbV1);
674        let epoch = params
675            .epoch
676            .as_ref()
677            .map(|s| s.to_lowercase())
678            .map(|s| Epoch::parse(s.as_str()).unwrap_or(Epoch::Millisecond));
679        let compression = params.compression.clone();
680
681        let prom_query = params.into();
682        let outputs = sql_handler.do_promql_query(&prom_query, query_ctx).await;
683
684        match format {
685            ResponseFormat::Arrow => ArrowResponse::from_output(outputs, compression).await,
686            ResponseFormat::Csv(with_names, with_types) => {
687                CsvResponse::from_output(outputs, with_names, with_types).await
688            }
689            ResponseFormat::Table => TableResponse::from_output(outputs).await,
690            ResponseFormat::GreptimedbV1 => GreptimedbV1Response::from_output(outputs).await,
691            ResponseFormat::InfluxdbV1 => InfluxdbV1Response::from_output(outputs, epoch).await,
692            ResponseFormat::Json => JsonResponse::from_output(outputs).await,
693            ResponseFormat::Null => NullResponse::from_output(outputs).await,
694        }
695    };
696
697    resp.with_execution_time(exec_start.elapsed().as_millis() as u64)
698        .into_response()
699}
700
701/// Handler to export metrics
702#[axum_macros::debug_handler]
703pub async fn metrics(
704    State(state): State<MetricsHandler>,
705    Query(_params): Query<HashMap<String, String>>,
706) -> String {
707    // A default ProcessCollector is registered automatically in prometheus.
708    // We do not need to explicitly collect process-related data.
709    // But ProcessCollector only support on linux.
710
711    #[cfg(not(windows))]
712    if let Some(c) = crate::metrics::jemalloc::JEMALLOC_COLLECTOR.as_ref()
713        && let Err(e) = c.update()
714    {
715        common_telemetry::error!(e; "Failed to update jemalloc metrics");
716    }
717    state.render()
718}
719
720#[derive(Debug, Serialize, Deserialize)]
721pub struct HealthQuery {}
722
723#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
724pub struct HealthResponse {}
725
726/// Handler to export healthy check
727///
728/// Currently simply return status "200 OK" (default) with an empty json payload "{}"
729#[axum_macros::debug_handler]
730pub async fn health(Query(_params): Query<HealthQuery>) -> Json<HealthResponse> {
731    Json(HealthResponse {})
732}
733
734#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
735pub struct StatusResponse<'a> {
736    pub commit: &'a str,
737    pub branch: &'a str,
738    pub rustc_version: &'a str,
739    pub hostname: String,
740    pub version: &'a str,
741}
742
743/// Handler to expose information info about runtime, build, etc.
744#[axum_macros::debug_handler]
745pub async fn status() -> Json<StatusResponse<'static>> {
746    let hostname = hostname::get()
747        .map(|s| s.to_string_lossy().to_string())
748        .unwrap_or_else(|_| "unknown".to_string());
749    let build_info = common_version::build_info();
750    Json(StatusResponse {
751        commit: build_info.commit,
752        branch: build_info.branch,
753        rustc_version: build_info.rustc,
754        hostname,
755        version: build_info.version,
756    })
757}
758
759/// Handler to expose configuration information info about runtime, build, etc.
760#[axum_macros::debug_handler]
761pub async fn config(State(state): State<GreptimeOptionsConfigState>) -> Response {
762    (axum::http::StatusCode::OK, state.greptime_config_options).into_response()
763}
764
765async fn validate_schema(
766    sql_handler: ServerSqlQueryHandlerRef,
767    query_ctx: QueryContextRef,
768) -> Option<(StatusCode, String)> {
769    match sql_handler
770        .is_valid_schema(query_ctx.current_catalog(), &query_ctx.current_schema())
771        .await
772    {
773        Ok(true) => None,
774        Ok(false) => Some((
775            StatusCode::DatabaseNotFound,
776            format!("Database not found: {}", query_ctx.get_db_string()),
777        )),
778        Err(e) => Some((
779            StatusCode::Internal,
780            format!(
781                "Error checking database: {}, {}",
782                query_ctx.get_db_string(),
783                e.output_msg(),
784            ),
785        )),
786    }
787}
788
789pub async fn index() -> axum::response::Html<String> {
790    let name = common_version::product_name();
791    let version = common_version::version();
792    axum::response::Html(format!(
793        r#"<!DOCTYPE html>
794<html>
795<head><title>{name}</title></head>
796<body>
797<h1>{name}</h1>
798<p>Version: {version}</p>
799<ul>
800<li><a href="/dashboard">Dashboard UI</a></li>
801<li><a href="/v1/health">Health</a> (JSON)</li>
802<li><a href="/status">Status</a> (JSON)</li>
803<li><a href="/metrics">Metrics</a> (For Prometheus Scrape)</li>
804<li><a href="/config">Config</a> (TXT)</li>
805</ul>
806</body>
807</html>"#,
808    ))
809}
810
811#[cfg(test)]
812mod tests {
813    use super::*;
814
815    #[test]
816    fn test_final_analyze_event_uses_error_event_for_conversion_error() {
817        let (event_name, payload) = make_final_analyze_event(
818            Err((
819                "conversion failed".to_string(),
820                StatusCode::InvalidArguments as u32,
821            )),
822            7,
823            Instant::now(),
824            None,
825        );
826
827        assert_eq!(event_name, "error");
828        let value: Value = serde_json::from_str(&payload).unwrap();
829        assert_eq!(value["state"], "error");
830        assert_eq!(value["reason"], "conversion failed");
831    }
832}