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, Filter, GroupBy, Mapping, MetricId, RelativeTime,
8        Source, 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    write!(f, "`{}`", ident.replace('\\', "\\\\").replace('`', "\\`"))
15}
16
17impl Display for Query {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        for param in self.params() {
20            writeln!(f, "param ${}: {};", param.name, param.typ)?;
21        }
22
23        match self {
24            Query::Simple {
25                sample,
26                source,
27                filters,
28                aggregates,
29                directives: _,
30                params: _,
31            } => {
32                writeln!(f, "{source}")?;
33                if let Some(sample) = sample {
34                    writeln!(f, "| sample {sample}")?;
35                }
36                for filter in filters {
37                    match filter {
38                        crate::query::FilterOrIfDef::Filter(filter) => {
39                            writeln!(f, "| where {filter}")?;
40                        }
41                        crate::query::FilterOrIfDef::Ifdef { param, filter } => {
42                            writeln!(f, "| ifdef(${}) {{ where {filter} }}", param.name)?;
43                        }
44                    }
45                }
46                for aggregate in aggregates {
47                    writeln!(f, " {aggregate}")?;
48                }
49            }
50            Query::Compute {
51                left,
52                right,
53                name,
54                op,
55                aggregates,
56                directives: _,
57                params: _,
58            } => {
59                writeln!(f, "( {left}, {right} )")?;
60                writeln!(f, "| compute {name} using {op}")?;
61                for aggregate in aggregates {
62                    writeln!(f, " {aggregate}")?;
63                }
64            }
65        }
66
67        Ok(())
68    }
69}
70
71impl Display for Source {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        let Source {
74            metric_id: MetricId { dataset, metric },
75            time,
76        } = self;
77        match dataset {
78            Parameterized::Concrete(dataset) => escape_ident(f, dataset)?,
79            Parameterized::Param { span: _, param } => {
80                write!(f, "$")?;
81                escape_ident(f, param.name.as_str())?;
82            }
83        }
84        write!(f, ":")?;
85        escape_ident(f, metric)?;
86        if let Some(time) = time {
87            write!(f, "{time}")?;
88        }
89        Ok(())
90    }
91}
92
93impl Display for TimeRange {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(f, "[{}..", self.start)?;
96        if let Some(end) = &self.end {
97            write!(f, "{end}]")?;
98        } else {
99            write!(f, "]")?;
100        }
101        Ok(())
102    }
103}
104
105impl Display for Time {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        match self {
108            Time::Relative(relative_time) => write!(f, "{relative_time}"),
109            Time::Timestamp(t) => write!(f, "{t}"),
110            Time::RFC3339(date_time) => write!(f, "{date_time}"),
111            Time::Modifier(m) => write!(f, "{m}"),
112        }
113    }
114}
115
116impl Display for RelativeTime {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        write!(f, "{}{}", self.value, self.unit)
119    }
120}
121
122impl Display for TimeUnit {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        match self {
125            TimeUnit::Millisecond => write!(f, "ms"),
126            TimeUnit::Second => write!(f, "s"),
127            TimeUnit::Minute => write!(f, "m"),
128            TimeUnit::Hour => write!(f, "h"),
129            TimeUnit::Day => write!(f, "d"),
130            TimeUnit::Week => write!(f, "w"),
131            TimeUnit::Month => write!(f, "M"),
132            TimeUnit::Year => write!(f, "y"),
133        }
134    }
135}
136
137impl Display for As {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        let As { name } = self;
140        write!(f, "as {name}")
141    }
142}
143
144impl Display for Filter {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        match self {
147            Filter::And(filters) => {
148                if let Some((first, rest)) = filters.split_first() {
149                    write!(f, "({first}")?;
150                    for filter in rest {
151                        write!(f, " and {filter}")?;
152                    }
153                    write!(f, ")")?;
154                }
155                Ok(())
156            }
157            Filter::Or(filters) => {
158                if let Some((first, rest)) = filters.split_first() {
159                    write!(f, "({first}")?;
160                    for filter in rest {
161                        write!(f, " or {filter}")?;
162                    }
163                    write!(f, ")")?;
164                }
165                Ok(())
166            }
167            Filter::Not(filter) => {
168                write!(f, "not {filter}")
169            }
170            Filter::Cmp { field, rhs } => {
171                escape_ident(f, field)?;
172                write!(f, " {rhs}")
173            }
174        }
175    }
176}
177
178impl Display for Cmp {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        match self {
181            Cmp::Eq(v) => write!(f, "== {v}"),
182            Cmp::Ne(v) => write!(f, "!= {v}"),
183            Cmp::Gt(v) => write!(f, "> {v}"),
184            Cmp::Ge(v) => write!(f, ">= {v}"),
185            Cmp::Lt(v) => write!(f, "< {v}"),
186            Cmp::Le(v) => write!(f, "<= {v}"),
187            Cmp::Is(v) => write!(f, "is {v}"),
188            Cmp::RegEx(r) => match r {
189                Parameterized::Concrete(r) => write!(f, "== {}", r.as_ref()),
190                Parameterized::Param { span: _, param } => write!(f, "== ${}", param.name),
191            },
192            Cmp::RegExNot(r) => match r {
193                Parameterized::Concrete(r) => write!(f, "!= {}", r.as_ref()),
194                Parameterized::Param { span: _, param } => write!(f, "!= ${}", param.name),
195            },
196        }
197    }
198}
199
200impl Display for Aggregate {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        write!(f, "| ")?;
203        match self {
204            Aggregate::As(As { name }) => write!(f, "as {name}"),
205            Aggregate::Map(Mapping {
206                function: MapFunction::Builtin(MapType::Rate),
207                arg: None,
208            }) => write!(f, "map rate"),
209            Aggregate::Map(map) => write!(f, "map {map}"),
210            Aggregate::Align(Align { function, time }) => {
211                write!(f, "align to {time} using {function}")
212            }
213            Aggregate::GroupBy(GroupBy {
214                span: _,
215                function,
216                tags: fields,
217            }) => {
218                if let Some((field, rest)) = fields.split_first() {
219                    write!(f, "group by ")?;
220                    escape_ident(f, field)?;
221                    for field in rest {
222                        write!(f, ", ")?;
223                        escape_ident(f, field)?;
224                    }
225                } else {
226                    write!(f, "group ")?;
227                }
228                write!(f, " using {function}")
229            }
230            Aggregate::Bucket(BucketBy {
231                span: _,
232                function,
233                time,
234                tags: fields,
235                spec,
236            }) => {
237                if let Some((field, rest)) = fields.split_first() {
238                    write!(f, "bucket by ")?;
239                    escape_ident(f, field)?;
240                    for field in rest {
241                        write!(f, ", ")?;
242                        escape_ident(f, field)?;
243                    }
244                } else {
245                    write!(f, "bucket ")?;
246                }
247                write!(f, " to {time} using {function}")?;
248                // For cumulative histogram, include the mode before bucket specs
249                let mode_prefix = if let BucketType::InterpolateCumulativeHistogram(mode) = function
250                {
251                    Some(mode)
252                } else {
253                    None
254                };
255                if let Some((first, rest)) = spec.split_first() {
256                    write!(f, "(")?;
257                    if let Some(mode) = mode_prefix {
258                        write!(f, "{mode}, ")?;
259                    }
260                    write!(f, "{first}")?;
261                    for s in rest {
262                        write!(f, ", {s}")?;
263                    }
264                    write!(f, ")")?;
265                }
266                Ok(())
267            }
268        }
269    }
270}
271impl Display for Mapping {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        match self {
274            Mapping {
275                function:
276                    MapFunction::Builtin(
277                        MapType::Mul
278                        | MapType::Div
279                        | MapType::Add
280                        | MapType::Sub
281                        | MapType::InterpolateLinear,
282                    ),
283                arg,
284            } => {
285                write!(f, " {}", self.function)?;
286                if let Some(arg) = arg {
287                    write!(f, " {arg}")?;
288                }
289            }
290            Mapping {
291                function:
292                    MapFunction::Builtin(
293                        MapType::Abs
294                        | MapType::Max
295                        | MapType::Min
296                        | MapType::Rate
297                        | MapType::FillConst
298                        | MapType::FillPrev
299                        | MapType::Increase
300                        | MapType::FilterLt
301                        | MapType::FilterGt
302                        | MapType::FilterEq
303                        | MapType::FilterNe
304                        | MapType::FilterGe
305                        | MapType::FilterLe
306                        | MapType::IsLt
307                        | MapType::IsGt
308                        | MapType::IsEq
309                        | MapType::IsNe
310                        | MapType::IsGe
311                        | MapType::IsLe,
312                    ),
313                arg,
314            } => {
315                write!(f, "{}", self.function)?;
316                if let Some(arg) = arg {
317                    write!(f, "({arg})")?;
318                }
319            }
320            Mapping {
321                function: MapFunction::UserDefined(func),
322                arg,
323            } => {
324                write!(f, " {func}")?;
325                if let Some(arg) = arg {
326                    write!(f, " {arg}")?;
327                }
328            }
329        }
330
331        Ok(())
332    }
333}