cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.80.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/source/source.go

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