github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/rowexec/inverted_expr_evaluator_test.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package rowexec
    12  
    13  import (
    14  	"fmt"
    15  	"math/rand"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/sql/opt/invertedexpr"
    20  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func setToString(set []KeyIndex) string {
    25  	var b strings.Builder
    26  	for i, elem := range set {
    27  		sep := ","
    28  		if i == len(set)-1 {
    29  			sep = ""
    30  		}
    31  		fmt.Fprintf(&b, "%d%s", elem, sep)
    32  	}
    33  	return b.String()
    34  }
    35  
    36  func writeSpan(b *strings.Builder, span invertedSpan) {
    37  	fmt.Fprintf(b, "[%s, %s) ", span.Start, span.End)
    38  }
    39  
    40  func spansToString(spans []invertedSpan) string {
    41  	var b strings.Builder
    42  	for _, elem := range spans {
    43  		writeSpan(&b, elem)
    44  	}
    45  	return b.String()
    46  }
    47  
    48  func spansIndexToString(spans []spansAndSetIndex) string {
    49  	var b strings.Builder
    50  	for _, elem := range spans {
    51  		b.WriteString("spans: ")
    52  		for _, s := range elem.spans {
    53  			writeSpan(&b, s)
    54  		}
    55  		fmt.Fprintf(&b, " setIndex: %d\n", elem.setIndex)
    56  	}
    57  	return b.String()
    58  }
    59  
    60  func fragmentedSpansToString(spans []invertedSpanRoutingInfo) string {
    61  	var b strings.Builder
    62  	for _, elem := range spans {
    63  		b.WriteString("span: ")
    64  		writeSpan(&b, elem.span)
    65  		b.WriteString(" indexes (expr, set): ")
    66  		for _, indexes := range elem.exprAndSetIndexList {
    67  			fmt.Fprintf(&b, "(%d, %d) ", indexes.exprIndex, indexes.setIndex)
    68  		}
    69  		b.WriteString("\n")
    70  	}
    71  	return b.String()
    72  }
    73  
    74  func keyIndexesToString(indexes [][]KeyIndex) string {
    75  	var b strings.Builder
    76  	for i, elem := range indexes {
    77  		fmt.Fprintf(&b, "%d: ", i)
    78  		for _, index := range elem {
    79  			fmt.Fprintf(&b, "%d ", index)
    80  		}
    81  		b.WriteString("\n")
    82  	}
    83  	return b.String()
    84  }
    85  
    86  func TestSetContainerUnion(t *testing.T) {
    87  	defer leaktest.AfterTest(t)()
    88  
    89  	type testCase struct {
    90  		a        setContainer
    91  		b        setContainer
    92  		expected setContainer
    93  	}
    94  	cases := []testCase{
    95  		{a: nil, b: nil, expected: nil},
    96  		{a: []KeyIndex{5}, b: nil, expected: []KeyIndex{5}},
    97  		{a: []KeyIndex{5}, b: []KeyIndex{2, 12}, expected: []KeyIndex{2, 5, 12}},
    98  		{a: []KeyIndex{2, 5}, b: []KeyIndex{12}, expected: []KeyIndex{2, 5, 12}},
    99  		{a: []KeyIndex{2}, b: []KeyIndex{5, 12}, expected: []KeyIndex{2, 5, 12}},
   100  		{a: []KeyIndex{2, 12}, b: []KeyIndex{2, 5}, expected: []KeyIndex{2, 5, 12}},
   101  		{a: []KeyIndex{2, 5}, b: []KeyIndex{5, 12}, expected: []KeyIndex{2, 5, 12}},
   102  	}
   103  	for _, c := range cases {
   104  		require.Equal(t, setToString(c.expected), setToString(unionSetContainers(c.a, c.b)))
   105  	}
   106  }
   107  
   108  func TestSetContainerIntersection(t *testing.T) {
   109  	defer leaktest.AfterTest(t)()
   110  
   111  	type testCase struct {
   112  		a        setContainer
   113  		b        setContainer
   114  		expected setContainer
   115  	}
   116  	cases := []testCase{
   117  		{a: nil, b: nil, expected: nil},
   118  		{a: []KeyIndex{5}, b: nil, expected: nil},
   119  		{a: []KeyIndex{5}, b: []KeyIndex{2, 12}, expected: nil},
   120  		{a: []KeyIndex{2, 12}, b: []KeyIndex{2, 5}, expected: []KeyIndex{2}},
   121  		{a: []KeyIndex{2, 5}, b: []KeyIndex{5, 12}, expected: []KeyIndex{5}},
   122  		{a: []KeyIndex{2, 5, 17, 25, 30}, b: []KeyIndex{2, 12, 13, 17, 23, 30},
   123  			expected: []KeyIndex{2, 17, 30}},
   124  	}
   125  	for _, c := range cases {
   126  		require.Equal(t, setToString(c.expected), setToString(intersectSetContainers(c.a, c.b)))
   127  	}
   128  }
   129  
   130  type keyAndIndex struct {
   131  	key   string
   132  	index int
   133  }
   134  
   135  // Tests both invertedExprEvaluator and batchedInvertedExprEvaluator.
   136  func TestInvertedExpressionEvaluator(t *testing.T) {
   137  	defer leaktest.AfterTest(t)()
   138  
   139  	leaf1 := &spanExpression{
   140  		FactoredUnionSpans: []invertedSpan{{Start: []byte("a"), End: []byte("d")}},
   141  		Operator:           invertedexpr.None,
   142  	}
   143  	leaf2 := &spanExpression{
   144  		FactoredUnionSpans: []invertedSpan{{Start: []byte("e"), End: []byte("h")}},
   145  		Operator:           invertedexpr.None,
   146  	}
   147  	l1Andl2 := &spanExpression{
   148  		FactoredUnionSpans: []invertedSpan{
   149  			{Start: []byte("i"), End: []byte("j")}, {Start: []byte("k"), End: []byte("n")}},
   150  		Operator: invertedexpr.SetIntersection,
   151  		Left:     leaf1,
   152  		Right:    leaf2,
   153  	}
   154  	leaf3 := &spanExpression{
   155  		FactoredUnionSpans: []invertedSpan{{Start: []byte("d"), End: []byte("f")}},
   156  		Operator:           invertedexpr.None,
   157  	}
   158  	leaf4 := &spanExpression{
   159  		FactoredUnionSpans: []invertedSpan{{Start: []byte("a"), End: []byte("c")}},
   160  		Operator:           invertedexpr.None,
   161  	}
   162  	l3Andl4 := &spanExpression{
   163  		FactoredUnionSpans: []invertedSpan{
   164  			{Start: []byte("g"), End: []byte("m")}},
   165  		Operator: invertedexpr.SetIntersection,
   166  		Left:     leaf3,
   167  		Right:    leaf4,
   168  	}
   169  	// In reality, the FactoredUnionSpans of l1Andl2 and l3Andl4 would be moved
   170  	// up to expr, by the factoring code in the invertedexpr package. But the
   171  	// evaluator does not care, and keeping them separate exercises more code.
   172  	exprUnion := &spanExpression{
   173  		Operator: invertedexpr.SetUnion,
   174  		Left:     l1Andl2,
   175  		Right:    l3Andl4,
   176  	}
   177  
   178  	exprIntersection := &spanExpression{
   179  		Operator: invertedexpr.SetIntersection,
   180  		Left:     l1Andl2,
   181  		Right:    l3Andl4,
   182  	}
   183  
   184  	expectedSpansAndSetIndex := "spans: [i, j) [k, n)  setIndex: 1\nspans: [a, d)  setIndex: 2\n" +
   185  		"spans: [e, h)  setIndex: 3\nspans: [g, m)  setIndex: 4\nspans: [d, f)  setIndex: 5\n" +
   186  		"spans: [a, c)  setIndex: 6\n"
   187  
   188  	// Test the getSpansAndSetIndex() method on the invertedExprEvaluator
   189  	// directly. The rest of the methods we will only exercise through
   190  	// batchedInvertedExprEvaluator.
   191  	evalUnion := newInvertedExprEvaluator(exprUnion)
   192  	// Indexes are being assigned using a pre-order traversal.
   193  	require.Equal(t, expectedSpansAndSetIndex,
   194  		spansIndexToString(evalUnion.getSpansAndSetIndex()))
   195  
   196  	evalIntersection := newInvertedExprEvaluator(exprIntersection)
   197  	require.Equal(t, expectedSpansAndSetIndex,
   198  		spansIndexToString(evalIntersection.getSpansAndSetIndex()))
   199  
   200  	// The batchedInvertedExprEvaluators will construct their own
   201  	// invertedExprEvaluators.
   202  	protoUnion := invertedexpr.SpanExpressionProto{Node: *exprUnion}
   203  	batchEvalUnion := &batchedInvertedExprEvaluator{
   204  		exprs: []*invertedexpr.SpanExpressionProto{&protoUnion, nil},
   205  	}
   206  	protoIntersection := invertedexpr.SpanExpressionProto{Node: *exprIntersection}
   207  	batchEvalIntersection := &batchedInvertedExprEvaluator{
   208  		exprs: []*invertedexpr.SpanExpressionProto{&protoIntersection, nil},
   209  	}
   210  	expectedSpans := "[a, n) "
   211  	expectedFragmentedSpans :=
   212  		"span: [a, c)  indexes (expr, set): (0, 6) (0, 2) \n" +
   213  			"span: [c, d)  indexes (expr, set): (0, 2) \n" +
   214  			"span: [d, e)  indexes (expr, set): (0, 5) \n" +
   215  			"span: [e, f)  indexes (expr, set): (0, 5) (0, 3) \n" +
   216  			"span: [f, g)  indexes (expr, set): (0, 3) \n" +
   217  			"span: [g, h)  indexes (expr, set): (0, 3) (0, 4) \n" +
   218  			"span: [h, i)  indexes (expr, set): (0, 4) \n" +
   219  			"span: [i, j)  indexes (expr, set): (0, 1) (0, 4) \n" +
   220  			"span: [j, k)  indexes (expr, set): (0, 4) \n" +
   221  			"span: [k, m)  indexes (expr, set): (0, 4) (0, 1) \n" +
   222  			"span: [m, n)  indexes (expr, set): (0, 1) \n"
   223  
   224  	require.Equal(t, expectedSpans, spansToString(batchEvalUnion.init()))
   225  	require.Equal(t, expectedFragmentedSpans,
   226  		fragmentedSpansToString(batchEvalUnion.fragmentedSpans))
   227  	require.Equal(t, expectedSpans, spansToString(batchEvalIntersection.init()))
   228  	require.Equal(t, expectedFragmentedSpans,
   229  		fragmentedSpansToString(batchEvalIntersection.fragmentedSpans))
   230  
   231  	// Construct the test input. This is hand-constructed to exercise cases where
   232  	// intersections sometimes propagate an index across multiple levels.
   233  	// This input is not in order of the inverted key since the evaluator does
   234  	// not care. In fact, we randomize the input order to exercise the code.
   235  	indexRows := []keyAndIndex{{"ii", 0}, {"a", 1}, {"ee", 2},
   236  		{"aa", 3}, {"ab", 4}, {"g", 4}, {"d", 3},
   237  		{"mn", 5}, {"mp", 6}, {"gh", 6}, {"gi", 7},
   238  		{"aaa", 8}, {"e", 8}, {"aaa", 1}}
   239  	// These expected values were hand-constructed by evaluating the expression over
   240  	// these inputs.
   241  	// (([a, d) ∩ [e, h)) U [i, j) U [k, n)) U
   242  	// (([d, f) ∩ [a, c)) U [g, m))
   243  	expectedUnion := "0: 0 3 4 5 6 7 8 \n1: \n"
   244  	// (([a, d) ∩ [e, h)) U [i, j) U [k, n)) ∩
   245  	// (([d, f) ∩ [a, c)) U [g, m))
   246  	expectedIntersection := "0: 0 4 6 8 \n1: \n"
   247  	rand.Shuffle(len(indexRows), func(i, j int) {
   248  		indexRows[i], indexRows[j] = indexRows[j], indexRows[i]
   249  	})
   250  	for _, elem := range indexRows {
   251  		batchEvalUnion.addIndexRow(invertedexpr.EncInvertedVal(elem.key), elem.index)
   252  		batchEvalIntersection.addIndexRow(invertedexpr.EncInvertedVal(elem.key), elem.index)
   253  	}
   254  	require.Equal(t, expectedUnion, keyIndexesToString(batchEvalUnion.evaluate()))
   255  	require.Equal(t, expectedIntersection, keyIndexesToString(batchEvalIntersection.evaluate()))
   256  
   257  	// Now do both exprUnion and exprIntersection in a single batch.
   258  	batchBoth := batchEvalUnion
   259  	batchBoth.reset()
   260  	batchBoth.exprs = append(batchBoth.exprs, &protoUnion, &protoIntersection)
   261  	batchBoth.init()
   262  	for _, elem := range indexRows {
   263  		batchBoth.addIndexRow(invertedexpr.EncInvertedVal(elem.key), elem.index)
   264  	}
   265  	require.Equal(t, "0: 0 3 4 5 6 7 8 \n1: 0 4 6 8 \n",
   266  		keyIndexesToString(batchBoth.evaluate()))
   267  
   268  	// Reset and evaluate nil expressions.
   269  	batchBoth.reset()
   270  	batchBoth.exprs = append(batchBoth.exprs, nil, nil)
   271  	require.Equal(t, 0, len(batchBoth.init()))
   272  	require.Equal(t, "0: \n1: \n", keyIndexesToString(batchBoth.evaluate()))
   273  }
   274  
   275  // Test fragmentation for routing when multiple expressions in the batch have
   276  // overlapping spans.
   277  func TestFragmentedSpans(t *testing.T) {
   278  	defer leaktest.AfterTest(t)()
   279  
   280  	expr1 := invertedexpr.SpanExpressionProto{
   281  		Node: spanExpression{
   282  			FactoredUnionSpans: []invertedSpan{{Start: []byte("a"), End: []byte("g")}},
   283  			Operator:           invertedexpr.None,
   284  		},
   285  	}
   286  	expr2 := invertedexpr.SpanExpressionProto{
   287  		Node: spanExpression{
   288  			FactoredUnionSpans: []invertedSpan{{Start: []byte("d"), End: []byte("j")}},
   289  			Operator:           invertedexpr.None,
   290  		},
   291  	}
   292  	expr3 := invertedexpr.SpanExpressionProto{
   293  		Node: spanExpression{
   294  			FactoredUnionSpans: []invertedSpan{
   295  				{Start: []byte("e"), End: []byte("f")}, {Start: []byte("i"), End: []byte("l")},
   296  				{Start: []byte("o"), End: []byte("p")}},
   297  			Operator: invertedexpr.None,
   298  		},
   299  	}
   300  	batchEval := &batchedInvertedExprEvaluator{
   301  		exprs: []*invertedexpr.SpanExpressionProto{&expr1, &expr2, &expr3},
   302  	}
   303  	require.Equal(t, "[a, l) [o, p) ", spansToString(batchEval.init()))
   304  	require.Equal(t,
   305  		"span: [a, d)  indexes (expr, set): (0, 0) \n"+
   306  			"span: [d, e)  indexes (expr, set): (0, 0) (1, 0) \n"+
   307  			"span: [e, f)  indexes (expr, set): (2, 0) (0, 0) (1, 0) \n"+
   308  			"span: [f, g)  indexes (expr, set): (0, 0) (1, 0) \n"+
   309  			"span: [g, i)  indexes (expr, set): (1, 0) \n"+
   310  			"span: [i, j)  indexes (expr, set): (1, 0) (2, 0) \n"+
   311  			"span: [j, l)  indexes (expr, set): (2, 0) \n"+
   312  			"span: [o, p)  indexes (expr, set): (2, 0) \n",
   313  		fragmentedSpansToString(batchEval.fragmentedSpans))
   314  }
   315  
   316  // TODO(sumeer): randomized inputs for union, intersection and expression evaluation.