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 }