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 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}