github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/queryinstant_codec_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package queryfrontend
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"io"
    10  	"net/http"
    11  	"testing"
    12  
    13  	"github.com/prometheus/common/model"
    14  	"github.com/prometheus/prometheus/model/labels"
    15  	"github.com/weaveworks/common/httpgrpc"
    16  
    17  	"github.com/efficientgo/core/testutil"
    18  	"github.com/thanos-io/thanos/internal/cortex/cortexpb"
    19  	"github.com/thanos-io/thanos/internal/cortex/querier/queryrange"
    20  	queryv1 "github.com/thanos-io/thanos/pkg/api/query"
    21  	"github.com/thanos-io/thanos/pkg/compact"
    22  )
    23  
    24  func TestQueryInstantCodec_DecodeRequest(t *testing.T) {
    25  	for _, tc := range []struct {
    26  		name            string
    27  		url             string
    28  		partialResponse bool
    29  		expectedError   error
    30  		expectedRequest *ThanosQueryInstantRequest
    31  	}{
    32  		{
    33  			name:            "cannot parse time",
    34  			url:             "/api/v1/query?time=foo",
    35  			partialResponse: false,
    36  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "foo" to a valid timestamp`),
    37  		},
    38  		{
    39  			name:            "parse time",
    40  			url:             "/api/v1/query?time=123",
    41  			partialResponse: false,
    42  			expectedRequest: &ThanosQueryInstantRequest{
    43  				Path:          "/api/v1/query",
    44  				Time:          123000,
    45  				Dedup:         true,
    46  				StoreMatchers: [][]*labels.Matcher{},
    47  			},
    48  		},
    49  		{
    50  			name:            "parse query",
    51  			url:             "/api/v1/query?time=123&query=up",
    52  			partialResponse: false,
    53  			expectedRequest: &ThanosQueryInstantRequest{
    54  				Path:          "/api/v1/query",
    55  				Query:         "up",
    56  				Time:          123000,
    57  				Dedup:         true,
    58  				StoreMatchers: [][]*labels.Matcher{},
    59  			},
    60  		},
    61  		{
    62  			name:            "cannot parse dedup",
    63  			url:             "/api/v1/query?dedup=bar",
    64  			partialResponse: false,
    65  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter dedup"),
    66  		},
    67  		{
    68  			name:            "cannot parse downsampling resolution",
    69  			url:             "/api/v1/query?max_source_resolution=bar",
    70  			partialResponse: false,
    71  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter max_source_resolution"),
    72  		},
    73  		{
    74  			name:            "negative downsampling resolution",
    75  			url:             "/api/v1/query?max_source_resolution=-1",
    76  			partialResponse: false,
    77  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, "negative max_source_resolution is not accepted. Try a positive integer"),
    78  		},
    79  		{
    80  			name: "auto downsampling enabled",
    81  			url:  "/api/v1/query?max_source_resolution=auto",
    82  			expectedRequest: &ThanosQueryInstantRequest{
    83  				Path:             "/api/v1/query",
    84  				AutoDownsampling: true,
    85  				Dedup:            true,
    86  				StoreMatchers:    [][]*labels.Matcher{},
    87  			},
    88  		},
    89  		{
    90  			name:            "cannot parse partial_response",
    91  			url:             "/api/v1/query?partial_response=bar",
    92  			partialResponse: false,
    93  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter partial_response"),
    94  		},
    95  		{
    96  			name:            "partial_response default to true",
    97  			url:             "/api/v1/query",
    98  			partialResponse: true,
    99  			expectedRequest: &ThanosQueryInstantRequest{
   100  				Path:            "/api/v1/query",
   101  				Dedup:           true,
   102  				PartialResponse: true,
   103  				StoreMatchers:   [][]*labels.Matcher{},
   104  			},
   105  		},
   106  		{
   107  			name:            "partial_response default to false, but set to true in query",
   108  			url:             "/api/v1/query?partial_response=true",
   109  			partialResponse: false,
   110  			expectedRequest: &ThanosQueryInstantRequest{
   111  				Path:            "/api/v1/query",
   112  				Dedup:           true,
   113  				PartialResponse: true,
   114  				StoreMatchers:   [][]*labels.Matcher{},
   115  			},
   116  		},
   117  		{
   118  			name:            "replicaLabels",
   119  			url:             "/api/v1/query?replicaLabels[]=foo&replicaLabels[]=bar",
   120  			partialResponse: false,
   121  			expectedRequest: &ThanosQueryInstantRequest{
   122  				Path:          "/api/v1/query",
   123  				Dedup:         true,
   124  				ReplicaLabels: []string{"foo", "bar"},
   125  				StoreMatchers: [][]*labels.Matcher{},
   126  			},
   127  		},
   128  		{
   129  			name:            "storeMatchers",
   130  			url:             `/api/v1/query?storeMatch[]={__address__="localhost:10901", cluster="test"}`,
   131  			partialResponse: false,
   132  			expectedRequest: &ThanosQueryInstantRequest{
   133  				Path:  "/api/v1/query",
   134  				Dedup: true,
   135  				StoreMatchers: [][]*labels.Matcher{
   136  					{
   137  						labels.MustNewMatcher(labels.MatchEqual, "__address__", "localhost:10901"),
   138  						labels.MustNewMatcher(labels.MatchEqual, "cluster", "test"),
   139  					},
   140  				},
   141  			},
   142  		},
   143  		{
   144  			name:            "lookback_delta",
   145  			url:             "/api/v1/query?lookback_delta=1000",
   146  			partialResponse: false,
   147  			expectedRequest: &ThanosQueryInstantRequest{
   148  				Path:          "/api/v1/query",
   149  				Dedup:         true,
   150  				LookbackDelta: 1000000,
   151  				StoreMatchers: [][]*labels.Matcher{},
   152  			},
   153  		},
   154  	} {
   155  		t.Run(tc.name, func(t *testing.T) {
   156  			r, err := http.NewRequest(http.MethodGet, tc.url, nil)
   157  			testutil.Ok(t, err)
   158  
   159  			codec := NewThanosQueryInstantCodec(tc.partialResponse)
   160  			req, err := codec.DecodeRequest(context.Background(), r, nil)
   161  			if tc.expectedError != nil {
   162  				testutil.Equals(t, err, tc.expectedError)
   163  			} else {
   164  				testutil.Ok(t, err)
   165  				testutil.Equals(t, req, tc.expectedRequest)
   166  			}
   167  		})
   168  	}
   169  }
   170  
   171  func TestQueryInstantCodec_EncodeRequest(t *testing.T) {
   172  	for _, tc := range []struct {
   173  		name          string
   174  		expectedError error
   175  		checkFunc     func(r *http.Request) bool
   176  		req           queryrange.Request
   177  	}{
   178  		{
   179  			name:          "prometheus request, invalid format",
   180  			req:           &queryrange.PrometheusRequest{},
   181  			expectedError: httpgrpc.Errorf(http.StatusBadRequest, "invalid request format"),
   182  		},
   183  		{
   184  			name: "empty thanos request",
   185  			req:  &ThanosQueryInstantRequest{},
   186  			checkFunc: func(r *http.Request) bool {
   187  				return r.FormValue("time") == "" && r.FormValue("query") == ""
   188  			},
   189  		},
   190  		{
   191  			name: "query set",
   192  			req:  &ThanosQueryInstantRequest{Query: "up"},
   193  			checkFunc: func(r *http.Request) bool {
   194  				return r.FormValue("query") == "up"
   195  			},
   196  		},
   197  		{
   198  			name: "time set",
   199  			req:  &ThanosQueryInstantRequest{Time: 123000},
   200  			checkFunc: func(r *http.Request) bool {
   201  				return r.FormValue("time") == "123"
   202  			},
   203  		},
   204  		{
   205  			name: "query and time set",
   206  			req:  &ThanosQueryInstantRequest{Time: 123000, Query: "foo"},
   207  			checkFunc: func(r *http.Request) bool {
   208  				return r.FormValue("time") == "123" && r.FormValue("query") == "foo"
   209  			},
   210  		},
   211  		{
   212  			name: "Dedup disabled",
   213  			req: &ThanosQueryInstantRequest{
   214  				Dedup: false,
   215  			},
   216  			checkFunc: func(r *http.Request) bool {
   217  				return r.FormValue(queryv1.DedupParam) == "false"
   218  			},
   219  		},
   220  		{
   221  			name: "Partial response set to true",
   222  			req: &ThanosQueryInstantRequest{
   223  				PartialResponse: true,
   224  			},
   225  			checkFunc: func(r *http.Request) bool {
   226  				return r.FormValue(queryv1.PartialResponseParam) == "true"
   227  			},
   228  		},
   229  		{
   230  			name: "Downsampling resolution set to 5m",
   231  			req: &ThanosQueryInstantRequest{
   232  				MaxSourceResolution: int64(compact.ResolutionLevel5m),
   233  			},
   234  			checkFunc: func(r *http.Request) bool {
   235  				return r.FormValue(queryv1.MaxSourceResolutionParam) == "300"
   236  			},
   237  		},
   238  		{
   239  			name: "Downsampling resolution set to 1h",
   240  			req: &ThanosQueryInstantRequest{
   241  				MaxSourceResolution: int64(compact.ResolutionLevel1h),
   242  			},
   243  			checkFunc: func(r *http.Request) bool {
   244  				return r.FormValue(queryv1.MaxSourceResolutionParam) == "3600"
   245  			},
   246  		},
   247  	} {
   248  		t.Run(tc.name, func(t *testing.T) {
   249  			// Default partial response value doesn't matter when encoding requests.
   250  			codec := NewThanosQueryInstantCodec(false)
   251  			r, err := codec.EncodeRequest(context.TODO(), tc.req)
   252  			if tc.expectedError != nil {
   253  				testutil.Equals(t, err, tc.expectedError)
   254  			} else {
   255  				testutil.Ok(t, err)
   256  				testutil.Equals(t, tc.checkFunc(r), true)
   257  			}
   258  		})
   259  	}
   260  }
   261  
   262  func TestMergeResponse(t *testing.T) {
   263  	codec := NewThanosQueryInstantCodec(false)
   264  	defaultReq := &queryrange.PrometheusRequest{
   265  		Query: "sum(up)",
   266  	}
   267  	for _, tc := range []struct {
   268  		name         string
   269  		req          *queryrange.PrometheusRequest
   270  		resps        []queryrange.Response
   271  		expectedResp queryrange.Response
   272  		expectedErr  error
   273  	}{
   274  		{
   275  			name:  "empty response",
   276  			req:   defaultReq,
   277  			resps: []queryrange.Response{},
   278  			expectedResp: &queryrange.PrometheusInstantQueryResponse{
   279  				Status: queryrange.StatusSuccess,
   280  				Data: queryrange.PrometheusInstantQueryData{
   281  					ResultType: model.ValVector.String(),
   282  					Result: queryrange.PrometheusInstantQueryResult{
   283  						Result: &queryrange.PrometheusInstantQueryResult_Vector{},
   284  					},
   285  				},
   286  			},
   287  		},
   288  		{
   289  			name: "one response",
   290  			req:  defaultReq,
   291  			resps: []queryrange.Response{
   292  				&queryrange.PrometheusInstantQueryResponse{
   293  					Status: queryrange.StatusSuccess,
   294  					Data: queryrange.PrometheusInstantQueryData{
   295  						ResultType: model.ValVector.String(),
   296  						Result: queryrange.PrometheusInstantQueryResult{
   297  							Result: &queryrange.PrometheusInstantQueryResult_Vector{
   298  								Vector: &queryrange.Vector{
   299  									Samples: []*queryrange.Sample{
   300  										{
   301  											Timestamp:   0,
   302  											SampleValue: 1,
   303  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   304  												"__name__": "up",
   305  											})),
   306  										},
   307  									},
   308  								},
   309  							},
   310  						},
   311  					},
   312  				},
   313  			},
   314  			expectedResp: &queryrange.PrometheusInstantQueryResponse{
   315  				Status: queryrange.StatusSuccess,
   316  				Data: queryrange.PrometheusInstantQueryData{
   317  					ResultType: model.ValVector.String(),
   318  					Result: queryrange.PrometheusInstantQueryResult{
   319  						Result: &queryrange.PrometheusInstantQueryResult_Vector{
   320  							Vector: &queryrange.Vector{
   321  								Samples: []*queryrange.Sample{
   322  									{
   323  										Timestamp:   0,
   324  										SampleValue: 1,
   325  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   326  											"__name__": "up",
   327  										})),
   328  									},
   329  								},
   330  							},
   331  						},
   332  					},
   333  				},
   334  			},
   335  		},
   336  		{
   337  			name: "merge two responses with sort",
   338  			req: &queryrange.PrometheusRequest{
   339  				Query: "1 + sort(topk(1, up))",
   340  			},
   341  			resps: []queryrange.Response{
   342  				&queryrange.PrometheusInstantQueryResponse{
   343  					Status: queryrange.StatusSuccess,
   344  					Data: queryrange.PrometheusInstantQueryData{
   345  						ResultType: model.ValVector.String(),
   346  						Result: queryrange.PrometheusInstantQueryResult{
   347  							Result: &queryrange.PrometheusInstantQueryResult_Vector{
   348  								Vector: &queryrange.Vector{
   349  									Samples: []*queryrange.Sample{
   350  										{
   351  											Timestamp:   0,
   352  											SampleValue: 1,
   353  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   354  												"__name__": "up",
   355  												"job":      "foo",
   356  											})),
   357  										},
   358  									},
   359  								},
   360  							},
   361  						},
   362  					},
   363  				},
   364  				&queryrange.PrometheusInstantQueryResponse{
   365  					Status: queryrange.StatusSuccess,
   366  					Data: queryrange.PrometheusInstantQueryData{
   367  						ResultType: model.ValVector.String(),
   368  						Result: queryrange.PrometheusInstantQueryResult{
   369  							Result: &queryrange.PrometheusInstantQueryResult_Vector{
   370  								Vector: &queryrange.Vector{
   371  									Samples: []*queryrange.Sample{
   372  										{
   373  											Timestamp:   0,
   374  											SampleValue: 2,
   375  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   376  												"__name__": "up",
   377  												"job":      "bar",
   378  											})),
   379  										},
   380  									},
   381  								},
   382  							},
   383  						},
   384  					},
   385  				},
   386  			},
   387  			expectedResp: &queryrange.PrometheusInstantQueryResponse{
   388  				Status: queryrange.StatusSuccess,
   389  				Data: queryrange.PrometheusInstantQueryData{
   390  					ResultType: model.ValVector.String(),
   391  					Result: queryrange.PrometheusInstantQueryResult{
   392  						Result: &queryrange.PrometheusInstantQueryResult_Vector{
   393  							Vector: &queryrange.Vector{
   394  								Samples: []*queryrange.Sample{
   395  									{
   396  										Timestamp:   0,
   397  										SampleValue: 1,
   398  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   399  											"__name__": "up",
   400  											"job":      "foo",
   401  										})),
   402  									},
   403  									{
   404  										Timestamp:   0,
   405  										SampleValue: 2,
   406  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   407  											"__name__": "up",
   408  											"job":      "bar",
   409  										})),
   410  									},
   411  								},
   412  							},
   413  						},
   414  					},
   415  				},
   416  			},
   417  		},
   418  		{
   419  			name: "merge two responses with topk",
   420  			req: &queryrange.PrometheusRequest{
   421  				Query: "topk(10, sort(up)) by (job)",
   422  			},
   423  			resps: []queryrange.Response{
   424  				&queryrange.PrometheusInstantQueryResponse{
   425  					Status: queryrange.StatusSuccess,
   426  					Data: queryrange.PrometheusInstantQueryData{
   427  						ResultType: model.ValVector.String(),
   428  						Result: queryrange.PrometheusInstantQueryResult{
   429  							Result: &queryrange.PrometheusInstantQueryResult_Vector{
   430  								Vector: &queryrange.Vector{
   431  									Samples: []*queryrange.Sample{
   432  										{
   433  											Timestamp:   0,
   434  											SampleValue: 1,
   435  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   436  												"__name__": "up",
   437  												"job":      "foo",
   438  											})),
   439  										},
   440  									},
   441  								},
   442  							},
   443  						},
   444  					},
   445  				},
   446  				&queryrange.PrometheusInstantQueryResponse{
   447  					Status: queryrange.StatusSuccess,
   448  					Data: queryrange.PrometheusInstantQueryData{
   449  						ResultType: model.ValVector.String(),
   450  						Result: queryrange.PrometheusInstantQueryResult{
   451  							Result: &queryrange.PrometheusInstantQueryResult_Vector{
   452  								Vector: &queryrange.Vector{
   453  									Samples: []*queryrange.Sample{
   454  										{
   455  											Timestamp:   0,
   456  											SampleValue: 2,
   457  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   458  												"__name__": "up",
   459  												"job":      "bar",
   460  											})),
   461  										},
   462  									},
   463  								},
   464  							},
   465  						},
   466  					},
   467  				},
   468  			},
   469  			expectedResp: &queryrange.PrometheusInstantQueryResponse{
   470  				Status: queryrange.StatusSuccess,
   471  				Data: queryrange.PrometheusInstantQueryData{
   472  					ResultType: model.ValVector.String(),
   473  					Result: queryrange.PrometheusInstantQueryResult{
   474  						Result: &queryrange.PrometheusInstantQueryResult_Vector{
   475  							Vector: &queryrange.Vector{
   476  								Samples: []*queryrange.Sample{
   477  									{
   478  										Timestamp:   0,
   479  										SampleValue: 1,
   480  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   481  											"__name__": "up",
   482  											"job":      "foo",
   483  										})),
   484  									},
   485  									{
   486  										Timestamp:   0,
   487  										SampleValue: 2,
   488  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   489  											"__name__": "up",
   490  											"job":      "bar",
   491  										})),
   492  									},
   493  								},
   494  							},
   495  						},
   496  					},
   497  				},
   498  			},
   499  		},
   500  		{
   501  			name: "merge two responses",
   502  			req:  defaultReq,
   503  			resps: []queryrange.Response{
   504  				&queryrange.PrometheusInstantQueryResponse{
   505  					Status: queryrange.StatusSuccess,
   506  					Data: queryrange.PrometheusInstantQueryData{
   507  						ResultType: model.ValVector.String(),
   508  						Result: queryrange.PrometheusInstantQueryResult{
   509  							Result: &queryrange.PrometheusInstantQueryResult_Vector{
   510  								Vector: &queryrange.Vector{
   511  									Samples: []*queryrange.Sample{
   512  										{
   513  											Timestamp:   0,
   514  											SampleValue: 1,
   515  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   516  												"__name__": "up",
   517  												"job":      "foo",
   518  											})),
   519  										},
   520  									},
   521  								},
   522  							},
   523  						},
   524  					},
   525  				},
   526  				&queryrange.PrometheusInstantQueryResponse{
   527  					Status: queryrange.StatusSuccess,
   528  					Data: queryrange.PrometheusInstantQueryData{
   529  						ResultType: model.ValVector.String(),
   530  						Result: queryrange.PrometheusInstantQueryResult{
   531  							Result: &queryrange.PrometheusInstantQueryResult_Vector{
   532  								Vector: &queryrange.Vector{
   533  									Samples: []*queryrange.Sample{
   534  										{
   535  											Timestamp:   0,
   536  											SampleValue: 2,
   537  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   538  												"__name__": "up",
   539  												"job":      "bar",
   540  											})),
   541  										},
   542  									},
   543  								},
   544  							},
   545  						},
   546  					},
   547  				},
   548  			},
   549  			expectedResp: &queryrange.PrometheusInstantQueryResponse{
   550  				Status: queryrange.StatusSuccess,
   551  				Data: queryrange.PrometheusInstantQueryData{
   552  					ResultType: model.ValVector.String(),
   553  					Result: queryrange.PrometheusInstantQueryResult{
   554  						Result: &queryrange.PrometheusInstantQueryResult_Vector{
   555  							Vector: &queryrange.Vector{
   556  								Samples: []*queryrange.Sample{
   557  									{
   558  										Timestamp:   0,
   559  										SampleValue: 2,
   560  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   561  											"__name__": "up",
   562  											"job":      "bar",
   563  										})),
   564  									},
   565  									{
   566  										Timestamp:   0,
   567  										SampleValue: 1,
   568  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   569  											"__name__": "up",
   570  											"job":      "foo",
   571  										})),
   572  									},
   573  								},
   574  							},
   575  						},
   576  					},
   577  				},
   578  			},
   579  		},
   580  		{
   581  			name: "merge multiple responses with same label sets, won't happen if sharding is enabled on downstream querier",
   582  			req:  defaultReq,
   583  			resps: []queryrange.Response{
   584  				&queryrange.PrometheusInstantQueryResponse{
   585  					Status: queryrange.StatusSuccess,
   586  					Data: queryrange.PrometheusInstantQueryData{
   587  						ResultType: model.ValVector.String(),
   588  						Result: queryrange.PrometheusInstantQueryResult{
   589  							Result: &queryrange.PrometheusInstantQueryResult_Vector{
   590  								Vector: &queryrange.Vector{
   591  									Samples: []*queryrange.Sample{
   592  										{
   593  											Timestamp:   0,
   594  											SampleValue: 1,
   595  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   596  												"__name__": "up",
   597  												"job":      "foo",
   598  											})),
   599  										},
   600  									},
   601  								},
   602  							},
   603  						},
   604  					},
   605  				},
   606  				&queryrange.PrometheusInstantQueryResponse{
   607  					Status: queryrange.StatusSuccess,
   608  					Data: queryrange.PrometheusInstantQueryData{
   609  						ResultType: model.ValVector.String(),
   610  						Result: queryrange.PrometheusInstantQueryResult{
   611  							Result: &queryrange.PrometheusInstantQueryResult_Vector{
   612  								Vector: &queryrange.Vector{
   613  									Samples: []*queryrange.Sample{
   614  										{
   615  											Timestamp:   1,
   616  											SampleValue: 2,
   617  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   618  												"__name__": "up",
   619  												"job":      "foo",
   620  											})),
   621  										},
   622  									},
   623  								},
   624  							},
   625  						},
   626  					},
   627  				},
   628  			},
   629  			expectedResp: &queryrange.PrometheusInstantQueryResponse{
   630  				Status: queryrange.StatusSuccess,
   631  				Data: queryrange.PrometheusInstantQueryData{
   632  					ResultType: model.ValVector.String(),
   633  					Result: queryrange.PrometheusInstantQueryResult{
   634  						Result: &queryrange.PrometheusInstantQueryResult_Vector{
   635  							Vector: &queryrange.Vector{
   636  								Samples: []*queryrange.Sample{
   637  									{
   638  										Timestamp:   1,
   639  										SampleValue: 2,
   640  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   641  											"__name__": "up",
   642  											"job":      "foo",
   643  										})),
   644  									},
   645  								},
   646  							},
   647  						},
   648  					},
   649  				},
   650  			},
   651  		},
   652  		{
   653  			name: "responses don't contain vector, return empty vector",
   654  			req:  defaultReq,
   655  			resps: []queryrange.Response{
   656  				&queryrange.PrometheusInstantQueryResponse{
   657  					Status: queryrange.StatusSuccess,
   658  					Data: queryrange.PrometheusInstantQueryData{
   659  						ResultType: model.ValScalar.String(),
   660  						Result: queryrange.PrometheusInstantQueryResult{
   661  							Result: &queryrange.PrometheusInstantQueryResult_Scalar{
   662  								Scalar: &cortexpb.Sample{
   663  									TimestampMs: 0,
   664  									Value:       1,
   665  								},
   666  							},
   667  						},
   668  					},
   669  				},
   670  				&queryrange.PrometheusInstantQueryResponse{
   671  					Status: queryrange.StatusSuccess,
   672  					Data: queryrange.PrometheusInstantQueryData{
   673  						ResultType: model.ValScalar.String(),
   674  						Result: queryrange.PrometheusInstantQueryResult{
   675  							Result: &queryrange.PrometheusInstantQueryResult_Scalar{
   676  								Scalar: &cortexpb.Sample{
   677  									TimestampMs: 0,
   678  									Value:       2,
   679  								},
   680  							},
   681  						},
   682  					},
   683  				},
   684  			},
   685  			expectedResp: &queryrange.PrometheusInstantQueryResponse{
   686  				Status: queryrange.StatusSuccess,
   687  				Data: queryrange.PrometheusInstantQueryData{
   688  					ResultType: model.ValVector.String(),
   689  					Result: queryrange.PrometheusInstantQueryResult{
   690  						Result: &queryrange.PrometheusInstantQueryResult_Vector{
   691  							Vector: &queryrange.Vector{
   692  								Samples: []*queryrange.Sample{},
   693  							},
   694  						},
   695  					},
   696  				},
   697  			},
   698  		},
   699  		{
   700  			name: "merge two matrix responses with non-duplicate samples",
   701  			req:  defaultReq,
   702  			resps: []queryrange.Response{
   703  				&queryrange.PrometheusInstantQueryResponse{
   704  					Status: queryrange.StatusSuccess,
   705  					Data: queryrange.PrometheusInstantQueryData{
   706  						ResultType: model.ValMatrix.String(),
   707  						Result: queryrange.PrometheusInstantQueryResult{
   708  							Result: &queryrange.PrometheusInstantQueryResult_Matrix{
   709  								Matrix: &queryrange.Matrix{
   710  									SampleStreams: []*queryrange.SampleStream{
   711  										{
   712  											Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}},
   713  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   714  												"__name__": "up",
   715  												"job":      "bar",
   716  											})),
   717  										},
   718  										{
   719  											Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}},
   720  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   721  												"__name__": "up",
   722  												"job":      "foo",
   723  											})),
   724  										},
   725  									},
   726  								},
   727  							},
   728  						},
   729  					},
   730  				},
   731  				&queryrange.PrometheusInstantQueryResponse{
   732  					Status: queryrange.StatusSuccess,
   733  					Data: queryrange.PrometheusInstantQueryData{
   734  						ResultType: model.ValMatrix.String(),
   735  						Result: queryrange.PrometheusInstantQueryResult{
   736  							Result: &queryrange.PrometheusInstantQueryResult_Matrix{
   737  								Matrix: &queryrange.Matrix{
   738  									SampleStreams: []*queryrange.SampleStream{
   739  										{
   740  											Samples: []cortexpb.Sample{{TimestampMs: 2, Value: 3}},
   741  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   742  												"__name__": "up",
   743  												"job":      "bar",
   744  											})),
   745  										},
   746  										{
   747  											Samples: []cortexpb.Sample{{TimestampMs: 2, Value: 3}},
   748  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   749  												"__name__": "up",
   750  												"job":      "foo",
   751  											})),
   752  										},
   753  									},
   754  								},
   755  							},
   756  						},
   757  					},
   758  				},
   759  			},
   760  			expectedResp: &queryrange.PrometheusInstantQueryResponse{
   761  				Status: queryrange.StatusSuccess,
   762  				Data: queryrange.PrometheusInstantQueryData{
   763  					ResultType: model.ValMatrix.String(),
   764  					Result: queryrange.PrometheusInstantQueryResult{
   765  						Result: &queryrange.PrometheusInstantQueryResult_Matrix{
   766  							Matrix: &queryrange.Matrix{
   767  								SampleStreams: []*queryrange.SampleStream{
   768  									{
   769  										Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}, {TimestampMs: 2, Value: 3}},
   770  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   771  											"__name__": "up",
   772  											"job":      "bar",
   773  										})),
   774  									},
   775  									{
   776  										Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}, {TimestampMs: 2, Value: 3}},
   777  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   778  											"__name__": "up",
   779  											"job":      "foo",
   780  										})),
   781  									},
   782  								},
   783  							},
   784  						},
   785  					},
   786  				},
   787  			},
   788  		},
   789  		{
   790  			name: "merge two matrix responses with duplicate samples",
   791  			req:  defaultReq,
   792  			resps: []queryrange.Response{
   793  				&queryrange.PrometheusInstantQueryResponse{
   794  					Status: queryrange.StatusSuccess,
   795  					Data: queryrange.PrometheusInstantQueryData{
   796  						ResultType: model.ValMatrix.String(),
   797  						Result: queryrange.PrometheusInstantQueryResult{
   798  							Result: &queryrange.PrometheusInstantQueryResult_Matrix{
   799  								Matrix: &queryrange.Matrix{
   800  									SampleStreams: []*queryrange.SampleStream{
   801  										{
   802  											Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}},
   803  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   804  												"__name__": "up",
   805  												"job":      "bar",
   806  											})),
   807  										},
   808  										{
   809  											Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}},
   810  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   811  												"__name__": "up",
   812  												"job":      "foo",
   813  											})),
   814  										},
   815  									},
   816  								},
   817  							},
   818  						},
   819  					},
   820  				},
   821  				&queryrange.PrometheusInstantQueryResponse{
   822  					Status: queryrange.StatusSuccess,
   823  					Data: queryrange.PrometheusInstantQueryData{
   824  						ResultType: model.ValMatrix.String(),
   825  						Result: queryrange.PrometheusInstantQueryResult{
   826  							Result: &queryrange.PrometheusInstantQueryResult_Matrix{
   827  								Matrix: &queryrange.Matrix{
   828  									SampleStreams: []*queryrange.SampleStream{
   829  										{
   830  											Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}},
   831  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   832  												"__name__": "up",
   833  												"job":      "bar",
   834  											})),
   835  										},
   836  										{
   837  											Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}},
   838  											Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   839  												"__name__": "up",
   840  												"job":      "foo",
   841  											})),
   842  										},
   843  									},
   844  								},
   845  							},
   846  						},
   847  					},
   848  				},
   849  			},
   850  			expectedResp: &queryrange.PrometheusInstantQueryResponse{
   851  				Status: queryrange.StatusSuccess,
   852  				Data: queryrange.PrometheusInstantQueryData{
   853  					ResultType: model.ValMatrix.String(),
   854  					Result: queryrange.PrometheusInstantQueryResult{
   855  						Result: &queryrange.PrometheusInstantQueryResult_Matrix{
   856  							Matrix: &queryrange.Matrix{
   857  								SampleStreams: []*queryrange.SampleStream{
   858  									{
   859  										Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}},
   860  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   861  											"__name__": "up",
   862  											"job":      "bar",
   863  										})),
   864  									},
   865  									{
   866  										Samples: []cortexpb.Sample{{TimestampMs: 1, Value: 2}},
   867  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   868  											"__name__": "up",
   869  											"job":      "foo",
   870  										})),
   871  									},
   872  								},
   873  							},
   874  						},
   875  					},
   876  				},
   877  			},
   878  		},
   879  	} {
   880  		t.Run(tc.name, func(t *testing.T) {
   881  			resp, err := codec.MergeResponse(tc.req, tc.resps...)
   882  			testutil.Equals(t, err, tc.expectedErr)
   883  			testutil.Equals(t, resp, tc.expectedResp)
   884  		})
   885  	}
   886  }
   887  
   888  func TestDecodeResponse(t *testing.T) {
   889  	codec := NewThanosQueryInstantCodec(false)
   890  	headers := []*queryrange.PrometheusResponseHeader{
   891  		{Name: "Content-Type", Values: []string{"application/json"}},
   892  	}
   893  	for _, tc := range []struct {
   894  		name             string
   895  		body             string
   896  		expectedResponse queryrange.Response
   897  		expectedErr      error
   898  	}{
   899  		{
   900  			name: "with explanation",
   901  			body: `{
   902    "status":"success",
   903    "data":{
   904  	"resultType":"vector",
   905  	"result":[],
   906  	"explanation": {
   907  		"name":"[*concurrencyOperator(buff=2)]",
   908  		"children":[{"name":"[*aggregate] sum by ([])", "children": []}]
   909      }
   910  }
   911  }`,
   912  			expectedResponse: &queryrange.PrometheusInstantQueryResponse{
   913  				Status:  queryrange.StatusSuccess,
   914  				Headers: headers,
   915  				Data: queryrange.PrometheusInstantQueryData{
   916  					Explanation: &queryrange.Explanation{
   917  						Name: "[*concurrencyOperator(buff=2)]",
   918  						Children: []*queryrange.Explanation{
   919  							{
   920  								Name:     "[*aggregate] sum by ([])",
   921  								Children: []*queryrange.Explanation{},
   922  							},
   923  						},
   924  					},
   925  					ResultType: model.ValVector.String(),
   926  					Result: queryrange.PrometheusInstantQueryResult{
   927  						Result: &queryrange.PrometheusInstantQueryResult_Vector{
   928  							Vector: &queryrange.Vector{
   929  								Samples: []*queryrange.Sample{},
   930  							},
   931  						},
   932  					},
   933  				},
   934  			},
   935  		},
   936  		{
   937  			name: "empty vector",
   938  			body: `{
   939    "status": "success",
   940    "data": {
   941      "resultType": "vector",
   942      "result": [
   943  
   944      ]
   945    }
   946  }`,
   947  			expectedResponse: &queryrange.PrometheusInstantQueryResponse{
   948  				Status:  queryrange.StatusSuccess,
   949  				Headers: headers,
   950  				Data: queryrange.PrometheusInstantQueryData{
   951  					ResultType: model.ValVector.String(),
   952  					Result: queryrange.PrometheusInstantQueryResult{
   953  						Result: &queryrange.PrometheusInstantQueryResult_Vector{
   954  							Vector: &queryrange.Vector{
   955  								Samples: []*queryrange.Sample{},
   956  							},
   957  						},
   958  					},
   959  				},
   960  			},
   961  		},
   962  		{
   963  			name: "vector",
   964  			body: `{
   965    "status": "success",
   966    "data": {
   967      "resultType": "vector",
   968      "result": [
   969        {
   970          "metric": {
   971            "__name__": "up",
   972            "instance": "localhost:9090",
   973            "job": "prometheus"
   974          },
   975          "value": [
   976            1661020672.043,
   977            "1"
   978          ]
   979        }
   980      ]
   981    }
   982  }`,
   983  			expectedResponse: &queryrange.PrometheusInstantQueryResponse{
   984  				Status:  queryrange.StatusSuccess,
   985  				Headers: headers,
   986  				Data: queryrange.PrometheusInstantQueryData{
   987  					ResultType: model.ValVector.String(),
   988  					Result: queryrange.PrometheusInstantQueryResult{
   989  						Result: &queryrange.PrometheusInstantQueryResult_Vector{
   990  							Vector: &queryrange.Vector{
   991  								Samples: []*queryrange.Sample{
   992  									{
   993  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
   994  											"__name__": "up",
   995  											"instance": "localhost:9090",
   996  											"job":      "prometheus",
   997  										})),
   998  										Timestamp:   1661020672043,
   999  										SampleValue: 1,
  1000  									},
  1001  								},
  1002  							},
  1003  						},
  1004  					},
  1005  				},
  1006  			},
  1007  		},
  1008  		{
  1009  			name: "scalar",
  1010  			body: `{
  1011    "status": "success",
  1012    "data": {
  1013      "resultType": "scalar",
  1014      "result": [
  1015        1661020145.547,
  1016        "1"
  1017      ]
  1018    }
  1019  }`,
  1020  			expectedResponse: &queryrange.PrometheusInstantQueryResponse{
  1021  				Status:  queryrange.StatusSuccess,
  1022  				Headers: headers,
  1023  				Data: queryrange.PrometheusInstantQueryData{
  1024  					ResultType: model.ValScalar.String(),
  1025  					Result: queryrange.PrometheusInstantQueryResult{
  1026  						Result: &queryrange.PrometheusInstantQueryResult_Scalar{
  1027  							Scalar: &cortexpb.Sample{TimestampMs: 1661020145547, Value: 1},
  1028  						},
  1029  					},
  1030  				},
  1031  			},
  1032  		},
  1033  		{
  1034  			name: "string",
  1035  			body: `{
  1036    "status": "success",
  1037    "data": {
  1038      "resultType": "string",
  1039      "result": [
  1040        1661020232.424,
  1041        "test"
  1042      ]
  1043    }
  1044  }`,
  1045  			expectedResponse: &queryrange.PrometheusInstantQueryResponse{
  1046  				Status:  queryrange.StatusSuccess,
  1047  				Headers: headers,
  1048  				Data: queryrange.PrometheusInstantQueryData{
  1049  					ResultType: model.ValString.String(),
  1050  					Result: queryrange.PrometheusInstantQueryResult{
  1051  						Result: &queryrange.PrometheusInstantQueryResult_StringSample{
  1052  							StringSample: &queryrange.StringSample{TimestampMs: 1661020232424, Value: "test"},
  1053  						},
  1054  					},
  1055  				},
  1056  			},
  1057  		},
  1058  		{
  1059  			name: "empty matrix",
  1060  			body: `{
  1061    "status": "success",
  1062    "data": {
  1063      "resultType": "matrix",
  1064      "result": [
  1065  
  1066      ]
  1067    }
  1068  }`,
  1069  			expectedResponse: &queryrange.PrometheusInstantQueryResponse{
  1070  				Status:  queryrange.StatusSuccess,
  1071  				Headers: headers,
  1072  				Data: queryrange.PrometheusInstantQueryData{
  1073  					ResultType: model.ValMatrix.String(),
  1074  					Result: queryrange.PrometheusInstantQueryResult{
  1075  						Result: &queryrange.PrometheusInstantQueryResult_Matrix{
  1076  							Matrix: &queryrange.Matrix{
  1077  								SampleStreams: []*queryrange.SampleStream{},
  1078  							},
  1079  						},
  1080  					},
  1081  				},
  1082  			},
  1083  		},
  1084  		{
  1085  			name: "matrix",
  1086  			body: `{
  1087    "status": "success",
  1088    "data": {
  1089      "resultType": "matrix",
  1090      "result": [
  1091        {
  1092          "metric": {
  1093            "__name__": "up",
  1094            "instance": "localhost:9090",
  1095            "job": "prometheus"
  1096          },
  1097          "values": [
  1098            [
  1099              1661020250.310,
  1100              "1"
  1101            ],
  1102            [
  1103              1661020265.309,
  1104              "1"
  1105            ],
  1106            [
  1107              1661020280.309,
  1108              "1"
  1109            ],
  1110            [
  1111              1661020295.310,
  1112              "1"
  1113            ]
  1114          ]
  1115        }
  1116      ]
  1117    }
  1118  }`,
  1119  			expectedResponse: &queryrange.PrometheusInstantQueryResponse{
  1120  				Status:  queryrange.StatusSuccess,
  1121  				Headers: headers,
  1122  				Data: queryrange.PrometheusInstantQueryData{
  1123  					ResultType: model.ValMatrix.String(),
  1124  					Result: queryrange.PrometheusInstantQueryResult{
  1125  						Result: &queryrange.PrometheusInstantQueryResult_Matrix{
  1126  							Matrix: &queryrange.Matrix{
  1127  								SampleStreams: []*queryrange.SampleStream{
  1128  									{
  1129  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
  1130  											"__name__": "up",
  1131  											"instance": "localhost:9090",
  1132  											"job":      "prometheus",
  1133  										})),
  1134  										Samples: []cortexpb.Sample{
  1135  											{TimestampMs: 1661020250310, Value: 1},
  1136  											{TimestampMs: 1661020265309, Value: 1},
  1137  											{TimestampMs: 1661020280309, Value: 1},
  1138  											{TimestampMs: 1661020295310, Value: 1},
  1139  										},
  1140  									},
  1141  								},
  1142  							},
  1143  						},
  1144  					},
  1145  				},
  1146  			},
  1147  		},
  1148  		{
  1149  			name: "matrix with multiple metrics",
  1150  			body: `{
  1151    "status": "success",
  1152    "data": {
  1153      "resultType": "matrix",
  1154      "result": [
  1155        {
  1156          "metric": {
  1157            "__name__": "prometheus_http_requests_total",
  1158            "code": "200",
  1159            "handler": "/favicon.ico",
  1160            "instance": "localhost:9090",
  1161            "job": "prometheus"
  1162          },
  1163          "values": [
  1164            [
  1165              1661020430.311,
  1166              "1"
  1167            ],
  1168            [
  1169              1661020445.312,
  1170              "1"
  1171            ],
  1172            [
  1173              1661020460.313,
  1174              "1"
  1175            ],
  1176            [
  1177              1661020475.313,
  1178              "1"
  1179            ]
  1180          ]
  1181        },
  1182        {
  1183          "metric": {
  1184            "__name__": "prometheus_http_requests_total",
  1185            "code": "200",
  1186            "handler": "/metrics",
  1187            "instance": "localhost:9090",
  1188            "job": "prometheus"
  1189          },
  1190          "values": [
  1191            [
  1192              1661020430.311,
  1193              "33"
  1194            ],
  1195            [
  1196              1661020445.312,
  1197              "34"
  1198            ],
  1199            [
  1200              1661020460.313,
  1201              "35"
  1202            ],
  1203            [
  1204              1661020475.313,
  1205              "36"
  1206            ]
  1207          ]
  1208        }
  1209      ]
  1210    }
  1211  }`,
  1212  			expectedResponse: &queryrange.PrometheusInstantQueryResponse{
  1213  				Status:  queryrange.StatusSuccess,
  1214  				Headers: headers,
  1215  				Data: queryrange.PrometheusInstantQueryData{
  1216  					ResultType: model.ValMatrix.String(),
  1217  					Result: queryrange.PrometheusInstantQueryResult{
  1218  						Result: &queryrange.PrometheusInstantQueryResult_Matrix{
  1219  							Matrix: &queryrange.Matrix{
  1220  								SampleStreams: []*queryrange.SampleStream{
  1221  									{
  1222  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
  1223  											"__name__": "prometheus_http_requests_total",
  1224  											"code":     "200",
  1225  											"handler":  "/favicon.ico",
  1226  											"instance": "localhost:9090",
  1227  											"job":      "prometheus",
  1228  										})),
  1229  										Samples: []cortexpb.Sample{
  1230  											{TimestampMs: 1661020430311, Value: 1},
  1231  											{TimestampMs: 1661020445312, Value: 1},
  1232  											{TimestampMs: 1661020460313, Value: 1},
  1233  											{TimestampMs: 1661020475313, Value: 1},
  1234  										},
  1235  									},
  1236  									{
  1237  										Labels: cortexpb.FromLabelsToLabelAdapters(labels.FromMap(map[string]string{
  1238  											"__name__": "prometheus_http_requests_total",
  1239  											"code":     "200",
  1240  											"handler":  "/metrics",
  1241  											"instance": "localhost:9090",
  1242  											"job":      "prometheus",
  1243  										})),
  1244  										Samples: []cortexpb.Sample{
  1245  											{TimestampMs: 1661020430311, Value: 33},
  1246  											{TimestampMs: 1661020445312, Value: 34},
  1247  											{TimestampMs: 1661020460313, Value: 35},
  1248  											{TimestampMs: 1661020475313, Value: 36},
  1249  										},
  1250  									},
  1251  								},
  1252  							},
  1253  						},
  1254  					},
  1255  				},
  1256  			},
  1257  		},
  1258  	} {
  1259  		resp := &http.Response{
  1260  			StatusCode: 200,
  1261  			Header:     http.Header{"Content-Type": []string{"application/json"}},
  1262  			Body:       io.NopCloser(bytes.NewBuffer([]byte(tc.body))),
  1263  		}
  1264  		gotResponse, err := codec.DecodeResponse(context.Background(), resp, nil)
  1265  		testutil.Equals(t, tc.expectedErr, err)
  1266  		testutil.Equals(t, tc.expectedResponse, gotResponse)
  1267  	}
  1268  }
  1269  
  1270  func Test_sortPlanForQuery(t *testing.T) {
  1271  	tc := []struct {
  1272  		query        string
  1273  		expectedPlan sortPlan
  1274  		err          bool
  1275  	}{
  1276  		{
  1277  			query:        "invalid(10, up)",
  1278  			expectedPlan: mergeOnly,
  1279  			err:          true,
  1280  		},
  1281  		{
  1282  			query:        "topk(10, up)",
  1283  			expectedPlan: mergeOnly,
  1284  			err:          false,
  1285  		},
  1286  		{
  1287  			query:        "bottomk(10, up)",
  1288  			expectedPlan: mergeOnly,
  1289  			err:          false,
  1290  		},
  1291  		{
  1292  			query:        "1 + topk(10, up)",
  1293  			expectedPlan: sortByLabels,
  1294  			err:          false,
  1295  		},
  1296  		{
  1297  			query:        "1 + sort_desc(sum by (job) (up) )",
  1298  			expectedPlan: sortByValuesDesc,
  1299  			err:          false,
  1300  		},
  1301  		{
  1302  			query:        "sort(topk by (job) (10, up))",
  1303  			expectedPlan: sortByValuesAsc,
  1304  			err:          false,
  1305  		},
  1306  		{
  1307  			query:        "topk(5, up) by (job) + sort_desc(up)",
  1308  			expectedPlan: sortByValuesDesc,
  1309  			err:          false,
  1310  		},
  1311  		{
  1312  			query:        "sort(up) + topk(5, up) by (job)",
  1313  			expectedPlan: sortByValuesAsc,
  1314  			err:          false,
  1315  		},
  1316  		{
  1317  			query:        "sum(up) by (job)",
  1318  			expectedPlan: sortByLabels,
  1319  			err:          false,
  1320  		},
  1321  	}
  1322  
  1323  	for _, tc := range tc {
  1324  		t.Run(tc.query, func(t *testing.T) {
  1325  			p, err := sortPlanForQuery(tc.query)
  1326  			if tc.err {
  1327  				testutil.NotOk(t, err)
  1328  			} else {
  1329  				testutil.Ok(t, err)
  1330  				testutil.Equals(t, tc.expectedPlan, p)
  1331  			}
  1332  		})
  1333  	}
  1334  }