github.com/m3db/m3@v1.5.0/src/dbnode/network/server/tchannelthrift/convert/convert.go (about) 1 // Copyright (c) 2016 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 convert 22 23 import ( 24 stdctx "context" 25 "errors" 26 "fmt" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/generated/thrift/rpc" 30 tterrors "github.com/m3db/m3/src/dbnode/network/server/tchannelthrift/errors" 31 "github.com/m3db/m3/src/dbnode/storage/index" 32 "github.com/m3db/m3/src/dbnode/storage/limits" 33 "github.com/m3db/m3/src/dbnode/x/xio" 34 "github.com/m3db/m3/src/dbnode/x/xpool" 35 "github.com/m3db/m3/src/m3ninx/generated/proto/querypb" 36 "github.com/m3db/m3/src/m3ninx/idx" 37 "github.com/m3db/m3/src/x/checked" 38 "github.com/m3db/m3/src/x/context" 39 xerrors "github.com/m3db/m3/src/x/errors" 40 "github.com/m3db/m3/src/x/ident" 41 xtime "github.com/m3db/m3/src/x/time" 42 ) 43 44 var ( 45 errUnknownTimeType = errors.New("unknown time type") 46 errUnknownUnit = errors.New("unknown unit") 47 errNilTaggedRequest = errors.New("nil write tagged request") 48 49 timeZero time.Time 50 ) 51 52 const ( 53 fetchTaggedTimeType = rpc.TimeType_UNIX_NANOSECONDS 54 ) 55 56 // ToTime converts a value to a time. 57 func ToTime(value int64, timeType rpc.TimeType) (xtime.UnixNano, error) { 58 unit, err := ToDuration(timeType) 59 if err != nil { 60 return 0, err 61 } 62 // NB(r): Doesn't matter what unit is if we have zero of them. 63 if value == 0 { 64 return 0, nil 65 } 66 return xtime.FromNormalizedTime(value, unit), nil 67 } 68 69 // ToValue converts a time to a value. 70 func ToValue(t xtime.UnixNano, timeType rpc.TimeType) (int64, error) { 71 unit, err := ToDuration(timeType) 72 if err != nil { 73 return 0, err 74 } 75 76 return t.ToNormalizedTime(unit), nil 77 } 78 79 // ToDuration converts a time type to a duration. 80 func ToDuration(timeType rpc.TimeType) (time.Duration, error) { 81 unit, err := ToUnit(timeType) 82 if err != nil { 83 return 0, err 84 } 85 return unit.Value() 86 } 87 88 // ToUnit converts a time type to a unit. 89 func ToUnit(timeType rpc.TimeType) (xtime.Unit, error) { 90 switch timeType { 91 case rpc.TimeType_UNIX_SECONDS: 92 return xtime.Second, nil 93 case rpc.TimeType_UNIX_MILLISECONDS: 94 return xtime.Millisecond, nil 95 case rpc.TimeType_UNIX_MICROSECONDS: 96 return xtime.Microsecond, nil 97 case rpc.TimeType_UNIX_NANOSECONDS: 98 return xtime.Nanosecond, nil 99 } 100 return 0, errUnknownTimeType 101 } 102 103 // ToTimeType converts a unit to a time type 104 func ToTimeType(unit xtime.Unit) (rpc.TimeType, error) { 105 switch unit { 106 case xtime.Second: 107 return rpc.TimeType_UNIX_SECONDS, nil 108 case xtime.Millisecond: 109 return rpc.TimeType_UNIX_MILLISECONDS, nil 110 case xtime.Microsecond: 111 return rpc.TimeType_UNIX_MICROSECONDS, nil 112 case xtime.Nanosecond: 113 return rpc.TimeType_UNIX_NANOSECONDS, nil 114 } 115 return 0, errUnknownUnit 116 } 117 118 // ToSegmentsResult is the result of a convert to segments call, 119 // if the segments were merged then checksum is ptr to the checksum 120 // otherwise it is nil. 121 type ToSegmentsResult struct { 122 Segments *rpc.Segments 123 Checksum *int64 124 } 125 126 // ToSegments converts a list of blocks to segments. 127 func ToSegments(ctx context.Context, blocks []xio.BlockReader) (ToSegmentsResult, error) { //nolint: gocyclo 128 if len(blocks) == 0 { 129 return ToSegmentsResult{}, nil 130 } 131 132 s := &rpc.Segments{} 133 134 if len(blocks) == 1 { 135 // check the deadline before potentially blocking for the results from the disk read. in the worst case we'll 136 // wait for one extra block past the rpc deadline. 137 select { 138 case <-ctx.GoContext().Done(): 139 return ToSegmentsResult{}, ctx.GoContext().Err() 140 default: 141 } 142 seg, err := blocks[0].Segment() 143 if err != nil { 144 return ToSegmentsResult{}, err 145 } 146 if seg.Len() == 0 { 147 return ToSegmentsResult{}, nil 148 } 149 startTime := int64(blocks[0].Start) 150 blockSize := xtime.ToNormalizedDuration(blocks[0].BlockSize, time.Nanosecond) 151 checksum := int64(seg.CalculateChecksum()) 152 s.Merged = &rpc.Segment{ 153 Head: bytesRef(seg.Head), 154 Tail: bytesRef(seg.Tail), 155 StartTime: &startTime, 156 BlockSize: &blockSize, 157 Checksum: &checksum, 158 } 159 return ToSegmentsResult{ 160 Segments: s, 161 Checksum: &checksum, 162 }, nil 163 } 164 165 for _, block := range blocks { 166 select { 167 case <-ctx.GoContext().Done(): 168 return ToSegmentsResult{}, ctx.GoContext().Err() 169 default: 170 } 171 seg, err := block.Segment() 172 if err != nil { 173 return ToSegmentsResult{}, err 174 } 175 if seg.Len() == 0 { 176 continue 177 } 178 startTime := int64(block.Start) 179 blockSize := xtime.ToNormalizedDuration(block.BlockSize, time.Nanosecond) 180 checksum := int64(seg.CalculateChecksum()) 181 s.Unmerged = append(s.Unmerged, &rpc.Segment{ 182 Head: bytesRef(seg.Head), 183 Tail: bytesRef(seg.Tail), 184 StartTime: &startTime, 185 BlockSize: &blockSize, 186 Checksum: &checksum, 187 }) 188 } 189 if len(s.Unmerged) == 0 { 190 return ToSegmentsResult{}, nil 191 } 192 193 return ToSegmentsResult{Segments: s}, nil 194 } 195 196 func bytesRef(data checked.Bytes) []byte { 197 if data != nil { 198 return data.Bytes() 199 } 200 return nil 201 } 202 203 // ToRPCError converts a server error to a RPC error. 204 func ToRPCError(err error) *rpc.Error { 205 if err == nil { 206 return nil 207 } 208 209 // If already an RPC error then just return it. 210 var rpcErr *rpc.Error 211 if errors.As(err, &rpcErr) { 212 return rpcErr 213 } 214 215 if limits.IsQueryLimitExceededError(err) { 216 return tterrors.NewResourceExhaustedError(err) 217 } 218 if xerrors.IsInvalidParams(err) { 219 return tterrors.NewBadRequestError(err) 220 } 221 if xerrors.Is(err, stdctx.Canceled) || xerrors.Is(err, stdctx.DeadlineExceeded) { 222 return tterrors.NewTimeoutError(err) 223 } 224 225 return tterrors.NewInternalError(err) 226 } 227 228 // FetchTaggedConversionPools allows users to pass a pool for conversions. 229 type FetchTaggedConversionPools interface { 230 // ID returns an ident.Pool 231 ID() ident.Pool 232 233 // CheckedBytesWrapperPool returns a CheckedBytesWrapperPool. 234 CheckedBytesWrapper() xpool.CheckedBytesWrapperPool 235 } 236 237 // FromRPCFetchTaggedRequest converts the rpc request type for FetchTaggedRequest into corresponding Go API types. 238 func FromRPCFetchTaggedRequest( 239 req *rpc.FetchTaggedRequest, pools FetchTaggedConversionPools, 240 ) (ident.ID, index.Query, index.QueryOptions, bool, error) { 241 start, rangeStartErr := ToTime(req.RangeStart, fetchTaggedTimeType) 242 if rangeStartErr != nil { 243 return nil, index.Query{}, index.QueryOptions{}, false, rangeStartErr 244 } 245 246 end, rangeEndErr := ToTime(req.RangeEnd, fetchTaggedTimeType) 247 if rangeEndErr != nil { 248 return nil, index.Query{}, index.QueryOptions{}, false, rangeEndErr 249 } 250 251 opts := index.QueryOptions{ 252 StartInclusive: start, 253 EndExclusive: end, 254 RequireExhaustive: req.RequireExhaustive, 255 RequireNoWait: req.RequireNoWait, 256 } 257 if l := req.SeriesLimit; l != nil { 258 opts.SeriesLimit = int(*l) 259 } 260 if l := req.DocsLimit; l != nil { 261 opts.DocsLimit = int(*l) 262 } 263 if len(req.Source) > 0 { 264 opts.Source = req.Source 265 } 266 267 q, err := idx.Unmarshal(req.Query) 268 if err != nil { 269 return nil, index.Query{}, index.QueryOptions{}, false, err 270 } 271 272 var ns ident.ID 273 if pools != nil { 274 nsBytes := pools.CheckedBytesWrapper().Get(req.NameSpace) 275 ns = pools.ID().BinaryID(nsBytes) 276 } else { 277 ns = ident.StringID(string(req.NameSpace)) 278 } 279 return ns, index.Query{Query: q}, opts, req.FetchData, nil 280 } 281 282 // ToRPCFetchTaggedRequest converts the Go `client/` types into rpc request type 283 // for FetchTaggedRequest. 284 func ToRPCFetchTaggedRequest( 285 ns ident.ID, 286 q index.Query, 287 opts index.QueryOptions, 288 fetchData bool, 289 ) (rpc.FetchTaggedRequest, error) { 290 rangeStart, tsErr := ToValue(opts.StartInclusive, fetchTaggedTimeType) 291 if tsErr != nil { 292 return rpc.FetchTaggedRequest{}, tsErr 293 } 294 295 rangeEnd, tsErr := ToValue(opts.EndExclusive, fetchTaggedTimeType) 296 if tsErr != nil { 297 return rpc.FetchTaggedRequest{}, tsErr 298 } 299 300 query, queryErr := idx.Marshal(q.Query) 301 if queryErr != nil { 302 return rpc.FetchTaggedRequest{}, queryErr 303 } 304 305 request := rpc.FetchTaggedRequest{ 306 NameSpace: ns.Bytes(), 307 RangeStart: rangeStart, 308 RangeEnd: rangeEnd, 309 FetchData: fetchData, 310 Query: query, 311 RequireExhaustive: opts.RequireExhaustive, 312 RequireNoWait: opts.RequireNoWait, 313 } 314 315 if opts.SeriesLimit > 0 { 316 l := int64(opts.SeriesLimit) 317 request.SeriesLimit = &l 318 } 319 320 if opts.DocsLimit > 0 { 321 l := int64(opts.DocsLimit) 322 request.DocsLimit = &l 323 } 324 325 if len(opts.Source) > 0 { 326 request.Source = opts.Source 327 } 328 329 return request, nil 330 } 331 332 // FromRPCAggregateQueryRequest converts the rpc request type for AggregateRawQueryRequest into corresponding Go API types. 333 func FromRPCAggregateQueryRequest( 334 req *rpc.AggregateQueryRequest, 335 ) (ident.ID, index.Query, index.AggregationOptions, error) { 336 start, rangeStartErr := ToTime(req.RangeStart, fetchTaggedTimeType) 337 if rangeStartErr != nil { 338 return nil, index.Query{}, index.AggregationOptions{}, rangeStartErr 339 } 340 341 end, rangeEndErr := ToTime(req.RangeEnd, fetchTaggedTimeType) 342 if rangeEndErr != nil { 343 return nil, index.Query{}, index.AggregationOptions{}, rangeEndErr 344 } 345 346 opts := index.AggregationOptions{ 347 QueryOptions: index.QueryOptions{ 348 StartInclusive: start, 349 EndExclusive: end, 350 }, 351 } 352 if l := req.SeriesLimit; l != nil { 353 opts.SeriesLimit = int(*l) 354 } 355 if l := req.DocsLimit; l != nil { 356 opts.DocsLimit = int(*l) 357 } 358 if r := req.RequireExhaustive; r != nil { 359 opts.RequireExhaustive = *r 360 } 361 if r := req.RequireNoWait; r != nil { 362 opts.RequireNoWait = *r 363 } 364 365 if len(req.Source) > 0 { 366 opts.Source = req.Source 367 } 368 369 query, err := FromRPCQuery(req.Query) 370 if err != nil { 371 return nil, index.Query{}, index.AggregationOptions{}, err 372 } 373 374 opts.FieldFilter = make(index.AggregateFieldFilter, 0, len(req.TagNameFilter)) 375 for _, f := range req.TagNameFilter { 376 opts.FieldFilter = append(opts.FieldFilter, []byte(f)) 377 } 378 379 if req.AggregateQueryType == rpc.AggregateQueryType_AGGREGATE_BY_TAG_NAME_VALUE { 380 opts.Type = index.AggregateTagNamesAndValues 381 } else { 382 opts.Type = index.AggregateTagNames 383 } 384 385 ns := ident.StringID(req.NameSpace) 386 return ns, index.Query{Query: query}, opts, nil 387 } 388 389 // FromRPCAggregateQueryRawRequest converts the rpc request type for AggregateRawQueryRequest into corresponding Go API types. 390 func FromRPCAggregateQueryRawRequest( 391 req *rpc.AggregateQueryRawRequest, 392 pools FetchTaggedConversionPools, 393 ) (ident.ID, index.Query, index.AggregationOptions, error) { 394 start, rangeStartErr := ToTime(req.RangeStart, fetchTaggedTimeType) 395 if rangeStartErr != nil { 396 return nil, index.Query{}, index.AggregationOptions{}, rangeStartErr 397 } 398 399 end, rangeEndErr := ToTime(req.RangeEnd, fetchTaggedTimeType) 400 if rangeEndErr != nil { 401 return nil, index.Query{}, index.AggregationOptions{}, rangeEndErr 402 } 403 404 opts := index.AggregationOptions{ 405 QueryOptions: index.QueryOptions{ 406 StartInclusive: start, 407 EndExclusive: end, 408 }, 409 } 410 if l := req.SeriesLimit; l != nil { 411 opts.SeriesLimit = int(*l) 412 } 413 if l := req.DocsLimit; l != nil { 414 opts.DocsLimit = int(*l) 415 } 416 if r := req.RequireExhaustive; r != nil { 417 opts.RequireExhaustive = *r 418 } 419 if r := req.RequireNoWait; r != nil { 420 opts.RequireNoWait = *r 421 } 422 423 if len(req.Source) > 0 { 424 opts.Source = req.Source 425 } 426 427 query, err := idx.Unmarshal(req.Query) 428 if err != nil { 429 return nil, index.Query{}, index.AggregationOptions{}, err 430 } 431 432 opts.FieldFilter = index.AggregateFieldFilter(req.TagNameFilter) 433 if req.AggregateQueryType == rpc.AggregateQueryType_AGGREGATE_BY_TAG_NAME_VALUE { 434 opts.Type = index.AggregateTagNamesAndValues 435 } else { 436 opts.Type = index.AggregateTagNames 437 } 438 439 var ns ident.ID 440 if pools != nil { 441 nsBytes := pools.CheckedBytesWrapper().Get(req.NameSpace) 442 ns = pools.ID().BinaryID(nsBytes) 443 } else { 444 ns = ident.StringID(string(req.NameSpace)) 445 } 446 return ns, index.Query{Query: query}, opts, nil 447 } 448 449 // ToRPCAggregateQueryRawRequest converts the Go `client/` types into rpc 450 // request type for AggregateQueryRawRequest. 451 func ToRPCAggregateQueryRawRequest( 452 ns ident.ID, 453 q index.Query, 454 opts index.AggregationOptions, 455 ) (rpc.AggregateQueryRawRequest, error) { 456 rangeStart, tsErr := ToValue(opts.StartInclusive, fetchTaggedTimeType) 457 if tsErr != nil { 458 return rpc.AggregateQueryRawRequest{}, tsErr 459 } 460 461 rangeEnd, tsErr := ToValue(opts.EndExclusive, fetchTaggedTimeType) 462 if tsErr != nil { 463 return rpc.AggregateQueryRawRequest{}, tsErr 464 } 465 466 request := rpc.AggregateQueryRawRequest{ 467 NameSpace: ns.Bytes(), 468 RangeStart: rangeStart, 469 RangeEnd: rangeEnd, 470 } 471 472 if opts.SeriesLimit > 0 { 473 l := int64(opts.SeriesLimit) 474 request.SeriesLimit = &l 475 } 476 if opts.DocsLimit > 0 { 477 l := int64(opts.DocsLimit) 478 request.DocsLimit = &l 479 } 480 if opts.RequireExhaustive { 481 r := opts.RequireExhaustive 482 request.RequireExhaustive = &r 483 } 484 if opts.RequireNoWait { 485 r := opts.RequireNoWait 486 request.RequireNoWait = &r 487 } 488 489 if len(opts.Source) > 0 { 490 request.Source = opts.Source 491 } 492 493 query, queryErr := idx.Marshal(q.Query) 494 if queryErr != nil { 495 return rpc.AggregateQueryRawRequest{}, queryErr 496 } 497 request.Query = query 498 499 if opts.Type == index.AggregateTagNamesAndValues { 500 request.AggregateQueryType = rpc.AggregateQueryType_AGGREGATE_BY_TAG_NAME_VALUE 501 } else { 502 request.AggregateQueryType = rpc.AggregateQueryType_AGGREGATE_BY_TAG_NAME 503 } 504 505 // TODO(prateek): pool the []byte underlying opts.FieldFilter 506 filters := make([][]byte, 0, len(opts.FieldFilter)) 507 for _, f := range opts.FieldFilter { 508 copied := append([]byte(nil), f...) 509 filters = append(filters, copied) 510 } 511 request.TagNameFilter = filters 512 513 return request, nil 514 } 515 516 // ToTagsIter returns a tag iterator over the given request. 517 func ToTagsIter(r *rpc.WriteTaggedRequest) (ident.TagIterator, error) { 518 if r == nil { 519 return nil, errNilTaggedRequest 520 } 521 522 return &writeTaggedIter{ 523 rawRequest: r, 524 currentIdx: -1, 525 }, nil 526 } 527 528 // NB(prateek): writeTaggedIter is in-efficient in how it handles internal 529 // allocations. Only use it for non-performance critical RPC endpoints. 530 type writeTaggedIter struct { 531 rawRequest *rpc.WriteTaggedRequest 532 currentIdx int 533 currentTag ident.Tag 534 } 535 536 func (w *writeTaggedIter) Next() bool { 537 w.release() 538 w.currentIdx++ 539 if w.currentIdx < len(w.rawRequest.Tags) { 540 w.currentTag.Name = ident.StringID(w.rawRequest.Tags[w.currentIdx].Name) 541 w.currentTag.Value = ident.StringID(w.rawRequest.Tags[w.currentIdx].Value) 542 return true 543 } 544 return false 545 } 546 547 func (w *writeTaggedIter) release() { 548 if i := w.currentTag.Name; i != nil { 549 w.currentTag.Name.Finalize() 550 w.currentTag.Name = nil 551 } 552 if i := w.currentTag.Value; i != nil { 553 w.currentTag.Value.Finalize() 554 w.currentTag.Value = nil 555 } 556 } 557 558 func (w *writeTaggedIter) Current() ident.Tag { 559 return w.currentTag 560 } 561 562 func (w *writeTaggedIter) CurrentIndex() int { 563 if w.currentIdx >= 0 { 564 return w.currentIdx 565 } 566 return 0 567 } 568 569 func (w *writeTaggedIter) Err() error { 570 return nil 571 } 572 573 func (w *writeTaggedIter) Close() { 574 w.release() 575 w.currentIdx = -1 576 } 577 578 func (w *writeTaggedIter) Len() int { 579 return len(w.rawRequest.Tags) 580 } 581 582 func (w *writeTaggedIter) Remaining() int { 583 if r := len(w.rawRequest.Tags) - 1 - w.currentIdx; r >= 0 { 584 return r 585 } 586 return 0 587 } 588 589 func (w *writeTaggedIter) Duplicate() ident.TagIterator { 590 return &writeTaggedIter{ 591 rawRequest: w.rawRequest, 592 currentIdx: -1, 593 } 594 } 595 596 func (w *writeTaggedIter) Rewind() { 597 w.release() 598 w.currentIdx = -1 599 } 600 601 // FromRPCQuery will create a m3ninx index query from an RPC query. 602 // NB: a nil query is considered equivalent to an `All` query. 603 func FromRPCQuery(query *rpc.Query) (idx.Query, error) { 604 if query == nil { 605 return idx.NewAllQuery(), nil 606 } 607 608 queryProto, err := parseQuery(query) 609 if err != nil { 610 return idx.Query{}, err 611 } 612 613 marshalled, err := queryProto.Marshal() 614 if err != nil { 615 return idx.Query{}, err 616 } 617 618 return idx.Unmarshal(marshalled) 619 } 620 621 func parseQuery(query *rpc.Query) (*querypb.Query, error) { 622 result := new(querypb.Query) 623 if query == nil { 624 return nil, xerrors.NewInvalidParamsError(fmt.Errorf("no query specified")) 625 } 626 if query.All != nil { 627 result.Query = &querypb.Query_All{ 628 All: &querypb.AllQuery{}, 629 } 630 } 631 if query.Field != nil { 632 result.Query = &querypb.Query_Field{ 633 Field: &querypb.FieldQuery{ 634 Field: []byte(query.Field.Field), 635 }, 636 } 637 } 638 if query.Term != nil { 639 result.Query = &querypb.Query_Term{ 640 Term: &querypb.TermQuery{ 641 Field: []byte(query.Term.Field), 642 Term: []byte(query.Term.Term), 643 }, 644 } 645 } 646 if query.Regexp != nil { 647 if result.Query != nil { 648 return nil, xerrors.NewInvalidParamsError(fmt.Errorf("multiple query types specified")) 649 } 650 result.Query = &querypb.Query_Regexp{ 651 Regexp: &querypb.RegexpQuery{ 652 Field: []byte(query.Regexp.Field), 653 Regexp: []byte(query.Regexp.Regexp), 654 }, 655 } 656 } 657 if query.Negation != nil { 658 if result.Query != nil { 659 return nil, xerrors.NewInvalidParamsError(fmt.Errorf("multiple query types specified")) 660 } 661 inner, err := parseQuery(query.Negation.Query) 662 if err != nil { 663 return nil, err 664 } 665 result.Query = &querypb.Query_Negation{ 666 Negation: &querypb.NegationQuery{ 667 Query: inner, 668 }, 669 } 670 } 671 if query.Conjunction != nil { 672 if result.Query != nil { 673 return nil, xerrors.NewInvalidParamsError(fmt.Errorf("multiple query types specified")) 674 } 675 var queries []*querypb.Query 676 for _, query := range query.Conjunction.Queries { 677 inner, err := parseQuery(query) 678 if err != nil { 679 return nil, err 680 } 681 queries = append(queries, inner) 682 } 683 result.Query = &querypb.Query_Conjunction{ 684 Conjunction: &querypb.ConjunctionQuery{ 685 Queries: queries, 686 }, 687 } 688 } 689 if query.Disjunction != nil { 690 if result.Query != nil { 691 return nil, xerrors.NewInvalidParamsError(fmt.Errorf("multiple query types specified")) 692 } 693 var queries []*querypb.Query 694 for _, query := range query.Disjunction.Queries { 695 inner, err := parseQuery(query) 696 if err != nil { 697 return nil, err 698 } 699 queries = append(queries, inner) 700 } 701 result.Query = &querypb.Query_Disjunction{ 702 Disjunction: &querypb.DisjunctionQuery{ 703 Queries: queries, 704 }, 705 } 706 } 707 if result.Query == nil { 708 return nil, xerrors.NewInvalidParamsError(fmt.Errorf("no query types specified")) 709 } 710 return result, nil 711 }