github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/index.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 storage
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/dbnode/storage/index"
    29  	"github.com/m3db/m3/src/m3ninx/idx"
    30  	"github.com/m3db/m3/src/query/models"
    31  	"github.com/m3db/m3/src/query/storage/m3/consolidators"
    32  	xerrors "github.com/m3db/m3/src/x/errors"
    33  	"github.com/m3db/m3/src/x/ident"
    34  	"github.com/m3db/m3/src/x/regexp"
    35  	xtime "github.com/m3db/m3/src/x/time"
    36  )
    37  
    38  var (
    39  	dotStar = []byte(".*")
    40  	dotPlus = []byte(".+")
    41  )
    42  
    43  // FromM3IdentToMetric converts an M3 ident metric to a coordinator metric.
    44  func FromM3IdentToMetric(
    45  	identID ident.ID,
    46  	iterTags ident.TagIterator,
    47  	tagOptions models.TagOptions,
    48  ) (models.Metric, error) {
    49  	tags, err := consolidators.FromIdentTagIteratorToTags(iterTags, tagOptions)
    50  	if err != nil {
    51  		return models.Metric{}, err
    52  	}
    53  
    54  	return models.Metric{
    55  		ID:   identID.Bytes(),
    56  		Tags: tags,
    57  	}, nil
    58  }
    59  
    60  // TagsToIdentTagIterator converts coordinator tags to ident tags.
    61  func TagsToIdentTagIterator(tags models.Tags) ident.TagIterator {
    62  	// TODO: get a tags and tag iterator from an ident.Pool here rather than allocing them here
    63  	identTags := make([]ident.Tag, 0, tags.Len())
    64  	for _, t := range tags.Tags {
    65  		identTags = append(identTags, ident.Tag{
    66  			Name:  ident.BytesID(t.Name),
    67  			Value: ident.BytesID(t.Value),
    68  		})
    69  	}
    70  
    71  	return ident.NewTagsIterator(ident.NewTags(identTags...))
    72  }
    73  
    74  // FetchOptionsToM3Options converts a set of coordinator options to M3 options.
    75  func FetchOptionsToM3Options(
    76  	fetchOptions *FetchOptions,
    77  	fetchQuery *FetchQuery,
    78  ) (index.QueryOptions, error) {
    79  	start, end, err := convertStartEndWithRangeLimit(fetchQuery.Start,
    80  		fetchQuery.End, fetchOptions)
    81  	if err != nil {
    82  		return index.QueryOptions{}, err
    83  	}
    84  
    85  	return index.QueryOptions{
    86  		SeriesLimit:                   fetchOptions.SeriesLimit,
    87  		InstanceMultiple:              fetchOptions.InstanceMultiple,
    88  		DocsLimit:                     fetchOptions.DocsLimit,
    89  		RequireExhaustive:             fetchOptions.RequireExhaustive,
    90  		RequireNoWait:                 fetchOptions.RequireNoWait,
    91  		ReadConsistencyLevel:          fetchOptions.ReadConsistencyLevel,
    92  		IterateEqualTimestampStrategy: fetchOptions.IterateEqualTimestampStrategy,
    93  		Source:                        fetchOptions.Source,
    94  		StartInclusive:                xtime.ToUnixNano(start),
    95  		EndExclusive:                  xtime.ToUnixNano(end),
    96  	}, nil
    97  }
    98  
    99  func convertStartEndWithRangeLimit(
   100  	start, end time.Time,
   101  	fetchOptions *FetchOptions,
   102  ) (time.Time, time.Time, error) {
   103  	fetchRangeLimit := fetchOptions.RangeLimit
   104  	if fetchRangeLimit <= 0 {
   105  		return start, end, nil
   106  	}
   107  
   108  	fetchRange := end.Sub(start)
   109  	if fetchRange <= fetchRangeLimit {
   110  		return start, end, nil
   111  	}
   112  
   113  	if fetchOptions.RequireExhaustive {
   114  		// Fail the query.
   115  		msg := fmt.Sprintf("query exceeded limit: require_exhaustive=%v, "+
   116  			"range_limit=%s, range_matched=%s",
   117  			fetchOptions.RequireExhaustive,
   118  			fetchRangeLimit.String(),
   119  			fetchRange.String())
   120  		err := xerrors.NewInvalidParamsError(consolidators.NewLimitError(msg))
   121  		return time.Time{}, time.Time{}, err
   122  	}
   123  
   124  	// Truncate the range.
   125  	start = end.Add(-1 * fetchRangeLimit)
   126  	return start, end, nil
   127  }
   128  
   129  func convertAggregateQueryType(completeNameOnly bool) index.AggregationType {
   130  	if completeNameOnly {
   131  		return index.AggregateTagNames
   132  	}
   133  
   134  	return index.AggregateTagNamesAndValues
   135  }
   136  
   137  // FetchOptionsToAggregateOptions converts a set of coordinator options as well
   138  // as complete tags query to an M3 aggregate query option.
   139  func FetchOptionsToAggregateOptions(
   140  	fetchOptions *FetchOptions,
   141  	tagQuery *CompleteTagsQuery,
   142  ) (index.AggregationOptions, error) {
   143  	start, end, err := convertStartEndWithRangeLimit(tagQuery.Start.ToTime(),
   144  		tagQuery.End.ToTime(), fetchOptions)
   145  	if err != nil {
   146  		return index.AggregationOptions{}, err
   147  	}
   148  
   149  	return index.AggregationOptions{
   150  		QueryOptions: index.QueryOptions{
   151  			SeriesLimit:       fetchOptions.SeriesLimit,
   152  			DocsLimit:         fetchOptions.DocsLimit,
   153  			Source:            fetchOptions.Source,
   154  			RequireExhaustive: fetchOptions.RequireExhaustive,
   155  			RequireNoWait:     fetchOptions.RequireNoWait,
   156  			StartInclusive:    xtime.ToUnixNano(start),
   157  			EndExclusive:      xtime.ToUnixNano(end),
   158  		},
   159  		FieldFilter: tagQuery.FilterNameTags,
   160  		Type:        convertAggregateQueryType(tagQuery.CompleteNameOnly),
   161  	}, nil
   162  }
   163  
   164  // FetchQueryToM3Query converts an m3coordinator fetch query to an M3 query.
   165  func FetchQueryToM3Query(
   166  	fetchQuery *FetchQuery,
   167  	options *FetchOptions,
   168  ) (index.Query, error) {
   169  	fetchQuery = fetchQuery.WithAppliedOptions(options)
   170  	matchers := fetchQuery.TagMatchers
   171  	// If no matchers provided, explicitly set this to an AllQuery.
   172  	if len(matchers) == 0 {
   173  		return index.Query{
   174  			Query: idx.NewAllQuery(),
   175  		}, nil
   176  	}
   177  
   178  	// Optimization for single matcher case.
   179  	if len(matchers) == 1 {
   180  		specialCase, err := isSpecialCaseMatcher(matchers[0])
   181  		if err != nil {
   182  			return index.Query{}, err
   183  		}
   184  		if specialCase.skip {
   185  			// NB: only matcher has no effect; this is synonymous to an AllQuery.
   186  			return index.Query{
   187  				Query: idx.NewAllQuery(),
   188  			}, nil
   189  		}
   190  
   191  		if specialCase.isSpecial {
   192  			return index.Query{Query: specialCase.query}, nil
   193  		}
   194  
   195  		q, err := matcherToQuery(matchers[0])
   196  		if err != nil {
   197  			return index.Query{}, err
   198  		}
   199  
   200  		return index.Query{Query: q}, nil
   201  	}
   202  
   203  	idxQueries := make([]idx.Query, 0, len(matchers))
   204  	for _, matcher := range matchers {
   205  		specialCase, err := isSpecialCaseMatcher(matcher)
   206  		if err != nil {
   207  			return index.Query{}, err
   208  		}
   209  		if specialCase.skip {
   210  			continue
   211  		}
   212  
   213  		if specialCase.isSpecial {
   214  			idxQueries = append(idxQueries, specialCase.query)
   215  			continue
   216  		}
   217  
   218  		q, err := matcherToQuery(matcher)
   219  		if err != nil {
   220  			return index.Query{}, err
   221  		}
   222  
   223  		idxQueries = append(idxQueries, q)
   224  	}
   225  
   226  	q := idx.NewConjunctionQuery(idxQueries...)
   227  
   228  	return index.Query{Query: q}, nil
   229  }
   230  
   231  type specialCase struct {
   232  	query     idx.Query
   233  	isSpecial bool
   234  	skip      bool
   235  }
   236  
   237  func isSpecialCaseMatcher(matcher models.Matcher) (specialCase, error) {
   238  	if len(matcher.Value) == 0 {
   239  		if matcher.Type == models.MatchNotRegexp ||
   240  			matcher.Type == models.MatchNotEqual {
   241  			query := idx.NewFieldQuery(matcher.Name)
   242  			return specialCase{query: query, isSpecial: true}, nil
   243  		}
   244  
   245  		if matcher.Type == models.MatchRegexp ||
   246  			matcher.Type == models.MatchEqual {
   247  			query := idx.NewNegationQuery(idx.NewFieldQuery(matcher.Name))
   248  			return specialCase{query: query, isSpecial: true}, nil
   249  		}
   250  
   251  		return specialCase{}, nil
   252  	}
   253  
   254  	// NB: no special case except for regex / notRegex here.
   255  	isNegatedRegex := matcher.Type == models.MatchNotRegexp
   256  	isRegex := matcher.Type == models.MatchRegexp
   257  	if !isNegatedRegex && !isRegex {
   258  		return specialCase{}, nil
   259  	}
   260  
   261  	if bytes.Equal(matcher.Value, dotStar) {
   262  		if isNegatedRegex {
   263  			// NB: This should match no results.
   264  			query := idx.NewNegationQuery(idx.NewAllQuery())
   265  			return specialCase{query: query, isSpecial: true}, nil
   266  		}
   267  
   268  		// NB: this matcher should not affect query results.
   269  		return specialCase{skip: true}, nil
   270  	}
   271  
   272  	if bytes.Equal(matcher.Value, dotPlus) {
   273  		query := idx.NewFieldQuery(matcher.Name)
   274  		if isNegatedRegex {
   275  			query = idx.NewNegationQuery(query)
   276  		}
   277  
   278  		return specialCase{query: query, isSpecial: true}, nil
   279  	}
   280  
   281  	matchesEmpty, err := regexp.MatchesEmptyValue(matcher.Value)
   282  	if err != nil {
   283  		return specialCase{}, regexError(err)
   284  	}
   285  
   286  	if matchesEmpty {
   287  		regexpQuery, err := idx.NewRegexpQuery(matcher.Name, matcher.Value)
   288  		if err != nil {
   289  			return specialCase{}, err
   290  		}
   291  
   292  		if isNegatedRegex {
   293  			return specialCase{
   294  				query: idx.NewConjunctionQuery(
   295  					idx.NewNegationQuery(regexpQuery),
   296  					idx.NewFieldQuery(matcher.Name),
   297  				),
   298  				isSpecial: true,
   299  			}, nil
   300  		}
   301  
   302  		return specialCase{
   303  			query: idx.NewDisjunctionQuery(
   304  				regexpQuery,
   305  				idx.NewNegationQuery(idx.NewFieldQuery(matcher.Name)),
   306  			),
   307  			isSpecial: true,
   308  		}, nil
   309  	}
   310  
   311  	return specialCase{}, nil
   312  }
   313  
   314  func matcherToQuery(matcher models.Matcher) (idx.Query, error) {
   315  	negate := false
   316  	switch matcher.Type {
   317  	// Support for Regexp types
   318  	case models.MatchNotRegexp:
   319  		negate = true
   320  		fallthrough
   321  
   322  	case models.MatchRegexp:
   323  		var (
   324  			query idx.Query
   325  			err   error
   326  		)
   327  
   328  		query, err = idx.NewRegexpQuery(matcher.Name, matcher.Value)
   329  		if err != nil {
   330  			return idx.Query{}, err
   331  		}
   332  
   333  		if negate {
   334  			query = idx.NewNegationQuery(query)
   335  		}
   336  
   337  		return query, nil
   338  
   339  		// Support exact matches
   340  	case models.MatchNotEqual:
   341  		negate = true
   342  		fallthrough
   343  
   344  	case models.MatchEqual:
   345  		query := idx.NewTermQuery(matcher.Name, matcher.Value)
   346  		if negate {
   347  			query = idx.NewNegationQuery(query)
   348  		}
   349  
   350  		return query, nil
   351  
   352  	case models.MatchNotField:
   353  		negate = true
   354  		fallthrough
   355  
   356  	case models.MatchField:
   357  		query := idx.NewFieldQuery(matcher.Name)
   358  		if negate {
   359  			query = idx.NewNegationQuery(query)
   360  		}
   361  
   362  		return query, nil
   363  
   364  	case models.MatchAll:
   365  		return idx.NewAllQuery(), nil
   366  
   367  	default:
   368  		return idx.Query{}, fmt.Errorf("unsupported query type: %v", matcher)
   369  	}
   370  }
   371  
   372  func regexError(err error) error {
   373  	return xerrors.NewInvalidParamsError(xerrors.Wrap(err, "regex error"))
   374  }