github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/constraint/constraint_set_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 "testing" 15 16 "github.com/cockroachdb/cockroach/pkg/sql/opt" 17 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 18 ) 19 20 func TestConstraintSetIntersect(t *testing.T) { 21 kc1 := testKeyContext(1) 22 kc2 := testKeyContext(2) 23 kc12 := testKeyContext(1, 2) 24 evalCtx := kc1.EvalCtx 25 26 test := func(cs *Set, expected string) { 27 t.Helper() 28 if cs.String() != expected { 29 t.Errorf("\nexpected:\n%v\nactual:\n%v", expected, cs.String()) 30 } 31 } 32 33 data := newSpanTestData() 34 35 // Simple AND case. 36 // @1 > 20 37 var c Constraint 38 c.InitSingleSpan(kc1, &data.spGt20) 39 gt20 := SingleConstraint(&c) 40 test(gt20, "/1: (/20 - ]") 41 42 // @1 <= 40 43 le40 := SingleSpanConstraint(kc1, &data.spLe40) 44 test(le40, "/1: [ - /40]") 45 46 // @1 > 20 AND @1 <= 40 47 range2040 := gt20.Intersect(evalCtx, le40) 48 test(range2040, "/1: (/20 - /40]") 49 range2040 = le40.Intersect(evalCtx, gt20) 50 test(range2040, "/1: (/20 - /40]") 51 52 // Include constraint on multiple columns. 53 // (@1, @2) >= (10, 15) 54 gt1015 := SingleSpanConstraint(kc12, &data.spGe1015) 55 test(gt1015, "/1/2: [/10/15 - ]") 56 57 // (@1, @2) >= (10, 15) AND @1 <= 40 58 multi2 := le40.Intersect(evalCtx, gt1015) 59 test(multi2, ""+ 60 "/1: [ - /40]; "+ 61 "/1/2: [/10/15 - ]") 62 63 multi2 = gt1015.Intersect(evalCtx, le40) 64 test(multi2, ""+ 65 "/1: [ - /40]; "+ 66 "/1/2: [/10/15 - ]") 67 68 // (@1, @2) >= (10, 15) AND @1 <= 40 AND @2 < 80 69 lt80 := SingleSpanConstraint(kc2, &data.spLt80) 70 multi3 := lt80.Intersect(evalCtx, multi2) 71 test(multi3, ""+ 72 "/1: [ - /40]; "+ 73 "/1/2: [/10/15 - ]; "+ 74 "/2: [ - /80)") 75 76 multi3 = multi2.Intersect(evalCtx, lt80) 77 test(multi3, ""+ 78 "/1: [ - /40]; "+ 79 "/1/2: [/10/15 - ]; "+ 80 "/2: [ - /80)") 81 82 // Mismatched number of constraints in each set. 83 eq10 := SingleSpanConstraint(kc1, &data.spEq10) 84 mismatched := eq10.Intersect(evalCtx, multi3) 85 test(mismatched, ""+ 86 "/1: [/10 - /10]; "+ 87 "/1/2: [/10/15 - ]; "+ 88 "/2: [ - /80)") 89 90 mismatched = multi3.Intersect(evalCtx, eq10) 91 test(mismatched, ""+ 92 "/1: [/10 - /10]; "+ 93 "/1/2: [/10/15 - ]; "+ 94 "/2: [ - /80)") 95 96 // Multiple intersecting constraints on different columns. 97 diffCols := eq10.Intersect(evalCtx, SingleSpanConstraint(kc2, &data.spGt20)) 98 res := diffCols.Intersect(evalCtx, multi3) 99 test(res, ""+ 100 "/1: [/10 - /10]; "+ 101 "/1/2: [/10/15 - ]; "+ 102 "/2: (/20 - /80)") 103 104 res = multi3.Intersect(evalCtx, diffCols) 105 test(res, ""+ 106 "/1: [/10 - /10]; "+ 107 "/1/2: [/10/15 - ]; "+ 108 "/2: (/20 - /80)") 109 110 // Intersection results in Contradiction. 111 res = eq10.Intersect(evalCtx, gt20) 112 test(res, "contradiction") 113 res = gt20.Intersect(evalCtx, eq10) 114 test(res, "contradiction") 115 116 // Intersect with Unconstrained (identity op). 117 res = range2040.Intersect(evalCtx, Unconstrained) 118 test(res, "/1: (/20 - /40]") 119 res = Unconstrained.Intersect(evalCtx, range2040) 120 test(res, "/1: (/20 - /40]") 121 122 // Intersect with Contradiction (always contradiction). 123 res = eq10.Intersect(evalCtx, Contradiction) 124 test(res, "contradiction") 125 res = Contradiction.Intersect(evalCtx, eq10) 126 test(res, "contradiction") 127 } 128 129 func TestConstraintSetUnion(t *testing.T) { 130 kc1 := testKeyContext(1) 131 kc2 := testKeyContext(2) 132 kc12 := testKeyContext(1, 2) 133 evalCtx := kc1.EvalCtx 134 data := newSpanTestData() 135 136 test := func(cs *Set, expected string) { 137 t.Helper() 138 if cs.String() != expected { 139 t.Errorf("\nexpected:\n%vactual:\n%v", expected, cs.String()) 140 } 141 } 142 143 // Simple OR case. 144 // @1 > 20 145 gt20 := SingleSpanConstraint(kc1, &data.spGt20) 146 test(gt20, "/1: (/20 - ]") 147 148 // @1 = 10 149 eq10 := SingleSpanConstraint(kc1, &data.spEq10) 150 test(eq10, "/1: [/10 - /10]") 151 152 // @1 > 20 OR @1 = 10 153 gt20eq10 := gt20.Union(evalCtx, eq10) 154 test(gt20eq10, "/1: [/10 - /10] (/20 - ]") 155 gt20eq10 = eq10.Union(evalCtx, gt20) 156 test(gt20eq10, "/1: [/10 - /10] (/20 - ]") 157 158 // Combine constraints that result in full span and unconstrained result. 159 // @1 > 20 OR @1 = 10 OR @1 <= 40 160 le40 := SingleSpanConstraint(kc1, &data.spLe40) 161 res := gt20eq10.Union(evalCtx, le40) 162 test(res, "unconstrained") 163 res = le40.Union(evalCtx, gt20eq10) 164 test(res, "unconstrained") 165 166 // Include constraint on multiple columns and union with itself. 167 // (@1, @2) >= (10, 15) 168 gt1015 := SingleSpanConstraint(kc12, &data.spGe1015) 169 res = gt1015.Union(evalCtx, gt1015) 170 test(res, "/1/2: [/10/15 - ]") 171 172 // Union incompatible constraints (both are discarded). 173 // (@1, @2) >= (10, 15) OR @2 < 80 174 lt80 := SingleSpanConstraint(kc2, &data.spLt80) 175 res = gt1015.Union(evalCtx, lt80) 176 test(res, "unconstrained") 177 res = lt80.Union(evalCtx, gt1015) 178 test(res, "unconstrained") 179 180 // Union two sets with multiple and differing numbers of constraints. 181 // ((@1, @2) >= (10, 15) AND @2 < 80 AND @1 > 20) OR (@1 = 10 AND @2 = 80) 182 multi3 := gt1015.Intersect(evalCtx, lt80) 183 multi3 = multi3.Intersect(evalCtx, gt20) 184 185 eq80 := SingleSpanConstraint(kc2, &data.spEq80) 186 multi2 := eq10.Intersect(evalCtx, eq80) 187 188 res = multi3.Union(evalCtx, multi2) 189 test(res, ""+ 190 "/1: [/10 - /10] (/20 - ]; "+ 191 "/2: [ - /80]") 192 res = multi2.Union(evalCtx, multi3) 193 test(res, ""+ 194 "/1: [/10 - /10] (/20 - ]; "+ 195 "/2: [ - /80]") 196 197 // Do same as previous, but in different order so that discarded constraint 198 // is at end of list rather than beginning. 199 // (@1 > 20 AND @2 < 80 AND (@1, @2) >= (10, 15)) OR (@1 = 10 AND @2 = 80) 200 multi3 = gt20.Intersect(evalCtx, lt80) 201 multi3 = multi3.Intersect(evalCtx, gt1015) 202 203 res = multi3.Union(evalCtx, multi2) 204 test(res, ""+ 205 "/1: [/10 - /10] (/20 - ]; "+ 206 "/2: [ - /80]") 207 res = multi2.Union(evalCtx, multi3) 208 test(res, ""+ 209 "/1: [/10 - /10] (/20 - ]; "+ 210 "/2: [ - /80]") 211 212 // Union with Unconstrained (always unconstrained). 213 res = gt20.Union(evalCtx, Unconstrained) 214 test(res, "unconstrained") 215 res = Unconstrained.Union(evalCtx, gt20) 216 test(res, "unconstrained") 217 218 // Union with Contradiction (identity op). 219 res = eq10.Union(evalCtx, Contradiction) 220 test(res, "/1: [/10 - /10]") 221 res = Contradiction.Union(evalCtx, eq10) 222 test(res, "/1: [/10 - /10]") 223 } 224 225 func TestExtractCols(t *testing.T) { 226 type testCase struct { 227 constraints []string 228 expected opt.ColSet 229 } 230 231 cols := opt.MakeColSet 232 233 cases := []testCase{ 234 { 235 []string{ 236 `/1: [/10 - /10]`, 237 `/2: [/8 - /8]`, 238 `/-3: [/13 - /7]`, 239 }, 240 cols(1, 2, 3), 241 }, 242 { 243 []string{ 244 `/1/2: [/10/4 - /10/5] [/12/4 - /12/5]`, 245 `/2: [/4 - /4]`, 246 }, 247 cols(1, 2), 248 }, 249 { 250 []string{ 251 `/1/2/3: [/10/4 - /10/5] [/12/4 - /12/5]`, 252 `/4: [/4 - /4]`, 253 }, 254 cols(1, 2, 3, 4), 255 }, 256 } 257 258 evalCtx := tree.NewTestingEvalContext(nil) 259 for _, tc := range cases { 260 cs := Unconstrained 261 for _, constraint := range tc.constraints { 262 constraint := ParseConstraint(evalCtx, constraint) 263 cs = cs.Intersect(evalCtx, SingleConstraint(&constraint)) 264 } 265 cols := cs.ExtractCols() 266 if !tc.expected.Equals(cols) { 267 t.Errorf("expected constant columns from %s to be %s, was %s", cs, tc.expected, cols) 268 } 269 } 270 } 271 272 func TestExtractConstCols(t *testing.T) { 273 type vals map[opt.ColumnID]string 274 type testCase struct { 275 constraints []string 276 expected vals 277 } 278 279 cases := []testCase{ 280 {[]string{`/1: [/10 - /10]`}, vals{1: "10"}}, 281 {[]string{`/-1: [/10 - /10]`}, vals{1: "10"}}, 282 {[]string{`/1: [/10 - /11]`}, vals{}}, 283 {[]string{`/1: [/10 - ]`}, vals{}}, 284 {[]string{`/1/2: [/10/2 - /10/4]`}, vals{1: "10"}}, 285 {[]string{`/1/-2: [/10/4 - /10/2]`}, vals{1: "10"}}, 286 {[]string{`/1/2: [/10/2 - /10/2]`}, vals{1: "10", 2: "2"}}, 287 {[]string{`/1/2: [/10/2 - /12/2]`}, vals{}}, 288 {[]string{`/1/2: [/9/2 - /9/2] [/10/2 - /12/2]`}, vals{}}, 289 {[]string{`/1: [/10 - /10] [/12 - /12]`}, vals{}}, 290 { 291 []string{ 292 `/1: [/10 - /10]`, 293 `/2: [/8 - /8]`, 294 `/-3: [/13 - /7]`, 295 }, 296 vals{1: "10", 2: "8"}, 297 }, 298 { 299 []string{ 300 `/1/2: [/10/4 - /10/5] [/12/4 - /12/5]`, 301 `/2: [/4 - /4]`, 302 }, 303 vals{2: "4"}, 304 }, 305 {[]string{`/1: [/10 - /11)`}, vals{}}, 306 // TODO(justin): column 1 here is constant but we don't infer it as such. 307 { 308 []string{ 309 `/2/1: [/900/4 - /900/4] [/1000/4 - /1000/4] [/1100/4 - /1100/4] [/1400/4 - /1400/4] [/1500/4 - /1500/4]`, 310 }, 311 vals{}, 312 }, 313 { 314 []string{ 315 `/1: [/2 - /3]`, 316 `/2/1: [/10/3 - /11/1]`, 317 }, 318 vals{}, 319 }, 320 } 321 322 evalCtx := tree.NewTestingEvalContext(nil) 323 for _, tc := range cases { 324 cs := Unconstrained 325 for _, constraint := range tc.constraints { 326 constraint := ParseConstraint(evalCtx, constraint) 327 cs = cs.Intersect(evalCtx, SingleConstraint(&constraint)) 328 } 329 cols := cs.ExtractConstCols(evalCtx) 330 var expCols opt.ColSet 331 for col := range tc.expected { 332 expCols.Add(col) 333 } 334 if !expCols.Equals(cols) { 335 t.Errorf("%s: expected constant columns be %s, was %s", cs, expCols, cols) 336 } 337 cols.ForEach(func(col opt.ColumnID) { 338 val := cs.ExtractValueForConstCol(evalCtx, col) 339 if val == nil { 340 t.Errorf("%s: no const value for column %d", cs, col) 341 return 342 } 343 if actual, expected := val.String(), tc.expected[col]; actual != expected { 344 t.Errorf("%s: expected value %s for column %d, got %s", cs, expected, col, actual) 345 } 346 }) 347 } 348 } 349 350 func TestIsSingleColumnConstValue(t *testing.T) { 351 type testCase struct { 352 constraints []string 353 col opt.ColumnID 354 val int 355 } 356 cases := []testCase{ 357 {[]string{`/1: [/10 - /10]`}, 1, 10}, 358 {[]string{`/-1: [/10 - /10]`}, 1, 10}, 359 {[]string{`/1: [/10 - /11]`}, 0, 0}, 360 {[]string{`/1: [/10 - /10] [/11 - /11]`}, 0, 0}, 361 {[]string{`/1/2: [/10/2 - /10/4]`}, 0, 0}, 362 {[]string{`/1/2: [/10/2 - /10/2]`}, 0, 0}, 363 { 364 []string{ 365 `/1: [/10 - /10]`, 366 `/2: [/8 - /8]`, 367 }, 368 0, 0, 369 }, 370 { 371 []string{ 372 `/1: [/10 - /10]`, 373 `/1/2: [/10/8 - /10/8]`, 374 }, 375 0, 0, 376 }, 377 } 378 evalCtx := tree.NewTestingEvalContext(nil) 379 for _, tc := range cases { 380 cs := Unconstrained 381 for _, constraint := range tc.constraints { 382 constraint := ParseConstraint(evalCtx, constraint) 383 cs = cs.Intersect(evalCtx, SingleConstraint(&constraint)) 384 } 385 col, val, ok := cs.IsSingleColumnConstValue(evalCtx) 386 intVal := 0 387 if ok { 388 intVal = int(*val.(*tree.DInt)) 389 } 390 if tc.col != col || tc.val != intVal { 391 t.Errorf("%s: expected %d,%d got %d,%d", cs, tc.col, tc.val, col, intVal) 392 } 393 } 394 } 395 396 type spanTestData struct { 397 spEq10 Span // [/10 - /10] 398 spGt20 Span // (/20 - ] 399 spLe40 Span // [ - /40] 400 spLt80 Span // [ - /80) 401 spEq80 Span // [/80 - /80] 402 spGe1015 Span // [/10/15 - ] 403 } 404 405 func newSpanTestData() *spanTestData { 406 data := &spanTestData{} 407 408 key10 := MakeKey(tree.NewDInt(10)) 409 key15 := MakeKey(tree.NewDInt(15)) 410 key20 := MakeKey(tree.NewDInt(20)) 411 key40 := MakeKey(tree.NewDInt(40)) 412 key80 := MakeKey(tree.NewDInt(80)) 413 414 // [/10 - /10] 415 data.spEq10.Init(key10, IncludeBoundary, key10, IncludeBoundary) 416 417 // (/20 - ] 418 data.spGt20.Init(key20, ExcludeBoundary, EmptyKey, IncludeBoundary) 419 420 // [ - /40] 421 data.spLe40.Init(EmptyKey, IncludeBoundary, key40, IncludeBoundary) 422 423 // [ - /80) 424 data.spLt80.Init(EmptyKey, IncludeBoundary, key80, ExcludeBoundary) 425 426 // [/80 - /80] 427 data.spEq80.Init(key80, IncludeBoundary, key80, IncludeBoundary) 428 429 // [/10/15 - ] 430 key1015 := key10.Concat(key15) 431 data.spGe1015.Init(key1015, IncludeBoundary, EmptyKey, IncludeBoundary) 432 433 return data 434 }