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  }