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