1use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17
18use async_trait::async_trait;
19use auth::{JAEGER_QUERY, PermissionReq, PermissionTableTarget, PermissionTableTargets};
20use catalog::CatalogManagerRef;
21use common_catalog::consts::{
22 TRACE_TABLE_NAME, trace_operations_table_name, trace_services_table_name,
23};
24use common_function::function::FunctionRef;
25use common_function::scalars::json::json_get::{
26 JsonGetBool, JsonGetFloat, JsonGetInt, JsonGetString, JsonGetWithType,
27};
28use common_function::scalars::udf::create_udf;
29use common_query::{Output, OutputData};
30use common_recordbatch::adapter::RecordBatchStreamAdapter;
31use common_recordbatch::util;
32use common_telemetry::warn;
33use datafusion::common::ScalarValue;
34use datafusion::dataframe::DataFrame;
35use datafusion::execution::SessionStateBuilder;
36use datafusion::execution::context::SessionContext;
37use datafusion::functions::core::expr_fn::coalesce;
38use datafusion::functions_aggregate::expr_fn::max;
39use datafusion::functions_window::expr_fn::row_number;
40use datafusion_expr::select_expr::SelectExpr;
41use datafusion_expr::{Expr, ExprFunctionExt, SortExpr, col, lit, lit_timestamp_nano, wildcard};
42use query::QueryEngineRef;
43use serde_json::Value as JsonValue;
44use servers::error::{
45 AuthSnafu, CollectRecordbatchSnafu, DataFusionSnafu, Result as ServerResult, TableNotFoundSnafu,
46};
47use servers::http::jaeger::{JAEGER_QUERY_TABLE_NAME_KEY, QueryTraceParams, TraceUserAgent};
48use servers::otlp::trace::{
49 DURATION_NANO_COLUMN, KEY_OTEL_STATUS_ERROR_KEY, RESOURCE_ATTRIBUTES_COLUMN,
50 SERVICE_NAME_COLUMN, SPAN_ATTRIBUTES_COLUMN, SPAN_KIND_COLUMN, SPAN_KIND_PREFIX,
51 SPAN_NAME_COLUMN, SPAN_STATUS_CODE, SPAN_STATUS_ERROR, TIMESTAMP_COLUMN, TRACE_ID_COLUMN,
52};
53use servers::query_handler::JaegerQueryHandler;
54use session::context::QueryContextRef;
55use snafu::{OptionExt, ResultExt};
56use table::TableRef;
57use table::table::adapter::DfTableProviderAdapter;
58
59use crate::instance::Instance;
60
61const DEFAULT_LIMIT: usize = 2000;
62const KEY_RN: &str = "greptime_rn";
63const KEY_LATEST_MATCH: &str = "greptime_latest_match";
64
65impl Instance {
66 async fn check_jaeger_query_permission(&self, ctx: &QueryContextRef) -> ServerResult<()> {
67 let table = ctx
68 .extension(JAEGER_QUERY_TABLE_NAME_KEY)
69 .unwrap_or(TRACE_TABLE_NAME);
70 let targets = PermissionTableTargets::resolved(vec![PermissionTableTarget::new(
71 ctx.current_catalog(),
72 ctx.current_schema(),
73 table,
74 )]);
75 let targets = self.resolve_query_permission_targets(targets, ctx).await?;
76 self.check_table_permission(ctx, PermissionReq::Action(JAEGER_QUERY), targets)
77 .context(AuthSnafu)?;
78 Ok(())
79 }
80}
81
82#[async_trait]
83impl JaegerQueryHandler for Instance {
84 async fn get_services(&self, ctx: QueryContextRef) -> ServerResult<Output> {
85 self.check_jaeger_query_permission(&ctx).await?;
86
87 Ok(query_trace_table(
89 ctx,
90 self,
91 vec![SelectExpr::from(col(SERVICE_NAME_COLUMN))],
92 vec![],
93 vec![],
94 None,
95 None,
96 vec![col(SERVICE_NAME_COLUMN)],
97 )
98 .await?)
99 }
100
101 async fn get_operations(
102 &self,
103 ctx: QueryContextRef,
104 service_name: &str,
105 span_kind: Option<&str>,
106 ) -> ServerResult<Output> {
107 self.check_jaeger_query_permission(&ctx).await?;
108
109 let mut filters = vec![col(SERVICE_NAME_COLUMN).eq(lit(service_name))];
110
111 if let Some(span_kind) = span_kind {
112 filters.push(col(SPAN_KIND_COLUMN).eq(lit(format!(
113 "{}{}",
114 SPAN_KIND_PREFIX,
115 span_kind.to_uppercase()
116 ))));
117 }
118
119 Ok(query_trace_table(
132 ctx,
133 self,
134 vec![
135 SelectExpr::from(col(SPAN_NAME_COLUMN)),
136 SelectExpr::from(col(SPAN_KIND_COLUMN)),
137 SelectExpr::from(col(SERVICE_NAME_COLUMN)),
138 SelectExpr::from(col(TIMESTAMP_COLUMN)),
139 ],
140 filters,
141 vec![col(SPAN_NAME_COLUMN).sort(true, false)], Some(DEFAULT_LIMIT),
143 None,
144 vec![col(SPAN_NAME_COLUMN), col(SPAN_KIND_COLUMN)],
145 )
146 .await?)
147 }
148
149 async fn get_trace(
150 &self,
151 ctx: QueryContextRef,
152 trace_id: &str,
153 start_time: Option<i64>,
154 end_time: Option<i64>,
155 limit: Option<usize>,
156 ) -> ServerResult<Output> {
157 self.check_jaeger_query_permission(&ctx).await?;
158
159 let selects = vec![wildcard()];
174
175 let mut filters = vec![col(TRACE_ID_COLUMN).eq(lit(trace_id))];
176
177 if let Some(start_time) = start_time {
178 filters.push(col(TIMESTAMP_COLUMN).gt_eq(lit_timestamp_nano(start_time)));
179 }
180
181 if let Some(end_time) = end_time {
182 filters.push(col(TIMESTAMP_COLUMN).lt_eq(lit_timestamp_nano(end_time)));
183 }
184
185 Ok(query_trace_table(
186 ctx,
187 self,
188 selects,
189 filters,
190 vec![col(TIMESTAMP_COLUMN).sort(false, false)], limit,
192 None,
193 vec![],
194 )
195 .await?)
196 }
197
198 async fn find_traces(
199 &self,
200 ctx: QueryContextRef,
201 query_params: QueryTraceParams,
202 ) -> ServerResult<Output> {
203 self.check_jaeger_query_permission(&ctx).await?;
204
205 let mut filters = vec![];
206
207 if let Some(service_name) = query_params.service_name {
208 filters.push(col(SERVICE_NAME_COLUMN).eq(lit(service_name)));
209 }
210
211 if let Some(operation_name) = query_params.operation_name {
212 filters.push(col(SPAN_NAME_COLUMN).eq(lit(operation_name)));
213 }
214
215 if let Some(start_time) = query_params.start_time {
216 filters.push(col(TIMESTAMP_COLUMN).gt_eq(lit_timestamp_nano(start_time)));
217 }
218
219 if let Some(end_time) = query_params.end_time {
220 filters.push(col(TIMESTAMP_COLUMN).lt_eq(lit_timestamp_nano(end_time)));
221 }
222
223 if let Some(min_duration) = query_params.min_duration {
224 filters.push(col(DURATION_NANO_COLUMN).gt_eq(lit(min_duration)));
225 }
226
227 if let Some(max_duration) = query_params.max_duration {
228 filters.push(col(DURATION_NANO_COLUMN).lt_eq(lit(max_duration)));
229 }
230
231 let output = find_latest_trace_ids(
250 ctx.clone(),
251 self,
252 filters,
253 query_params.limit.unwrap_or(DEFAULT_LIMIT),
254 query_params.tags,
255 )
256 .await?;
257
258 let mut filters = vec![
272 col(TRACE_ID_COLUMN).in_list(
273 trace_ids_from_output(output)
274 .await?
275 .iter()
276 .map(lit)
277 .collect::<Vec<Expr>>(),
278 false,
279 ),
280 ];
281
282 if !query_params.fetch_full_trace
283 && let Some(start_time) = query_params.start_time
284 {
285 filters.push(col(TIMESTAMP_COLUMN).gt_eq(lit_timestamp_nano(start_time)));
286 }
287
288 if !query_params.fetch_full_trace
289 && let Some(end_time) = query_params.end_time
290 {
291 filters.push(col(TIMESTAMP_COLUMN).lt_eq(lit_timestamp_nano(end_time)));
292 }
293
294 match query_params.user_agent {
295 TraceUserAgent::Grafana => {
296 let table_name = ctx
300 .extension(JAEGER_QUERY_TABLE_NAME_KEY)
301 .unwrap_or(TRACE_TABLE_NAME);
302
303 let table = get_table(ctx.clone(), self.catalog_manager(), table_name).await?;
304
305 Ok(find_traces_rank_3(
306 table,
307 self.query_engine(),
308 filters,
309 vec![col(TIMESTAMP_COLUMN).sort(false, false)], )
311 .await?)
312 }
313 _ => {
314 Ok(query_trace_table(
316 ctx,
317 self,
318 vec![wildcard()],
319 filters,
320 vec![col(TIMESTAMP_COLUMN).sort(false, false)], None,
322 None,
323 vec![],
324 )
325 .await?)
326 }
327 }
328 }
329}
330
331#[allow(clippy::too_many_arguments)]
332async fn query_trace_table(
333 ctx: QueryContextRef,
334 instance: &Instance,
335 selects: Vec<SelectExpr>,
336 filters: Vec<Expr>,
337 sorts: Vec<SortExpr>,
338 limit: Option<usize>,
339 tags: Option<HashMap<String, JsonValue>>,
340 distincts: Vec<Expr>,
341) -> ServerResult<Output> {
342 let dataframe =
343 build_trace_dataframe(&ctx, instance, selects, filters, tags, &distincts).await?;
344
345 let dataframe = if !distincts.is_empty() {
347 dataframe
348 .distinct_on(distincts.clone(), distincts, None)
349 .context(DataFusionSnafu)?
350 } else {
351 dataframe
352 };
353
354 let dataframe = if !sorts.is_empty() {
356 dataframe.sort(sorts).context(DataFusionSnafu)?
357 } else {
358 dataframe
359 };
360
361 let dataframe = if let Some(limit) = limit {
363 dataframe.limit(0, Some(limit)).context(DataFusionSnafu)?
364 } else {
365 dataframe
366 };
367
368 execute_trace_dataframe(dataframe).await
369}
370
371async fn find_latest_trace_ids(
377 ctx: QueryContextRef,
378 instance: &Instance,
379 filters: Vec<Expr>,
380 limit: usize,
381 tags: Option<HashMap<String, JsonValue>>,
382) -> ServerResult<Output> {
383 let dataframe =
384 build_trace_dataframe(&ctx, instance, vec![wildcard()], filters, tags, &[]).await?;
385 let dataframe = dataframe
386 .aggregate(
387 vec![col(TRACE_ID_COLUMN)],
388 vec![max(col(TIMESTAMP_COLUMN)).alias(KEY_LATEST_MATCH)],
389 )
390 .context(DataFusionSnafu)?
391 .sort(vec![
392 col(KEY_LATEST_MATCH).sort(false, false),
393 col(TRACE_ID_COLUMN).sort(true, false),
394 ])
395 .context(DataFusionSnafu)?
396 .limit(0, Some(limit))
397 .context(DataFusionSnafu)?
398 .select(vec![col(TRACE_ID_COLUMN)])
399 .context(DataFusionSnafu)?;
400
401 execute_trace_dataframe(dataframe).await
402}
403
404async fn build_trace_dataframe(
408 ctx: &QueryContextRef,
409 instance: &Instance,
410 selects: Vec<SelectExpr>,
411 filters: Vec<Expr>,
412 tags: Option<HashMap<String, JsonValue>>,
413 distincts: &[Expr],
414) -> ServerResult<DataFrame> {
415 let trace_table_name = ctx
416 .extension(JAEGER_QUERY_TABLE_NAME_KEY)
417 .unwrap_or(TRACE_TABLE_NAME);
418
419 let table_name = {
422 if match selects.as_slice() {
423 [SelectExpr::Expression(x)] => x == &col(SERVICE_NAME_COLUMN),
424 _ => false,
425 } {
426 &trace_services_table_name(trace_table_name)
427 } else if !distincts.is_empty()
428 && distincts.contains(&col(SPAN_NAME_COLUMN))
429 && distincts.contains(&col(SPAN_KIND_COLUMN))
430 {
431 &trace_operations_table_name(trace_table_name)
432 } else {
433 trace_table_name
434 }
435 };
436
437 let table = instance
438 .catalog_manager()
439 .table(
440 ctx.current_catalog(),
441 &ctx.current_schema(),
442 table_name,
443 Some(ctx),
444 )
445 .await?
446 .with_context(|| TableNotFoundSnafu {
447 table: table_name,
448 catalog: ctx.current_catalog(),
449 schema: ctx.current_schema(),
450 })?;
451
452 let table_info = table.table_info();
453 let data_model = table_info.meta.options.data_model();
454
455 let col_names = table_info
457 .meta
458 .field_column_names()
459 .map(|s| format!("\"{}\"", s))
460 .collect::<HashSet<String>>();
461
462 let df_context = create_df_context(instance.query_engine())?;
463
464 let dataframe = df_context
465 .read_table(Arc::new(DfTableProviderAdapter::new(table)))
466 .context(DataFusionSnafu)?;
467
468 let dataframe = dataframe.select(selects).context(DataFusionSnafu)?;
469
470 let dataframe = filters
472 .into_iter()
473 .chain(tags.map_or(Ok(vec![]), |t| {
474 tags_filters(&dataframe, t, data_model, &col_names)
475 })?)
476 .try_fold(dataframe, |df, expr| {
477 df.filter(expr).context(DataFusionSnafu)
478 })?;
479
480 Ok(dataframe)
481}
482
483async fn execute_trace_dataframe(dataframe: DataFrame) -> ServerResult<Output> {
486 let stream = dataframe.execute_stream().await.context(DataFusionSnafu)?;
487
488 let output = Output::new_with_stream(Box::pin(
489 RecordBatchStreamAdapter::try_new(stream).context(CollectRecordbatchSnafu)?,
490 ));
491
492 output
493 .map_dictionary_to_values()
494 .context(CollectRecordbatchSnafu)
495}
496
497async fn get_table(
498 ctx: QueryContextRef,
499 catalog_manager: &CatalogManagerRef,
500 table_name: &str,
501) -> ServerResult<TableRef> {
502 catalog_manager
503 .table(
504 ctx.current_catalog(),
505 &ctx.current_schema(),
506 table_name,
507 Some(&ctx),
508 )
509 .await?
510 .with_context(|| TableNotFoundSnafu {
511 table: table_name,
512 catalog: ctx.current_catalog(),
513 schema: ctx.current_schema(),
514 })
515}
516
517async fn find_traces_rank_3(
518 table: TableRef,
519 query_engine: &QueryEngineRef,
520 filters: Vec<Expr>,
521 sorts: Vec<SortExpr>,
522) -> ServerResult<Output> {
523 let df_context = create_df_context(query_engine)?;
524
525 let dataframe = df_context
526 .read_table(Arc::new(DfTableProviderAdapter::new(table)))
527 .context(DataFusionSnafu)?;
528
529 let dataframe = dataframe
530 .select(vec![wildcard()])
531 .context(DataFusionSnafu)?;
532
533 let dataframe = filters.into_iter().try_fold(dataframe, |df, expr| {
535 df.filter(expr).context(DataFusionSnafu)
536 })?;
537
538 let dataframe = if !sorts.is_empty() {
540 dataframe.sort(sorts).context(DataFusionSnafu)?
541 } else {
542 dataframe
543 };
544
545 let trace_id_col = vec![col(TRACE_ID_COLUMN)];
547 let timestamp_asc = vec![col(TIMESTAMP_COLUMN).sort(true, false)];
548
549 let dataframe = dataframe
550 .with_column(
551 KEY_RN,
552 row_number()
553 .partition_by(trace_id_col)
554 .order_by(timestamp_asc)
555 .build()
556 .context(DataFusionSnafu)?,
557 )
558 .context(DataFusionSnafu)?;
559
560 let dataframe = dataframe
561 .filter(col(KEY_RN).lt_eq(lit(3)))
562 .context(DataFusionSnafu)?;
563
564 let stream = dataframe.execute_stream().await.context(DataFusionSnafu)?;
566
567 let output = Output::new_with_stream(Box::pin(
568 RecordBatchStreamAdapter::try_new(stream).context(CollectRecordbatchSnafu)?,
569 ));
570
571 output
572 .map_dictionary_to_values()
573 .context(CollectRecordbatchSnafu)
574}
575
576fn create_df_context(query_engine: &QueryEngineRef) -> ServerResult<SessionContext> {
581 let df_context = SessionContext::new_with_state(
582 SessionStateBuilder::new_from_existing(query_engine.engine_state().session_state()).build(),
583 );
584
585 let udfs: Vec<FunctionRef> = vec![
587 Arc::new(JsonGetWithType::default()),
588 Arc::new(JsonGetInt::default()),
589 Arc::new(JsonGetFloat::default()),
590 Arc::new(JsonGetBool::default()),
591 Arc::new(JsonGetString::default()),
592 ];
593
594 for udf in udfs {
595 df_context.register_udf(create_udf(udf));
596 }
597
598 Ok(df_context)
599}
600
601fn json_tag_filters(
602 dataframe: &DataFrame,
603 tags: HashMap<String, JsonValue>,
604) -> ServerResult<Vec<Expr>> {
605 let has_resource_attributes = dataframe
606 .schema()
607 .has_column_with_unqualified_name(RESOURCE_ATTRIBUTES_COLUMN);
608 let mut filters = vec![];
609
610 for (key, value) in tags.iter() {
612 let (udf, value) = match value {
613 JsonValue::String(value) => (JsonGetString::NAME, lit(value)),
614 JsonValue::Number(value) if value.is_i64() => {
615 (JsonGetInt::NAME, lit(value.as_i64().unwrap()))
616 }
617 JsonValue::Number(value) if value.is_f64() => {
618 (JsonGetFloat::NAME, lit(value.as_f64().unwrap()))
619 }
620 JsonValue::Bool(value) => (JsonGetBool::NAME, lit(*value)),
621 _ => continue,
622 };
623 let get = dataframe.registry().udf(udf).context(DataFusionSnafu)?;
624 let path = lit(format!("[\"{}\"]", key));
625 let span_value = get.call(vec![col(SPAN_ATTRIBUTES_COLUMN), path.clone()]);
626 let attribute = if has_resource_attributes {
628 coalesce(vec![
629 span_value,
630 get.call(vec![col(RESOURCE_ATTRIBUTES_COLUMN), path]),
631 ])
632 } else {
633 span_value
634 };
635 filters.push(attribute.eq(value));
636 }
637
638 Ok(filters)
639}
640
641fn json2_tag_filters(
643 dataframe: &DataFrame,
644 tags: HashMap<String, JsonValue>,
645) -> ServerResult<Vec<Expr>> {
646 let get = dataframe
647 .registry()
648 .udf(JsonGetWithType::NAME)
649 .context(DataFusionSnafu)?;
650 tags.into_iter()
651 .map(|(key, value)| {
652 if key == KEY_OTEL_STATUS_ERROR_KEY && value == JsonValue::Bool(true) {
653 return Ok(col(SPAN_STATUS_CODE).eq(lit(SPAN_STATUS_ERROR)));
654 }
655 let path = lit(format!("$[{}]", JsonValue::String(key)));
657 let (value, value_type) = match value {
658 JsonValue::String(value) => (
659 ScalarValue::Utf8View(Some(value)),
660 ScalarValue::Utf8View(None),
661 ),
662 JsonValue::Bool(value) => (
663 ScalarValue::Boolean(Some(value)),
664 ScalarValue::Boolean(None),
665 ),
666 JsonValue::Number(value) => {
667 if let Some(value) = value.as_i64() {
668 (ScalarValue::Int64(Some(value)), ScalarValue::Int64(None))
669 } else if let Some(value) = value.as_u64() {
670 (ScalarValue::UInt64(Some(value)), ScalarValue::UInt64(None))
671 } else if let Some(value) = value.as_f64() {
672 (
673 ScalarValue::Float64(Some(value)),
674 ScalarValue::Float64(None),
675 )
676 } else {
677 return Ok(lit(false));
678 }
679 }
680 JsonValue::Null => (ScalarValue::Utf8View(None), ScalarValue::Utf8View(None)),
681 JsonValue::Array(_) | JsonValue::Object(_) => return Ok(lit(false)),
682 };
683 let attribute = coalesce(vec![
684 get.call(vec![
685 col(SPAN_ATTRIBUTES_COLUMN),
686 path.clone(),
687 lit(value_type.clone()),
688 ]),
689 get.call(vec![col(RESOURCE_ATTRIBUTES_COLUMN), path, lit(value_type)]),
690 ]);
691 Ok(if value.is_null() {
692 attribute.is_null()
693 } else {
694 attribute.eq(lit(value))
695 })
696 })
697 .collect()
698}
699
700#[inline]
703fn check_col_and_build_expr<F>(
704 span_key: String,
705 resource_key: String,
706 key: &str,
707 col_names: &HashSet<String>,
708 expr_builder: F,
709) -> Option<Expr>
710where
711 F: FnOnce(String) -> Expr,
712{
713 if col_names.contains(&span_key) {
714 return Some(expr_builder(span_key));
715 }
716 if col_names.contains(&resource_key) {
717 return Some(expr_builder(resource_key));
718 }
719 warn!("tag key {} not found in table columns", key);
720 None
721}
722
723fn flatten_tag_filters(
724 tags: HashMap<String, JsonValue>,
725 col_names: &HashSet<String>,
726) -> ServerResult<Vec<Expr>> {
727 let filters = tags
728 .into_iter()
729 .filter_map(|(key, value)| {
730 if key == KEY_OTEL_STATUS_ERROR_KEY && value == JsonValue::Bool(true) {
731 return Some(col(SPAN_STATUS_CODE).eq(lit(SPAN_STATUS_ERROR)));
732 }
733
734 let span_key = format!("\"span_attributes.{}\"", key);
736 let resource_key = format!("\"resource_attributes.{}\"", key);
737 match value {
738 JsonValue::String(value) => {
739 check_col_and_build_expr(span_key, resource_key, &key, col_names, |k| {
740 col(k).eq(lit(value))
741 })
742 }
743 JsonValue::Number(value) => {
744 if value.is_f64() {
745 let value = value.as_f64().unwrap();
747 check_col_and_build_expr(span_key, resource_key, &key, col_names, |k| {
748 col(k).eq(lit(value))
749 })
750 } else {
751 let value = value.as_i64().unwrap();
752 check_col_and_build_expr(span_key, resource_key, &key, col_names, |k| {
753 col(k).eq(lit(value))
754 })
755 }
756 }
757 JsonValue::Bool(value) => {
758 check_col_and_build_expr(span_key, resource_key, &key, col_names, |k| {
759 col(k).eq(lit(value))
760 })
761 }
762 JsonValue::Null => {
763 check_col_and_build_expr(span_key, resource_key, &key, col_names, |k| {
764 col(k).is_null()
765 })
766 }
767 JsonValue::Array(_value) => None,
769 JsonValue::Object(_value) => None,
770 }
771 })
772 .collect();
773 Ok(filters)
774}
775
776fn tags_filters(
777 dataframe: &DataFrame,
778 tags: HashMap<String, JsonValue>,
779 data_model: Option<&str>,
780 col_names: &HashSet<String>,
781) -> ServerResult<Vec<Expr>> {
782 match data_model {
783 Some(table::requests::TABLE_DATA_MODEL_TRACE_V1) => flatten_tag_filters(tags, col_names),
784 Some(table::requests::TABLE_DATA_MODEL_TRACE_V2) => json2_tag_filters(dataframe, tags),
785 _ => json_tag_filters(dataframe, tags),
786 }
787}
788
789async fn trace_ids_from_output(output: Output) -> ServerResult<Vec<String>> {
791 if let OutputData::Stream(stream) = output.data {
792 let schema = stream.schema().clone();
793 let recordbatches = util::collect(stream)
794 .await
795 .context(CollectRecordbatchSnafu)?;
796
797 if !recordbatches.is_empty()
799 && schema.num_columns() == 1
800 && schema.contains_column(TRACE_ID_COLUMN)
801 {
802 let mut trace_ids = vec![];
803 for recordbatch in recordbatches {
804 recordbatch
805 .iter_column_as_string(0)
806 .flatten()
807 .for_each(|x| trace_ids.push(x));
808 }
809
810 return Ok(trace_ids);
811 }
812 }
813
814 Ok(vec![])
815}