github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/functions/binary/binary_test.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package binary
    22  
    23  import (
    24  	"math"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/query/block"
    29  	"github.com/m3db/m3/src/query/executor/transform"
    30  	"github.com/m3db/m3/src/query/functions/utils"
    31  	"github.com/m3db/m3/src/query/models"
    32  	"github.com/m3db/m3/src/query/parser"
    33  	"github.com/m3db/m3/src/query/test"
    34  	"github.com/m3db/m3/src/query/test/compare"
    35  	"github.com/m3db/m3/src/query/test/executor"
    36  	xtime "github.com/m3db/m3/src/x/time"
    37  
    38  	"github.com/stretchr/testify/assert"
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  var scalarTests = []struct {
    43  	name     string
    44  	lVal     float64
    45  	opType   string
    46  	rVal     float64
    47  	expected float64
    48  }{
    49  	/* Arithmetic */
    50  	// +
    51  	{"1 + 3 = 4", 1, PlusType, 3, 4},
    52  	// -
    53  	{"1 - 3 = -2", 1, MinusType, 3, -2},
    54  	{"3 - 1 = 2", 3, MinusType, 1, 2},
    55  	// *
    56  	{"7 * 3 = 21", 7, MultiplyType, 3, 21},
    57  	// /
    58  	{"4 / 8 = 0.5", 4, DivType, 8, 0.5},
    59  	{"4 / 8 = 2", 8, DivType, 4, 2},
    60  	{"8 / 8 = 1", 8, DivType, 8, 1},
    61  	{"8 / 0 = +infinity", 8, DivType, 0, math.Inf(1)},
    62  	{"-8/ 0 = -infinity", -8, DivType, 0, math.Inf(-1)},
    63  	{"0 / 0 = NaN", 0, DivType, 0, math.NaN()},
    64  	// ^
    65  	{"0 ^ 0 = 1", 0, ExpType, 0, 1},
    66  	{"x ^ 0 = 1", 2, ExpType, 0, 1},
    67  	{"0 ^ x = 0", 0, ExpType, 2, 0},
    68  	// %
    69  	{"8 % 0 = NaN", 8, ModType, 0, math.NaN()},
    70  	{"8 % 3 = 2", 8, ModType, 3, 2},
    71  	{"8 % 2 = 0", 8, ModType, 2, 0},
    72  	{"8 % 1.5 = 1", 8, ModType, 1.5, 0.5},
    73  	/* Comparison */
    74  	// ==
    75  	{"2 == 1 = 0", 2, EqType, 1, 0},
    76  	{"2 == 2 = 1", 2, EqType, 2, 1},
    77  	{"2 == 3 = 0", 2, EqType, 3, 0},
    78  	// !=
    79  	{"2 != 1 = 1", 2, NotEqType, 1, 1},
    80  	{"2 != 2 = 0", 2, NotEqType, 2, 0},
    81  	{"2 != 3 = 1", 2, NotEqType, 3, 1},
    82  	// >
    83  	{"2 > 1 = 1", 2, GreaterType, 1, 1},
    84  	{"2 > 2 = 0", 2, GreaterType, 2, 0},
    85  	{"2 > 3 = 0", 2, GreaterType, 3, 0},
    86  	// <
    87  	{"2 < 1 = 0", 2, LesserType, 1, 0},
    88  	{"2 < 2 = 0", 2, LesserType, 2, 0},
    89  	{"2 < 3 = 1", 2, LesserType, 3, 1},
    90  	// >=
    91  	{"2 >= 1 = 1", 2, GreaterEqType, 1, 1},
    92  	{"2 >= 2 = 1", 2, GreaterEqType, 2, 1},
    93  	{"2 >= 3 = 0", 2, GreaterEqType, 3, 0},
    94  	// <=
    95  	{"2 <= 1 = 0", 2, LesserEqType, 1, 0},
    96  	{"2 <= 2 = 1", 2, LesserEqType, 2, 1},
    97  	{"2 <= 3 = 1", 2, LesserEqType, 3, 1},
    98  }
    99  
   100  func TestScalars(t *testing.T) {
   101  	_, bounds := test.GenerateValuesAndBounds(nil, nil)
   102  
   103  	for _, tt := range scalarTests {
   104  		t.Run(tt.name, func(t *testing.T) {
   105  			op, err := NewOp(
   106  				tt.opType,
   107  				NodeParams{
   108  					LNode:                parser.NodeID(rune(0)),
   109  					RNode:                parser.NodeID(rune(1)),
   110  					ReturnBool:           true,
   111  					VectorMatcherBuilder: emptyVectorMatcherBuilder,
   112  				},
   113  			)
   114  			require.NoError(t, err)
   115  
   116  			c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
   117  			node := op.(baseOp).Node(c, transform.Options{})
   118  
   119  			err = node.Process(
   120  				models.NoopQueryContext(),
   121  				parser.NodeID(rune(0)),
   122  				block.NewScalar(tt.lVal, block.Metadata{
   123  					Bounds: bounds,
   124  					Tags:   models.EmptyTags(),
   125  				}),
   126  			)
   127  
   128  			require.NoError(t, err)
   129  			err = node.Process(
   130  				models.NoopQueryContext(),
   131  				parser.NodeID(rune(1)),
   132  				block.NewScalar(tt.rVal, block.Metadata{
   133  					Bounds: bounds,
   134  					Tags:   models.EmptyTags(),
   135  				}),
   136  			)
   137  
   138  			expected := [][]float64{{
   139  				tt.expected, tt.expected, tt.expected,
   140  				tt.expected, tt.expected,
   141  			}}
   142  
   143  			compare.EqualsWithNans(t, expected, sink.Values)
   144  
   145  			assert.Equal(t, bounds, sink.Meta.Bounds)
   146  			assert.Equal(t, 0, sink.Meta.Tags.Len())
   147  
   148  			assert.Len(t, sink.Metas, 1)
   149  			assert.Equal(t, []byte(nil), sink.Metas[0].Name)
   150  			assert.Equal(t, 0, sink.Metas[0].Tags.Len())
   151  		})
   152  	}
   153  }
   154  
   155  func TestScalarsReturnBoolFalse(t *testing.T) {
   156  	_, bounds := test.GenerateValuesAndBounds(nil, nil)
   157  
   158  	for _, tt := range scalarTests {
   159  		t.Run(tt.name, func(t *testing.T) {
   160  			op, err := NewOp(
   161  				tt.opType,
   162  				NodeParams{
   163  					LNode:                parser.NodeID(rune(0)),
   164  					RNode:                parser.NodeID(rune(1)),
   165  					ReturnBool:           false,
   166  					VectorMatcherBuilder: emptyVectorMatcherBuilder,
   167  				},
   168  			)
   169  			require.NoError(t, err)
   170  
   171  			c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
   172  			node := op.(baseOp).Node(c, transform.Options{})
   173  
   174  			err = node.Process(
   175  				models.NoopQueryContext(),
   176  				parser.NodeID(rune(0)),
   177  				block.NewScalar(tt.lVal, block.Metadata{
   178  					Bounds: bounds,
   179  					Tags:   models.EmptyTags(),
   180  				}),
   181  			)
   182  
   183  			require.NoError(t, err)
   184  			err = node.Process(
   185  				models.NoopQueryContext(),
   186  				parser.NodeID(rune(1)),
   187  				block.NewScalar(tt.rVal, block.Metadata{
   188  					Bounds: bounds,
   189  					Tags:   models.EmptyTags(),
   190  				}),
   191  			)
   192  
   193  			if tt.opType == EqType || tt.opType == NotEqType ||
   194  				tt.opType == GreaterType || tt.opType == LesserType ||
   195  				tt.opType == GreaterEqType || tt.opType == LesserEqType {
   196  				require.Error(t, err, "scalar comparisons must fail without returnBool")
   197  				return
   198  			}
   199  
   200  			require.NoError(t, err, "scalar maths must succeed without returnBool")
   201  
   202  			expected := [][]float64{{
   203  				tt.expected, tt.expected, tt.expected,
   204  				tt.expected, tt.expected,
   205  			}}
   206  
   207  			compare.EqualsWithNans(t, expected, sink.Values)
   208  
   209  			assert.Equal(t, bounds, sink.Meta.Bounds)
   210  			assert.Equal(t, 0, sink.Meta.Tags.Len())
   211  
   212  			assert.Len(t, sink.Metas, 1)
   213  			assert.Equal(t, []byte(nil), sink.Metas[0].Name)
   214  			assert.Equal(t, 0, sink.Metas[0].Tags.Len())
   215  		})
   216  	}
   217  }
   218  
   219  var singleSeriesTests = []struct {
   220  	name         string
   221  	seriesValues [][]float64
   222  	scalarVal    float64
   223  	opType       string
   224  	seriesLeft   bool
   225  	expected     [][]float64
   226  	expectedBool [][]float64
   227  }{
   228  	/* Arithmetic */
   229  	// +
   230  	{
   231  		name:         "series + scalar",
   232  		seriesValues: [][]float64{{1, math.NaN(), 3}, {4, 5, 6}},
   233  		opType:       PlusType,
   234  		scalarVal:    10,
   235  		seriesLeft:   true,
   236  		expected:     [][]float64{{11, math.NaN(), 13}, {14, 15, 16}},
   237  		expectedBool: [][]float64{{11, math.NaN(), 13}, {14, 15, 16}},
   238  	},
   239  	{
   240  		name:         "scalar + series",
   241  		scalarVal:    10,
   242  		opType:       PlusType,
   243  		seriesValues: [][]float64{{1, 2, 3}, {4, 5, math.NaN()}},
   244  		seriesLeft:   false,
   245  		expected:     [][]float64{{11, 12, 13}, {14, 15, math.NaN()}},
   246  		expectedBool: [][]float64{{11, 12, 13}, {14, 15, math.NaN()}},
   247  	},
   248  	// -
   249  	{
   250  		name:         "series - scalar",
   251  		seriesValues: [][]float64{{1, 2, 3}, {4, 5, 6}},
   252  		opType:       MinusType,
   253  		scalarVal:    10,
   254  		seriesLeft:   true,
   255  		expected:     [][]float64{{-9, -8, -7}, {-6, -5, -4}},
   256  		expectedBool: [][]float64{{-9, -8, -7}, {-6, -5, -4}},
   257  	},
   258  	{
   259  		name:         "scalar - series",
   260  		scalarVal:    10,
   261  		opType:       MinusType,
   262  		seriesValues: [][]float64{{1, 2, 3}, {4, 5, 6}},
   263  		seriesLeft:   false,
   264  		expected:     [][]float64{{9, 8, 7}, {6, 5, 4}},
   265  		expectedBool: [][]float64{{9, 8, 7}, {6, 5, 4}},
   266  	},
   267  	// *
   268  	{
   269  		name: "series * scalar",
   270  		seriesValues: [][]float64{
   271  			{-1, 0, math.NaN()},
   272  			{math.MaxFloat64 - 1, -1 * (math.MaxFloat64 - 1), 1},
   273  		},
   274  		opType:       MultiplyType,
   275  		scalarVal:    10,
   276  		seriesLeft:   true,
   277  		expected:     [][]float64{{-10, 0, math.NaN()}, {math.Inf(1), math.Inf(-1), 10}},
   278  		expectedBool: [][]float64{{-10, 0, math.NaN()}, {math.Inf(1), math.Inf(-1), 10}},
   279  	},
   280  	{
   281  		name:         "scalar * series",
   282  		scalarVal:    10,
   283  		opType:       MultiplyType,
   284  		seriesValues: [][]float64{{-1, 0, math.NaN(), math.MaxFloat64 - 1, -1 * (math.MaxFloat64 - 1), 1}},
   285  		seriesLeft:   false,
   286  		expected:     [][]float64{{-10, 0, math.NaN(), math.Inf(1), math.Inf(-1), 10}},
   287  		expectedBool: [][]float64{{-10, 0, math.NaN(), math.Inf(1), math.Inf(-1), 10}},
   288  	},
   289  	// /
   290  	{
   291  		name:         "series / scalar",
   292  		seriesValues: [][]float64{{10, 0, 5, math.Inf(1), math.Inf(-1), math.NaN()}},
   293  		opType:       DivType,
   294  		scalarVal:    10,
   295  		seriesLeft:   true,
   296  		expected:     [][]float64{{1, 0, 0.5, math.Inf(1), math.Inf(-1), math.NaN()}},
   297  		expectedBool: [][]float64{{1, 0, 0.5, math.Inf(1), math.Inf(-1), math.NaN()}},
   298  	},
   299  	{
   300  		name:         "scalar / series",
   301  		scalarVal:    10,
   302  		opType:       DivType,
   303  		seriesValues: [][]float64{{10, 0, 5, math.Inf(1), math.Inf(-1), math.NaN()}},
   304  		seriesLeft:   false,
   305  		expected:     [][]float64{{1, math.Inf(1), 2, 0, 0, math.NaN()}},
   306  		expectedBool: [][]float64{{1, math.Inf(1), 2, 0, 0, math.NaN()}},
   307  	},
   308  	{
   309  		name:         "series / 0",
   310  		seriesValues: [][]float64{{1, -2}, {3, -4}, {0, math.NaN()}},
   311  		opType:       DivType,
   312  		scalarVal:    0,
   313  		seriesLeft:   true,
   314  		expected:     [][]float64{{math.Inf(1), math.Inf(-1)}, {math.Inf(1), math.Inf(-1)}, {math.NaN(), math.NaN()}},
   315  		expectedBool: [][]float64{{math.Inf(1), math.Inf(-1)}, {math.Inf(1), math.Inf(-1)}, {math.NaN(), math.NaN()}},
   316  	},
   317  	// ^
   318  	{
   319  		name:         "series ^ scalar",
   320  		seriesValues: [][]float64{{1, 2, 3}, {4, math.NaN(), math.MaxFloat64}},
   321  		opType:       ExpType,
   322  		scalarVal:    2,
   323  		seriesLeft:   true,
   324  		expected:     [][]float64{{1, 4, 9}, {16, math.NaN(), math.Inf(1)}},
   325  		expectedBool: [][]float64{{1, 4, 9}, {16, math.NaN(), math.Inf(1)}},
   326  	},
   327  	{
   328  		name:         "scalar ^ series",
   329  		scalarVal:    10,
   330  		opType:       ExpType,
   331  		seriesValues: [][]float64{{1, 2, 3}, {4, 5, 6}},
   332  		seriesLeft:   false,
   333  		expected:     [][]float64{{10, 100, 1000}, {10000, 100000, 1000000}},
   334  		expectedBool: [][]float64{{10, 100, 1000}, {10000, 100000, 1000000}},
   335  	},
   336  	{
   337  		name:         "series ^ 0",
   338  		seriesValues: [][]float64{{1, 2, 3}, {1, math.NaN(), math.MaxFloat64}},
   339  		opType:       ExpType,
   340  		scalarVal:    0,
   341  		seriesLeft:   true,
   342  		expected:     [][]float64{{1, 1, 1}, {1, 1, 1}},
   343  		expectedBool: [][]float64{{1, 1, 1}, {1, 1, 1}},
   344  	},
   345  	{
   346  		name:         "series ^ 0.5",
   347  		seriesValues: [][]float64{{1, 4, 9}},
   348  		opType:       ExpType,
   349  		scalarVal:    0.5,
   350  		seriesLeft:   true,
   351  		expected:     [][]float64{{1, 2, 3}},
   352  		expectedBool: [][]float64{{1, 2, 3}},
   353  	},
   354  	{
   355  		name:         "series ^ -1",
   356  		seriesValues: [][]float64{{1, 2, 4}},
   357  		opType:       ExpType,
   358  		scalarVal:    -1,
   359  		seriesLeft:   true,
   360  		expected:     [][]float64{{1, .5, .25}},
   361  		expectedBool: [][]float64{{1, .5, .25}},
   362  	},
   363  	// %
   364  	{
   365  		name:         "series % scalar",
   366  		seriesValues: [][]float64{{1, 2, 3}, {14, -105, 60}},
   367  		opType:       ModType,
   368  		scalarVal:    10,
   369  		seriesLeft:   true,
   370  		expected:     [][]float64{{1, 2, 3}, {4, -5, 0}},
   371  		expectedBool: [][]float64{{1, 2, 3}, {4, -5, 0}},
   372  	},
   373  	{
   374  		name:         "scalar % series",
   375  		scalarVal:    10,
   376  		opType:       ModType,
   377  		seriesValues: [][]float64{{1, 2, 3}, {4, 5, 6}},
   378  		seriesLeft:   false,
   379  		expected:     [][]float64{{0, 0, 1}, {2, 0, 4}},
   380  		expectedBool: [][]float64{{0, 0, 1}, {2, 0, 4}},
   381  	},
   382  	/* Comparison */
   383  	// ==
   384  	{
   385  		name: "series == scalar",
   386  		seriesValues: [][]float64{{-10, 0, 1, 9, 10},
   387  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)}},
   388  		opType:     EqType,
   389  		scalarVal:  10,
   390  		seriesLeft: true,
   391  		expected: [][]float64{{math.NaN(), math.NaN(), math.NaN(), math.NaN(), 10},
   392  			{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN()}},
   393  		expectedBool: [][]float64{{0, 0, 0, 0, 1}, {0, 0, 0, 0, 0}},
   394  	},
   395  	{
   396  		name:      "scalar == series",
   397  		scalarVal: 10,
   398  		opType:    EqType,
   399  		seriesValues: [][]float64{{-10, 0, 1, 9, 10},
   400  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)}},
   401  		seriesLeft: false,
   402  		expected: [][]float64{{math.NaN(), math.NaN(), math.NaN(), math.NaN(), 10},
   403  			{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN()}},
   404  		expectedBool: [][]float64{{0, 0, 0, 0, 1}, {0, 0, 0, 0, 0}},
   405  	},
   406  	// !=
   407  	{
   408  		name:         "series != scalar",
   409  		seriesValues: [][]float64{{-10, 0, 1, 9, 10}, {11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)}},
   410  		opType:       NotEqType,
   411  		scalarVal:    10,
   412  		seriesLeft:   true,
   413  		expected:     [][]float64{{-10, 0, 1, 9, math.NaN()}, {11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)}},
   414  		expectedBool: [][]float64{{1, 1, 1, 1, 0}, {1, 1, 1, 1, 1}},
   415  	},
   416  	{
   417  		name:         "scalar != series",
   418  		scalarVal:    10,
   419  		opType:       NotEqType,
   420  		seriesValues: [][]float64{{-10, 0, 1, 9, 10}, {11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)}},
   421  		seriesLeft:   false,
   422  		expected:     [][]float64{{10, 10, 10, 10, math.NaN()}, {10, 10, 10, 10, 10}},
   423  		expectedBool: [][]float64{{1, 1, 1, 1, 0}, {1, 1, 1, 1, 1}},
   424  	},
   425  	// >
   426  	{
   427  		name: "series > scalar",
   428  		seriesValues: [][]float64{{-10, 0, 1, 9, 10},
   429  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)}},
   430  		opType:     GreaterType,
   431  		scalarVal:  10,
   432  		seriesLeft: true,
   433  		expected: [][]float64{
   434  			{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN()},
   435  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.NaN()},
   436  		},
   437  		expectedBool: [][]float64{{0, 0, 0, 0, 0}, {1, 1, 0, 1, 0}},
   438  	},
   439  	{
   440  		name:      "scalar > series",
   441  		scalarVal: 10,
   442  		opType:    GreaterType,
   443  		seriesValues: [][]float64{
   444  			{-10, 0, 1, 9, 10},
   445  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)},
   446  		},
   447  		seriesLeft: false,
   448  		expected: [][]float64{
   449  			{10, 10, 10, 10, math.NaN()},
   450  			{math.NaN(), math.NaN(), math.NaN(), math.NaN(), 10},
   451  		},
   452  		expectedBool: [][]float64{{1, 1, 1, 1, 0}, {0, 0, 0, 0, 1}},
   453  	},
   454  	// >
   455  	{
   456  		name: "series < scalar",
   457  		seriesValues: [][]float64{
   458  			{-10, 0, 1, 9, 10},
   459  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)},
   460  		},
   461  		opType:     LesserType,
   462  		scalarVal:  10,
   463  		seriesLeft: true,
   464  		expected: [][]float64{
   465  			{-10, 0, 1, 9, math.NaN()},
   466  			{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.Inf(-1)},
   467  		},
   468  		expectedBool: [][]float64{{1, 1, 1, 1, 0}, {0, 0, 0, 0, 1}},
   469  	},
   470  	{
   471  		name:      "scalar < series",
   472  		scalarVal: 10,
   473  		opType:    LesserType,
   474  		seriesValues: [][]float64{{-10, 0, 1, 9, 10},
   475  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)}},
   476  		seriesLeft: false,
   477  		expected: [][]float64{
   478  			{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.NaN()},
   479  			{10, 10, math.NaN(), 10, math.NaN()},
   480  		},
   481  		expectedBool: [][]float64{{0, 0, 0, 0, 0}, {1, 1, 0, 1, 0}},
   482  	},
   483  	// >=
   484  	{
   485  		name: "series >= scalar",
   486  		seriesValues: [][]float64{{-10, 0, 1, 9, 10},
   487  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)}},
   488  		opType:     GreaterEqType,
   489  		scalarVal:  10,
   490  		seriesLeft: true,
   491  		expected: [][]float64{{math.NaN(), math.NaN(), math.NaN(), math.NaN(), 10},
   492  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.NaN()}},
   493  		expectedBool: [][]float64{{0, 0, 0, 0, 1}, {1, 1, 0, 1, 0}},
   494  	},
   495  	{
   496  		name:      "scalar >= series",
   497  		scalarVal: 10,
   498  		opType:    GreaterEqType,
   499  		seriesValues: [][]float64{
   500  			{-10, 0, 1, 9, 10},
   501  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)},
   502  		},
   503  		seriesLeft: false,
   504  		expected: [][]float64{
   505  			{10, 10, 10, 10, 10},
   506  			{math.NaN(), math.NaN(), math.NaN(), math.NaN(), 10},
   507  		},
   508  		expectedBool: [][]float64{{1, 1, 1, 1, 1}, {0, 0, 0, 0, 1}},
   509  	},
   510  	// <=
   511  	{
   512  		name: "series <= scalar",
   513  		seriesValues: [][]float64{
   514  			{-10, 0, 1, 9, 10},
   515  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)},
   516  		},
   517  		opType:     LesserEqType,
   518  		scalarVal:  10,
   519  		seriesLeft: true,
   520  		expected: [][]float64{
   521  			{-10, 0, 1, 9, 10},
   522  			{math.NaN(), math.NaN(), math.NaN(), math.NaN(), math.Inf(-1)},
   523  		},
   524  		expectedBool: [][]float64{{1, 1, 1, 1, 1}, {0, 0, 0, 0, 1}},
   525  	},
   526  	{
   527  		name:      "scalar <= series",
   528  		scalarVal: 10,
   529  		opType:    LesserEqType,
   530  		seriesValues: [][]float64{
   531  			{-10, 0, 1, 9, 10},
   532  			{11, math.MaxFloat64, math.NaN(), math.Inf(1), math.Inf(-1)},
   533  		},
   534  		seriesLeft: false,
   535  		expected: [][]float64{
   536  			{math.NaN(), math.NaN(), math.NaN(), math.NaN(), 10},
   537  			{10, 10, math.NaN(), 10, math.NaN()},
   538  		},
   539  		expectedBool: [][]float64{{0, 0, 0, 0, 1}, {1, 1, 0, 1, 0}},
   540  	},
   541  }
   542  
   543  func TestSingleSeriesReturnBool(t *testing.T) {
   544  	now := xtime.Now()
   545  
   546  	for _, tt := range singleSeriesTests {
   547  		t.Run(tt.name, func(t *testing.T) {
   548  			op, err := NewOp(
   549  				tt.opType,
   550  				NodeParams{
   551  					LNode:                parser.NodeID(rune(0)),
   552  					RNode:                parser.NodeID(rune(1)),
   553  					ReturnBool:           true,
   554  					VectorMatcherBuilder: emptyVectorMatcherBuilder,
   555  				},
   556  			)
   557  			require.NoError(t, err)
   558  
   559  			c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
   560  			node := op.(baseOp).Node(c, transform.Options{})
   561  
   562  			seriesValues := tt.seriesValues
   563  			metas := test.NewSeriesMeta("a", len(seriesValues))
   564  			bounds := models.Bounds{
   565  				Start:    now,
   566  				Duration: time.Minute * time.Duration(len(seriesValues[0])),
   567  				StepSize: time.Minute,
   568  			}
   569  
   570  			series := test.NewBlockFromValuesWithSeriesMeta(bounds, metas, seriesValues)
   571  			// Set the series and scalar blocks on the correct sides
   572  			if tt.seriesLeft {
   573  				err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), series)
   574  				require.NoError(t, err)
   575  
   576  				err = node.Process(
   577  					models.NoopQueryContext(),
   578  					parser.NodeID(rune(1)),
   579  					block.NewScalar(tt.scalarVal, block.Metadata{
   580  						Bounds: bounds,
   581  						Tags:   models.EmptyTags(),
   582  					}),
   583  				)
   584  
   585  				require.NoError(t, err)
   586  			} else {
   587  				err = node.Process(
   588  					models.NoopQueryContext(),
   589  					parser.NodeID(rune(0)),
   590  					block.NewScalar(tt.scalarVal, block.Metadata{
   591  						Bounds: bounds,
   592  						Tags:   models.EmptyTags(),
   593  					}),
   594  				)
   595  
   596  				require.NoError(t, err)
   597  				err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)), series)
   598  				require.NoError(t, err)
   599  			}
   600  
   601  			compare.EqualsWithNans(t, tt.expectedBool, sink.Values)
   602  
   603  			assert.Equal(t, bounds, sink.Meta.Bounds)
   604  			assert.Equal(t, 0, sink.Meta.Tags.Len())
   605  
   606  			assert.Equal(t, metas, sink.Metas)
   607  		})
   608  	}
   609  }
   610  
   611  func TestSingleSeriesReturnValues(t *testing.T) {
   612  	now := xtime.Now()
   613  
   614  	for _, tt := range singleSeriesTests {
   615  		t.Run(tt.name, func(t *testing.T) {
   616  			op, err := NewOp(
   617  				tt.opType,
   618  				NodeParams{
   619  					LNode:                parser.NodeID(rune(0)),
   620  					RNode:                parser.NodeID(rune(1)),
   621  					ReturnBool:           false,
   622  					VectorMatcherBuilder: emptyVectorMatcherBuilder,
   623  				},
   624  			)
   625  
   626  			require.NoError(t, err)
   627  			c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
   628  			node := op.(baseOp).Node(c, transform.Options{})
   629  
   630  			seriesValues := tt.seriesValues
   631  			metas := test.NewSeriesMeta("a", len(seriesValues))
   632  			bounds := models.Bounds{
   633  				Start:    now,
   634  				Duration: time.Minute * time.Duration(len(seriesValues[0])),
   635  				StepSize: time.Minute,
   636  			}
   637  
   638  			series := test.NewBlockFromValuesWithSeriesMeta(bounds, metas, seriesValues)
   639  			// Set the series and scalar blocks on the correct sides
   640  			if tt.seriesLeft {
   641  				err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), series)
   642  				require.NoError(t, err)
   643  
   644  				err = node.Process(
   645  					models.NoopQueryContext(),
   646  					parser.NodeID(rune(1)),
   647  					block.NewScalar(tt.scalarVal, block.Metadata{
   648  						Bounds: bounds,
   649  						Tags:   models.EmptyTags(),
   650  					}),
   651  				)
   652  
   653  				require.NoError(t, err)
   654  			} else {
   655  				err = node.Process(
   656  					models.NoopQueryContext(),
   657  					parser.NodeID(rune(0)),
   658  					block.NewScalar(tt.scalarVal, block.Metadata{
   659  						Bounds: bounds,
   660  						Tags:   models.EmptyTags(),
   661  					}),
   662  				)
   663  
   664  				require.NoError(t, err)
   665  				err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)), series)
   666  				require.NoError(t, err)
   667  			}
   668  
   669  			compare.EqualsWithNans(t, tt.expected, sink.Values)
   670  
   671  			assert.Equal(t, bounds, sink.Meta.Bounds)
   672  			assert.Equal(t, 0, sink.Meta.Tags.Len())
   673  
   674  			assert.Equal(t, metas, sink.Metas)
   675  		})
   676  	}
   677  }
   678  
   679  var bothSeriesTests = []struct {
   680  	name          string
   681  	opType        string
   682  	lhsMeta       []block.SeriesMeta
   683  	lhs           [][]float64
   684  	rhsMeta       []block.SeriesMeta
   685  	rhs           [][]float64
   686  	returnBool    bool
   687  	expectedMetas []block.SeriesMeta
   688  	expected      [][]float64
   689  }{
   690  	/* Arithmetic */
   691  	{
   692  		"+, second series matches first",
   693  		PlusType,
   694  		test.NewSeriesMeta("a", 2),
   695  		[][]float64{{1, 2, 3}, {4, 5, 6}},
   696  		test.NewSeriesMeta("a", 3)[1:],
   697  		[][]float64{{10, 20, 30}, {40, 50, 60}},
   698  		true,
   699  		test.NewSeriesMeta("a", 2)[1:],
   700  		[][]float64{{14, 25, 36}},
   701  	},
   702  	{
   703  		"-, first two series on lhs match",
   704  		MinusType,
   705  		test.NewSeriesMeta("a", 2),
   706  		[][]float64{{1, 2, 3}, {4, 5, 6}},
   707  		test.NewSeriesMeta("a", 3),
   708  		[][]float64{{10, 20, 30}, {40, 50, 60}, {700, 800, 900}},
   709  		true,
   710  		test.NewSeriesMeta("a", 2),
   711  		[][]float64{{-9, -18, -27}, {-36, -45, -54}},
   712  	},
   713  	{
   714  		"*, last two series on lhs match",
   715  		MultiplyType,
   716  		test.NewSeriesMeta("a", 3),
   717  		[][]float64{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},
   718  		test.NewSeriesMeta("a", 3)[1:],
   719  		[][]float64{{10, 20, 30}, {40, 50, 60}},
   720  		true,
   721  		test.NewSeriesMeta("a", 3)[1:],
   722  		[][]float64{{40, 100, 180}, {280, 400, 540}},
   723  	},
   724  	{
   725  		"/, both series match",
   726  		DivType,
   727  		test.NewSeriesMeta("a", 2),
   728  		[][]float64{{1, 2, 3}, {40, 50, 60}},
   729  		test.NewSeriesMeta("a", 2),
   730  		[][]float64{{10, 20, 30}, {4, 5, 6}},
   731  		true,
   732  		test.NewSeriesMeta("a", 2),
   733  		[][]float64{{0.1, 0.1, 0.1}, {10, 10, 10}},
   734  	},
   735  	{
   736  		"^, single matching series",
   737  		ExpType,
   738  		test.NewSeriesMeta("a", 1),
   739  		[][]float64{{10, math.NaN(), -1, 9, 10, 4}},
   740  		test.NewSeriesMeta("a", 1),
   741  		[][]float64{{2, 2, 0.5, 0.5, -1, -0.5}},
   742  		true,
   743  		test.NewSeriesMeta("a", 1),
   744  		[][]float64{{100, math.NaN(), math.NaN(), 3, 0.1, 0.5}},
   745  	},
   746  	{
   747  		"%, single matching series",
   748  		ModType,
   749  		test.NewSeriesMeta("a", 1),
   750  		[][]float64{{10, 11, 12, 13, 14, 15}},
   751  		test.NewSeriesMeta("a", 1),
   752  		[][]float64{{2, 3, -5, 1.5, 1.5, -1.5}},
   753  		true,
   754  		test.NewSeriesMeta("a", 1),
   755  		[][]float64{{0, 2, 2, 1, 0.5, 0}},
   756  	},
   757  	/* Comparison */
   758  	{
   759  		"==, second series matches first",
   760  		EqType,
   761  		test.NewSeriesMeta("a", 2),
   762  		[][]float64{{1, 2, 3}, {3, 6, 9}},
   763  		test.NewSeriesMeta("a", 3)[1:],
   764  		[][]float64{{10, 6, 30}, {40, 50, 60}},
   765  		false,
   766  		test.NewSeriesMeta("a", 2)[1:],
   767  		[][]float64{{math.NaN(), 6, math.NaN()}},
   768  	},
   769  	{
   770  		"== BOOL, second series matches first",
   771  		EqType,
   772  		test.NewSeriesMeta("a", 2),
   773  		[][]float64{{1, 2, 3}, {3, 6, 9}},
   774  		test.NewSeriesMeta("a", 3)[1:],
   775  		[][]float64{{10, 6, 30}, {40, 50, 60}},
   776  		true,
   777  		test.NewSeriesMeta("a", 2)[1:],
   778  		[][]float64{{0, 1, 0}},
   779  	},
   780  	{
   781  		"=! BOOL, both series match",
   782  		NotEqType,
   783  		test.NewSeriesMeta("a", 2),
   784  		[][]float64{{1, 2, 3}, {3, 6, 9}},
   785  		test.NewSeriesMeta("a", 2),
   786  		[][]float64{{1, 20, 3}, {40, 6, 60}},
   787  		true,
   788  		test.NewSeriesMeta("a", 2),
   789  		[][]float64{{0, 1, 0}, {1, 0, 1}},
   790  	},
   791  	{
   792  		"=!, both series match",
   793  		NotEqType,
   794  		test.NewSeriesMeta("a", 2),
   795  		[][]float64{{1, 2, 3}, {3, 6, 9}},
   796  		test.NewSeriesMeta("a", 2),
   797  		[][]float64{{1, 20, 3}, {40, 6, 60}},
   798  		false,
   799  		test.NewSeriesMeta("a", 2),
   800  		[][]float64{{math.NaN(), 2, math.NaN()}, {3, math.NaN(), 9}},
   801  	},
   802  	{
   803  		"> BOOL, last two series of rhs match",
   804  		GreaterType,
   805  		test.NewSeriesMeta("a", 3)[1:],
   806  		[][]float64{{1, 2, 3}, {3, 6, 9}},
   807  		test.NewSeriesMeta("a", 3),
   808  		[][]float64{{10, 10, 10}, {1, 20, -100}, {2, 4, 10}},
   809  		true,
   810  		test.NewSeriesMeta("a", 3)[1:],
   811  		[][]float64{{0, 0, 1}, {1, 1, 0}},
   812  	},
   813  	{
   814  		">, last two series of rhs match",
   815  		GreaterType,
   816  		test.NewSeriesMeta("a", 3)[1:],
   817  		[][]float64{{1, 2, 3}, {3, 6, 9}},
   818  		test.NewSeriesMeta("a", 3),
   819  		[][]float64{{10, 10, 10}, {1, 20, -100}, {2, 4, 10}},
   820  		false,
   821  		test.NewSeriesMeta("a", 3)[1:],
   822  		[][]float64{{math.NaN(), math.NaN(), 3}, {3, 6, math.NaN()}},
   823  	},
   824  	{
   825  		"< BOOL, single series matches",
   826  		LesserType,
   827  		test.NewSeriesMeta("a", 1),
   828  		[][]float64{{1, 2, 3}},
   829  		test.NewSeriesMeta("a", 1),
   830  		[][]float64{{-1, 2, 5}},
   831  		true,
   832  		test.NewSeriesMeta("a", 1),
   833  		[][]float64{{0, 0, 1}},
   834  	},
   835  	{
   836  		"<, single series matches",
   837  		LesserType,
   838  		test.NewSeriesMeta("a", 1),
   839  		[][]float64{{1, 2, 3}},
   840  		test.NewSeriesMeta("a", 1),
   841  		[][]float64{{-1, 2, 5}},
   842  		false,
   843  		test.NewSeriesMeta("a", 1),
   844  		[][]float64{{math.NaN(), math.NaN(), 3}},
   845  	},
   846  	{
   847  		">= BOOL, single series matches",
   848  		GreaterEqType,
   849  		test.NewSeriesMeta("a", 1),
   850  		[][]float64{{1, 2, 3}},
   851  		test.NewSeriesMeta("a", 1),
   852  		[][]float64{{-1, 2, 5}},
   853  		true,
   854  		test.NewSeriesMeta("a", 1),
   855  		[][]float64{{1, 1, 0}},
   856  	},
   857  	{
   858  		">=, single series matches",
   859  		GreaterEqType,
   860  		test.NewSeriesMeta("a", 1),
   861  		[][]float64{{1, 2, 3}},
   862  		test.NewSeriesMeta("a", 1),
   863  		[][]float64{{-1, 2, 5}},
   864  		false,
   865  		test.NewSeriesMeta("a", 1),
   866  		[][]float64{{1, 2, math.NaN()}},
   867  	},
   868  	{
   869  		"<= BOOL, single series matches",
   870  		LesserEqType,
   871  		test.NewSeriesMeta("a", 1),
   872  		[][]float64{{1, 2, 3}},
   873  		test.NewSeriesMeta("a", 1),
   874  		[][]float64{{-1, 2, 5}},
   875  		true,
   876  		test.NewSeriesMeta("a", 1),
   877  		[][]float64{{0, 1, 1}},
   878  	},
   879  	{
   880  		"<=, single series matches",
   881  		LesserEqType,
   882  		test.NewSeriesMeta("a", 1),
   883  		[][]float64{{1, 2, 3}},
   884  		test.NewSeriesMeta("a", 1),
   885  		[][]float64{{-1, 2, 5}},
   886  		false,
   887  		test.NewSeriesMeta("a", 1),
   888  		[][]float64{{math.NaN(), 2, 3}},
   889  	},
   890  }
   891  
   892  func TestBothSeries(t *testing.T) {
   893  	now := xtime.Now()
   894  
   895  	for _, tt := range bothSeriesTests {
   896  		t.Run(tt.name, func(t *testing.T) {
   897  			op, err := NewOp(
   898  				tt.opType,
   899  				NodeParams{
   900  					LNode:                parser.NodeID(rune(0)),
   901  					RNode:                parser.NodeID(rune(1)),
   902  					ReturnBool:           tt.returnBool,
   903  					VectorMatcherBuilder: emptyVectorMatcherBuilder,
   904  				},
   905  			)
   906  			require.NoError(t, err)
   907  
   908  			c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
   909  			node := op.(baseOp).Node(c, transform.Options{})
   910  			bounds := models.Bounds{
   911  				Start:    now,
   912  				Duration: time.Minute * time.Duration(len(tt.lhs[0])),
   913  				StepSize: time.Minute,
   914  			}
   915  
   916  			err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)),
   917  				test.NewBlockFromValuesWithSeriesMeta(bounds, tt.lhsMeta, tt.lhs))
   918  			require.NoError(t, err)
   919  
   920  			err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)),
   921  				test.NewBlockFromValuesWithSeriesMeta(bounds, tt.rhsMeta, tt.rhs))
   922  			require.NoError(t, err)
   923  
   924  			compare.EqualsWithNans(t, tt.expected, sink.Values)
   925  
   926  			// Extract duped expected metas
   927  			expectedMeta := block.Metadata{
   928  				Bounds:         bounds,
   929  				ResultMetadata: block.NewResultMetadata(),
   930  			}
   931  			var expectedMetas []block.SeriesMeta
   932  			expectedMeta.Tags, expectedMetas = utils.DedupeMetadata(
   933  				tt.expectedMetas, models.NewTagOptions())
   934  			expectedMeta, expectedMetas = removeNameTags(expectedMeta, expectedMetas)
   935  			assert.Equal(t, expectedMeta, sink.Meta)
   936  			assert.Equal(t, expectedMetas, sink.Metas)
   937  		})
   938  	}
   939  }
   940  
   941  func TestBinaryFunctionWithDifferentNames(t *testing.T) {
   942  	now := xtime.Now()
   943  
   944  	meta := func(bounds models.Bounds, name string, m block.ResultMetadata) block.Metadata {
   945  		return block.Metadata{
   946  			Bounds:         bounds,
   947  			Tags:           models.NewTags(1, models.NewTagOptions()).SetName([]byte(name)),
   948  			ResultMetadata: m,
   949  		}
   950  	}
   951  
   952  	var (
   953  		bounds = models.Bounds{
   954  			Start:    now,
   955  			Duration: time.Minute * 3,
   956  			StepSize: time.Minute,
   957  		}
   958  
   959  		lhsResultMeta = block.ResultMetadata{
   960  			LocalOnly:  true,
   961  			Exhaustive: false,
   962  			Warnings:   []block.Warning{},
   963  		}
   964  
   965  		lhsMeta  = meta(bounds, "left", lhsResultMeta)
   966  		lhsMetas = test.NewSeriesMeta("a", 2)
   967  		lhs      = [][]float64{{1, 2, 3}, {4, 5, 6}}
   968  		left     = test.NewBlockFromValuesWithMetaAndSeriesMeta(
   969  			lhsMeta, lhsMetas, lhs,
   970  		)
   971  
   972  		rhsResultMeta = block.ResultMetadata{
   973  			LocalOnly:  false,
   974  			Exhaustive: true,
   975  			Warnings:   []block.Warning{block.Warning{Name: "foo", Message: "bar"}},
   976  		}
   977  
   978  		rhsMeta  = meta(bounds, "right", rhsResultMeta)
   979  		rhsMetas = test.NewSeriesMeta("a", 3)[1:]
   980  		rhs      = [][]float64{{10, 20, 30}, {40, 50, 60}}
   981  		right    = test.NewBlockFromValuesWithMetaAndSeriesMeta(
   982  			rhsMeta, rhsMetas, rhs,
   983  		)
   984  
   985  		expected = [][]float64{{14, 25, 36}}
   986  	)
   987  
   988  	op, err := NewOp(
   989  		PlusType,
   990  		NodeParams{
   991  			LNode:                parser.NodeID(rune(0)),
   992  			RNode:                parser.NodeID(rune(1)),
   993  			VectorMatcherBuilder: emptyVectorMatcherBuilder,
   994  		},
   995  	)
   996  	require.NoError(t, err)
   997  
   998  	c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
   999  	node := op.(baseOp).Node(c, transform.Options{})
  1000  
  1001  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), left)
  1002  	require.NoError(t, err)
  1003  
  1004  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)), right)
  1005  	require.NoError(t, err)
  1006  
  1007  	compare.EqualsWithNans(t, expected, sink.Values)
  1008  
  1009  	exResultMeta := block.ResultMetadata{
  1010  		LocalOnly:  false,
  1011  		Exhaustive: false,
  1012  		Warnings:   []block.Warning{block.Warning{Name: "foo", Message: "bar"}},
  1013  	}
  1014  
  1015  	// Extract duped expected metas
  1016  	expectedMeta := block.Metadata{
  1017  		Bounds:         bounds,
  1018  		Tags:           models.NewTags(1, models.NewTagOptions()).AddTag(toTag("a1", "a1")),
  1019  		ResultMetadata: exResultMeta,
  1020  	}
  1021  
  1022  	expectedMetas := []block.SeriesMeta{
  1023  		block.SeriesMeta{
  1024  			Name: []byte("a1"),
  1025  			Tags: models.EmptyTags(),
  1026  		},
  1027  	}
  1028  
  1029  	assert.Equal(t, expectedMeta, sink.Meta)
  1030  	assert.Equal(t, expectedMetas, sink.Metas)
  1031  }
  1032  
  1033  func TestOneToOneMatcher(t *testing.T) {
  1034  	now := xtime.Now()
  1035  
  1036  	meta := func(bounds models.Bounds, name string, m block.ResultMetadata) block.Metadata {
  1037  		return block.Metadata{
  1038  			Bounds:         bounds,
  1039  			Tags:           models.NewTags(1, models.NewTagOptions()).SetName([]byte(name)),
  1040  			ResultMetadata: m,
  1041  		}
  1042  	}
  1043  
  1044  	var (
  1045  		bounds = models.Bounds{
  1046  			Start:    now,
  1047  			Duration: time.Minute * 3,
  1048  			StepSize: time.Minute,
  1049  		}
  1050  
  1051  		lhsResultMeta = block.ResultMetadata{
  1052  			LocalOnly:  true,
  1053  			Exhaustive: false,
  1054  			Warnings:   []block.Warning{},
  1055  		}
  1056  
  1057  		lhsMeta  = meta(bounds, "left", lhsResultMeta)
  1058  		lhsMetas = test.NewSeriesMeta("a", 2)
  1059  		lhs      = [][]float64{{1, 2, 3}, {4, 5, 6}}
  1060  		left     = test.NewBlockFromValuesWithMetaAndSeriesMeta(
  1061  			lhsMeta, lhsMetas, lhs,
  1062  		)
  1063  
  1064  		rhsResultMeta = block.ResultMetadata{
  1065  			LocalOnly:  false,
  1066  			Exhaustive: true,
  1067  			Warnings:   []block.Warning{{Name: "foo", Message: "bar"}},
  1068  		}
  1069  
  1070  		rhsMeta  = meta(bounds, "right", rhsResultMeta)
  1071  		rhsMetas = test.NewSeriesMeta("a", 3)[1:]
  1072  		rhs      = [][]float64{{10, 20, 30}, {40, 50, 60}}
  1073  		right    = test.NewBlockFromValuesWithMetaAndSeriesMeta(
  1074  			rhsMeta, rhsMetas, rhs,
  1075  		)
  1076  
  1077  		expected = [][]float64{{41, 52, 63}, {14, 25, 36}}
  1078  	)
  1079  
  1080  	op, err := NewOp(
  1081  		PlusType,
  1082  		NodeParams{
  1083  			LNode:                parser.NodeID(rune(0)),
  1084  			RNode:                parser.NodeID(rune(1)),
  1085  			VectorMatcherBuilder: oneToOneVectorMatchingBuilder,
  1086  		},
  1087  	)
  1088  	require.NoError(t, err)
  1089  
  1090  	c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
  1091  	node := op.(baseOp).Node(c, transform.Options{})
  1092  
  1093  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), left)
  1094  	require.NoError(t, err)
  1095  
  1096  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)), right)
  1097  	require.NoError(t, err)
  1098  
  1099  	compare.EqualsWithNans(t, expected, sink.Values)
  1100  
  1101  	expectedMetas := []block.SeriesMeta{
  1102  		{
  1103  			Name: []byte("a0"),
  1104  			Tags: models.EmptyTags(),
  1105  		},
  1106  		{
  1107  			Name: []byte("a1"),
  1108  			Tags: models.NewTags(1, models.NewTagOptions()).AddTag(toTag("a1", "a1")),
  1109  		},
  1110  	}
  1111  
  1112  	assert.Equal(t, expectedMetas, sink.Metas)
  1113  }
  1114  
  1115  func oneToOneVectorMatchingBuilder(_, _ block.Block) VectorMatching {
  1116  	return VectorMatching{
  1117  		Set:            true,
  1118  		Card:           CardOneToOne,
  1119  		On:             true,
  1120  		MatchingLabels: [][]byte{[]byte("a1")},
  1121  	}
  1122  }