github.com/m3db/m3@v1.5.0/src/query/storage/index_test.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  	"testing"
    25  	"time"
    26  
    27  	"github.com/m3db/m3/src/dbnode/storage/index"
    28  	"github.com/m3db/m3/src/query/models"
    29  	"github.com/m3db/m3/src/x/ident"
    30  	xtime "github.com/m3db/m3/src/x/time"
    31  
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  var (
    37  	testID   = ident.StringID("test_id")
    38  	testTags = []models.Tag{
    39  		{Name: []byte("t1"), Value: []byte("v1")},
    40  		{Name: []byte("t2"), Value: []byte("v2")},
    41  	}
    42  	now = time.Now()
    43  )
    44  
    45  func makeTagIter() ident.TagIterator {
    46  	ts := models.EmptyTags().AddTags(testTags)
    47  	return TagsToIdentTagIterator(ts)
    48  }
    49  
    50  func TestTagsToIdentTagIterator(t *testing.T) {
    51  	tagIter := makeTagIter()
    52  	defer tagIter.Close()
    53  
    54  	tags := make([]models.Tag, len(testTags))
    55  	for i := 0; tagIter.Next(); i++ {
    56  		tags[i] = models.Tag{
    57  			Name:  tagIter.Current().Name.Bytes(),
    58  			Value: tagIter.Current().Value.Bytes(),
    59  		}
    60  	}
    61  
    62  	assert.Equal(t, testTags, tags)
    63  }
    64  
    65  func TestFromM3IdentToMetric(t *testing.T) {
    66  	tagIters := makeTagIter()
    67  	name := []byte("foobarbaz")
    68  	metric, err := FromM3IdentToMetric(testID, tagIters, models.NewTagOptions().SetMetricName(name))
    69  	require.NoError(t, err)
    70  
    71  	assert.Equal(t, testID.Bytes(), metric.ID)
    72  	assert.Equal(t, testTags, metric.Tags.Tags)
    73  	assert.Equal(t, name, metric.Tags.Opts.MetricName())
    74  }
    75  
    76  func TestFetchQueryToM3Query(t *testing.T) {
    77  	tests := []struct {
    78  		name     string
    79  		expected string
    80  		matchers models.Matchers
    81  	}{
    82  		{
    83  			name:     "exact match",
    84  			expected: "term(t1,v1)",
    85  			matchers: models.Matchers{
    86  				{
    87  					Type:  models.MatchEqual,
    88  					Name:  []byte("t1"),
    89  					Value: []byte("v1"),
    90  				},
    91  			},
    92  		},
    93  		{
    94  			name:     "exact match negated",
    95  			expected: "negation(term(t1,v1))",
    96  			matchers: models.Matchers{
    97  				{
    98  					Type:  models.MatchNotEqual,
    99  					Name:  []byte("t1"),
   100  					Value: []byte("v1"),
   101  				},
   102  			},
   103  		},
   104  		{
   105  			name:     "regexp match",
   106  			expected: "regexp(t1,v1)",
   107  			matchers: models.Matchers{
   108  				{
   109  					Type:  models.MatchRegexp,
   110  					Name:  []byte("t1"),
   111  					Value: []byte("v1"),
   112  				},
   113  			},
   114  		},
   115  		{
   116  			name:     "regexp match dot star -> all",
   117  			expected: "all()",
   118  			matchers: models.Matchers{
   119  				{
   120  					Type:  models.MatchRegexp,
   121  					Name:  []byte("t1"),
   122  					Value: []byte(".*"),
   123  				},
   124  			},
   125  		},
   126  		{
   127  			name:     "regexp match dot plus -> field",
   128  			expected: "field(t1)",
   129  			matchers: models.Matchers{
   130  				{
   131  					Type:  models.MatchRegexp,
   132  					Name:  []byte("t1"),
   133  					Value: []byte(".+"),
   134  				},
   135  			},
   136  		},
   137  		{
   138  			name:     "regexp match negated",
   139  			expected: "negation(regexp(t1,v1))",
   140  			matchers: models.Matchers{
   141  				{
   142  					Type:  models.MatchNotRegexp,
   143  					Name:  []byte("t1"),
   144  					Value: []byte("v1"),
   145  				},
   146  			},
   147  		},
   148  		{
   149  			name:     "regexp match negated",
   150  			expected: "negation(all())",
   151  			matchers: models.Matchers{
   152  				{
   153  					Type:  models.MatchNotRegexp,
   154  					Name:  []byte("t1"),
   155  					Value: []byte(".*"),
   156  				},
   157  			},
   158  		},
   159  		{
   160  			name:     "field match",
   161  			expected: "field(t1)",
   162  			matchers: models.Matchers{
   163  				{
   164  					Type:  models.MatchField,
   165  					Name:  []byte("t1"),
   166  					Value: []byte("v1"),
   167  				},
   168  			},
   169  		},
   170  		{
   171  			name:     "field match negated",
   172  			expected: "negation(field(t1))",
   173  			matchers: models.Matchers{
   174  				{
   175  					Type:  models.MatchNotField,
   176  					Name:  []byte("t1"),
   177  					Value: []byte("v1"),
   178  				},
   179  			},
   180  		},
   181  		{
   182  			name:     "all matchers",
   183  			expected: "all()",
   184  			matchers: models.Matchers{},
   185  		},
   186  		{
   187  			name:     "all matchers",
   188  			expected: "all()",
   189  			matchers: models.Matchers{
   190  				{
   191  					Type: models.MatchAll,
   192  				},
   193  			},
   194  		},
   195  		{
   196  			name:     "regexp match dot star with trailing characters -> regex",
   197  			expected: "regexp(t1,.*foo)",
   198  			matchers: models.Matchers{
   199  				{
   200  					Type:  models.MatchRegexp,
   201  					Name:  []byte("t1"),
   202  					Value: []byte(".*foo"),
   203  				},
   204  			},
   205  		},
   206  		{
   207  			name:     "regexp match dot plus with trailing characters -> regex",
   208  			expected: "regexp(t1,.+foo)",
   209  			matchers: models.Matchers{
   210  				{
   211  					Type:  models.MatchRegexp,
   212  					Name:  []byte("t1"),
   213  					Value: []byte(".+foo"),
   214  				},
   215  			},
   216  		},
   217  		{
   218  			name:     "not regexp match dot star with trailing characters -> regex",
   219  			expected: "negation(regexp(t1,.*foo))",
   220  			matchers: models.Matchers{
   221  				{
   222  					Type:  models.MatchNotRegexp,
   223  					Name:  []byte("t1"),
   224  					Value: []byte(".*foo"),
   225  				},
   226  			},
   227  		},
   228  		{
   229  			name:     "not regexp match dot plus with trailing characters -> regex",
   230  			expected: "negation(regexp(t1,.+foo))",
   231  			matchers: models.Matchers{
   232  				{
   233  					Type:  models.MatchNotRegexp,
   234  					Name:  []byte("t1"),
   235  					Value: []byte(".+foo"),
   236  				},
   237  			},
   238  		},
   239  	}
   240  
   241  	for _, test := range tests {
   242  		t.Run(test.name, func(t *testing.T) {
   243  			fetchQuery := &FetchQuery{
   244  				Raw:         "up",
   245  				TagMatchers: test.matchers,
   246  				Start:       now.Add(-5 * time.Minute),
   247  				End:         now,
   248  				Interval:    15 * time.Second,
   249  			}
   250  
   251  			m3Query, err := FetchQueryToM3Query(fetchQuery, nil)
   252  			require.NoError(t, err)
   253  			assert.Equal(t, test.expected, m3Query.String())
   254  		})
   255  	}
   256  }
   257  
   258  func TestFetchOptionsToAggregateOptions(t *testing.T) {
   259  	now := time.Now()
   260  
   261  	tests := []struct {
   262  		name                  string
   263  		fetchOptions          *FetchOptions
   264  		tagQuery              *CompleteTagsQuery
   265  		expectedErr           bool
   266  		expectedAdjustedStart *time.Time
   267  		expectedAdjustedEnd   *time.Time
   268  	}{
   269  		{
   270  			name: "all options",
   271  			fetchOptions: &FetchOptions{
   272  				SeriesLimit:       7,
   273  				DocsLimit:         8,
   274  				RangeLimit:        2 * time.Hour,
   275  				RequireExhaustive: true,
   276  			},
   277  			tagQuery: &CompleteTagsQuery{
   278  				Start: xtime.ToUnixNano(now.Add(-1 * time.Hour)),
   279  				End:   xtime.ToUnixNano(now),
   280  				TagMatchers: models.Matchers{
   281  					models.Matcher{
   282  						Type: models.MatchNotRegexp,
   283  						Name: []byte("foo"), Value: []byte("bar"),
   284  					},
   285  				},
   286  				FilterNameTags:   [][]byte{[]byte("filter")},
   287  				CompleteNameOnly: true,
   288  			},
   289  		},
   290  		{
   291  			name: "range limit exceeded error",
   292  			fetchOptions: &FetchOptions{
   293  				RangeLimit:        30 * time.Minute,
   294  				RequireExhaustive: true,
   295  			},
   296  			tagQuery: &CompleteTagsQuery{
   297  				Start: xtime.ToUnixNano(now.Add(-1 * time.Hour)),
   298  				End:   xtime.ToUnixNano(now),
   299  				TagMatchers: models.Matchers{
   300  					models.Matcher{
   301  						Type: models.MatchNotRegexp,
   302  						Name: []byte("foo"), Value: []byte("bar"),
   303  					},
   304  				},
   305  			},
   306  			expectedErr: true,
   307  		},
   308  		{
   309  			name: "range limit truncate start/end",
   310  			fetchOptions: &FetchOptions{
   311  				RangeLimit:        30 * time.Minute,
   312  				RequireExhaustive: false,
   313  			},
   314  			tagQuery: &CompleteTagsQuery{
   315  				Start: xtime.ToUnixNano(now.Add(-1 * time.Hour)),
   316  				End:   xtime.ToUnixNano(now),
   317  				TagMatchers: models.Matchers{
   318  					models.Matcher{
   319  						Type: models.MatchNotRegexp,
   320  						Name: []byte("foo"), Value: []byte("bar"),
   321  					},
   322  				},
   323  			},
   324  			expectedAdjustedStart: timePtr(now.Add(-30 * time.Minute)),
   325  			expectedAdjustedEnd:   timePtr(now),
   326  		},
   327  	}
   328  
   329  	for _, tt := range tests {
   330  		t.Run(tt.name, func(t *testing.T) {
   331  			aggOpts, err := FetchOptionsToAggregateOptions(tt.fetchOptions, tt.tagQuery)
   332  
   333  			if tt.expectedErr {
   334  				require.Error(t, err)
   335  				return
   336  			}
   337  
   338  			require.NoError(t, err)
   339  
   340  			expectedStart := tt.tagQuery.Start
   341  			expectedEnd := tt.tagQuery.End
   342  			if v := tt.expectedAdjustedStart; v != nil {
   343  				expectedStart = xtime.ToUnixNano(*v)
   344  			}
   345  			if v := tt.expectedAdjustedEnd; v != nil {
   346  				expectedEnd = xtime.ToUnixNano(*v)
   347  			}
   348  			require.Equal(t, expectedStart, aggOpts.StartInclusive)
   349  			require.Equal(t, expectedEnd, aggOpts.EndExclusive)
   350  
   351  			if tt.tagQuery.CompleteNameOnly {
   352  				require.Equal(t, index.AggregateTagNames, aggOpts.Type)
   353  			} else {
   354  				require.Equal(t, index.AggregateTagNamesAndValues, aggOpts.Type)
   355  			}
   356  			require.Equal(t, tt.tagQuery.FilterNameTags, [][]byte(aggOpts.FieldFilter))
   357  			require.Equal(t, tt.fetchOptions.SeriesLimit, aggOpts.SeriesLimit)
   358  			require.Equal(t, tt.fetchOptions.DocsLimit, aggOpts.DocsLimit)
   359  			require.Equal(t, tt.fetchOptions.RequireExhaustive, aggOpts.RequireExhaustive)
   360  		})
   361  	}
   362  }
   363  
   364  func timePtr(t time.Time) *time.Time {
   365  	return &t
   366  }