From d9b6af35bb29d01f023f88d0986b22a1f771c8d9 Mon Sep 17 00:00:00 2001 From: Ruben Vargas Date: Wed, 13 Dec 2023 13:10:22 -0600 Subject: [PATCH] Rename some methods to be more consistent Signed-off-by: Ruben Vargas --- modules/frontend/frontend_test.go | 2 +- modules/frontend/searchsharding_test.go | 8 +- modules/frontend/tagsharding_handlers.go | 2 +- modules/frontend/tagsharding_handlers_test.go | 2 +- modules/querier/querier.go | 8 +- pkg/api/http.go | 438 ----------------- pkg/api/search_tags.go | 441 ++++++++++++++++++ pkg/util/test/req.go | 8 +- tempodb/tempodb.go | 14 +- tempodb/tempodb_test.go | 136 +++--- 10 files changed, 542 insertions(+), 517 deletions(-) diff --git a/modules/frontend/frontend_test.go b/modules/frontend/frontend_test.go index 2fac51dd648..f16d40e4247 100644 --- a/modules/frontend/frontend_test.go +++ b/modules/frontend/frontend_test.go @@ -70,7 +70,7 @@ func TestFrontendRoundTripsTagSearch(t *testing.T) { }, SLO: testSLOcfg, }, - }, next, nil, nil, "", log.NewNopLogger(), nil) + }, next, nil, nil, nil, "", log.NewNopLogger(), nil) require.NoError(t, err) req := httptest.NewRequest("GET", "/", nil) diff --git a/modules/frontend/searchsharding_test.go b/modules/frontend/searchsharding_test.go index b3499ea0f84..afa31805c03 100644 --- a/modules/frontend/searchsharding_test.go +++ b/modules/frontend/searchsharding_test.go @@ -42,19 +42,19 @@ type mockReader struct { metas []*backend.BlockMeta } -func (m *mockReader) SearchForTags(context.Context, *backend.BlockMeta, string, common.SearchOptions) (*tempopb.SearchTagsResponse, error) { +func (m *mockReader) SearchTags(context.Context, *backend.BlockMeta, string, common.SearchOptions) (*tempopb.SearchTagsResponse, error) { return nil, nil } -func (m *mockReader) SearchForTagValues(context.Context, *backend.BlockMeta, string, common.SearchOptions) ([]string, error) { +func (m *mockReader) SearchTagValues(context.Context, *backend.BlockMeta, string, common.SearchOptions) ([]string, error) { return nil, nil } -func (m *mockReader) SearchForTagsV2(context.Context, *backend.BlockMeta, []string, common.SearchOptions) (*tempopb.SearchTagsV2Response, error) { +func (m *mockReader) SearchTagsV2(context.Context, *backend.BlockMeta, []string, common.SearchOptions) (*tempopb.SearchTagsV2Response, error) { return nil, nil } -func (m *mockReader) SearchForTagValuesV2(context.Context, *backend.BlockMeta, *tempopb.SearchTagValuesRequest, common.SearchOptions) (*tempopb.SearchTagValuesV2Response, error) { +func (m *mockReader) SearchTagValuesV2(context.Context, *backend.BlockMeta, *tempopb.SearchTagValuesRequest, common.SearchOptions) (*tempopb.SearchTagValuesV2Response, error) { return nil, nil } diff --git a/modules/frontend/tagsharding_handlers.go b/modules/frontend/tagsharding_handlers.go index f64b770b49f..5f6fd63cbef 100644 --- a/modules/frontend/tagsharding_handlers.go +++ b/modules/frontend/tagsharding_handlers.go @@ -35,7 +35,7 @@ func (r *tagsSearchRequest) adjustRange(start, end uint32) tagSearchReq { } func (r *tagsSearchRequest) buildSearchTagRequest(subR *http.Request) (*http.Request, error) { - return api.BuildSearchTagRequest(subR, &r.request) + return api.BuildSearchTagsRequest(subR, &r.request) } func (r *tagsSearchRequest) buildTagSearchBlockRequest(subR *http.Request, blockID string, diff --git a/modules/frontend/tagsharding_handlers_test.go b/modules/frontend/tagsharding_handlers_test.go index 263caf1c087..36cd378f3f9 100644 --- a/modules/frontend/tagsharding_handlers_test.go +++ b/modules/frontend/tagsharding_handlers_test.go @@ -48,7 +48,7 @@ func TestTagsResultsHandler(t *testing.T) { result2: "{ \"tagNames\":[\"tag2\",\"tag3\"]}", expectedResult: "{\"tagNames\":[\"tag1\",\"tag2\",\"tag3\"]}", expectedReq: func(r *http.Request) *http.Request { - expectedReq, _ := api.BuildSearchTagRequest(r, &tempopb.SearchTagsRequest{ + expectedReq, _ := api.BuildSearchTagsRequest(r, &tempopb.SearchTagsRequest{ Scope: "all", Start: start, End: end, diff --git a/modules/querier/querier.go b/modules/querier/querier.go index 134cbf88006..6911bfc0726 100644 --- a/modules/querier/querier.go +++ b/modules/querier/querier.go @@ -874,7 +874,7 @@ func (q *Querier) internalTagSearchBlock(ctx context.Context, req *tempopb.Searc opts.TotalPages = int(req.PagesToSearch) opts.MaxBytes = q.limits.MaxBytesPerTrace(tenantID) - return q.store.SearchForTags(ctx, meta, req.SearchReq.Scope, opts) + return q.store.SearchTags(ctx, meta, req.SearchReq.Scope, opts) } func (q *Querier) internalTagSearchBlockV2(ctx context.Context, req *tempopb.SearchTagsBlockRequest) (*tempopb.SearchTagsV2Response, error) { @@ -883,7 +883,7 @@ func (q *Querier) internalTagSearchBlockV2(ctx context.Context, req *tempopb.Sea if req.SearchReq.Scope == "" { // start with intrinsic scope and all traceql attribute scopes atts := traceql.AllAttributeScopes() - scopes = make([]string, 0, len(atts)) // +1 for intrinsic + scopes = make([]string, 0, len(atts)+1) // +1 for intrinsic scopes = append(scopes, api.ParamScopeIntrinsic) for _, att := range atts { scopes = append(scopes, att.String()) @@ -969,7 +969,7 @@ func (q *Querier) internalTagValuesSearchBlock(ctx context.Context, req *tempopb opts.MaxBytes = q.limits.MaxBytesPerTrace(tenantID) if !v2 { - tags, err := q.store.SearchForTagValues(ctx, meta, req.SearchReq.TagName, opts) + tags, err := q.store.SearchTagValues(ctx, meta, req.SearchReq.TagName, opts) if err != nil { return nil, err } @@ -979,7 +979,7 @@ func (q *Querier) internalTagValuesSearchBlock(ctx context.Context, req *tempopb }, }, nil } - resp, err := q.store.SearchForTagValuesV2(ctx, meta, req.SearchReq, opts) + resp, err := q.store.SearchTagValuesV2(ctx, meta, req.SearchReq, opts) if err != nil { return nil, err } diff --git a/pkg/api/http.go b/pkg/api/http.go index 6fa7d2fef74..f86d10e4d30 100644 --- a/pkg/api/http.go +++ b/pkg/api/http.go @@ -18,7 +18,6 @@ import ( "github.com/grafana/tempo/pkg/traceql" "github.com/grafana/tempo/pkg/util" "github.com/grafana/tempo/tempodb" - "github.com/grafana/tempo/tempodb/backend" ) const ( @@ -243,343 +242,6 @@ func ParseSearchRequest(r *http.Request) (*tempopb.SearchRequest, error) { return req, nil } -// ParseSearchBlockRequest parses all http parameters necessary to perform a block search. -func ParseSearchBlockRequest(r *http.Request) (*tempopb.SearchBlockRequest, error) { - searchReq, err := ParseSearchRequest(r) - if err != nil { - return nil, err - } - - // start and end = 0 is NOT fine for a block search request - if searchReq.End == 0 { - return nil, errors.New("start and end required") - } - - req := &tempopb.SearchBlockRequest{ - SearchReq: searchReq, - } - - s := r.URL.Query().Get(urlParamStartPage) - startPage, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid startPage: %w", err) - } - if startPage < 0 { - return nil, fmt.Errorf("startPage must be non-negative. received: %s", s) - } - req.StartPage = uint32(startPage) - - s = r.URL.Query().Get(urlParamPagesToSearch) - pagesToSearch64, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid pagesToSearch %s: %w", s, err) - } - if pagesToSearch64 <= 0 { - return nil, fmt.Errorf("pagesToSearch must be greater than 0. received: %s", s) - } - req.PagesToSearch = uint32(pagesToSearch64) - - s = r.URL.Query().Get(urlParamBlockID) - blockID, err := uuid.Parse(s) - if err != nil { - return nil, fmt.Errorf("invalid blockID: %w", err) - } - req.BlockID = blockID.String() - - s = r.URL.Query().Get(urlParamEncoding) - encoding, err := backend.ParseEncoding(s) - if err != nil { - return nil, err - } - req.Encoding = encoding.String() - - s = r.URL.Query().Get(urlParamIndexPageSize) - indexPageSize, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid indexPageSize %s: %w", s, err) - } - req.IndexPageSize = uint32(indexPageSize) - - s = r.URL.Query().Get(urlParamTotalRecords) - totalRecords, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid totalRecords %s: %w", s, err) - } - if totalRecords <= 0 { - return nil, fmt.Errorf("totalRecords must be greater than 0. received %d", totalRecords) - } - req.TotalRecords = uint32(totalRecords) - - // Data encoding can be blank for some block formats, therefore - // no validation on the param here. Eventually we may be able - // to remove this parameter entirely. - dataEncoding := r.URL.Query().Get(urlParamDataEncoding) - req.DataEncoding = dataEncoding - - version := r.URL.Query().Get(urlParamVersion) - if version == "" { - return nil, errors.New("version required") - } - req.Version = version - - s = r.URL.Query().Get(urlParamSize) - size, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid size %s: %w", s, err) - } - req.Size_ = size - - // Footer size can be 0 for some blocks, just ensure we - // get a valid integer. - f := r.URL.Query().Get(urlParamFooterSize) - footerSize, err := strconv.ParseUint(f, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid footerSize %s: %w", f, err) - } - req.FooterSize = uint32(footerSize) - - s = r.URL.Query().Get(urlParamDedicatedColumns) - if s != "" { - var dedicatedColumns []*tempopb.DedicatedColumn - err = json.Unmarshal([]byte(s), &dedicatedColumns) - if err != nil { - return nil, fmt.Errorf("invalid dedicatedColumns '%s': %w", s, err) - } - req.DedicatedColumns = dedicatedColumns - } - - return req, nil -} - -func ParseSearchTagValuesBlockRequest(r *http.Request) (*tempopb.SearchTagValuesBlockRequest, error) { - return parseSearchTagValuesBlockRequest(r, false) -} - -func ParseSearchTagValuesBlockRequestV2(r *http.Request) (*tempopb.SearchTagValuesBlockRequest, error) { - return parseSearchTagValuesBlockRequest(r, true) -} - -func parseSearchTagValuesBlockRequest(r *http.Request, enforceTraceQL bool) (*tempopb.SearchTagValuesBlockRequest, error) { - var tagSearchReq *tempopb.SearchTagValuesRequest - var err error - if !enforceTraceQL { - tagSearchReq, err = ParseSearchTagValuesRequest(r) - } else { - tagSearchReq, err = ParseSearchTagValuesRequestV2(r) - } - - if err != nil { - return nil, err - } - - // start and end = 0 is NOT fine for a block search request - if tagSearchReq.End == 0 { - return nil, errors.New("start and end required") - } - - req := &tempopb.SearchTagValuesBlockRequest{ - SearchReq: tagSearchReq, - } - - s := r.URL.Query().Get(urlParamStartPage) - startPage, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid startPage: %w", err) - } - if startPage < 0 { - return nil, fmt.Errorf("startPage must be non-negative. received: %s", s) - } - req.StartPage = uint32(startPage) - - s = r.URL.Query().Get(urlParamPagesToSearch) - pagesToSearch64, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid pagesToSearch %s: %w", s, err) - } - if pagesToSearch64 <= 0 { - return nil, fmt.Errorf("pagesToSearch must be greater than 0. received: %s", s) - } - req.PagesToSearch = uint32(pagesToSearch64) - - s = r.URL.Query().Get(urlParamBlockID) - blockID, err := uuid.Parse(s) - if err != nil { - return nil, fmt.Errorf("invalid blockID: %w", err) - } - req.BlockID = blockID.String() - - s = r.URL.Query().Get(urlParamEncoding) - encoding, err := backend.ParseEncoding(s) - if err != nil { - return nil, err - } - req.Encoding = encoding.String() - - s = r.URL.Query().Get(urlParamIndexPageSize) - indexPageSize, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid indexPageSize %s: %w", s, err) - } - req.IndexPageSize = uint32(indexPageSize) - - s = r.URL.Query().Get(urlParamTotalRecords) - totalRecords, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid totalRecords %s: %w", s, err) - } - if totalRecords <= 0 { - return nil, fmt.Errorf("totalRecords must be greater than 0. received %d", totalRecords) - } - req.TotalRecords = uint32(totalRecords) - - // Data encoding can be blank for some block formats, therefore - // no validation on the param here. Eventually we may be able - // to remove this parameter entirely. - dataEncoding := r.URL.Query().Get(urlParamDataEncoding) - req.DataEncoding = dataEncoding - - version := r.URL.Query().Get(urlParamVersion) - if version == "" { - return nil, errors.New("version required") - } - req.Version = version - - s = r.URL.Query().Get(urlParamSize) - size, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid size %s: %w", s, err) - } - req.Size_ = size - - // Footer size can be 0 for some blocks, just ensure we - // get a valid integer. - f := r.URL.Query().Get(urlParamFooterSize) - footerSize, err := strconv.ParseUint(f, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid footerSize %s: %w", f, err) - } - req.FooterSize = uint32(footerSize) - - s = r.URL.Query().Get(urlParamDedicatedColumns) - if s != "" { - var dedicatedColumns []*tempopb.DedicatedColumn - err = json.Unmarshal([]byte(s), &dedicatedColumns) - if err != nil { - return nil, fmt.Errorf("invalid dedicatedColumns '%s': %w", s, err) - } - req.DedicatedColumns = dedicatedColumns - } - - return req, nil -} - -func ParseSearchTagsBlockRequest(r *http.Request) (*tempopb.SearchTagsBlockRequest, error) { - tagSearchReq, err := ParseSearchTagsRequest(r) - if err != nil { - return nil, err - } - - // start and end = 0 is NOT fine for a block search request - if tagSearchReq.End == 0 { - return nil, errors.New("start and end required") - } - - req := &tempopb.SearchTagsBlockRequest{ - SearchReq: tagSearchReq, - } - - s := r.URL.Query().Get(urlParamStartPage) - startPage, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid startPage: %w", err) - } - if startPage < 0 { - return nil, fmt.Errorf("startPage must be non-negative. received: %s", s) - } - req.StartPage = uint32(startPage) - - s = r.URL.Query().Get(urlParamPagesToSearch) - pagesToSearch64, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid pagesToSearch %s: %w", s, err) - } - if pagesToSearch64 <= 0 { - return nil, fmt.Errorf("pagesToSearch must be greater than 0. received: %s", s) - } - req.PagesToSearch = uint32(pagesToSearch64) - - s = r.URL.Query().Get(urlParamBlockID) - blockID, err := uuid.Parse(s) - if err != nil { - return nil, fmt.Errorf("invalid blockID: %w", err) - } - req.BlockID = blockID.String() - - s = r.URL.Query().Get(urlParamEncoding) - encoding, err := backend.ParseEncoding(s) - if err != nil { - return nil, err - } - req.Encoding = encoding.String() - - s = r.URL.Query().Get(urlParamIndexPageSize) - indexPageSize, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid indexPageSize %s: %w", s, err) - } - req.IndexPageSize = uint32(indexPageSize) - - s = r.URL.Query().Get(urlParamTotalRecords) - totalRecords, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid totalRecords %s: %w", s, err) - } - if totalRecords <= 0 { - return nil, fmt.Errorf("totalRecords must be greater than 0. received %d", totalRecords) - } - req.TotalRecords = uint32(totalRecords) - - // Data encoding can be blank for some block formats, therefore - // no validation on the param here. Eventually we may be able - // to remove this parameter entirely. - dataEncoding := r.URL.Query().Get(urlParamDataEncoding) - req.DataEncoding = dataEncoding - - version := r.URL.Query().Get(urlParamVersion) - if version == "" { - return nil, errors.New("version required") - } - req.Version = version - - s = r.URL.Query().Get(urlParamSize) - size, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid size %s: %w", s, err) - } - req.Size_ = size - - // Footer size can be 0 for some blocks, just ensure we - // get a valid integer. - f := r.URL.Query().Get(urlParamFooterSize) - footerSize, err := strconv.ParseUint(f, 10, 32) - if err != nil { - return nil, fmt.Errorf("invalid footerSize %s: %w", f, err) - } - req.FooterSize = uint32(footerSize) - - s = r.URL.Query().Get(urlParamDedicatedColumns) - if s != "" { - var dedicatedColumns []*tempopb.DedicatedColumn - err = json.Unmarshal([]byte(s), &dedicatedColumns) - if err != nil { - return nil, fmt.Errorf("invalid dedicatedColumns '%s': %w", s, err) - } - req.DedicatedColumns = dedicatedColumns - } - - return req, nil -} - func ParseSpanMetricsRequest(r *http.Request) (*tempopb.SpanMetricsRequest, error) { req := &tempopb.SpanMetricsRequest{} @@ -706,106 +368,6 @@ func BuildSearchRequest(req *http.Request, searchReq *tempopb.SearchRequest) (*h return req, nil } -func BuildSearchTagRequest(req *http.Request, searchReq *tempopb.SearchTagsRequest) (*http.Request, error) { - if req == nil { - req = &http.Request{ - URL: &url.URL{}, - } - } - - if searchReq == nil { - return req, nil - } - - q := req.URL.Query() - q.Set(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) - q.Set(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) - q.Set(urlParamScope, searchReq.Scope) - - req.URL.RawQuery = q.Encode() - - return req, nil -} - -func BuildSearchTagsBlockRequest(req *http.Request, searchReq *tempopb.SearchTagsBlockRequest) (*http.Request, error) { - if req == nil { - req = &http.Request{ - URL: &url.URL{}, - } - } - - req, err := BuildSearchTagRequest(req, searchReq.SearchReq) - if err != nil { - return nil, err - } - - q := req.URL.Query() - q.Set(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) - q.Set(urlParamBlockID, searchReq.BlockID) - q.Set(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) - q.Set(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) - q.Set(urlParamEncoding, searchReq.Encoding) - q.Set(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) - q.Set(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) - q.Set(urlParamDataEncoding, searchReq.DataEncoding) - q.Set(urlParamVersion, searchReq.Version) - q.Set(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) - - req.URL.RawQuery = q.Encode() - - return req, nil -} - -func BuildSearchTagValuesRequest(req *http.Request, searchReq *tempopb.SearchTagValuesRequest) (*http.Request, error) { - if req == nil { - req = &http.Request{ - URL: &url.URL{}, - } - } - - if searchReq == nil { - return req, nil - } - - q := req.URL.Query() - q.Set(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) - q.Set(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) - q.Set(urlParamQuery, searchReq.Query) - - req.URL.RawQuery = q.Encode() - - return req, nil -} - -func BuildSearchTagValuesBlockRequest(req *http.Request, searchReq *tempopb.SearchTagValuesBlockRequest) (*http.Request, error) { - if req == nil { - req = &http.Request{ - URL: &url.URL{}, - } - } - - req, err := BuildSearchTagValuesRequest(req, searchReq.SearchReq) - if err != nil { - return nil, err - } - - q := req.URL.Query() - q.Set(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) - q.Set(urlParamBlockID, searchReq.BlockID) - q.Set(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) - q.Set(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) - q.Set(urlParamEncoding, searchReq.Encoding) - q.Set(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) - q.Set(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) - q.Set(urlParamDataEncoding, searchReq.DataEncoding) - q.Set(urlParamVersion, searchReq.Version) - q.Set(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) - - req.URL.RawQuery = q.Encode() - - return req, nil -} - // BuildSearchBlockRequest takes a tempopb.SearchBlockRequest and populates the passed http.Request // with the appropriate params. If no http.Request is provided a new one is created. func BuildSearchBlockRequest(req *http.Request, searchReq *tempopb.SearchBlockRequest) (*http.Request, error) { diff --git a/pkg/api/search_tags.go b/pkg/api/search_tags.go index 1eb2b6989ec..bf8fc1922d7 100644 --- a/pkg/api/search_tags.go +++ b/pkg/api/search_tags.go @@ -1,15 +1,19 @@ package api import ( + "encoding/json" "errors" "fmt" "net/http" "net/url" "strconv" + "github.com/google/uuid" "github.com/gorilla/mux" + "github.com/grafana/tempo/pkg/tempopb" "github.com/grafana/tempo/pkg/traceql" + "github.com/grafana/tempo/tempodb/backend" ) const ( @@ -18,6 +22,343 @@ const ( ParamScopeIntrinsic = "intrinsic" ) +// ParseSearchBlockRequest parses all http parameters necessary to perform a block search. +func ParseSearchBlockRequest(r *http.Request) (*tempopb.SearchBlockRequest, error) { + searchReq, err := ParseSearchRequest(r) + if err != nil { + return nil, err + } + + // start and end = 0 is NOT fine for a block search request + if searchReq.End == 0 { + return nil, errors.New("start and end required") + } + + req := &tempopb.SearchBlockRequest{ + SearchReq: searchReq, + } + + s := r.URL.Query().Get(urlParamStartPage) + startPage, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid startPage: %w", err) + } + if startPage < 0 { + return nil, fmt.Errorf("startPage must be non-negative. received: %s", s) + } + req.StartPage = uint32(startPage) + + s = r.URL.Query().Get(urlParamPagesToSearch) + pagesToSearch64, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid pagesToSearch %s: %w", s, err) + } + if pagesToSearch64 <= 0 { + return nil, fmt.Errorf("pagesToSearch must be greater than 0. received: %s", s) + } + req.PagesToSearch = uint32(pagesToSearch64) + + s = r.URL.Query().Get(urlParamBlockID) + blockID, err := uuid.Parse(s) + if err != nil { + return nil, fmt.Errorf("invalid blockID: %w", err) + } + req.BlockID = blockID.String() + + s = r.URL.Query().Get(urlParamEncoding) + encoding, err := backend.ParseEncoding(s) + if err != nil { + return nil, err + } + req.Encoding = encoding.String() + + s = r.URL.Query().Get(urlParamIndexPageSize) + indexPageSize, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid indexPageSize %s: %w", s, err) + } + req.IndexPageSize = uint32(indexPageSize) + + s = r.URL.Query().Get(urlParamTotalRecords) + totalRecords, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid totalRecords %s: %w", s, err) + } + if totalRecords <= 0 { + return nil, fmt.Errorf("totalRecords must be greater than 0. received %d", totalRecords) + } + req.TotalRecords = uint32(totalRecords) + + // Data encoding can be blank for some block formats, therefore + // no validation on the param here. Eventually we may be able + // to remove this parameter entirely. + dataEncoding := r.URL.Query().Get(urlParamDataEncoding) + req.DataEncoding = dataEncoding + + version := r.URL.Query().Get(urlParamVersion) + if version == "" { + return nil, errors.New("version required") + } + req.Version = version + + s = r.URL.Query().Get(urlParamSize) + size, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid size %s: %w", s, err) + } + req.Size_ = size + + // Footer size can be 0 for some blocks, just ensure we + // get a valid integer. + f := r.URL.Query().Get(urlParamFooterSize) + footerSize, err := strconv.ParseUint(f, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid footerSize %s: %w", f, err) + } + req.FooterSize = uint32(footerSize) + + s = r.URL.Query().Get(urlParamDedicatedColumns) + if s != "" { + var dedicatedColumns []*tempopb.DedicatedColumn + err = json.Unmarshal([]byte(s), &dedicatedColumns) + if err != nil { + return nil, fmt.Errorf("invalid dedicatedColumns '%s': %w", s, err) + } + req.DedicatedColumns = dedicatedColumns + } + + return req, nil +} + +func ParseSearchTagValuesBlockRequest(r *http.Request) (*tempopb.SearchTagValuesBlockRequest, error) { + return parseSearchTagValuesBlockRequest(r, false) +} + +func ParseSearchTagValuesBlockRequestV2(r *http.Request) (*tempopb.SearchTagValuesBlockRequest, error) { + return parseSearchTagValuesBlockRequest(r, true) +} + +func parseSearchTagValuesBlockRequest(r *http.Request, enforceTraceQL bool) (*tempopb.SearchTagValuesBlockRequest, error) { + var tagSearchReq *tempopb.SearchTagValuesRequest + var err error + if !enforceTraceQL { + tagSearchReq, err = ParseSearchTagValuesRequest(r) + } else { + tagSearchReq, err = ParseSearchTagValuesRequestV2(r) + } + + if err != nil { + return nil, err + } + + // start and end = 0 is NOT fine for a block search request + if tagSearchReq.End == 0 { + return nil, errors.New("start and end required") + } + + req := &tempopb.SearchTagValuesBlockRequest{ + SearchReq: tagSearchReq, + } + + s := r.URL.Query().Get(urlParamStartPage) + startPage, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid startPage: %w", err) + } + if startPage < 0 { + return nil, fmt.Errorf("startPage must be non-negative. received: %s", s) + } + req.StartPage = uint32(startPage) + + s = r.URL.Query().Get(urlParamPagesToSearch) + pagesToSearch64, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid pagesToSearch %s: %w", s, err) + } + if pagesToSearch64 <= 0 { + return nil, fmt.Errorf("pagesToSearch must be greater than 0. received: %s", s) + } + req.PagesToSearch = uint32(pagesToSearch64) + + s = r.URL.Query().Get(urlParamBlockID) + blockID, err := uuid.Parse(s) + if err != nil { + return nil, fmt.Errorf("invalid blockID: %w", err) + } + req.BlockID = blockID.String() + + s = r.URL.Query().Get(urlParamEncoding) + encoding, err := backend.ParseEncoding(s) + if err != nil { + return nil, err + } + req.Encoding = encoding.String() + + s = r.URL.Query().Get(urlParamIndexPageSize) + indexPageSize, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid indexPageSize %s: %w", s, err) + } + req.IndexPageSize = uint32(indexPageSize) + + s = r.URL.Query().Get(urlParamTotalRecords) + totalRecords, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid totalRecords %s: %w", s, err) + } + if totalRecords <= 0 { + return nil, fmt.Errorf("totalRecords must be greater than 0. received %d", totalRecords) + } + req.TotalRecords = uint32(totalRecords) + + // Data encoding can be blank for some block formats, therefore + // no validation on the param here. Eventually we may be able + // to remove this parameter entirely. + dataEncoding := r.URL.Query().Get(urlParamDataEncoding) + req.DataEncoding = dataEncoding + + version := r.URL.Query().Get(urlParamVersion) + if version == "" { + return nil, errors.New("version required") + } + req.Version = version + + s = r.URL.Query().Get(urlParamSize) + size, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid size %s: %w", s, err) + } + req.Size_ = size + + // Footer size can be 0 for some blocks, just ensure we + // get a valid integer. + f := r.URL.Query().Get(urlParamFooterSize) + footerSize, err := strconv.ParseUint(f, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid footerSize %s: %w", f, err) + } + req.FooterSize = uint32(footerSize) + + s = r.URL.Query().Get(urlParamDedicatedColumns) + if s != "" { + var dedicatedColumns []*tempopb.DedicatedColumn + err = json.Unmarshal([]byte(s), &dedicatedColumns) + if err != nil { + return nil, fmt.Errorf("invalid dedicatedColumns '%s': %w", s, err) + } + req.DedicatedColumns = dedicatedColumns + } + + return req, nil +} + +func ParseSearchTagsBlockRequest(r *http.Request) (*tempopb.SearchTagsBlockRequest, error) { + tagSearchReq, err := ParseSearchTagsRequest(r) + if err != nil { + return nil, err + } + + // start and end = 0 is NOT fine for a block search request + if tagSearchReq.End == 0 { + return nil, errors.New("start and end required") + } + + req := &tempopb.SearchTagsBlockRequest{ + SearchReq: tagSearchReq, + } + + s := r.URL.Query().Get(urlParamStartPage) + startPage, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid startPage: %w", err) + } + if startPage < 0 { + return nil, fmt.Errorf("startPage must be non-negative. received: %s", s) + } + req.StartPage = uint32(startPage) + + s = r.URL.Query().Get(urlParamPagesToSearch) + pagesToSearch64, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid pagesToSearch %s: %w", s, err) + } + if pagesToSearch64 <= 0 { + return nil, fmt.Errorf("pagesToSearch must be greater than 0. received: %s", s) + } + req.PagesToSearch = uint32(pagesToSearch64) + + s = r.URL.Query().Get(urlParamBlockID) + blockID, err := uuid.Parse(s) + if err != nil { + return nil, fmt.Errorf("invalid blockID: %w", err) + } + req.BlockID = blockID.String() + + s = r.URL.Query().Get(urlParamEncoding) + encoding, err := backend.ParseEncoding(s) + if err != nil { + return nil, err + } + req.Encoding = encoding.String() + + s = r.URL.Query().Get(urlParamIndexPageSize) + indexPageSize, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid indexPageSize %s: %w", s, err) + } + req.IndexPageSize = uint32(indexPageSize) + + s = r.URL.Query().Get(urlParamTotalRecords) + totalRecords, err := strconv.ParseInt(s, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid totalRecords %s: %w", s, err) + } + if totalRecords <= 0 { + return nil, fmt.Errorf("totalRecords must be greater than 0. received %d", totalRecords) + } + req.TotalRecords = uint32(totalRecords) + + // Data encoding can be blank for some block formats, therefore + // no validation on the param here. Eventually we may be able + // to remove this parameter entirely. + dataEncoding := r.URL.Query().Get(urlParamDataEncoding) + req.DataEncoding = dataEncoding + + version := r.URL.Query().Get(urlParamVersion) + if version == "" { + return nil, errors.New("version required") + } + req.Version = version + + s = r.URL.Query().Get(urlParamSize) + size, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid size %s: %w", s, err) + } + req.Size_ = size + + // Footer size can be 0 for some blocks, just ensure we + // get a valid integer. + f := r.URL.Query().Get(urlParamFooterSize) + footerSize, err := strconv.ParseUint(f, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid footerSize %s: %w", f, err) + } + req.FooterSize = uint32(footerSize) + + s = r.URL.Query().Get(urlParamDedicatedColumns) + if s != "" { + var dedicatedColumns []*tempopb.DedicatedColumn + err = json.Unmarshal([]byte(s), &dedicatedColumns) + if err != nil { + return nil, fmt.Errorf("invalid dedicatedColumns '%s': %w", s, err) + } + req.DedicatedColumns = dedicatedColumns + } + + return req, nil +} + // ParseSearchTagValuesRequest handles parsing of requests from /api/search/tags/{tagName}/values and /api/v2/search/tags/{tagName}/values func ParseSearchTagValuesRequest(r *http.Request) (*tempopb.SearchTagValuesRequest, error) { return parseSearchTagValuesRequest(r, false) @@ -104,3 +445,103 @@ func ParseSearchTagsRequest(r *http.Request) (*tempopb.SearchTagsRequest, error) } return req, nil } + +func BuildSearchTagsRequest(req *http.Request, searchReq *tempopb.SearchTagsRequest) (*http.Request, error) { + if req == nil { + req = &http.Request{ + URL: &url.URL{}, + } + } + + if searchReq == nil { + return req, nil + } + + q := req.URL.Query() + q.Set(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) + q.Set(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) + q.Set(urlParamScope, searchReq.Scope) + + req.URL.RawQuery = q.Encode() + + return req, nil +} + +func BuildSearchTagsBlockRequest(req *http.Request, searchReq *tempopb.SearchTagsBlockRequest) (*http.Request, error) { + if req == nil { + req = &http.Request{ + URL: &url.URL{}, + } + } + + req, err := BuildSearchTagsRequest(req, searchReq.SearchReq) + if err != nil { + return nil, err + } + + q := req.URL.Query() + q.Set(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) + q.Set(urlParamBlockID, searchReq.BlockID) + q.Set(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) + q.Set(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) + q.Set(urlParamEncoding, searchReq.Encoding) + q.Set(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) + q.Set(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) + q.Set(urlParamDataEncoding, searchReq.DataEncoding) + q.Set(urlParamVersion, searchReq.Version) + q.Set(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) + + req.URL.RawQuery = q.Encode() + + return req, nil +} + +func BuildSearchTagValuesRequest(req *http.Request, searchReq *tempopb.SearchTagValuesRequest) (*http.Request, error) { + if req == nil { + req = &http.Request{ + URL: &url.URL{}, + } + } + + if searchReq == nil { + return req, nil + } + + q := req.URL.Query() + q.Set(urlParamStart, strconv.FormatUint(uint64(searchReq.Start), 10)) + q.Set(urlParamEnd, strconv.FormatUint(uint64(searchReq.End), 10)) + q.Set(urlParamQuery, searchReq.Query) + + req.URL.RawQuery = q.Encode() + + return req, nil +} + +func BuildSearchTagValuesBlockRequest(req *http.Request, searchReq *tempopb.SearchTagValuesBlockRequest) (*http.Request, error) { + if req == nil { + req = &http.Request{ + URL: &url.URL{}, + } + } + + req, err := BuildSearchTagValuesRequest(req, searchReq.SearchReq) + if err != nil { + return nil, err + } + + q := req.URL.Query() + q.Set(urlParamSize, strconv.FormatUint(searchReq.Size_, 10)) + q.Set(urlParamBlockID, searchReq.BlockID) + q.Set(urlParamStartPage, strconv.FormatUint(uint64(searchReq.StartPage), 10)) + q.Set(urlParamPagesToSearch, strconv.FormatUint(uint64(searchReq.PagesToSearch), 10)) + q.Set(urlParamEncoding, searchReq.Encoding) + q.Set(urlParamIndexPageSize, strconv.FormatUint(uint64(searchReq.IndexPageSize), 10)) + q.Set(urlParamTotalRecords, strconv.FormatUint(uint64(searchReq.TotalRecords), 10)) + q.Set(urlParamDataEncoding, searchReq.DataEncoding) + q.Set(urlParamVersion, searchReq.Version) + q.Set(urlParamFooterSize, strconv.FormatUint(uint64(searchReq.FooterSize), 10)) + + req.URL.RawQuery = q.Encode() + + return req, nil +} diff --git a/pkg/util/test/req.go b/pkg/util/test/req.go index b63fecf44bb..51168416227 100644 --- a/pkg/util/test/req.go +++ b/pkg/util/test/req.go @@ -272,7 +272,7 @@ func TracesEqual(t *testing.T, t1 *tempopb.Trace, t2 *tempopb.Trace) { } } -func MakeTraceWithTags(traceID []byte) *tempopb.Trace { +func MakeTraceWithTags(traceID []byte, service string, intValue int64) *tempopb.Trace { now := time.Now() traceID = ValidTraceID(traceID) @@ -289,7 +289,7 @@ func MakeTraceWithTags(traceID []byte) *tempopb.Trace { attributes = append(attributes, &v1_common.KeyValue{ Key: "intTag", - Value: &v1_common.AnyValue{Value: &v1_common.AnyValue_IntValue{IntValue: 2}}, + Value: &v1_common.AnyValue{Value: &v1_common.AnyValue_IntValue{IntValue: intValue}}, }) trace.Batches = append(trace.Batches, &v1_trace.ResourceSpans{ @@ -299,7 +299,7 @@ func MakeTraceWithTags(traceID []byte) *tempopb.Trace { Key: "service.name", Value: &v1_common.AnyValue{ Value: &v1_common.AnyValue_StringValue{ - StringValue: "test-service", + StringValue: service, }, }, }, @@ -307,7 +307,7 @@ func MakeTraceWithTags(traceID []byte) *tempopb.Trace { Key: "other", Value: &v1_common.AnyValue{ Value: &v1_common.AnyValue_StringValue{ - StringValue: "test-service", + StringValue: "other-value", }, }, }, diff --git a/tempodb/tempodb.go b/tempodb/tempodb.go index 991569ab0d7..dccefcf37e6 100644 --- a/tempodb/tempodb.go +++ b/tempodb/tempodb.go @@ -81,9 +81,9 @@ type Reader interface { Fetch(ctx context.Context, meta *backend.BlockMeta, req traceql.FetchSpansRequest, opts common.SearchOptions) (traceql.FetchSpansResponse, error) BlockMetas(tenantID string) []*backend.BlockMeta EnablePolling(ctx context.Context, sharder blocklist.JobSharder) - SearchForTags(ctx context.Context, meta *backend.BlockMeta, scope string, opts common.SearchOptions) (*tempopb.SearchTagsResponse, error) - SearchForTagValues(ctx context.Context, meta *backend.BlockMeta, tag string, opts common.SearchOptions) ([]string, error) - SearchForTagValuesV2(ctx context.Context, meta *backend.BlockMeta, req *tempopb.SearchTagValuesRequest, opts common.SearchOptions) (*tempopb.SearchTagValuesV2Response, error) + SearchTags(ctx context.Context, meta *backend.BlockMeta, scope string, opts common.SearchOptions) (*tempopb.SearchTagsResponse, error) + SearchTagValues(ctx context.Context, meta *backend.BlockMeta, tag string, opts common.SearchOptions) ([]string, error) + SearchTagValuesV2(ctx context.Context, meta *backend.BlockMeta, req *tempopb.SearchTagValuesRequest, opts common.SearchOptions) (*tempopb.SearchTagValuesV2Response, error) Shutdown() } @@ -356,7 +356,7 @@ func (rw *readerWriter) Search(ctx context.Context, meta *backend.BlockMeta, req return block.Search(ctx, req, opts) } -func (rw *readerWriter) SearchForTags(ctx context.Context, meta *backend.BlockMeta, scope string, opts common.SearchOptions) (*tempopb.SearchTagsResponse, error) { +func (rw *readerWriter) SearchTags(ctx context.Context, meta *backend.BlockMeta, scope string, opts common.SearchOptions) (*tempopb.SearchTagsResponse, error) { attributeScope := traceql.AttributeScopeFromString(scope) if attributeScope == traceql.AttributeScopeUnknown { @@ -382,7 +382,7 @@ func (rw *readerWriter) SearchForTags(ctx context.Context, meta *backend.BlockMe }, err } -func (rw *readerWriter) SearchForTagValues(ctx context.Context, meta *backend.BlockMeta, tag string, opts common.SearchOptions) ([]string, error) { +func (rw *readerWriter) SearchTagValues(ctx context.Context, meta *backend.BlockMeta, tag string, opts common.SearchOptions) ([]string, error) { block, err := encoding.OpenBlock(meta, rw.r) if err != nil { return nil, err @@ -401,7 +401,7 @@ func (rw *readerWriter) SearchForTagValues(ctx context.Context, meta *backend.Bl return tags, err } -func (rw *readerWriter) SearchForTagValuesV2(ctx context.Context, meta *backend.BlockMeta, req *tempopb.SearchTagValuesRequest, opts common.SearchOptions) (*tempopb.SearchTagValuesV2Response, error) { +func (rw *readerWriter) SearchTagValuesV2(ctx context.Context, meta *backend.BlockMeta, req *tempopb.SearchTagValuesRequest, opts common.SearchOptions) (*tempopb.SearchTagValuesV2Response, error) { block, err := encoding.OpenBlock(meta, rw.r) if err != nil { return nil, err @@ -420,7 +420,7 @@ func (rw *readerWriter) SearchForTagValuesV2(ctx context.Context, meta *backend. if !traceql.IsEmptyQuery(query) { fetcher := traceql.NewAutocompleteFetcherWrapper(func(ctx context.Context, req traceql.AutocompleteRequest, cb traceql.AutocompleteCallback) error { - return block.FetchTagValues(ctx, req, cb, common.DefaultSearchOptions()) + return block.FetchTagValues(ctx, req, cb, opts) }) err := engine.ExecuteTagValues(ctx, tag, query, traceql.MakeCollectTagValueFunc(valueCollector.Collect), fetcher) diff --git a/tempodb/tempodb_test.go b/tempodb/tempodb_test.go index 743fcefcd92..aa875d59c65 100644 --- a/tempodb/tempodb_test.go +++ b/tempodb/tempodb_test.go @@ -42,37 +42,6 @@ type testConfigOption func(*Config) func testConfig(t *testing.T, enc backend.Encoding, blocklistPoll time.Duration, opts ...testConfigOption) (Reader, Writer, Compactor, string) { tempDir := t.TempDir() - cfg := &Config{ - Backend: backend.Local, - Local: &local.Config{ - Path: path.Join(tempDir, "traces"), - }, - Block: &common.BlockConfig{ - IndexDownsampleBytes: 17, - BloomFP: .01, - BloomShardSizeBytes: 100_000, - Version: encoding.DefaultEncoding().Version(), - Encoding: enc, - IndexPageSizeBytes: 1000, - }, - WAL: &wal.Config{ - Filepath: path.Join(tempDir, "wal"), - }, - BlocklistPoll: blocklistPoll, - } - - for _, opt := range opts { - opt(cfg) - } - - r, w, c, err := New(cfg, nil, log.NewNopLogger()) - require.NoError(t, err) - return r, w, c, tempDir -} - -func testTagsConfig(t *testing.T, enc backend.Encoding, blocklistPoll time.Duration, opts ...testConfigOption) (Reader, Writer, Compactor, string) { - tempDir := t.TempDir() - cfg := &Config{ Backend: backend.Local, Local: &local.Config{ @@ -106,7 +75,7 @@ func testTagsConfig(t *testing.T, enc backend.Encoding, blocklistPoll time.Durat } func TestSearchForTags(t *testing.T) { - r, w, c, _ := testTagsConfig(t, backend.EncGZIP, 0) + r, w, c, _ := testConfig(t, backend.EncGZIP, 0) err := c.EnableCompaction(context.Background(), &CompactorConfig{ ChunkSizeBytes: 10, @@ -128,52 +97,105 @@ func TestSearchForTags(t *testing.T) { dec := model.MustNewSegmentDecoder(model.CurrentEncoding) // write - numMsgs := 2 reqs := make([]*tempopb.Trace, 2) ids := make([]common.ID, 2) - for i := 0; i < numMsgs; i++ { - ids[i] = test.ValidTraceID(nil) - reqs[i] = test.MakeTraceWithTags(ids[i]) - writeTraceToWal(t, head, dec, ids[i], reqs[i], 0, 0) - } + ids[0] = test.ValidTraceID(nil) + reqs[0] = test.MakeTraceWithTags(ids[0], "test-service", 2) + writeTraceToWal(t, head, dec, ids[0], reqs[0], 0, 0) + + ids[1] = test.ValidTraceID(nil) + reqs[1] = test.MakeTraceWithTags(ids[0], "test-service-2", 3) + writeTraceToWal(t, head, dec, ids[1], reqs[1], 0, 0) block, err := w.CompleteBlock(context.Background(), head) assert.NoError(t, err) - tags, err := r.SearchForTags(context.Background(), block.BlockMeta(), "", common.DefaultSearchOptions()) + tags, err := r.SearchTags(context.Background(), block.BlockMeta(), "", common.DefaultSearchOptions()) assert.NoError(t, err) expectedTags := []string{"stringTag", "intTag", "service.name", "other"} sort.Strings(expectedTags) sort.Strings(tags.TagNames) assert.Equal(t, expectedTags, tags.TagNames) - values, err := r.SearchForTagValues(context.Background(), block.BlockMeta(), "service.name", common.DefaultSearchOptions()) - assert.Equal(t, []string{"test-service"}, values) + values, err := r.SearchTagValues(context.Background(), block.BlockMeta(), "service.name", common.DefaultSearchOptions()) + expectedTagsValues := []string{"test-service", "test-service-2"} + sort.Strings(expectedTagsValues) + sort.Strings(values) + assert.Equal(t, expectedTagsValues, values) - values, err = r.SearchForTagValues(context.Background(), block.BlockMeta(), "intTag", common.DefaultSearchOptions()) - assert.Equal(t, []string{"2"}, values) + values, err = r.SearchTagValues(context.Background(), block.BlockMeta(), "intTag", common.DefaultSearchOptions()) + expectedTagsValues = []string{"2", "3"} + sort.Strings(expectedTagsValues) + sort.Strings(values) + assert.Equal(t, expectedTagsValues, values) - values, err = r.SearchForTagValues(context.Background(), block.BlockMeta(), "intTag", common.DefaultSearchOptions()) - assert.Equal(t, []string{"2"}, values) - - tagValues, err := r.SearchForTagValuesV2(context.Background(), block.BlockMeta(), &tempopb.SearchTagValuesRequest{ + tagValues, err := r.SearchTagValuesV2(context.Background(), block.BlockMeta(), &tempopb.SearchTagValuesRequest{ TagName: ".service.name", }, common.DefaultSearchOptions()) - expected := []*tempopb.TagValue{{ - Type: "string", - Value: "test-service", - }} + expected := []*tempopb.TagValue{ + { + Type: "string", + Value: "test-service", + }, + { + Type: "string", + Value: "test-service-2", + }, + } - tagValues, err = r.SearchForTagValuesV2(context.Background(), block.BlockMeta(), &tempopb.SearchTagValuesRequest{ - TagName: ".intTag", + sort.SliceStable(expected, func(i, j int) bool { + return expected[i].Value < expected[j].Value + }) + sort.SliceStable(tagValues.TagValues, func(i, j int) bool { + return tagValues.TagValues[i].Value < tagValues.TagValues[j].Value + }) + + assert.Equal(t, expected, tagValues.TagValues) + + tagValues, err = r.SearchTagValuesV2(context.Background(), block.BlockMeta(), &tempopb.SearchTagValuesRequest{ + TagName: "span.intTag", }, common.DefaultSearchOptions()) + require.NoError(t, err) - expected = []*tempopb.TagValue{{ - Type: "int", - Value: "2", - }} + expected = []*tempopb.TagValue{ + { + Type: "int", + Value: "2", + }, + { + Type: "int", + Value: "3", + }, + } + sort.SliceStable(expected, func(i, j int) bool { + return expected[i].Value < expected[j].Value + }) + + sort.SliceStable(tagValues.TagValues, func(i, j int) bool { + return tagValues.TagValues[i].Value < tagValues.TagValues[j].Value + }) + assert.Equal(t, expected, tagValues.TagValues) + + tagValues, err = r.SearchTagValuesV2(context.Background(), block.BlockMeta(), &tempopb.SearchTagValuesRequest{ + TagName: "span.intTag", + Query: `{resource.service.name="test-service-2"}`, + }, common.DefaultSearchOptions()) + + require.NoError(t, err) + expected = []*tempopb.TagValue{ + { + Type: "int", + Value: "3", + }, + } + sort.SliceStable(expected, func(i, j int) bool { + return expected[i].Value < expected[j].Value + }) + sort.SliceStable(tagValues.TagValues, func(i, j int) bool { + return tagValues.TagValues[i].Value < tagValues.TagValues[j].Value + }) assert.Equal(t, expected, tagValues.TagValues) }