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  }