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

     1  // Copyright 2020 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 enum
    12  
    13  import "bytes"
    14  
    15  // Note that while maxToken is outside the range of a single
    16  // byte, we never actually insert it in GenByteStringBetween.
    17  // We only use it to perform computation using the value 256.
    18  const minToken int = 0
    19  const maxToken int = 256
    20  const midToken = maxToken / 2
    21  const shiftInterval = 5
    22  
    23  // ByteSpacing is a type that controls what distribution of generated
    24  // bytes strings is created with calls to GenByteStringBetween.
    25  type ByteSpacing int
    26  
    27  const (
    28  	// PackedSpacing is used when the generated bytes are intended to be "close"
    29  	// together in the generated key space.
    30  	PackedSpacing ByteSpacing = iota
    31  	// SpreadSpacing is used when the generated bytes are intended to be evenly
    32  	// spaced out within the generated key space.
    33  	SpreadSpacing
    34  )
    35  
    36  func (s ByteSpacing) String() string {
    37  	switch s {
    38  	case PackedSpacing:
    39  		return "packed"
    40  	case SpreadSpacing:
    41  		return "spread"
    42  	}
    43  	panic("unknown spacing type")
    44  }
    45  
    46  // GenByteStringBetween generates a byte string that sorts
    47  // between the two input strings. If prev is length 0, it is
    48  // treated as negative infinity. If next is length 0, it is
    49  // treated as positive infinity. Importantly, the input strings
    50  // cannot end with minToken.
    51  func GenByteStringBetween(prev []byte, next []byte, spacing ByteSpacing) []byte {
    52  	result := make([]byte, 0)
    53  	if len(prev) == 0 && len(next) == 0 {
    54  		// If both prev and next are unbounded, return the midpoint.
    55  		return append(result, byte(midToken))
    56  	}
    57  	maxLen := len(prev)
    58  	if len(next) > maxLen {
    59  		maxLen = len(next)
    60  	}
    61  
    62  	// Construct the prefix of prev and next.
    63  	pos := 0
    64  	for ; pos < maxLen; pos++ {
    65  		p, n := get(prev, pos, minToken), get(next, pos, maxToken)
    66  		if p != n {
    67  			break
    68  		}
    69  		result = append(result, byte(p))
    70  	}
    71  
    72  	// We've found an index where prev and next disagree. So, it's time to start
    73  	// trying to construct a value between prev and next.
    74  	p, n := get(prev, pos, minToken), get(next, pos, maxToken)
    75  	var mid int
    76  	switch spacing {
    77  	case PackedSpacing:
    78  		mid = byteBetweenPacked(p, n)
    79  	case SpreadSpacing:
    80  		mid = byteBetweenSpread(p, n)
    81  	}
    82  
    83  	// If mid == p, then we know there is no more room between
    84  	// prev and next at this index. So, we can append p to result
    85  	// to ensure that it is less than next. To generate the rest
    86  	// of the string, we try to find a string that fits between
    87  	// the remainder of prev and posinf.
    88  	if mid == p {
    89  		result = append(result, byte(p))
    90  		rest := GenByteStringBetween(slice(prev, pos+1), nil, spacing)
    91  		return append(result, rest...)
    92  	}
    93  
    94  	// If mid != p, then there is room between prev and next at this index.
    95  	// So, occupy that spot and return.
    96  	result = append(result, byte(mid))
    97  	return result
    98  }
    99  
   100  // Utility functions for GenByteStringBetween.
   101  
   102  func get(arr []byte, idx int, def int) int {
   103  	if idx >= len(arr) {
   104  		return def
   105  	}
   106  	return int(arr[idx])
   107  }
   108  
   109  func slice(arr []byte, idx int) []byte {
   110  	if idx > len(arr) {
   111  		return []byte(nil)
   112  	}
   113  	return arr[idx:]
   114  }
   115  
   116  // byteBetweenPacked generates a byte value between hi and lo, inclusive.
   117  // Additionally, it optimizes for adding values at the beginning and end of the
   118  // enum byte sequence by returning a value moved by a small constant
   119  // rather than in the middle of the range when the upper and lower
   120  // bounds are the min or max token.
   121  func byteBetweenPacked(lo int, hi int) int {
   122  	switch {
   123  	case lo == minToken && hi == maxToken:
   124  		return lo + (hi-lo)/2
   125  	case lo == minToken && hi-lo > shiftInterval:
   126  		return hi - shiftInterval
   127  	case hi == maxToken && hi-lo > shiftInterval:
   128  		return lo + shiftInterval
   129  	default:
   130  		return lo + (hi-lo)/2
   131  	}
   132  }
   133  
   134  // byteBetweenSpread returns a byte value between hi and lo, inclusive.
   135  func byteBetweenSpread(lo int, hi int) int {
   136  	return lo + (hi-lo)/2
   137  }
   138  
   139  // GenerateNEvenlySpacedBytes returns an array of n byte slices that
   140  // evenly split the key space into n pieces.
   141  func GenerateNEvenlySpacedBytes(n int) [][]byte {
   142  	result := make([][]byte, n)
   143  	genEvenlySpacedHelper(result, 0, n, nil, nil)
   144  	return result
   145  }
   146  
   147  // genEvenlySpacedHelper fills in result with a byte value between bot and top
   148  // at the index between lo and hi.
   149  func genEvenlySpacedHelper(result [][]byte, lo, hi int, bot, top []byte) {
   150  	if lo == hi {
   151  		return
   152  	}
   153  	mid := lo + (hi-lo)/2
   154  	midBytes := GenByteStringBetween(bot, top, SpreadSpacing)
   155  	result[mid] = midBytes
   156  	genEvenlySpacedHelper(result, lo, mid, bot, midBytes)
   157  	genEvenlySpacedHelper(result, mid+1, hi, midBytes, top)
   158  }
   159  
   160  // enumBytesAreLess is a utility function for comparing byte values generated
   161  // for enum's physical representation. It adjusts bytes.Compare to treat b=nil
   162  // as positive infinity.
   163  func enumBytesAreLess(a []byte, b []byte) bool {
   164  	if len(b) == 0 {
   165  		return true
   166  	}
   167  	return bytes.Compare(a, b) == -1
   168  }