github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/props/histogram_test.go (about)

     1  // Copyright 2019 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 props
    12  
    13  import (
    14  	"math"
    15  	"reflect"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/opt"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/opt/constraint"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    25  )
    26  
    27  func TestCanFilter(t *testing.T) {
    28  	evalCtx := tree.MakeTestingEvalContext(cluster.MakeTestingClusterSettings())
    29  
    30  	// The histogram column ID is 1 for all test cases. CanFilter should only
    31  	// return true for constraints in which column ID 1 is part of the exact
    32  	// prefix or the first column after.
    33  	testData := []struct {
    34  		constraint string
    35  		canFilter  bool
    36  		colIdx     int
    37  	}{
    38  		{
    39  			constraint: "/1: [/0 - /0]",
    40  			canFilter:  true,
    41  			colIdx:     0,
    42  		},
    43  		{
    44  			constraint: "/2: [/0 - /0]",
    45  			canFilter:  false,
    46  		},
    47  		{
    48  			constraint: "/1: [/0 - /0] [/2 - /2]",
    49  			canFilter:  true,
    50  			colIdx:     0,
    51  		},
    52  		{
    53  			constraint: "/1: [/3 - /20] [/22 - ]",
    54  			canFilter:  true,
    55  			colIdx:     0,
    56  		},
    57  		{
    58  			constraint: "/1/2: [/0/3 - /0/3] [/2/3 - /2/3]",
    59  			canFilter:  true,
    60  			colIdx:     0,
    61  		},
    62  		{
    63  			constraint: "/2/-1: [/0/3 - /0/3] [/2/3 - /2/3]",
    64  			canFilter:  false,
    65  		},
    66  		{
    67  			constraint: "/2/1: [/0/3 - /0/3] [/0/5 - /0/5]",
    68  			canFilter:  true,
    69  			colIdx:     1,
    70  		},
    71  		{
    72  			constraint: "/2/-1: [/0/5 - /0/3] [/0/1 - /0/1]",
    73  			canFilter:  true,
    74  			colIdx:     1,
    75  		},
    76  		{
    77  			constraint: "/2/3/1: [/0/3/NULL - /0/3/100] [/0/5/NULL - /0/5/100]",
    78  			canFilter:  false,
    79  		},
    80  		{
    81  			constraint: "/2/-3/1: [/0/5/NULL - /0/5/100] [/0/3/NULL - /0/3/100]",
    82  			canFilter:  false,
    83  		},
    84  		{
    85  			constraint: "/2/1/3: [/0/3/NULL - /0/3/100] [/0/3/200 - /0/3/300]",
    86  			canFilter:  true,
    87  			colIdx:     1,
    88  		},
    89  	}
    90  
    91  	h := Histogram{}
    92  	h.Init(&evalCtx, opt.ColumnID(1), []cat.HistogramBucket{})
    93  	for _, tc := range testData {
    94  		c := constraint.ParseConstraint(&evalCtx, tc.constraint)
    95  		colIdx, _, ok := h.CanFilter(&c)
    96  		if ok != tc.canFilter {
    97  			t.Fatalf(
    98  				"for constraint %s, expected canFilter=%v but found %v", tc.constraint, tc.canFilter, ok,
    99  			)
   100  		}
   101  		if ok && colIdx != tc.colIdx {
   102  			t.Fatalf(
   103  				"for constraint %s, expected colIdx=%d but found %d", tc.constraint, tc.colIdx, colIdx,
   104  			)
   105  		}
   106  	}
   107  }
   108  
   109  func TestHistogram(t *testing.T) {
   110  	evalCtx := tree.MakeTestingEvalContext(cluster.MakeTestingClusterSettings())
   111  
   112  	//   0  1  3  3   4  5   0  0   40  35
   113  	// <--- 1 --- 10 --- 25 --- 30 ---- 42
   114  	histData := []cat.HistogramBucket{
   115  		{NumRange: 0, DistinctRange: 0, NumEq: 1, UpperBound: tree.NewDInt(1)},
   116  		{NumRange: 3, DistinctRange: 2, NumEq: 3, UpperBound: tree.NewDInt(10)},
   117  		{NumRange: 4, DistinctRange: 2, NumEq: 5, UpperBound: tree.NewDInt(25)},
   118  		{NumRange: 0, DistinctRange: 0, NumEq: 0, UpperBound: tree.NewDInt(30)},
   119  		{NumRange: 40, DistinctRange: 7, NumEq: 35, UpperBound: tree.NewDInt(42)},
   120  	}
   121  	h := &Histogram{}
   122  	h.Init(&evalCtx, opt.ColumnID(1), histData)
   123  	count, expected := h.ValuesCount(), float64(91)
   124  	if count != expected {
   125  		t.Fatalf("expected %f but found %f", expected, count)
   126  	}
   127  	maxDistinct, expected := h.maxDistinctValuesCount(), float64(22)
   128  	if maxDistinct != expected {
   129  		t.Fatalf("expected %f but found %f", expected, maxDistinct)
   130  	}
   131  	distinct, expected := h.DistinctValuesCount(), float64(15)
   132  	if distinct != expected {
   133  		t.Fatalf("expected %f but found %f", expected, distinct)
   134  	}
   135  
   136  	testData := []struct {
   137  		constraint  string
   138  		buckets     []cat.HistogramBucket
   139  		count       float64
   140  		maxDistinct float64
   141  		distinct    float64
   142  	}{
   143  		{
   144  			constraint:  "/1: [/0 - /0]",
   145  			buckets:     []cat.HistogramBucket{},
   146  			count:       0,
   147  			maxDistinct: 0,
   148  			distinct:    0,
   149  		},
   150  		{
   151  			constraint:  "/1: [/50 - /100]",
   152  			buckets:     []cat.HistogramBucket{},
   153  			count:       0,
   154  			maxDistinct: 0,
   155  			distinct:    0,
   156  		},
   157  		{
   158  			constraint: "/1: [ - /1] [/11 - /24] [/30 - /45]",
   159  			//   0  1  0  0   3.7143 0.28571 0  0   40  35
   160  			// <--- 1 --- 10 --------- 24 ----- 30 ---- 42
   161  			buckets: []cat.HistogramBucket{
   162  				{NumRange: 0, NumEq: 1, DistinctRange: 0, UpperBound: tree.NewDInt(1)},
   163  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(10)},
   164  				{NumRange: 3.71, NumEq: 0.29, DistinctRange: 1.86, UpperBound: tree.NewDInt(24)},
   165  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(30)},
   166  				{NumRange: 40, NumEq: 35, DistinctRange: 7, UpperBound: tree.NewDInt(42)},
   167  			},
   168  			count:       80,
   169  			maxDistinct: 17,
   170  			distinct:    11.14,
   171  		},
   172  		{
   173  			constraint: "/1: [/5 - /10] [/15 - /32] [/34 - /36] [/38 - ]",
   174  			//   0  0  1.875  3   0  0   2.8571  5   0  0   3.6364 3.6364 0  0   7.2727 3.6364 0  0   14.545  35
   175  			// <--- 4 ------- 10 --- 14 -------- 25 --- 30 --------- 32 ---- 33 --------- 36 ---- 37 -------- 42
   176  			buckets: []cat.HistogramBucket{
   177  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(4)},
   178  				{NumRange: 1.88, NumEq: 3, DistinctRange: 1.25, UpperBound: tree.NewDInt(10)},
   179  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(14)},
   180  				{NumRange: 2.86, NumEq: 5, DistinctRange: 1.43, UpperBound: tree.NewDInt(25)},
   181  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(30)},
   182  				{NumRange: 3.64, NumEq: 3.64, DistinctRange: 0.64, UpperBound: tree.NewDInt(32)},
   183  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(33)},
   184  				{NumRange: 7.27, NumEq: 3.64, DistinctRange: 1.27, UpperBound: tree.NewDInt(36)},
   185  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(37)},
   186  				{NumRange: 14.55, NumEq: 35, DistinctRange: 2.55, UpperBound: tree.NewDInt(42)},
   187  			},
   188  			count:       80.46,
   189  			maxDistinct: 16.73,
   190  			distinct:    12.13,
   191  		},
   192  		{
   193  			constraint: "/1: [ - /41]",
   194  			//   0  1  3  3   4  5   0  0   36.364 3.6364
   195  			// <--- 1 --- 10 --- 25 --- 30 --------- 41 -
   196  			buckets: []cat.HistogramBucket{
   197  				{NumRange: 0, NumEq: 1, DistinctRange: 0, UpperBound: tree.NewDInt(1)},
   198  				{NumRange: 3, NumEq: 3, DistinctRange: 2, UpperBound: tree.NewDInt(10)},
   199  				{NumRange: 4, NumEq: 5, DistinctRange: 2, UpperBound: tree.NewDInt(25)},
   200  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(30)},
   201  				{NumRange: 36.36, NumEq: 3.64, DistinctRange: 6.36, UpperBound: tree.NewDInt(41)},
   202  			},
   203  			count:       56,
   204  			maxDistinct: 21,
   205  			distinct:    14.36,
   206  		},
   207  		{
   208  			constraint: "/1: [/1 - ]",
   209  			//   0  1  3  3   4  5   0  0   40  35
   210  			// <--- 1 --- 10 --- 25 --- 30 ---- 42
   211  			buckets: []cat.HistogramBucket{
   212  				{NumRange: 0, NumEq: 1, DistinctRange: 0, UpperBound: tree.NewDInt(1)},
   213  				{NumRange: 3, NumEq: 3, DistinctRange: 2, UpperBound: tree.NewDInt(10)},
   214  				{NumRange: 4, NumEq: 5, DistinctRange: 2, UpperBound: tree.NewDInt(25)},
   215  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(30)},
   216  				{NumRange: 40, NumEq: 35, DistinctRange: 7, UpperBound: tree.NewDInt(42)},
   217  			},
   218  			count:       91,
   219  			maxDistinct: 22,
   220  			distinct:    15,
   221  		},
   222  		{
   223  			constraint: "/1: [/40 - /40]",
   224  			//   0 5.7143
   225  			// <---- 40 -
   226  			buckets: []cat.HistogramBucket{
   227  				{NumRange: 0, NumEq: 5.71, DistinctRange: 0, UpperBound: tree.NewDInt(40)},
   228  			},
   229  			count:       5.71,
   230  			maxDistinct: 1,
   231  			distinct:    1,
   232  		},
   233  		{
   234  			constraint: "/1: [/0 - /100]",
   235  			//   0  1  3  3   4  5   0  0   40  35
   236  			// <--- 1 --- 10 --- 25 --- 30 ---- 42
   237  			buckets: []cat.HistogramBucket{
   238  				{NumRange: 0, DistinctRange: 0, NumEq: 1, UpperBound: tree.NewDInt(1)},
   239  				{NumRange: 3, DistinctRange: 2, NumEq: 3, UpperBound: tree.NewDInt(10)},
   240  				{NumRange: 4, DistinctRange: 2, NumEq: 5, UpperBound: tree.NewDInt(25)},
   241  				{NumRange: 0, DistinctRange: 0, NumEq: 0, UpperBound: tree.NewDInt(30)},
   242  				{NumRange: 40, DistinctRange: 7, NumEq: 35, UpperBound: tree.NewDInt(42)},
   243  			},
   244  			count:       91,
   245  			maxDistinct: 22,
   246  			distinct:    15,
   247  		},
   248  
   249  		// Tests with multiple columns.
   250  		{
   251  			constraint: "/1/2: [ - /1/3] [/11 - /24/3] [/30 - /45/3]",
   252  			//   0  1  0  0   3.7143 0.28571 0  0   40  35
   253  			// <--- 1 --- 10 --------- 24 ----- 30 ---- 42
   254  			buckets: []cat.HistogramBucket{
   255  				{NumRange: 0, NumEq: 1, DistinctRange: 0, UpperBound: tree.NewDInt(1)},
   256  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(10)},
   257  				{NumRange: 3.71, NumEq: 0.29, DistinctRange: 1.86, UpperBound: tree.NewDInt(24)},
   258  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(30)},
   259  				{NumRange: 40, NumEq: 35, DistinctRange: 7, UpperBound: tree.NewDInt(42)},
   260  			},
   261  			count:       80,
   262  			maxDistinct: 17,
   263  			distinct:    11.14,
   264  		},
   265  		{
   266  			constraint: "/2/1: [/3 - /3/1] [/3/11 - /3/24] [/3/30 - /3/45]",
   267  			//   0  1  0  0   3.7143 0.28571 0  0   40  35
   268  			// <--- 1 --- 10 --------- 24 ----- 30 ---- 42
   269  			buckets: []cat.HistogramBucket{
   270  				{NumRange: 0, NumEq: 1, DistinctRange: 0, UpperBound: tree.NewDInt(1)},
   271  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(10)},
   272  				{NumRange: 3.71, NumEq: 0.29, DistinctRange: 1.86, UpperBound: tree.NewDInt(24)},
   273  				{NumRange: 0, NumEq: 0, DistinctRange: 0, UpperBound: tree.NewDInt(30)},
   274  				{NumRange: 40, NumEq: 35, DistinctRange: 7, UpperBound: tree.NewDInt(42)},
   275  			},
   276  			count:       80,
   277  			maxDistinct: 17,
   278  			distinct:    11.14,
   279  		},
   280  		{
   281  			constraint: "/2/1/3: [/1/40/2 - /1/40/3]",
   282  			//   0 5.7143
   283  			// <---- 40 -
   284  			buckets: []cat.HistogramBucket{
   285  				{NumRange: 0, NumEq: 5.71, DistinctRange: 0, UpperBound: tree.NewDInt(40)},
   286  			},
   287  			count:       5.71,
   288  			maxDistinct: 1,
   289  			distinct:    1,
   290  		},
   291  		{
   292  			constraint: "/2/1/3: [/1/40/2 - /1/40/2] [/1/40/4 - /1/40/4] [/1/40/6 - /1/40/6]",
   293  			//   0 5.7143
   294  			// <---- 40 -
   295  			buckets: []cat.HistogramBucket{
   296  				{NumRange: 0, NumEq: 5.71, DistinctRange: 0, UpperBound: tree.NewDInt(40)},
   297  			},
   298  			count:       5.71,
   299  			maxDistinct: 1,
   300  			distinct:    1,
   301  		},
   302  	}
   303  
   304  	for i := range testData {
   305  		c := constraint.ParseConstraint(&evalCtx, testData[i].constraint)
   306  		ascAndDesc := []constraint.Constraint{c, makeDescConstraint(&c)}
   307  
   308  		// Make sure all test cases work with both ascending and descending columns.
   309  		for _, c := range ascAndDesc {
   310  			if _, _, ok := h.CanFilter(&c); !ok {
   311  				t.Fatalf("constraint %s cannot filter histogram %v", c.String(), *h)
   312  			}
   313  			filtered := h.Filter(&c)
   314  			count := roundVal(filtered.ValuesCount())
   315  			if testData[i].count != count {
   316  				t.Fatalf("expected %f but found %f", testData[i].count, count)
   317  			}
   318  			maxDistinct := roundVal(filtered.maxDistinctValuesCount())
   319  			if testData[i].maxDistinct != maxDistinct {
   320  				t.Fatalf("expected %f but found %f", testData[i].maxDistinct, maxDistinct)
   321  			}
   322  			distinct := roundVal(filtered.DistinctValuesCount())
   323  			if testData[i].distinct != distinct {
   324  				t.Fatalf("expected %f but found %f", testData[i].distinct, distinct)
   325  			}
   326  			roundHistogram(filtered)
   327  			if !reflect.DeepEqual(testData[i].buckets, filtered.buckets) {
   328  				t.Fatalf("expected %v but found %v", testData[i].buckets, filtered.buckets)
   329  			}
   330  		}
   331  	}
   332  }
   333  
   334  func TestFilterBucket(t *testing.T) {
   335  	evalCtx := tree.MakeTestingEvalContext(cluster.MakeTestingClusterSettings())
   336  	keyCtx := constraint.KeyContext{EvalCtx: &evalCtx}
   337  	col := opt.ColumnID(1)
   338  
   339  	type testCase struct {
   340  		span     string
   341  		expected *cat.HistogramBucket
   342  		isError  bool
   343  	}
   344  
   345  	runTestCase := func(
   346  		h *Histogram, span *constraint.Span, desc bool,
   347  	) (actual *cat.HistogramBucket, err error) {
   348  		defer func() {
   349  			// Any errors will be propagated as panics.
   350  			if r := recover(); r != nil {
   351  				if e, ok := r.(error); ok {
   352  					err = e
   353  					return
   354  				}
   355  				panic(r)
   356  			}
   357  		}()
   358  
   359  		keyCtx.Columns.InitSingle(opt.MakeOrderingColumn(col, desc))
   360  		var iter histogramIter
   361  		iter.init(h, desc)
   362  
   363  		// All test cases have two buckets. The first bucket is empty and used to
   364  		// mark the lower bound of the second bucket. Set the iterator to point to
   365  		// the second bucket.
   366  		iter.setIdx(1)
   367  		b := getFilteredBucket(&iter, &keyCtx, span, 0 /* colIdx */)
   368  		roundBucket(b)
   369  		return b, nil
   370  	}
   371  
   372  	runTest := func(h *Histogram, testData []testCase, typ types.Family) {
   373  		for _, testCase := range testData {
   374  			span := constraint.ParseSpan(&evalCtx, testCase.span, typ)
   375  			ascAndDesc := []constraint.Span{span, makeDescSpan(&span)}
   376  
   377  			// Make sure all test cases work with both ascending and descending columns.
   378  			for i, span := range ascAndDesc {
   379  				actual, err := runTestCase(h, &span, i == 1 /* desc */)
   380  				if err != nil && !testCase.isError {
   381  					t.Fatalf("for span %s got error %v", testCase.span, err)
   382  				} else if err == nil {
   383  					if testCase.isError {
   384  						t.Fatalf("for span %s expected an error", testCase.span)
   385  					}
   386  					if !reflect.DeepEqual(testCase.expected, actual) {
   387  						t.Fatalf("for span %s exected %v but found %v", testCase.span, testCase.expected, actual)
   388  					}
   389  				}
   390  			}
   391  		}
   392  	}
   393  
   394  	// Each of the tests below have a histogram with two buckets. The first
   395  	// bucket is empty and simply used to mark the lower bound of the second
   396  	// bucket.
   397  	//
   398  	// getPrevUpperBound is used to find the upper bound of the first bucket so
   399  	// that the lower bound of the second bucket will equal upperBound.Next().
   400  	getPrevUpperBound := func(lowerBound tree.Datum) tree.Datum {
   401  		res, ok := lowerBound.Prev(&evalCtx)
   402  		if !ok {
   403  			res = lowerBound
   404  		}
   405  		return res
   406  	}
   407  
   408  	t.Run("int", func(t *testing.T) {
   409  		h := &Histogram{evalCtx: &evalCtx, col: col, buckets: []cat.HistogramBucket{
   410  			{NumEq: 0, NumRange: 0, DistinctRange: 0, UpperBound: getPrevUpperBound(tree.NewDInt(0))},
   411  			{NumEq: 5, NumRange: 10, DistinctRange: 10, UpperBound: tree.NewDInt(10)},
   412  		}}
   413  		testData := []testCase{
   414  			{
   415  				span:     "[/0 - /0]",
   416  				expected: &cat.HistogramBucket{NumEq: 1, NumRange: 0, DistinctRange: 0, UpperBound: tree.NewDInt(0)},
   417  			},
   418  			{
   419  				span:     "[/0 - /5]",
   420  				expected: &cat.HistogramBucket{NumEq: 1, NumRange: 5, DistinctRange: 5, UpperBound: tree.NewDInt(5)},
   421  			},
   422  			{
   423  				span:     "[/2 - /9]",
   424  				expected: &cat.HistogramBucket{NumEq: 1, NumRange: 7, DistinctRange: 7, UpperBound: tree.NewDInt(9)},
   425  			},
   426  			{
   427  				span:     "[/2 - /10]",
   428  				expected: &cat.HistogramBucket{NumEq: 5, NumRange: 8, DistinctRange: 8, UpperBound: tree.NewDInt(10)},
   429  			},
   430  			{
   431  				span:     "[/10 - /10]",
   432  				expected: &cat.HistogramBucket{NumEq: 5, NumRange: 0, DistinctRange: 0, UpperBound: tree.NewDInt(10)},
   433  			},
   434  			{
   435  				span:    "[/20 - /30]",
   436  				isError: true,
   437  			},
   438  		}
   439  
   440  		runTest(h, testData, types.IntFamily)
   441  	})
   442  
   443  	t.Run("float", func(t *testing.T) {
   444  		h := &Histogram{evalCtx: &evalCtx, col: col, buckets: []cat.HistogramBucket{
   445  			{NumEq: 0, NumRange: 0, DistinctRange: 0, UpperBound: getPrevUpperBound(tree.NewDFloat(0))},
   446  			{NumEq: 5, NumRange: 10, DistinctRange: 10, UpperBound: tree.NewDFloat(10)},
   447  		}}
   448  		testData := []testCase{
   449  			{
   450  				span:     "[/0 - /0]",
   451  				expected: &cat.HistogramBucket{NumEq: 1, NumRange: 0, DistinctRange: 0, UpperBound: tree.NewDFloat(0)},
   452  			},
   453  			{
   454  				span:     "(/0 - /5]",
   455  				expected: &cat.HistogramBucket{NumEq: 0, NumRange: 5, DistinctRange: 5, UpperBound: tree.NewDFloat(5)},
   456  			},
   457  			{
   458  				span:     "[/2.5 - /9)",
   459  				expected: &cat.HistogramBucket{NumEq: 0, NumRange: 6.5, DistinctRange: 6.5, UpperBound: tree.NewDFloat(9)},
   460  			},
   461  			{
   462  				span:     "[/2 - /10]",
   463  				expected: &cat.HistogramBucket{NumEq: 5, NumRange: 8, DistinctRange: 8, UpperBound: tree.NewDFloat(10)},
   464  			},
   465  			{
   466  				span:     "[/10 - /10]",
   467  				expected: &cat.HistogramBucket{NumEq: 5, NumRange: 0, DistinctRange: 0, UpperBound: tree.NewDFloat(10)},
   468  			},
   469  			{
   470  				span:    "[/10 - /20]",
   471  				isError: true,
   472  			},
   473  		}
   474  
   475  		runTest(h, testData, types.FloatFamily)
   476  	})
   477  
   478  	t.Run("decimal", func(t *testing.T) {
   479  		upperBound, err := tree.ParseDDecimal("10")
   480  		if err != nil {
   481  			t.Fatal(err)
   482  		}
   483  		lowerBound, err := tree.ParseDDecimal("0")
   484  		if err != nil {
   485  			t.Fatal(err)
   486  		}
   487  		h := &Histogram{evalCtx: &evalCtx, col: col, buckets: []cat.HistogramBucket{
   488  			{NumEq: 0, NumRange: 0, DistinctRange: 0, UpperBound: getPrevUpperBound(lowerBound)},
   489  			{NumEq: 5, NumRange: 10, DistinctRange: 10, UpperBound: upperBound},
   490  		}}
   491  
   492  		ub1, err := tree.ParseDDecimal("9")
   493  		if err != nil {
   494  			t.Fatal(err)
   495  		}
   496  		ub2, err := tree.ParseDDecimal("10.00")
   497  		if err != nil {
   498  			t.Fatal(err)
   499  		}
   500  
   501  		testData := []testCase{
   502  			{
   503  				span:     "[/2.50 - /9)",
   504  				expected: &cat.HistogramBucket{NumEq: 0, NumRange: 6.5, DistinctRange: 6.5, UpperBound: ub1},
   505  			},
   506  			{
   507  				span:     "[/2 - /10.00]",
   508  				expected: &cat.HistogramBucket{NumEq: 5, NumRange: 8, DistinctRange: 8, UpperBound: ub2},
   509  			},
   510  		}
   511  
   512  		runTest(h, testData, types.DecimalFamily)
   513  	})
   514  
   515  	t.Run("date", func(t *testing.T) {
   516  		upperBound, err := tree.ParseDDate(&evalCtx, "2019-08-01")
   517  		if err != nil {
   518  			t.Fatal(err)
   519  		}
   520  		lowerBound, err := tree.ParseDDate(&evalCtx, "2019-07-01")
   521  		if err != nil {
   522  			t.Fatal(err)
   523  		}
   524  		h := &Histogram{evalCtx: &evalCtx, col: col, buckets: []cat.HistogramBucket{
   525  			{NumEq: 0, NumRange: 0, DistinctRange: 0, UpperBound: getPrevUpperBound(lowerBound)},
   526  			{NumEq: 1, NumRange: 62, DistinctRange: 31, UpperBound: upperBound},
   527  		}}
   528  
   529  		ub1, err := tree.ParseDDate(&evalCtx, "2019-07-02")
   530  		if err != nil {
   531  			t.Fatal(err)
   532  		}
   533  
   534  		testData := []testCase{
   535  			{
   536  				span:     "[/2019-07-01 - /2019-07-02]",
   537  				expected: &cat.HistogramBucket{NumEq: 2, NumRange: 2, DistinctRange: 1, UpperBound: ub1},
   538  			},
   539  			{
   540  				span:     "[/2019-07-05 - /2019-08-01]",
   541  				expected: &cat.HistogramBucket{NumEq: 1, NumRange: 54, DistinctRange: 27, UpperBound: upperBound},
   542  			},
   543  		}
   544  
   545  		runTest(h, testData, types.DateFamily)
   546  	})
   547  
   548  	t.Run("timestamp", func(t *testing.T) {
   549  		upperBound, err := tree.ParseDTimestamp(&evalCtx, "2019-08-01 12:00:00.000000", time.Microsecond)
   550  		if err != nil {
   551  			t.Fatal(err)
   552  		}
   553  		lowerBound, err := tree.ParseDTimestamp(&evalCtx, "2019-07-01 12:00:00.000000", time.Microsecond)
   554  		if err != nil {
   555  			t.Fatal(err)
   556  		}
   557  		h := &Histogram{evalCtx: &evalCtx, col: col, buckets: []cat.HistogramBucket{
   558  			{NumEq: 0, NumRange: 0, DistinctRange: 0, UpperBound: getPrevUpperBound(lowerBound)},
   559  			{NumEq: 1, NumRange: 62, DistinctRange: 31, UpperBound: upperBound},
   560  		}}
   561  
   562  		ub1, err := tree.ParseDTimestamp(&evalCtx, "2019-07-02 00:00:00.000000", time.Microsecond)
   563  		if err != nil {
   564  			t.Fatal(err)
   565  		}
   566  
   567  		testData := []testCase{
   568  			{
   569  				span:     "[/2019-07-01 12:00:00.000000 - /2019-07-02 00:00:00.000000)",
   570  				expected: &cat.HistogramBucket{NumEq: 0, NumRange: 1, DistinctRange: 0.5, UpperBound: ub1},
   571  			},
   572  			{
   573  				span:     "[/2019-07-05 12:00:00.000000 - /2019-08-01 12:00:00.000000)",
   574  				expected: &cat.HistogramBucket{NumEq: 0, NumRange: 54, DistinctRange: 27, UpperBound: upperBound},
   575  			},
   576  		}
   577  
   578  		runTest(h, testData, types.TimestampFamily)
   579  	})
   580  
   581  	t.Run("string", func(t *testing.T) {
   582  		h := &Histogram{evalCtx: &evalCtx, col: col, buckets: []cat.HistogramBucket{
   583  			{NumEq: 0, NumRange: 0, DistinctRange: 0, UpperBound: getPrevUpperBound(tree.NewDString("baq"))},
   584  			{NumEq: 5, NumRange: 10, DistinctRange: 10, UpperBound: tree.NewDString("foo")},
   585  		}}
   586  		testData := []testCase{
   587  			{
   588  				span:     "[/bar - /baz]",
   589  				expected: &cat.HistogramBucket{NumEq: 0, NumRange: 5, DistinctRange: 5, UpperBound: tree.NewDString("baz")},
   590  			},
   591  			{
   592  				span:     "[/baz - /foo]",
   593  				expected: &cat.HistogramBucket{NumEq: 5, NumRange: 5, DistinctRange: 5, UpperBound: tree.NewDString("foo")},
   594  			},
   595  		}
   596  
   597  		runTest(h, testData, types.StringFamily)
   598  	})
   599  
   600  }
   601  
   602  // makeDescSpan makes an equivalent version of s in which the start and end
   603  // keys are swapped.
   604  func makeDescSpan(s *constraint.Span) constraint.Span {
   605  	var desc constraint.Span
   606  	desc.Init(s.EndKey(), s.EndBoundary(), s.StartKey(), s.StartBoundary())
   607  	return desc
   608  }
   609  
   610  // makeDescConstraint makes an equivalent version of c in which all columns
   611  // are descending.
   612  func makeDescConstraint(c *constraint.Constraint) constraint.Constraint {
   613  	var desc constraint.Constraint
   614  
   615  	// Negate all the columns.
   616  	cols := make([]opt.OrderingColumn, c.Columns.Count())
   617  	for i := range cols {
   618  		cols[i] = -c.Columns.Get(i)
   619  	}
   620  	desc.Columns.Init(cols)
   621  
   622  	// Add all the spans in reverse order, with their start and end keys
   623  	// swapped.
   624  	desc.Spans.Alloc(c.Spans.Count())
   625  	for i := c.Spans.Count() - 1; i >= 0; i-- {
   626  		s := makeDescSpan(c.Spans.Get(i))
   627  		desc.Spans.Append(&s)
   628  	}
   629  
   630  	return desc
   631  }
   632  
   633  // Round all values to two decimal places.
   634  func roundVal(val float64) float64 {
   635  	return math.Round(val*100.0) / 100.0
   636  }
   637  
   638  func roundBucket(b *cat.HistogramBucket) {
   639  	b.NumRange = roundVal(b.NumRange)
   640  	b.NumEq = roundVal(b.NumEq)
   641  	b.DistinctRange = roundVal(b.DistinctRange)
   642  }
   643  
   644  func roundHistogram(h *Histogram) {
   645  	for i := range h.buckets {
   646  		roundBucket(&h.buckets[i])
   647  	}
   648  }