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  }