cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.79.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/parser/source/source.go

1583lines · modecode

1package source
2
3import (
4 "fmt"
5 "math"
6 "regexp"
7 "slices"
8 "strconv"
9 "strings"
10
11 "github.com/prometheus/common/model"
12 "github.com/prometheus/prometheus/model/labels"
13 promParser "github.com/prometheus/prometheus/promql/parser"
14 "github.com/prometheus/prometheus/promql/parser/posrange"
15)
16
17var guaranteedLabelsMatches = []labels.MatchType{labels.MatchEqual, labels.MatchRegexp}
18
19type Type uint8
20
21const (
22 UnknownSource Type = iota
23 NumberSource
24 StringSource
25 SelectorSource
26 FuncSource
27 AggregateSource
28)
29
30// Used for test snapshots.
31func (st Type) MarshalYAML() (any, error) {
32 var name string
33 switch st { // nolint: exhaustive
34 case NumberSource:
35 name = "number"
36 case StringSource:
37 name = "string"
38 case SelectorSource:
39 name = "selector"
40 case FuncSource:
41 name = "function"
42 case AggregateSource:
43 name = "aggregation"
44 }
45 return name, nil
46}
47
48type LabelPromiseType uint8
49
50const (
51 ImpossibleLabel LabelPromiseType = iota
52 PossibleLabel
53 GuaranteedLabel
54)
55
56// Used for test snapshots.
57func (lpt LabelPromiseType) MarshalYAML() (any, error) {
58 var name string
59 switch lpt {
60 case ImpossibleLabel:
61 name = "excluded"
62 case PossibleLabel:
63 name = "included"
64 case GuaranteedLabel:
65 name = "guaranteed"
66 }
67 return name, nil
68}
69
70type LabelTransform struct {
71 Reason string
72 Kind LabelPromiseType
73 Fragment posrange.PositionRange
74}
75
76type DeadInfo struct {
77 Reason string
78 Fragment posrange.PositionRange
79}
80
81type DeadLabelKind uint8
82
83const (
84 ImpossibleDeadLabel DeadLabelKind = iota
85 OrphanedLabel
86 DuplicatedJoin
87 UnusedLabel
88)
89
90func (dlk DeadLabelKind) String() string {
91 switch dlk {
92 case ImpossibleDeadLabel:
93 return "impossible label"
94 case OrphanedLabel:
95 return "orphaned label"
96 case DuplicatedJoin:
97 return "redundant label"
98 case UnusedLabel:
99 return "unused label"
100 }
101 return "unknown"
102}
103
104type DeadLabel struct {
105 Name string
106 Reason string
107 LabelReason string
108 UsageFragment posrange.PositionRange
109 LabelFragment posrange.PositionRange
110 Kind DeadLabelKind
111}
112
113type ReturnInfo struct {
114 LogicalExpr string
115 ValuePosition posrange.PositionRange
116 ReturnedNumber float64 // If AlwaysReturns=true this is the number that's returned
117 AlwaysReturns bool // True if this source always returns results.
118 KnownReturn bool // True if we always know the return value.
119 IsReturnBool bool // True if this source uses the 'bool' modifier.
120}
121
122type Operation struct {
123 Node promParser.Node
124 Operation string
125 Arguments []string
126}
127
128// Used for test snapshots.
129func (so Operation) MarshalYAML() (any, error) {
130 y := map[string]any{
131 "op": so.Operation,
132 "node": fmt.Sprintf("[%T] %s", so.Node, so.Node.String()),
133 }
134 if so.Arguments != nil {
135 y["args"] = so.Arguments
136 }
137 return y, nil
138}
139
140type Operations []Operation
141
142func MostOuterOperation[T promParser.Node](s Source) (T, bool) {
143 for i := len(s.Operations) - 1; i >= 0; i-- {
144 op := s.Operations[i]
145 if o, ok := op.Node.(T); ok {
146 return o, true
147 }
148 }
149 return *new(T), false
150}
151
152type Join struct {
153 MatchingLabels []string
154 AddedLabels []string
155 Src Source // The source we're joining with.
156 Op promParser.ItemType // The binary operation used for this join.
157 Depth int // Zero if this is a direct join, non-zero otherwise. sum(foo * bar) would be in-direct join.
158 IsOn bool
159}
160
161type Unless struct {
162 MatchingLabels []string
163 Src Source
164 IsOn bool
165}
166
167type Source struct {
168 Labels map[string]LabelTransform `yaml:"labels,omitempty"`
169 DeadInfo *DeadInfo `yaml:"deadInfo,omitempty"`
170 DeadLabels []DeadLabel `yaml:"deadLabels,omitempty"`
171 Returns promParser.ValueType `yaml:"returns"`
172 Operations Operations `yaml:"operations,omitempty"`
173 // Any other sources this source joins with.
174 Joins []Join `yaml:"joins,omitempty"`
175 // Any other sources this source is suppressed by.
176 Unless []Unless `yaml:"unless,omitempty"`
177 UsedLabels []string `yaml:"usedLabels,omitempty"`
178 ReturnInfo ReturnInfo `yaml:"returnInfo,omitempty"`
179 Position posrange.PositionRange `yaml:"position"`
180 Type Type `yaml:"type"`
181 // Labels are fixed and only allowed labels can be present.
182 FixedLabels bool `yaml:"fixedLabels,omitempty"`
183 // True if this source is guarded by 'foo > 5' or other condition.
184 IsConditional bool `yaml:"isConditional,omitempty"`
185}
186
187func (s Source) Operation() string {
188 if len(s.Operations) > 0 {
189 return s.Operations[len(s.Operations)-1].Operation
190 }
191 return ""
192}
193
194func (s Source) CanHaveLabel(name string) bool {
195 if v, ok := s.Labels[name]; ok {
196 if v.Kind == ImpossibleLabel {
197 return false
198 }
199 if v.Kind == PossibleLabel || v.Kind == GuaranteedLabel {
200 return true
201 }
202 }
203 return !s.FixedLabels
204}
205
206func (s Source) TransformedLabels(kinds ...LabelPromiseType) []string {
207 names := make([]string, 0, len(s.Labels))
208 for name, l := range s.Labels {
209 if slices.Contains(kinds, l.Kind) {
210 names = append(names, name)
211 }
212 }
213 return names
214}
215
216func (s Source) LabelExcludeReason(name string) (string, posrange.PositionRange) {
217 if l, ok := s.Labels[name]; ok && l.Kind == ImpossibleLabel {
218 return l.Reason, l.Fragment
219 }
220 return s.Labels[""].Reason, s.Labels[""].Fragment
221}
222
223func (s *Source) excludeAllLabels(expr, reason string, fragment, allFragment posrange.PositionRange, except []string) {
224 // Everything that was included until now but will be removed needs an explicit stamp to mark it as gone.
225 for name, l := range s.Labels {
226 if slices.Contains(except, name) {
227 continue
228 }
229 if l.Kind == PossibleLabel || l.Kind == GuaranteedLabel {
230 s.Labels[name] = LabelTransform{
231 Kind: ImpossibleLabel,
232 Reason: reason,
233 Fragment: fragment,
234 }
235 }
236 }
237 s.UsedLabels = slices.DeleteFunc(s.UsedLabels, func(name string) bool {
238 return !slices.Contains(except, name)
239 })
240 // Mark except labels as possible, unless they are already guaranteed.
241 s.UsedLabels = appendToSlice(s.UsedLabels, except...)
242 for _, name := range except {
243 if l, ok := s.Labels[name]; ok && l.Kind == GuaranteedLabel {
244 continue
245 }
246
247 // We have grouping labels set, if they are possible mark them as such, if not mark as impossible.
248 if s.CanHaveLabel(name) {
249 s.Labels[name] = LabelTransform{
250 Kind: PossibleLabel,
251 Reason: reason,
252 Fragment: FindArgumentPosition(expr, fragment, name),
253 }
254 } else {
255 r, f := s.LabelExcludeReason(name)
256 s.Labels[name] = LabelTransform{
257 Kind: ImpossibleLabel,
258 Reason: r,
259 Fragment: f,
260 }
261 }
262
263 }
264 s.Labels[""] = LabelTransform{
265 Kind: ImpossibleLabel,
266 Reason: reason,
267 Fragment: allFragment,
268 }
269 s.FixedLabels = true
270}
271
272func (s *Source) excludeLabel(reason string, fragment posrange.PositionRange, name string) {
273 s.Labels[name] = LabelTransform{
274 Kind: ImpossibleLabel,
275 Reason: reason,
276 Fragment: fragment,
277 }
278 s.UsedLabels = slices.DeleteFunc(s.UsedLabels, func(s string) bool {
279 return s == name
280 })
281}
282
283func (s *Source) joinLabels(expr string, within posrange.PositionRange, op promParser.ItemType, names []string, outside []posrange.PositionRange) {
284 for _, name := range names {
285 if l, ok := s.Labels[name]; ok && l.Kind == GuaranteedLabel {
286 s.DeadLabels = append(s.DeadLabels, DeadLabel{
287 Kind: DuplicatedJoin,
288 Name: name,
289 Reason: "Query is trying to join the `" + name + "` label that is already present on the other side of the query.",
290 UsageFragment: FindArgumentPosition(
291 expr,
292 FindFuncPosition(expr, within, promParser.ItemTypeStr[op], outside),
293 name,
294 ),
295 LabelReason: l.Reason,
296 LabelFragment: l.Fragment,
297 })
298 return
299 }
300 s.Labels[name] = LabelTransform{
301 Kind: PossibleLabel,
302 Reason: fmt.Sprintf(
303 "Query is using `%s(%s)`, all labels included inside `%s(...)` will be joined to the results on the other side of the query.",
304 promParser.ItemTypeStr[op], strings.Join(names, ", "), promParser.ItemTypeStr[op],
305 ),
306 Fragment: FindArgumentPosition(
307 expr,
308 FindFuncPosition(expr, within, promParser.ItemTypeStr[op], outside),
309 name,
310 ),
311 }
312 }
313}
314
315func (s *Source) includeLabel(expr, reason string, fragment posrange.PositionRange, name string) {
316 if l, ok := s.Labels[name]; ok && l.Kind == GuaranteedLabel {
317 return
318 }
319 s.Labels[name] = LabelTransform{
320 Kind: PossibleLabel,
321 Reason: reason,
322 Fragment: FindArgumentPosition(expr, fragment, name),
323 }
324}
325
326func (s *Source) guaranteeLabel(reason string, fragment posrange.PositionRange, names ...string) {
327 for _, name := range names {
328 s.Labels[name] = LabelTransform{
329 Kind: GuaranteedLabel,
330 Reason: reason,
331 Fragment: fragment,
332 }
333 }
334}
335
336func (s *Source) checkIncludedLabels(expr string, pos posrange.PositionRange, names []string) {
337 for _, name := range names {
338 if !s.CanHaveLabel(name) {
339 reason, fragment := s.LabelExcludeReason(name)
340 s.DeadLabels = append(s.DeadLabels, DeadLabel{
341 Kind: ImpossibleDeadLabel,
342 Name: name,
343 Reason: "You can't use `" + name + "` because this label is not possible here.",
344 UsageFragment: FindArgumentPosition(expr, pos, name),
345 LabelReason: reason,
346 LabelFragment: fragment,
347 })
348 }
349 }
350}
351
352func (s *Source) checkAggregationLabels(expr string, n *promParser.AggregateExpr) {
353 var pos posrange.PositionRange
354 switch {
355 case len(n.Grouping) == 0:
356 pos = FindFuncNamePosition(expr, n.PosRange, promParser.ItemTypeStr[n.Op])
357 case n.Without:
358 pos = FindFuncPosition(expr, n.PosRange, promParser.ItemTypeStr[promParser.WITHOUT], nil)
359 default:
360 pos = FindFuncPosition(expr, n.PosRange, promParser.ItemTypeStr[promParser.BY], nil)
361 }
362
363 for _, j := range s.Joins {
364 for _, name := range j.AddedLabels {
365 if slices.Contains(s.UsedLabels, name) {
366 continue
367 }
368 if !n.Without && slices.Contains(n.Grouping, name) {
369 continue
370 }
371 if n.Without && !slices.Contains(n.Grouping, name) {
372 continue
373 }
374 var (
375 labelPos = pos
376 labelReason string
377 )
378 if t := s.findLabelTransform(name); t != nil {
379 labelPos = t.Fragment
380 labelReason = t.Reason
381 }
382 s.DeadLabels = append(s.DeadLabels, DeadLabel{
383 Kind: UnusedLabel,
384 Name: name,
385 Reason: fmt.Sprintf("Previously joined label `%s` is being removed from the results.", name),
386 UsageFragment: FindArgumentPosition(expr, pos, name),
387 LabelReason: labelReason,
388 LabelFragment: labelPos,
389 })
390 }
391 }
392}
393
394func (s *Source) findLabelTransform(name string) *LabelTransform {
395 if t, ok := s.Labels[name]; ok {
396 return &t
397 }
398 for _, j := range s.Joins {
399 if t := j.Src.findLabelTransform(name); t != nil {
400 return t
401 }
402 }
403 return nil
404}
405
406func (s *Source) checkJoinedLabels(expr string, n *promParser.BinaryExpr, dst Source) (dead []DeadLabel) {
407 pos := findBinOpsOperatorPosition(expr, n, promParser.ItemTypeStr[n.Op])
408 for _, j := range s.Joins {
409 for _, name := range j.AddedLabels {
410 if slices.Contains(n.VectorMatching.Include, name) {
411 // label is included in group_left or group_right
412 continue
413 }
414 if n.VectorMatching.On && slices.Contains(n.VectorMatching.MatchingLabels, name) {
415 // label is included inside on(...)
416 continue
417 }
418 if !n.VectorMatching.On && !slices.Contains(n.VectorMatching.MatchingLabels, name) {
419 // label is NOT included inside ignoring(...)
420 continue
421 }
422 var (
423 labelPos = pos
424 labelReason string
425 )
426 if t := dst.findLabelTransform(name); t != nil {
427 labelPos = t.Fragment
428 labelReason = t.Reason
429 }
430 dead = append(dead, DeadLabel{
431 Kind: OrphanedLabel,
432 Name: name,
433 Reason: fmt.Sprintf("This binary operation prevents previously joined label `%s` from being added to the results.", name),
434 UsageFragment: pos,
435 LabelReason: labelReason,
436 LabelFragment: labelPos,
437 })
438 }
439 }
440 return dead
441}
442
443func (s *Source) useLabelsNotExcluded(excluded []string) {
444 // Iterating over a map can yield labels in different order each time
445 // so append labels to an extra slice, sort it, and then append the
446 // sorted results to UsedLabels.
447 // Without this tests might show a diff sometimes.
448 toAdd := make([]string, 0, len(s.Labels))
449 for name, lt := range s.Labels {
450 if lt.Kind == ImpossibleLabel {
451 continue
452 }
453 if !slices.Contains(excluded, name) {
454 toAdd = appendToSlice(toAdd, name)
455 }
456 }
457 slices.Sort(toAdd)
458 s.UsedLabels = appendToSlice(s.UsedLabels, toAdd...)
459}
460
461type Visitor func(s Source, j *Join, u *Unless)
462
463func innerWalk(fn Visitor, s Source, j *Join, u *Unless) {
464 fn(s, j, u)
465 for _, j := range s.Joins {
466 innerWalk(fn, j.Src, &j, nil)
467 }
468 for _, u := range s.Unless {
469 innerWalk(fn, u.Src, nil, &u)
470 }
471}
472
473func (s Source) WalkSources(fn Visitor) {
474 innerWalk(fn, s, nil, nil)
475}
476
477func LabelsSource(expr string, node promParser.Node) (src []Source) {
478 return walkNode(expr, node)
479}
480
481func walkNode(expr string, node promParser.Node) (src []Source) {
482 switch n := node.(type) {
483 case *promParser.AggregateExpr:
484 src = append(src, walkAggregation(expr, n)...)
485
486 case *promParser.BinaryExpr:
487 src = append(src, parseBinOps(expr, n)...)
488
489 case *promParser.Call:
490 src = append(src, parseCall(expr, n)...)
491
492 case *promParser.MatrixSelector:
493 for _, s := range walkNode(expr, n.VectorSelector) {
494 s.Returns = promParser.ValueTypeMatrix
495 /*
496 // Prepend Matrix operation
497 s.Operations = append(SourceOperations{
498 {
499 Operation: "",
500 Node: node,
501 Arguments: nil,
502 },
503 }, s.Operations...)
504 */
505 src = append(src, s)
506 }
507
508 case *promParser.SubqueryExpr:
509 src = append(src, walkNode(expr, n.Expr)...)
510
511 case *promParser.NumberLiteral:
512 var s Source
513 s.Labels = map[string]LabelTransform{}
514 s.Type = NumberSource
515 s.Returns = promParser.ValueTypeScalar
516 s.ReturnInfo.KnownReturn = true
517 s.ReturnInfo.ReturnedNumber = n.Val
518 s.ReturnInfo.AlwaysReturns = true
519 s.ReturnInfo.ValuePosition = n.PosRange
520 s.excludeAllLabels(expr, "This query returns a number value with no labels.", n.PosRange, n.PosRange, nil)
521 s.Position = n.PosRange
522 src = append(src, s)
523
524 case *promParser.ParenExpr:
525 src = append(src, walkNode(expr, n.Expr)...)
526
527 case *promParser.StringLiteral:
528 var s Source
529 s.Labels = map[string]LabelTransform{}
530 s.Type = StringSource
531 s.Returns = promParser.ValueTypeString
532 s.ReturnInfo.AlwaysReturns = true
533 s.excludeAllLabels(expr, "This query returns a string value with no labels.", n.PosRange, n.PosRange, nil)
534 s.Position = n.PosRange
535 src = append(src, s)
536
537 case *promParser.UnaryExpr:
538 src = append(src, walkNode(expr, n.Expr)...)
539
540 // case *promParser.StepInvariantExpr:
541 // Not possible to get this from the parser.
542
543 case *promParser.VectorSelector:
544 var s Source
545 s.Labels = map[string]LabelTransform{}
546 s.Type = SelectorSource
547 s.Returns = promParser.ValueTypeVector
548 s.Operations = append(s.Operations, Operation{
549 Operation: "",
550 Node: n,
551 Arguments: nil,
552 })
553 s.guaranteeLabel(
554 "Query will only return series where these labels are present.",
555 n.PosRange,
556 labelsFromSelectors(guaranteedLabelsMatches, n)...,
557 )
558 for _, name := range labelsWithEmptyValueSelector(n) {
559 s.excludeLabel(
560 "Query uses `{"+name+"=\"\"}` selector which will filter out any time series with the `"+name+"` label set.",
561 n.PosRange,
562 name,
563 )
564 }
565 s.Position = n.PosRange
566 src = append(src, s)
567 }
568 return src
569}
570
571func appendToSlice(dst []string, values ...string) []string {
572 for _, v := range values {
573 if !slices.Contains(dst, v) {
574 dst = append(dst, v)
575 }
576 }
577 return dst
578}
579
580func labelsFromSelectors(matches []labels.MatchType, selector *promParser.VectorSelector) (names []string) {
581 if selector == nil {
582 return nil
583 }
584 // Any label used in positive filters is gurnateed to be present.
585 for _, lm := range selector.LabelMatchers {
586 if lm.Name == model.MetricNameLabel {
587 continue
588 }
589 if !slices.Contains(matches, lm.Type) {
590 continue
591 }
592 names = appendToSlice(names, lm.Name)
593 }
594 return names
595}
596
597func labelsWithEmptyValueSelector(selector *promParser.VectorSelector) (names []string) {
598 for _, lm := range selector.LabelMatchers {
599 if lm.Name == model.MetricNameLabel {
600 continue
601 }
602 if lm.Type == labels.MatchEqual && lm.Value == "" {
603 names = appendToSlice(names, lm.Name)
604 }
605 }
606 return names
607}
608
609func GetQueryFragment(expr string, pos posrange.PositionRange) string {
610 return expr[pos.Start:pos.End]
611}
612
613func walkAggregation(expr string, n *promParser.AggregateExpr) (src []Source) {
614 var s Source
615
616 var args []string
617 if n.Param != nil {
618 args = append(args, n.Param.String())
619 }
620
621 switch n.Op {
622 case promParser.COUNT_VALUES:
623 for _, s = range parseAggregation(expr, n) {
624 s.Operations = append(s.Operations, Operation{
625 Operation: promParser.ItemTypeStr[n.Op],
626 Node: n,
627 Arguments: args,
628 })
629 // Param is the label to store the count value in.
630 s.guaranteeLabel(
631 "This label will be added to the results by the count_values() call.",
632 n.PosRange,
633 n.Param.(*promParser.StringLiteral).Val,
634 )
635 if n.Without || !slices.Contains(n.Grouping, model.MetricNameLabel) {
636 s.excludeLabel("Aggregation removes metric name.", n.PosRange, model.MetricNameLabel)
637 }
638 src = append(src, s)
639 }
640 case promParser.TOPK, promParser.BOTTOMK:
641 for _, s = range walkNode(expr, n.Expr) {
642 for i := range s.Joins {
643 s.Joins[i].Depth++
644 }
645 s.Type = AggregateSource
646 s.Operations = append(s.Operations, Operation{
647 Operation: promParser.ItemTypeStr[n.Op],
648 Node: n,
649 Arguments: args,
650 })
651 src = append(src, s)
652 }
653 default:
654 for _, s = range parseAggregation(expr, n) {
655 s.Operations = append(s.Operations, Operation{
656 Operation: promParser.ItemTypeStr[n.Op],
657 Node: n,
658 Arguments: args,
659 })
660 if n.Without || !slices.Contains(n.Grouping, model.MetricNameLabel) {
661 s.excludeLabel("Aggregation removes metric name.", n.PosRange, model.MetricNameLabel)
662 }
663 src = append(src, s)
664 }
665 /*
666 TODO these are experimental and promParser.EnableExperimentalFunctions must be set to true to enable parsing of these.
667 case promParser.LIMITK:
668 s = walkNode(expr, n.Expr)
669 s.Type = AggregateSource
670 s.Operation = "limitk"
671 case promParser.LIMIT_RATIO:
672 s = walkNode(expr, n.Expr)
673 s.Type = AggregateSource
674 s.Operation = "limit_ratio"
675 */
676 }
677 return src
678}
679
680func parseAggregation(expr string, n *promParser.AggregateExpr) (src []Source) {
681 var s Source
682 for _, s = range walkNode(expr, n.Expr) {
683 // If we have sum(foo * bar) then we start with:
684 // - source: foo
685 // joins: bar
686 // Then we parse aggregation and end up with:
687 // - source: sum(foo)
688 // joins: bar
689 // Which is misleading and wrong, so we bump depth value to know about it.
690 for i := range s.Joins {
691 s.Joins[i].Depth++
692 }
693
694 s.checkAggregationLabels(expr, n)
695 if n.Without {
696 for _, name := range n.Grouping {
697 s.excludeLabel(
698 fmt.Sprintf("Query is using aggregation with `%s(%s)`, all labels included inside `%s(...)` will be removed from the results.",
699 promParser.ItemTypeStr[promParser.WITHOUT], strings.Join(n.Grouping, ", "), promParser.ItemTypeStr[promParser.WITHOUT]),
700 FindArgumentPosition(
701 expr,
702 FindFuncPosition(expr, n.PosRange, promParser.ItemTypeStr[promParser.WITHOUT], nil),
703 name,
704 ),
705 name,
706 )
707 }
708 } else {
709 if len(n.Grouping) == 0 {
710 funcNamePos := FindFuncNamePosition(expr, n.PosRange, promParser.ItemTypeStr[n.Op])
711 s.excludeAllLabels(
712 expr,
713 "Query is using aggregation that removes all labels.",
714 funcNamePos,
715 funcNamePos,
716 nil,
717 )
718 } else {
719 s.UsedLabels = appendToSlice(s.UsedLabels, n.Grouping...)
720 s.checkIncludedLabels(
721 expr,
722 FindFuncPosition(expr, n.PosRange, promParser.ItemTypeStr[promParser.BY], nil),
723 n.Grouping,
724 )
725 s.excludeAllLabels(
726 expr,
727 fmt.Sprintf("Query is using aggregation with `%s(%s)`, only labels included inside `%s(...)` will be present on the results.",
728 promParser.ItemTypeStr[promParser.BY], strings.Join(n.Grouping, ", "), promParser.ItemTypeStr[promParser.BY]),
729 FindFuncPosition(expr, n.PosRange, promParser.ItemTypeStr[promParser.BY], nil),
730 FindFuncNamePosition(expr, n.PosRange, promParser.ItemTypeStr[promParser.BY]),
731 n.Grouping,
732 )
733 }
734 }
735 s.Type = AggregateSource
736 s.Returns = promParser.ValueTypeVector
737 src = append(src, s)
738 }
739 return src
740}
741
742func parsePromQLFunc(s Source, expr string, n *promParser.Call) Source {
743 switch n.Func.Name {
744 case "abs", "sgn", "acos", "acosh", "asin", "asinh", "atan", "atanh", "cos", "cosh", "sin", "sinh", "tan", "tanh":
745 // No change to labels.
746 s.Returns = promParser.ValueTypeVector
747 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
748 s.guaranteeLabel(
749 "Query will only return series where these labels are present.",
750 n.PosRange,
751 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
752 )
753
754 case "ceil", "floor", "round":
755 // No change to labels.
756 s.Returns = promParser.ValueTypeVector
757 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
758 s.guaranteeLabel(
759 "Query will only return series where these labels are present.",
760 n.PosRange,
761 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
762 )
763
764 case "changes", "resets":
765 // No change to labels.
766 s.Returns = promParser.ValueTypeVector
767 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
768 s.guaranteeLabel(
769 "Query will only return series where these labels are present.",
770 n.PosRange,
771 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
772 )
773
774 case "clamp", "clamp_max", "clamp_min":
775 // No change to labels.
776 s.Returns = promParser.ValueTypeVector
777 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
778 s.guaranteeLabel(
779 "Query will only return series where these labels are present.",
780 n.PosRange,
781 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
782 )
783
784 case "absent", "absent_over_time":
785 s.Returns = promParser.ValueTypeVector
786 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
787 names := labelsFromSelectors([]labels.MatchType{labels.MatchEqual}, vs)
788 funcNamePos := FindFuncNamePosition(expr, n.PosRange, n.Func.Name)
789 s.excludeAllLabels(
790 expr,
791 fmt.Sprintf(`The [%s()](https://prometheus.io/docs/prometheus/latest/querying/functions/#%s) function is used to check if provided query doesn't match any time series.
792You will only get any results back if the metric selector you pass doesn't match anything.
793Since there are no matching time series there are also no labels. If some time series is missing you cannot read its labels.
794This means that the only labels you can get back from absent call are the ones you pass to it.
795If you're hoping to get instance specific labels this way and alert when some target is down then that won't work, use the `+"`up`"+` metric instead.`,
796 n.Func.Name, n.Func.Name),
797 funcNamePos,
798 funcNamePos,
799 names,
800 )
801 s.guaranteeLabel(
802 fmt.Sprintf("All labels passed to %s() call will be present on the results if the query doesn't match anything.", n.Func.Name),
803 n.PosRange,
804 names...,
805 )
806
807 case "avg_over_time", "count_over_time", "last_over_time", "max_over_time", "min_over_time", "present_over_time", "quantile_over_time", "stddev_over_time", "stdvar_over_time", "sum_over_time":
808 // No change to labels.
809 s.Returns = promParser.ValueTypeVector
810 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
811 s.guaranteeLabel(
812 "Query will only return series where these labels are present.",
813 n.PosRange,
814 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
815 )
816
817 case "days_in_month", "day_of_month", "day_of_week", "day_of_year", "hour", "minute", "month", "year":
818 s.Returns = promParser.ValueTypeVector
819 // No labels if we don't pass any arguments.
820 // Otherwise no change to labels.
821 if len(n.Args) == 0 {
822 s.ReturnInfo.AlwaysReturns = true
823 s.excludeAllLabels(
824 expr,
825 fmt.Sprintf("Calling `%s()` with no arguments will return an empty time series with no labels.",
826 n.Func.Name),
827 n.PosRange,
828 n.PosRange,
829 nil,
830 )
831 } else {
832 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
833 s.guaranteeLabel(
834 "Query will only return series where these labels are present.",
835 n.PosRange,
836 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
837 )
838 }
839
840 case "deg", "rad", "ln", "log10", "log2", "sqrt", "exp":
841 // No change to labels.
842 s.Returns = promParser.ValueTypeVector
843 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
844 s.guaranteeLabel(
845 "Query will only return series where these labels are present.",
846 n.PosRange,
847 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
848 )
849 case "delta", "idelta", "increase", "deriv", "irate", "rate":
850 // No change to labels.
851 s.Returns = promParser.ValueTypeVector
852 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
853 s.guaranteeLabel(
854 "Query will only return series where these labels are present.",
855 n.PosRange,
856 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
857 )
858
859 case "histogram_avg", "histogram_count", "histogram_sum", "histogram_stddev", "histogram_stdvar", "histogram_fraction", "histogram_quantile":
860 // No change to labels.
861 s.Returns = promParser.ValueTypeVector
862 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
863 s.guaranteeLabel(
864 "Query will only return series where these labels are present.",
865 n.PosRange,
866 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
867 )
868
869 case "holt_winters", "predict_linear":
870 // No change to labels.
871 s.Returns = promParser.ValueTypeVector
872 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
873 s.guaranteeLabel(
874 "Query will only return series where these labels are present.",
875 n.PosRange,
876 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
877 )
878 case "label_join":
879 // One label added to the results.
880 // label_join(v instant-vector, dst_label string, separator string, src_label_1 string, src_label_2 string, ...)
881 s.Returns = promParser.ValueTypeVector
882 s.guaranteeLabel(
883 fmt.Sprintf("This label will be added to the result by %s() call.", n.Func.Name),
884 n.PosRange,
885 n.Args[1].(*promParser.StringLiteral).Val,
886 )
887 for i := 3; i < len(n.Args); i++ {
888 s.UsedLabels = appendToSlice(s.UsedLabels, n.Args[i].(*promParser.StringLiteral).Val)
889 }
890 case "label_replace":
891 // One label added to the results.
892 // label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)
893 s.Returns = promParser.ValueTypeVector
894 s.guaranteeLabel(
895 fmt.Sprintf("This label will be added to the result by %s() call.", n.Func.Name),
896 n.PosRange,
897 n.Args[1].(*promParser.StringLiteral).Val,
898 )
899 s.UsedLabels = appendToSlice(s.UsedLabels, n.Args[3].(*promParser.StringLiteral).Val)
900
901 case "pi":
902 s.Returns = promParser.ValueTypeScalar
903 s.ReturnInfo.AlwaysReturns = true
904 s.ReturnInfo.KnownReturn = true
905 s.ReturnInfo.ReturnedNumber = math.Pi
906 s.ReturnInfo.ValuePosition = n.PosRange
907 s.excludeAllLabels(
908 expr,
909 fmt.Sprintf("Calling `%s()` will return a scalar value with no labels.", n.Func.Name),
910 n.PosRange,
911 n.PosRange,
912 nil,
913 )
914
915 case "scalar":
916 s.Returns = promParser.ValueTypeScalar
917 s.ReturnInfo.AlwaysReturns = true
918 funcPos := FindFuncPosition(expr, n.PositionRange(), n.Func.Name, nil)
919 s.excludeAllLabels(
920 expr,
921 fmt.Sprintf("Calling `%s()` will return a scalar value with no labels.", n.Func.Name),
922 funcPos,
923 funcPos,
924 nil,
925 )
926
927 case "sort", "sort_desc":
928 // No change to labels.
929 s.Returns = promParser.ValueTypeVector
930
931 case "time":
932 s.Returns = promParser.ValueTypeScalar
933 s.ReturnInfo.AlwaysReturns = true
934 s.ReturnInfo.ValuePosition = n.PosRange
935 s.excludeAllLabels(
936 expr,
937 fmt.Sprintf("Calling `%s()` will return a scalar value with no labels.", n.Func.Name),
938 n.PosRange,
939 n.PosRange,
940 nil,
941 )
942
943 case "timestamp":
944 // No change to labels.
945 s.Returns = promParser.ValueTypeVector
946 vs, _ := MostOuterOperation[*promParser.VectorSelector](s)
947 s.guaranteeLabel(
948 "Query will only return series where these labels are present.",
949 n.PosRange,
950 labelsFromSelectors(guaranteedLabelsMatches, vs)...,
951 )
952
953 case "vector":
954 s.Returns = promParser.ValueTypeVector
955 s.ReturnInfo.AlwaysReturns = true
956 s.ReturnInfo.ValuePosition = n.PosRange
957 for _, vs := range walkNode(expr, n.Args[0]) {
958 if vs.ReturnInfo.KnownReturn {
959 s.ReturnInfo.ReturnedNumber = vs.ReturnInfo.ReturnedNumber
960 s.ReturnInfo.KnownReturn = true
961 }
962 }
963 funcNamePos := FindFuncNamePosition(expr, n.PosRange, n.Func.Name)
964 s.excludeAllLabels(
965 expr,
966 fmt.Sprintf("Calling `%s()` will return a vector value with no labels.", n.Func.Name),
967 funcNamePos,
968 funcNamePos,
969 nil,
970 )
971
972 default:
973 // Unsupported function
974 return Source{} // nolint: exhaustruct
975 }
976 return s
977}
978
979func parseCall(expr string, n *promParser.Call) (src []Source) {
980 var args []string
981 var exprs []promParser.Expr
982
983 var vt promParser.ValueType
984 for i, e := range n.Args {
985 if i >= len(n.Func.ArgTypes) {
986 vt = n.Func.ArgTypes[len(n.Func.ArgTypes)-1]
987 } else {
988 vt = n.Func.ArgTypes[i]
989 }
990
991 switch vt {
992 case promParser.ValueTypeVector, promParser.ValueTypeMatrix:
993 exprs = append(exprs, e)
994 case promParser.ValueTypeNone, promParser.ValueTypeScalar, promParser.ValueTypeString:
995 args = append(args, e.String())
996 }
997 }
998
999 for _, e := range exprs {
1000 for _, es := range walkNode(expr, e) {
1001 es.Type = FuncSource
1002 es.Operations = append(es.Operations, Operation{
1003 Operation: n.Func.Name,
1004 Node: n,
1005 Arguments: args,
1006 })
1007 es.Position = e.PositionRange()
1008 src = append(src, parsePromQLFunc(es, expr, n))
1009 }
1010 }
1011
1012 if len(src) == 0 {
1013 s := Source{ // nolint: exhaustruct
1014 Labels: map[string]LabelTransform{},
1015 Type: FuncSource,
1016 Operations: Operations{
1017 {
1018 Operation: n.Func.Name,
1019 Node: n,
1020 Arguments: args,
1021 },
1022 },
1023 Position: n.PosRange,
1024 }
1025 src = append(src, parsePromQLFunc(s, expr, n))
1026 }
1027
1028 return src
1029}
1030
1031func parseBinOps(expr string, n *promParser.BinaryExpr) (src []Source) {
1032 pos := n.PositionRange()
1033 src = make([]Source, 0, 2)
1034 switch {
1035 // foo{} + 1
1036 // 1 + foo{}
1037 // foo{} > 1
1038 // 1 > foo{}
1039 case n.VectorMatching == nil:
1040 lhs := walkNode(expr, n.LHS)
1041 rhs := walkNode(expr, n.RHS)
1042 for _, ls := range lhs {
1043 ls.IsConditional, ls.ReturnInfo.IsReturnBool = checkConditions(ls, n.Op, n.ReturnBool)
1044 for _, rs := range rhs {
1045 rs.IsConditional, rs.ReturnInfo.IsReturnBool = checkConditions(rs, n.Op, n.ReturnBool)
1046 var side Source
1047 switch {
1048 case ls.Returns == promParser.ValueTypeVector, ls.Returns == promParser.ValueTypeMatrix:
1049 // Use labels from LHS
1050 side = ls
1051 case rs.Returns == promParser.ValueTypeVector, rs.Returns == promParser.ValueTypeMatrix:
1052 // Use labels from RHS
1053 side = rs
1054 default:
1055 side = ls
1056 }
1057 if ls.ReturnInfo.AlwaysReturns && rs.ReturnInfo.AlwaysReturns && ls.ReturnInfo.KnownReturn && rs.ReturnInfo.KnownReturn {
1058 // Both sides always return something
1059 side.ReturnInfo, side.DeadInfo = calculateStaticReturn(expr, ls, rs, n)
1060 }
1061 src = append(src, side)
1062 }
1063 }
1064
1065 // foo{} + bar{}
1066 // foo{} + on(...) bar{}
1067 // foo{} * ignoring(...) bar{}
1068 // foo{} / bar{}
1069 case n.VectorMatching.Card == promParser.CardOneToOne:
1070 rhs := walkNode(expr, n.RHS)
1071 for _, ls := range walkNode(expr, n.LHS) {
1072 if n.VectorMatching.On {
1073 ls.UsedLabels = appendToSlice(ls.UsedLabels, n.VectorMatching.MatchingLabels...)
1074 ls.checkIncludedLabels(expr, pos, n.VectorMatching.MatchingLabels)
1075 funcPos := FindFuncPosition(expr, pos, promParser.ItemTypeStr[promParser.ON], []posrange.PositionRange{
1076 n.LHS.PositionRange(), n.RHS.PositionRange(),
1077 })
1078 ls.excludeAllLabels(
1079 expr,
1080 fmt.Sprintf(
1081 "Query is using %s vector matching with `%s(%s)`, only labels included inside `%s(...)` will be present on the results.",
1082 n.VectorMatching.Card, promParser.ItemTypeStr[promParser.ON], strings.Join(n.VectorMatching.MatchingLabels, ", "), promParser.ItemTypeStr[promParser.ON],
1083 ),
1084 funcPos,
1085 funcPos,
1086 n.VectorMatching.MatchingLabels,
1087 )
1088 } else {
1089 ls.useLabelsNotExcluded(n.VectorMatching.MatchingLabels)
1090 for _, name := range n.VectorMatching.MatchingLabels {
1091 ls.excludeLabel(
1092 fmt.Sprintf(
1093 "Query is using %s vector matching with `%s(%s)`, all labels included inside `%s(...)` will be removed on the results.",
1094 n.VectorMatching.Card, promParser.ItemTypeStr[promParser.IGNORING], strings.Join(n.VectorMatching.MatchingLabels, ", "), promParser.ItemTypeStr[promParser.IGNORING],
1095 ),
1096 FindArgumentPosition(
1097 expr,
1098 FindFuncPosition(expr, pos, promParser.ItemTypeStr[promParser.IGNORING], []posrange.PositionRange{
1099 n.LHS.PositionRange(), n.RHS.PositionRange(),
1100 }),
1101 name,
1102 ),
1103 name,
1104 )
1105 }
1106 for _, rs := range rhs {
1107 rs.IsConditional, rs.ReturnInfo.IsReturnBool = checkConditions(rs, n.Op, n.ReturnBool)
1108 if ls.ReturnInfo.AlwaysReturns && rs.ReturnInfo.AlwaysReturns && ls.ReturnInfo.KnownReturn && rs.ReturnInfo.KnownReturn {
1109 // Both sides always return something
1110 ls.ReturnInfo, ls.DeadInfo = calculateStaticReturn(expr, ls, rs, n)
1111 }
1112 }
1113 }
1114 for _, rs := range rhs {
1115 rs.DeadLabels = append(rs.DeadLabels, rs.checkJoinedLabels(expr, n, rs)...)
1116 if ok, s, p := canJoin(ls, rs, n.VectorMatching); !ok {
1117 rs.DeadInfo = &DeadInfo{
1118 Reason: s,
1119 Fragment: p,
1120 }
1121 }
1122 ls.Joins = append(ls.Joins, Join{
1123 Src: rs,
1124 Op: n.Op,
1125 Depth: 0,
1126 MatchingLabels: n.VectorMatching.MatchingLabels,
1127 AddedLabels: nil,
1128 IsOn: n.VectorMatching.On,
1129 })
1130 }
1131 ls.DeadLabels = append(ls.DeadLabels, ls.checkJoinedLabels(expr, n, ls)...)
1132 ls.excludeLabel("Binary operation between two vectors removes metric names.", pos, model.MetricNameLabel)
1133 ls.IsConditional, ls.ReturnInfo.IsReturnBool = checkConditions(ls, n.Op, n.ReturnBool)
1134 src = append(src, ls)
1135 }
1136
1137 // foo{} + on(...) group_right(...) bar{}
1138 // foo{} + ignoring(...) group_right(...) bar{}
1139 case n.VectorMatching.Card == promParser.CardOneToMany:
1140 lhs := walkNode(expr, n.LHS)
1141 for _, rs := range walkNode(expr, n.RHS) {
1142 rs.joinLabels(expr, pos, promParser.GROUP_RIGHT, n.VectorMatching.Include, []posrange.PositionRange{
1143 n.LHS.PositionRange(), n.RHS.PositionRange(),
1144 })
1145 // If we have:
1146 // foo * on(instance) group_left(a,b) bar{x="y"}
1147 // then only group_left() labels will be included.
1148 if n.VectorMatching.On {
1149 rs.UsedLabels = appendToSlice(rs.UsedLabels, n.VectorMatching.MatchingLabels...)
1150 rs.checkIncludedLabels(expr, pos, n.VectorMatching.MatchingLabels)
1151 for _, name := range n.VectorMatching.MatchingLabels {
1152 rs.includeLabel(
1153 expr,
1154 fmt.Sprintf(
1155 "Query is using %s vector matching with `on(%s)`, labels included inside `on(...)` will be present on the results.",
1156 n.VectorMatching.Card, strings.Join(n.VectorMatching.MatchingLabels, ", "),
1157 ),
1158 FindArgumentPosition(
1159 expr,
1160 FindFuncPosition(expr, pos, promParser.ItemTypeStr[promParser.ON], []posrange.PositionRange{
1161 n.LHS.PositionRange(), n.RHS.PositionRange(),
1162 }),
1163 name,
1164 ),
1165 name,
1166 )
1167 }
1168 } else {
1169 rs.useLabelsNotExcluded(n.VectorMatching.MatchingLabels)
1170 }
1171 for _, ls := range lhs {
1172 ls.checkIncludedLabels(
1173 expr,
1174 FindFuncPosition(expr, pos, promParser.ItemTypeStr[promParser.GROUP_RIGHT], []posrange.PositionRange{
1175 n.LHS.PositionRange(),
1176 n.RHS.PositionRange(),
1177 }),
1178 n.VectorMatching.Include,
1179 )
1180 rs.DeadLabels = append(rs.DeadLabels, ls.checkJoinedLabels(expr, n, rs)...)
1181 if ok, s, p := canJoin(rs, ls, n.VectorMatching); !ok {
1182 ls.DeadInfo = &DeadInfo{
1183 Reason: s,
1184 Fragment: p,
1185 }
1186 }
1187 rs.Joins = append(rs.Joins, Join{
1188 Src: ls,
1189 Op: n.Op,
1190 Depth: 0,
1191 MatchingLabels: n.VectorMatching.MatchingLabels,
1192 AddedLabels: n.VectorMatching.Include,
1193 IsOn: n.VectorMatching.On,
1194 })
1195 }
1196 rs.excludeLabel("Binary operation between two vectors removes metric names.", pos, model.MetricNameLabel)
1197 rs.IsConditional, rs.ReturnInfo.IsReturnBool = checkConditions(rs, n.Op, n.ReturnBool)
1198 src = append(src, rs)
1199 }
1200
1201 // foo{} + on(...) group_left(...) bar{}
1202 // foo{} + ignoring(...) group_left(...) bar{}
1203 case n.VectorMatching.Card == promParser.CardManyToOne:
1204 rhs := walkNode(expr, n.RHS)
1205 for _, ls := range walkNode(expr, n.LHS) {
1206 ls.joinLabels(expr, pos, promParser.GROUP_LEFT, n.VectorMatching.Include, []posrange.PositionRange{
1207 n.LHS.PositionRange(), n.RHS.PositionRange(),
1208 })
1209 if n.VectorMatching.On {
1210 ls.UsedLabels = appendToSlice(ls.UsedLabels, n.VectorMatching.MatchingLabels...)
1211 ls.checkIncludedLabels(expr, pos, n.VectorMatching.MatchingLabels)
1212 for _, name := range n.VectorMatching.MatchingLabels {
1213 ls.includeLabel(
1214 expr,
1215 fmt.Sprintf(
1216 "Query is using %s vector matching with `on(%s)`, labels included inside `on(...)` will be present on the results.",
1217 n.VectorMatching.Card, strings.Join(n.VectorMatching.MatchingLabels, ", "),
1218 ),
1219 FindArgumentPosition(
1220 expr,
1221 FindFuncPosition(expr, pos, promParser.ItemTypeStr[promParser.ON], []posrange.PositionRange{
1222 n.LHS.PositionRange(), n.RHS.PositionRange(),
1223 }),
1224 name,
1225 ),
1226 name,
1227 )
1228 }
1229 } else {
1230 ls.useLabelsNotExcluded(n.VectorMatching.MatchingLabels)
1231 }
1232 for _, rs := range rhs {
1233 rs.checkIncludedLabels(
1234 expr,
1235 FindFuncPosition(expr, pos, promParser.ItemTypeStr[promParser.GROUP_LEFT], []posrange.PositionRange{
1236 n.LHS.PositionRange(),
1237 n.RHS.PositionRange(),
1238 }),
1239 n.VectorMatching.Include,
1240 )
1241 ls.DeadLabels = append(ls.DeadLabels, rs.checkJoinedLabels(expr, n, ls)...)
1242 if ok, s, p := canJoin(ls, rs, n.VectorMatching); !ok {
1243 rs.DeadInfo = &DeadInfo{
1244 Reason: s,
1245 Fragment: p,
1246 }
1247 }
1248 ls.Joins = append(ls.Joins, Join{
1249 Src: rs,
1250 Op: n.Op,
1251 Depth: 0,
1252 MatchingLabels: n.VectorMatching.MatchingLabels,
1253 AddedLabels: n.VectorMatching.Include,
1254 IsOn: n.VectorMatching.On,
1255 })
1256 }
1257 ls.excludeLabel("Binary operation between two vectors removes metric names.", pos, model.MetricNameLabel)
1258 ls.IsConditional, ls.ReturnInfo.IsReturnBool = checkConditions(ls, n.Op, n.ReturnBool)
1259 src = append(src, ls)
1260 }
1261
1262 // foo{} and on(...) bar{}
1263 // foo{} and ignoring(...) bar{}
1264 // foo{} and bar{}
1265 // foo{} unless bar{}
1266 // foo{} or bar{}
1267 // foo{} or on(...) bar{}
1268 // foo{} or ignoring(...) bar{}
1269 //
1270 // 'foo or bar' means:
1271 // - take all foo{} results
1272 // - take all bar{} results
1273 // - remove any bar{} result if there's a foo{} result with same labels (except __name__)
1274 //
1275 // 'foo or on(a) bar' means we only compare 'a' label and remove any bar{} results with 'a' value
1276 // already provided by foo{}
1277 //
1278 // 'foo or ignoring(a) bar' means we ignore the 'a' label when comparing foo{} and bar{} results,
1279 // so (for example) bar{} results with 'a' labels present where foo{} doesn't have any 'a' label
1280 // will still be excluded if all other labels match.
1281 case n.VectorMatching.Card == promParser.CardManyToMany:
1282 var lhsCanBeEmpty bool // true if any of the LHS query can produce empty results.
1283 rhs := walkNode(expr, n.RHS)
1284 for _, ls := range walkNode(expr, n.LHS) {
1285 var rhsConditional bool
1286 // With many-to-many on/ignore is only used for matching series, it doesn't impact
1287 // returned labels.
1288 if n.VectorMatching.On {
1289 ls.UsedLabels = appendToSlice(ls.UsedLabels, n.VectorMatching.MatchingLabels...)
1290 ls.checkIncludedLabels(expr, pos, n.VectorMatching.MatchingLabels)
1291 for _, name := range n.VectorMatching.MatchingLabels {
1292 ls.includeLabel(
1293 expr,
1294 fmt.Sprintf(
1295 "Query is using %s vector matching with `on(%s)`, labels included inside `on(...)` will be present on the results if matched time series have them.",
1296 n.VectorMatching.Card, strings.Join(n.VectorMatching.MatchingLabels, ", "),
1297 ),
1298 FindArgumentPosition(
1299 expr,
1300 FindFuncPosition(expr, pos, promParser.ItemTypeStr[promParser.ON], []posrange.PositionRange{
1301 n.LHS.PositionRange(), n.RHS.PositionRange(),
1302 }),
1303 name,
1304 ),
1305 name,
1306 )
1307 }
1308 } else {
1309 ls.useLabelsNotExcluded(n.VectorMatching.MatchingLabels)
1310 }
1311 if !ls.ReturnInfo.AlwaysReturns || ls.IsConditional {
1312 lhsCanBeEmpty = true
1313 }
1314 for _, rs := range rhs {
1315 isConditional, _ := checkConditions(rs, n.Op, n.ReturnBool)
1316 if isConditional {
1317 rhsConditional = true
1318 }
1319 if ok, s, p := canJoin(ls, rs, n.VectorMatching); !ok {
1320 rs.DeadInfo = &DeadInfo{
1321 Reason: s,
1322 Fragment: p,
1323 }
1324 }
1325 switch {
1326 case n.Op == promParser.LUNLESS:
1327 if n.VectorMatching.On && len(n.VectorMatching.MatchingLabels) == 0 && rs.ReturnInfo.AlwaysReturns && !rs.IsConditional {
1328 ls.DeadInfo = &DeadInfo{
1329 Reason: "This query will never return anything because the `unless` query always returns something.",
1330 Fragment: rs.Position,
1331 }
1332 }
1333 ls.Unless = append(ls.Unless, Unless{
1334 Src: rs,
1335 MatchingLabels: n.VectorMatching.MatchingLabels,
1336 IsOn: n.VectorMatching.On,
1337 })
1338 case n.Op != promParser.LOR:
1339 ls.Joins = append(ls.Joins, Join{
1340 Src: rs,
1341 Op: n.Op,
1342 Depth: 0,
1343 MatchingLabels: n.VectorMatching.MatchingLabels,
1344 AddedLabels: nil,
1345 IsOn: n.VectorMatching.On,
1346 })
1347 }
1348 ls.DeadLabels = append(ls.DeadLabels, rs.checkJoinedLabels(expr, n, ls)...)
1349 }
1350 if n.Op == promParser.LAND && rhsConditional {
1351 ls.IsConditional = true
1352 }
1353 src = append(src, ls)
1354 }
1355 if n.Op == promParser.LOR {
1356 for _, rs := range rhs {
1357 // If LHS can NOT be empty then RHS is dead code.
1358 if !lhsCanBeEmpty {
1359 rs.DeadInfo = &DeadInfo{
1360 Reason: "The left hand side always returns something and so the right hand side is never used.",
1361 Fragment: rs.Position,
1362 }
1363 }
1364 src = append(src, rs)
1365 }
1366 }
1367 }
1368 return src
1369}
1370
1371func checkConditions(s Source, op promParser.ItemType, isBool bool) (isConditional, isReturnBool bool) {
1372 if !s.IsConditional && isBool {
1373 isReturnBool = isBool
1374 }
1375 if s.IsConditional {
1376 isConditional = s.IsConditional
1377 } else {
1378 isConditional = op.IsComparisonOperator()
1379 }
1380 return isConditional, isReturnBool
1381}
1382
1383func canJoin(ls, rs Source, vm *promParser.VectorMatching) (bool, string, posrange.PositionRange) {
1384 var side string
1385 if vm.Card == promParser.CardOneToMany {
1386 side = "left"
1387 } else {
1388 side = "right"
1389 }
1390
1391 switch {
1392 case vm.On && len(vm.MatchingLabels) == 0: // ls on() unless rs
1393 return true, "", posrange.PositionRange{}
1394 case vm.On: // ls on(...) unless rs
1395 for _, name := range vm.MatchingLabels {
1396 if ls.CanHaveLabel(name) && !rs.CanHaveLabel(name) {
1397 reason, fragment := rs.LabelExcludeReason(name)
1398 return false, fmt.Sprintf("The %s hand side will never be matched because it doesn't have the `%s` label from `on(...)`. %s",
1399 side, name, reason), fragment
1400 }
1401 }
1402 default: // ls unless rs
1403 for name, l := range ls.Labels {
1404 if l.Kind != GuaranteedLabel {
1405 continue
1406 }
1407 if slices.Contains(vm.MatchingLabels, name) {
1408 continue
1409 }
1410 if ls.CanHaveLabel(name) && !rs.CanHaveLabel(name) {
1411 reason, fragment := rs.LabelExcludeReason(name)
1412 return false, fmt.Sprintf("The %s hand side will never be matched because it doesn't have the `%s` label while the left hand side will. %s",
1413 side, name, reason), fragment
1414 }
1415 }
1416 }
1417 return true, "", posrange.PositionRange{}
1418}
1419
1420func describeDeadCode(expr string, ls, rs Source, op *promParser.BinaryExpr, match string) *DeadInfo {
1421 var lse, rse string
1422 if ls.ReturnInfo.LogicalExpr != "" {
1423 lse = ls.ReturnInfo.LogicalExpr
1424 } else {
1425 lse = GetQueryFragment(expr, ls.ReturnInfo.ValuePosition)
1426 }
1427 if rs.ReturnInfo.LogicalExpr != "" {
1428 rse = rs.ReturnInfo.LogicalExpr
1429 } else {
1430 rse = GetQueryFragment(expr, rs.ReturnInfo.ValuePosition)
1431 }
1432
1433 cmpPrefix := fmt.Sprintf("`%s %s %s` always evaluates to", lse, op.Op, rse)
1434
1435 var cmpSuffix string
1436 if op.ReturnBool {
1437 cmpSuffix = "and uses the `bool` modifier which means it will always return 0"
1438 } else {
1439 cmpSuffix = "which is not possible, so it will never return anything."
1440 }
1441 return &DeadInfo{
1442 Reason: fmt.Sprintf(
1443 "%s `%s %s %s` %s",
1444 cmpPrefix,
1445 strconv.FormatFloat(ls.ReturnInfo.ReturnedNumber, 'f', -1, 64),
1446 match,
1447 strconv.FormatFloat(rs.ReturnInfo.ReturnedNumber, 'f', -1, 64),
1448 cmpSuffix,
1449 ),
1450 Fragment: ls.Position,
1451 }
1452}
1453
1454func calculateStaticReturn(expr string, ls, rs Source, op *promParser.BinaryExpr) (ret ReturnInfo, deadinfo *DeadInfo) {
1455 ret = ls.ReturnInfo
1456 switch op.Op {
1457 case promParser.EQLC:
1458 if ls.ReturnInfo.ReturnedNumber != rs.ReturnInfo.ReturnedNumber {
1459 deadinfo = describeDeadCode(expr, ls, rs, op, "==")
1460 }
1461 case promParser.NEQ:
1462 if ls.ReturnInfo.ReturnedNumber == rs.ReturnInfo.ReturnedNumber {
1463 deadinfo = describeDeadCode(expr, ls, rs, op, "!=")
1464 }
1465 case promParser.LTE:
1466 if ls.ReturnInfo.ReturnedNumber > rs.ReturnInfo.ReturnedNumber {
1467 deadinfo = describeDeadCode(expr, ls, rs, op, "<=")
1468 }
1469 case promParser.LSS:
1470 if ls.ReturnInfo.ReturnedNumber >= rs.ReturnInfo.ReturnedNumber {
1471 deadinfo = describeDeadCode(expr, ls, rs, op, "<")
1472 }
1473 case promParser.GTE:
1474 if ls.ReturnInfo.ReturnedNumber < rs.ReturnInfo.ReturnedNumber {
1475 deadinfo = describeDeadCode(expr, ls, rs, op, ">=")
1476 }
1477 case promParser.GTR:
1478 if ls.ReturnInfo.ReturnedNumber <= rs.ReturnInfo.ReturnedNumber {
1479 deadinfo = describeDeadCode(expr, ls, rs, op, ">")
1480 }
1481 case promParser.ADD:
1482 ret.ReturnedNumber = ls.ReturnInfo.ReturnedNumber + rs.ReturnInfo.ReturnedNumber
1483 ret.LogicalExpr = formatDesc(expr, ls, rs, "+")
1484 case promParser.SUB:
1485 ret.ReturnedNumber = ls.ReturnInfo.ReturnedNumber - rs.ReturnInfo.ReturnedNumber
1486 ret.LogicalExpr = formatDesc(expr, ls, rs, "-")
1487 case promParser.MUL:
1488 ret.ReturnedNumber = ls.ReturnInfo.ReturnedNumber * rs.ReturnInfo.ReturnedNumber
1489 ret.LogicalExpr = formatDesc(expr, ls, rs, "*")
1490 case promParser.DIV:
1491 ret.ReturnedNumber = ls.ReturnInfo.ReturnedNumber / rs.ReturnInfo.ReturnedNumber
1492 ret.LogicalExpr = formatDesc(expr, ls, rs, "/")
1493 case promParser.MOD:
1494 ret.ReturnedNumber = math.Mod(ls.ReturnInfo.ReturnedNumber, rs.ReturnInfo.ReturnedNumber)
1495 ret.LogicalExpr = formatDesc(expr, ls, rs, "%")
1496 case promParser.POW:
1497 ret.ReturnedNumber = math.Pow(ls.ReturnInfo.ReturnedNumber, rs.ReturnInfo.ReturnedNumber)
1498 ret.LogicalExpr = formatDesc(expr, ls, rs, "^")
1499 }
1500 return ret, deadinfo
1501}
1502
1503func formatDesc(expr string, ls, rs Source, op string) string {
1504 var lse, rse string
1505 if ls.ReturnInfo.LogicalExpr != "" {
1506 lse = ls.ReturnInfo.LogicalExpr
1507 } else {
1508 lse = GetQueryFragment(expr, ls.ReturnInfo.ValuePosition)
1509 }
1510 if rs.ReturnInfo.LogicalExpr != "" {
1511 rse = rs.ReturnInfo.LogicalExpr
1512 } else {
1513 rse = GetQueryFragment(expr, rs.ReturnInfo.ValuePosition)
1514 }
1515 return lse + " " + op + " " + rse
1516}
1517
1518func isOutside(pos posrange.PositionRange, outside []posrange.PositionRange) bool {
1519 for _, out := range outside {
1520 if pos.Start >= out.Start && pos.End <= out.End {
1521 return false
1522 }
1523 }
1524 return true
1525}
1526
1527func FindFuncNamePosition(expr string, within posrange.PositionRange, fn string) posrange.PositionRange {
1528 re := regexp.MustCompile("(?si)(" + regexp.QuoteMeta(fn) + ")(?:[ \n\t]*?)\\(")
1529 idx := re.FindStringSubmatchIndex(GetQueryFragment(expr, within))
1530 if idx == nil {
1531 return within
1532 }
1533 return posrange.PositionRange{
1534 Start: within.Start + posrange.Pos(idx[0]),
1535 End: within.Start + posrange.Pos(idx[1]-1),
1536 }
1537}
1538
1539func FindFuncPosition(expr string, within posrange.PositionRange, fn string, outside []posrange.PositionRange) posrange.PositionRange {
1540 re := regexp.MustCompile("(?si)(" + regexp.QuoteMeta(fn) + ")(?:[ \n\t]*?)\\((?:.*?)\\)")
1541 idx := re.FindStringSubmatchIndex(GetQueryFragment(expr, within))
1542 if idx == nil {
1543 return within
1544 }
1545 var pos posrange.PositionRange
1546 for chk := range slices.Chunk(idx, 2) {
1547 pos = posrange.PositionRange{
1548 Start: within.Start + posrange.Pos(chk[0]),
1549 End: within.Start + posrange.Pos(chk[1]),
1550 }
1551 if isOutside(pos, outside) {
1552 return pos
1553 }
1554 }
1555 return within
1556}
1557
1558func FindArgumentPosition(expr string, within posrange.PositionRange, name string) posrange.PositionRange {
1559 re := regexp.MustCompile("(?s)\\((?:(.*,?))(?:[ \n\t]*?)(" + regexp.QuoteMeta(name) + ")(?:[ \n\t]*?)(?:(,.*)?)\\)")
1560 idx := re.FindStringSubmatchIndex(GetQueryFragment(expr, within))
1561 if idx == nil {
1562 return within
1563 }
1564 return posrange.PositionRange{
1565 Start: within.Start + posrange.Pos(idx[4]),
1566 End: within.Start + posrange.Pos(idx[5]),
1567 }
1568}
1569
1570func findBinOpsOperatorPosition(expr string, n *promParser.BinaryExpr, op string) posrange.PositionRange {
1571 within := posrange.PositionRange{
1572 Start: n.LHS.PositionRange().End + 1,
1573 End: n.RHS.PositionRange().Start,
1574 }
1575 idx := strings.Index(GetQueryFragment(expr, within), op)
1576 if idx < 0 {
1577 return within
1578 }
1579 return posrange.PositionRange{
1580 Start: within.Start + posrange.Pos(idx),
1581 End: within.Start + posrange.Pos(idx+len(op)),
1582 }
1583}
1584