github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/parser/promql/matchers.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package promql 22 23 import ( 24 "fmt" 25 26 "github.com/m3db/m3/src/query/block" 27 "github.com/m3db/m3/src/query/functions" 28 "github.com/m3db/m3/src/query/functions/aggregation" 29 "github.com/m3db/m3/src/query/functions/binary" 30 "github.com/m3db/m3/src/query/functions/linear" 31 "github.com/m3db/m3/src/query/functions/scalar" 32 "github.com/m3db/m3/src/query/functions/tag" 33 "github.com/m3db/m3/src/query/functions/temporal" 34 "github.com/m3db/m3/src/query/functions/unconsolidated" 35 "github.com/m3db/m3/src/query/models" 36 "github.com/m3db/m3/src/query/parser" 37 "github.com/m3db/m3/src/query/parser/common" 38 39 "github.com/prometheus/common/model" 40 "github.com/prometheus/prometheus/model/labels" 41 promql "github.com/prometheus/prometheus/promql/parser" 42 ) 43 44 // NewSelectorFromVector creates a new fetchop. 45 func NewSelectorFromVector( 46 n *promql.VectorSelector, 47 tagOpts models.TagOptions, 48 ) (parser.Params, error) { 49 matchers, err := LabelMatchersToModelMatcher(n.LabelMatchers, tagOpts) 50 if err != nil { 51 return nil, err 52 } 53 54 return functions.FetchOp{ 55 Name: n.Name, 56 Offset: n.Offset, 57 Matchers: matchers, 58 }, nil 59 } 60 61 // NewSelectorFromMatrix creates a new fetchop. 62 func NewSelectorFromMatrix( 63 n *promql.MatrixSelector, 64 tagOpts models.TagOptions, 65 ) (parser.Params, error) { 66 vectorSelector := n.VectorSelector.(*promql.VectorSelector) 67 matchers, err := LabelMatchersToModelMatcher(vectorSelector.LabelMatchers, tagOpts) 68 if err != nil { 69 return nil, err 70 } 71 72 return functions.FetchOp{ 73 Name: vectorSelector.Name, 74 Offset: vectorSelector.Offset, 75 Matchers: matchers, 76 Range: n.Range, 77 }, nil 78 } 79 80 // NewAggregationOperator creates a new aggregation operator based on the type. 81 func NewAggregationOperator(expr *promql.AggregateExpr) (parser.Params, error) { 82 opType := expr.Op 83 byteMatchers := make([][]byte, len(expr.Grouping)) 84 for i, grouping := range expr.Grouping { 85 byteMatchers[i] = []byte(grouping) 86 } 87 88 nodeInformation := aggregation.NodeParams{ 89 MatchingTags: byteMatchers, 90 Without: expr.Without, 91 } 92 93 op := getAggOpType(opType) 94 switch op { 95 case common.UnknownOpType: 96 return nil, fmt.Errorf("operator not supported: %s", opType) 97 98 case aggregation.BottomKType, aggregation.TopKType: 99 val, err := resolveScalarArgument(expr.Param) 100 if err != nil { 101 return nil, err 102 } 103 104 nodeInformation.Parameter = val 105 return aggregation.NewTakeOp(op, nodeInformation) 106 107 case aggregation.CountValuesType: 108 paren := unwrapParenExpr(expr.Param) 109 val, err := resolveStringArgument(paren) 110 if err != nil { 111 return nil, err 112 } 113 nodeInformation.StringParameter = val 114 return aggregation.NewCountValuesOp(op, nodeInformation) 115 116 case aggregation.QuantileType: 117 val, err := resolveScalarArgument(expr.Param) 118 if err != nil { 119 return nil, err 120 } 121 122 nodeInformation.Parameter = val 123 } 124 return aggregation.NewAggregationOp(op, nodeInformation) 125 } 126 127 func unwrapParenExpr(expr promql.Expr) promql.Expr { 128 for { 129 if paren, ok := expr.(*promql.ParenExpr); ok { 130 expr = paren.Expr 131 } else { 132 return expr 133 } 134 } 135 } 136 137 func resolveStringArgument(expr promql.Expr) (string, error) { 138 if expr == nil { 139 return "", fmt.Errorf("expression is nil") 140 } 141 142 if str, ok := expr.(*promql.StringLiteral); ok { 143 return str.Val, nil 144 } 145 146 return expr.String(), nil 147 } 148 149 func getAggOpType(opType promql.ItemType) string { 150 switch opType { 151 case promql.SUM: 152 return aggregation.SumType 153 case promql.MIN: 154 return aggregation.MinType 155 case promql.MAX: 156 return aggregation.MaxType 157 case promql.AVG: 158 return aggregation.AverageType 159 case promql.STDDEV: 160 return aggregation.StandardDeviationType 161 case promql.STDVAR: 162 return aggregation.StandardVarianceType 163 case promql.COUNT: 164 return aggregation.CountType 165 166 case promql.TOPK: 167 return aggregation.TopKType 168 case promql.BOTTOMK: 169 return aggregation.BottomKType 170 case promql.QUANTILE: 171 return aggregation.QuantileType 172 case promql.COUNT_VALUES: 173 return aggregation.CountValuesType 174 175 default: 176 return common.UnknownOpType 177 } 178 } 179 180 func newScalarOperator( 181 expr *promql.NumberLiteral, 182 tagOpts models.TagOptions, 183 ) (parser.Params, error) { 184 return scalar.NewScalarOp(expr.Val, tagOpts) 185 } 186 187 // NewBinaryOperator creates a new binary operator based on the type. 188 func NewBinaryOperator(expr *promql.BinaryExpr, 189 lhs, rhs parser.NodeID) (parser.Params, error) { 190 matcherBuilder := promMatchingToM3(expr.VectorMatching) 191 nodeParams := binary.NodeParams{ 192 LNode: lhs, 193 RNode: rhs, 194 ReturnBool: expr.ReturnBool, 195 VectorMatcherBuilder: matcherBuilder, 196 } 197 198 op := getBinaryOpType(expr.Op) 199 return binary.NewOp(op, nodeParams) 200 } 201 202 var dateFuncs = []string{linear.DayOfMonthType, linear.DayOfWeekType, 203 linear.DaysInMonthType, linear.HourType, linear.MinuteType, 204 linear.MonthType, linear.YearType} 205 206 func isDateFunc(name string) bool { 207 for _, n := range dateFuncs { 208 if name == n { 209 return true 210 } 211 } 212 213 return false 214 } 215 216 // NewFunctionExpr creates a new function expr based on the type. 217 func NewFunctionExpr( 218 name string, 219 argValues []interface{}, 220 stringValues []string, 221 hasArgValue bool, 222 inner string, 223 tagOptions models.TagOptions, 224 ) (parser.Params, bool, error) { 225 var ( 226 p parser.Params 227 err error 228 ) 229 230 if isDateFunc(name) { 231 p, err = linear.NewDateOp(name, hasArgValue) 232 return p, true, err 233 } 234 235 switch name { 236 case linear.AbsType, linear.CeilType, linear.ExpType, 237 linear.FloorType, linear.LnType, linear.Log10Type, 238 linear.Log2Type, linear.SqrtType: 239 p, err = linear.NewMathOp(name) 240 return p, true, err 241 242 case aggregation.AbsentType: 243 p = aggregation.NewAbsentOp() 244 return p, true, err 245 246 case linear.ClampMinType, linear.ClampMaxType: 247 p, err = linear.NewClampOp(argValues, name) 248 return p, true, err 249 250 case linear.HistogramQuantileType: 251 p, err = linear.NewHistogramQuantileOp(argValues, name) 252 return p, true, err 253 254 case linear.RoundType: 255 p, err = linear.NewRoundOp(argValues) 256 return p, true, err 257 258 case tag.TagJoinType, tag.TagReplaceType: 259 p, err = tag.NewTagOp(name, stringValues) 260 return p, true, err 261 262 case temporal.AvgType, temporal.CountType, temporal.MinType, 263 temporal.MaxType, temporal.SumType, temporal.StdDevType, 264 temporal.StdVarType, temporal.LastType: 265 p, err = temporal.NewAggOp(argValues, name) 266 return p, true, err 267 268 case temporal.QuantileType: 269 p, err = temporal.NewQuantileOp(argValues, name) 270 return p, true, err 271 272 case temporal.HoltWintersType: 273 p, err = temporal.NewHoltWintersOp(argValues) 274 return p, true, err 275 276 case temporal.IRateType, temporal.IDeltaType, 277 temporal.RateType, temporal.IncreaseType, 278 temporal.DeltaType: 279 p, err = temporal.NewRateOp(argValues, name) 280 return p, true, err 281 282 case temporal.PredictLinearType, temporal.DerivType: 283 p, err = temporal.NewLinearRegressionOp(argValues, name) 284 return p, true, err 285 286 case temporal.ResetsType, temporal.ChangesType: 287 p, err = temporal.NewFunctionOp(argValues, name) 288 return p, true, err 289 290 case unconsolidated.TimestampType: 291 p, err = unconsolidated.NewTimestampOp(name) 292 return p, true, err 293 294 case scalar.TimeType: 295 p, err = scalar.NewTimeOp(tagOptions) 296 return p, true, err 297 298 case linear.SortType, linear.SortDescType: 299 p, err = linear.NewSortOp(name) 300 return p, true, err 301 302 // NB: no-ops. 303 case scalar.ScalarType: 304 return nil, false, err 305 306 default: 307 return nil, false, fmt.Errorf("function not supported: %s", name) 308 } 309 } 310 311 func getBinaryOpType(opType promql.ItemType) string { 312 switch opType { 313 case promql.LAND: 314 return binary.AndType 315 case promql.LOR: 316 return binary.OrType 317 case promql.LUNLESS: 318 return binary.UnlessType 319 320 case promql.ADD: 321 return binary.PlusType 322 case promql.SUB: 323 return binary.MinusType 324 case promql.MUL: 325 return binary.MultiplyType 326 case promql.DIV: 327 return binary.DivType 328 case promql.POW: 329 return binary.ExpType 330 case promql.MOD: 331 return binary.ModType 332 333 case promql.EQL, promql.EQLC: 334 return binary.EqType 335 case promql.NEQ: 336 return binary.NotEqType 337 case promql.GTR: 338 return binary.GreaterType 339 case promql.LSS: 340 return binary.LesserType 341 case promql.GTE: 342 return binary.GreaterEqType 343 case promql.LTE: 344 return binary.LesserEqType 345 346 default: 347 return common.UnknownOpType 348 } 349 } 350 351 // getUnaryOpType returns the M3 unary op type based on the Prom op type. 352 func getUnaryOpType(opType promql.ItemType) (string, error) { 353 switch opType { 354 case promql.ADD: 355 return binary.PlusType, nil 356 case promql.SUB: 357 return binary.MinusType, nil 358 default: 359 return "", fmt.Errorf( 360 "only + and - operators allowed for unary expressions, received: %s", 361 opType.String(), 362 ) 363 } 364 } 365 366 const ( 367 anchorStart = byte('^') 368 anchorEnd = byte('$') 369 escapeChar = byte('\\') 370 startGroup = byte('[') 371 endGroup = byte(']') 372 ) 373 374 func sanitizeRegex(value []byte) []byte { 375 lIndex := 0 376 rIndex := len(value) 377 escape := false 378 inGroup := false 379 for i, b := range value { 380 if escape { 381 escape = false 382 continue 383 } 384 385 if inGroup { 386 switch b { 387 case escapeChar: 388 escape = true 389 case endGroup: 390 inGroup = false 391 } 392 393 continue 394 } 395 396 switch b { 397 case anchorStart: 398 lIndex = i + 1 399 case anchorEnd: 400 rIndex = i 401 case escapeChar: 402 escape = true 403 case startGroup: 404 inGroup = true 405 } 406 } 407 408 if lIndex > rIndex { 409 return []byte{} 410 } 411 412 return value[lIndex:rIndex] 413 } 414 415 // LabelMatchersToModelMatcher parses promql matchers to model matchers. 416 func LabelMatchersToModelMatcher( 417 lMatchers []*labels.Matcher, 418 tagOpts models.TagOptions, 419 ) (models.Matchers, error) { 420 matchers := make(models.Matchers, 0, len(lMatchers)) 421 for _, m := range lMatchers { 422 matchType, err := promTypeToM3(m.Type) 423 if err != nil { 424 return nil, err 425 } 426 427 var name []byte 428 if m.Name == model.MetricNameLabel { 429 name = tagOpts.MetricName() 430 } else { 431 name = []byte(m.Name) 432 } 433 434 value := []byte(m.Value) 435 // NB: special case here since by Prometheus convention, a NEQ tag with no 436 // provided value is interpreted as verifying that the tag exists. 437 // Similarily, EQ tag with no provided value is interpreted as ensuring that 438 // the tag does not exist. 439 if len(value) == 0 { 440 if matchType == models.MatchNotEqual { 441 matchType = models.MatchField 442 } else if matchType == models.MatchEqual { 443 matchType = models.MatchNotField 444 } 445 } 446 447 if matchType == models.MatchRegexp || matchType == models.MatchNotRegexp { 448 // NB: special case here since tags such as `{foo=~"$bar"}` are valid in 449 // prometheus regex patterns, but invalid with m3 index queries. Simplify 450 // these matchers here. 451 value = sanitizeRegex(value) 452 } 453 454 match, err := models.NewMatcher(matchType, name, value) 455 if err != nil { 456 return nil, err 457 } 458 459 matchers = append(matchers, match) 460 } 461 462 return matchers, nil 463 } 464 465 // promTypeToM3 converts a prometheus label type to m3 matcher type. 466 // TODO(nikunj): Consider merging with prompb code. 467 func promTypeToM3(labelType labels.MatchType) (models.MatchType, error) { 468 switch labelType { 469 case labels.MatchEqual: 470 return models.MatchEqual, nil 471 case labels.MatchNotEqual: 472 return models.MatchNotEqual, nil 473 case labels.MatchRegexp: 474 return models.MatchRegexp, nil 475 case labels.MatchNotRegexp: 476 return models.MatchNotRegexp, nil 477 478 default: 479 return 0, fmt.Errorf("unknown match type %v", labelType) 480 } 481 } 482 483 func promVectorCardinalityToM3( 484 card promql.VectorMatchCardinality, 485 ) binary.VectorMatchCardinality { 486 switch card { 487 case promql.CardOneToOne: 488 return binary.CardOneToOne 489 case promql.CardManyToMany: 490 return binary.CardManyToMany 491 case promql.CardManyToOne: 492 return binary.CardManyToOne 493 case promql.CardOneToMany: 494 return binary.CardOneToMany 495 } 496 497 panic(fmt.Sprintf("unknown prom cardinality %d", card)) 498 } 499 500 func promMatchingToM3( 501 vectorMatching *promql.VectorMatching, 502 ) binary.VectorMatcherBuilder { 503 // vectorMatching can be nil iff at least one of the sides is a scalar. 504 if vectorMatching == nil { 505 return nil 506 } 507 508 byteMatchers := make([][]byte, len(vectorMatching.MatchingLabels)) 509 for i, label := range vectorMatching.MatchingLabels { 510 byteMatchers[i] = []byte(label) 511 } 512 513 return func(_, _ block.Block) binary.VectorMatching { 514 return binary.VectorMatching{ 515 Set: true, 516 Card: promVectorCardinalityToM3(vectorMatching.Card), 517 MatchingLabels: byteMatchers, 518 On: vectorMatching.On, 519 Include: vectorMatching.Include, 520 } 521 } 522 }