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.