github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/constraint/constraint_test.go (about) 1 // Copyright 2018 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 constraint 12 13 import ( 14 "fmt" 15 "testing" 16 17 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 18 "github.com/cockroachdb/cockroach/pkg/sql/opt" 19 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 20 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 21 ) 22 23 func TestConstraintUnion(t *testing.T) { 24 test := func(t *testing.T, evalCtx *tree.EvalContext, left, right *Constraint, expected string) { 25 t.Helper() 26 clone := *left 27 clone.UnionWith(evalCtx, right) 28 29 if actual := clone.String(); actual != expected { 30 format := "left: %s, right: %s, expected: %v, actual: %v" 31 t.Errorf(format, left.String(), right.String(), expected, actual) 32 } 33 } 34 35 st := cluster.MakeTestingClusterSettings() 36 evalCtx := tree.MakeTestingEvalContext(st) 37 data := newConstraintTestData(&evalCtx) 38 39 // Union constraint with itself. 40 test(t, &evalCtx, &data.c1to10, &data.c1to10, "/1: [/1 - /10]") 41 42 // Merge first spans in each constraint. 43 test(t, &evalCtx, &data.c1to10, &data.c5to25, "/1: [/1 - /25)") 44 test(t, &evalCtx, &data.c5to25, &data.c1to10, "/1: [/1 - /25)") 45 46 // Disjoint spans in each constraint. 47 test(t, &evalCtx, &data.c1to10, &data.c40to50, "/1: [/1 - /10] [/40 - /50]") 48 test(t, &evalCtx, &data.c40to50, &data.c1to10, "/1: [/1 - /10] [/40 - /50]") 49 50 // Adjacent disjoint spans in each constraint. 51 test(t, &evalCtx, &data.c20to30, &data.c30to40, "/1: [/20 - /40]") 52 test(t, &evalCtx, &data.c30to40, &data.c20to30, "/1: [/20 - /40]") 53 54 // Merge multiple spans down to single span. 55 var left, right Constraint 56 left = data.c1to10 57 left.UnionWith(&evalCtx, &data.c20to30) 58 left.UnionWith(&evalCtx, &data.c40to50) 59 60 right = data.c5to25 61 right.UnionWith(&evalCtx, &data.c30to40) 62 63 test(t, &evalCtx, &left, &right, "/1: [/1 - /50]") 64 test(t, &evalCtx, &right, &left, "/1: [/1 - /50]") 65 66 // Multiple disjoint spans on each side. 67 left = data.c1to10 68 left.UnionWith(&evalCtx, &data.c20to30) 69 70 right = data.c40to50 71 right.UnionWith(&evalCtx, &data.c60to70) 72 73 test(t, &evalCtx, &left, &right, "/1: [/1 - /10] [/20 - /30) [/40 - /50] (/60 - /70)") 74 test(t, &evalCtx, &right, &left, "/1: [/1 - /10] [/20 - /30) [/40 - /50] (/60 - /70)") 75 76 // Multiple spans that yield the unconstrained span. 77 left = data.cLt10 78 right = data.c5to25 79 right.UnionWith(&evalCtx, &data.cGt20) 80 81 test(t, &evalCtx, &left, &right, "/1: unconstrained") 82 test(t, &evalCtx, &right, &left, "/1: unconstrained") 83 84 if left.String() != "/1: [ - /10)" { 85 t.Errorf("tryUnionWith failed, but still modified one of the spans: %v", left.String()) 86 } 87 if right.String() != "/1: (/5 - ]" { 88 t.Errorf("tryUnionWith failed, but still modified one of the spans: %v", right.String()) 89 } 90 91 // Multiple columns. 92 expected := "/1/2: [/'cherry'/true - /'strawberry']" 93 test(t, &evalCtx, &data.cherryRaspberry, &data.mangoStrawberry, expected) 94 test(t, &evalCtx, &data.mangoStrawberry, &data.cherryRaspberry, expected) 95 } 96 97 func TestConstraintIntersect(t *testing.T) { 98 test := func(t *testing.T, evalCtx *tree.EvalContext, left, right *Constraint, expected string) { 99 t.Helper() 100 clone := *left 101 clone.IntersectWith(evalCtx, right) 102 if actual := clone.String(); actual != expected { 103 format := "left: %s, right: %s, expected: %v, actual: %v" 104 t.Errorf(format, left.String(), right.String(), expected, actual) 105 } 106 } 107 108 st := cluster.MakeTestingClusterSettings() 109 evalCtx := tree.MakeTestingEvalContext(st) 110 data := newConstraintTestData(&evalCtx) 111 112 // Intersect constraint with itself. 113 test(t, &evalCtx, &data.c1to10, &data.c1to10, "/1: [/1 - /10]") 114 115 // Intersect first spans in each constraint. 116 test(t, &evalCtx, &data.c1to10, &data.c5to25, "/1: (/5 - /10]") 117 test(t, &evalCtx, &data.c5to25, &data.c1to10, "/1: (/5 - /10]") 118 119 // Disjoint spans in each constraint. 120 test(t, &evalCtx, &data.c1to10, &data.c40to50, "/1: contradiction") 121 test(t, &evalCtx, &data.c40to50, &data.c1to10, "/1: contradiction") 122 123 // Intersect multiple spans. 124 var left, right Constraint 125 left = data.c1to10 126 left.UnionWith(&evalCtx, &data.c20to30) 127 left.UnionWith(&evalCtx, &data.c40to50) 128 129 right = data.c5to25 130 right.UnionWith(&evalCtx, &data.c30to40) 131 132 test(t, &evalCtx, &right, &left, "/1: (/5 - /10] [/20 - /25) [/40 - /40]") 133 test(t, &evalCtx, &left, &right, "/1: (/5 - /10] [/20 - /25) [/40 - /40]") 134 135 // Intersect multiple disjoint spans. 136 left = data.c1to10 137 left.UnionWith(&evalCtx, &data.c20to30) 138 139 right = data.c40to50 140 right.UnionWith(&evalCtx, &data.c60to70) 141 142 test(t, &evalCtx, &left, &right, "/1: contradiction") 143 test(t, &evalCtx, &right, &left, "/1: contradiction") 144 145 if left.String() != "/1: [/1 - /10] [/20 - /30)" { 146 t.Errorf("tryIntersectWith failed, but still modified one of the spans: %v", left.String()) 147 } 148 if right.String() != "/1: [/40 - /50] (/60 - /70)" { 149 t.Errorf("tryIntersectWith failed, but still modified one of the spans: %v", right.String()) 150 } 151 152 // Multiple columns. 153 expected := "/1/2: [/'mango'/false - /'raspberry'/false)" 154 test(t, &evalCtx, &data.cherryRaspberry, &data.mangoStrawberry, expected) 155 test(t, &evalCtx, &data.mangoStrawberry, &data.cherryRaspberry, expected) 156 } 157 158 func TestConstraintContainsSpan(t *testing.T) { 159 st := cluster.MakeTestingClusterSettings() 160 evalCtx := tree.MakeTestingEvalContext(st) 161 162 // Each test case has a bunch of spans that are expected to be contained, and 163 // a bunch of spans that are expected not to be contained. 164 testData := []struct { 165 constraint string 166 containedSpans string 167 notContainedSpans string 168 }{ 169 { 170 constraint: "/1: [/1 - /3]", 171 containedSpans: "[/1 - /1] (/1 - /2) (/1 - /3) [/2 - /3] [/1 - /3]", 172 notContainedSpans: "[/0 - /1] (/0 - /1] (/0 - /2] (/0 - /3) [/1 - /4) [/2 - /5]", 173 }, 174 { 175 constraint: "/1/2: [ - /2] [/4 - /4] [/5/3 - /7) [/9 - /9/20]", 176 containedSpans: "[ - /1] [ - /2) [ - /2] [/1 - /2] [/2 - /2] [/4 - /4] " + 177 "[/5/3 - /5/3/1] [/6 - /6] [/5/5 - /7) [/9/10 - /9/15] [/9/19 - /9/20]", 178 notContainedSpans: "[ - /3] [/1 - /3] [/3 - /4] [/3 - /6] [/5/3 - /7] [/6 - /8] " + 179 "[/9/20 - /9/21] [/8 - /9]", 180 }, 181 { 182 constraint: "/1/-2: [/1/5 - /1/2] [/3/5 - /5/2] [/7 - ]", 183 containedSpans: "[/1/5 - /1/2] [/1/4 - /1/3] [/1/4 - /1/2] [/4 - /5) [/4/6 - /5/3] [/7/1 - ]", 184 notContainedSpans: "[/1/5 - /1/1] [/1/3 - /1/1] [/3/6 - /3/5] [/4 - /5] [/4 - /5/1] [/6/10 - ]", 185 }, 186 } 187 188 for i, tc := range testData { 189 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 190 c := ParseConstraint(&evalCtx, tc.constraint) 191 192 spans := parseSpans(&evalCtx, tc.containedSpans) 193 for i := 0; i < spans.Count(); i++ { 194 if sp := spans.Get(i); !c.ContainsSpan(&evalCtx, sp) { 195 t.Errorf("%s should contain span %s", c, sp) 196 } 197 } 198 spans = parseSpans(&evalCtx, tc.notContainedSpans) 199 for i := 0; i < spans.Count(); i++ { 200 if sp := spans.Get(i); c.ContainsSpan(&evalCtx, sp) { 201 t.Errorf("%s should not contain span %s", c, sp) 202 } 203 } 204 }) 205 } 206 } 207 208 func TestConstraintCombine(t *testing.T) { 209 st := cluster.MakeTestingClusterSettings() 210 evalCtx := tree.MakeTestingEvalContext(st) 211 212 testData := []struct { 213 a, b, e string 214 }{ 215 { 216 a: "/1/2: [ - /2] [/4 - /4] [/5/30 - /7] [/9 - /9/20]", 217 b: "/2: [/10 - /10] [/20 - /20] [/30 - /30] [/40 - /40]", 218 e: "/1/2: [ - /2/40] [/4/10 - /4/10] [/4/20 - /4/20] [/4/30 - /4/30] [/4/40 - /4/40] " + 219 "[/5/30 - /7/40] [/9/10 - /9/20]", 220 }, 221 { 222 a: "/1/2/3: [ - /1/10] [/2 - /3/20] [/4/30 - /5] [/6/10 - /6/10]", 223 b: "/3: [/50 - /50] [/60 - /70]", 224 e: "/1/2/3: [ - /1/10/70] [/2 - /3/20/70] [/4/30/50 - /5] [/6/10/50 - /6/10/50] " + 225 "[/6/10/60 - /6/10/70]", 226 }, 227 { 228 a: "/1/2/3/4: [ - /10] [/15 - /15] [/20 - /20/10] [/30 - /40) [/80 - ]", 229 b: "/2/3/4: [/20 - /20/10] [/30 - /30] [/40 - /40]", 230 e: "/1/2/3/4: [ - /10/40] [/15/20 - /15/20/10] [/15/30 - /15/30] [/15/40 - /15/40] " + 231 "[/30/20 - /40) [/80/20 - ]", 232 }, 233 { 234 a: "/1/2/3/4: [ - /10/40] [/15/20 - /15/20/10] [/15/30 - /15/30] [/15/40 - /15/40] " + 235 "[/30/20 - /40) [/80/20 - ]", 236 b: "/4: [/20/10 - /30] [/40 - /40]", 237 e: "/1/2/3/4: [ - /10/40] [/15/20 - /15/20/10/40] [/15/30 - /15/30] [/15/40 - /15/40] " + 238 "[/30/20 - /40) [/80/20 - ]", 239 }, 240 { 241 a: "/1/2: [/1 - /1/6]", 242 b: "/2: [/8 - /8]", 243 e: "/1/2: contradiction", 244 }, 245 } 246 247 for i, tc := range testData { 248 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 249 a := ParseConstraint(&evalCtx, tc.a) 250 b := ParseConstraint(&evalCtx, tc.b) 251 a.Combine(&evalCtx, &b) 252 if res := a.String(); res != tc.e { 253 t.Errorf("expected\n %s; got\n %s", tc.e, res) 254 } 255 }) 256 } 257 } 258 259 func TestConsolidateSpans(t *testing.T) { 260 defer leaktest.AfterTest(t)() 261 st := cluster.MakeTestingClusterSettings() 262 evalCtx := tree.MakeTestingEvalContext(st) 263 264 testData := []struct { 265 s string 266 // expected value 267 e string 268 }{ 269 { 270 s: "[/1 - /2] [/3 - /5] [/7 - /9]", 271 e: "[/1 - /5] [/7 - /9]", 272 }, 273 { 274 s: "[/1 - /2] (/3 - /5] [/7 - /9]", 275 e: "[/1 - /2] (/3 - /5] [/7 - /9]", 276 }, 277 { 278 s: "[/1 - /2) [/3 - /5] [/7 - /9]", 279 e: "[/1 - /2) [/3 - /5] [/7 - /9]", 280 }, 281 { 282 s: "[/1 - /2) (/3 - /5] [/7 - /9]", 283 e: "[/1 - /2) (/3 - /5] [/7 - /9]", 284 }, 285 { 286 s: "[/1/1 - /1/3] [/1/4 - /2]", 287 e: "[/1/1 - /2]", 288 }, 289 { 290 s: "[/1/1/5 - /1/1/3] [/1/1/2 - /1/1/1]", 291 e: "[/1/1/5 - /1/1/1]", 292 }, 293 { 294 s: "[/1/1/5 - /1/1/3] [/1/2/2 - /1/2/1]", 295 e: "[/1/1/5 - /1/1/3] [/1/2/2 - /1/2/1]", 296 }, 297 { 298 s: "[/1 - /2] [/3 - /4] [/5 - /6] [/8 - /9] [/10 - /11] [/12 - /13] [/15 - /16]", 299 e: "[/1 - /6] [/8 - /13] [/15 - /16]", 300 }, 301 { 302 // Test that consolidating two spans preserves the correct type of ending 303 // boundary (#38878). 304 s: "[/1 - /2] [/3 - /5)", 305 e: "[/1 - /5)", 306 }, 307 } 308 309 kc := testKeyContext(1, 2, -3) 310 for i, tc := range testData { 311 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 312 spans := parseSpans(&evalCtx, tc.s) 313 var c Constraint 314 c.Init(kc, &spans) 315 c.ConsolidateSpans(kc.EvalCtx) 316 if res := c.Spans.String(); res != tc.e { 317 t.Errorf("expected %s got %s", tc.e, res) 318 } 319 }) 320 } 321 } 322 323 func TestExactPrefix(t *testing.T) { 324 defer leaktest.AfterTest(t)() 325 st := cluster.MakeTestingClusterSettings() 326 evalCtx := tree.MakeTestingEvalContext(st) 327 328 testData := []struct { 329 s string 330 // expected value 331 e int 332 }{ 333 { 334 s: "", 335 e: 0, 336 }, 337 { 338 s: "[/1 - /1]", 339 e: 1, 340 }, 341 { 342 s: "[/1 - /2]", 343 e: 0, 344 }, 345 { 346 s: "[/1/2/3 - /1/2/3]", 347 e: 3, 348 }, 349 { 350 s: "[/1/2/3 - /1/2/3] [/1/2/5 - /1/2/8]", 351 e: 2, 352 }, 353 { 354 s: "[/1/2/3 - /1/2/3] [/1/2/5 - /1/3/8]", 355 e: 1, 356 }, 357 { 358 s: "[/1/2/3 - /1/2/3] [/1/3/3 - /1/3/3]", 359 e: 1, 360 }, 361 { 362 s: "[/1/2/3 - /1/2/3] [/3 - /4]", 363 e: 0, 364 }, 365 { 366 s: "[/1/2/1 - /1/2/1] [/1/3/1 - /1/4/1]", 367 e: 1, 368 }, 369 } 370 371 kc := testKeyContext(1, 2, 3) 372 for i, tc := range testData { 373 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 374 spans := parseSpans(&evalCtx, tc.s) 375 var c Constraint 376 c.Init(kc, &spans) 377 if res := c.ExactPrefix(kc.EvalCtx); res != tc.e { 378 t.Errorf("expected %d got %d", tc.e, res) 379 } 380 }) 381 } 382 } 383 384 type constraintTestData struct { 385 cLt10 Constraint // [ - /10) 386 cGt20 Constraint // (/20 - ] 387 c1to10 Constraint // [/1 - /10] 388 c5to25 Constraint // (/5 - /25) 389 c20to30 Constraint // [/20 - /30) 390 c30to40 Constraint // [/30 - /40] 391 c40to50 Constraint // [/40 - /50] 392 c60to70 Constraint // (/60 - /70) 393 cherryRaspberry Constraint // [/'cherry'/true - /'raspberry'/false) 394 mangoStrawberry Constraint // [/'mango'/false - /'strawberry'] 395 } 396 397 func newConstraintTestData(evalCtx *tree.EvalContext) *constraintTestData { 398 data := &constraintTestData{} 399 400 key1 := MakeKey(tree.NewDInt(1)) 401 key5 := MakeKey(tree.NewDInt(5)) 402 key10 := MakeKey(tree.NewDInt(10)) 403 key20 := MakeKey(tree.NewDInt(20)) 404 key25 := MakeKey(tree.NewDInt(25)) 405 key30 := MakeKey(tree.NewDInt(30)) 406 key40 := MakeKey(tree.NewDInt(40)) 407 key50 := MakeKey(tree.NewDInt(50)) 408 key60 := MakeKey(tree.NewDInt(60)) 409 key70 := MakeKey(tree.NewDInt(70)) 410 411 kc12 := testKeyContext(1, 2) 412 kc1 := testKeyContext(1) 413 414 cherry := MakeCompositeKey(tree.NewDString("cherry"), tree.DBoolTrue) 415 mango := MakeCompositeKey(tree.NewDString("mango"), tree.DBoolFalse) 416 raspberry := MakeCompositeKey(tree.NewDString("raspberry"), tree.DBoolFalse) 417 strawberry := MakeKey(tree.NewDString("strawberry")) 418 419 var span Span 420 421 // [ - /10) 422 span.Init(EmptyKey, IncludeBoundary, key10, ExcludeBoundary) 423 data.cLt10.InitSingleSpan(kc1, &span) 424 425 // (/20 - ] 426 span.Init(key20, ExcludeBoundary, EmptyKey, IncludeBoundary) 427 data.cGt20.InitSingleSpan(kc1, &span) 428 429 // [/1 - /10] 430 span.Init(key1, IncludeBoundary, key10, IncludeBoundary) 431 data.c1to10.InitSingleSpan(kc1, &span) 432 433 // (/5 - /25) 434 span.Init(key5, ExcludeBoundary, key25, ExcludeBoundary) 435 data.c5to25.InitSingleSpan(kc1, &span) 436 437 // [/20 - /30) 438 span.Init(key20, IncludeBoundary, key30, ExcludeBoundary) 439 data.c20to30.InitSingleSpan(kc1, &span) 440 441 // [/30 - /40] 442 span.Init(key30, IncludeBoundary, key40, IncludeBoundary) 443 data.c30to40.InitSingleSpan(kc1, &span) 444 445 // [/40 - /50] 446 span.Init(key40, IncludeBoundary, key50, IncludeBoundary) 447 data.c40to50.InitSingleSpan(kc1, &span) 448 449 // (/60 - /70) 450 span.Init(key60, ExcludeBoundary, key70, ExcludeBoundary) 451 data.c60to70.InitSingleSpan(kc1, &span) 452 453 // [/'cherry'/true - /'raspberry'/false) 454 span.Init(cherry, IncludeBoundary, raspberry, ExcludeBoundary) 455 data.cherryRaspberry.InitSingleSpan(kc12, &span) 456 457 // [/'mango'/false - /'strawberry'] 458 span.Init(mango, IncludeBoundary, strawberry, IncludeBoundary) 459 data.mangoStrawberry.InitSingleSpan(kc12, &span) 460 461 return data 462 } 463 464 func TestExtractNotNullCols(t *testing.T) { 465 st := cluster.MakeTestingClusterSettings() 466 evalCtx := tree.MakeTestingEvalContext(st) 467 468 testData := []struct { 469 c string 470 e []opt.ColumnID 471 }{ 472 { // 0 473 c: "/1: [/2 - ]", 474 e: []opt.ColumnID{1}, 475 }, 476 { // 1 477 c: "/1: [ - /2]", 478 e: []opt.ColumnID{}, 479 }, 480 { // 2 481 c: "/1: [/NULL - /4]", 482 e: []opt.ColumnID{}, 483 }, 484 { // 3 485 c: "/1: (/NULL - /4]", 486 e: []opt.ColumnID{1}, 487 }, 488 { // 4 489 c: "/-1: [ - /2]", 490 e: []opt.ColumnID{1}, 491 }, 492 { // 5 493 c: "/-1: [/2 - ]", 494 e: []opt.ColumnID{}, 495 }, 496 { // 6 497 c: "/-1: [/4 - /NULL]", 498 e: []opt.ColumnID{}, 499 }, 500 { // 7 501 c: "/-1: [/4 - /NULL)", 502 e: []opt.ColumnID{1}, 503 }, 504 { // 8 505 c: "/1/2/3: [/1/1/1 - /1/1/2] [/3/3/3 - /3/3/4]", 506 e: []opt.ColumnID{1, 2, 3}, 507 }, 508 { // 9 509 c: "/1/2/3/4: [/1/1/1/1 - /1/1/2/1] [/3/3/3/1 - /3/3/4/1]", 510 e: []opt.ColumnID{1, 2, 3}, 511 }, 512 { // 10 513 c: "/1/2/3: [/1/1 - /1/1/2] [/3/3/3 - /3/3/4]", 514 e: []opt.ColumnID{1, 2}, 515 }, 516 { // 11 517 c: "/1/-2/-3: [/1/1/2 - /1/1] [/3/3/4 - /3/3/3]", 518 e: []opt.ColumnID{1, 2}, 519 }, 520 { // 12 521 c: "/1/2/3: [/1/1/1 - /1/1/2] [/3/3/3 - /3/3/4] [/4/4/1 - /5]", 522 e: []opt.ColumnID{1}, 523 }, 524 { // 13 525 c: "/1/2/3: [/1/1/NULL - /1/1/2] [/3/3/3 - /3/3/4]", 526 e: []opt.ColumnID{1, 2}, 527 }, 528 { // 13 529 c: "/1/2/3: [/1/1/1 - /1/1/1] [/2/NULL/2 - /2/NULL/3]", 530 e: []opt.ColumnID{1, 3}, 531 }, 532 } 533 534 for i, tc := range testData { 535 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 536 c := ParseConstraint(&evalCtx, tc.c) 537 cols := c.ExtractNotNullCols(&evalCtx) 538 if exp := opt.MakeColSet(tc.e...); !cols.Equals(exp) { 539 t.Errorf("expected %s; got %s", exp, cols) 540 } 541 }) 542 } 543 }