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 }