diff --git a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go new file mode 100644 index 00000000000..ec87042fae8 --- /dev/null +++ b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go @@ -0,0 +1,404 @@ +/* +Copyright 2017 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vindexes + +import ( + "encoding/binary" + "encoding/json" + "fmt" + + "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/key" + topodatapb "vitess.io/vitess/go/vt/proto/topodata" +) + +var ( + _ Vindex = (*LookupUnicodeLooseMD5Hash)(nil) + _ Lookup = (*LookupUnicodeLooseMD5Hash)(nil) + _ Vindex = (*LookupUnicodeLooseMD5HashUnique)(nil) + _ Lookup = (*LookupUnicodeLooseMD5HashUnique)(nil) +) + +func init() { + Register("lookup_unicodeloosemd5_hash", NewLookupUnicodeLooseMD5Hash) + Register("lookup_unicodeloosemd5_hash_unique", NewLookupUnicodeLooseMD5HashUnique) +} + +//==================================================================== + +// LookupUnicodeLooseMD5Hash defines a vindex that uses a lookup table. +// The table is expected to define the id column as unique. It's +// NonUnique and a Lookup and stores the from value in a hashed form. +// Warning: This Vindex is being depcreated in favor of Lookup +type LookupUnicodeLooseMD5Hash struct { + name string + writeOnly bool + lkp lookupInternal +} + +// NewLookupUnicodeLooseMD5Hash creates a LookupUnicodeLooseMD5Hash vindex. +// The supplied map has the following required fields: +// table: name of the backing table. It can be qualified by the keyspace. +// from: list of columns in the table that have the 'from' values of the lookup vindex. +// to: The 'to' column name of the table. +// +// The following fields are optional: +// autocommit: setting this to "true" will cause inserts to upsert and deletes to be ignored. +// write_only: in this mode, Map functions return the full keyrange causing a full scatter. +func NewLookupUnicodeLooseMD5Hash(name string, m map[string]string) (Vindex, error) { + lh := &LookupUnicodeLooseMD5Hash{name: name} + + autocommit, err := boolFromMap(m, "autocommit") + if err != nil { + return nil, err + } + lh.writeOnly, err = boolFromMap(m, "write_only") + if err != nil { + return nil, err + } + + // if autocommit is on for non-unique lookup, upsert should also be on. + if err := lh.lkp.Init(m, autocommit, autocommit /* upsert */); err != nil { + return nil, err + } + return lh, nil +} + +// String returns the name of the vindex. +func (lh *LookupUnicodeLooseMD5Hash) String() string { + return lh.name +} + +// Cost returns the cost of this vindex as 20. +func (lh *LookupUnicodeLooseMD5Hash) Cost() int { + return 20 +} + +// IsUnique returns false since the Vindex is not unique. +func (lh *LookupUnicodeLooseMD5Hash) IsUnique() bool { + return false +} + +// IsFunctional returns false since the Vindex is not functional. +func (lh *LookupUnicodeLooseMD5Hash) IsFunctional() bool { + return false +} + +// Map can map ids to key.Destination objects. +func (lh *LookupUnicodeLooseMD5Hash) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) { + out := make([]key.Destination, 0, len(ids)) + if lh.writeOnly { + for range ids { + out = append(out, key.DestinationKeyRange{KeyRange: &topodatapb.KeyRange{}}) + } + return out, nil + } + + ids, err := convertIds(ids) + if err != nil { + return nil, err + } + results, err := lh.lkp.Lookup(vcursor, ids) + if err != nil { + return nil, err + } + for _, result := range results { + if len(result.Rows) == 0 { + out = append(out, key.DestinationNone{}) + continue + } + ksids := make([][]byte, 0, len(result.Rows)) + for _, row := range result.Rows { + num, err := sqltypes.ToUint64(row[0]) + if err != nil { + // A failure to convert is equivalent to not being + // able to map. + continue + } + ksids = append(ksids, vhash(num)) + } + out = append(out, key.DestinationKeyspaceIDs(ksids)) + } + return out, nil +} + +// Verify returns true if ids maps to ksids. +func (lh *LookupUnicodeLooseMD5Hash) Verify(vcursor VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) { + if lh.writeOnly { + out := make([]bool, len(ids)) + for i := range ids { + out[i] = true + } + return out, nil + } + + values, err := unhashList(ksids) + if err != nil { + return nil, fmt.Errorf("lookup.Verify.vunhash: %v", err) + } + ids, err = convertIds(ids) + if err != nil { + return nil, fmt.Errorf("lookup.Verify.vunhash: %v", err) + } + return lh.lkp.Verify(vcursor, ids, values) +} + +// Create reserves the id by inserting it into the vindex table. +func (lh *LookupUnicodeLooseMD5Hash) Create(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte, ignoreMode bool) error { + values, err := unhashList(ksids) + if err != nil { + return fmt.Errorf("lookup.Create.vunhash: %v", err) + } + rowsColValues, err = convertRows(rowsColValues) + if err != nil { + return fmt.Errorf("lookup.Create.convert: %v", err) + } + return lh.lkp.Create(vcursor, rowsColValues, values, ignoreMode) +} + +// Update updates the entry in the vindex table. +func (lh *LookupUnicodeLooseMD5Hash) Update(vcursor VCursor, oldValues []sqltypes.Value, ksid []byte, newValues []sqltypes.Value) error { + v, err := vunhash(ksid) + if err != nil { + return fmt.Errorf("lookup.Update.vunhash: %v", err) + } + newValues, err = convertIds(newValues) + if err != nil { + return fmt.Errorf("lookup.Update.convert: %v", err) + } + oldValues, err = convertIds(oldValues) + if err != nil { + return fmt.Errorf("lookup.Update.convert: %v", err) + } + return lh.lkp.Update(vcursor, oldValues, sqltypes.NewUint64(v), newValues) +} + +// Delete deletes the entry from the vindex table. +func (lh *LookupUnicodeLooseMD5Hash) Delete(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksid []byte) error { + v, err := vunhash(ksid) + if err != nil { + return fmt.Errorf("lookup.Delete.vunhash: %v", err) + } + rowsColValues, err = convertRows(rowsColValues) + if err != nil { + return fmt.Errorf("lookup.Delete.convert: %v", err) + } + return lh.lkp.Delete(vcursor, rowsColValues, sqltypes.NewUint64(v)) +} + +// MarshalJSON returns a JSON representation of LookupHash. +func (lh *LookupUnicodeLooseMD5Hash) MarshalJSON() ([]byte, error) { + return json.Marshal(lh.lkp) +} + +//==================================================================== + +// LookupUnicodeLooseMD5HashUnique defines a vindex that uses a lookup table. +// The table is expected to define the id column as unique. It's +// Unique and a Lookup and will store the from value in a hashed format. +// Warning: This Vindex is being depcreated in favor of LookupUnique +type LookupUnicodeLooseMD5HashUnique struct { + name string + writeOnly bool + lkp lookupInternal +} + +// NewLookupUnicodeLooseMD5HashUnique creates a LookupUnicodeLooseMD5HashUnique vindex. +// The supplied map has the following required fields: +// table: name of the backing table. It can be qualified by the keyspace. +// from: list of columns in the table that have the 'from' values of the lookup vindex. +// to: The 'to' column name of the table. +// +// The following fields are optional: +// autocommit: setting this to "true" will cause deletes to be ignored. +// write_only: in this mode, Map functions return the full keyrange causing a full scatter. +func NewLookupUnicodeLooseMD5HashUnique(name string, m map[string]string) (Vindex, error) { + lhu := &LookupUnicodeLooseMD5HashUnique{name: name} + + autocommit, err := boolFromMap(m, "autocommit") + if err != nil { + return nil, err + } + lhu.writeOnly, err = boolFromMap(m, "write_only") + if err != nil { + return nil, err + } + + // Don't allow upserts for unique vindexes. + if err := lhu.lkp.Init(m, autocommit, false /* upsert */); err != nil { + return nil, err + } + return lhu, nil +} + +// String returns the name of the vindex. +func (lhu *LookupUnicodeLooseMD5HashUnique) String() string { + return lhu.name +} + +// Cost returns the cost of this vindex as 10. +func (lhu *LookupUnicodeLooseMD5HashUnique) Cost() int { + return 10 +} + +// IsUnique returns true since the Vindex is unique. +func (lhu *LookupUnicodeLooseMD5HashUnique) IsUnique() bool { + return true +} + +// IsFunctional returns false since the Vindex is not functional. +func (lhu *LookupUnicodeLooseMD5HashUnique) IsFunctional() bool { + return false +} + +// Map can map ids to key.Destination objects. +func (lhu *LookupUnicodeLooseMD5HashUnique) Map(vcursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) { + out := make([]key.Destination, 0, len(ids)) + if lhu.writeOnly { + for range ids { + out = append(out, key.DestinationKeyRange{KeyRange: &topodatapb.KeyRange{}}) + } + return out, nil + } + + ids, err := convertIds(ids) + if err != nil { + return nil, err + } + results, err := lhu.lkp.Lookup(vcursor, ids) + if err != nil { + return nil, err + } + for i, result := range results { + switch len(result.Rows) { + case 0: + out = append(out, key.DestinationNone{}) + case 1: + num, err := sqltypes.ToUint64(result.Rows[0][0]) + if err != nil { + out = append(out, key.DestinationNone{}) + continue + } + out = append(out, key.DestinationKeyspaceID(vhash(num))) + default: + return nil, fmt.Errorf("LookupUnicodeLooseMD5HashUnique.Map: unexpected multiple results from vindex %s: %v", lhu.lkp.Table, ids[i]) + } + } + return out, nil +} + +// Verify returns true if ids maps to ksids. +func (lhu *LookupUnicodeLooseMD5HashUnique) Verify(vcursor VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) { + if lhu.writeOnly { + out := make([]bool, len(ids)) + for i := range ids { + out[i] = true + } + return out, nil + } + + values, err := unhashList(ksids) + if err != nil { + return nil, fmt.Errorf("lookup.Verify.vunhash: %v", err) + } + ids, err = convertIds(ids) + if err != nil { + return nil, fmt.Errorf("lookup.Verify.vunhash: %v", err) + } + return lhu.lkp.Verify(vcursor, ids, values) +} + +// Create reserves the id by inserting it into the vindex table. +func (lhu *LookupUnicodeLooseMD5HashUnique) Create(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte, ignoreMode bool) error { + values, err := unhashList(ksids) + if err != nil { + return fmt.Errorf("lookup.Create.vunhash: %v", err) + } + rowsColValues, err = convertRows(rowsColValues) + if err != nil { + return fmt.Errorf("lookup.Create.convert: %v", err) + } + return lhu.lkp.Create(vcursor, rowsColValues, values, ignoreMode) +} + +// Delete deletes the entry from the vindex table. +func (lhu *LookupUnicodeLooseMD5HashUnique) Delete(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksid []byte) error { + v, err := vunhash(ksid) + if err != nil { + return fmt.Errorf("lookup.Delete.vunhash: %v", err) + } + rowsColValues, err = convertRows(rowsColValues) + if err != nil { + return fmt.Errorf("lookup.Delete.convert: %v", err) + } + return lhu.lkp.Delete(vcursor, rowsColValues, sqltypes.NewUint64(v)) +} + +// Update updates the entry in the vindex table. +func (lhu *LookupUnicodeLooseMD5HashUnique) Update(vcursor VCursor, oldValues []sqltypes.Value, ksid []byte, newValues []sqltypes.Value) error { + v, err := vunhash(ksid) + if err != nil { + return fmt.Errorf("lookup.Update.vunhash: %v", err) + } + newValues, err = convertIds(newValues) + if err != nil { + return fmt.Errorf("lookup.Update.convert: %v", err) + } + oldValues, err = convertIds(oldValues) + if err != nil { + return fmt.Errorf("lookup.Update.convert: %v", err) + } + return lhu.lkp.Update(vcursor, oldValues, sqltypes.NewUint64(v), newValues) +} + +// MarshalJSON returns a JSON representation of LookupHashUnique. +func (lhu *LookupUnicodeLooseMD5HashUnique) MarshalJSON() ([]byte, error) { + return json.Marshal(lhu.lkp) +} + +func unicodeHashValue(value sqltypes.Value) (sqltypes.Value, error) { + hash, err := unicodeHash(value) + if err != nil { + return sqltypes.NULL, err + } + + return sqltypes.NewUint64(binary.BigEndian.Uint64(hash[:8])), nil +} + +func convertIds(ids []sqltypes.Value) ([]sqltypes.Value, error) { + converted := make([]sqltypes.Value, 0, len(ids)) + for _, id := range ids { + idVal, err := unicodeHashValue(id) + if err != nil { + return nil, err + } + converted = append(converted, idVal) + } + return converted, nil +} + +func convertRows(rows [][]sqltypes.Value) ([][]sqltypes.Value, error) { + converted := make([][]sqltypes.Value, 0, len(rows)) + for _, row := range rows { + row, err := convertIds(row) + if err != nil { + return nil, err + } + converted = append(converted, row) + } + return converted, nil +} diff --git a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go new file mode 100644 index 00000000000..54eeb5f047c --- /dev/null +++ b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go @@ -0,0 +1,455 @@ +/* +Copyright 2017 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreedto in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vindexes + +import ( + "reflect" + "testing" + + "vitess.io/vitess/go/sqltypes" + + "vitess.io/vitess/go/vt/key" + querypb "vitess.io/vitess/go/vt/proto/query" + topodatapb "vitess.io/vitess/go/vt/proto/topodata" +) + +const hashed10 uint64 = 17563797831108199066 +const hashed20 uint64 = 8729390916138266389 +const hashed30 uint64 = 1472608112194674795 +const hashed40 uint64 = 16576388050845489136 + +func TestLookupUnicodeLooseMD5HashMap(t *testing.T) { + lookup := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{numRows: 2} + + got, err := lookup.Map(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + want := []key.Destination{ + key.DestinationKeyspaceIDs([][]byte{ + []byte("\x16k@\xb4J\xbaK\xd6"), + []byte("\x06\xe7\xea\"Βp\x8f"), + }), + key.DestinationKeyspaceIDs([][]byte{ + []byte("\x16k@\xb4J\xbaK\xd6"), + []byte("\x06\xe7\xea\"Βp\x8f"), + }), + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Map(): %#v, want %+v", got, want) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "select toc from t where fromc = :fromc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + }, + }, { + Sql: "select toc from t where fromc = :fromc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Map queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // Test query fail. + vc.mustFail = true + _, err = lookup.Map(vc, []sqltypes.Value{sqltypes.NewInt64(1)}) + wantErr := "lookup.Map: execute failed" + if err == nil || err.Error() != wantErr { + t.Errorf("lookup(query fail) err: %v, want %s", err, wantErr) + } + vc.mustFail = false +} + +func TestLookupUnicodeLooseMD5HashMapAutocommit(t *testing.T) { + lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + "table": "t", + "from": "fromc", + "to": "toc", + "hash_from": "true", + "autocommit": "true", + }) + if err != nil { + t.Fatal(err) + } + vc := &vcursor{numRows: 2} + + got, err := lookupNonUnique.Map(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + want := []key.Destination{ + key.DestinationKeyspaceIDs([][]byte{ + []byte("\x16k@\xb4J\xbaK\xd6"), + []byte("\x06\xe7\xea\"Βp\x8f"), + }), + key.DestinationKeyspaceIDs([][]byte{ + []byte("\x16k@\xb4J\xbaK\xd6"), + []byte("\x06\xe7\xea\"Βp\x8f"), + }), + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Map(): %#v, want %+v", got, want) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "select toc from t where fromc = :fromc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + }, + }, { + Sql: "select toc from t where fromc = :fromc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Map queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + if got, want := vc.autocommits, 2; got != want { + t.Errorf("Create(autocommit) count: %d, want %d", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashMapWriteOnly(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", true) + vc := &vcursor{numRows: 0} + + got, err := lookupNonUnique.Map(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + want := []key.Destination{ + key.DestinationKeyRange{ + KeyRange: &topodatapb.KeyRange{}, + }, + key.DestinationKeyRange{ + KeyRange: &topodatapb.KeyRange{}, + }, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Map(): %#v, want %+v", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashMapAbsent(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{numRows: 0} + + got, err := lookupNonUnique.Map(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + want := []key.Destination{ + key.DestinationNone{}, + key.DestinationNone{}, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Map(): %#v, want %+v", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashVerify(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{numRows: 1} + + got, err := lookupNonUnique.Verify(vc, + []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Βp\x8f")}) + if err != nil { + t.Error(err) + } + wantResult := []bool{true, true} + if !reflect.DeepEqual(got, wantResult) { + t.Errorf("lookuphash.Verify(match): %v, want %v", got, wantResult) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "select fromc from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + "toc": sqltypes.Uint64BindVariable(1), + }, + }, { + Sql: "select fromc from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + "toc": sqltypes.Uint64BindVariable(2), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Verify queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // Test query fail. + vc.mustFail = true + _, err = lookupNonUnique.Verify(vc, []sqltypes.Value{sqltypes.NewInt64(1)}, [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6")}) + want := "lookup.Verify: execute failed" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } + vc.mustFail = false + + // writeOnly true should always yield true. + lookupNonUnique = createLookup(t, "lookup_unicodeloosemd5_hash", true) + vc.queries = nil + + got, err = lookupNonUnique.Verify(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}, [][]byte{[]byte(""), []byte("")}) + if err != nil { + t.Error(err) + } + if vc.queries != nil { + t.Errorf("lookup.Verify(writeOnly), queries: %v, want nil", vc.queries) + } + wantBools := []bool{true, true} + if !reflect.DeepEqual(got, wantBools) { + t.Errorf("lookup.Verify(writeOnly): %v, want %v", got, wantBools) + } +} + +func TestLookupUnicodeLooseMD5HashVerifyAutocommit(t *testing.T) { + lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + "table": "t", + "from": "fromc", + "to": "toc", + "autocommit": "true", + }) + if err != nil { + t.Fatal(err) + } + vc := &vcursor{numRows: 1} + + _, err = lookupNonUnique.Verify(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Βp\x8f")}) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "select fromc from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + "toc": sqltypes.Uint64BindVariable(1), + }, + }, { + Sql: "select fromc from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + "toc": sqltypes.Uint64BindVariable(2), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Verify queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + if got, want := vc.autocommits, 2; got != want { + t.Errorf("Create(autocommit) count: %d, want %d", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashCreate(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{} + + err := lookupNonUnique.(Lookup).Create(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}, {sqltypes.NewInt64(20)}}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Βp\x8f")}, false /* ignoreMode */) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "insert into t(fromc, toc) values(:fromc0, :toc0), (:fromc1, :toc1)", + BindVariables: map[string]*querypb.BindVariable{ + "fromc0": sqltypes.Uint64BindVariable(hashed10), + "toc0": sqltypes.Uint64BindVariable(1), + "fromc1": sqltypes.Uint64BindVariable(hashed20), + "toc1": sqltypes.Uint64BindVariable(2), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Create queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // With ignore. + vc.queries = nil + err = lookupNonUnique.(Lookup).Create(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}, {sqltypes.NewInt64(20)}}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Βp\x8f")}, true /* ignoreMode */) + if err != nil { + t.Error(err) + } + + wantqueries[0].Sql = "insert ignore into t(fromc, toc) values(:fromc0, :toc0), (:fromc1, :toc1)" + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Create queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // Test query fail. + vc.mustFail = true + err = lookupNonUnique.(Lookup).Create(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}}, [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6")}, false /* ignoreMode */) + want := "lookup.Create: execute failed" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } + vc.mustFail = false + + // Test column mismatch. + err = lookupNonUnique.(Lookup).Create(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}}, [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6")}, false /* ignoreMode */) + want = "lookup.Create: column vindex count does not match the columns in the lookup: 2 vs [fromc]" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } +} + +func TestLookupUnicodeLooseMD5HashCreateAutocommit(t *testing.T) { + lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + "table": "t", + "from": "from1,from2", + "to": "toc", + "autocommit": "true", + }) + if err != nil { + t.Fatal(err) + } + vc := &vcursor{} + + err = lookupNonUnique.(Lookup).Create( + vc, + [][]sqltypes.Value{{ + sqltypes.NewInt64(10), sqltypes.NewInt64(20), + }, { + sqltypes.NewInt64(30), sqltypes.NewInt64(40), + }}, + [][]byte{[]byte("\x16k@\xb4J\xbaK\xd6"), []byte("\x06\xe7\xea\"Βp\x8f")}, + false /* ignoreMode */) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "insert into t(from1, from2, toc) values(:from10, :from20, :toc0), (:from11, :from21, :toc1) on duplicate key update from1=values(from1), from2=values(from2), toc=values(toc)", + BindVariables: map[string]*querypb.BindVariable{ + "from10": sqltypes.Uint64BindVariable(hashed10), + "from20": sqltypes.Uint64BindVariable(hashed20), + "toc0": sqltypes.Uint64BindVariable(1), + "from11": sqltypes.Uint64BindVariable(hashed30), + "from21": sqltypes.Uint64BindVariable(hashed40), + "toc1": sqltypes.Uint64BindVariable(2), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Create queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + if got, want := vc.autocommits, 1; got != want { + t.Errorf("Create(autocommit) count: %d, want %d", got, want) + } +} + +func TestLookupUnicodeLooseMD5HashDelete(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{} + + err := lookupNonUnique.(Lookup).Delete(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}, {sqltypes.NewInt64(20)}}, []byte("\x16k@\xb4J\xbaK\xd6")) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "delete from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + "toc": sqltypes.Uint64BindVariable(1), + }, + }, { + Sql: "delete from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed20), + "toc": sqltypes.Uint64BindVariable(1), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Delete queries:\n%v, want\n%v", vc.queries, wantqueries) + } + + // Test query fail. + vc.mustFail = true + err = lookupNonUnique.(Lookup).Delete(vc, [][]sqltypes.Value{{sqltypes.NewInt64(1)}}, []byte("\x16k@\xb4J\xbaK\xd6")) + want := "lookup.Delete: execute failed" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } + vc.mustFail = false + + // Test column count fail. + err = lookupNonUnique.(Lookup).Delete(vc, [][]sqltypes.Value{{sqltypes.NewInt64(1), sqltypes.NewInt64(2)}}, []byte("\x16k@\xb4J\xbaK\xd6")) + want = "lookup.Delete: column vindex count does not match the columns in the lookup: 2 vs [fromc]" + if err == nil || err.Error() != want { + t.Errorf("lookupNonUnique(query fail) err: %v, want %s", err, want) + } +} + +func TestLookupUnicodeLooseMD5HashDeleteAutocommit(t *testing.T) { + lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + "table": "t", + "from": "fromc", + "to": "toc", + "autocommit": "true", + }) + vc := &vcursor{} + + err = lookupNonUnique.(Lookup).Delete(vc, [][]sqltypes.Value{{sqltypes.NewInt64(10)}, {sqltypes.NewInt64(20)}}, []byte("\x16k@\xb4J\xbaK\xd6")) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery(nil) + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Delete queries:\n%v, want\n%v", vc.queries, wantqueries) + } +} + +func TestLookupUnicodeLooseMD5HashUpdate(t *testing.T) { + lookupNonUnique := createLookup(t, "lookup_unicodeloosemd5_hash", false) + vc := &vcursor{} + + err := lookupNonUnique.(Lookup).Update(vc, []sqltypes.Value{sqltypes.NewInt64(10)}, []byte("\x16k@\xb4J\xbaK\xd6"), []sqltypes.Value{sqltypes.NewInt64(20)}) + if err != nil { + t.Error(err) + } + + wantqueries := []*querypb.BoundQuery{{ + Sql: "delete from t where fromc = :fromc and toc = :toc", + BindVariables: map[string]*querypb.BindVariable{ + "fromc": sqltypes.Uint64BindVariable(hashed10), + "toc": sqltypes.Uint64BindVariable(1), + }, + }, { + Sql: "insert into t(fromc, toc) values(:fromc0, :toc0)", + BindVariables: map[string]*querypb.BindVariable{ + "fromc0": sqltypes.Uint64BindVariable(hashed20), + "toc0": sqltypes.Uint64BindVariable(1), + }, + }} + if !reflect.DeepEqual(vc.queries, wantqueries) { + t.Errorf("lookup.Update queries:\n%v, want\n%v", vc.queries, wantqueries) + } +}