Skip to main content

frontend/instance/
promql.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::sync::Arc;
16use std::time::SystemTime;
17
18use auth::PermissionTableTarget;
19use catalog::information_schema::TABLES;
20use client::OutputData;
21use common_catalog::consts::INFORMATION_SCHEMA_NAME;
22use common_catalog::format_full_table_name;
23use common_recordbatch::util;
24use common_telemetry::tracing;
25use datafusion_expr::LogicalPlan;
26use promql_parser::label::{Matcher, Matchers};
27use query::promql;
28use query::promql::planner::PromPlanner;
29use servers::prometheus;
30use session::context::QueryContextRef;
31use snafu::{OptionExt, ResultExt};
32
33use crate::error::{
34    CatalogSnafu, CollectRecordbatchSnafu, ExecLogicalPlanSnafu,
35    PrometheusLabelValuesQueryPlanSnafu, PrometheusMetricNamesQueryPlanSnafu, ReadTableSnafu,
36    Result, TableNotFoundSnafu, TableSnafu,
37};
38use crate::instance::Instance;
39
40/// Strips the output sort a PromQL plan ends with, keeping the plan schema intact.
41///
42/// Only the sort the caller would observe is removed: recursion stops at any other
43/// node, so ordering consumed by windows, limits or PromQL extension nodes stays.
44pub(super) fn remove_output_sort(plan: LogicalPlan) -> LogicalPlan {
45    match plan {
46        LogicalPlan::Sort(sort) if sort.fetch.is_none() => Arc::unwrap_or_clone(sort.input),
47        LogicalPlan::Projection(mut projection) => {
48            projection.input = Arc::new(remove_output_sort(Arc::unwrap_or_clone(projection.input)));
49            LogicalPlan::Projection(projection)
50        }
51        plan => plan,
52    }
53}
54
55impl Instance {
56    /// Handles metric names query request, returns the names.
57    #[tracing::instrument(skip_all)]
58    pub(crate) async fn handle_query_metric_names(
59        &self,
60        matchers: Vec<Matcher>,
61        schema: &str,
62        ctx: &QueryContextRef,
63    ) -> Result<Vec<String>> {
64        let _timer = crate::metrics::PROMQL_QUERY_METRICS_ELAPSED
65            .with_label_values(&[ctx.get_db_string().as_str()])
66            .start_timer();
67
68        let table = self
69            .catalog_manager
70            .table(
71                ctx.current_catalog(),
72                INFORMATION_SCHEMA_NAME,
73                TABLES,
74                Some(ctx),
75            )
76            .await
77            .context(CatalogSnafu)?
78            .with_context(|| TableNotFoundSnafu {
79                table_name: "greptime.information_schema.tables",
80            })?;
81
82        let dataframe = self
83            .query_engine
84            .read_table(table)
85            .with_context(|_| ReadTableSnafu {
86                table_name: "greptime.information_schema.tables",
87            })?;
88
89        let logical_plan =
90            prometheus::metric_name_matchers_to_plan(dataframe, matchers, schema, ctx)
91                .context(PrometheusMetricNamesQueryPlanSnafu)?;
92
93        let results = self
94            .query_engine
95            .execute(logical_plan, ctx.clone())
96            .await
97            .context(ExecLogicalPlanSnafu)?;
98
99        let batches = match results.data {
100            OutputData::Stream(stream) => util::collect(stream)
101                .await
102                .context(CollectRecordbatchSnafu)?,
103            OutputData::RecordBatches(rbs) => rbs.take(),
104            _ => unreachable!("should not happen"),
105        };
106
107        let mut results = Vec::with_capacity(batches.iter().map(|b| b.num_rows()).sum());
108
109        for batch in batches {
110            // Only one column the results, ensured by `prometheus::metric_name_matchers_to_plan`.
111            batch
112                .iter_column_as_string(0)
113                .flatten()
114                .for_each(|x| results.push(x))
115        }
116
117        Ok(results)
118    }
119
120    /// Handles label values query request, returns the values.
121    #[tracing::instrument(skip_all)]
122    pub(crate) async fn handle_query_label_values(
123        &self,
124        target: PermissionTableTarget,
125        label_name: String,
126        matchers: Vec<Matcher>,
127        start: SystemTime,
128        end: SystemTime,
129        ctx: &QueryContextRef,
130    ) -> Result<Vec<String>> {
131        let full_table_name =
132            format_full_table_name(&target.catalog, &target.schema, &target.table);
133        let table = self
134            .catalog_manager
135            .table(&target.catalog, &target.schema, &target.table, Some(ctx))
136            .await
137            .context(CatalogSnafu)?
138            .with_context(|| TableNotFoundSnafu {
139                table_name: full_table_name.clone(),
140            })?;
141
142        // Check label column existence before building the query plan so a missing label can be
143        // reported as `TableColumnNotFound` and handled like Prometheus expects.
144        if table.schema().column_schema_by_name(&label_name).is_none() {
145            return table::error::ColumnNotExistsSnafu {
146                column_name: label_name,
147                table_name: full_table_name,
148            }
149            .fail()
150            .context(TableSnafu);
151        }
152
153        let dataframe = self
154            .query_engine
155            .read_table(table.clone())
156            .with_context(|_| ReadTableSnafu {
157                table_name: full_table_name,
158            })?;
159
160        let scan_plan = dataframe.into_unoptimized_plan();
161        let filter_conditions =
162            PromPlanner::matchers_to_expr(Matchers::new(matchers), scan_plan.schema())
163                .context(PrometheusLabelValuesQueryPlanSnafu)?;
164        let logical_plan = promql::label_values::rewrite_label_values_query(
165            table,
166            scan_plan,
167            filter_conditions,
168            label_name,
169            start,
170            end,
171        )
172        .context(PrometheusLabelValuesQueryPlanSnafu)?;
173
174        let results = self
175            .query_engine
176            .execute(logical_plan, ctx.clone())
177            .await
178            .context(ExecLogicalPlanSnafu)?;
179
180        let batches = match results.data {
181            OutputData::Stream(stream) => util::collect(stream)
182                .await
183                .context(CollectRecordbatchSnafu)?,
184            OutputData::RecordBatches(rbs) => rbs.take(),
185            _ => unreachable!("should not happen"),
186        };
187
188        let mut results = Vec::with_capacity(batches.iter().map(|b| b.num_rows()).sum());
189        for batch in batches {
190            // Only one column in results, ensured by `prometheus::label_values_matchers_to_plan`.
191            batch
192                .iter_column_as_string(0)
193                .flatten()
194                .for_each(|x| results.push(x))
195        }
196
197        Ok(results)
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use datafusion::arrow::array::Int64Array;
204    use datafusion::prelude::{SessionConfig, SessionContext};
205    use datafusion_expr::{LogicalPlanBuilder, Sort, col, lit};
206
207    use super::*;
208
209    #[tokio::test]
210    async fn remove_output_sort_keeps_schema_and_row_selection() {
211        let input =
212            LogicalPlanBuilder::values(vec![vec![lit(3_i64)], vec![lit(1_i64)], vec![lit(2_i64)]])
213                .unwrap()
214                .build()
215                .unwrap();
216        let sort = Sort {
217            expr: vec![col("column1").sort(true, false)],
218            input: Arc::new(input),
219            fetch: None,
220        };
221        let projected = LogicalPlanBuilder::from(LogicalPlan::Sort(sort.clone()))
222            .project(vec![col("column1").alias("sample")])
223            .unwrap()
224            .project(vec![col("sample")])
225            .unwrap()
226            .build()
227            .unwrap();
228        // The root sort is removed, but the sort feeding the limit selects the rows.
229        let limited = LogicalPlanBuilder::from(LogicalPlan::Sort(sort.clone()))
230            .limit(0, Some(2))
231            .unwrap()
232            .sort(vec![col("column1").sort(false, false)])
233            .unwrap()
234            .build()
235            .unwrap();
236        let fetched = LogicalPlan::Sort(Sort {
237            fetch: Some(2),
238            ..sort.clone()
239        });
240
241        let context =
242            SessionContext::new_with_config(SessionConfig::new().with_target_partitions(1));
243        for (name, plan, expected) in [
244            ("root", LogicalPlan::Sort(sort), vec![3, 1, 2]),
245            ("projection", projected, vec![3, 1, 2]),
246            ("sort below limit", limited, vec![1, 2]),
247            ("fetch", fetched, vec![1, 2]),
248        ] {
249            let schema = plan.schema().clone();
250            let plan = remove_output_sort(plan);
251            assert_eq!(plan.schema(), &schema, "{name}");
252            let output = context
253                .execute_logical_plan(plan)
254                .await
255                .unwrap()
256                .collect()
257                .await
258                .unwrap();
259            let values = output
260                .iter()
261                .flat_map(|batch| {
262                    batch
263                        .column(0)
264                        .as_any()
265                        .downcast_ref::<Int64Array>()
266                        .unwrap()
267                        .values()
268                        .iter()
269                        .copied()
270                })
271                .collect::<Vec<_>>();
272            assert_eq!(values, expected, "{name}");
273        }
274    }
275}