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