1// Copyright 2021 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14import chai from 'chai';
15import { Parser } from './parser';
16import { Diagnostic } from '@codemirror/lint';
17import { createEditorState } from '../test/utils.test';
18import { syntaxTree } from '@codemirror/language';
19import { ValueType } from '../types';
20
21describe('promql operations', () => {
22  const testCases = [
23    {
24      expr: '1',
25      expectedValueType: ValueType.scalar,
26      expectedDiag: [] as Diagnostic[],
27    },
28    {
29      expr: '2 * 3',
30      expectedValueType: ValueType.scalar,
31      expectedDiag: [] as Diagnostic[],
32    },
33    {
34      expr: '1 unless 1',
35      expectedValueType: ValueType.scalar,
36      expectedDiag: [
37        {
38          from: 0,
39          to: 10,
40          message: 'set operator not allowed in binary scalar expression',
41          severity: 'error',
42        },
43      ],
44    },
45    {
46      expr: 'metric_name * "string"',
47      expectedValueType: ValueType.vector,
48      expectedDiag: [
49        {
50          from: 14,
51          to: 22,
52          message: 'binary expression must contain only scalar and instant vector types',
53          severity: 'error',
54        },
55      ] as Diagnostic[],
56    },
57    {
58      expr: 'metric_name_1 > bool metric_name_2',
59      expectedValueType: ValueType.vector,
60      expectedDiag: [] as Diagnostic[],
61    },
62    {
63      expr: 'metric_name_1 + bool metric_name_2',
64      expectedValueType: ValueType.vector,
65      expectedDiag: [
66        {
67          from: 0,
68          to: 34,
69          message: 'bool modifier can only be used on comparison operators',
70          severity: 'error',
71        },
72      ] as Diagnostic[],
73    },
74    {
75      expr: 'metric_name offset 1d',
76      expectedValueType: ValueType.vector,
77      expectedDiag: [] as Diagnostic[],
78    },
79    {
80      expr: 'metric_name[5m] offset 1d',
81      expectedValueType: ValueType.matrix,
82      expectedDiag: [] as Diagnostic[],
83    },
84    {
85      expr: 'rate(metric_name[5m])[1h:] offset 1m',
86      expectedValueType: ValueType.matrix,
87      expectedDiag: [] as Diagnostic[],
88    },
89    {
90      expr: 'sum(metric_name offset 1m)',
91      expectedValueType: ValueType.vector,
92      expectedDiag: [] as Diagnostic[],
93    },
94    {
95      expr: 'rate(metric_name[5m] offset 1d)',
96      expectedValueType: ValueType.vector,
97      expectedDiag: [] as Diagnostic[],
98    },
99    {
100      expr: 'max_over_time(rate(metric_name[5m])[1h:] offset 1m)',
101      expectedValueType: ValueType.vector,
102      expectedDiag: [] as Diagnostic[],
103    },
104    {
105      expr: 'foo * bar',
106      expectedValueType: ValueType.vector,
107      expectedDiag: [] as Diagnostic[],
108    },
109    {
110      expr: 'foo*bar',
111      expectedValueType: ValueType.vector,
112      expectedDiag: [] as Diagnostic[],
113    },
114    {
115      expr: 'foo* bar',
116      expectedValueType: ValueType.vector,
117      expectedDiag: [] as Diagnostic[],
118    },
119    {
120      expr: 'foo *bar',
121      expectedValueType: ValueType.vector,
122      expectedDiag: [] as Diagnostic[],
123    },
124    {
125      expr: 'foo==bar',
126      expectedValueType: ValueType.vector,
127      expectedDiag: [] as Diagnostic[],
128    },
129    {
130      expr: 'foo * sum',
131      expectedValueType: ValueType.vector,
132      expectedDiag: [] as Diagnostic[],
133    },
134    {
135      expr: 'foo == 1',
136      expectedValueType: ValueType.vector,
137      expectedDiag: [] as Diagnostic[],
138    },
139    {
140      expr: 'foo == bool 1',
141      expectedValueType: ValueType.vector,
142      expectedDiag: [] as Diagnostic[],
143    },
144    {
145      expr: '2.5 / bar',
146      expectedValueType: ValueType.vector,
147      expectedDiag: [] as Diagnostic[],
148    },
149    {
150      expr: 'foo and bar',
151      expectedValueType: ValueType.vector,
152      expectedDiag: [] as Diagnostic[],
153    },
154    {
155      expr: 'foo or bar',
156      expectedValueType: ValueType.vector,
157      expectedDiag: [] as Diagnostic[],
158    },
159    {
160      expr: 'foo unless bar',
161      expectedValueType: ValueType.vector,
162      expectedDiag: [] as Diagnostic[],
163    },
164    {
165      // Test and/or precedence and reassigning of operands.
166      // Here it will test only the first VectorMatching so (a + b) or (c and d) ==> ManyToMany
167      expr: 'foo + bar or bla and blub',
168      expectedValueType: ValueType.vector,
169      expectedDiag: [] as Diagnostic[],
170    },
171    {
172      // Test and/or/unless precedence.
173      // Here it will test only the first VectorMatching so ((a and b) unless c) or d ==> ManyToMany
174      expr: 'foo and bar unless baz or qux',
175      expectedValueType: ValueType.vector,
176      expectedDiag: [] as Diagnostic[],
177    },
178    {
179      expr: 'foo * on(test,blub) bar',
180      expectedValueType: ValueType.vector,
181      expectedDiag: [] as Diagnostic[],
182    },
183    {
184      expr: 'foo*on(test,blub)bar',
185      expectedValueType: ValueType.vector,
186      expectedDiag: [] as Diagnostic[],
187    },
188    {
189      expr: 'foo * on(test,blub) group_left bar',
190      expectedValueType: ValueType.vector,
191      expectedDiag: [] as Diagnostic[],
192    },
193    {
194      expr: 'foo*on(test,blub)group_left()bar',
195      expectedValueType: ValueType.vector,
196      expectedDiag: [] as Diagnostic[],
197    },
198    {
199      expr: 'foo and on(test,blub) bar',
200      expectedValueType: ValueType.vector,
201      expectedDiag: [] as Diagnostic[],
202    },
203    {
204      expr: 'foo and on() bar',
205      expectedValueType: ValueType.vector,
206      expectedDiag: [] as Diagnostic[],
207    },
208    {
209      expr: 'foo and ignoring(test,blub) bar',
210      expectedValueType: ValueType.vector,
211      expectedDiag: [] as Diagnostic[],
212    },
213    {
214      expr: 'foo and ignoring() bar',
215      expectedValueType: ValueType.vector,
216      expectedDiag: [] as Diagnostic[],
217    },
218    {
219      expr: 'foo unless on(bar) baz',
220      expectedValueType: ValueType.vector,
221      expectedDiag: [] as Diagnostic[],
222    },
223    {
224      expr: 'foo / on(test,blub) group_left(bar) bar',
225      expectedValueType: ValueType.vector,
226      expectedDiag: [] as Diagnostic[],
227    },
228    {
229      expr: 'foo / ignoring(test,blub) group_left(blub) bar',
230      expectedValueType: ValueType.vector,
231      expectedDiag: [] as Diagnostic[],
232    },
233    {
234      expr: 'foo / ignoring(test,blub) group_left(bar) bar',
235      expectedValueType: ValueType.vector,
236      expectedDiag: [] as Diagnostic[],
237    },
238    {
239      expr: 'foo - on(test,blub) group_right(bar,foo) bar',
240      expectedValueType: ValueType.vector,
241      expectedDiag: [] as Diagnostic[],
242    },
243    {
244      expr: 'foo - ignoring(test,blub) group_right(bar,foo) bar',
245      expectedValueType: ValueType.vector,
246      expectedDiag: [] as Diagnostic[],
247    },
248    {
249      expr: 'foo and 1',
250      expectedValueType: ValueType.vector,
251      expectedDiag: [
252        {
253          from: 0,
254          to: 9,
255          message: 'set operator not allowed in binary scalar expression',
256          severity: 'error',
257        },
258      ],
259    },
260    {
261      expr: '1 and foo',
262      expectedValueType: ValueType.vector,
263      expectedDiag: [
264        {
265          from: 0,
266          to: 9,
267          message: 'set operator not allowed in binary scalar expression',
268          severity: 'error',
269        },
270      ],
271    },
272    {
273      expr: 'foo or 1',
274      expectedValueType: ValueType.vector,
275      expectedDiag: [
276        {
277          from: 0,
278          to: 8,
279          message: 'set operator not allowed in binary scalar expression',
280          severity: 'error',
281        },
282      ],
283    },
284    {
285      expr: '1 or foo',
286      expectedValueType: ValueType.vector,
287      expectedDiag: [
288        {
289          from: 0,
290          to: 8,
291          message: 'set operator not allowed in binary scalar expression',
292          severity: 'error',
293        },
294      ],
295    },
296    {
297      expr: 'foo unless 1',
298      expectedValueType: ValueType.vector,
299      expectedDiag: [
300        {
301          from: 0,
302          to: 12,
303          message: 'set operator not allowed in binary scalar expression',
304          severity: 'error',
305        },
306      ],
307    },
308    {
309      expr: '1 unless foo',
310      expectedValueType: ValueType.vector,
311      expectedDiag: [
312        {
313          from: 0,
314          to: 12,
315          message: 'set operator not allowed in binary scalar expression',
316          severity: 'error',
317        },
318      ],
319    },
320    {
321      expr: '1 or on(bar) foo',
322      expectedValueType: ValueType.vector,
323      expectedDiag: [
324        {
325          from: 0,
326          to: 16,
327          message: 'vector matching only allowed between instant vectors',
328          severity: 'error',
329        },
330        {
331          from: 0,
332          to: 16,
333          message: 'set operator not allowed in binary scalar expression',
334          severity: 'error',
335        },
336      ],
337    },
338    {
339      expr: 'foo == on(bar) 10',
340      expectedValueType: ValueType.vector,
341      expectedDiag: [
342        {
343          from: 0,
344          to: 17,
345          message: 'vector matching only allowed between instant vectors',
346          severity: 'error',
347        },
348      ],
349    },
350    {
351      expr: 'foo and on(bar) group_left(baz) bar',
352      expectedValueType: ValueType.vector,
353      expectedDiag: [
354        {
355          from: 0,
356          to: 35,
357          message: 'no grouping allowed for set operations',
358          severity: 'error',
359        },
360        {
361          from: 0,
362          to: 35,
363          message: 'set operations must always be many-to-many',
364          severity: 'error',
365        },
366      ],
367    },
368    {
369      expr: 'foo and on(bar) group_right(baz) bar',
370      expectedValueType: ValueType.vector,
371      expectedDiag: [
372        {
373          from: 0,
374          to: 36,
375          message: 'no grouping allowed for set operations',
376          severity: 'error',
377        },
378        {
379          from: 0,
380          to: 36,
381          message: 'set operations must always be many-to-many',
382          severity: 'error',
383        },
384      ],
385    },
386    {
387      expr: 'foo or on(bar) group_left(baz) bar',
388      expectedValueType: ValueType.vector,
389      expectedDiag: [
390        {
391          from: 0,
392          to: 34,
393          message: 'no grouping allowed for set operations',
394          severity: 'error',
395        },
396        {
397          from: 0,
398          to: 34,
399          message: 'set operations must always be many-to-many',
400          severity: 'error',
401        },
402      ],
403    },
404    {
405      expr: 'foo or on(bar) group_right(baz) bar',
406      expectedValueType: ValueType.vector,
407      expectedDiag: [
408        {
409          from: 0,
410          to: 35,
411          message: 'no grouping allowed for set operations',
412          severity: 'error',
413        },
414        {
415          from: 0,
416          to: 35,
417          message: 'set operations must always be many-to-many',
418          severity: 'error',
419        },
420      ],
421    },
422    {
423      expr: 'foo unless on(bar) group_left(baz) bar',
424      expectedValueType: ValueType.vector,
425      expectedDiag: [
426        {
427          from: 0,
428          to: 38,
429          message: 'no grouping allowed for set operations',
430          severity: 'error',
431        },
432        {
433          from: 0,
434          to: 38,
435          message: 'set operations must always be many-to-many',
436          severity: 'error',
437        },
438      ],
439    },
440    {
441      expr: 'foo unless on(bar) group_right(baz) bar',
442      expectedValueType: ValueType.vector,
443      expectedDiag: [
444        {
445          from: 0,
446          to: 39,
447          message: 'no grouping allowed for set operations',
448          severity: 'error',
449        },
450        {
451          from: 0,
452          to: 39,
453          message: 'set operations must always be many-to-many',
454          severity: 'error',
455        },
456      ],
457    },
458    {
459      expr: 'http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}',
460      expectedValueType: ValueType.vector,
461      expectedDiag: [
462        {
463          from: 0,
464          to: 95,
465          message: 'label "instance" must not occur in ON and GROUP clause at once',
466          severity: 'error',
467        },
468      ],
469    },
470    {
471      expr: 'foo + bool bar',
472      expectedValueType: ValueType.vector,
473      expectedDiag: [
474        {
475          from: 0,
476          to: 14,
477          message: 'bool modifier can only be used on comparison operators',
478          severity: 'error',
479        },
480      ],
481    },
482    {
483      expr: 'foo + bool 10',
484      expectedValueType: ValueType.vector,
485      expectedDiag: [
486        {
487          from: 0,
488          to: 13,
489          message: 'bool modifier can only be used on comparison operators',
490          severity: 'error',
491        },
492      ],
493    },
494    {
495      expr: 'foo and bool 10',
496      expectedValueType: ValueType.vector,
497      expectedDiag: [
498        {
499          from: 0,
500          to: 15,
501          message: 'bool modifier can only be used on comparison operators',
502          severity: 'error',
503        },
504        {
505          from: 0,
506          to: 15,
507          message: 'set operator not allowed in binary scalar expression',
508          severity: 'error',
509        },
510      ],
511    },
512    // test aggregration
513    {
514      expr: 'sum by (foo)(some_metric)',
515      expectedValueType: ValueType.vector,
516      expectedDiag: [],
517    },
518    {
519      expr: 'avg by (foo)(some_metric)',
520      expectedValueType: ValueType.vector,
521      expectedDiag: [],
522    },
523    {
524      expr: 'max by (foo)(some_metric)',
525      expectedValueType: ValueType.vector,
526      expectedDiag: [],
527    },
528    {
529      expr: 'sum without (foo) (some_metric)',
530      expectedValueType: ValueType.vector,
531      expectedDiag: [],
532    },
533    {
534      expr: 'sum (some_metric) without (foo)',
535      expectedValueType: ValueType.vector,
536      expectedDiag: [],
537    },
538    {
539      expr: 'stddev(some_metric)',
540      expectedValueType: ValueType.vector,
541      expectedDiag: [],
542    },
543    {
544      expr: 'stdvar by (foo)(some_metric)',
545      expectedValueType: ValueType.vector,
546      expectedDiag: [],
547    },
548    {
549      expr: 'sum by ()(some_metric)',
550      expectedValueType: ValueType.vector,
551      expectedDiag: [],
552    },
553    {
554      expr: 'sum by (foo,bar,)(some_metric)',
555      expectedValueType: ValueType.vector,
556      expectedDiag: [],
557    },
558    {
559      expr: 'sum by (foo,)(some_metric)',
560      expectedValueType: ValueType.vector,
561      expectedDiag: [],
562    },
563    {
564      expr: 'topk(5, some_metric)',
565      expectedValueType: ValueType.vector,
566      expectedDiag: [],
567    },
568    {
569      expr: 'topk( # my awesome comment\n' + '5, some_metric)',
570      expectedValueType: ValueType.vector,
571      expectedDiag: [],
572    },
573    {
574      expr: 'count_values("value", some_metric)',
575      expectedValueType: ValueType.vector,
576      expectedDiag: [],
577    },
578    {
579      expr: 'sum without(and, by, avg, count, alert, annotations)(some_metric)',
580      expectedValueType: ValueType.vector,
581      expectedDiag: [],
582    },
583    {
584      expr: 'sum some_metric by (test)',
585      expectedValueType: ValueType.vector,
586      expectedDiag: [
587        {
588          from: 0,
589          to: 25,
590          message: 'unable to find the parameter for the expression',
591          severity: 'error',
592        },
593      ],
594    },
595    // Test function calls.
596    {
597      expr: 'time()',
598      expectedValueType: ValueType.scalar,
599      expectedDiag: [],
600    },
601    {
602      expr: 'floor(some_metric{foo!="bar"})',
603      expectedValueType: ValueType.vector,
604      expectedDiag: [],
605    },
606    {
607      expr: 'rate(some_metric[5m])',
608      expectedValueType: ValueType.vector,
609      expectedDiag: [],
610    },
611    {
612      expr: 'round(some_metric)',
613      expectedValueType: ValueType.vector,
614      expectedDiag: [],
615    },
616    {
617      expr: 'round(some_metric, 5)',
618      expectedValueType: ValueType.vector,
619      expectedDiag: [],
620    },
621    {
622      expr: 'floor()',
623      expectedValueType: ValueType.vector,
624      expectedDiag: [
625        {
626          from: 0,
627          to: 7,
628          message: 'expected 1 argument(s) in call to "floor", got 0',
629          severity: 'error',
630        },
631      ],
632    },
633    {
634      expr: 'floor(some_metric, other_metric)',
635      expectedValueType: ValueType.vector,
636      expectedDiag: [
637        {
638          from: 0,
639          to: 32,
640          message: 'expected 1 argument(s) in call to "floor", got 2',
641          severity: 'error',
642        },
643      ],
644    },
645    {
646      expr: 'floor(some_metric, 1)',
647      expectedValueType: ValueType.vector,
648      expectedDiag: [
649        {
650          from: 0,
651          to: 21,
652          message: 'expected 1 argument(s) in call to "floor", got 2',
653          severity: 'error',
654        },
655      ],
656    },
657    {
658      expr: 'floor(1)',
659      expectedValueType: ValueType.vector,
660      expectedDiag: [
661        {
662          from: 6,
663          to: 7,
664          message: 'expected type vector in call to function "floor", got scalar',
665          severity: 'error',
666        },
667      ],
668    },
669    {
670      expr: 'hour(some_metric, some_metric, some_metric)',
671      expectedValueType: ValueType.vector,
672      expectedDiag: [
673        {
674          from: 0,
675          to: 43,
676          message: 'expected at most 1 argument(s) in call to "hour", got 3',
677          severity: 'error',
678        },
679      ],
680    },
681    {
682      expr: 'time(some_metric)',
683      expectedValueType: ValueType.scalar,
684      expectedDiag: [
685        {
686          from: 0,
687          to: 17,
688          message: 'expected 0 argument(s) in call to "time", got 1',
689          severity: 'error',
690        },
691      ],
692    },
693    {
694      expr: 'rate(some_metric)',
695      expectedValueType: ValueType.vector,
696      expectedDiag: [
697        {
698          from: 5,
699          to: 16,
700          message: 'expected type matrix in call to function "rate", got vector',
701          severity: 'error',
702        },
703      ],
704    },
705    {
706      expr:
707        'histogram_quantile(                                             # Root of the query, final result, approximates a quantile.\n' +
708        '  0.9,                                                          # 1st argument to histogram_quantile(), the target quantile.\n' +
709        '  sum by(le, method, path) (                                    # 2nd argument to histogram_quantile(), an aggregated histogram.\n' +
710        '    rate(                                                       # Argument to sum(), the per-second increase of a histogram over 5m.\n' +
711        '      demo_api_request_duration_seconds_bucket{job="demo"}[5m]  # Argument to rate(), the raw histogram series over the last 5m.\n' +
712        '    )\n' +
713        '  )\n' +
714        ')',
715      expectedValueType: ValueType.vector,
716      expectedDiag: [],
717    },
718    {
719      expr: '1 @ start()',
720      expectedValueType: ValueType.scalar,
721      expectedDiag: [
722        {
723          from: 0,
724          to: 11,
725          message: '@ modifier must be preceded by an instant selector vector or range vector selector or a subquery',
726          severity: 'error',
727        },
728      ],
729    },
730    {
731      expr: 'foo @ 879',
732      expectedValueType: ValueType.vector,
733      expectedDiag: [],
734    },
735    {
736      expr: 'food @ start()',
737      expectedValueType: ValueType.vector,
738      expectedDiag: [],
739    },
740    {
741      expr: 'food @ end()',
742      expectedValueType: ValueType.vector,
743      expectedDiag: [],
744    },
745    {
746      expr: 'sum (rate(foo[5m])) @ 456',
747      expectedValueType: ValueType.vector,
748      expectedDiag: [],
749    },
750  ];
751  testCases.forEach((value) => {
752    const state = createEditorState(value.expr);
753    const parser = new Parser(state);
754    it(value.expr, () => {
755      chai.expect(parser.checkAST(syntaxTree(state).topNode.firstChild)).to.equal(value.expectedValueType);
756      chai.expect(parser.getDiagnostics()).to.deep.equal(value.expectedDiag);
757    });
758  });
759});
760