vitess.io/vitess@v0.16.2/go/vt/vtgate/vindexes/hash.go (about)

     1  /*
     2  Copyright 2019 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  	"crypto/cipher"
    23  	"crypto/des"
    24  	"encoding/binary"
    25  	"encoding/hex"
    26  	"fmt"
    27  	"strconv"
    28  
    29  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    30  
    31  	"vitess.io/vitess/go/sqltypes"
    32  	"vitess.io/vitess/go/vt/key"
    33  )
    34  
    35  var (
    36  	_ SingleColumn = (*Hash)(nil)
    37  	_ Reversible   = (*Hash)(nil)
    38  	_ Hashing      = (*Hash)(nil)
    39  )
    40  
    41  // Hash defines vindex that hashes an int64 to a KeyspaceId
    42  // by using null-key DES hash. It's Unique, Reversible and
    43  // Functional.
    44  // Note that at once stage we used a 3DES-based hash here,
    45  // but for a null key as in our case, they are completely equivalent.
    46  type Hash struct {
    47  	name string
    48  }
    49  
    50  // NewHash creates a new Hash.
    51  func NewHash(name string, _ map[string]string) (Vindex, error) {
    52  	return &Hash{name: name}, nil
    53  }
    54  
    55  // String returns the name of the vindex.
    56  func (vind *Hash) String() string {
    57  	return vind.name
    58  }
    59  
    60  // Cost returns the cost of this index as 1.
    61  func (vind *Hash) Cost() int {
    62  	return 1
    63  }
    64  
    65  // IsUnique returns true since the Vindex is unique.
    66  func (vind *Hash) IsUnique() bool {
    67  	return true
    68  }
    69  
    70  // NeedsVCursor satisfies the Vindex interface.
    71  func (vind *Hash) NeedsVCursor() bool {
    72  	return false
    73  }
    74  
    75  // Map can map ids to key.Destination objects.
    76  func (vind *Hash) Map(ctx context.Context, vcursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) {
    77  	out := make([]key.Destination, len(ids))
    78  	for i, id := range ids {
    79  		ksid, err := vind.Hash(id)
    80  		if err != nil {
    81  			out[i] = key.DestinationNone{}
    82  			continue
    83  		}
    84  		out[i] = key.DestinationKeyspaceID(ksid)
    85  	}
    86  	return out, nil
    87  }
    88  
    89  // Verify returns true if ids maps to ksids.
    90  func (vind *Hash) Verify(ctx context.Context, vcursor VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) {
    91  	out := make([]bool, len(ids))
    92  	for i := range ids {
    93  		num, err := evalengine.ToUint64(ids[i])
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  		out[i] = bytes.Equal(vhash(num), ksids[i])
    98  	}
    99  	return out, nil
   100  }
   101  
   102  // ReverseMap returns the ids from ksids.
   103  func (vind *Hash) ReverseMap(_ VCursor, ksids [][]byte) ([]sqltypes.Value, error) {
   104  	reverseIds := make([]sqltypes.Value, 0, len(ksids))
   105  	for _, keyspaceID := range ksids {
   106  		val, err := vunhash(keyspaceID)
   107  		if err != nil {
   108  			return reverseIds, err
   109  		}
   110  		reverseIds = append(reverseIds, sqltypes.NewUint64(val))
   111  	}
   112  	return reverseIds, nil
   113  }
   114  
   115  func (vind *Hash) Hash(id sqltypes.Value) ([]byte, error) {
   116  	var num uint64
   117  	var err error
   118  
   119  	if id.IsSigned() {
   120  		// This is ToUint64 with no check on negative values.
   121  		str := id.ToString()
   122  		var ival int64
   123  		ival, err = strconv.ParseInt(str, 10, 64)
   124  		num = uint64(ival)
   125  	} else {
   126  		num, err = evalengine.ToUint64(id)
   127  	}
   128  
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	return vhash(num), nil
   133  }
   134  
   135  var blockDES cipher.Block
   136  
   137  func init() {
   138  	var err error
   139  	blockDES, err = des.NewCipher(make([]byte, 8))
   140  	if err != nil {
   141  		panic(err)
   142  	}
   143  	Register("hash", NewHash)
   144  }
   145  
   146  func vhash(shardKey uint64) []byte {
   147  	var keybytes, hashed [8]byte
   148  	binary.BigEndian.PutUint64(keybytes[:], shardKey)
   149  	blockDES.Encrypt(hashed[:], keybytes[:])
   150  	return hashed[:]
   151  }
   152  
   153  func vunhash(k []byte) (uint64, error) {
   154  	if len(k) != 8 {
   155  		return 0, fmt.Errorf("invalid keyspace id: %v", hex.EncodeToString(k))
   156  	}
   157  	var unhashed [8]byte
   158  	blockDES.Decrypt(unhashed[:], k)
   159  	return binary.BigEndian.Uint64(unhashed[:]), nil
   160  }