github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/functions/binary/or_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  	"fmt"
    25  	"math"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/query/block"
    30  	"github.com/m3db/m3/src/query/executor/transform"
    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  func TestOrWithExactValues(t *testing.T) {
    43  	values, bounds := test.GenerateValuesAndBounds(nil, nil)
    44  	block1 := test.NewBlockFromValues(bounds, values)
    45  	block2 := test.NewBlockFromValues(bounds, values)
    46  
    47  	op, err := NewOp(
    48  		OrType,
    49  		NodeParams{
    50  			LNode:                parser.NodeID(rune(0)),
    51  			RNode:                parser.NodeID(rune(1)),
    52  			VectorMatcherBuilder: emptyVectorMatcherBuilder,
    53  		},
    54  	)
    55  	require.NoError(t, err)
    56  
    57  	c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
    58  	node := op.(baseOp).Node(c, transform.Options{})
    59  
    60  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)), block2)
    61  	require.NoError(t, err)
    62  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), block1)
    63  	require.NoError(t, err)
    64  	assert.Equal(t, values, sink.Values)
    65  }
    66  
    67  func TestOrWithSomeValues(t *testing.T) {
    68  	values1, bounds := test.GenerateValuesAndBounds(nil, nil)
    69  	block1 := test.NewBlockFromValues(bounds, values1)
    70  
    71  	v := [][]float64{
    72  		{0, math.NaN(), 2, 3, 4},
    73  		{math.NaN(), 6, 7, 8, 9},
    74  	}
    75  
    76  	block2 := test.NewBlockFromValues(bounds, v)
    77  
    78  	op, err := NewOp(
    79  		OrType,
    80  		NodeParams{
    81  			LNode:                parser.NodeID(rune(0)),
    82  			RNode:                parser.NodeID(rune(1)),
    83  			VectorMatcherBuilder: emptyVectorMatcherBuilder,
    84  		},
    85  	)
    86  	require.NoError(t, err)
    87  
    88  	c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
    89  	node := op.(baseOp).Node(c, transform.Options{})
    90  
    91  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)), block2)
    92  	require.NoError(t, err)
    93  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), block1)
    94  	require.NoError(t, err)
    95  	// NAN values should be filled
    96  	expected := values1
    97  
    98  	compare.EqualsWithNans(t, expected, sink.Values)
    99  }
   100  
   101  func generateMetaDataWithTagsInRange(
   102  	fromRange int,
   103  	toRange int,
   104  ) []block.SeriesMeta {
   105  	length := toRange - fromRange
   106  	meta := make([]block.SeriesMeta, length)
   107  	for i := 0; i < length; i++ {
   108  		idx := []byte(fmt.Sprint(fromRange + i))
   109  		tags := test.TagSliceToTags([]models.Tag{{Name: idx, Value: idx}})
   110  		meta[i] = block.SeriesMeta{
   111  			Tags: tags,
   112  			Name: idx,
   113  		}
   114  	}
   115  	return meta
   116  }
   117  
   118  var indexMatchingTests = []struct {
   119  	name     string
   120  	lhs      []block.SeriesMeta
   121  	rhs      []block.SeriesMeta
   122  	expected []int
   123  }{
   124  	{
   125  		"equal tags",
   126  		generateMetaDataWithTagsInRange(0, 5),
   127  		generateMetaDataWithTagsInRange(0, 5),
   128  		[]int{0, 1, 2, 3, 4},
   129  	},
   130  	{
   131  		"empty rhs",
   132  		generateMetaDataWithTagsInRange(0, 5),
   133  		[]block.SeriesMeta{},
   134  		[]int{},
   135  	},
   136  	{
   137  		"empty lhs",
   138  		[]block.SeriesMeta{},
   139  		generateMetaDataWithTagsInRange(0, 5),
   140  		[]int{0, 1, 2, 3, 4},
   141  	},
   142  	{
   143  		"longer rhs",
   144  		generateMetaDataWithTagsInRange(0, 5),
   145  		generateMetaDataWithTagsInRange(-1, 6),
   146  		[]int{5, 0, 1, 2, 3, 4, 6},
   147  	},
   148  	{
   149  		"no overlap",
   150  		generateMetaDataWithTagsInRange(0, 5),
   151  		generateMetaDataWithTagsInRange(6, 9),
   152  		[]int{5, 6, 7},
   153  	},
   154  }
   155  
   156  func TestIndexMerging(t *testing.T) {
   157  	matching := VectorMatching{}
   158  	for _, tt := range indexMatchingTests {
   159  		t.Run(tt.name, func(t *testing.T) {
   160  			matching, _ := mergeIndices(matching, tt.lhs, tt.rhs)
   161  			assert.Equal(t, tt.expected, matching)
   162  		})
   163  	}
   164  }
   165  
   166  var orTests = []struct {
   167  	name          string
   168  	lhsMeta       []block.SeriesMeta
   169  	lhs           [][]float64
   170  	rhsMeta       []block.SeriesMeta
   171  	rhs           [][]float64
   172  	expectedMetas []block.SeriesMeta
   173  	expected      [][]float64
   174  	err           error
   175  }{
   176  	{
   177  		"valid, equal tags",
   178  		test.NewSeriesMeta("a", 2),
   179  		[][]float64{{1, 2}, {10, 20}},
   180  		test.NewSeriesMeta("a", 2),
   181  		[][]float64{{3, 4}, {30, 40}},
   182  		test.NewSeriesMetaWithoutName("a", 2),
   183  		[][]float64{{1, 2}, {10, 20}},
   184  		nil,
   185  	},
   186  	{
   187  		"valid, some overlap",
   188  		test.NewSeriesMeta("a", 2),
   189  		[][]float64{{1, 2}, {10, 20}},
   190  		test.NewSeriesMeta("a", 3),
   191  		[][]float64{{3, 4}, {30, 40}, {50, 60}},
   192  		test.NewSeriesMetaWithoutName("a", 3),
   193  		[][]float64{{1, 2}, {10, 20}, {50, 60}},
   194  		nil,
   195  	},
   196  	{
   197  		"valid, some overlap, updating NaNs",
   198  		test.NewSeriesMeta("a", 2),
   199  		[][]float64{{1, math.NaN()}, {math.NaN(), 20}},
   200  		test.NewSeriesMeta("a", 3),
   201  		[][]float64{{3, 4}, {30, 40}, {50, math.NaN()}},
   202  		test.NewSeriesMetaWithoutName("a", 3),
   203  		[][]float64{{1, 4}, {30, 20}, {50, math.NaN()}},
   204  		nil,
   205  	},
   206  	{
   207  		"valid, equal size",
   208  		test.NewSeriesMeta("a", 2),
   209  		[][]float64{{1, 2}, {10, 20}},
   210  		test.NewSeriesMeta("b", 2),
   211  		[][]float64{{3, 4}, {30, 40}},
   212  		append(
   213  			test.NewSeriesMetaWithoutName("a", 2),
   214  			test.NewSeriesMetaWithoutName("b", 2)...,
   215  		),
   216  		[][]float64{{1, 2}, {10, 20}, {3, 4}, {30, 40}},
   217  		nil,
   218  	},
   219  	{
   220  		"valid, longer rhs",
   221  		test.NewSeriesMeta("a", 2),
   222  		[][]float64{{1, 2}, {10, 20}},
   223  		test.NewSeriesMeta("b", 3),
   224  		[][]float64{{3, 4}, {30, 40}, {300, 400}},
   225  		append(
   226  			test.NewSeriesMetaWithoutName("a", 2),
   227  			test.NewSeriesMetaWithoutName("b", 3)...,
   228  		),
   229  		[][]float64{{1, 2}, {10, 20}, {3, 4}, {30, 40}, {300, 400}},
   230  		nil,
   231  	},
   232  	{
   233  		"valid, longer lhs",
   234  		test.NewSeriesMeta("a", 3),
   235  		[][]float64{{1, 2}, {10, 20}, {100, 200}},
   236  		test.NewSeriesMeta("b", 2),
   237  		[][]float64{{3, 4}, {30, 40}},
   238  		append(
   239  			test.NewSeriesMetaWithoutName("a", 3),
   240  			test.NewSeriesMetaWithoutName("b", 2)...,
   241  		),
   242  		[][]float64{{1, 2}, {10, 20}, {100, 200}, {3, 4}, {30, 40}},
   243  		nil,
   244  	},
   245  	{
   246  		"mismatched step counts",
   247  		test.NewSeriesMeta("a", 2),
   248  		[][]float64{{1, 2, 3}, {10, 20, 30}},
   249  		test.NewSeriesMeta("b", 2),
   250  		[][]float64{{3, 4}, {30, 40}},
   251  		append(
   252  			test.NewSeriesMetaWithoutName("a", 2),
   253  			test.NewSeriesMetaWithoutName("b", 2)...,
   254  		),
   255  		[][]float64{{1, 2}, {10, 20}, {3, 4}, {30, 40}},
   256  		errMismatchedStepCounts,
   257  	},
   258  }
   259  
   260  func TestOrs(t *testing.T) {
   261  	now := xtime.Now()
   262  	for _, tt := range orTests {
   263  		t.Run(tt.name, func(t *testing.T) {
   264  			op, err := NewOp(
   265  				OrType,
   266  				NodeParams{
   267  					LNode:                parser.NodeID(rune(0)),
   268  					RNode:                parser.NodeID(rune(1)),
   269  					VectorMatcherBuilder: emptyVectorMatcherBuilder,
   270  				},
   271  			)
   272  			require.NoError(t, err)
   273  
   274  			c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
   275  			node := op.(baseOp).Node(c, transform.Options{})
   276  			bounds := models.Bounds{
   277  				Start:    now,
   278  				Duration: time.Minute * time.Duration(len(tt.lhs[0])),
   279  				StepSize: time.Minute,
   280  			}
   281  
   282  			lhs := test.NewBlockFromValuesWithSeriesMeta(bounds, tt.lhsMeta, tt.lhs)
   283  			err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), lhs)
   284  			require.NoError(t, err)
   285  
   286  			bounds = models.Bounds{
   287  				Start:    now,
   288  				Duration: time.Minute * time.Duration(len(tt.rhs[0])),
   289  				StepSize: time.Minute,
   290  			}
   291  
   292  			rhs := test.NewBlockFromValuesWithSeriesMeta(bounds, tt.rhsMeta, tt.rhs)
   293  			err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)), rhs)
   294  			if tt.err != nil {
   295  				require.EqualError(t, err, tt.err.Error())
   296  				return
   297  			}
   298  
   299  			require.NoError(t, err)
   300  			compare.EqualsWithNans(t, tt.expected, sink.Values)
   301  			assert.Equal(t, tt.expectedMetas, sink.Metas)
   302  		})
   303  	}
   304  }
   305  
   306  func TestOrsBoundsError(t *testing.T) {
   307  	tt := orTests[0]
   308  	bounds := models.Bounds{
   309  		Start:    xtime.Now(),
   310  		Duration: time.Minute * time.Duration(len(tt.lhs[0])),
   311  		StepSize: time.Minute,
   312  	}
   313  
   314  	op, err := NewOp(
   315  		OrType,
   316  		NodeParams{
   317  			LNode:                parser.NodeID(rune(0)),
   318  			RNode:                parser.NodeID(rune(1)),
   319  			VectorMatcherBuilder: emptyVectorMatcherBuilder,
   320  		},
   321  	)
   322  	require.NoError(t, err)
   323  
   324  	c, _ := executor.NewControllerWithSink(parser.NodeID(rune(2)))
   325  	node := op.(baseOp).Node(c, transform.Options{})
   326  
   327  	lhs := test.NewBlockFromValuesWithSeriesMeta(bounds, tt.lhsMeta, tt.lhs)
   328  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), lhs)
   329  	require.NoError(t, err)
   330  
   331  	differentBounds := models.Bounds{
   332  		Start:    bounds.Start.Add(1),
   333  		Duration: bounds.Duration,
   334  		StepSize: bounds.StepSize,
   335  	}
   336  	rhs := test.NewBlockFromValuesWithSeriesMeta(
   337  		differentBounds, tt.rhsMeta, tt.rhs)
   338  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)), rhs)
   339  	require.EqualError(t, err, errMismatchedBounds.Error())
   340  }
   341  
   342  func createSeriesMeta() []block.SeriesMeta {
   343  	return []block.SeriesMeta{
   344  		{Tags: test.TagSliceToTags([]models.Tag{
   345  			{Name: []byte("foo"), Value: []byte("bar")},
   346  		})},
   347  		{Tags: test.TagSliceToTags([]models.Tag{
   348  			{Name: []byte("baz"), Value: []byte("qux")},
   349  		})},
   350  	}
   351  }
   352  
   353  func TestOrCombinedMetadata(t *testing.T) {
   354  	op, err := NewOp(
   355  		OrType,
   356  		NodeParams{
   357  			LNode:                parser.NodeID(rune(0)),
   358  			RNode:                parser.NodeID(rune(1)),
   359  			VectorMatcherBuilder: emptyVectorMatcherBuilder,
   360  		},
   361  	)
   362  	require.NoError(t, err)
   363  
   364  	c, sink := executor.NewControllerWithSink(parser.NodeID(rune(2)))
   365  	node := op.(baseOp).Node(c, transform.Options{})
   366  
   367  	bounds := models.Bounds{
   368  		Start:    xtime.Now(),
   369  		Duration: time.Minute * 2,
   370  		StepSize: time.Minute,
   371  	}
   372  
   373  	strTags := test.StringTags{
   374  		{N: "a", V: "b"}, {N: "c", V: "d"}, {N: "e", V: "f"},
   375  	}
   376  	lhsMeta := block.Metadata{
   377  		Bounds: bounds,
   378  		Tags:   test.StringTagsToTags(strTags),
   379  	}
   380  
   381  	lSeriesMeta := createSeriesMeta()
   382  	lhs := test.NewBlockFromValuesWithMetaAndSeriesMeta(
   383  		lhsMeta,
   384  		lSeriesMeta,
   385  		[][]float64{{1, 2}, {10, 20}})
   386  
   387  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(0)), lhs)
   388  	require.NoError(t, err)
   389  
   390  	strTags = test.StringTags{
   391  		{N: "a", V: "b"}, {N: "c", V: "*d"}, {N: "g", V: "h"},
   392  	}
   393  	rhsMeta := block.Metadata{
   394  		Bounds: bounds,
   395  		Tags:   test.StringTagsToTags(strTags),
   396  	}
   397  
   398  	// NB (arnikola): since common tags for the series differ,
   399  	// all four series should be included in the combined
   400  	// block despite the individual seriesMeta tags being the same.
   401  	rSeriesMeta := createSeriesMeta()
   402  	rhs := test.NewBlockFromValuesWithMetaAndSeriesMeta(
   403  		rhsMeta,
   404  		rSeriesMeta,
   405  		[][]float64{{3, 4}, {30, 40}})
   406  
   407  	err = node.Process(models.NoopQueryContext(), parser.NodeID(rune(1)), rhs)
   408  	require.NoError(t, err)
   409  
   410  	compare.EqualsWithNans(t, [][]float64{
   411  		{1, 2}, {10, 20}, {3, 4}, {30, 40},
   412  	}, sink.Values)
   413  
   414  	assert.Equal(t, sink.Meta.Bounds, bounds)
   415  	exTags := test.TagSliceToTags([]models.Tag{
   416  		{Name: []byte("a"), Value: []byte("b")},
   417  	})
   418  	assert.Equal(t, exTags.Tags, sink.Meta.Tags.Tags)
   419  
   420  	stringTags := []test.StringTags{
   421  		{{N: "c", V: "d"}, {N: "e", V: "f"}, {N: "foo", V: "bar"}},
   422  		{{N: "baz", V: "qux"}, {N: "c", V: "d"}, {N: "e", V: "f"}},
   423  		{{N: "c", V: "*d"}, {N: "foo", V: "bar"}, {N: "g", V: "h"}},
   424  		{{N: "baz", V: "qux"}, {N: "c", V: "*d"}, {N: "g", V: "h"}},
   425  	}
   426  
   427  	tags := test.StringTagsSliceToTagSlice(stringTags)
   428  	expectedMetas := make([]block.SeriesMeta, len(tags))
   429  	for i, t := range tags {
   430  		expectedMetas[i] = block.SeriesMeta{Tags: t}
   431  	}
   432  
   433  	assert.Equal(t, expectedMetas, sink.Metas)
   434  }