1use std::collections::HashMap;
16use std::future;
17use std::panic::AssertUnwindSafe;
18use std::sync::Arc;
19use std::sync::atomic::{AtomicU64, Ordering};
20use std::time::{Duration, Instant};
21
22use axum::extract::rejection::FormRejection;
23use axum::extract::{Json, Query, State};
24use axum::response::sse::{Event, KeepAlive, Sse};
25use axum::response::{IntoResponse, Response};
26use axum::{Extension, Form};
27use common_catalog::parse_catalog_and_schema_from_db_string;
28use common_error::ext::ErrorExt;
29use common_error::status_code::StatusCode;
30use common_plugins::GREPTIME_EXEC_WRITE_COST;
31use common_query::{Output, OutputData};
32use common_recordbatch::error::Result as RecordBatchResult;
33use common_recordbatch::{RecordBatch, RecordBatchStreamWrapper, RecordBatches, util};
34use common_telemetry::tracing;
35use datafusion::physical_plan::ExecutionPlan;
36use futures::{FutureExt, StreamExt};
37use query::parser::{DEFAULT_LOOKBACK_STRING, PromQuery};
38use serde::{Deserialize, Serialize};
39use serde_json::Value;
40use session::context::{Channel, QueryContext, QueryContextRef};
41use snafu::ResultExt;
42use sql::dialect::GreptimeDbDialect;
43use sql::parser::{ParseOptions, ParserContext};
44use sql::statements::statement::Statement;
45use tokio::sync::{Notify, watch};
46
47use crate::error::{
48 CollectRecordbatchSnafu, FailedToParseQuerySnafu, InvalidQuerySnafu, Result,
49 log_error_if_necessary,
50};
51use crate::http::header::collect_plan_metrics;
52use crate::http::result::arrow_result::ArrowResponse;
53use crate::http::result::csv_result::CsvResponse;
54use crate::http::result::error_result::ErrorResponse;
55use crate::http::result::greptime_result_v1::GreptimedbV1Response;
56use crate::http::result::influxdb_result_v1::InfluxdbV1Response;
57use crate::http::result::json_result::JsonResponse;
58use crate::http::result::null_result::NullResponse;
59use crate::http::result::table_result::TableResponse;
60use crate::http::{
61 ApiState, Epoch, GreptimeOptionsConfigState, GreptimeQueryOutput, HttpRecordsOutput,
62 HttpResponse, ResponseFormat,
63};
64use crate::metrics_handler::MetricsHandler;
65use crate::query_handler::sql::ServerSqlQueryHandlerRef;
66
67#[derive(Debug, Default, Serialize, Deserialize)]
68pub struct SqlQuery {
69 pub db: Option<String>,
70 pub sql: Option<String>,
71 pub format: Option<String>,
75 pub epoch: Option<String>,
84 pub limit: Option<usize>,
85 pub compression: Option<String>,
87 pub snapshot_interval_ms: Option<u64>,
88}
89
90const DEFAULT_ANALYZE_SNAPSHOT_INTERVAL_MS: u64 = 5000;
91const MIN_ANALYZE_SNAPSHOT_INTERVAL_MS: u64 = 1000;
92const MAX_ANALYZE_SNAPSHOT_INTERVAL_MS: u64 = 60000;
93
94#[derive(Serialize)]
95struct AnalyzeStreamPayload {
96 seq: u64,
97 state: &'static str,
98 partial: bool,
99 elapsed_ms: u64,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 metrics: Option<Value>,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 output: Option<GreptimeQueryOutput>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 reason: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 code: Option<u32>,
108}
109
110#[derive(Clone, Debug)]
111#[doc(hidden)]
112pub struct AnalyzeStreamMessage {
113 pub event_name: &'static str,
114 pub payload: String,
115}
116
117struct AnalyzeStreamWorkerGuard {
118 cancel: watch::Sender<bool>,
119 handle: common_runtime::JoinHandle<()>,
120}
121
122impl Drop for AnalyzeStreamWorkerGuard {
123 fn drop(&mut self) {
124 let _ = self.cancel.send(true);
125 self.handle.abort();
126 }
127}
128
129struct AnalyzeStreamBodyState {
130 latest_metrics: watch::Receiver<Option<String>>,
131 terminal: watch::Receiver<Option<AnalyzeStreamMessage>>,
132 notify: Arc<Notify>,
133 _worker: AnalyzeStreamWorkerGuard,
136 done: bool,
137}
138
139#[axum_macros::debug_handler]
141#[tracing::instrument(skip_all, fields(protocol = "http", request_type = "sql"))]
142pub async fn sql(
143 State(state): State<ApiState>,
144 Query(query_params): Query<SqlQuery>,
145 Extension(mut query_ctx): Extension<QueryContext>,
146 Form(form_params): Form<SqlQuery>,
147) -> HttpResponse {
148 let start = Instant::now();
149 let sql_handler = &state.sql_handler;
150 if let Some(db) = &query_params.db.or(form_params.db) {
151 let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
152 query_ctx.set_current_catalog(&catalog);
153 query_ctx.set_current_schema(&schema);
154 }
155 let db = query_ctx.get_db_string();
156
157 query_ctx.set_channel(Channel::HttpSql);
158 let query_ctx = Arc::new(query_ctx);
159
160 let _timer = crate::metrics::METRIC_HTTP_SQL_ELAPSED
161 .with_label_values(&[db.as_str()])
162 .start_timer();
163
164 let sql = query_params.sql.or(form_params.sql);
165 let format = query_params
166 .format
167 .or(form_params.format)
168 .map(|s| s.to_lowercase())
169 .map(|s| ResponseFormat::parse(s.as_str()).unwrap_or(ResponseFormat::GreptimedbV1))
170 .unwrap_or(ResponseFormat::GreptimedbV1);
171 let epoch = query_params
172 .epoch
173 .or(form_params.epoch)
174 .map(|s| s.to_lowercase())
175 .map(|s| Epoch::parse(s.as_str()).unwrap_or(Epoch::Millisecond));
176
177 let result = if let Some(sql) = &sql {
178 if let Some((status, msg)) = validate_schema(sql_handler.clone(), query_ctx.clone()).await {
179 Err((status, msg))
180 } else {
181 Ok(sql_handler.do_query(sql, query_ctx.clone()).await)
182 }
183 } else {
184 Err((
185 StatusCode::InvalidArguments,
186 "sql parameter is required.".to_string(),
187 ))
188 };
189
190 let outputs = match result {
191 Err((status, msg)) => {
192 return HttpResponse::Error(
193 ErrorResponse::from_error_message(status, msg)
194 .with_execution_time(start.elapsed().as_millis() as u64),
195 );
196 }
197 Ok(outputs) => outputs,
198 };
199
200 let outputs = match query_params.limit {
201 Some(limit)
202 if matches!(
203 format,
204 ResponseFormat::Csv(..)
205 | ResponseFormat::Table
206 | ResponseFormat::GreptimedbV1
207 | ResponseFormat::Json
208 ) =>
209 {
210 outputs
211 .into_iter()
212 .map(|output| output.and_then(|output| limit_output_rows(output, limit)))
213 .collect()
214 }
215 _ => outputs,
216 };
217
218 let resp = match format {
219 ResponseFormat::Arrow => {
220 ArrowResponse::from_output(outputs, query_params.compression).await
221 }
222 ResponseFormat::Csv(with_names, with_types) => {
223 CsvResponse::from_output(outputs, with_names, with_types).await
224 }
225 ResponseFormat::Table => TableResponse::from_output(outputs).await,
226 ResponseFormat::GreptimedbV1 => GreptimedbV1Response::from_output(outputs).await,
227 ResponseFormat::InfluxdbV1 => InfluxdbV1Response::from_output(outputs, epoch).await,
228 ResponseFormat::Json => JsonResponse::from_output(outputs).await,
229 ResponseFormat::Null => NullResponse::from_output(outputs).await,
230 };
231
232 resp.with_execution_time(start.elapsed().as_millis() as u64)
233}
234
235#[derive(Deserialize)]
237pub struct LogicalTableDdl {
238 sql: Option<String>,
239 db: Option<String>,
240}
241
242pub async fn create_logical_tables(
244 State(state): State<ApiState>,
245 Extension(mut query_ctx): Extension<QueryContext>,
246 form: std::result::Result<Form<LogicalTableDdl>, FormRejection>,
247) -> HttpResponse {
248 let start = Instant::now();
249 let form = match form {
250 Ok(Form(form)) => form,
251 Err(_) => {
252 return HttpResponse::Error(ErrorResponse::from_error_message(
253 StatusCode::InvalidArguments,
254 "invalid or oversized form body".to_string(),
255 ));
256 }
257 };
258 let Some(sql) = form.sql else {
259 return HttpResponse::Error(ErrorResponse::from_error_message(
260 StatusCode::InvalidArguments,
261 "sql parameter is required.".to_string(),
262 ));
263 };
264 if sql.len() > crate::query_handler::sql::MAX_LOGICAL_TABLE_DDL_BYTES {
265 return HttpResponse::Error(ErrorResponse::from_error_message(
266 StatusCode::InvalidArguments,
267 "logical-table DDL exceeds 1 MiB".to_string(),
268 ));
269 }
270 if let Some(db) = form.db {
271 let (catalog, schema) = parse_catalog_and_schema_from_db_string(&db);
272 query_ctx.set_current_catalog(&catalog);
273 query_ctx.set_current_schema(&schema);
274 }
275 query_ctx.set_channel(Channel::HttpSql);
276 let query_ctx = Arc::new(query_ctx);
277 if let Some((status, msg)) = validate_schema(state.sql_handler.clone(), query_ctx.clone()).await
278 {
279 return HttpResponse::Error(ErrorResponse::from_error_message(status, msg));
280 }
281 let outputs = match state
282 .sql_handler
283 .create_logical_tables(&sql, query_ctx)
284 .await
285 {
286 Ok(outputs)
287 if outputs
288 .iter()
289 .all(|output| matches!(output.data, OutputData::AffectedRows(0))) =>
290 {
291 outputs.into_iter().map(Ok).collect()
292 }
293 other => {
294 let status = match other {
295 Err(error) => {
296 log_error_if_necessary(&error);
297 error.status_code()
298 }
299 Ok(_) => StatusCode::Internal,
300 };
301 return HttpResponse::Error(
302 ErrorResponse::from_error_message(status, "logical-table batch failed".to_string())
303 .with_execution_time(start.elapsed().as_millis() as u64),
304 );
305 }
306 };
307 GreptimedbV1Response::from_output(outputs)
308 .await
309 .with_execution_time(start.elapsed().as_millis() as u64)
310}
311
312fn limit_output_rows(output: Output, limit: usize) -> Result<Output> {
314 let mut remaining = limit;
315 let data = match output.data {
316 OutputData::AffectedRows(rows) => OutputData::AffectedRows(rows),
317 OutputData::RecordBatches(batches) => {
318 let schema = batches.schema();
319 let batches = batches
320 .into_iter()
321 .filter_map(|batch| take_response_rows(batch, &mut remaining).transpose())
322 .collect::<RecordBatchResult<Vec<_>>>()
323 .context(CollectRecordbatchSnafu)?;
324 OutputData::RecordBatches(
325 RecordBatches::try_new(schema, batches).context(CollectRecordbatchSnafu)?,
326 )
327 }
328 OutputData::Stream(stream) => {
329 let schema = stream.schema();
330 let stream = stream.filter_map(move |batch| {
333 future::ready(
334 batch
335 .and_then(|batch| take_response_rows(batch, &mut remaining))
336 .transpose(),
337 )
338 });
339 OutputData::Stream(Box::pin(RecordBatchStreamWrapper::new(schema, stream)))
340 }
341 };
342 Ok(Output::new(data, output.meta))
343}
344
345fn take_response_rows(
346 batch: RecordBatch,
347 remaining: &mut usize,
348) -> RecordBatchResult<Option<RecordBatch>> {
349 let num_rows = batch.num_rows().min(*remaining);
350 *remaining -= num_rows;
351 if num_rows == 0 {
352 Ok(None)
353 } else if num_rows == batch.num_rows() {
354 Ok(Some(batch))
355 } else {
356 batch.slice(0, num_rows).map(Some)
357 }
358}
359
360#[axum_macros::debug_handler]
369#[tracing::instrument(
370 skip_all,
371 fields(protocol = "http", request_type = "sql_analyze_stream")
372)]
373pub async fn sql_analyze_stream(
374 State(state): State<ApiState>,
375 Query(query_params): Query<SqlQuery>,
376 Extension(mut query_ctx): Extension<QueryContext>,
377 form_params: std::result::Result<Form<SqlQuery>, FormRejection>,
378) -> Response {
379 let start = Instant::now();
380 let form_params = match form_params {
381 Ok(Form(params)) => params,
382 Err(err) => {
383 if err.status() != axum::http::StatusCode::UNSUPPORTED_MEDIA_TYPE {
384 return ErrorResponse::from_error_message(
385 StatusCode::InvalidArguments,
386 err.body_text(),
387 )
388 .with_execution_time(start.elapsed().as_millis() as u64)
389 .into_response();
390 }
391 SqlQuery::default()
392 }
393 };
394 let sql_handler = &state.sql_handler;
395 if let Some(db) = &query_params.db.or(form_params.db) {
396 let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
397 query_ctx.set_current_catalog(&catalog);
398 query_ctx.set_current_schema(&schema);
399 }
400 query_ctx.set_channel(Channel::HttpSql);
401 query_ctx.enable_live_analyze_metrics();
402 let query_ctx = Arc::new(query_ctx);
403
404 let Some(sql) = query_params.sql.or(form_params.sql) else {
405 return ErrorResponse::from_error_message(
406 StatusCode::InvalidArguments,
407 "sql parameter is required.".to_string(),
408 )
409 .with_execution_time(start.elapsed().as_millis() as u64)
410 .into_response();
411 };
412 if let Some((status, msg)) = validate_schema(sql_handler.clone(), query_ctx.clone()).await {
413 return ErrorResponse::from_error_message(status, msg)
414 .with_execution_time(start.elapsed().as_millis() as u64)
415 .into_response();
416 }
417
418 let interval_ms = query_params
419 .snapshot_interval_ms
420 .or(form_params.snapshot_interval_ms)
421 .unwrap_or(DEFAULT_ANALYZE_SNAPSHOT_INTERVAL_MS)
422 .clamp(
423 MIN_ANALYZE_SNAPSHOT_INTERVAL_MS,
424 MAX_ANALYZE_SNAPSHOT_INTERVAL_MS,
425 );
426
427 let output = match state
428 .sql_handler
429 .do_analyze_stream_query(&sql, query_ctx.clone())
430 .await
431 {
432 Ok(output) => output,
433 Err(err) => {
434 return ErrorResponse::from_error(err)
435 .with_execution_time(start.elapsed().as_millis() as u64)
436 .into_response();
437 }
438 };
439
440 let plan = output.meta.plan.clone();
441 let OutputData::Stream(stream) = output.data else {
442 return ErrorResponse::from_error_message(
443 StatusCode::InvalidArguments,
444 "analyze stream query must return a stream".to_string(),
445 )
446 .with_execution_time(start.elapsed().as_millis() as u64)
447 .into_response();
448 };
449 let schema = stream.schema();
450
451 let (metrics_tx, metrics_rx) = watch::channel::<Option<String>>(None);
452 let (terminal_tx, terminal_rx) = watch::channel::<Option<AnalyzeStreamMessage>>(None);
453 let notify = Arc::new(Notify::new());
454 let (cancel_tx, mut cancel_rx) = watch::channel(false);
455 let worker_notify = notify.clone();
456 let panic_notify = notify.clone();
457 let sequence = Arc::new(AtomicU64::new(0));
458 let worker_sequence = sequence.clone();
459 let worker_terminal_tx = terminal_tx.clone();
460 let worker = common_runtime::spawn_global(async move {
461 let worker_result = AssertUnwindSafe(async move {
462 let mut stream = stream;
463 let mut batches = Vec::new();
464 let mut current_interval_ms = interval_ms;
465 let tick = tokio::time::sleep(Duration::from_millis(current_interval_ms));
466 tokio::pin!(tick);
467
468 loop {
469 tokio::select! {
470 _ = cancel_rx.changed() => return,
471 item = stream.next() => {
472 match item {
473 Some(Ok(next_batch)) => batches.push(next_batch),
474 Some(Err(err)) => {
475 let status = err.status_code();
476 let event_name = if status == StatusCode::Cancelled { "canceled" } else { "error" };
477 let (payload, _) = make_analyze_payload(AnalyzePayloadArgs {
478 seq: worker_sequence.load(Ordering::Relaxed),
479 state: event_name,
480 partial: false,
481 start,
482 plan: plan.as_ref(),
483 output: None,
484 reason: Some(err.output_msg()),
485 code: Some(status as u32),
486 });
487 send_analyze_terminal(&terminal_tx, &worker_notify, event_name, payload);
488 return;
489 }
490 None => {
491 let output = HttpRecordsOutput::try_new(schema.clone(), batches)
492 .map(GreptimeQueryOutput::Records);
493 let (event_name, payload) = make_final_analyze_event(
494 output.map_err(|err| (err.output_msg(), err.status_code() as u32)),
495 worker_sequence.load(Ordering::Relaxed),
496 start,
497 plan.as_ref(),
498 );
499 send_analyze_terminal(&terminal_tx, &worker_notify, event_name, payload);
500 return;
501 }
502 }
503 }
504 _ = &mut tick, if plan.is_some() => {
505 let (payload, payload_bytes) = make_analyze_payload(AnalyzePayloadArgs {
506 seq: worker_sequence.load(Ordering::Relaxed),
507 state: "metrics",
508 partial: true,
509 start,
510 plan: plan.as_ref(),
511 output: None,
512 reason: None,
513 code: None,
514 });
515 current_interval_ms = adaptive_interval_ms(payload_bytes, interval_ms);
516 worker_sequence.fetch_add(1, Ordering::Relaxed);
517 if metrics_tx.send(Some(payload)).is_err() {
518 return;
519 }
520 worker_notify.notify_one();
521 tick.as_mut().reset(tokio::time::Instant::now() + Duration::from_millis(current_interval_ms));
522 }
523 }
524 }
525 })
526 .catch_unwind()
527 .await;
528
529 if worker_result.is_err() {
530 tracing::debug!("analyze stream worker panicked");
531 let (payload, _) = make_analyze_payload(AnalyzePayloadArgs {
532 seq: sequence.load(Ordering::Relaxed),
533 state: "error",
534 partial: false,
535 start,
536 plan: None,
537 output: None,
538 reason: Some("analyze stream worker panicked".to_string()),
539 code: Some(StatusCode::Internal as u32),
540 });
541 send_analyze_terminal(&worker_terminal_tx, &panic_notify, "error", payload);
542 }
543 });
544
545 let sse_stream = analyze_stream_body(metrics_rx, terminal_rx, notify, cancel_tx, worker);
546
547 Sse::new(sse_stream)
548 .keep_alive(KeepAlive::new().interval(Duration::from_secs(15)))
549 .into_response()
550}
551
552#[doc(hidden)]
553pub fn analyze_stream_body(
554 metrics: watch::Receiver<Option<String>>,
555 terminal: watch::Receiver<Option<AnalyzeStreamMessage>>,
556 notify: Arc<Notify>,
557 cancel: watch::Sender<bool>,
558 worker: common_runtime::JoinHandle<()>,
559) -> impl futures::Stream<Item = std::result::Result<Event, std::convert::Infallible>> {
560 futures::stream::unfold(
561 AnalyzeStreamBodyState {
562 latest_metrics: metrics,
563 terminal,
564 notify,
565 _worker: AnalyzeStreamWorkerGuard {
566 cancel,
567 handle: worker,
568 },
569 done: false,
570 },
571 |mut state| async move {
572 if state.done {
573 return None;
574 }
575 loop {
576 let notify = Arc::clone(&state.notify);
577 let notified = notify.notified();
578 tokio::pin!(notified);
579 notified.as_mut().enable();
581 let latest = {
582 let latest = state.latest_metrics.borrow_and_update();
583 latest.has_changed().then(|| latest.clone())
584 };
585 if let Some(Some(payload)) = latest {
586 return Some((Ok(Event::default().event("metrics").data(payload)), state));
587 }
588 if state.terminal.borrow().is_some() {
591 let terminal = { state.terminal.borrow_and_update().clone() };
592 if let Some(AnalyzeStreamMessage {
593 event_name,
594 payload,
595 }) = terminal
596 {
597 state.done = true;
598 return Some((Ok(Event::default().event(event_name).data(payload)), state));
599 }
600 }
601 notified.await;
602 }
603 },
604 )
605}
606
607#[doc(hidden)]
608pub fn send_analyze_terminal(
609 terminal_tx: &watch::Sender<Option<AnalyzeStreamMessage>>,
610 notify: &Notify,
611 event_name: &'static str,
612 payload: String,
613) {
614 if terminal_tx.send_if_modified(|terminal| {
615 if terminal.is_none() {
616 *terminal = Some(AnalyzeStreamMessage {
617 event_name,
618 payload,
619 });
620 true
621 } else {
622 false
623 }
624 }) {
625 notify.notify_one();
626 }
627}
628
629fn adaptive_interval_ms(payload_bytes: usize, requested_ms: u64) -> u64 {
630 if payload_bytes >= 10 * 1024 * 1024 {
631 requested_ms.max(30_000)
632 } else if payload_bytes >= 1024 * 1024 {
633 requested_ms.max(10_000)
634 } else {
635 requested_ms
636 }
637}
638
639fn make_final_analyze_event(
640 output: std::result::Result<GreptimeQueryOutput, (String, u32)>,
641 seq: u64,
642 start: Instant,
643 plan: Option<&Arc<dyn ExecutionPlan>>,
644) -> (&'static str, String) {
645 match output {
646 Ok(output) => (
647 "final",
648 make_analyze_payload(AnalyzePayloadArgs {
649 seq,
650 state: "final",
651 partial: false,
652 start,
653 plan,
654 output: Some(output),
655 reason: None,
656 code: None,
657 })
658 .0,
659 ),
660 Err((reason, code)) => (
661 "error",
662 make_analyze_payload(AnalyzePayloadArgs {
663 seq,
664 state: "error",
665 partial: false,
666 start,
667 plan,
668 output: None,
669 reason: Some(reason),
670 code: Some(code),
671 })
672 .0,
673 ),
674 }
675}
676
677struct AnalyzePayloadArgs<'a> {
678 seq: u64,
679 state: &'static str,
680 partial: bool,
681 start: Instant,
682 plan: Option<&'a Arc<dyn ExecutionPlan>>,
683 output: Option<GreptimeQueryOutput>,
684 reason: Option<String>,
685 code: Option<u32>,
686}
687
688fn make_analyze_payload(args: AnalyzePayloadArgs<'_>) -> (String, usize) {
689 let AnalyzePayloadArgs {
690 seq,
691 state,
692 partial,
693 start,
694 plan,
695 output,
696 reason,
697 code,
698 } = args;
699 let metrics =
701 plan.and_then(|plan| query::analyze_plan_metrics_to_json_value(plan, !partial).ok());
702 let payload = AnalyzeStreamPayload {
703 seq,
704 state,
705 partial,
706 elapsed_ms: start.elapsed().as_millis() as u64,
707 metrics,
708 output,
709 reason,
710 code,
711 };
712 let payload_string = serde_json::to_string(&payload).unwrap_or_else(|e| {
713 serde_json::json!({
714 "seq": seq,
715 "state": "error",
716 "partial": false,
717 "reason": format!("Failed to serialize SSE payload: {e}"),
718 })
719 .to_string()
720 });
721 let payload_bytes = payload_string.len();
722 (payload_string, payload_bytes)
723}
724
725#[axum_macros::debug_handler]
727#[tracing::instrument(skip_all, fields(protocol = "http", request_type = "sql"))]
728pub async fn sql_parse(
729 Query(query_params): Query<SqlQuery>,
730 Form(form_params): Form<SqlQuery>,
731) -> Result<Json<Vec<Statement>>> {
732 let Some(sql) = query_params.sql.or(form_params.sql) else {
733 return InvalidQuerySnafu {
734 reason: "sql parameter is required.",
735 }
736 .fail();
737 };
738
739 let stmts =
740 ParserContext::create_with_dialect(&sql, &GreptimeDbDialect {}, ParseOptions::default())
741 .context(FailedToParseQuerySnafu)?;
742
743 Ok(stmts.into())
744}
745
746#[derive(Debug, Serialize, Deserialize)]
747pub struct SqlFormatResponse {
748 pub formatted: String,
749}
750
751#[axum_macros::debug_handler]
753#[tracing::instrument(skip_all, fields(protocol = "http", request_type = "sql_format"))]
754pub async fn sql_format(
755 Query(query_params): Query<SqlQuery>,
756 Form(form_params): Form<SqlQuery>,
757) -> axum::response::Response {
758 let Some(sql) = query_params.sql.or(form_params.sql) else {
759 let resp = ErrorResponse::from_error_message(
760 StatusCode::InvalidArguments,
761 "sql parameter is required.".to_string(),
762 );
763 return HttpResponse::Error(resp).into_response();
764 };
765
766 let stmts = match ParserContext::create_with_dialect(
768 &sql,
769 &GreptimeDbDialect {},
770 ParseOptions::default(),
771 ) {
772 Ok(v) => v,
773 Err(e) => return HttpResponse::Error(ErrorResponse::from_error(e)).into_response(),
774 };
775
776 let mut parts: Vec<String> = Vec::with_capacity(stmts.len());
777 for stmt in stmts {
778 let mut s = format!("{stmt}");
779 if !s.trim_end().ends_with(';') {
780 s.push(';');
781 }
782 parts.push(s);
783 }
784
785 let formatted = parts.join("\n");
786 Json(SqlFormatResponse { formatted }).into_response()
787}
788
789pub async fn from_output(
791 outputs: Vec<crate::error::Result<Output>>,
792) -> std::result::Result<(Vec<GreptimeQueryOutput>, HashMap<String, Value>), ErrorResponse> {
793 let mut results = Vec::with_capacity(outputs.len());
796 let mut merge_map = HashMap::new();
797
798 for out in outputs {
799 match out {
800 Ok(o) => match o.data {
801 OutputData::AffectedRows(rows) => {
802 results.push(GreptimeQueryOutput::AffectedRows(rows));
803 if o.meta.cost > 0 {
804 merge_map.insert(GREPTIME_EXEC_WRITE_COST.to_string(), o.meta.cost as u64);
805 }
806 }
807 OutputData::Stream(stream) => {
808 let schema = stream.schema().clone();
809 let mut http_record_output = match util::collect(stream).await {
811 Ok(rows) => match HttpRecordsOutput::try_new(schema, rows) {
812 Ok(rows) => rows,
813 Err(err) => {
814 return Err(ErrorResponse::from_error(err));
815 }
816 },
817 Err(err) => {
818 return Err(ErrorResponse::from_error(err));
819 }
820 };
821 if let Some(physical_plan) = o.meta.plan {
822 let mut result_map = HashMap::new();
823
824 let mut tmp = vec![&mut merge_map, &mut result_map];
825 collect_plan_metrics(&physical_plan, &mut tmp);
826 let re = result_map
827 .into_iter()
828 .map(|(k, v)| (k, Value::from(v)))
829 .collect::<HashMap<String, Value>>();
830 http_record_output.metrics.extend(re);
831 }
832 results.push(GreptimeQueryOutput::Records(http_record_output))
833 }
834 OutputData::RecordBatches(rbs) => {
835 match HttpRecordsOutput::try_new(rbs.schema(), rbs.take()) {
836 Ok(rows) => {
837 results.push(GreptimeQueryOutput::Records(rows));
838 }
839 Err(err) => {
840 return Err(ErrorResponse::from_error(err));
841 }
842 }
843 }
844 },
845
846 Err(err) => {
847 return Err(ErrorResponse::from_error(err));
848 }
849 }
850 }
851
852 let merge_map = merge_map
853 .into_iter()
854 .map(|(k, v)| (k, Value::from(v)))
855 .collect();
856
857 Ok((results, merge_map))
858}
859
860#[derive(Debug, Default, Serialize, Deserialize)]
861pub struct PromqlQuery {
862 pub query: String,
863 pub start: String,
864 pub end: String,
865 pub step: String,
866 pub lookback: Option<String>,
867 pub db: Option<String>,
868 pub format: Option<String>,
872 pub compression: Option<String>,
874 pub epoch: Option<String>,
885}
886
887impl From<PromqlQuery> for PromQuery {
888 fn from(query: PromqlQuery) -> Self {
889 PromQuery {
890 query: query.query,
891 start: query.start,
892 end: query.end,
893 step: query.step,
894 lookback: query
895 .lookback
896 .unwrap_or_else(|| DEFAULT_LOOKBACK_STRING.to_string()),
897 alias: None,
899 }
900 }
901}
902
903#[axum_macros::debug_handler]
905#[tracing::instrument(skip_all, fields(protocol = "http", request_type = "promql"))]
906pub async fn promql(
907 State(state): State<ApiState>,
908 Query(params): Query<PromqlQuery>,
909 Extension(mut query_ctx): Extension<QueryContext>,
910) -> Response {
911 let sql_handler = &state.sql_handler;
912 let exec_start = Instant::now();
913 let db = query_ctx.get_db_string();
914
915 query_ctx.set_channel(Channel::Promql);
916 let query_ctx = Arc::new(query_ctx);
917
918 let _timer = crate::metrics::METRIC_HTTP_PROMQL_ELAPSED
919 .with_label_values(&[db.as_str()])
920 .start_timer();
921
922 let resp = if let Some((status, msg)) =
923 validate_schema(sql_handler.clone(), query_ctx.clone()).await
924 {
925 let resp = ErrorResponse::from_error_message(status, msg);
926 HttpResponse::Error(resp)
927 } else {
928 let format = params
929 .format
930 .as_ref()
931 .map(|s| s.to_lowercase())
932 .map(|s| ResponseFormat::parse(s.as_str()).unwrap_or(ResponseFormat::GreptimedbV1))
933 .unwrap_or(ResponseFormat::GreptimedbV1);
934 let epoch = params
935 .epoch
936 .as_ref()
937 .map(|s| s.to_lowercase())
938 .map(|s| Epoch::parse(s.as_str()).unwrap_or(Epoch::Millisecond));
939 let compression = params.compression.clone();
940
941 let prom_query = params.into();
942 let outputs = sql_handler.do_promql_query(&prom_query, query_ctx).await;
943
944 match format {
945 ResponseFormat::Arrow => ArrowResponse::from_output(outputs, compression).await,
946 ResponseFormat::Csv(with_names, with_types) => {
947 CsvResponse::from_output(outputs, with_names, with_types).await
948 }
949 ResponseFormat::Table => TableResponse::from_output(outputs).await,
950 ResponseFormat::GreptimedbV1 => GreptimedbV1Response::from_output(outputs).await,
951 ResponseFormat::InfluxdbV1 => InfluxdbV1Response::from_output(outputs, epoch).await,
952 ResponseFormat::Json => JsonResponse::from_output(outputs).await,
953 ResponseFormat::Null => NullResponse::from_output(outputs).await,
954 }
955 };
956
957 resp.with_execution_time(exec_start.elapsed().as_millis() as u64)
958 .into_response()
959}
960
961#[axum_macros::debug_handler]
963pub async fn metrics(
964 State(state): State<MetricsHandler>,
965 Query(_params): Query<HashMap<String, String>>,
966) -> String {
967 #[cfg(not(windows))]
972 if let Some(c) = crate::metrics::jemalloc::JEMALLOC_COLLECTOR.as_ref()
973 && let Err(e) = c.update()
974 {
975 common_telemetry::error!(e; "Failed to update jemalloc metrics");
976 }
977 state.render()
978}
979
980#[derive(Debug, Serialize, Deserialize)]
981pub struct HealthQuery {}
982
983#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
984pub struct HealthResponse {}
985
986#[axum_macros::debug_handler]
990pub async fn health(Query(_params): Query<HealthQuery>) -> Json<HealthResponse> {
991 Json(HealthResponse {})
992}
993
994#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
995pub struct StatusResponse<'a> {
996 pub commit: &'a str,
997 pub branch: &'a str,
998 pub rustc_version: &'a str,
999 pub hostname: String,
1000 pub version: &'a str,
1001}
1002
1003#[axum_macros::debug_handler]
1005pub async fn status() -> Json<StatusResponse<'static>> {
1006 let hostname = hostname::get()
1007 .map(|s| s.to_string_lossy().to_string())
1008 .unwrap_or_else(|_| "unknown".to_string());
1009 let build_info = common_version::build_info();
1010 Json(StatusResponse {
1011 commit: build_info.commit,
1012 branch: build_info.branch,
1013 rustc_version: build_info.rustc,
1014 hostname,
1015 version: build_info.version,
1016 })
1017}
1018
1019#[axum_macros::debug_handler]
1021pub async fn config(State(state): State<GreptimeOptionsConfigState>) -> Response {
1022 (axum::http::StatusCode::OK, state.greptime_config_options).into_response()
1023}
1024
1025async fn validate_schema(
1026 sql_handler: ServerSqlQueryHandlerRef,
1027 query_ctx: QueryContextRef,
1028) -> Option<(StatusCode, String)> {
1029 match sql_handler
1030 .is_valid_schema(query_ctx.current_catalog(), &query_ctx.current_schema())
1031 .await
1032 {
1033 Ok(true) => None,
1034 Ok(false) => Some((
1035 StatusCode::DatabaseNotFound,
1036 format!("Database not found: {}", query_ctx.get_db_string()),
1037 )),
1038 Err(e) => Some((
1039 StatusCode::Internal,
1040 format!(
1041 "Error checking database: {}, {}",
1042 query_ctx.get_db_string(),
1043 e.output_msg(),
1044 ),
1045 )),
1046 }
1047}
1048
1049pub async fn index() -> axum::response::Html<String> {
1050 let name = common_version::product_name();
1051 let version = common_version::version();
1052 axum::response::Html(format!(
1053 r#"<!DOCTYPE html>
1054<html>
1055<head><title>{name}</title></head>
1056<body>
1057<h1>{name}</h1>
1058<p>Version: {version}</p>
1059<ul>
1060<li><a href="/dashboard">Dashboard UI</a></li>
1061<li><a href="/v1/health">Health</a> (JSON)</li>
1062<li><a href="/status">Status</a> (JSON)</li>
1063<li><a href="/metrics">Metrics</a> (For Prometheus Scrape)</li>
1064<li><a href="/config">Config</a> (TXT)</li>
1065</ul>
1066</body>
1067</html>"#,
1068 ))
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use std::sync::Mutex;
1074 use std::sync::atomic::AtomicUsize;
1075
1076 use async_trait::async_trait;
1077 use common_error::ext::{BoxedError, PlainError};
1078 use common_query::OutputMeta;
1079 use common_telemetry::tracing::instrument::WithSubscriber;
1080 use datafusion_expr::LogicalPlan;
1081 use datatypes::prelude::ConcreteDataType;
1082 use datatypes::schema::{ColumnSchema, Schema};
1083 use datatypes::vectors::{BinaryVector, UInt32Vector, VectorRef};
1084 use futures::stream;
1085 use query::query_engine::DescribeResult;
1086 use snafu::IntoError;
1087
1088 use super::*;
1089 use crate::query_handler::sql::SqlQueryHandler;
1090
1091 struct TestSqlQueryHandler {
1092 outputs: Mutex<Vec<Result<Output>>>,
1093 }
1094
1095 #[async_trait]
1096 impl SqlQueryHandler for TestSqlQueryHandler {
1097 async fn do_query(&self, _: &str, _: QueryContextRef) -> Vec<Result<Output>> {
1098 std::mem::take(&mut *self.outputs.lock().unwrap())
1099 }
1100
1101 async fn create_logical_tables(&self, _: &str, _: QueryContextRef) -> Result<Vec<Output>> {
1102 std::mem::take(&mut *self.outputs.lock().unwrap())
1103 .into_iter()
1104 .collect()
1105 }
1106
1107 async fn do_analyze_stream_query(&self, _: &str, _: QueryContextRef) -> Result<Output> {
1108 unimplemented!()
1109 }
1110
1111 async fn do_exec_plan(
1112 &self,
1113 _: LogicalPlan,
1114 _: Option<Statement>,
1115 _: QueryContextRef,
1116 ) -> Result<Output> {
1117 unimplemented!()
1118 }
1119
1120 async fn do_promql_query(&self, _: &PromQuery, _: QueryContextRef) -> Vec<Result<Output>> {
1121 unimplemented!()
1122 }
1123
1124 async fn do_describe(
1125 &self,
1126 _: Statement,
1127 _: QueryContextRef,
1128 ) -> Result<Option<DescribeResult>> {
1129 unimplemented!()
1130 }
1131
1132 async fn is_valid_schema(&self, _: &str, _: &str) -> Result<bool> {
1133 Ok(true)
1134 }
1135 }
1136
1137 async fn sql_response(
1138 outputs: Vec<Result<Output>>,
1139 format: &str,
1140 limit: Option<usize>,
1141 ) -> HttpResponse {
1142 sql(
1143 State(ApiState {
1144 sql_handler: Arc::new(TestSqlQueryHandler {
1145 outputs: Mutex::new(outputs),
1146 }),
1147 }),
1148 Query(SqlQuery {
1149 sql: Some("select number from numbers".to_string()),
1150 format: Some(format.to_string()),
1151 limit,
1152 ..Default::default()
1153 }),
1154 Extension(QueryContext::with_db_name(None)),
1155 Form(SqlQuery::default()),
1156 )
1157 .await
1158 }
1159
1160 #[tokio::test]
1161 async fn test_logical_ddl_logs_diagnostics_without_exposing_sql_or_response_details() {
1162 let sql = "CREATE TABLE logs (ts TIMESTAMP TIME INDEX) ENGINE=metric WITH (on_physical_table='request-only-value')";
1163 for (status, level) in [
1164 (StatusCode::InvalidArguments, "WARN"),
1165 (StatusCode::Internal, "ERROR"),
1166 ] {
1167 let logs = tempfile::NamedTempFile::new().unwrap();
1168 let writer = logs.reopen().unwrap();
1169 let subscriber = common_telemetry::tracing_subscriber::fmt()
1170 .without_time()
1171 .with_ansi(false)
1172 .with_writer(move || writer.try_clone().unwrap())
1173 .finish();
1174 let error = crate::error::ExecuteQuerySnafu.into_error(BoxedError::new(
1175 PlainError::new("sensitive-option-value".to_string(), status),
1176 ));
1177 let response = create_logical_tables(
1178 State(ApiState {
1179 sql_handler: Arc::new(TestSqlQueryHandler {
1180 outputs: Mutex::new(vec![Err(error)]),
1181 }),
1182 }),
1183 Extension(QueryContext::with_db_name(None)),
1184 Ok(Form(LogicalTableDdl {
1185 sql: Some(sql.to_string()),
1186 db: None,
1187 })),
1188 )
1189 .with_subscriber(subscriber)
1190 .await
1191 .into_response();
1192 let diagnostics = std::fs::read_to_string(logs.path()).unwrap();
1193 assert!(diagnostics.contains(level), "{diagnostics}");
1194 assert!(
1195 diagnostics.contains("Failed to execute query"),
1196 "{diagnostics}"
1197 );
1198 assert!(
1199 diagnostics.contains("sensitive-option-value"),
1200 "{diagnostics}"
1201 );
1202 assert!(!diagnostics.contains(sql), "{diagnostics}");
1203 assert!(!diagnostics.contains("request-only-value"), "{diagnostics}");
1204 assert!(!format!("{:?}", response.headers()).contains("sensitive-option-value"));
1205 let bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
1206 .await
1207 .unwrap();
1208 let body: Value = serde_json::from_slice(&bytes).unwrap();
1209 assert_eq!(body["code"], status as u32);
1210 assert_eq!(body["error"], "logical-table batch failed");
1211 assert!(body.get("output").is_none());
1212 }
1213 }
1214
1215 fn number_batches() -> RecordBatches {
1216 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1217 "number",
1218 ConcreteDataType::uint32_datatype(),
1219 false,
1220 )]));
1221 let batches = [vec![], vec![0, 1], vec![], vec![2, 3, 4], vec![]]
1222 .into_iter()
1223 .map(|values| {
1224 let columns: Vec<VectorRef> = vec![Arc::new(UInt32Vector::from_slice(values))];
1225 RecordBatch::new(schema.clone(), columns).unwrap()
1226 })
1227 .collect();
1228 RecordBatches::try_new(schema, batches).unwrap()
1229 }
1230
1231 fn number_output(streaming: bool) -> Output {
1232 let batches = number_batches();
1233 if streaming {
1234 Output::new_with_stream(batches.as_stream())
1235 } else {
1236 Output::new_with_record_batches(batches)
1237 }
1238 }
1239
1240 #[tokio::test]
1241 async fn test_sql_response_limit_across_batches_and_formats() {
1242 for format in [
1243 "greptimedb_v1",
1244 "json",
1245 "csv",
1246 "csvWithNames",
1247 "csvWithNamesAndTypes",
1248 "table",
1249 ] {
1250 for streaming in [false, true] {
1251 for limit in [
1252 None,
1253 Some(0),
1254 Some(1),
1255 Some(2),
1256 Some(3),
1257 Some(5),
1258 Some(10),
1259 Some(usize::MAX),
1260 ] {
1261 let response =
1262 sql_response(vec![Ok(number_output(streaming))], format, limit).await;
1263 let output = match &response {
1264 HttpResponse::GreptimedbV1(response) => response.output(),
1265 HttpResponse::Json(response) => response.output(),
1266 HttpResponse::Csv(response) => response.output(),
1267 HttpResponse::Table(response) => response.output(),
1268 _ => panic!("unexpected response: {response:?}"),
1269 };
1270 let GreptimeQueryOutput::Records(records) = &output[0] else {
1271 panic!("expected records");
1272 };
1273 let expected_rows: Vec<_> = (0..limit.unwrap_or(5).min(5))
1274 .map(|number| vec![Value::from(number)])
1275 .collect();
1276 assert_eq!(records.rows(), &expected_rows);
1277 assert_eq!(records.total_rows, expected_rows.len());
1278 assert_eq!(records.schema.column_schemas[0].name, "number");
1279 }
1280 }
1281 }
1282 }
1283
1284 #[tokio::test]
1285 async fn test_sql_response_limit_is_per_statement_and_preserves_write_cost() {
1286 let affected = Output::new(
1287 OutputData::AffectedRows(7),
1288 OutputMeta {
1289 cost: 42,
1290 ..Default::default()
1291 },
1292 );
1293 let response = sql_response(
1294 vec![
1295 Ok(number_output(true)),
1296 Ok(affected),
1297 Ok(number_output(false)),
1298 ],
1299 "greptimedb_v1",
1300 Some(1),
1301 )
1302 .await;
1303 let HttpResponse::GreptimedbV1(response) = response else {
1304 panic!("expected greptimedb response");
1305 };
1306 assert_eq!(response.output().len(), 3);
1307 for index in [0, 2] {
1308 let GreptimeQueryOutput::Records(records) = &response.output()[index] else {
1309 panic!("expected records");
1310 };
1311 assert_eq!(records.rows(), &vec![vec![Value::from(0)]]);
1312 }
1313 assert!(matches!(
1314 response.output()[1],
1315 GreptimeQueryOutput::AffectedRows(7)
1316 ));
1317 assert_eq!(
1318 response.resp_metrics[GREPTIME_EXEC_WRITE_COST],
1319 Value::from(42)
1320 );
1321 }
1322
1323 #[tokio::test]
1324 async fn test_sql_response_limit_does_not_affect_other_formats() {
1325 for format in ["arrow", "influxdb_v1", "null"] {
1326 let unlimited = sql_response(vec![Ok(number_output(true))], format, None).await;
1327 let limited = sql_response(vec![Ok(number_output(true))], format, Some(0)).await;
1328 assert_eq!(
1329 serde_json::to_value(unlimited.with_execution_time(0)).unwrap(),
1330 serde_json::to_value(limited.with_execution_time(0)).unwrap(),
1331 );
1332 }
1333 }
1334
1335 #[tokio::test]
1336 async fn test_sql_response_limit_drains_input_and_propagates_late_errors() {
1337 for limit in [0, 1, 3] {
1338 for late_error in [false, true] {
1339 let batches = number_batches();
1340 let schema = batches.schema();
1341 let mut input: Vec<_> = batches.into_iter().map(Ok).collect();
1342 if late_error {
1343 input.push(
1344 common_recordbatch::error::CreateRecordBatchesSnafu {
1345 reason: "late stream error",
1346 }
1347 .fail(),
1348 );
1349 }
1350 let expected_batches = input.len();
1351 let observed = Arc::new(AtomicUsize::new(0));
1352 let counter = observed.clone();
1353 let stream = stream::iter(input).inspect(move |_| {
1354 counter.fetch_add(1, Ordering::Relaxed);
1355 });
1356 let output = Output::new_with_stream(Box::pin(RecordBatchStreamWrapper::new(
1357 schema, stream,
1358 )));
1359 let response = sql_response(vec![Ok(output)], "greptimedb_v1", Some(limit)).await;
1360 assert_eq!(observed.load(Ordering::Relaxed), expected_batches);
1361 assert_eq!(matches!(response, HttpResponse::Error(_)), late_error);
1362 }
1363 }
1364 }
1365
1366 #[tokio::test]
1367 async fn test_sql_response_limit_skips_conversion_of_discarded_rows() {
1368 let schema = Arc::new(Schema::new(vec![ColumnSchema::new(
1369 "payload",
1370 ConcreteDataType::json_datatype(),
1371 false,
1372 )]));
1373 let columns: Vec<VectorRef> = vec![Arc::new(BinaryVector::from(vec![
1374 datatypes::types::parse_string_to_jsonb(r#"{"ok":1}"#).unwrap(),
1375 b"invalid jsonb".to_vec(),
1376 ]))];
1377 let batch = RecordBatch::new(schema.clone(), columns).unwrap();
1378 for streaming in [false, true] {
1379 for limit in [0, 1, 2] {
1380 let batches = RecordBatches::try_new(schema.clone(), vec![batch.clone()]).unwrap();
1381 let output = if streaming {
1382 Output::new_with_stream(batches.as_stream())
1383 } else {
1384 Output::new_with_record_batches(batches)
1385 };
1386 let response = sql_response(vec![Ok(output)], "greptimedb_v1", Some(limit)).await;
1387 assert_eq!(matches!(response, HttpResponse::Error(_)), limit == 2);
1389 }
1390 }
1391 }
1392
1393 #[test]
1394 fn test_final_analyze_event_uses_error_event_for_conversion_error() {
1395 let (event_name, payload) = make_final_analyze_event(
1396 Err((
1397 "conversion failed".to_string(),
1398 StatusCode::InvalidArguments as u32,
1399 )),
1400 7,
1401 Instant::now(),
1402 None,
1403 );
1404
1405 assert_eq!(event_name, "error");
1406 let value: Value = serde_json::from_str(&payload).unwrap();
1407 assert_eq!(value["state"], "error");
1408 assert_eq!(value["reason"], "conversion failed");
1409 }
1410}