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 }