diff --git a/go/vt/binlog/keyspace_id_resolver.go b/go/vt/binlog/keyspace_id_resolver.go index 204960f06ca..a005222434d 100644 --- a/go/vt/binlog/keyspace_id_resolver.go +++ b/go/vt/binlog/keyspace_id_resolver.go @@ -147,7 +147,8 @@ func newKeyspaceIDResolverFactoryV3(ctx context.Context, ts *topo.Server, keyspa if col.Name.EqualString(shardingColumnName) { // We found the column. return i, &keyspaceIDResolverFactoryV3{ - vindex: colVindex.Vindex, + // Only SingleColumn vindexes are returned by FindVindexForSharding. + vindex: colVindex.Vindex.(vindexes.SingleColumn), }, nil } } @@ -158,7 +159,7 @@ func newKeyspaceIDResolverFactoryV3(ctx context.Context, ts *topo.Server, keyspa // keyspaceIDResolverFactoryV3 uses the Vindex to compute the value. type keyspaceIDResolverFactoryV3 struct { - vindex vindexes.Vindex + vindex vindexes.SingleColumn } func (r *keyspaceIDResolverFactoryV3) keyspaceID(v sqltypes.Value) ([]byte, error) { diff --git a/go/vt/vtgate/engine/delete.go b/go/vt/vtgate/engine/delete.go index 9d59c69181e..22e2fd61a5c 100644 --- a/go/vt/vtgate/engine/delete.go +++ b/go/vt/vtgate/engine/delete.go @@ -50,7 +50,7 @@ type Delete struct { Query string // Vindex specifies the vindex to be used. - Vindex vindexes.Vindex + Vindex vindexes.SingleColumn // Values specifies the vindex values to use for routing. // For now, only one value is specified. Values []sqltypes.PlanValue diff --git a/go/vt/vtgate/engine/delete_test.go b/go/vt/vtgate/engine/delete_test.go index a61dcc702e7..d2b7c9173af 100644 --- a/go/vt/vtgate/engine/delete_test.go +++ b/go/vt/vtgate/engine/delete_test.go @@ -65,7 +65,7 @@ func TestDeleteEqual(t *testing.T) { Sharded: true, }, Query: "dummy_delete", - Vindex: vindex, + Vindex: vindex.(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, } @@ -98,7 +98,7 @@ func TestDeleteEqualNoRoute(t *testing.T) { Sharded: true, }, Query: "dummy_delete", - Vindex: vindex, + Vindex: vindex.(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, } @@ -127,7 +127,7 @@ func TestDeleteEqualNoScatter(t *testing.T) { Sharded: true, }, Query: "dummy_delete", - Vindex: vindex, + Vindex: vindex.(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, } @@ -142,7 +142,7 @@ func TestDeleteOwnedVindex(t *testing.T) { Opcode: DeleteEqual, Keyspace: ks.Keyspace, Query: "dummy_delete", - Vindex: ks.Vindexes["hash"], + Vindex: ks.Vindexes["hash"].(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, Table: ks.Tables["t1"], OwnedVindexQuery: "dummy_subquery", diff --git a/go/vt/vtgate/engine/insert.go b/go/vt/vtgate/engine/insert.go index bf1de16defb..bf75e6a4468 100644 --- a/go/vt/vtgate/engine/insert.go +++ b/go/vt/vtgate/engine/insert.go @@ -391,19 +391,10 @@ func (ins *Insert) getInsertShardedRoute(vcursor VCursor, bindVars map[string]*q // keyspace ids. For regular inserts, a failure to find a route // results in an error. For 'ignore' type inserts, the keyspace // id is returned as nil, which is used later to drop the corresponding rows. - colVindex := ins.Table.ColumnVindexes[0] - keyspaceIDs, err := ins.processPrimary(vcursor, vindexRowsValues[0], colVindex) + keyspaceIDs, err := ins.processPrimary(vcursor, vindexRowsValues[0], ins.Table.ColumnVindexes[0]) if err != nil { return nil, nil, vterrors.Wrap(err, "getInsertShardedRoute") } - // Primary vindex can be owned. If so, go through the processOwned flow. - // If not owned, we don't do processUnowned because there's no need to verify - // the keyspace ids we just generated. - if colVindex.Owned { - if err := ins.processOwned(vcursor, vindexRowsValues[0], colVindex, keyspaceIDs); err != nil { - return nil, nil, vterrors.Wrap(err, "getInsertShardedRoute") - } - } for vIdx := 1; vIdx < len(ins.Table.ColumnVindexes); vIdx++ { colVindex := ins.Table.ColumnVindexes[vIdx] diff --git a/go/vt/vtgate/engine/insert_test.go b/go/vt/vtgate/engine/insert_test.go index d8373863f93..7989ae64f3b 100644 --- a/go/vt/vtgate/engine/insert_test.go +++ b/go/vt/vtgate/engine/insert_test.go @@ -20,7 +20,6 @@ import ( "errors" "testing" - "github.com/stretchr/testify/assert" "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/vt/vtgate/vindexes" @@ -655,9 +654,14 @@ func TestInsertShardedGeo(t *testing.T) { Type: "region_experimental", Params: map[string]string{ "region_bytes": "1", - "table": "lkp", - "from": "id,region", - "to": "toc", + }, + }, + "lookup": { + Type: "lookup_unique", + Params: map[string]string{ + "table": "id_idx", + "from": "id", + "to": "keyspace_id", }, Owner: "t1", }, @@ -666,7 +670,10 @@ func TestInsertShardedGeo(t *testing.T) { "t1": { ColumnVindexes: []*vschemapb.ColumnVindex{{ Name: "geo", - Columns: []string{"id", "region"}, + Columns: []string{"region", "id"}, + }, { + Name: "lookup", + Columns: []string{"id"}, }}, }, }, @@ -683,20 +690,30 @@ func TestInsertShardedGeo(t *testing.T) { InsertSharded, ks.Keyspace, []sqltypes.PlanValue{{ - // colVindex columns: id, region + // colVindex columns: region, id Values: []sqltypes.PlanValue{{ + // rows for region + Values: []sqltypes.PlanValue{{ + Value: sqltypes.NewInt64(1), + }, { + Value: sqltypes.NewInt64(255), + }}, + }, { // rows for id Values: []sqltypes.PlanValue{{ Value: sqltypes.NewInt64(1), }, { Value: sqltypes.NewInt64(1), }}, - }, { - // rows for region + }}, + }, { + // colVindex columns: id + Values: []sqltypes.PlanValue{{ + // rows for id Values: []sqltypes.PlanValue{{ Value: sqltypes.NewInt64(1), }, { - Value: sqltypes.NewInt64(255), + Value: sqltypes.NewInt64(1), }}, }}, }}, @@ -715,11 +732,9 @@ func TestInsertShardedGeo(t *testing.T) { t.Fatal(err) } vc.ExpectLog(t, []string{ - // ExecutePre proves that keyspace ids are generated, and that they are inserted into the lookup. - `ExecutePre insert into lkp(id, region, toc) values(:id0, :region0, :toc0), (:id1, :region1, :toc1) ` + + `Execute insert into id_idx(id, keyspace_id) values(:id0, :keyspace_id0), (:id1, :keyspace_id1) ` + `id0: type:INT64 value:"1" id1: type:INT64 value:"1" ` + - `region0: type:INT64 value:"1" region1: type:INT64 value:"255" ` + - `toc0: type:VARBINARY value:"\001\026k@\264J\272K\326" toc1: type:VARBINARY value:"\377\026k@\264J\272K\326" true`, + `keyspace_id0: type:VARBINARY value:"\001\026k@\264J\272K\326" keyspace_id1: type:VARBINARY value:"\377\026k@\264J\272K\326" true`, `ResolveDestinations sharded [value:"0" value:"1" ] Destinations:DestinationKeyspaceID(01166b40b44aba4bd6),DestinationKeyspaceID(ff166b40b44aba4bd6)`, `ExecuteMultiShard sharded.20-: prefix mid1 suffix /* vtgate:: keyspace_id:01166b40b44aba4bd6 */ ` + `{_id0: type:INT64 value:"1" _id1: type:INT64 value:"1" ` + @@ -922,104 +937,6 @@ func TestInsertShardedIgnoreOwned(t *testing.T) { }) } -func TestInsertIgnoreGeo(t *testing.T) { - invschema := &vschemapb.SrvVSchema{ - Keyspaces: map[string]*vschemapb.Keyspace{ - "sharded": { - Sharded: true, - Vindexes: map[string]*vschemapb.Vindex{ - "geo": { - Type: "region_experimental", - Params: map[string]string{ - "region_bytes": "1", - "table": "lkp", - "from": "id,region", - "to": "toc", - }, - Owner: "t1", - }, - }, - Tables: map[string]*vschemapb.Table{ - "t1": { - ColumnVindexes: []*vschemapb.ColumnVindex{{ - Name: "geo", - Columns: []string{"id", "region"}, - }}, - }, - }, - }, - }, - } - vs, err := vindexes.BuildVSchema(invschema) - if err != nil { - t.Fatal(err) - } - ks := vs.Keyspaces["sharded"] - - ins := NewInsert( - InsertShardedIgnore, - ks.Keyspace, - []sqltypes.PlanValue{{ - // colVindex columns: id, region - Values: []sqltypes.PlanValue{{ - // rows for id - Values: []sqltypes.PlanValue{{ - Value: sqltypes.NewInt64(1), - }, { - Value: sqltypes.NewInt64(2), - }}, - }, { - // rows for region - Values: []sqltypes.PlanValue{{ - Value: sqltypes.NewInt64(1), - }, { - Value: sqltypes.NewInt64(2), - }}, - }}, - }}, - ks.Tables["t1"], - "prefix", - []string{" mid1", " mid2"}, - " suffix", - ) - - ksid0 := sqltypes.MakeTestResult( - sqltypes.MakeTestFields( - "to", - "varbinary", - ), - "\x00", - ) - noresult := &sqltypes.Result{} - vc := &loggingVCursor{ - shards: []string{"-20", "20-"}, - shardForKsid: []string{"20-", "-20"}, - results: []*sqltypes.Result{ - // insert lkp - noresult, - // fail one verification (row 2) - ksid0, - noresult, - }, - } - _, err = ins.Execute(vc, map[string]*querypb.BindVariable{}, false) - if err != nil { - t.Fatal(err) - } - vc.ExpectLog(t, []string{ - `ExecutePre insert ignore into lkp(id, region, toc) values(:id0, :region0, :toc0), (:id1, :region1, :toc1) ` + - `id0: type:INT64 value:"1" id1: type:INT64 value:"2" ` + - `region0: type:INT64 value:"1" region1: type:INT64 value:"2" ` + - `toc0: type:VARBINARY value:"\001\026k@\264J\272K\326" toc1: type:VARBINARY value:"\002\006\347\352\"\316\222p\217" true`, - // Row 2 will fail verification. This is what we're testing. The second row should not get inserted. - `ExecutePre select id from lkp where id = :id and toc = :toc id: type:INT64 value:"1" toc: type:VARBINARY value:"\001\026k@\264J\272K\326" false`, - `ExecutePre select id from lkp where id = :id and toc = :toc id: type:INT64 value:"2" toc: type:VARBINARY value:"\002\006\347\352\"\316\222p\217" false`, - `ResolveDestinations sharded [value:"0" ] Destinations:DestinationKeyspaceID(01166b40b44aba4bd6)`, - `ExecuteMultiShard sharded.20-: prefix mid1 suffix /* vtgate:: keyspace_id:01166b40b44aba4bd6 */ ` + - `{_id0: type:INT64 value:"1" _region0: type:INT64 value:"1" } true true`, - }) -} - func TestInsertShardedIgnoreOwnedWithNull(t *testing.T) { invschema := &vschemapb.SrvVSchema{ Keyspaces: map[string]*vschemapb.Keyspace{ @@ -1270,91 +1187,6 @@ func TestInsertShardedUnownedVerify(t *testing.T) { }) } -func TestInsertUnownedGeo(t *testing.T) { - invschema := &vschemapb.SrvVSchema{ - Keyspaces: map[string]*vschemapb.Keyspace{ - "sharded": { - Sharded: true, - Vindexes: map[string]*vschemapb.Vindex{ - "primary": { - Type: "hash", - }, - "geo": { - Type: "region_experimental", - Params: map[string]string{ - "region_bytes": "1", - "table": "lkp", - "from": "other_id,region", - "to": "toc", - }, - }, - }, - Tables: map[string]*vschemapb.Table{ - "t1": { - ColumnVindexes: []*vschemapb.ColumnVindex{{ - Name: "primary", - Columns: []string{"id"}, - }, { - Name: "geo", - Columns: []string{"other_id", "region"}, - }}, - }, - }, - }, - }, - } - vs, err := vindexes.BuildVSchema(invschema) - if err != nil { - t.Fatal(err) - } - ks := vs.Keyspaces["sharded"] - - ins := NewInsert( - InsertSharded, - ks.Keyspace, - []sqltypes.PlanValue{{ - // colVindex columns: id - Values: []sqltypes.PlanValue{{ - // rows for id - Values: []sqltypes.PlanValue{{ - Value: sqltypes.NewInt64(1), - }}, - }}, - }, { - // colVindex columns: other_id, region - Values: []sqltypes.PlanValue{{ - // rows for other_id - Values: []sqltypes.PlanValue{{ - Value: sqltypes.NewInt64(2), - }}, - }, { - // rows for region - Values: []sqltypes.PlanValue{{ - Value: sqltypes.NewInt64(3), - }}, - }}, - }}, - ks.Tables["t1"], - "prefix", - []string{" mid1"}, - " suffix", - ) - - noresult := &sqltypes.Result{} - vc := &loggingVCursor{ - shards: []string{"-20", "20-"}, - results: []*sqltypes.Result{ - // fail verification - noresult, - }, - } - _, err = ins.Execute(vc, map[string]*querypb.BindVariable{}, false) - assert.EqualError(t, err, "execInsertSharded: getInsertShardedRoute: values [[INT64(2) INT64(3)]] for column [other_id region] does not map to keyspace ids") - vc.ExpectLog(t, []string{ - `ExecutePre select other_id from lkp where other_id = :other_id and toc = :toc other_id: type:INT64 value:"2" toc: type:VARBINARY value:"\026k@\264J\272K\326" false`, - }) -} - func TestInsertShardedIgnoreUnownedVerify(t *testing.T) { invschema := &vschemapb.SrvVSchema{ Keyspaces: map[string]*vschemapb.Keyspace{ diff --git a/go/vt/vtgate/engine/route.go b/go/vt/vtgate/engine/route.go index 2ad95038820..4576894f13c 100644 --- a/go/vt/vtgate/engine/route.go +++ b/go/vt/vtgate/engine/route.go @@ -65,7 +65,7 @@ type Route struct { FieldQuery string // Vindex specifies the vindex to be used. - Vindex vindexes.Vindex + Vindex vindexes.SingleColumn // Values specifies the vindex values to use for routing. Values []sqltypes.PlanValue @@ -463,7 +463,7 @@ func (route *Route) sort(in *sqltypes.Result) (*sqltypes.Result, error) { return out, err } -func resolveSingleShard(vcursor VCursor, vindex vindexes.Vindex, keyspace *vindexes.Keyspace, vindexKey sqltypes.Value) (*srvtopo.ResolvedShard, []byte, error) { +func resolveSingleShard(vcursor VCursor, vindex vindexes.SingleColumn, keyspace *vindexes.Keyspace, vindexKey sqltypes.Value) (*srvtopo.ResolvedShard, []byte, error) { destinations, err := vindex.Map(vcursor, []sqltypes.Value{vindexKey}) if err != nil { return nil, nil, err diff --git a/go/vt/vtgate/engine/route_test.go b/go/vt/vtgate/engine/route_test.go index 5e34bb5f4ce..5855e2e5873 100644 --- a/go/vt/vtgate/engine/route_test.go +++ b/go/vt/vtgate/engine/route_test.go @@ -119,7 +119,7 @@ func TestSelectEqualUnique(t *testing.T) { "dummy_select", "dummy_select_field", ) - sel.Vindex = vindex + sel.Vindex = vindex.(vindexes.SingleColumn) sel.Values = []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}} vc := &loggingVCursor{ @@ -164,7 +164,7 @@ func TestSelectEqualUniqueScatter(t *testing.T) { "dummy_select", "dummy_select_field", ) - sel.Vindex = vindex + sel.Vindex = vindex.(vindexes.SingleColumn) sel.Values = []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}} vc := &loggingVCursor{ @@ -208,7 +208,7 @@ func TestSelectEqual(t *testing.T) { "dummy_select", "dummy_select_field", ) - sel.Vindex = vindex + sel.Vindex = vindex.(vindexes.SingleColumn) sel.Values = []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}} vc := &loggingVCursor{ @@ -264,7 +264,7 @@ func TestSelectEqualNoRoute(t *testing.T) { "dummy_select", "dummy_select_field", ) - sel.Vindex = vindex + sel.Vindex = vindex.(vindexes.SingleColumn) sel.Values = []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}} vc := &loggingVCursor{shards: []string{"-20", "20-"}} @@ -301,7 +301,7 @@ func TestSelectINUnique(t *testing.T) { "dummy_select", "dummy_select_field", ) - sel.Vindex = vindex + sel.Vindex = vindex.(vindexes.SingleColumn) sel.Values = []sqltypes.PlanValue{{ Values: []sqltypes.PlanValue{{ Value: sqltypes.NewInt64(1), @@ -357,7 +357,7 @@ func TestSelectINNonUnique(t *testing.T) { "dummy_select", "dummy_select_field", ) - sel.Vindex = vindex + sel.Vindex = vindex.(vindexes.SingleColumn) sel.Values = []sqltypes.PlanValue{{ Values: []sqltypes.PlanValue{{ Value: sqltypes.NewInt64(1), @@ -542,7 +542,7 @@ func TestRouteGetFields(t *testing.T) { "dummy_select", "dummy_select_field", ) - sel.Vindex = vindex + sel.Vindex = vindex.(vindexes.SingleColumn) sel.Values = []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}} vc := &loggingVCursor{shards: []string{"-20", "20-"}} diff --git a/go/vt/vtgate/engine/update.go b/go/vt/vtgate/engine/update.go index 075a5a8c93c..dc3c5915468 100644 --- a/go/vt/vtgate/engine/update.go +++ b/go/vt/vtgate/engine/update.go @@ -50,7 +50,7 @@ type Update struct { Query string // Vindex specifies the vindex to be used. - Vindex vindexes.Vindex + Vindex vindexes.SingleColumn // Values specifies the vindex values to use for routing. // For now, only one value is specified. Values []sqltypes.PlanValue diff --git a/go/vt/vtgate/engine/update_test.go b/go/vt/vtgate/engine/update_test.go index fcd51942ffa..d21f62990f7 100644 --- a/go/vt/vtgate/engine/update_test.go +++ b/go/vt/vtgate/engine/update_test.go @@ -66,7 +66,7 @@ func TestUpdateEqual(t *testing.T) { Sharded: true, }, Query: "dummy_update", - Vindex: vindex, + Vindex: vindex.(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, } @@ -95,7 +95,7 @@ func TestUpdateScatter(t *testing.T) { Sharded: true, }, Query: "dummy_update", - Vindex: vindex, + Vindex: vindex.(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, } @@ -118,7 +118,7 @@ func TestUpdateScatter(t *testing.T) { Sharded: true, }, Query: "dummy_update", - Vindex: vindex, + Vindex: vindex.(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, MultiShardAutocommit: true, } @@ -148,7 +148,7 @@ func TestUpdateEqualNoRoute(t *testing.T) { Sharded: true, }, Query: "dummy_update", - Vindex: vindex, + Vindex: vindex.(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, } @@ -177,7 +177,7 @@ func TestUpdateEqualNoScatter(t *testing.T) { Sharded: true, }, Query: "dummy_update", - Vindex: vindex, + Vindex: vindex.(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, } @@ -192,7 +192,7 @@ func TestUpdateEqualChangedVindex(t *testing.T) { Opcode: UpdateEqual, Keyspace: ks.Keyspace, Query: "dummy_update", - Vindex: ks.Vindexes["hash"], + Vindex: ks.Vindexes["hash"].(vindexes.SingleColumn), Values: []sqltypes.PlanValue{{Value: sqltypes.NewInt64(1)}}, ChangedVindexValues: map[string][]sqltypes.PlanValue{ "twocol": {{ diff --git a/go/vt/vtgate/engine/vindex_func.go b/go/vt/vtgate/engine/vindex_func.go index 99c0a153195..8ffb5914055 100644 --- a/go/vt/vtgate/engine/vindex_func.go +++ b/go/vt/vtgate/engine/vindex_func.go @@ -35,8 +35,9 @@ type VindexFunc struct { // Fields is the field info for the result. Fields []*querypb.Field // Cols contains source column numbers: 0 for id, 1 for keyspace_id. - Cols []int - Vindex vindexes.Vindex + Cols []int + // TODO(sougou): add support for MultiColumn. + Vindex vindexes.SingleColumn Value sqltypes.PlanValue } diff --git a/go/vt/vtgate/engine/vindex_func_test.go b/go/vt/vtgate/engine/vindex_func_test.go index 917fc73f1b8..338ca9f7ddd 100644 --- a/go/vt/vtgate/engine/vindex_func_test.go +++ b/go/vt/vtgate/engine/vindex_func_test.go @@ -246,7 +246,7 @@ func TestFieldOrder(t *testing.T) { } } -func testVindexFunc(v vindexes.Vindex) *VindexFunc { +func testVindexFunc(v vindexes.SingleColumn) *VindexFunc { return &VindexFunc{ Fields: sqltypes.MakeTestFields("id|keyspace_id|range_start|range_end", "varbinary|varbinary|varbinary|varbinary"), Cols: []int{0, 1, 2, 3}, diff --git a/go/vt/vtgate/executor.go b/go/vt/vtgate/executor.go index 51440ed5308..b3c9e2c8980 100644 --- a/go/vt/vtgate/executor.go +++ b/go/vt/vtgate/executor.go @@ -1280,7 +1280,11 @@ func (e *Executor) MessageAck(ctx context.Context, keyspace, name string, ids [] } // We always use the (unique) primary vindex. The ID must be the // primary vindex for message tables. - destinations, err := table.ColumnVindexes[0].Vindex.Map(vcursor, values) + single, ok := table.ColumnVindexes[0].Vindex.(vindexes.SingleColumn) + if !ok { + return 0, fmt.Errorf("multi-column vindexes not supported") + } + destinations, err := single.Map(vcursor, values) if err != nil { return 0, err } diff --git a/go/vt/vtgate/planbuilder/from.go b/go/vt/vtgate/planbuilder/from.go index 7f919ca21c0..2c2813bce10 100644 --- a/go/vt/vtgate/planbuilder/from.go +++ b/go/vt/vtgate/planbuilder/from.go @@ -195,7 +195,11 @@ func (pb *primitiveBuilder) buildTablePrimitive(tableExpr *sqlparser.AliasedTabl return err } if vindex != nil { - pb.bldr, pb.st = newVindexFunc(alias, vindex) + single, ok := vindex.(vindexes.SingleColumn) + if !ok { + return fmt.Errorf("multi-column vindexes not supported") + } + pb.bldr, pb.st = newVindexFunc(alias, single) return nil } @@ -244,7 +248,8 @@ func (pb *primitiveBuilder) buildTablePrimitive(tableExpr *sqlparser.AliasedTabl // Use the Binary vindex, which is the identity function // for keyspace id. eroute = engine.NewSimpleRoute(engine.SelectEqualUnique, vst.Keyspace) - eroute.Vindex, _ = vindexes.NewBinary("binary", nil) + vindex, _ = vindexes.NewBinary("binary", nil) + eroute.Vindex, _ = vindex.(vindexes.SingleColumn) eroute.Values = []sqltypes.PlanValue{{Value: sqltypes.MakeTrusted(sqltypes.VarBinary, vst.Pinned)}} } // set table name into route diff --git a/go/vt/vtgate/planbuilder/route_option.go b/go/vt/vtgate/planbuilder/route_option.go index 5a4f45f60a7..1d6ba1c3679 100644 --- a/go/vt/vtgate/planbuilder/route_option.go +++ b/go/vt/vtgate/planbuilder/route_option.go @@ -37,7 +37,7 @@ type routeOption struct { // vindexMap is a map of all vindexMap that can be used // for the routeOption. - vindexMap map[*column]vindexes.Vindex + vindexMap map[*column]vindexes.SingleColumn // condition stores the AST condition that will be used // to resolve the ERoute Values field. @@ -58,7 +58,7 @@ func newSimpleRouteOption(rb *route, eroute *engine.Route) *routeOption { } } -func newRouteOption(rb *route, vst *vindexes.Table, sub *tableSubstitution, vindexMap map[*column]vindexes.Vindex, eroute *engine.Route) *routeOption { +func newRouteOption(rb *route, vst *vindexes.Table, sub *tableSubstitution, vindexMap map[*column]vindexes.SingleColumn, eroute *engine.Route) *routeOption { var subs []*tableSubstitution if sub != nil && sub.newExpr != nil { subs = []*tableSubstitution{sub} @@ -95,7 +95,7 @@ func (ro *routeOption) MergeJoin(rro *routeOption, isLeftJoin bool) { // Add RHS vindexes only if it's not a left join. for c, v := range rro.vindexMap { if ro.vindexMap == nil { - ro.vindexMap = make(map[*column]vindexes.Vindex) + ro.vindexMap = make(map[*column]vindexes.SingleColumn) } ro.vindexMap[c] = v } @@ -126,7 +126,7 @@ func (ro *routeOption) MergeUnion(rro *routeOption) { ro.substitutions = append(ro.substitutions, rro.substitutions...) } -func (ro *routeOption) SubqueryToTable(rb *route, vindexMap map[*column]vindexes.Vindex) { +func (ro *routeOption) SubqueryToTable(rb *route, vindexMap map[*column]vindexes.SingleColumn) { ro.rb = rb ro.vschemaTable = nil ro.vindexMap = vindexMap @@ -234,14 +234,14 @@ func (ro *routeOption) UpdatePlan(pb *primitiveBuilder, filter sqlparser.Expr) { } } -func (ro *routeOption) updateRoute(opcode engine.RouteOpcode, vindex vindexes.Vindex, condition sqlparser.Expr) { +func (ro *routeOption) updateRoute(opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) { ro.eroute.Opcode = opcode ro.eroute.Vindex = vindex ro.condition = condition } // computePlan computes the plan for the specified filter. -func (ro *routeOption) computePlan(pb *primitiveBuilder, filter sqlparser.Expr) (opcode engine.RouteOpcode, vindex vindexes.Vindex, condition sqlparser.Expr) { +func (ro *routeOption) computePlan(pb *primitiveBuilder, filter sqlparser.Expr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) { switch node := filter.(type) { case *sqlparser.ComparisonExpr: switch node.Operator { @@ -257,7 +257,7 @@ func (ro *routeOption) computePlan(pb *primitiveBuilder, filter sqlparser.Expr) } // computeEqualPlan computes the plan for an equality constraint. -func (ro *routeOption) computeEqualPlan(pb *primitiveBuilder, comparison *sqlparser.ComparisonExpr) (opcode engine.RouteOpcode, vindex vindexes.Vindex, condition sqlparser.Expr) { +func (ro *routeOption) computeEqualPlan(pb *primitiveBuilder, comparison *sqlparser.ComparisonExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) { left := comparison.Left right := comparison.Right vindex = ro.FindVindex(pb, left) @@ -278,7 +278,7 @@ func (ro *routeOption) computeEqualPlan(pb *primitiveBuilder, comparison *sqlpar } // computeINPlan computes the plan for an IN constraint. -func (ro *routeOption) computeINPlan(pb *primitiveBuilder, comparison *sqlparser.ComparisonExpr) (opcode engine.RouteOpcode, vindex vindexes.Vindex, condition sqlparser.Expr) { +func (ro *routeOption) computeINPlan(pb *primitiveBuilder, comparison *sqlparser.ComparisonExpr) (opcode engine.RouteOpcode, vindex vindexes.SingleColumn, condition sqlparser.Expr) { vindex = ro.FindVindex(pb, comparison.Left) if vindex == nil { return engine.SelectScatter, nil, nil @@ -323,7 +323,7 @@ func (ro *routeOption) isBetterThan(other *routeOption) bool { return false } -func (ro *routeOption) FindVindex(pb *primitiveBuilder, expr sqlparser.Expr) vindexes.Vindex { +func (ro *routeOption) FindVindex(pb *primitiveBuilder, expr sqlparser.Expr) vindexes.SingleColumn { col, ok := expr.(*sqlparser.ColName) if !ok { return nil diff --git a/go/vt/vtgate/planbuilder/route_option_test.go b/go/vt/vtgate/planbuilder/route_option_test.go index 425aa3d34f5..c39dfe48b02 100644 --- a/go/vt/vtgate/planbuilder/route_option_test.go +++ b/go/vt/vtgate/planbuilder/route_option_test.go @@ -168,10 +168,11 @@ func TestIsBetterThan(t *testing.T) { case 2: v, _ = newLookupIndex("", nil) } + single, _ := v.(vindexes.SingleColumn) return &routeOption{ eroute: &engine.Route{ Opcode: opt, - Vindex: v, + Vindex: single, }, } } diff --git a/go/vt/vtgate/planbuilder/symtab.go b/go/vt/vtgate/planbuilder/symtab.go index fb9ad2301c2..654245c3187 100644 --- a/go/vt/vtgate/planbuilder/symtab.go +++ b/go/vt/vtgate/planbuilder/symtab.go @@ -87,13 +87,13 @@ func newSymtabWithRoute(rb *route) *symtab { // AddVSchemaTable takes a list of vschema tables as input and // creates a table with multiple route options. It returns a // list of vindex maps, one for each input. -func (st *symtab) AddVSchemaTable(alias sqlparser.TableName, vschemaTables []*vindexes.Table, rb *route) (vindexMaps []map[*column]vindexes.Vindex, err error) { +func (st *symtab) AddVSchemaTable(alias sqlparser.TableName, vschemaTables []*vindexes.Table, rb *route) (vindexMaps []map[*column]vindexes.SingleColumn, err error) { t := &table{ alias: alias, origin: rb, } - vindexMaps = make([]map[*column]vindexes.Vindex, len(vschemaTables)) + vindexMaps = make([]map[*column]vindexes.SingleColumn, len(vschemaTables)) for i, vst := range vschemaTables { // The following logic allows the first table to be authoritative while the rest // are not. But there's no need to reveal this flexibility to the user. @@ -115,8 +115,12 @@ func (st *symtab) AddVSchemaTable(alias sqlparser.TableName, vschemaTables []*vi t.isAuthoritative = true } - var vindexMap map[*column]vindexes.Vindex + var vindexMap map[*column]vindexes.SingleColumn for _, cv := range vst.ColumnVindexes { + single, ok := cv.Vindex.(vindexes.SingleColumn) + if !ok { + continue + } for j, cvcol := range cv.Columns { col, err := t.mergeColumn(cvcol, &column{ origin: rb, @@ -128,10 +132,10 @@ func (st *symtab) AddVSchemaTable(alias sqlparser.TableName, vschemaTables []*vi if j == 0 { // For now, only the first column is used for vindex Map functions. if vindexMap == nil { - vindexMap = make(map[*column]vindexes.Vindex) + vindexMap = make(map[*column]vindexes.SingleColumn) } - if vindexMap[col] == nil || vindexMap[col].Cost() > cv.Vindex.Cost() { - vindexMap[col] = cv.Vindex + if vindexMap[col] == nil || vindexMap[col].Cost() > single.Cost() { + vindexMap[col] = single } } } diff --git a/go/vt/vtgate/planbuilder/symtab_test.go b/go/vt/vtgate/planbuilder/symtab_test.go index 34006ff4b2e..81e85e889b7 100644 --- a/go/vt/vtgate/planbuilder/symtab_test.go +++ b/go/vt/vtgate/planbuilder/symtab_test.go @@ -28,6 +28,8 @@ func TestSymtabAddVSchemaTable(t *testing.T) { tname := sqlparser.TableName{Name: sqlparser.NewTableIdent("t")} rb := &route{} + null, _ := vindexes.CreateVindex("null", "null", nil) + tcases := []struct { in []*vindexes.Table authoritative bool @@ -49,6 +51,7 @@ func TestSymtabAddVSchemaTable(t *testing.T) { in: []*vindexes.Table{{ ColumnVindexes: []*vindexes.ColumnVindex{{ Columns: []sqlparser.ColIdent{sqlparser.NewColIdent("C1")}, + Vindex: null, }}, Columns: []vindexes.Column{{ Name: sqlparser.NewColIdent("C1"), @@ -66,6 +69,7 @@ func TestSymtabAddVSchemaTable(t *testing.T) { sqlparser.NewColIdent("C1"), sqlparser.NewColIdent("C2"), }, + Vindex: null, }}, Columns: []vindexes.Column{{ Name: sqlparser.NewColIdent("C1"), @@ -94,6 +98,7 @@ func TestSymtabAddVSchemaTable(t *testing.T) { in: []*vindexes.Table{{ ColumnVindexes: []*vindexes.ColumnVindex{{ Columns: []sqlparser.ColIdent{sqlparser.NewColIdent("C1")}, + Vindex: null, }}, Columns: []vindexes.Column{{ Name: sqlparser.NewColIdent("C2"), @@ -109,6 +114,7 @@ func TestSymtabAddVSchemaTable(t *testing.T) { sqlparser.NewColIdent("C1"), sqlparser.NewColIdent("C2"), }, + Vindex: null, }}, }}, authoritative: false, @@ -145,12 +151,14 @@ func TestSymtabAddVSchemaTable(t *testing.T) { Columns: []sqlparser.ColIdent{ sqlparser.NewColIdent("C1"), }, + Vindex: null, }}, }, { ColumnVindexes: []*vindexes.ColumnVindex{{ Columns: []sqlparser.ColIdent{ sqlparser.NewColIdent("C2"), }, + Vindex: null, }}, }}, authoritative: false, @@ -162,10 +170,12 @@ func TestSymtabAddVSchemaTable(t *testing.T) { Columns: []sqlparser.ColIdent{ sqlparser.NewColIdent("C1"), }, + Vindex: null, }, { Columns: []sqlparser.ColIdent{ sqlparser.NewColIdent("C2"), }, + Vindex: null, }}, }}, authoritative: false, @@ -246,6 +256,7 @@ func TestSymtabAddVSchemaTable(t *testing.T) { Columns: []sqlparser.ColIdent{ sqlparser.NewColIdent("C2"), }, + Vindex: null, }}, }}, err: "column C2 not found in t", diff --git a/go/vt/vtgate/planbuilder/update.go b/go/vt/vtgate/planbuilder/update.go index 78a8f7a14ae..f8bd77f7937 100644 --- a/go/vt/vtgate/planbuilder/update.go +++ b/go/vt/vtgate/planbuilder/update.go @@ -204,7 +204,7 @@ func generateQuery(statement sqlparser.Statement) string { // getDMLRouting returns the vindex and values for the DML, // If it cannot find a unique vindex match, it returns an error. -func getDMLRouting(where *sqlparser.Where, table *vindexes.Table) (vindexes.Vindex, []sqltypes.PlanValue, error) { +func getDMLRouting(where *sqlparser.Where, table *vindexes.Table) (vindexes.SingleColumn, []sqltypes.PlanValue, error) { if where == nil { return nil, nil, errors.New("unsupported: multi-shard where clause in DML") } @@ -212,8 +212,12 @@ func getDMLRouting(where *sqlparser.Where, table *vindexes.Table) (vindexes.Vind if !index.Vindex.IsUnique() { continue } + single, ok := index.Vindex.(vindexes.SingleColumn) + if !ok { + continue + } if pv, ok := getMatch(where.Expr, index.Columns[0]); ok { - return index.Vindex, []sqltypes.PlanValue{pv}, nil + return single, []sqltypes.PlanValue{pv}, nil } } return nil, nil, errors.New("unsupported: multi-shard where clause in DML") diff --git a/go/vt/vtgate/planbuilder/vindex_func.go b/go/vt/vtgate/planbuilder/vindex_func.go index 6135338c120..3902f4a1030 100644 --- a/go/vt/vtgate/planbuilder/vindex_func.go +++ b/go/vt/vtgate/planbuilder/vindex_func.go @@ -40,7 +40,7 @@ type vindexFunc struct { eVindexFunc *engine.VindexFunc } -func newVindexFunc(alias sqlparser.TableName, vindex vindexes.Vindex) (*vindexFunc, *symtab) { +func newVindexFunc(alias sqlparser.TableName, vindex vindexes.SingleColumn) (*vindexFunc, *symtab) { vf := &vindexFunc{ order: 1, eVindexFunc: &engine.VindexFunc{ diff --git a/go/vt/vtgate/vindexes/binary.go b/go/vt/vtgate/vindexes/binary.go index 1d9e2bf132e..50d0500a9bd 100644 --- a/go/vt/vtgate/vindexes/binary.go +++ b/go/vt/vtgate/vindexes/binary.go @@ -25,8 +25,8 @@ import ( ) var ( - _ Vindex = (*Binary)(nil) - _ Reversible = (*Binary)(nil) + _ SingleColumn = (*Binary)(nil) + _ Reversible = (*Binary)(nil) ) // Binary is a vindex that converts binary bits to a keyspace id. diff --git a/go/vt/vtgate/vindexes/binary_test.go b/go/vt/vtgate/vindexes/binary_test.go index a6fe555558a..ac9d5666097 100644 --- a/go/vt/vtgate/vindexes/binary_test.go +++ b/go/vt/vtgate/vindexes/binary_test.go @@ -26,10 +26,11 @@ import ( "vitess.io/vitess/go/vt/key" ) -var binOnlyVindex Vindex +var binOnlyVindex SingleColumn func init() { - binOnlyVindex, _ = CreateVindex("binary", "binary_varchar", nil) + vindex, _ := CreateVindex("binary", "binary_varchar", nil) + binOnlyVindex = vindex.(SingleColumn) } func TestBinaryCost(t *testing.T) { diff --git a/go/vt/vtgate/vindexes/binarymd5.go b/go/vt/vtgate/vindexes/binarymd5.go index a75326e276e..be6cea79311 100644 --- a/go/vt/vtgate/vindexes/binarymd5.go +++ b/go/vt/vtgate/vindexes/binarymd5.go @@ -25,7 +25,7 @@ import ( ) var ( - _ Vindex = (*BinaryMD5)(nil) + _ SingleColumn = (*BinaryMD5)(nil) ) // BinaryMD5 is a vindex that hashes binary bits to a keyspace id. diff --git a/go/vt/vtgate/vindexes/binarymd5_test.go b/go/vt/vtgate/vindexes/binarymd5_test.go index e3a133c1fac..f959fb4cd19 100644 --- a/go/vt/vtgate/vindexes/binarymd5_test.go +++ b/go/vt/vtgate/vindexes/binarymd5_test.go @@ -26,10 +26,11 @@ import ( "vitess.io/vitess/go/vt/key" ) -var binVindex Vindex +var binVindex SingleColumn func init() { - binVindex, _ = CreateVindex("binary_md5", "binary_md5_varchar", nil) + vindex, _ := CreateVindex("binary_md5", "binary_md5_varchar", nil) + binVindex = vindex.(SingleColumn) } func TestBinaryMD5Cost(t *testing.T) { diff --git a/go/vt/vtgate/vindexes/consistent_lookup.go b/go/vt/vtgate/vindexes/consistent_lookup.go index 546b70c244e..6d862e7c8ce 100644 --- a/go/vt/vtgate/vindexes/consistent_lookup.go +++ b/go/vt/vtgate/vindexes/consistent_lookup.go @@ -33,10 +33,10 @@ import ( ) var ( - _ Vindex = (*ConsistentLookupUnique)(nil) + _ SingleColumn = (*ConsistentLookupUnique)(nil) _ Lookup = (*ConsistentLookupUnique)(nil) _ WantOwnerInfo = (*ConsistentLookupUnique)(nil) - _ Vindex = (*ConsistentLookup)(nil) + _ SingleColumn = (*ConsistentLookup)(nil) _ Lookup = (*ConsistentLookup)(nil) _ WantOwnerInfo = (*ConsistentLookup)(nil) ) diff --git a/go/vt/vtgate/vindexes/consistent_lookup_test.go b/go/vt/vtgate/vindexes/consistent_lookup_test.go index e1e571d07ec..bb25c204273 100644 --- a/go/vt/vtgate/vindexes/consistent_lookup_test.go +++ b/go/vt/vtgate/vindexes/consistent_lookup_test.go @@ -412,7 +412,7 @@ func TestConsistentLookupNoUpdate(t *testing.T) { vc.verifyLog(t, []string{}) } -func createConsistentLookup(t *testing.T, name string) Vindex { +func createConsistentLookup(t *testing.T, name string) SingleColumn { t.Helper() l, err := CreateVindex(name, name, map[string]string{ "table": "t", @@ -429,7 +429,7 @@ func createConsistentLookup(t *testing.T, name string) Vindex { if err := l.(WantOwnerInfo).SetOwnerInfo("ks", "t1", cols); err != nil { t.Fatal(err) } - return l + return l.(SingleColumn) } type loggingVCursor struct { diff --git a/go/vt/vtgate/vindexes/hash.go b/go/vt/vtgate/vindexes/hash.go index 43859e4f38a..db7b86e6389 100644 --- a/go/vt/vtgate/vindexes/hash.go +++ b/go/vt/vtgate/vindexes/hash.go @@ -31,8 +31,8 @@ import ( ) var ( - _ Vindex = (*Hash)(nil) - _ Reversible = (*Hash)(nil) + _ SingleColumn = (*Hash)(nil) + _ Reversible = (*Hash)(nil) ) // Hash defines vindex that hashes an int64 to a KeyspaceId diff --git a/go/vt/vtgate/vindexes/hash_test.go b/go/vt/vtgate/vindexes/hash_test.go index c0e811a3616..847cf1968f0 100644 --- a/go/vt/vtgate/vindexes/hash_test.go +++ b/go/vt/vtgate/vindexes/hash_test.go @@ -25,14 +25,14 @@ import ( "vitess.io/vitess/go/vt/key" ) -var hash Vindex +var hash SingleColumn func init() { hv, err := CreateVindex("hash", "nn", map[string]string{"Table": "t", "Column": "c"}) if err != nil { panic(err) } - hash = hv + hash = hv.(SingleColumn) } func TestHashCost(t *testing.T) { diff --git a/go/vt/vtgate/vindexes/lookup.go b/go/vt/vtgate/vindexes/lookup.go index a6869066e4c..6f13abb0e56 100644 --- a/go/vt/vtgate/vindexes/lookup.go +++ b/go/vt/vtgate/vindexes/lookup.go @@ -27,10 +27,10 @@ import ( ) var ( - _ Vindex = (*LookupUnique)(nil) - _ Lookup = (*LookupUnique)(nil) - _ Vindex = (*LookupNonUnique)(nil) - _ Lookup = (*LookupNonUnique)(nil) + _ SingleColumn = (*LookupUnique)(nil) + _ Lookup = (*LookupUnique)(nil) + _ SingleColumn = (*LookupNonUnique)(nil) + _ Lookup = (*LookupNonUnique)(nil) ) func init() { diff --git a/go/vt/vtgate/vindexes/lookup_hash.go b/go/vt/vtgate/vindexes/lookup_hash.go index 30118bd274a..46809ad4b78 100644 --- a/go/vt/vtgate/vindexes/lookup_hash.go +++ b/go/vt/vtgate/vindexes/lookup_hash.go @@ -27,10 +27,10 @@ import ( ) var ( - _ Vindex = (*LookupHash)(nil) - _ Lookup = (*LookupHash)(nil) - _ Vindex = (*LookupHashUnique)(nil) - _ Lookup = (*LookupHashUnique)(nil) + _ SingleColumn = (*LookupHash)(nil) + _ Lookup = (*LookupHash)(nil) + _ SingleColumn = (*LookupHashUnique)(nil) + _ Lookup = (*LookupHashUnique)(nil) ) func init() { diff --git a/go/vt/vtgate/vindexes/lookup_hash_unique_test.go b/go/vt/vtgate/vindexes/lookup_hash_unique_test.go index bd67e77523d..27ea2cb4e2d 100644 --- a/go/vt/vtgate/vindexes/lookup_hash_unique_test.go +++ b/go/vt/vtgate/vindexes/lookup_hash_unique_test.go @@ -31,12 +31,13 @@ func TestLookupHashUniqueNew(t *testing.T) { t.Errorf("Create(lookup, false): %v, want %v", got, want) } - l, _ = CreateVindex("lookup_hash_unique", "lookup_hash_unique", map[string]string{ + vindex, _ := CreateVindex("lookup_hash_unique", "lookup_hash_unique", map[string]string{ "table": "t", "from": "fromc", "to": "toc", "write_only": "true", }) + l = vindex.(SingleColumn) if want, got := l.(*LookupHashUnique).writeOnly, true; got != want { t.Errorf("Create(lookup, false): %v, want %v", got, want) } diff --git a/go/vt/vtgate/vindexes/lookup_test.go b/go/vt/vtgate/vindexes/lookup_test.go index ca26ce61e9d..12865b8fb75 100644 --- a/go/vt/vtgate/vindexes/lookup_test.go +++ b/go/vt/vtgate/vindexes/lookup_test.go @@ -185,7 +185,7 @@ func TestLookupNonUniqueMap(t *testing.T) { } func TestLookupNonUniqueMapAutocommit(t *testing.T) { - lookupNonUnique, err := CreateVindex("lookup", "lookup", map[string]string{ + vindex, err := CreateVindex("lookup", "lookup", map[string]string{ "table": "t", "from": "fromc", "to": "toc", @@ -194,6 +194,7 @@ func TestLookupNonUniqueMapAutocommit(t *testing.T) { if err != nil { t.Fatal(err) } + lookupNonUnique := vindex.(SingleColumn) vc := &vcursor{numRows: 2} got, err := lookupNonUnique.Map(vc, []sqltypes.Value{sqltypes.NewInt64(1), sqltypes.NewInt64(2)}) @@ -325,7 +326,7 @@ func TestLookupNonUniqueVerify(t *testing.T) { } func TestLookupNonUniqueVerifyAutocommit(t *testing.T) { - lookupNonUnique, err := CreateVindex("lookup", "lookup", map[string]string{ + vindex, err := CreateVindex("lookup", "lookup", map[string]string{ "table": "t", "from": "fromc", "to": "toc", @@ -334,6 +335,7 @@ func TestLookupNonUniqueVerifyAutocommit(t *testing.T) { if err != nil { t.Fatal(err) } + lookupNonUnique := vindex.(SingleColumn) vc := &vcursor{numRows: 1} _, err = lookupNonUnique.Verify(vc, []sqltypes.Value{sqltypes.NewInt64(1), sqltypes.NewInt64(2)}, [][]byte{[]byte("test1"), []byte("test2")}) @@ -549,7 +551,7 @@ func TestLookupNonUniqueUpdate(t *testing.T) { } } -func createLookup(t *testing.T, name string, writeOnly bool) Vindex { +func createLookup(t *testing.T, name string, writeOnly bool) SingleColumn { t.Helper() write := "false" if writeOnly { @@ -564,5 +566,5 @@ func createLookup(t *testing.T, name string, writeOnly bool) Vindex { if err != nil { t.Fatal(err) } - return l + return l.(SingleColumn) } diff --git a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go index 30b12a33f30..f56d302c819 100644 --- a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go +++ b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash.go @@ -28,10 +28,10 @@ import ( ) var ( - _ Vindex = (*LookupUnicodeLooseMD5Hash)(nil) - _ Lookup = (*LookupUnicodeLooseMD5Hash)(nil) - _ Vindex = (*LookupUnicodeLooseMD5HashUnique)(nil) - _ Lookup = (*LookupUnicodeLooseMD5HashUnique)(nil) + _ SingleColumn = (*LookupUnicodeLooseMD5Hash)(nil) + _ Lookup = (*LookupUnicodeLooseMD5Hash)(nil) + _ SingleColumn = (*LookupUnicodeLooseMD5HashUnique)(nil) + _ Lookup = (*LookupUnicodeLooseMD5HashUnique)(nil) ) func init() { diff --git a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go index dc87eaf6aa9..abe193974e7 100644 --- a/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go +++ b/go/vt/vtgate/vindexes/lookup_unicodeloosemd5_hash_test.go @@ -82,7 +82,7 @@ func TestLookupUnicodeLooseMD5HashMap(t *testing.T) { } func TestLookupUnicodeLooseMD5HashMapAutocommit(t *testing.T) { - lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + vindex, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ "table": "t", "from": "fromc", "to": "toc", @@ -92,6 +92,7 @@ func TestLookupUnicodeLooseMD5HashMapAutocommit(t *testing.T) { if err != nil { t.Fatal(err) } + lookupNonUnique := vindex.(SingleColumn) vc := &vcursor{numRows: 2} got, err := lookupNonUnique.Map(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}) @@ -229,7 +230,7 @@ func TestLookupUnicodeLooseMD5HashVerify(t *testing.T) { } func TestLookupUnicodeLooseMD5HashVerifyAutocommit(t *testing.T) { - lookupNonUnique, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ + vindex, err := CreateVindex("lookup_unicodeloosemd5_hash", "lookup", map[string]string{ "table": "t", "from": "fromc", "to": "toc", @@ -238,6 +239,7 @@ func TestLookupUnicodeLooseMD5HashVerifyAutocommit(t *testing.T) { if err != nil { t.Fatal(err) } + lookupNonUnique := vindex.(SingleColumn) vc := &vcursor{numRows: 1} _, err = lookupNonUnique.Verify(vc, []sqltypes.Value{sqltypes.NewInt64(10), sqltypes.NewInt64(20)}, diff --git a/go/vt/vtgate/vindexes/lookup_unique_test.go b/go/vt/vtgate/vindexes/lookup_unique_test.go index 5e33bf370a9..cb9ab2bed8b 100644 --- a/go/vt/vtgate/vindexes/lookup_unique_test.go +++ b/go/vt/vtgate/vindexes/lookup_unique_test.go @@ -33,12 +33,13 @@ func TestLookupUniqueNew(t *testing.T) { t.Errorf("Create(lookup, false): %v, want %v", got, want) } - l, _ = CreateVindex("lookup_unique", "lookup_unique", map[string]string{ + vindex, _ := CreateVindex("lookup_unique", "lookup_unique", map[string]string{ "table": "t", "from": "fromc", "to": "toc", "write_only": "true", }) + l = vindex.(SingleColumn) if want, got := l.(*LookupUnique).writeOnly, true; got != want { t.Errorf("Create(lookup, false): %v, want %v", got, want) } diff --git a/go/vt/vtgate/vindexes/null_test.go b/go/vt/vtgate/vindexes/null_test.go index f92855edca5..11568f9eb5c 100644 --- a/go/vt/vtgate/vindexes/null_test.go +++ b/go/vt/vtgate/vindexes/null_test.go @@ -25,14 +25,14 @@ import ( "vitess.io/vitess/go/vt/key" ) -var null Vindex +var null SingleColumn func init() { hv, err := CreateVindex("null", "nn", map[string]string{"Table": "t", "Column": "c"}) if err != nil { panic(err) } - null = hv + null = hv.(SingleColumn) } func TestNullCost(t *testing.T) { diff --git a/go/vt/vtgate/vindexes/numeric.go b/go/vt/vtgate/vindexes/numeric.go index 1b0b5c2a071..8ebdbe5c2d6 100644 --- a/go/vt/vtgate/vindexes/numeric.go +++ b/go/vt/vtgate/vindexes/numeric.go @@ -27,8 +27,8 @@ import ( ) var ( - _ Vindex = (*Numeric)(nil) - _ Reversible = (*Numeric)(nil) + _ SingleColumn = (*Numeric)(nil) + _ Reversible = (*Numeric)(nil) ) // Numeric defines a bit-pattern mapping of a uint64 to the KeyspaceId. diff --git a/go/vt/vtgate/vindexes/numeric_static_map.go b/go/vt/vtgate/vindexes/numeric_static_map.go index 9f51a80a888..39832aa3777 100644 --- a/go/vt/vtgate/vindexes/numeric_static_map.go +++ b/go/vt/vtgate/vindexes/numeric_static_map.go @@ -30,7 +30,7 @@ import ( ) var ( - _ Vindex = (*NumericStaticMap)(nil) + _ SingleColumn = (*NumericStaticMap)(nil) ) // NumericLookupTable stores the mapping of keys. diff --git a/go/vt/vtgate/vindexes/numeric_static_map_test.go b/go/vt/vtgate/vindexes/numeric_static_map_test.go index e81abe331db..94035320f0d 100644 --- a/go/vt/vtgate/vindexes/numeric_static_map_test.go +++ b/go/vt/vtgate/vindexes/numeric_static_map_test.go @@ -28,10 +28,14 @@ import ( // createVindex creates the "numeric_static_map" vindex object which is used by // each test. -func createVindex() (Vindex, error) { +func createVindex() (SingleColumn, error) { m := make(map[string]string) m["json_path"] = "testdata/numeric_static_map_test.json" - return CreateVindex("numeric_static_map", "numericStaticMap", m) + vindex, err := CreateVindex("numeric_static_map", "numericStaticMap", m) + if err != nil { + panic(err) + } + return vindex.(SingleColumn), nil } func TestNumericStaticMapCost(t *testing.T) { diff --git a/go/vt/vtgate/vindexes/numeric_test.go b/go/vt/vtgate/vindexes/numeric_test.go index 7ad9a357f69..3d0d0532414 100644 --- a/go/vt/vtgate/vindexes/numeric_test.go +++ b/go/vt/vtgate/vindexes/numeric_test.go @@ -26,10 +26,11 @@ import ( "vitess.io/vitess/go/vt/key" ) -var numeric Vindex +var numeric SingleColumn func init() { - numeric, _ = CreateVindex("numeric", "num", nil) + vindex, _ := CreateVindex("numeric", "num", nil) + numeric = vindex.(SingleColumn) } func TestNumericCost(t *testing.T) { diff --git a/go/vt/vtgate/vindexes/region_experimental.go b/go/vt/vtgate/vindexes/region_experimental.go index 15159517dd9..ba10a10f7dc 100644 --- a/go/vt/vtgate/vindexes/region_experimental.go +++ b/go/vt/vtgate/vindexes/region_experimental.go @@ -26,10 +26,7 @@ import ( ) var ( - _ Vindex = (*RegionExperimental)(nil) - _ Lookup = (*RegionExperimental)(nil) - _ WantOwnerInfo = (*RegionExperimental)(nil) - _ MultiColumn = (*RegionExperimental)(nil) + _ MultiColumn = (*RegionExperimental)(nil) ) func init() { @@ -40,8 +37,8 @@ func init() { // The table is expected to define the id column as unique. It's // Unique and a Lookup. type RegionExperimental struct { + name string regionBytes int - *ConsistentLookupUnique } // NewRegionExperimental creates a RegionExperimental vindex. @@ -61,45 +58,51 @@ func NewRegionExperimental(name string, m map[string]string) (Vindex, error) { default: return nil, fmt.Errorf("region_bits must be 1 or 2: %v", rbs) } - vindex, err := NewConsistentLookupUnique(name, m) - if err != nil { - // Unreachable. - return nil, err - } - cl := vindex.(*ConsistentLookupUnique) - if len(cl.lkp.FromColumns) != 2 { - return nil, fmt.Errorf("two columns are required for region_experimental: %v", cl.lkp.FromColumns) - } return &RegionExperimental{ - regionBytes: rb, - ConsistentLookupUnique: cl, + name: name, + regionBytes: rb, }, nil } -// MapMulti satisfies MultiColumn. -func (ge *RegionExperimental) MapMulti(vcursor VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) { +// String returns the name of the vindex. +func (ge *RegionExperimental) String() string { + return ge.name +} + +// Cost returns the cost of this index as 1. +func (ge *RegionExperimental) Cost() int { + return 1 +} + +// IsUnique returns true since the Vindex is unique. +func (ge *RegionExperimental) IsUnique() bool { + return true +} + +// Map satisfies MultiColumn. +func (ge *RegionExperimental) Map(vcursor VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) { destinations := make([]key.Destination, 0, len(rowsColValues)) for _, row := range rowsColValues { if len(row) != 2 { destinations = append(destinations, key.DestinationNone{}) continue } - // Compute hash. - hn, err := sqltypes.ToUint64(row[0]) + // Compute region prefix. + rn, err := sqltypes.ToUint64(row[0]) if err != nil { destinations = append(destinations, key.DestinationNone{}) continue } - h := vhash(hn) + r := make([]byte, 2, 2+8) + binary.BigEndian.PutUint16(r, uint16(rn)) - // Compute region prefix. - rn, err := sqltypes.ToUint64(row[1]) + // Compute hash. + hn, err := sqltypes.ToUint64(row[1]) if err != nil { destinations = append(destinations, key.DestinationNone{}) continue } - r := make([]byte, 2) - binary.BigEndian.PutUint16(r, uint16(rn)) + h := vhash(hn) // Concatenate and add to destinations. if ge.regionBytes == 1 { @@ -111,10 +114,10 @@ func (ge *RegionExperimental) MapMulti(vcursor VCursor, rowsColValues [][]sqltyp return destinations, nil } -// VerifyMulti satisfies MultiColumn. -func (ge *RegionExperimental) VerifyMulti(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte) ([]bool, error) { +// Verify satisfies MultiColumn. +func (ge *RegionExperimental) Verify(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte) ([]bool, error) { result := make([]bool, len(rowsColValues)) - destinations, _ := ge.MapMulti(vcursor, rowsColValues) + destinations, _ := ge.Map(vcursor, rowsColValues) for i, dest := range destinations { destksid, ok := dest.(key.DestinationKeyspaceID) if !ok { @@ -122,14 +125,5 @@ func (ge *RegionExperimental) VerifyMulti(vcursor VCursor, rowsColValues [][]sql } result[i] = bytes.Equal([]byte(destksid), ksids[i]) } - // We also need to verify from the lookup. - // TODO(sougou): we should only verify true values from previous result. - lresult, err := Verify(ge.ConsistentLookupUnique, vcursor, rowsColValues, ksids) - if err != nil { - return nil, err - } - for i := range result { - result[i] = result[i] && lresult[i] - } return result, nil } diff --git a/go/vt/vtgate/vindexes/region_experimental_test.go b/go/vt/vtgate/vindexes/region_experimental_test.go index ef85c65b90e..f1162f43613 100644 --- a/go/vt/vtgate/vindexes/region_experimental_test.go +++ b/go/vt/vtgate/vindexes/region_experimental_test.go @@ -25,23 +25,32 @@ import ( "vitess.io/vitess/go/vt/key" ) -func TestRegionExperimentalMapMulti1(t *testing.T) { +func TestRegionExperimentalMisc(t *testing.T) { ge, err := createRegionVindex(t, "region_experimental", "f1,f2", 1) assert.NoError(t, err) - got, err := ge.(MultiColumn).MapMulti(nil, [][]sqltypes.Value{{ + assert.Equal(t, 1, ge.Cost()) + assert.Equal(t, "region_experimental", ge.String()) + assert.True(t, ge.IsUnique()) +} + +func TestRegionExperimentalMap(t *testing.T) { + vindex, err := createRegionVindex(t, "region_experimental", "f1,f2", 1) + assert.NoError(t, err) + ge := vindex.(MultiColumn) + got, err := ge.Map(nil, [][]sqltypes.Value{{ sqltypes.NewInt64(1), sqltypes.NewInt64(1), }, { - sqltypes.NewInt64(1), sqltypes.NewInt64(255), + sqltypes.NewInt64(255), sqltypes.NewInt64(1), }, { - sqltypes.NewInt64(1), sqltypes.NewInt64(256), + sqltypes.NewInt64(256), sqltypes.NewInt64(1), }, { // Invalid length. sqltypes.NewInt64(1), }, { - // Invalid id. + // Invalid region. sqltypes.NewVarBinary("abcd"), sqltypes.NewInt64(256), }, { - // Invalid region. + // Invalid id. sqltypes.NewInt64(1), sqltypes.NewVarBinary("abcd"), }}) assert.NoError(t, err) @@ -58,16 +67,17 @@ func TestRegionExperimentalMapMulti1(t *testing.T) { } func TestRegionExperimentalMapMulti2(t *testing.T) { - ge, err := createRegionVindex(t, "region_experimental", "f1,f2", 2) + vindex, err := createRegionVindex(t, "region_experimental", "f1,f2", 2) assert.NoError(t, err) - got, err := ge.(MultiColumn).MapMulti(nil, [][]sqltypes.Value{{ + ge := vindex.(MultiColumn) + got, err := ge.Map(nil, [][]sqltypes.Value{{ sqltypes.NewInt64(1), sqltypes.NewInt64(1), }, { - sqltypes.NewInt64(1), sqltypes.NewInt64(255), + sqltypes.NewInt64(255), sqltypes.NewInt64(1), }, { - sqltypes.NewInt64(1), sqltypes.NewInt64(256), + sqltypes.NewInt64(256), sqltypes.NewInt64(1), }, { - sqltypes.NewInt64(1), sqltypes.NewInt64(0x10000), + sqltypes.NewInt64(0x10000), sqltypes.NewInt64(1), }}) assert.NoError(t, err) @@ -81,15 +91,12 @@ func TestRegionExperimentalMapMulti2(t *testing.T) { } func TestRegionExperimentalVerifyMulti(t *testing.T) { - - ge, err := createRegionVindex(t, "region_experimental", "f1,f2", 1) + vindex, err := createRegionVindex(t, "region_experimental", "f1,f2", 1) assert.NoError(t, err) + ge := vindex.(MultiColumn) vals := [][]sqltypes.Value{{ // One for match sqltypes.NewInt64(1), sqltypes.NewInt64(1), - }, { - // One for mismatch by lookup - sqltypes.NewInt64(1), sqltypes.NewInt64(1), }, { // One for mismatch sqltypes.NewInt64(1), sqltypes.NewInt64(1), @@ -98,27 +105,14 @@ func TestRegionExperimentalVerifyMulti(t *testing.T) { sqltypes.NewInt64(1), }} ksids := [][]byte{ - []byte("\x01\x16k@\xb4J\xbaK\xd6"), []byte("\x01\x16k@\xb4J\xbaK\xd6"), []byte("no match"), []byte(""), } - vc := &loggingVCursor{} - vc.AddResult(makeTestResult(1), nil) - // The second value should return a mismatch. - vc.AddResult(&sqltypes.Result{}, nil) - vc.AddResult(makeTestResult(1), nil) - vc.AddResult(makeTestResult(1), nil) - want := []bool{true, false, false, false} - got, err := ge.(MultiColumn).VerifyMulti(vc, vals, ksids) + want := []bool{true, false, false} + got, err := ge.Verify(nil, vals, ksids) assert.NoError(t, err) - vc.verifyLog(t, []string{ - "ExecutePre select f1 from t where f1 = :f1 and toc = :toc [{f1 1} {toc \x01\x16k@\xb4J\xbaK\xd6}] false", - "ExecutePre select f1 from t where f1 = :f1 and toc = :toc [{f1 1} {toc \x01\x16k@\xb4J\xbaK\xd6}] false", - "ExecutePre select f1 from t where f1 = :f1 and toc = :toc [{f1 1} {toc no match}] false", - "ExecutePre select f1 from t where f1 = :f1 and toc = :toc [{f1 1} {toc }] false", - }) assert.Equal(t, want, got) } @@ -127,8 +121,6 @@ func TestRegionExperimentalCreateErrors(t *testing.T) { assert.EqualError(t, err, "region_bits must be 1 or 2: 3") _, err = CreateVindex("region_experimental", "region_experimental", nil) assert.EqualError(t, err, "region_experimental missing region_bytes param") - _, err = createRegionVindex(t, "region_experimental", "f1", 2) - assert.EqualError(t, err, "two columns are required for region_experimental: [f1]") } func createRegionVindex(t *testing.T, name, from string, rb int) (Vindex, error) { diff --git a/go/vt/vtgate/vindexes/reverse_bits.go b/go/vt/vtgate/vindexes/reverse_bits.go index 9957988fe58..f199eaaea35 100644 --- a/go/vt/vtgate/vindexes/reverse_bits.go +++ b/go/vt/vtgate/vindexes/reverse_bits.go @@ -29,8 +29,8 @@ import ( ) var ( - _ Vindex = (*ReverseBits)(nil) - _ Reversible = (*ReverseBits)(nil) + _ SingleColumn = (*ReverseBits)(nil) + _ Reversible = (*ReverseBits)(nil) ) // ReverseBits defines vindex that reverses the bits of a number. diff --git a/go/vt/vtgate/vindexes/reverse_bits_test.go b/go/vt/vtgate/vindexes/reverse_bits_test.go index bfd5a1c8b33..a135b8ca564 100644 --- a/go/vt/vtgate/vindexes/reverse_bits_test.go +++ b/go/vt/vtgate/vindexes/reverse_bits_test.go @@ -25,14 +25,14 @@ import ( "vitess.io/vitess/go/vt/key" ) -var reverseBits Vindex +var reverseBits SingleColumn func init() { hv, err := CreateVindex("reverse_bits", "rr", map[string]string{"Table": "t", "Column": "c"}) if err != nil { panic(err) } - reverseBits = hv + reverseBits = hv.(SingleColumn) } func TestReverseBitsCost(t *testing.T) { diff --git a/go/vt/vtgate/vindexes/unicodeloosemd5.go b/go/vt/vtgate/vindexes/unicodeloosemd5.go index 8264ebfe912..ef3a70c9f19 100644 --- a/go/vt/vtgate/vindexes/unicodeloosemd5.go +++ b/go/vt/vtgate/vindexes/unicodeloosemd5.go @@ -30,7 +30,7 @@ import ( ) var ( - _ Vindex = (*UnicodeLooseMD5)(nil) + _ SingleColumn = (*UnicodeLooseMD5)(nil) ) // UnicodeLooseMD5 is a vindex that normalizes and hashes unicode strings diff --git a/go/vt/vtgate/vindexes/unicodeloosemd5_test.go b/go/vt/vtgate/vindexes/unicodeloosemd5_test.go index 9c51f178b1e..89bcc19fbb1 100644 --- a/go/vt/vtgate/vindexes/unicodeloosemd5_test.go +++ b/go/vt/vtgate/vindexes/unicodeloosemd5_test.go @@ -26,10 +26,11 @@ import ( "vitess.io/vitess/go/vt/key" ) -var charVindex Vindex +var charVindex SingleColumn func init() { - charVindex, _ = CreateVindex("unicode_loose_md5", "utf8ch", nil) + vindex, _ := CreateVindex("unicode_loose_md5", "utf8ch", nil) + charVindex = vindex.(SingleColumn) } func TestUnicodeLooseMD5Cost(t *testing.T) { diff --git a/go/vt/vtgate/vindexes/vindex.go b/go/vt/vtgate/vindexes/vindex.go index 35ab8c853e2..3d06668def9 100644 --- a/go/vt/vtgate/vindexes/vindex.go +++ b/go/vt/vtgate/vindexes/vindex.go @@ -22,9 +22,11 @@ import ( "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/vt/key" "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vterrors" querypb "vitess.io/vitess/go/vt/proto/query" vtgatepb "vitess.io/vitess/go/vt/proto/vtgate" + vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" ) // This file defines interfaces and registration for vindexes. @@ -56,33 +58,37 @@ type Vindex interface { // IsUnique returns true if the Vindex is unique. // Which means Map() maps to either a KeyRange or a single KeyspaceID. IsUnique() bool +} +// SingleColumn defines the interface for a single column vindex. +type SingleColumn interface { + Vindex // Map can map ids to key.Destination objects. // If the Vindex is unique, each id would map to either // a KeyRange, or a single KeyspaceID. // If the Vindex is non-unique, each id would map to either // a KeyRange, or a list of KeyspaceID. - // If the error returned if nil, then the array len of the - // key.Destination array must match len(ids). Map(vcursor VCursor, ids []sqltypes.Value) ([]key.Destination, error) - // Verify must be implented by all vindexes. It should return - // true if the ids can be mapped to the keyspace ids. + // Verify returns true for every id that successfully maps to the + // specified keyspace id. Verify(vcursor VCursor, ids []sqltypes.Value, ksids [][]byte) ([]bool, error) } -// MultiColumn defines the interface for vindexes that can -// support multi-column vindexes. +// MultiColumn defines the interface for a multi-column vindex. type MultiColumn interface { - MapMulti(vcursor VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) - VerifyMulti(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte) ([]bool, error) + Vindex + Map(vcursor VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) + Verify(vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte) ([]bool, error) } // A Reversible vindex is one that can perform a // reverse lookup from a keyspace id to an id. This // is optional. If present, VTGate can use it to // fill column values based on the target keyspace id. +// Reversible is supported only for SingleColumn vindexes. type Reversible interface { + SingleColumn ReverseMap(vcursor VCursor, ks [][]byte) ([]sqltypes.Value, error) } @@ -140,20 +146,26 @@ func CreateVindex(vindexType, name string, params map[string]string) (Vindex, er return f(name, params) } -// Map invokes MapMulti or Map depending on which is available. +// Map invokes the Map implementation supplied by the vindex. func Map(vindex Vindex, vcursor VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) { - if multi, ok := vindex.(MultiColumn); ok { - return multi.MapMulti(vcursor, rowsColValues) + switch vindex := vindex.(type) { + case MultiColumn: + return vindex.Map(vcursor, rowsColValues) + case SingleColumn: + return vindex.Map(vcursor, firstColsOnly(rowsColValues)) } - return vindex.Map(vcursor, firstColsOnly(rowsColValues)) + return nil, vterrors.New(vtrpcpb.Code_INTERNAL, "vindex does not have Map functions") } -// Verify invokes VerifyMulti or Verify depending on which is available. +// Verify invokes the Verify implementation supplied by the vindex. func Verify(vindex Vindex, vcursor VCursor, rowsColValues [][]sqltypes.Value, ksids [][]byte) ([]bool, error) { - if multi, ok := vindex.(MultiColumn); ok { - return multi.VerifyMulti(vcursor, rowsColValues, ksids) + switch vindex := vindex.(type) { + case MultiColumn: + return vindex.Verify(vcursor, rowsColValues, ksids) + case SingleColumn: + return vindex.Verify(vcursor, firstColsOnly(rowsColValues), ksids) } - return vindex.Verify(vcursor, firstColsOnly(rowsColValues), ksids) + return nil, vterrors.New(vtrpcpb.Code_INTERNAL, "vindex does not have Map functions") } func firstColsOnly(rowsColValues [][]sqltypes.Value) []sqltypes.Value { diff --git a/go/vt/vtgate/vindexes/vindex_test.go b/go/vt/vtgate/vindexes/vindex_test.go index 1c85f83a1fd..c78ab192b26 100644 --- a/go/vt/vtgate/vindexes/vindex_test.go +++ b/go/vt/vtgate/vindexes/vindex_test.go @@ -51,12 +51,10 @@ func TestVindexMap(t *testing.T) { } func TestVindexVerify(t *testing.T) { - vc := &loggingVCursor{} - vc.AddResult(makeTestResult(1), nil) ge, err := createRegionVindex(t, "region_experimental", "f1,f2", 1) assert.NoError(t, err) - got, err := Verify(ge, vc, [][]sqltypes.Value{{ + got, err := Verify(ge, nil, [][]sqltypes.Value{{ sqltypes.NewInt64(1), sqltypes.NewInt64(1), }}, [][]byte{ @@ -64,9 +62,6 @@ func TestVindexVerify(t *testing.T) { }, ) assert.NoError(t, err) - vc.verifyLog(t, []string{ - "ExecutePre select f1 from t where f1 = :f1 and toc = :toc [{f1 1} {toc \x01\x16k@\xb4J\xbaK\xd6}] false", - }) want := []bool{true} assert.Equal(t, want, got) diff --git a/go/vt/vtgate/vindexes/vschema.go b/go/vt/vtgate/vindexes/vschema.go index 8f519fb7093..a1f15ad862e 100644 --- a/go/vt/vtgate/vindexes/vschema.go +++ b/go/vt/vtgate/vindexes/vschema.go @@ -313,6 +313,9 @@ func buildTables(ks *vschemapb.Keyspace, vschema *VSchema, ksvschema *KeyspaceSc if !columnVindex.Vindex.IsUnique() { return fmt.Errorf("primary vindex %s is not Unique for table %s", ind.Name, tname) } + if owned { + return fmt.Errorf("primary vindex %s cannot be owned for table %s", ind.Name, tname) + } } t.ColumnVindexes = append(t.ColumnVindexes, columnVindex) if owned { @@ -617,6 +620,10 @@ func FindVindexForSharding(tableName string, colVindexes []*ColumnVindex) (*Colu } result := colVindexes[0] for _, colVindex := range colVindexes { + // Only allow SingleColumn for legacy resharding. + if _, ok := colVindex.Vindex.(SingleColumn); !ok { + continue + } if colVindex.Vindex.Cost() < result.Vindex.Cost() && colVindex.Vindex.IsUnique() { result = colVindex } diff --git a/go/vt/vtgate/vindexes/vschema_test.go b/go/vt/vtgate/vindexes/vschema_test.go index 200636c2388..5ed01c5285c 100644 --- a/go/vt/vtgate/vindexes/vschema_test.go +++ b/go/vt/vtgate/vindexes/vschema_test.go @@ -52,7 +52,7 @@ func NewSTFU(name string, params map[string]string) (Vindex, error) { return &stFU{name: name, Params: params}, nil } -var _ Vindex = (*stFU)(nil) +var _ SingleColumn = (*stFU)(nil) // stLN is a Lookup, NonUnique Vindex. type stLN struct { @@ -73,7 +73,7 @@ func NewSTLN(name string, params map[string]string) (Vindex, error) { return &stLN{name: name, Params: params}, nil } -var _ Vindex = (*stLN)(nil) +var _ SingleColumn = (*stLN)(nil) var _ Lookup = (*stLN)(nil) // stLU is a Lookup, Unique Vindex. @@ -95,7 +95,7 @@ func NewSTLU(name string, params map[string]string) (Vindex, error) { return &stLU{name: name, Params: params}, nil } -var _ Vindex = (*stLO)(nil) +var _ SingleColumn = (*stLO)(nil) var _ Lookup = (*stLO)(nil) var _ WantOwnerInfo = (*stLO)(nil) @@ -126,7 +126,7 @@ func NewSTLO(name string, _ map[string]string) (Vindex, error) { return &stLO{name: name}, nil } -var _ Vindex = (*stLO)(nil) +var _ SingleColumn = (*stLO)(nil) var _ Lookup = (*stLO)(nil) func init() { @@ -1554,6 +1554,38 @@ func TestBuildVSchemaNotUniqueFail(t *testing.T) { } } +func TestBuildVSchemaPrimaryCannotBeOwned(t *testing.T) { + bad := vschemapb.SrvVSchema{ + Keyspaces: map[string]*vschemapb.Keyspace{ + "sharded": { + Sharded: true, + Vindexes: map[string]*vschemapb.Vindex{ + "stlu": { + Type: "stlu", + Owner: "t1", + }, + }, + Tables: map[string]*vschemapb.Table{ + "t1": { + ColumnVindexes: []*vschemapb.ColumnVindex{ + { + Column: "c1", + Name: "stlu", + }, + }, + }, + }, + }, + }, + } + got, _ := BuildVSchema(&bad) + err := got.Keyspaces["sharded"].Error + want := "primary vindex stlu cannot be owned for table t1" + if err == nil || err.Error() != want { + t.Errorf("BuildVSchema: %v, want %v", err, want) + } +} + func TestSequence(t *testing.T) { good := vschemapb.SrvVSchema{ Keyspaces: map[string]*vschemapb.Keyspace{ diff --git a/go/vt/vtgate/vindexes/xxhash.go b/go/vt/vtgate/vindexes/xxhash.go index 48677a736f5..fd648e3117d 100644 --- a/go/vt/vtgate/vindexes/xxhash.go +++ b/go/vt/vtgate/vindexes/xxhash.go @@ -27,7 +27,7 @@ import ( ) var ( - _ Vindex = (*XXHash)(nil) + _ SingleColumn = (*XXHash)(nil) ) // XXHash defines vindex that hashes any sql types to a KeyspaceId diff --git a/go/vt/vtgate/vindexes/xxhash_test.go b/go/vt/vtgate/vindexes/xxhash_test.go index 36c1719443c..187682f9724 100644 --- a/go/vt/vtgate/vindexes/xxhash_test.go +++ b/go/vt/vtgate/vindexes/xxhash_test.go @@ -29,14 +29,14 @@ import ( "vitess.io/vitess/go/vt/key" ) -var xxHash Vindex +var xxHash SingleColumn func init() { hv, err := CreateVindex("xxhash", "xxhash_name", map[string]string{"Table": "t", "Column": "c"}) if err != nil { panic(err) } - xxHash = hv + xxHash = hv.(SingleColumn) } func TestXXHashCost(t *testing.T) { diff --git a/go/vt/vttablet/tabletserver/vstreamer/engine_test.go b/go/vt/vttablet/tabletserver/vstreamer/engine_test.go index 575b146b6e0..28c5da5643d 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/engine_test.go +++ b/go/vt/vttablet/tabletserver/vstreamer/engine_test.go @@ -26,7 +26,8 @@ import ( binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" ) -var shardedVSchema = `{ +var ( + shardedVSchema = `{ "sharded": true, "vindexes": { "hash": { @@ -45,6 +46,32 @@ var shardedVSchema = `{ } }` + multicolumnVSchema = `{ + "sharded": true, + "vindexes": { + "region_vdx": { + "type": "region_experimental", + "params": { + "region_bytes": "1" + } + } + }, + "tables": { + "t1": { + "column_vindexes": [ + { + "columns": [ + "region", + "id" + ], + "name": "region_vdx" + } + ] + } + } +}` +) + func TestUpdateVSchema(t *testing.T) { if testing.Short() { t.Skip() diff --git a/go/vt/vttablet/tabletserver/vstreamer/planbuilder.go b/go/vt/vttablet/tabletserver/vstreamer/planbuilder.go index e13b466b55b..8e8f211cde3 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/planbuilder.go +++ b/go/vt/vttablet/tabletserver/vstreamer/planbuilder.go @@ -35,19 +35,28 @@ import ( // Plan represents the plan for a table. type Plan struct { - Table *Table - ColExprs []ColExpr - VindexColumn int - Vindex vindexes.Vindex - KeyRange *topodatapb.KeyRange + Table *Table + ColExprs []ColExpr + + // Vindex, VindexColumns and KeyRange, if set, will be used + // to filter the row. + Vindex vindexes.Vindex + VindexColumns []int + KeyRange *topodatapb.KeyRange } // ColExpr represents a column expression. type ColExpr struct { + // ColNum specifies the source column value. ColNum int - Vindex vindexes.Vindex - Alias sqlparser.ColIdent - Type querypb.Type + + // Vindex and VindexColumns, if set, will be used to generate + // a keyspace_id. If so, ColNum is ignored. + Vindex vindexes.Vindex + VindexColumns []int + + Alias sqlparser.ColIdent + Type querypb.Type } // Table contains the metadata for a table. @@ -71,37 +80,44 @@ func (plan *Plan) fields() []*querypb.Field { // filter filters the row against the plan. It returns false if the row did not match. // If the row matched, it returns the columns to be sent. func (plan *Plan) filter(values []sqltypes.Value) (bool, []sqltypes.Value, error) { + if plan.Vindex != nil { + vindexValues := make([]sqltypes.Value, 0, len(plan.VindexColumns)) + for _, col := range plan.VindexColumns { + vindexValues = append(vindexValues, values[col]) + } + ksid, err := getKeyspaceID(vindexValues, plan.Vindex) + if err != nil { + return false, nil, err + } + if !key.KeyRangeContains(plan.KeyRange, ksid) { + return false, nil, nil + } + } + result := make([]sqltypes.Value, len(plan.ColExprs)) for i, colExpr := range plan.ColExprs { if colExpr.ColNum >= len(values) { return false, nil, fmt.Errorf("index out of range, colExpr.ColNum: %d, len(values): %d", colExpr.ColNum, len(values)) } - val := values[colExpr.ColNum] - if colExpr.Vindex != nil { - ksid, err := getKeyspaceID(val, colExpr.Vindex) + if colExpr.Vindex == nil { + result[i] = values[colExpr.ColNum] + } else { + vindexValues := make([]sqltypes.Value, 0, len(colExpr.VindexColumns)) + for _, col := range colExpr.VindexColumns { + vindexValues = append(vindexValues, values[col]) + } + ksid, err := getKeyspaceID(vindexValues, colExpr.Vindex) if err != nil { return false, nil, err } - val = sqltypes.MakeTrusted(sqltypes.VarBinary, []byte(ksid)) + result[i] = sqltypes.MakeTrusted(sqltypes.VarBinary, []byte(ksid)) } - result[i] = val - } - if plan.Vindex == nil { - return true, result, nil - } - - ksid, err := getKeyspaceID(result[plan.VindexColumn], plan.Vindex) - if err != nil { - return false, nil, err - } - if !key.KeyRangeContains(plan.KeyRange, ksid) { - return false, nil, nil } return true, result, nil } -func getKeyspaceID(value sqltypes.Value, vindex vindexes.Vindex) (key.DestinationKeyspaceID, error) { - destinations, err := vindex.Map(nil, []sqltypes.Value{value}) +func getKeyspaceID(values []sqltypes.Value, vindex vindexes.Vindex) (key.DestinationKeyspaceID, error) { + destinations, err := vindexes.Map(vindex, nil, [][]sqltypes.Value{values}) if err != nil { return nil, err } @@ -110,7 +126,7 @@ func getKeyspaceID(value sqltypes.Value, vindex vindexes.Vindex) (key.Destinatio } ksid, ok := destinations[0].(key.DestinationKeyspaceID) if !ok || len(ksid) == 0 { - return nil, fmt.Errorf("could not map %v to a keyspace id, got destination %v", value, destinations[0]) + return nil, fmt.Errorf("could not map %v to a keyspace id, got destination %v", values, destinations[0]) } return ksid, nil } @@ -223,14 +239,12 @@ func buildREPlan(ti *Table, kschema *vindexes.KeyspaceSchema, filter string) (*P if len(table.ColumnVindexes) == 0 { return nil, fmt.Errorf("table %s has no primary vindex", ti.Name) } - // findColumn can be used here because result column list is same - // as source. - colnum, err := findColumn(ti, table.ColumnVindexes[0].Columns[0]) + plan.Vindex = table.ColumnVindexes[0].Vindex + var err error + plan.VindexColumns, err = buildVindexColumns(plan.Table, table.ColumnVindexes[0].Columns) if err != nil { return nil, err } - plan.VindexColumn = colnum - plan.Vindex = table.ColumnVindexes[0].Vindex // Parse keyrange. keyranges, err := key.ParseShardingSpec(filter) @@ -341,7 +355,11 @@ func (plan *Plan) analyzeExpr(kschema *vindexes.KeyspaceSchema, selExpr sqlparse if as.IsEmpty() { as = sqlparser.NewColIdent(sqlparser.String(aliased.Expr)) } - return ColExpr{ColNum: colnum, Alias: as, Type: plan.Table.Columns[colnum].Type}, nil + return ColExpr{ + ColNum: colnum, + Alias: as, + Type: plan.Table.Columns[colnum].Type, + }, nil case *sqlparser.FuncExpr: if inner.Name.Lowered() != "keyspace_id" { return ColExpr{}, fmt.Errorf("unsupported function: %v", sqlparser.String(inner)) @@ -357,21 +375,26 @@ func (plan *Plan) analyzeExpr(kschema *vindexes.KeyspaceSchema, selExpr sqlparse if len(table.ColumnVindexes) == 0 { return ColExpr{}, fmt.Errorf("table %s has no primary vindex", plan.Table.Name) } - colnum, err := findColumn(plan.Table, table.ColumnVindexes[0].Columns[0]) + vindexColumns, err := buildVindexColumns(plan.Table, table.ColumnVindexes[0].Columns) if err != nil { return ColExpr{}, err } - return ColExpr{ColNum: colnum, Vindex: table.ColumnVindexes[0].Vindex, Alias: sqlparser.NewColIdent("keyspace_id"), Type: sqltypes.VarBinary}, nil + return ColExpr{ + Vindex: table.ColumnVindexes[0].Vindex, + VindexColumns: vindexColumns, + Alias: sqlparser.NewColIdent("keyspace_id"), + Type: sqltypes.VarBinary, + }, nil default: return ColExpr{}, fmt.Errorf("unsupported: %v", sqlparser.String(aliased.Expr)) } } func (plan *Plan) analyzeInKeyRange(kschema *vindexes.KeyspaceSchema, exprs sqlparser.SelectExprs) error { - var colname sqlparser.ColIdent + var colnames []sqlparser.ColIdent var krExpr sqlparser.SelectExpr - switch len(exprs) { - case 1: + switch { + case len(exprs) == 1: table := kschema.Tables[plan.Table.Name] if table == nil { return fmt.Errorf("no vschema definition for table %s", plan.Table.Name) @@ -380,23 +403,26 @@ func (plan *Plan) analyzeInKeyRange(kschema *vindexes.KeyspaceSchema, exprs sqlp if len(table.ColumnVindexes) == 0 { return fmt.Errorf("table %s has no primary vindex", plan.Table.Name) } - colname = table.ColumnVindexes[0].Columns[0] + colnames = table.ColumnVindexes[0].Columns plan.Vindex = table.ColumnVindexes[0].Vindex krExpr = exprs[0] - case 3: - aexpr, ok := exprs[0].(*sqlparser.AliasedExpr) - if !ok { - return fmt.Errorf("unexpected: %v", sqlparser.String(exprs[0])) - } - qualifiedName, ok := aexpr.Expr.(*sqlparser.ColName) - if !ok { - return fmt.Errorf("unexpected: %v", sqlparser.String(exprs[0])) - } - if !qualifiedName.Qualifier.IsEmpty() { - return fmt.Errorf("unsupported qualifier for column: %v", sqlparser.String(colname)) + case len(exprs) >= 3: + for _, expr := range exprs[:len(exprs)-2] { + aexpr, ok := expr.(*sqlparser.AliasedExpr) + if !ok { + return fmt.Errorf("unexpected: %v", sqlparser.String(expr)) + } + qualifiedName, ok := aexpr.Expr.(*sqlparser.ColName) + if !ok { + return fmt.Errorf("unexpected: %v", sqlparser.String(expr)) + } + if !qualifiedName.Qualifier.IsEmpty() { + return fmt.Errorf("unsupported qualifier for column: %v", sqlparser.String(qualifiedName)) + } + colnames = append(colnames, qualifiedName.Name) } - colname = qualifiedName.Name - vtype, err := selString(exprs[1]) + + vtype, err := selString(exprs[len(exprs)-2]) if err != nil { return err } @@ -407,20 +433,15 @@ func (plan *Plan) analyzeInKeyRange(kschema *vindexes.KeyspaceSchema, exprs sqlp if !plan.Vindex.IsUnique() { return fmt.Errorf("vindex must be Unique to be used for VReplication: %s", vtype) } - krExpr = exprs[2] + + krExpr = exprs[len(exprs)-1] default: return fmt.Errorf("unexpected in_keyrange parameters: %v", sqlparser.String(exprs)) } - found := false - for i, cExpr := range plan.ColExprs { - if cExpr.Alias.Equal(colname) { - found = true - plan.VindexColumn = i - break - } - } - if !found { - return fmt.Errorf("keyrange expression does not reference a column in the select list: %v", sqlparser.String(colname)) + var err error + plan.VindexColumns, err = buildVindexColumns(plan.Table, colnames) + if err != nil { + return err } kr, err := selString(krExpr) if err != nil { @@ -449,6 +470,18 @@ func selString(expr sqlparser.SelectExpr) (string, error) { return string(val.Val), nil } +func buildVindexColumns(ti *Table, colnames []sqlparser.ColIdent) ([]int, error) { + vindexColumns := make([]int, 0, len(colnames)) + for _, colname := range colnames { + colnum, err := findColumn(ti, colname) + if err != nil { + return nil, err + } + vindexColumns = append(vindexColumns, colnum) + } + return vindexColumns, nil +} + func findColumn(ti *Table, name sqlparser.ColIdent) (int, error) { for i, col := range ti.Columns { if name.Equal(col.Name) { diff --git a/go/vt/vttablet/tabletserver/vstreamer/planbuilder_test.go b/go/vt/vttablet/tabletserver/vstreamer/planbuilder_test.go index 4c70242d7f0..e6d38059e94 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/planbuilder_test.go +++ b/go/vt/vttablet/tabletserver/vstreamer/planbuilder_test.go @@ -41,8 +41,11 @@ func init() { "hash": { "type": "hash" }, - "lookup": { - "type": "lookup" + "region_vdx": { + "type": "region_experimental", + "params": { + "region_bytes": "1" + } } }, "tables": { @@ -53,6 +56,17 @@ func init() { "name": "hash" } ] + }, + "regional": { + "column_vindexes": [ + { + "columns": [ + "region", + "id" + ], + "name": "region_vdx" + } + ] } } }` @@ -177,6 +191,19 @@ func TestPlanbuilder(t *testing.T) { Type: sqltypes.VarBinary, }}, } + regional := &Table{ + Name: "regional", + Columns: []schema.TableColumn{{ + Name: sqlparser.NewColIdent("region"), + Type: sqltypes.Int64, + }, { + Name: sqlparser.NewColIdent("id"), + Type: sqltypes.Int64, + }, { + Name: sqlparser.NewColIdent("val"), + Type: sqltypes.VarBinary, + }}, + } testcases := []struct { inTable *Table @@ -210,7 +237,7 @@ func TestPlanbuilder(t *testing.T) { Alias: sqlparser.NewColIdent("val"), Type: sqltypes.VarBinary, }}, - VindexColumn: 0, + VindexColumns: []int{0}, }, }, { inTable: t1, @@ -267,7 +294,7 @@ func TestPlanbuilder(t *testing.T) { Alias: sqlparser.NewColIdent("id"), Type: sqltypes.Int64, }}, - VindexColumn: 1, + VindexColumns: []int{0}, }, }, { inTable: t1, @@ -282,11 +309,41 @@ func TestPlanbuilder(t *testing.T) { Alias: sqlparser.NewColIdent("id"), Type: sqltypes.Int64, }}, - VindexColumn: 1, + VindexColumns: []int{0}, }, }, { inTable: t2, inRule: &binlogdatapb.Rule{Match: "/t1/"}, + }, { + inTable: regional, + inRule: &binlogdatapb.Rule{Match: "regional", Filter: "select val, id from regional where in_keyrange('-80')"}, + outPlan: &Plan{ + ColExprs: []ColExpr{{ + ColNum: 2, + Alias: sqlparser.NewColIdent("val"), + Type: sqltypes.VarBinary, + }, { + ColNum: 1, + Alias: sqlparser.NewColIdent("id"), + Type: sqltypes.Int64, + }}, + VindexColumns: []int{0, 1}, + }, + }, { + inTable: regional, + inRule: &binlogdatapb.Rule{Match: "regional", Filter: "select id, keyspace_id() from regional"}, + outPlan: &Plan{ + ColExprs: []ColExpr{{ + ColNum: 1, + Alias: sqlparser.NewColIdent("id"), + Type: sqltypes.Int64, + }, { + Alias: sqlparser.NewColIdent("keyspace_id"), + Vindex: testKSChema.Vindexes["region_vdx"], + VindexColumns: []int{0, 1}, + Type: sqltypes.VarBinary, + }}, + }, }, { inTable: t1, inRule: &binlogdatapb.Rule{Match: "/*/"}, @@ -355,10 +412,6 @@ func TestPlanbuilder(t *testing.T) { inTable: t1, inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(1, 'hash', '-80')"}, outErr: `unexpected: 1`, - }, { - inTable: t1, - inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(none, 'hash', '-80')"}, - outErr: `keyrange expression does not reference a column in the select list: none`, }, { inTable: t1, inRule: &binlogdatapb.Rule{Match: "t1", Filter: "select id, val from t1 where in_keyrange(id, 'lookup', '-80')"}, diff --git a/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go b/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go index 907514632a4..a3eca41a71f 100644 --- a/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go +++ b/go/vt/vttablet/tabletserver/vstreamer/vstreamer_test.go @@ -284,6 +284,119 @@ func TestREKeyRange(t *testing.T) { }}) } +func TestInKeyRangeMultiColumn(t *testing.T) { + if testing.Short() { + t.Skip() + } + + execStatements(t, []string{ + "create table t1(region int, id int, val varbinary(128), primary key(id))", + }) + defer execStatements(t, []string{ + "drop table t1", + }) + engine.se.Reload(context.Background()) + + if err := env.SetVSchema(multicolumnVSchema); err != nil { + t.Fatal(err) + } + defer env.SetVSchema("{}") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + filter := &binlogdatapb.Filter{ + Rules: []*binlogdatapb.Rule{{ + Match: "t1", + Filter: "select id, region, val, keyspace_id() from t1 where in_keyrange('-80')", + }}, + } + ch := startStream(ctx, t, filter, "") + + // 1, 2, 3 and 5 are in shard -80. + // 4 and 6 are in shard 80-. + input := []string{ + "begin", + "insert into t1 values (1, 1, 'aaa')", + "insert into t1 values (128, 2, 'bbb')", + // Stay in shard. + "update t1 set region = 2 where id = 1", + // Move from -80 to 80-. + "update t1 set region = 128 where id = 1", + // Move from 80- to -80. + "update t1 set region = 1 where id = 2", + "commit", + } + execStatements(t, input) + expectLog(ctx, t, input, ch, [][]string{{ + `begin`, + `type:FIELD field_event: fields: fields: fields: > `, + `type:ROW row_event: > > `, + `type:ROW row_event: ` + + `after: > > `, + `type:ROW row_event: > > `, + `type:ROW row_event: > > `, + `gtid`, + `commit`, + }}) +} + +func TestREMultiColumnVindex(t *testing.T) { + if testing.Short() { + t.Skip() + } + + execStatements(t, []string{ + "create table t1(region int, id int, val varbinary(128), primary key(id))", + }) + defer execStatements(t, []string{ + "drop table t1", + }) + engine.se.Reload(context.Background()) + + if err := env.SetVSchema(multicolumnVSchema); err != nil { + t.Fatal(err) + } + defer env.SetVSchema("{}") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + filter := &binlogdatapb.Filter{ + Rules: []*binlogdatapb.Rule{{ + Match: "/.*/", + Filter: "-80", + }}, + } + ch := startStream(ctx, t, filter, "") + + // 1, 2, 3 and 5 are in shard -80. + // 4 and 6 are in shard 80-. + input := []string{ + "begin", + "insert into t1 values (1, 1, 'aaa')", + "insert into t1 values (128, 2, 'bbb')", + // Stay in shard. + "update t1 set region = 2 where id = 1", + // Move from -80 to 80-. + "update t1 set region = 128 where id = 1", + // Move from 80- to -80. + "update t1 set region = 1 where id = 2", + "commit", + } + execStatements(t, input) + expectLog(ctx, t, input, ch, [][]string{{ + `begin`, + `type:FIELD field_event: fields: fields: > `, + `type:ROW row_event: > > `, + `type:ROW row_event: after: > > `, + `type:ROW row_event: > > `, + `type:ROW row_event: > > `, + `gtid`, + `commit`, + }}) +} + func TestSelectFilter(t *testing.T) { if testing.Short() { t.Skip() diff --git a/go/vt/worker/key_resolver.go b/go/vt/worker/key_resolver.go index bc53eafd1c3..a79fb1b66f6 100644 --- a/go/vt/worker/key_resolver.go +++ b/go/vt/worker/key_resolver.go @@ -93,7 +93,7 @@ func (r *v2Resolver) keyspaceID(row []sqltypes.Value) ([]byte, error) { // table. type v3Resolver struct { shardingColumnIndex int - vindex vindexes.Vindex + vindex vindexes.SingleColumn } // newV3ResolverFromTableDefinition returns a keyspaceIDResolver for a v3 table. @@ -119,7 +119,8 @@ func newV3ResolverFromTableDefinition(keyspaceSchema *vindexes.KeyspaceSchema, t return &v3Resolver{ shardingColumnIndex: columnIndex, - vindex: colVindex.Vindex, + // Only SingleColumn vindexes are returned by FindVindexForSharding. + vindex: colVindex.Vindex.(vindexes.SingleColumn), }, nil } @@ -149,7 +150,8 @@ func newV3ResolverFromColumnList(keyspaceSchema *vindexes.KeyspaceSchema, name s return &v3Resolver{ shardingColumnIndex: columnIndex, - vindex: colVindex.Vindex, + // Only SingleColumn vindexes are returned by FindVindexForSharding. + vindex: colVindex.Vindex.(vindexes.SingleColumn), }, nil }