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 }