github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/invertedexpr/expression_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 invertedexpr 12 13 import ( 14 "fmt" 15 "strings" 16 "testing" 17 18 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 19 "github.com/cockroachdb/cockroach/pkg/util/treeprinter" 20 "github.com/cockroachdb/datadriven" 21 "github.com/gogo/protobuf/proto" 22 "github.com/stretchr/testify/require" 23 ) 24 25 /* 26 Format for the datadriven test: 27 28 new-span-leaf name=<name> tight=<true|false> span=<start>[,<end>] 29 ---- 30 <SpanExpression as string> 31 32 Creates a new leaf SpanExpression with the given name 33 34 new-unknown-leaf name=<name> tight=<true|false> 35 ---- 36 37 Creates a new leaf unknownExpression with the given name 38 39 new-non-inverted-leaf name=<name> 40 ---- 41 42 Creates a new NonInvertedColExpression with the given name 43 44 and result=<name> left=<name> right=<name> 45 ---- 46 <SpanExpression as string> 47 48 Ands the left and right expressions and stores the result 49 50 or result=<name> left=<name> right=<name> 51 ---- 52 <SpanExpression as string> 53 54 Ors the left and right expressions and stores the result 55 56 to-proto name=<name> 57 ---- 58 <SpanExpressionProto as string> 59 60 Converts the SpanExpression to SpanExpressionProto 61 */ 62 63 func getSpan(t *testing.T, d *datadriven.TestData) InvertedSpan { 64 var str string 65 d.ScanArgs(t, "span", &str) 66 parts := strings.Split(str, ",") 67 if len(parts) > 2 { 68 d.Fatalf(t, "incorrect span format: %s", str) 69 return InvertedSpan{} 70 } else if len(parts) == 2 { 71 return InvertedSpan{start: []byte(parts[0]), end: []byte(parts[1])} 72 } else { 73 return MakeSingleInvertedValSpan([]byte(parts[0])) 74 } 75 } 76 77 type UnknownExpression struct { 78 tight bool 79 } 80 81 func (u UnknownExpression) IsTight() bool { return u.tight } 82 func (u UnknownExpression) SetNotTight() { u.tight = false } 83 func (u UnknownExpression) String() string { 84 return fmt.Sprintf("UnknownExpression: tight=%t", u.tight) 85 } 86 87 // Makes a (shallow) copy of the root node of the expression identified 88 // by name, since calls to And() and Or() can modify that root node, and 89 // the test wants to preserve the unmodified expression for later use. 90 func getExprCopy( 91 t *testing.T, d *datadriven.TestData, name string, exprsByName map[string]InvertedExpression, 92 ) InvertedExpression { 93 expr := exprsByName[name] 94 if expr == nil { 95 d.Fatalf(t, "unknown expr: %s", name) 96 } 97 switch e := expr.(type) { 98 case *SpanExpression: 99 return &SpanExpression{ 100 Tight: e.Tight, 101 SpansToRead: append([]InvertedSpan(nil), e.SpansToRead...), 102 FactoredUnionSpans: append([]InvertedSpan(nil), e.FactoredUnionSpans...), 103 Operator: e.Operator, 104 Left: e.Left, 105 Right: e.Right, 106 } 107 case NonInvertedColExpression: 108 return NonInvertedColExpression{} 109 case UnknownExpression: 110 return UnknownExpression{tight: e.tight} 111 default: 112 d.Fatalf(t, "unknown expr type") 113 return nil 114 } 115 } 116 117 func toString(expr InvertedExpression) string { 118 tp := treeprinter.New() 119 formatExpression(tp, expr) 120 return tp.String() 121 } 122 123 func getLeftAndRightExpr( 124 t *testing.T, d *datadriven.TestData, exprsByName map[string]InvertedExpression, 125 ) (InvertedExpression, InvertedExpression) { 126 var leftName, rightName string 127 d.ScanArgs(t, "left", &leftName) 128 d.ScanArgs(t, "right", &rightName) 129 return getExprCopy(t, d, leftName, exprsByName), getExprCopy(t, d, rightName, exprsByName) 130 } 131 132 func TestExpression(t *testing.T) { 133 defer leaktest.AfterTest(t)() 134 exprsByName := make(map[string]InvertedExpression) 135 136 datadriven.RunTest(t, "testdata/expression", func(t *testing.T, d *datadriven.TestData) string { 137 switch d.Cmd { 138 case "new-span-leaf": 139 var name string 140 d.ScanArgs(t, "name", &name) 141 var tight bool 142 d.ScanArgs(t, "tight", &tight) 143 span := getSpan(t, d) 144 expr := ExprForInvertedSpan(span, tight) 145 exprsByName[name] = expr 146 return expr.String() 147 case "new-unknown-leaf": 148 var name string 149 d.ScanArgs(t, "name", &name) 150 var tight bool 151 d.ScanArgs(t, "tight", &tight) 152 expr := UnknownExpression{tight: tight} 153 exprsByName[name] = expr 154 return fmt.Sprintf("%v", expr) 155 case "new-non-inverted-leaf": 156 var name string 157 d.ScanArgs(t, "name", &name) 158 exprsByName[name] = NonInvertedColExpression{} 159 return "" 160 case "and": 161 var name string 162 d.ScanArgs(t, "result", &name) 163 left, right := getLeftAndRightExpr(t, d, exprsByName) 164 expr := And(left, right) 165 exprsByName[name] = expr 166 return toString(expr) 167 case "or": 168 var name string 169 d.ScanArgs(t, "result", &name) 170 left, right := getLeftAndRightExpr(t, d, exprsByName) 171 expr := Or(left, right) 172 exprsByName[name] = expr 173 return toString(expr) 174 case "to-proto": 175 var name string 176 d.ScanArgs(t, "name", &name) 177 expr := exprsByName[name] 178 if expr == nil { 179 expr = (*SpanExpression)(nil) 180 } 181 return proto.MarshalTextString(expr.(*SpanExpression).ToProto()) 182 default: 183 return fmt.Sprintf("unknown command: %s", d.Cmd) 184 } 185 }) 186 } 187 188 func span(start, end string) InvertedSpan { 189 return InvertedSpan{start: []byte(start), end: []byte(end)} 190 } 191 192 func single(start string) InvertedSpan { 193 return MakeSingleInvertedValSpan([]byte(start)) 194 } 195 196 func checkEqual(t *testing.T, expected, actual []InvertedSpan) { 197 require.Equal(t, len(expected), len(actual)) 198 for i := range expected { 199 require.Equal(t, expected[i].start, actual[i].start) 200 require.Equal(t, expected[i].end, actual[i].end) 201 } 202 } 203 204 func TestSetUnion(t *testing.T) { 205 checkEqual(t, 206 []InvertedSpan{span("b", "b\x00")}, 207 unionSpans( 208 []InvertedSpan{single("b")}, 209 []InvertedSpan{span("b", "b\x00")}, 210 ), 211 ) 212 checkEqual(t, 213 []InvertedSpan{span("b", "b\x00\x00")}, 214 unionSpans( 215 []InvertedSpan{single("b")}, 216 []InvertedSpan{single("b\x00")}, 217 ), 218 ) 219 checkEqual(t, 220 []InvertedSpan{span("b", "c")}, 221 unionSpans( 222 []InvertedSpan{single("b")}, 223 []InvertedSpan{span("b\x00", "c")}, 224 ), 225 ) 226 checkEqual(t, 227 []InvertedSpan{span("b", "c")}, 228 unionSpans( 229 []InvertedSpan{span("b", "b\x00")}, 230 []InvertedSpan{span("b", "c")}, 231 ), 232 ) 233 checkEqual(t, 234 []InvertedSpan{span("b", "c"), single("d\x00")}, 235 unionSpans( 236 []InvertedSpan{span("b", "c")}, 237 []InvertedSpan{single("d\x00")}, 238 ), 239 ) 240 checkEqual(t, 241 []InvertedSpan{span("b", "c"), single("d"), single("e")}, 242 unionSpans( 243 []InvertedSpan{span("b", "c"), single("e")}, 244 []InvertedSpan{single("d")}, 245 ), 246 ) 247 checkEqual(t, 248 []InvertedSpan{span("b", "d"), single("e")}, 249 unionSpans( 250 []InvertedSpan{span("b", "c"), single("e")}, 251 []InvertedSpan{span("c", "d")}, 252 ), 253 ) 254 checkEqual(t, 255 []InvertedSpan{span("b", "f")}, 256 unionSpans( 257 []InvertedSpan{span("b", "c"), single("e")}, 258 []InvertedSpan{span("c", "f")}, 259 ), 260 ) 261 } 262 263 func TestSetIntersection(t *testing.T) { 264 checkEqual(t, 265 []InvertedSpan{single("b")}, 266 intersectSpans( 267 []InvertedSpan{single("b")}, 268 []InvertedSpan{span("b", "b\x00")}, 269 ), 270 ) 271 checkEqual(t, 272 nil, 273 intersectSpans( 274 []InvertedSpan{single("b")}, 275 []InvertedSpan{span("b\x00", "c")}, 276 ), 277 ) 278 checkEqual(t, 279 []InvertedSpan{single("b"), span("d", "d\x00"), span("dd", "e"), span("f", "ff")}, 280 intersectSpans( 281 []InvertedSpan{single("b"), span("d", "e"), span("f", "g")}, 282 []InvertedSpan{span("b", "d\x00"), span("dd", "ff")}, 283 ), 284 ) 285 } 286 287 func TestSetSubtraction(t *testing.T) { 288 checkEqual(t, 289 nil, 290 subtractSpans( 291 []InvertedSpan{single("b")}, 292 []InvertedSpan{span("b", "b\x00")}, 293 ), 294 ) 295 checkEqual(t, 296 []InvertedSpan{span("b\x00", "d")}, 297 subtractSpans( 298 []InvertedSpan{span("b", "d")}, 299 []InvertedSpan{span("b", "b\x00")}, 300 ), 301 ) 302 checkEqual(t, 303 []InvertedSpan{span("b", "d"), span("e", "ea")}, 304 subtractSpans( 305 []InvertedSpan{span("b", "d"), span("e", "f")}, 306 []InvertedSpan{span("ea", "f")}, 307 ), 308 ) 309 checkEqual(t, 310 []InvertedSpan{span("d", "da"), span("da\x00", "dc"), 311 span("dd", "df"), span("fa", "g")}, 312 subtractSpans( 313 []InvertedSpan{single("b"), span("d", "e"), span("f", "g")}, 314 []InvertedSpan{span("b", "b\x00"), single("da"), 315 span("dc", "dd"), span("df", "e"), span("f", "fa")}, 316 ), 317 ) 318 319 }