github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/constraint/span_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  // This file implements data structures used by index constraints generation.
    12  
    13  package constraint
    14  
    15  import (
    16  	"fmt"
    17  	"math"
    18  	"testing"
    19  
    20  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    22  )
    23  
    24  func TestSpanSet(t *testing.T) {
    25  	testCases := []struct {
    26  		start         Key
    27  		startBoundary SpanBoundary
    28  		end           Key
    29  		endBoundary   SpanBoundary
    30  		expected      string
    31  	}{
    32  		{ // 0
    33  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
    34  			MakeKey(tree.NewDInt(5)), IncludeBoundary,
    35  			"[/1 - /5]",
    36  		},
    37  		{ // 1
    38  			MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(5)), IncludeBoundary,
    39  			MakeCompositeKey(tree.NewDString("mango"), tree.NewDInt(1)), ExcludeBoundary,
    40  			"[/'cherry'/5 - /'mango'/1)",
    41  		},
    42  		{ // 2
    43  			MakeCompositeKey(tree.NewDInt(5), tree.NewDInt(1)), ExcludeBoundary,
    44  			MakeKey(tree.NewDInt(5)), IncludeBoundary,
    45  			"(/5/1 - /5]",
    46  		},
    47  		{ // 3
    48  			MakeKey(tree.NewDInt(5)), IncludeBoundary,
    49  			MakeCompositeKey(tree.NewDInt(5), tree.NewDInt(1)), ExcludeBoundary,
    50  			"[/5 - /5/1)",
    51  		},
    52  		{ // 4
    53  			MakeKey(tree.DNull), IncludeBoundary,
    54  			MakeCompositeKey(tree.NewDInt(5), tree.NewDInt(1)), ExcludeBoundary,
    55  			"[/NULL - /5/1)",
    56  		},
    57  	}
    58  
    59  	for i, tc := range testCases {
    60  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
    61  			var sp Span
    62  			sp.Init(tc.start, tc.startBoundary, tc.end, tc.endBoundary)
    63  			if sp.String() != tc.expected {
    64  				t.Errorf("expected: %s, actual: %s", tc.expected, sp.String())
    65  			}
    66  		})
    67  	}
    68  
    69  	testPanic := func(t *testing.T, fn func(), expected string) {
    70  		t.Helper()
    71  		defer func() {
    72  			if r := recover(); r == nil {
    73  				t.Errorf("panic expected with message: %s", expected)
    74  			} else if fmt.Sprint(r) != expected {
    75  				t.Errorf("expected: %s, actual: %v", expected, r)
    76  			}
    77  		}()
    78  		fn()
    79  	}
    80  
    81  	var sp Span
    82  	// Create exclusive empty start boundary.
    83  	testPanic(t, func() {
    84  		sp.Init(EmptyKey, ExcludeBoundary, MakeKey(tree.DNull), IncludeBoundary)
    85  	}, "an empty start boundary must be inclusive")
    86  
    87  	// Create exclusive empty end boundary.
    88  	testPanic(t, func() {
    89  		sp.Init(MakeKey(tree.DNull), IncludeBoundary, EmptyKey, ExcludeBoundary)
    90  	}, "an empty end boundary must be inclusive")
    91  }
    92  
    93  func TestSpanUnconstrained(t *testing.T) {
    94  	// Test unconstrained span.
    95  	unconstrained := Span{}
    96  	if !unconstrained.IsUnconstrained() {
    97  		t.Errorf("default span is not unconstrained")
    98  	}
    99  
   100  	if unconstrained.String() != "[ - ]" {
   101  		t.Errorf("unexpected string value for unconstrained span: %s", unconstrained.String())
   102  	}
   103  
   104  	unconstrained.startBoundary = IncludeBoundary
   105  	unconstrained.start = MakeKey(tree.DNull)
   106  	if !unconstrained.IsUnconstrained() {
   107  		t.Errorf("span beginning with NULL is not unconstrained")
   108  	}
   109  
   110  	// Test constrained span's IsUnconstrained method.
   111  	var sp Span
   112  	sp.Init(MakeKey(tree.NewDInt(5)), IncludeBoundary, MakeKey(tree.NewDInt(5)), IncludeBoundary)
   113  	if sp.IsUnconstrained() {
   114  		t.Errorf("IsUnconstrained should have returned false")
   115  	}
   116  }
   117  
   118  func TestSpanSingleKey(t *testing.T) {
   119  	testCases := []struct {
   120  		start         Key
   121  		startBoundary SpanBoundary
   122  		end           Key
   123  		endBoundary   SpanBoundary
   124  		expected      bool
   125  	}{
   126  		{ // 0
   127  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   128  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   129  			true,
   130  		},
   131  		{ // 1
   132  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   133  			MakeKey(tree.NewDInt(2)), IncludeBoundary,
   134  			false,
   135  		},
   136  		{ // 2
   137  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   138  			MakeKey(tree.NewDInt(1)), ExcludeBoundary,
   139  			false,
   140  		},
   141  		{ // 3
   142  			MakeKey(tree.NewDInt(1)), ExcludeBoundary,
   143  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   144  			false,
   145  		},
   146  		{ // 4
   147  			EmptyKey, IncludeBoundary,
   148  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   149  			false,
   150  		},
   151  		{ // 5
   152  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   153  			EmptyKey, IncludeBoundary,
   154  			false,
   155  		},
   156  		{ // 6
   157  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   158  			MakeKey(tree.DNull), IncludeBoundary,
   159  			false,
   160  		},
   161  		{ // 7
   162  			MakeKey(tree.NewDString("a")), IncludeBoundary,
   163  			MakeKey(tree.NewDString("ab")), IncludeBoundary,
   164  			false,
   165  		},
   166  		{ // 8
   167  			MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(1)), IncludeBoundary,
   168  			MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(1)), IncludeBoundary,
   169  			true,
   170  		},
   171  		{ // 9
   172  			MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(1)), IncludeBoundary,
   173  			MakeCompositeKey(tree.NewDString("mango"), tree.NewDInt(1)), IncludeBoundary,
   174  			false,
   175  		},
   176  		{ // 10
   177  			MakeCompositeKey(tree.NewDString("cherry")), IncludeBoundary,
   178  			MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(1)), IncludeBoundary,
   179  			false,
   180  		},
   181  		{ // 11
   182  			MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(1), tree.DNull), IncludeBoundary,
   183  			MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(1), tree.DNull), IncludeBoundary,
   184  			true,
   185  		},
   186  	}
   187  
   188  	for i, tc := range testCases {
   189  		st := cluster.MakeTestingClusterSettings()
   190  		evalCtx := tree.MakeTestingEvalContext(st)
   191  
   192  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   193  			var sp Span
   194  			sp.Init(tc.start, tc.startBoundary, tc.end, tc.endBoundary)
   195  			if sp.HasSingleKey(&evalCtx) != tc.expected {
   196  				t.Errorf("expected: %v, actual: %v", tc.expected, !tc.expected)
   197  			}
   198  		})
   199  	}
   200  }
   201  
   202  func TestSpanCompare(t *testing.T) {
   203  	keyCtx := testKeyContext(1, 2)
   204  
   205  	testComp := func(t *testing.T, left, right Span, expected int) {
   206  		t.Helper()
   207  		if actual := left.Compare(keyCtx, &right); actual != expected {
   208  			format := "left: %s, right: %s, expected: %v, actual: %v"
   209  			t.Errorf(format, left.String(), right.String(), expected, actual)
   210  		}
   211  	}
   212  
   213  	one := MakeKey(tree.NewDInt(1))
   214  	two := MakeKey(tree.NewDInt(2))
   215  	oneone := MakeCompositeKey(tree.NewDInt(1), tree.NewDInt(1))
   216  	twoone := MakeCompositeKey(tree.NewDInt(2), tree.NewDInt(1))
   217  
   218  	var spans [17]Span
   219  
   220  	// [ - /2)
   221  	spans[0].Init(EmptyKey, IncludeBoundary, two, ExcludeBoundary)
   222  
   223  	// [ - /2/1)
   224  	spans[1].Init(EmptyKey, IncludeBoundary, twoone, ExcludeBoundary)
   225  
   226  	// [ - /2/1]
   227  	spans[2].Init(EmptyKey, IncludeBoundary, twoone, IncludeBoundary)
   228  
   229  	// [ - /2]
   230  	spans[3].Init(EmptyKey, IncludeBoundary, two, IncludeBoundary)
   231  
   232  	// [ - ]
   233  	spans[4] = Span{}
   234  
   235  	// [/1 - /2/1)
   236  	spans[5].Init(one, IncludeBoundary, twoone, ExcludeBoundary)
   237  
   238  	// [/1 - /2/1]
   239  	spans[6].Init(one, IncludeBoundary, twoone, IncludeBoundary)
   240  
   241  	// [/1 - ]
   242  	spans[7].Init(one, IncludeBoundary, EmptyKey, IncludeBoundary)
   243  
   244  	// [/1/1 - /2)
   245  	spans[8].Init(oneone, IncludeBoundary, two, ExcludeBoundary)
   246  
   247  	// [/1/1 - /2]
   248  	spans[9].Init(oneone, IncludeBoundary, two, IncludeBoundary)
   249  
   250  	// [/1/1 - ]
   251  	spans[10].Init(oneone, IncludeBoundary, EmptyKey, IncludeBoundary)
   252  
   253  	// (/1/1 - /2)
   254  	spans[11].Init(oneone, ExcludeBoundary, two, ExcludeBoundary)
   255  
   256  	// (/1/1 - /2]
   257  	spans[12].Init(oneone, ExcludeBoundary, two, IncludeBoundary)
   258  
   259  	// (/1/1 - ]
   260  	spans[13].Init(oneone, ExcludeBoundary, EmptyKey, IncludeBoundary)
   261  
   262  	// (/1 - /2/1)
   263  	spans[14].Init(one, ExcludeBoundary, twoone, ExcludeBoundary)
   264  
   265  	// (/1 - /2/1]
   266  	spans[15].Init(one, ExcludeBoundary, twoone, IncludeBoundary)
   267  
   268  	// (/1 - ]
   269  	spans[16].Init(one, ExcludeBoundary, EmptyKey, IncludeBoundary)
   270  
   271  	for i := 0; i < len(spans)-1; i++ {
   272  		testComp(t, spans[i], spans[i+1], -1)
   273  		testComp(t, spans[i+1], spans[i], 1)
   274  		testComp(t, spans[i], spans[i], 0)
   275  		testComp(t, spans[i+1], spans[i+1], 0)
   276  	}
   277  
   278  	keyCtx = testKeyContext(-1, 2)
   279  
   280  	// [ - /1)
   281  	spans[0].Init(EmptyKey, IncludeBoundary, one, ExcludeBoundary)
   282  
   283  	// [ - /1/1)
   284  	spans[1].Init(EmptyKey, IncludeBoundary, oneone, ExcludeBoundary)
   285  
   286  	// [ - /1/1]
   287  	spans[2].Init(EmptyKey, IncludeBoundary, oneone, IncludeBoundary)
   288  
   289  	// [ - /1]
   290  	spans[3].Init(EmptyKey, IncludeBoundary, one, IncludeBoundary)
   291  
   292  	// [ - ]
   293  	spans[4] = Span{}
   294  
   295  	// [/2 - /1/1)
   296  	spans[5].Init(two, IncludeBoundary, oneone, ExcludeBoundary)
   297  
   298  	// [/2 - /1/1]
   299  	spans[6].Init(two, IncludeBoundary, oneone, IncludeBoundary)
   300  
   301  	// [/2 - ]
   302  	spans[7].Init(two, IncludeBoundary, EmptyKey, IncludeBoundary)
   303  
   304  	// [/2/1 - /1)
   305  	spans[8].Init(twoone, IncludeBoundary, one, ExcludeBoundary)
   306  
   307  	// [/2/1 - /1]
   308  	spans[9].Init(twoone, IncludeBoundary, one, IncludeBoundary)
   309  
   310  	// [/2/1 - ]
   311  	spans[10].Init(twoone, IncludeBoundary, EmptyKey, IncludeBoundary)
   312  
   313  	// (/2/1 - /1)
   314  	spans[11].Init(twoone, ExcludeBoundary, one, ExcludeBoundary)
   315  
   316  	// (/2/1 - /1]
   317  	spans[12].Init(twoone, ExcludeBoundary, one, IncludeBoundary)
   318  
   319  	// (/2/1 - ]
   320  	spans[13].Init(twoone, ExcludeBoundary, EmptyKey, IncludeBoundary)
   321  
   322  	// (/2 - /1/1)
   323  	spans[14].Init(two, ExcludeBoundary, oneone, ExcludeBoundary)
   324  
   325  	// (/2 - /1/1]
   326  	spans[15].Init(two, ExcludeBoundary, oneone, IncludeBoundary)
   327  
   328  	// (/2 - ]
   329  	spans[16].Init(two, ExcludeBoundary, EmptyKey, IncludeBoundary)
   330  
   331  	for i := 0; i < len(spans)-1; i++ {
   332  		testComp(t, spans[i], spans[i+1], -1)
   333  		testComp(t, spans[i+1], spans[i], 1)
   334  		testComp(t, spans[i], spans[i], 0)
   335  		testComp(t, spans[i+1], spans[i+1], 0)
   336  	}
   337  }
   338  
   339  func TestSpanCompareStarts(t *testing.T) {
   340  	keyCtx := testKeyContext(1, 2)
   341  
   342  	test := func(left, right Span, expected int) {
   343  		t.Helper()
   344  		if actual := left.CompareStarts(keyCtx, &right); actual != expected {
   345  			format := "left: %s, right: %s, expected: %v, actual: %v"
   346  			t.Errorf(format, left.String(), right.String(), expected, actual)
   347  		}
   348  	}
   349  
   350  	one := MakeKey(tree.NewDInt(1))
   351  	two := MakeKey(tree.NewDInt(2))
   352  	five := MakeKey(tree.NewDInt(5))
   353  	nine := MakeKey(tree.NewDInt(9))
   354  
   355  	var onefive Span
   356  	onefive.Init(one, IncludeBoundary, five, IncludeBoundary)
   357  	var twonine Span
   358  	twonine.Init(two, ExcludeBoundary, nine, ExcludeBoundary)
   359  
   360  	// Same span.
   361  	test(onefive, onefive, 0)
   362  
   363  	// Different spans.
   364  	test(onefive, twonine, -1)
   365  	test(twonine, onefive, 1)
   366  }
   367  
   368  func TestSpanCompareEnds(t *testing.T) {
   369  	keyCtx := testKeyContext(1, 2)
   370  
   371  	test := func(left, right Span, expected int) {
   372  		t.Helper()
   373  		if actual := left.CompareEnds(keyCtx, &right); actual != expected {
   374  			format := "left: %s, right: %s, expected: %v, actual: %v"
   375  			t.Errorf(format, left.String(), right.String(), expected, actual)
   376  		}
   377  	}
   378  
   379  	one := MakeKey(tree.NewDInt(1))
   380  	two := MakeKey(tree.NewDInt(2))
   381  	five := MakeKey(tree.NewDInt(5))
   382  	nine := MakeKey(tree.NewDInt(9))
   383  
   384  	var onefive Span
   385  	onefive.Init(one, IncludeBoundary, five, IncludeBoundary)
   386  	var twonine Span
   387  	twonine.Init(two, ExcludeBoundary, nine, ExcludeBoundary)
   388  
   389  	// Same span.
   390  	test(onefive, onefive, 0)
   391  
   392  	// Different spans.
   393  	test(onefive, twonine, -1)
   394  	test(twonine, onefive, 1)
   395  }
   396  
   397  func TestSpanStartsAfter(t *testing.T) {
   398  	keyCtx := testKeyContext(1, 2)
   399  
   400  	test := func(left, right Span, expected, expectedStrict bool) {
   401  		t.Helper()
   402  		if actual := left.StartsAfter(keyCtx, &right); actual != expected {
   403  			format := "left: %s, right: %s, expected: %v, actual: %v"
   404  			t.Errorf(format, left.String(), right.String(), expected, actual)
   405  		}
   406  		if actual := left.StartsStrictlyAfter(keyCtx, &right); actual != expectedStrict {
   407  			format := "left: %s, right: %s, expected: %v, actual: %v"
   408  			t.Errorf(format, left.String(), right.String(), expectedStrict, actual)
   409  		}
   410  	}
   411  
   412  	// Same span.
   413  	var banana Span
   414  	banana.Init(
   415  		MakeCompositeKey(tree.DNull, tree.NewDInt(100)), IncludeBoundary,
   416  		MakeCompositeKey(tree.NewDString("banana"), tree.NewDInt(50)), IncludeBoundary,
   417  	)
   418  	test(banana, banana, false, false)
   419  
   420  	// Right span's start equal to left span's end.
   421  	var cherry Span
   422  	cherry.Init(
   423  		MakeCompositeKey(tree.NewDString("banana"), tree.NewDInt(50)), ExcludeBoundary,
   424  		MakeKey(tree.NewDString("cherry")), ExcludeBoundary,
   425  	)
   426  	test(banana, cherry, false, false)
   427  	test(cherry, banana, true, false)
   428  
   429  	// Right span's start greater than left span's end, and inverse.
   430  	var cherry2 Span
   431  	cherry2.Init(
   432  		MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(0)), IncludeBoundary,
   433  		MakeKey(tree.NewDString("mango")), ExcludeBoundary,
   434  	)
   435  	test(cherry, cherry2, false, false)
   436  	test(cherry2, cherry, true, true)
   437  }
   438  
   439  func TestSpanIntersect(t *testing.T) {
   440  	keyCtx := testKeyContext(1, 2)
   441  	testInt := func(left, right Span, expected string) {
   442  		t.Helper()
   443  		sp := left
   444  		ok := sp.TryIntersectWith(keyCtx, &right)
   445  
   446  		var actual string
   447  		if ok {
   448  			actual = sp.String()
   449  		}
   450  
   451  		if actual != expected {
   452  			format := "left: %s, right: %s, expected: %v, actual: %v"
   453  			t.Errorf(format, left.String(), right.String(), expected, actual)
   454  		}
   455  	}
   456  
   457  	// Same span.
   458  	var banana Span
   459  	banana.Init(
   460  		MakeCompositeKey(tree.DNull, tree.NewDInt(100)), IncludeBoundary,
   461  		MakeCompositeKey(tree.NewDString("banana"), tree.NewDInt(50)), IncludeBoundary,
   462  	)
   463  	testInt(banana, banana, "[/NULL/100 - /'banana'/50]")
   464  
   465  	// One span immediately after the other.
   466  	var grape Span
   467  	grape.Init(
   468  		MakeCompositeKey(tree.NewDString("banana"), tree.NewDInt(50)), ExcludeBoundary,
   469  		MakeCompositeKey(tree.NewDString("grape")), ExcludeBoundary,
   470  	)
   471  	testInt(banana, grape, "")
   472  	testInt(grape, banana, "")
   473  
   474  	// Partial overlap.
   475  	var apple Span
   476  	apple.Init(
   477  		MakeCompositeKey(tree.NewDString("apple"), tree.NewDInt(200)), ExcludeBoundary,
   478  		MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(300)), ExcludeBoundary,
   479  	)
   480  	testInt(banana, apple, "(/'apple'/200 - /'banana'/50]")
   481  	testInt(apple, banana, "(/'apple'/200 - /'banana'/50]")
   482  
   483  	// One span is subset of other.
   484  	var mango Span
   485  	mango.Init(
   486  		MakeCompositeKey(tree.NewDString("apple"), tree.NewDInt(200)), ExcludeBoundary,
   487  		MakeCompositeKey(tree.NewDString("mango")), ExcludeBoundary,
   488  	)
   489  	testInt(apple, mango, "(/'apple'/200 - /'cherry'/300)")
   490  	testInt(mango, apple, "(/'apple'/200 - /'cherry'/300)")
   491  	testInt(Span{}, mango, "(/'apple'/200 - /'mango')")
   492  	testInt(mango, Span{}, "(/'apple'/200 - /'mango')")
   493  
   494  	// Spans are disjoint.
   495  	var pear Span
   496  	pear.Init(
   497  		MakeCompositeKey(tree.NewDString("mango"), tree.NewDInt(0)), IncludeBoundary,
   498  		MakeCompositeKey(tree.NewDString("pear"), tree.NewDInt(10)), IncludeBoundary,
   499  	)
   500  	testInt(mango, pear, "")
   501  	testInt(pear, mango, "")
   502  
   503  	// Ensure that if TryIntersectWith results in empty set, that it does not
   504  	// update either span.
   505  	mango2 := mango
   506  	pear2 := pear
   507  	mango2.TryIntersectWith(keyCtx, &pear2)
   508  	if mango2.Compare(keyCtx, &mango) != 0 {
   509  		t.Errorf("mango2 was incorrectly updated during TryIntersectWith")
   510  	}
   511  	if pear2.Compare(keyCtx, &pear) != 0 {
   512  		t.Errorf("pear2 was incorrectly updated during TryIntersectWith")
   513  	}
   514  
   515  	// Partial overlap on second key.
   516  	pear2.Init(
   517  		MakeCompositeKey(tree.NewDString("pear"), tree.NewDInt(5), tree.DNull), ExcludeBoundary,
   518  		MakeCompositeKey(tree.NewDString("raspberry"), tree.NewDInt(100)), IncludeBoundary,
   519  	)
   520  	testInt(pear, pear2, "(/'pear'/5/NULL - /'pear'/10]")
   521  	testInt(pear2, pear, "(/'pear'/5/NULL - /'pear'/10]")
   522  
   523  	// Unconstrained (uninitialized) span.
   524  	testInt(banana, Span{}, "[/NULL/100 - /'banana'/50]")
   525  	testInt(Span{}, banana, "[/NULL/100 - /'banana'/50]")
   526  }
   527  
   528  func TestSpanUnion(t *testing.T) {
   529  	keyCtx := testKeyContext(1, 2)
   530  
   531  	testUnion := func(left, right Span, expected string) {
   532  		t.Helper()
   533  		sp := left
   534  		ok := sp.TryUnionWith(keyCtx, &right)
   535  
   536  		var actual string
   537  		if ok {
   538  			actual = sp.String()
   539  		}
   540  
   541  		if actual != expected {
   542  			format := "left: %s, right: %s, expected: %v, actual: %v"
   543  			t.Errorf(format, left.String(), right.String(), expected, actual)
   544  		}
   545  	}
   546  
   547  	// Same span.
   548  	var banana Span
   549  	banana.Init(
   550  		MakeCompositeKey(tree.DNull, tree.NewDInt(100)), IncludeBoundary,
   551  		MakeCompositeKey(tree.NewDString("banana"), tree.NewDInt(50)), IncludeBoundary,
   552  	)
   553  	testUnion(banana, banana, "[/NULL/100 - /'banana'/50]")
   554  
   555  	// Partial overlap.
   556  	var apple Span
   557  	apple.Init(
   558  		MakeCompositeKey(tree.NewDString("apple"), tree.NewDInt(200)), ExcludeBoundary,
   559  		MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(300)), ExcludeBoundary,
   560  	)
   561  	testUnion(banana, apple, "[/NULL/100 - /'cherry'/300)")
   562  	testUnion(apple, banana, "[/NULL/100 - /'cherry'/300)")
   563  
   564  	// One span is subset of other.
   565  	var mango Span
   566  	mango.Init(
   567  		MakeCompositeKey(tree.NewDString("apple"), tree.NewDInt(200)), ExcludeBoundary,
   568  		MakeCompositeKey(tree.NewDString("mango")), ExcludeBoundary,
   569  	)
   570  	testUnion(apple, mango, "(/'apple'/200 - /'mango')")
   571  	testUnion(mango, apple, "(/'apple'/200 - /'mango')")
   572  	testUnion(Span{}, mango, "[ - ]")
   573  	testUnion(mango, Span{}, "[ - ]")
   574  
   575  	// Spans are disjoint.
   576  	var pear Span
   577  	pear.Init(
   578  		MakeCompositeKey(tree.NewDString("mango"), tree.NewDInt(0)), IncludeBoundary,
   579  		MakeCompositeKey(tree.NewDString("pear"), tree.NewDInt(10)), IncludeBoundary,
   580  	)
   581  	testUnion(mango, pear, "")
   582  	testUnion(pear, mango, "")
   583  
   584  	// Ensure that if TryUnionWith fails to merge, that it does not update
   585  	// either span.
   586  	mango2 := mango
   587  	pear2 := pear
   588  	mango2.TryUnionWith(keyCtx, &pear2)
   589  	if mango2.Compare(keyCtx, &mango) != 0 {
   590  		t.Errorf("mango2 was incorrectly updated during TryUnionWith")
   591  	}
   592  	if pear2.Compare(keyCtx, &pear) != 0 {
   593  		t.Errorf("pear2 was incorrectly updated during TryUnionWith")
   594  	}
   595  
   596  	// Partial overlap on second key.
   597  	pear2.Init(
   598  		MakeCompositeKey(tree.NewDString("pear"), tree.NewDInt(5), tree.DNull), ExcludeBoundary,
   599  		MakeCompositeKey(tree.NewDString("raspberry"), tree.NewDInt(100)), IncludeBoundary,
   600  	)
   601  	testUnion(pear, pear2, "[/'mango'/0 - /'raspberry'/100]")
   602  	testUnion(pear, pear2, "[/'mango'/0 - /'raspberry'/100]")
   603  
   604  	// Unconstrained (uninitialized) span.
   605  	testUnion(banana, Span{}, "[ - ]")
   606  	testUnion(Span{}, banana, "[ - ]")
   607  }
   608  
   609  func TestSpanPreferInclusive(t *testing.T) {
   610  	keyCtx := testKeyContext(1, 2)
   611  
   612  	testCases := []struct {
   613  		start         Key
   614  		startBoundary SpanBoundary
   615  		end           Key
   616  		endBoundary   SpanBoundary
   617  		expected      string
   618  	}{
   619  		{ // 0
   620  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   621  			MakeKey(tree.NewDInt(5)), IncludeBoundary,
   622  			"[/1 - /5]",
   623  		},
   624  		{ // 1
   625  			MakeKey(tree.NewDInt(1)), IncludeBoundary,
   626  			MakeKey(tree.NewDInt(5)), ExcludeBoundary,
   627  			"[/1 - /4]",
   628  		},
   629  		{ // 2
   630  			MakeKey(tree.NewDInt(1)), ExcludeBoundary,
   631  			MakeKey(tree.NewDInt(5)), IncludeBoundary,
   632  			"[/2 - /5]",
   633  		},
   634  		{ // 3
   635  			MakeKey(tree.NewDInt(1)), ExcludeBoundary,
   636  			MakeKey(tree.NewDInt(5)), ExcludeBoundary,
   637  			"[/2 - /4]",
   638  		},
   639  		{ // 4
   640  			MakeCompositeKey(tree.NewDInt(1), tree.NewDInt(math.MaxInt64)), ExcludeBoundary,
   641  			MakeCompositeKey(tree.NewDInt(2), tree.NewDInt(math.MinInt64)), ExcludeBoundary,
   642  			"(/1/9223372036854775807 - /2/-9223372036854775808)",
   643  		},
   644  		{ // 5
   645  			MakeCompositeKey(tree.NewDString("cherry"), tree.NewDInt(5)), ExcludeBoundary,
   646  			MakeCompositeKey(tree.NewDString("mango"), tree.NewDInt(1)), ExcludeBoundary,
   647  			"[/'cherry'/6 - /'mango'/0]",
   648  		},
   649  		{ // 6
   650  			MakeCompositeKey(tree.NewDInt(1), tree.NewDString("cherry")), ExcludeBoundary,
   651  			MakeCompositeKey(tree.NewDInt(2), tree.NewDString("mango")), ExcludeBoundary,
   652  			"[/1/e'cherry\\x00' - /2/'mango')",
   653  		},
   654  	}
   655  
   656  	for i, tc := range testCases {
   657  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   658  			var sp Span
   659  			sp.Init(tc.start, tc.startBoundary, tc.end, tc.endBoundary)
   660  			sp.PreferInclusive(keyCtx)
   661  			if sp.String() != tc.expected {
   662  				t.Errorf("expected: %s, actual: %s", tc.expected, sp.String())
   663  			}
   664  		})
   665  	}
   666  }