cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.81.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/promql.go

164lines · modecode

1package parser
2
3import (
4 "errors"
5 "slices"
6 "sync"
7
8 promParser "github.com/prometheus/prometheus/promql/parser"
9
10 "github.com/cloudflare/pint/internal/parser/source"
11)
12
13// PromQLParser is a shared, goroutine-safe PromQL parser with all feature flags enabled.
14// Each method creates its own internal parser state, so it is safe to call concurrently.
15var PromQLParser = promParser.NewParser(promParser.Options{
16 EnableExperimentalFunctions: true,
17 ExperimentalDurationExpr: true,
18 EnableExtendedRangeSelectors: true,
19 EnableBinopFillModifiers: true,
20})
21
22type PromQLExpr struct {
23 syntaxError error
24 Value *YamlNode
25 query *PromQLNode
26 mu *sync.Mutex
27 source []source.Source
28 hasSource bool
29}
30
31func (pn *PromQLExpr) parse() {
32 if pn.query == nil {
33 pn.query, pn.syntaxError = DecodeExpr(pn.Value.Value)
34 }
35}
36
37func (pn *PromQLExpr) Source() []source.Source {
38 pn.mu.Lock()
39 defer pn.mu.Unlock()
40 pn.parse()
41 if !pn.hasSource {
42 pn.source = source.LabelsSource(pn.Value.Value, pn.query.Expr)
43 pn.hasSource = true
44 }
45 return pn.source
46}
47
48func (pn *PromQLExpr) Query() *PromQLNode {
49 pn.mu.Lock()
50 defer pn.mu.Unlock()
51 pn.parse()
52 return pn.query
53}
54
55func (pn *PromQLExpr) SyntaxError() error {
56 pn.mu.Lock()
57 defer pn.mu.Unlock()
58 pn.parse()
59 return pn.syntaxError
60}
61
62// PromQLNode is used to turn the parsed PromQL query expression into a tree.
63// This allows us to walk the tree up & down and look for either parents
64// or children of specific type. Which is useful if you, for example,
65// want to check if all vector selectors are wrapped inside function
66// calls etc.
67type PromQLNode struct {
68 Parent *PromQLNode
69 Expr promParser.Node
70 Children []*PromQLNode
71}
72
73// Tree takes a parsed PromQL node and turns it into a Node
74// instance with parent and children populated.
75func tree(expr promParser.Node, parent *PromQLNode) *PromQLNode {
76 n := PromQLNode{
77 Parent: parent,
78 Expr: expr,
79 Children: nil,
80 }
81 for _, child := range promParser.Children(expr) {
82 n.Children = append(n.Children, tree(child, &n))
83 }
84
85 return &n
86}
87
88// WalkUpExpr allows to iterate a promQLNode node looking for
89// parents of specific type.
90// Prometheus parser returns interfaces which makes it more difficult
91// to figure out what kind of node we're dealing with, hence this
92// helper takes a type parameter it tries to cast.
93// It starts by checking the node passed to it and then walks
94// up by visiting all parent nodes.
95func WalkUpExpr[T promParser.Node](node *PromQLNode) (nodes []*PromQLNode) {
96 if node == nil {
97 return nodes
98 }
99 if _, ok := node.Expr.(T); ok {
100 nodes = append(nodes, node)
101 }
102 if node.Parent != nil {
103 nodes = append(nodes, WalkUpExpr[T](node.Parent)...)
104 }
105 return nodes
106}
107
108// WalkDownExpr works just like WalkUpExpr but it walks the tree
109// down, visiting all children.
110// It also starts by checking the node passed to it before walking
111// down the tree.
112func WalkDownExpr[T promParser.Node](node *PromQLNode) (nodes []*PromQLNode) {
113 if _, ok := node.Expr.(T); ok {
114 nodes = append(nodes, node)
115 }
116 for _, child := range node.Children {
117 nodes = append(nodes, WalkDownExpr[T](child)...)
118 }
119 return nodes
120}
121
122// WalkUpParent works like WalkUpExpr but checks the parent
123// (if present) instead of the node itself.
124// It returns the nodes where the parent is of given type.
125func WalkUpParent[T promParser.Node](node *PromQLNode) (nodes []*PromQLNode) {
126 if node == nil || node.Parent == nil {
127 return nodes
128 }
129 if _, ok := node.Parent.Expr.(T); ok {
130 nodes = append(nodes, node)
131 }
132 if node.Parent != nil {
133 nodes = append(nodes, WalkUpParent[T](node.Parent)...)
134 }
135 return nodes
136}
137
138func DecodeExpr(expr string) (*PromQLNode, error) {
139 node, err := PromQLParser.ParseExpr(expr)
140 if err != nil {
141 if errorList, ok := errors.AsType[promParser.ParseErrors](err); ok {
142 // Find the error pointing at the shortest query fragment.
143 slices.SortFunc(errorList, func(a, b promParser.ParseErr) int {
144 ar := a.PositionRange.End - a.PositionRange.Start
145 br := b.PositionRange.End - b.PositionRange.Start
146 switch {
147 case ar < br:
148 return -1
149 case ar > br:
150 return 1
151 default:
152 return 0
153 }
154 })
155 for _, el := range errorList {
156 if el.PositionRange.Start > 0 && el.PositionRange.End > 0 {
157 return nil, promParser.ParseErrors{el}
158 }
159 }
160 }
161 return nil, err
162 }
163 return tree(node, nil), nil
164}
165