github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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 name: "disjunction with empty (no field) match, no parens", 241 expected: "disjunction(negation(field(env)), regexp(env,one|))", 242 matchers: models.Matchers{ 243 { 244 Type: models.MatchRegexp, 245 Name: []byte("env"), 246 Value: []byte("one|"), 247 }, 248 }, 249 }, 250 { 251 name: "disjunction with empty (no field) match", 252 expected: "disjunction(negation(field(env)), regexp(env,(|one|two)))", 253 matchers: models.Matchers{ 254 { 255 Type: models.MatchRegexp, 256 Name: []byte("env"), 257 Value: []byte("(|one|two)"), 258 }, 259 }, 260 }, 261 { 262 name: "disjunction with non trivial empty (no field) match", 263 expected: "disjunction(negation(field(env)), regexp(env,\\d*|one))", 264 matchers: models.Matchers{ 265 { 266 Type: models.MatchRegexp, 267 Name: []byte("env"), 268 Value: []byte("\\d*|one"), 269 }, 270 }, 271 }, 272 { 273 name: "disjunction with both empty (no field) matches", 274 expected: "disjunction(negation(field(env)), regexp(env,(|)))", 275 matchers: models.Matchers{ 276 { 277 Type: models.MatchRegexp, 278 Name: []byte("env"), 279 Value: []byte("(|)"), 280 }, 281 }, 282 }, 283 { 284 name: "negated disjunction with empty (no field) match", 285 expected: "conjunction(field(env),negation(regexp(env,(|one))))", 286 matchers: models.Matchers{ 287 { 288 Type: models.MatchNotRegexp, 289 Name: []byte("env"), 290 Value: []byte("(|one)"), 291 }, 292 }, 293 }, 294 { 295 name: "negated disjunction with both empty (no field) matches", 296 expected: "conjunction(field(env),negation(regexp(env,(|))))", 297 matchers: models.Matchers{ 298 { 299 Type: models.MatchNotRegexp, 300 Name: []byte("env"), 301 Value: []byte("(|)"), 302 }, 303 }, 304 }, 305 } 306 307 for _, test := range tests { 308 t.Run(test.name, func(t *testing.T) { 309 fetchQuery := &FetchQuery{ 310 Raw: "up", 311 TagMatchers: test.matchers, 312 Start: now.Add(-5 * time.Minute), 313 End: now, 314 Interval: 15 * time.Second, 315 } 316 317 m3Query, err := FetchQueryToM3Query(fetchQuery, nil) 318 require.NoError(t, err) 319 assert.Equal(t, test.expected, m3Query.String()) 320 }) 321 } 322 } 323 324 func TestFetchOptionsToAggregateOptions(t *testing.T) { 325 now := time.Now() 326 327 tests := []struct { 328 name string 329 fetchOptions *FetchOptions 330 tagQuery *CompleteTagsQuery 331 expectedErr bool 332 expectedAdjustedStart *time.Time 333 expectedAdjustedEnd *time.Time 334 }{ 335 { 336 name: "all options", 337 fetchOptions: &FetchOptions{ 338 SeriesLimit: 7, 339 DocsLimit: 8, 340 RangeLimit: 2 * time.Hour, 341 RequireExhaustive: true, 342 }, 343 tagQuery: &CompleteTagsQuery{ 344 Start: xtime.ToUnixNano(now.Add(-1 * time.Hour)), 345 End: xtime.ToUnixNano(now), 346 TagMatchers: models.Matchers{ 347 models.Matcher{ 348 Type: models.MatchNotRegexp, 349 Name: []byte("foo"), Value: []byte("bar"), 350 }, 351 }, 352 FilterNameTags: [][]byte{[]byte("filter")}, 353 CompleteNameOnly: true, 354 }, 355 }, 356 { 357 name: "range limit exceeded error", 358 fetchOptions: &FetchOptions{ 359 RangeLimit: 30 * time.Minute, 360 RequireExhaustive: true, 361 }, 362 tagQuery: &CompleteTagsQuery{ 363 Start: xtime.ToUnixNano(now.Add(-1 * time.Hour)), 364 End: xtime.ToUnixNano(now), 365 TagMatchers: models.Matchers{ 366 models.Matcher{ 367 Type: models.MatchNotRegexp, 368 Name: []byte("foo"), Value: []byte("bar"), 369 }, 370 }, 371 }, 372 expectedErr: true, 373 }, 374 { 375 name: "range limit truncate start/end", 376 fetchOptions: &FetchOptions{ 377 RangeLimit: 30 * time.Minute, 378 RequireExhaustive: false, 379 }, 380 tagQuery: &CompleteTagsQuery{ 381 Start: xtime.ToUnixNano(now.Add(-1 * time.Hour)), 382 End: xtime.ToUnixNano(now), 383 TagMatchers: models.Matchers{ 384 models.Matcher{ 385 Type: models.MatchNotRegexp, 386 Name: []byte("foo"), Value: []byte("bar"), 387 }, 388 }, 389 }, 390 expectedAdjustedStart: timePtr(now.Add(-30 * time.Minute)), 391 expectedAdjustedEnd: timePtr(now), 392 }, 393 } 394 395 for _, tt := range tests { 396 t.Run(tt.name, func(t *testing.T) { 397 aggOpts, err := FetchOptionsToAggregateOptions(tt.fetchOptions, tt.tagQuery) 398 399 if tt.expectedErr { 400 require.Error(t, err) 401 return 402 } 403 404 require.NoError(t, err) 405 406 expectedStart := tt.tagQuery.Start 407 expectedEnd := tt.tagQuery.End 408 if v := tt.expectedAdjustedStart; v != nil { 409 expectedStart = xtime.ToUnixNano(*v) 410 } 411 if v := tt.expectedAdjustedEnd; v != nil { 412 expectedEnd = xtime.ToUnixNano(*v) 413 } 414 require.Equal(t, expectedStart, aggOpts.StartInclusive) 415 require.Equal(t, expectedEnd, aggOpts.EndExclusive) 416 417 if tt.tagQuery.CompleteNameOnly { 418 require.Equal(t, index.AggregateTagNames, aggOpts.Type) 419 } else { 420 require.Equal(t, index.AggregateTagNamesAndValues, aggOpts.Type) 421 } 422 require.Equal(t, tt.tagQuery.FilterNameTags, [][]byte(aggOpts.FieldFilter)) 423 require.Equal(t, tt.fetchOptions.SeriesLimit, aggOpts.SeriesLimit) 424 require.Equal(t, tt.fetchOptions.DocsLimit, aggOpts.DocsLimit) 425 require.Equal(t, tt.fetchOptions.RequireExhaustive, aggOpts.RequireExhaustive) 426 }) 427 } 428 } 429 430 func timePtr(t time.Time) *time.Time { 431 return &t 432 }