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