vitess.io/vitess@v0.16.2/go/vt/vtgate/vindexes/multicol.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vindexes 18 19 import ( 20 "bytes" 21 "context" 22 "math" 23 "strconv" 24 "strings" 25 26 "vitess.io/vitess/go/sqltypes" 27 "vitess.io/vitess/go/vt/key" 28 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 29 "vitess.io/vitess/go/vt/vterrors" 30 ) 31 32 var _ MultiColumn = (*MultiCol)(nil) 33 34 type MultiCol struct { 35 name string 36 cost int 37 noOfCols int 38 columnVdx map[int]Hashing 39 columnBytes map[int]int 40 } 41 42 const ( 43 paramColumnCount = "column_count" 44 paramColumnBytes = "column_bytes" 45 paramColumnVindex = "column_vindex" 46 defaultVindex = "hash" 47 ) 48 49 // NewMultiCol creates a new MultiCol. 50 func NewMultiCol(name string, m map[string]string) (Vindex, error) { 51 colCount, err := getColumnCount(m) 52 if err != nil { 53 return nil, err 54 } 55 columnBytes, err := getColumnBytes(m, colCount) 56 if err != nil { 57 return nil, err 58 } 59 columnVdx, vindexCost, err := getColumnVindex(m, colCount) 60 if err != nil { 61 return nil, err 62 } 63 64 return &MultiCol{ 65 name: name, 66 cost: vindexCost, 67 noOfCols: colCount, 68 columnVdx: columnVdx, 69 columnBytes: columnBytes, 70 }, nil 71 } 72 73 func (m *MultiCol) String() string { 74 return m.name 75 } 76 77 func (m *MultiCol) Cost() int { 78 return m.cost 79 } 80 81 func (m *MultiCol) IsUnique() bool { 82 return true 83 } 84 85 func (m *MultiCol) NeedsVCursor() bool { 86 return false 87 } 88 89 func (m *MultiCol) Map(ctx context.Context, vcursor VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) { 90 out := make([]key.Destination, 0, len(rowsColValues)) 91 for _, colValues := range rowsColValues { 92 partial, ksid, err := m.mapKsid(colValues) 93 if err != nil { 94 out = append(out, key.DestinationNone{}) 95 continue 96 } 97 if partial { 98 out = append(out, NewKeyRangeFromPrefix(ksid)) 99 continue 100 } 101 out = append(out, key.DestinationKeyspaceID(ksid)) 102 } 103 return out, nil 104 } 105 106 func (m *MultiCol) Verify(ctx context.Context, vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte) ([]bool, error) { 107 out := make([]bool, 0, len(rowsColValues)) 108 for idx, colValues := range rowsColValues { 109 _, ksid, err := m.mapKsid(colValues) 110 if err != nil { 111 return nil, err 112 } 113 out = append(out, bytes.Equal(ksid, ksids[idx])) 114 } 115 return out, nil 116 } 117 118 func (m *MultiCol) PartialVindex() bool { 119 return true 120 } 121 122 func (m *MultiCol) mapKsid(colValues []sqltypes.Value) (bool, []byte, error) { 123 if m.noOfCols < len(colValues) { 124 // wrong number of column values were passed 125 return false, nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] wrong number of column values were passed: maximum allowed %d, got %d", m.noOfCols, len(colValues)) 126 } 127 ksid := make([]byte, 0, 8) 128 minLength := 0 129 for idx, colVal := range colValues { 130 lksid, err := m.columnVdx[idx].Hash(colVal) 131 if err != nil { 132 return false, nil, err 133 } 134 // keyspace id should fill the minimum length i.e. the bytes utilized before the current column hash. 135 padZero := minLength - len(ksid) 136 for ; padZero > 0; padZero-- { 137 ksid = append(ksid, uint8(0)) 138 } 139 maxIndex := m.columnBytes[idx] 140 for r, v := range lksid { 141 if r >= maxIndex { 142 break 143 } 144 ksid = append(ksid, v) 145 } 146 minLength = minLength + maxIndex 147 } 148 partial := m.noOfCols > len(colValues) 149 return partial, ksid, nil 150 } 151 152 func init() { 153 Register("multicol", NewMultiCol) 154 } 155 156 func getColumnVindex(m map[string]string, colCount int) (map[int]Hashing, int, error) { 157 var colVdxs []string 158 colVdxsStr, ok := m[paramColumnVindex] 159 if ok { 160 colVdxs = strings.Split(colVdxsStr, ",") 161 } 162 if len(colVdxs) > colCount { 163 return nil, 0, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "number of vindex function provided are more than column count in the parameter '%s'", paramColumnVindex) 164 } 165 columnVdx := make(map[int]Hashing, colCount) 166 vindexCost := 0 167 for i := 0; i < colCount; i++ { 168 selVdx := defaultVindex 169 if len(colVdxs) > i { 170 providedVdx := strings.TrimSpace(colVdxs[i]) 171 if providedVdx != "" { 172 selVdx = providedVdx 173 } 174 } 175 // TODO: reuse vindex. avoid creating same vindex. 176 vdx, err := CreateVindex(selVdx, selVdx, m) 177 if err != nil { 178 return nil, 0, err 179 } 180 hashVdx, isHashVdx := vdx.(Hashing) 181 if !isHashVdx || !vdx.IsUnique() || vdx.NeedsVCursor() { 182 return nil, 0, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "multicol vindex supports vindexes that exports hashing function, are unique and are non-lookup vindex, passed vindex '%s' is invalid", selVdx) 183 } 184 vindexCost = vindexCost + vdx.Cost() 185 columnVdx[i] = hashVdx 186 } 187 return columnVdx, vindexCost, nil 188 } 189 190 func getColumnBytes(m map[string]string, colCount int) (map[int]int, error) { 191 var colByteStr []string 192 colBytesStr, ok := m[paramColumnBytes] 193 if ok { 194 colByteStr = strings.Split(colBytesStr, ",") 195 } 196 if len(colByteStr) > colCount { 197 return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "number of column bytes provided are more than column count in the parameter '%s'", paramColumnBytes) 198 } 199 // validate bytes count 200 bytesUsed := 0 201 columnBytes := make(map[int]int, colCount) 202 for idx, byteStr := range colByteStr { 203 if byteStr == "" { 204 continue 205 } 206 colByte, err := strconv.Atoi(byteStr) 207 if err != nil { 208 return nil, err 209 } 210 bytesUsed = bytesUsed + colByte 211 columnBytes[idx] = colByte 212 } 213 pendingCol := colCount - len(columnBytes) 214 remainingBytes := 8 - bytesUsed 215 if pendingCol > remainingBytes { 216 return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "column bytes count exceeds the keyspace id length (total bytes count cannot exceed 8 bytes) in the parameter '%s'", paramColumnBytes) 217 } 218 if pendingCol <= 0 { 219 return columnBytes, nil 220 } 221 for idx := 0; idx < colCount; idx++ { 222 if _, defined := columnBytes[idx]; defined { 223 continue 224 } 225 bytesToAssign := int(math.Ceil(float64(remainingBytes) / float64(pendingCol))) 226 columnBytes[idx] = bytesToAssign 227 remainingBytes = remainingBytes - bytesToAssign 228 pendingCol-- 229 } 230 return columnBytes, nil 231 } 232 233 func getColumnCount(m map[string]string) (int, error) { 234 colCountStr, ok := m[paramColumnCount] 235 if !ok { 236 return 0, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "number of columns not provided in the parameter '%s'", paramColumnCount) 237 } 238 colCount, err := strconv.Atoi(colCountStr) 239 if err != nil { 240 return 0, err 241 } 242 if colCount > 8 || colCount < 1 { 243 return 0, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "number of columns should be between 1 and 8 in the parameter '%s'", paramColumnCount) 244 } 245 return colCount, nil 246 }