Skip to main content

mpl_lang/query/
fmt.rs

1use std::fmt::Display;
2
3use crate::{
4    Query,
5    linker::MapFunction,
6    query::{
7        Aggregate, Align, As, BucketBy, Cmp, Expr, Filter, GroupBy, Mapping, MetricId,
8        RelativeTime, Source, StringFragment, TagExtend, Time, TimeRange, TimeUnit,
9    },
10    types::{BucketType, MapType, Parameterized},
11};
12
13fn escape_ident(f: &mut std::fmt::Formatter<'_>, ident: &str) -> std::fmt::Result {
14    let mut chars = ident.chars();
15
16    if let Some(c) = chars.next()
17        && (c.is_ascii_alphabetic() || c == '_')
18        && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
19    {
20        write!(f, "{ident}")
21    } else {
22        write!(f, "`{}`", ident.replace('\\', "\\\\").replace('`', "\\`"))
23    }
24}
25
26impl Display for Query {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        for param in self.params() {
29            writeln!(f, "param ${}: {};", param.name, param.typ)?;
30        }
31
32        match self {
33            Query::Simple {
34                sample,
35                source,
36                filters,
37                aggregates,
38                extends,
39                directives: _,
40                params: _,
41            } => {
42                writeln!(f, "{source}")?;
43                if let Some(sample) = sample {
44                    writeln!(f, "| sample {sample}")?;
45                }
46                for filter in filters {
47                    match filter {
48                        crate::query::FilterOrIfDef::Filter(filter) => {
49                            writeln!(f, "| where {filter}")?;
50                        }
51                        crate::query::FilterOrIfDef::Ifdef {
52                            param,
53                            filter,
54                            else_filter: None,
55                        } => {
56                            writeln!(f, "| ifdef(${}) {{ where {filter} }}", param.name)?;
57                        }
58                        crate::query::FilterOrIfDef::Ifdef {
59                            param,
60                            filter,
61                            else_filter: Some(else_filter),
62                        } => {
63                            writeln!(
64                                f,
65                                "| ifdef(${}) {{ where {filter} }} else {{ where {else_filter} }}",
66                                param.name
67                            )?;
68                        }
69                    }
70                }
71                for aggregate in aggregates {
72                    writeln!(f, " {aggregate}")?;
73                }
74                if let Some((first, rest)) = extends.split_first() {
75                    writeln!(f, "| extend {first}")?;
76                    for extend in rest {
77                        writeln!(f, ", {extend}")?;
78                    }
79                }
80            }
81            Query::Compute {
82                left,
83                right,
84                name,
85                op,
86                aggregates,
87                extends,
88                directives: _,
89                params: _,
90            } => {
91                writeln!(f, "( {left}, {right} )")?;
92                writeln!(f, "| compute {name} using {op}")?;
93                for aggregate in aggregates {
94                    writeln!(f, " {aggregate}")?;
95                }
96                if let Some((first, rest)) = extends.split_first() {
97                    writeln!(f, "| extend {first}")?;
98                    for extend in rest {
99                        writeln!(f, ", {extend}")?;
100                    }
101                }
102            }
103        }
104
105        Ok(())
106    }
107}
108impl Display for TagExtend {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        write!(f, "{tag} = {value}", tag = self.tag, value = self.value)
111    }
112}
113fn write_string(f: &mut std::fmt::Formatter<'_>, text: &str) -> std::fmt::Result {
114    for c in text.chars() {
115        match c {
116            '\r' => write!(f, "\\r")?,
117            '\n' => write!(f, "\\n")?,
118            '\t' => write!(f, "\\t")?,
119            '\x08' => write!(f, "\\b")?,
120            '\x0C' => write!(f, "\\f")?,
121            '\\' => write!(f, "\\\\")?,
122            '$' => write!(f, "\\$")?,
123            '"' => write!(f, "\\\"")?,
124            _ => write!(f, "{c}")?,
125        }
126    }
127    Ok(())
128}
129impl Display for Expr {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        match self {
132            Expr::Const(c) => write!(f, "{c}"),
133            Expr::Tag(tag) => escape_ident(f, tag),
134            Expr::Param { span: _, param } => {
135                write!(f, "$")?;
136                escape_ident(f, param.name.as_str())
137            }
138            Expr::String(string_fragments) => {
139                write!(f, "\"")?;
140                for fragment in string_fragments {
141                    match fragment {
142                        StringFragment::Text(text) => write_string(f, text)?,
143                        StringFragment::Expr(expr) => write!(f, "${{ {expr} }}")?,
144                    }
145                }
146                write!(f, "\"")
147            }
148            Expr::Array(parts) => {
149                let Some((first, rest)) = parts.split_first() else {
150                    return write!(f, "[]");
151                };
152                write!(f, "[{first}")?;
153
154                for e in rest {
155                    write!(f, ", {e}")?;
156                }
157                write!(f, "]")
158            }
159        }
160    }
161}
162impl Display for Source {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        let Source {
165            metric_id: MetricId { dataset, metric },
166            time,
167        } = self;
168        match dataset {
169            Parameterized::Concrete(dataset) => escape_ident(f, dataset)?,
170            Parameterized::Param { span: _, param } => {
171                write!(f, "$")?;
172                escape_ident(f, param.name.as_str())?;
173            }
174        }
175        write!(f, ":")?;
176        escape_ident(f, metric)?;
177        if let Some(time) = time {
178            write!(f, "{time}")?;
179        }
180        Ok(())
181    }
182}
183
184impl Display for TimeRange {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        write!(f, "[{}..", self.start)?;
187        if let Some(end) = &self.end {
188            write!(f, "{end}]")?;
189        } else {
190            write!(f, "]")?;
191        }
192        Ok(())
193    }
194}
195
196impl Display for Time {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        match self {
199            Time::Relative(relative_time) => write!(f, "{relative_time}"),
200            Time::Timestamp(t) => write!(f, "{t}"),
201            Time::RFC3339(date_time) => write!(f, "{date_time}"),
202            Time::Modifier(m) => write!(f, "{m}"),
203        }
204    }
205}
206
207impl Display for RelativeTime {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        write!(f, "{}{}", self.value, self.unit)
210    }
211}
212
213impl Display for TimeUnit {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        match self {
216            TimeUnit::Millisecond => write!(f, "ms"),
217            TimeUnit::Second => write!(f, "s"),
218            TimeUnit::Minute => write!(f, "m"),
219            TimeUnit::Hour => write!(f, "h"),
220            TimeUnit::Day => write!(f, "d"),
221            TimeUnit::Week => write!(f, "w"),
222            TimeUnit::Month => write!(f, "M"),
223            TimeUnit::Year => write!(f, "y"),
224        }
225    }
226}
227
228impl Display for As {
229    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230        let As { name } = self;
231        write!(f, "as {name}")
232    }
233}
234
235impl Display for Filter {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        match self {
238            Filter::And(filters) => {
239                if let Some((first, rest)) = filters.split_first() {
240                    write!(f, "({first}")?;
241                    for filter in rest {
242                        write!(f, " and {filter}")?;
243                    }
244                    write!(f, ")")?;
245                }
246                Ok(())
247            }
248            Filter::Or(filters) => {
249                if let Some((first, rest)) = filters.split_first() {
250                    write!(f, "({first}")?;
251                    for filter in rest {
252                        write!(f, " or {filter}")?;
253                    }
254                    write!(f, ")")?;
255                }
256                Ok(())
257            }
258            Filter::Not(filter) => {
259                write!(f, "not {filter}")
260            }
261            Filter::Cmp { field, rhs } => {
262                escape_ident(f, field)?;
263                write!(f, " {rhs}")
264            }
265        }
266    }
267}
268
269impl Display for Cmp {
270    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271        match self {
272            Cmp::Eq(v) => write!(f, "== {v}"),
273            Cmp::Ne(v) => write!(f, "!= {v}"),
274            Cmp::Gt(v) => write!(f, "> {v}"),
275            Cmp::Ge(v) => write!(f, ">= {v}"),
276            Cmp::Lt(v) => write!(f, "< {v}"),
277            Cmp::Le(v) => write!(f, "<= {v}"),
278            Cmp::Is(v) => write!(f, "is {v}"),
279            Cmp::In(v) => write!(f, "in {v}"),
280            Cmp::RegEx(r) => match r {
281                Parameterized::Concrete(r) => write!(f, "== {}", r.as_ref()),
282                Parameterized::Param { span: _, param } => write!(f, "== ${}", param.name),
283            },
284            Cmp::RegExNot(r) => match r {
285                Parameterized::Concrete(r) => write!(f, "!= {}", r.as_ref()),
286                Parameterized::Param { span: _, param } => write!(f, "!= ${}", param.name),
287            },
288        }
289    }
290}
291
292impl Display for Aggregate {
293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294        write!(f, "| ")?;
295        match self {
296            Aggregate::As(As { name }) => write!(f, "as {name}"),
297            Aggregate::Map(Mapping {
298                function: MapFunction::Builtin(MapType::Rate),
299                arg: None,
300            }) => write!(f, "map rate"),
301            Aggregate::Map(map) => write!(f, "map {map}"),
302            Aggregate::Align(Align { function, time }) => {
303                if let Some(time) = time {
304                    write!(f, "align to {time} using {function}")
305                } else {
306                    write!(f, "align using {function}")
307                }
308            }
309            Aggregate::GroupBy(GroupBy {
310                span: _,
311                function,
312                tags: fields,
313            }) => {
314                if let Some((field, rest)) = fields.split_first() {
315                    write!(f, "group by ")?;
316                    escape_ident(f, field)?;
317                    for field in rest {
318                        write!(f, ", ")?;
319                        escape_ident(f, field)?;
320                    }
321                } else {
322                    write!(f, "group ")?;
323                }
324                write!(f, " using {function}")
325            }
326            Aggregate::Bucket(BucketBy {
327                span: _,
328                function,
329                time,
330                tags: fields,
331                spec,
332            }) => {
333                if let Some((field, rest)) = fields.split_first() {
334                    write!(f, "bucket by ")?;
335                    escape_ident(f, field)?;
336                    for field in rest {
337                        write!(f, ", ")?;
338                        escape_ident(f, field)?;
339                    }
340                } else {
341                    write!(f, "bucket ")?;
342                }
343                if let Some(time) = time {
344                    write!(f, " to {time} using {function}")?;
345                } else {
346                    write!(f, " using {function}")?;
347                }
348                // For cumulative histogram, include the mode before bucket specs
349                let mode_prefix = if let BucketType::InterpolateCumulativeHistogram(mode) = function
350                {
351                    Some(mode)
352                } else {
353                    None
354                };
355                if let Some((first, rest)) = spec.split_first() {
356                    write!(f, "(")?;
357                    if let Some(mode) = mode_prefix {
358                        write!(f, "{mode}, ")?;
359                    }
360                    write!(f, "{first}")?;
361                    for s in rest {
362                        write!(f, ", {s}")?;
363                    }
364                    write!(f, ")")?;
365                }
366                Ok(())
367            }
368        }
369    }
370}
371impl Display for Mapping {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        match self {
374            Mapping {
375                function:
376                    MapFunction::Builtin(
377                        MapType::Mul
378                        | MapType::Div
379                        | MapType::Add
380                        | MapType::Sub
381                        | MapType::InterpolateLinear,
382                    ),
383                arg,
384            } => {
385                write!(f, " {}", self.function)?;
386                if let Some(arg) = arg {
387                    write!(f, " {arg}")?;
388                }
389            }
390            Mapping {
391                function:
392                    MapFunction::Builtin(
393                        MapType::Abs
394                        | MapType::Max
395                        | MapType::Min
396                        | MapType::Rate
397                        | MapType::FillConst
398                        | MapType::FillPrev
399                        | MapType::Increase
400                        | MapType::FilterLt
401                        | MapType::FilterGt
402                        | MapType::FilterEq
403                        | MapType::FilterNe
404                        | MapType::FilterGe
405                        | MapType::FilterLe
406                        | MapType::IsLt
407                        | MapType::IsGt
408                        | MapType::IsEq
409                        | MapType::IsNe
410                        | MapType::IsGe
411                        | MapType::IsLe,
412                    ),
413                arg,
414            } => {
415                write!(f, "{}", self.function)?;
416                if let Some(arg) = arg {
417                    write!(f, "({arg})")?;
418                }
419            }
420            Mapping {
421                function: MapFunction::UserDefined(func),
422                arg,
423            } => {
424                write!(f, " {func}")?;
425                if let Some(arg) = arg {
426                    write!(f, " {arg}")?;
427                }
428            }
429        }
430
431        Ok(())
432    }
433}
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    #[test]
439    fn test_ident_escaping() {
440        assert_eq!("a", format!("{}", Expr::Tag("a".into())));
441        assert_eq!("a1_2", format!("{}", Expr::Tag("a1_2".into())));
442        assert_eq!("`a1.2`", format!("{}", Expr::Tag("a1.2".into())));
443        assert_eq!("`1`", format!("{}", Expr::Tag("1".into())));
444        assert_eq!("`1abc`", format!("{}", Expr::Tag("1abc".into())));
445    }
446}