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