diff --git a/go/vt/vtgate/vindexes/multisharded.go b/go/vt/vtgate/vindexes/multisharded.go index 899c9736d2..abb7bf3e0a 100644 --- a/go/vt/vtgate/vindexes/multisharded.go +++ b/go/vt/vtgate/vindexes/multisharded.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "vitess.io/vitess/go/sqltypes" "vitess.io/vitess/go/vt/key" @@ -32,7 +33,16 @@ func init() { Register("etsy_multisharded_hybrid", NewMultiSharded) } -// Multisharded defines a multicolumn vindex that resolves a provided +type MissingSubvindexError struct { + MissingSubvindexes []string + Method string +} + +func (e *MissingSubvindexError) Error() string { + return fmt.Sprintf("%s: The following subvindexes have not been defined: %s", e.Method, strings.Join(e.MissingSubvindexes, ", ")) +} + +// MultiSharded defines a multicolumn vindex that resolves a provided // typeId column value to a hybrid subvindex and applies the subvindex // to the given id column value type MultiSharded struct { @@ -58,16 +68,20 @@ func NewMultiSharded(name string, m map[string]string) (Vindex, error) { } subvindexes := make(map[string]SingleColumn) + missingSubvindexes := []string{} for _, vindexName := range typeIdToSubvindexName { - // Only hybrid vindexes defined above this etsy_multisharded_hybrid vindex in the vschema, - // and therefore initialized before this vschema, will be avaiable in `hybridVindexes`. + // Only hybrid vindexes instantiated before this vindex will be available in `hybridVindexes`. if _, ok := hybridVindexes[vindexName]; ok { subvindexes[vindexName] = hybridVindexes[vindexName] } else { - return nil, fmt.Errorf("Multisharded.NewMultisharded: No hybrid vindex named %s has been defined", vindexName) + missingSubvindexes = append(missingSubvindexes, vindexName) } } + if len(missingSubvindexes) > 0 { + return nil, &MissingSubvindexError{MissingSubvindexes: missingSubvindexes, Method: "Multisharded.NewMultiSharded"} + } + return &MultiSharded{ name: name, typeIdToSubvindexName: typeIdToSubvindexName, diff --git a/go/vt/vtgate/vindexes/multisharded_test.go b/go/vt/vtgate/vindexes/multisharded_test.go index d26f943931..7209f4e374 100644 --- a/go/vt/vtgate/vindexes/multisharded_test.go +++ b/go/vt/vtgate/vindexes/multisharded_test.go @@ -81,32 +81,58 @@ func TestMultiShardedCreation(t *testing.T) { func TestMultiShardedCreationWithNonexistantSubvindex(t *testing.T) { hybridVindexes = map[string]SingleColumn{ - "etsy_hybrid_DNE": &HybridStub{}, + "etsy_hybrid_user": &HybridStub{}, "etsy_hybrid_shop": &HybridStub{}, } + expectedMissingSubvindexes := map[string]bool{"etsy_hybrid_DNE": true, "etsy_hybrid_DNE_2": true} + expectedMissingSubvindexesSlice := []string{} + for expectedSubvindex := range expectedMissingSubvindexes { + expectedMissingSubvindexesSlice = append(expectedMissingSubvindexesSlice, expectedSubvindex) + } + params := map[string]string{ - "type_id_to_vindex": `{"1":"etsy_hybrid_user", "2":"etsy_hybrid_shop"}`, + "type_id_to_vindex": `{"1":"etsy_hybrid_DNE", "2":"etsy_hybrid_shop", "3":"etsy_hybrid_DNE_2"}`, } expectedName := "multisharded_test" _, err := CreateVindex("etsy_multisharded_hybrid", expectedName, params) + if err == nil { - t.Errorf("Expected error from multisharded.NewMultiSharded, got nil") + t.Errorf("Expected MissingSubvindexError from multisharded.NewMultiSharded, got nil") + } + + if _, ok := err.(*MissingSubvindexError); !ok { + t.Errorf("Expected MissingSubvindexError from multisharded.NewMultiSharded, got %s", err.Error()) + } + + if len(expectedMissingSubvindexes) != len(err.(*MissingSubvindexError).MissingSubvindexes) { + t.Errorf( + "Got unexpected value for MissingSubvindexError.MissingSubvindexes. Expected: %v, Got: %v", + expectedMissingSubvindexesSlice, + err.(*MissingSubvindexError).MissingSubvindexes) + } + + for _, subvindex := range err.(*MissingSubvindexError).MissingSubvindexes { + if _, ok := expectedMissingSubvindexes[subvindex]; !ok { + t.Errorf( + "Got unexpected value for MissingSubvindexError.MissingSubvindexes. Expected: %v, Got: %v", + expectedMissingSubvindexesSlice, + err.(*MissingSubvindexError).MissingSubvindexes) + + } } } func TestMultiShardedMap(t *testing.T) { cases := []struct { - name string - typeIdToVindex string - rowsColValues [][]sqltypes.Value - expected []key.Destination - shouldErr bool + name string + rowsColValues [][]sqltypes.Value + expected []key.Destination + shouldErr bool }{ { "All rows map to same subvindex", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(1), sqltypes.NewInt64(5)}, @@ -121,7 +147,6 @@ func TestMultiShardedMap(t *testing.T) { }, { "Rows map to different subvindexes", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(2), sqltypes.NewInt64(60)}, @@ -136,7 +161,6 @@ func TestMultiShardedMap(t *testing.T) { }, { "Some rows map to subvindexes", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(2), sqltypes.NewInt64(60)}, @@ -147,7 +171,6 @@ func TestMultiShardedMap(t *testing.T) { }, { "No rows map to subvindexes", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(100), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(200), sqltypes.NewInt64(60)}, @@ -158,7 +181,6 @@ func TestMultiShardedMap(t *testing.T) { }, { "Errors when type_id is negative int", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(-1), sqltypes.NewInt64(100)}, }, @@ -167,7 +189,6 @@ func TestMultiShardedMap(t *testing.T) { }, { "Errors when id is negative int", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(-100)}, }, @@ -176,7 +197,6 @@ func TestMultiShardedMap(t *testing.T) { }, { "Errors when type_id is negative string", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewVarChar("-1"), sqltypes.NewVarChar("100")}, }, @@ -185,7 +205,6 @@ func TestMultiShardedMap(t *testing.T) { }, { "Errors when id is negative string", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewVarChar("1"), sqltypes.NewVarChar("-100")}, }, @@ -194,7 +213,6 @@ func TestMultiShardedMap(t *testing.T) { }, { "String ids supported", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewVarChar("1"), sqltypes.NewVarChar("1")}, {sqltypes.NewVarBinary("2"), sqltypes.NewVarBinary("60")}, @@ -214,7 +232,7 @@ func TestMultiShardedMap(t *testing.T) { multisharded, err := CreateVindex( "etsy_multisharded_hybrid", "multisharded_test", - map[string]string{"type_id_to_vindex": c.typeIdToVindex}) + map[string]string{"type_id_to_vindex": `{"1":"subvindex_a", "2":"subvindex_b"}`}) if err != nil { t.Fatal(err) @@ -242,16 +260,14 @@ func TestMultiShardedMap(t *testing.T) { func TestMultiShardedVerify(t *testing.T) { cases := []struct { - name string - typeIdToVindex string - rowsColValues [][]sqltypes.Value - ksids [][]byte - expected []bool - shouldErr bool + name string + rowsColValues [][]sqltypes.Value + ksids [][]byte + expected []bool + shouldErr bool }{ { "Same subvindex, all ksids map correctly to ids", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(1), sqltypes.NewInt64(2)}, @@ -267,7 +283,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "Same subvindex, some incorrect ksids", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(1), sqltypes.NewInt64(2)}, @@ -283,7 +298,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "Different subvindexes, all ksids map correctly to ids", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(1), sqltypes.NewInt64(2)}, @@ -299,7 +313,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "Different subvindexes, some incorrect ksids", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(1), sqltypes.NewInt64(2)}, @@ -315,7 +328,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "Different subvindexes, no correct ksids", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(1), sqltypes.NewInt64(2)}, @@ -333,7 +345,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "Some rows map to subvindexes", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(1), sqltypes.NewInt64(2)}, @@ -349,7 +360,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "No rows map to subvindexes", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(100), sqltypes.NewInt64(1)}, {sqltypes.NewInt64(200), sqltypes.NewInt64(2)}, @@ -365,7 +375,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "Errors when type_id is negative int", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(-1), sqltypes.NewInt64(1)}, }, @@ -377,7 +386,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "Errors when id is negative int", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewInt64(1), sqltypes.NewInt64(-1)}, }, @@ -389,7 +397,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "Errors when type_id is negative string", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewVarChar("-1"), sqltypes.NewVarChar("1")}, }, @@ -401,7 +408,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "Errors when id is negative string", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewVarChar("1"), sqltypes.NewVarChar("-1")}, }, @@ -413,7 +419,6 @@ func TestMultiShardedVerify(t *testing.T) { }, { "String ids supported", - `{"1":"subvindex_a", "2":"subvindex_b"}`, [][]sqltypes.Value{ {sqltypes.NewVarChar("1"), sqltypes.NewVarChar("1")}, {sqltypes.NewVarBinary("1"), sqltypes.NewVarBinary("2")}, @@ -434,7 +439,7 @@ func TestMultiShardedVerify(t *testing.T) { multisharded, err := CreateVindex( "etsy_multisharded_hybrid", "multisharded_test", - map[string]string{"type_id_to_vindex": c.typeIdToVindex}) + map[string]string{"type_id_to_vindex": `{"1":"subvindex_a", "2":"subvindex_b"}`}) if err != nil { t.Fatal(err) diff --git a/go/vt/vtgate/vindexes/vschema.go b/go/vt/vtgate/vindexes/vschema.go index 258a396f1c..72971c6f1e 100644 --- a/go/vt/vtgate/vindexes/vschema.go +++ b/go/vt/vtgate/vindexes/vschema.go @@ -249,22 +249,21 @@ func buildKeyspaces(source *vschemapb.SrvVSchema, vschema *VSchema) { func buildTables(ks *vschemapb.Keyspace, vschema *VSchema, ksvschema *KeyspaceSchema) error { keyspace := ksvschema.Keyspace - for vname, vindexInfo := range ks.Vindexes { - vindex, err := CreateVindex(vindexInfo.Type, vname, vindexInfo.Params) - if err != nil { - return err - } - // If the keyspace requires explicit routing, don't include it in global routing - if !ks.RequireExplicitRouting { - if _, ok := vschema.uniqueVindexes[vname]; ok { - vschema.uniqueVindexes[vname] = nil - } else { - vschema.uniqueVindexes[vname] = vindex - } - } - ksvschema.Vindexes[vname] = vindex + // CreateVindex will fail if it attempts to create a vindex that depends on another subvindex's existence + // and the subvindex has not been created yet. + // The below retries vindexes that fail to be created due to missing subvindexes. + // Note that this only allows for one layer of dependency between vindexes (failed vindexes are only retried once) + toRetry, err := buildVindexes(ks.Vindexes, ks.RequireExplicitRouting, vschema, ksvschema, true) + if err != nil { + return err + } + // Retry vindexes that failed due to missing subvindexes + _, err = buildVindexes(toRetry, ks.RequireExplicitRouting, vschema, ksvschema, false) + if err != nil { + return err } + for tname, table := range ks.Tables { t := &Table{ Name: sqlparser.NewIdentifierCS(tname), @@ -403,6 +402,45 @@ func buildTables(ks *vschemapb.Keyspace, vschema *VSchema, ksvschema *KeyspaceSc return nil } +func buildVindexes(vindexes map[string]*vschemapb.Vindex, requireExplicitRouting bool, vschema *VSchema, ksvschema *KeyspaceSchema, shouldRetry bool) (map[string]*vschemapb.Vindex, error) { + toRetry := map[string]*vschemapb.Vindex{} + for vname, vindexInfo := range vindexes { + vindex, err := CreateVindex(vindexInfo.Type, vname, vindexInfo.Params) + if err != nil { + if _, ok := err.(*MissingSubvindexError); ok && shouldRetry { + + for _, subvindexName := range err.(*MissingSubvindexError).MissingSubvindexes { + // If the vindex depends on itself, return an error without retrying. + if subvindexName == vname { + return nil, fmt.Errorf("circular vindex dependency: Vindex %s depends on itself", vname) + } + // If missing subvindexes arent in `vindexes`, they'll never be instantiated. + // In this case, return an error without retrying. + if _, ok := vindexes[subvindexName]; !ok { + return nil, err + } + } + + toRetry[vname] = vindexInfo + continue + } else { + return nil, err + } + } + + // If the keyspace requires explicit routing, don't include it in global routing + if !requireExplicitRouting { + if _, ok := vschema.uniqueVindexes[vname]; ok { + vschema.uniqueVindexes[vname] = nil + } else { + vschema.uniqueVindexes[vname] = vindex + } + } + ksvschema.Vindexes[vname] = vindex + } + return toRetry, nil +} + func resolveAutoIncrement(source *vschemapb.SrvVSchema, vschema *VSchema) { for ksname, ks := range source.Keyspaces { ksvschema := vschema.Keyspaces[ksname] diff --git a/go/vt/vtgate/vindexes/vschema_test.go b/go/vt/vtgate/vindexes/vschema_test.go index 4752b771d6..c7c11d0065 100644 --- a/go/vt/vtgate/vindexes/vschema_test.go +++ b/go/vt/vtgate/vindexes/vschema_test.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "reflect" "strings" "testing" @@ -203,6 +204,88 @@ func NewSTLO(name string, _ map[string]string) (Vindex, error) { return &stLO{name: name}, nil } +// stFUE is a single-column Functional, Unique Vindex that conditionally errs when instantiated. +// If stFUE errs on creation, it will not err the next time an stFUE vindex with the same name is created. +// Used for testing retry logic in buildVindexes +type stFUE struct { + name string + Params map[string]string +} + +func (v *stFUE) String() string { return v.name } +func (*stFUE) Cost() int { return 1 } +func (*stFUE) IsUnique() bool { return true } +func (*stFUE) NeedsVCursor() bool { return false } +func (*stFUE) Verify(context.Context, VCursor, [][]sqltypes.Value, [][]byte) ([]bool, error) { + return []bool{}, nil +} +func (*stFUE) Map(ctx context.Context, vcursor VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) { + return nil, nil +} +func (*stFUE) PartialVindex() bool { return false } + +// Allows stfue vindex to err on the first CreateVindex call, +// But succeed on retry +var stfueShouldErr = make(map[string]bool) +var stfueErrors = make(map[string]error) + +func NewSTFUE(name string, params map[string]string) (Vindex, error) { + shouldErr, ok := stfueShouldErr[name] + if !ok { + return nil, fmt.Errorf("NewSTFUE: Missing entry for %s in stfueShouldErr", name) + } + err, ok := stfueErrors[name] + if !ok { + return nil, fmt.Errorf("NewSTFUE: Missing entry for %s in stfueErrors", name) + } + if shouldErr { + stfueShouldErr[name] = false + return nil, err + } + return &stFUE{name: name, Params: params}, nil +} + +// stFUD is a single-column Functional, Unique Vindex that takes a dependency on other vindexes +type stFUD struct { + name string + Params map[string]string +} + +func (v *stFUD) String() string { return v.name } +func (*stFUD) Cost() int { return 1 } +func (*stFUD) IsUnique() bool { return true } +func (*stFUD) NeedsVCursor() bool { return false } +func (*stFUD) Verify(context.Context, VCursor, [][]sqltypes.Value, [][]byte) ([]bool, error) { + return []bool{}, nil +} +func (*stFUD) Map(ctx context.Context, vcursor VCursor, rowsColValues [][]sqltypes.Value) ([]key.Destination, error) { + return nil, nil +} +func (*stFUD) PartialVindex() bool { return false } + +var stfudVindexes = make(map[string]Vindex) + +func NewSTFUD(name string, params map[string]string) (Vindex, error) { + dependencies := []string{} + err := json.Unmarshal([]byte(params["dependencies"]), &dependencies) + if err != nil { + return nil, err + } + + missingSubvindexes := []string{} + for _, subvindex := range dependencies { + if _, ok := stfudVindexes[subvindex]; !ok { + missingSubvindexes = append(missingSubvindexes, subvindex) + } + } + if len(missingSubvindexes) > 0 { + return nil, &MissingSubvindexError{MissingSubvindexes: missingSubvindexes, Method: "NewSTFUD"} + } + vindex := &stFUD{name: name, Params: params} + stfudVindexes[name] = vindex + return vindex, nil +} + var _ SingleColumn = (*stLO)(nil) var _ Lookup = (*stLO)(nil) @@ -237,6 +320,8 @@ func init() { Register("stln", NewSTLN) Register("stlu", NewSTLU) Register("stlo", NewSTLO) + Register("stfue", NewSTFUE) + Register("stfud", NewSTFUD) Register("region_experimental_test", NewRegionExperimental) Register("mcfu", NewMCFU) } @@ -452,6 +537,178 @@ func TestVSchemaColumnsFail(t *testing.T) { } } +func TestVSchemaRetryVindexWithRetryableError(t *testing.T) { + // Because the order in which BuildVSchema instantiates vindexes is nondeterministic, + // test buildVindexes's retry logic by mocking the stfue vindexes so that each returns a + // MissingSubvindexError the first time it is created, and returns no error the second time it is created. + stfueName1 := "stfue1" + stfueName2 := "stfue2" + stfueShouldErr[stfueName1] = true + stfueShouldErr[stfueName2] = true + stfueErrors[stfueName1] = &MissingSubvindexError{MissingSubvindexes: []string{"subvindex"}, Method: "foo"} + stfueErrors[stfueName2] = &MissingSubvindexError{MissingSubvindexes: []string{"subvindex2"}, Method: "foo"} + + schema := vschemapb.SrvVSchema{ + Keyspaces: map[string]*vschemapb.Keyspace{ + "sharded": { + Sharded: true, + Vindexes: map[string]*vschemapb.Vindex{ + stfueName1: { + Type: "stfue", + Params: map[string]string{}, + }, + stfueName2: { + Type: "stfue", + Params: map[string]string{}, + }, + "subvindex": { + Type: "stfu", + Params: map[string]string{}, + }, + "subvindex2": { + Type: "stfu", + Params: map[string]string{}, + }, + }, + }, + }, + } + got := BuildVSchema(&schema) + err := got.Keyspaces["sharded"].Error + if err != nil { + t.Fatal(err) + } +} + +func TestVSchemaNoRetryIfSubvindexNotInVSchema(t *testing.T) { + stfueName := "stfue" + stfueShouldErr[stfueName] = true + stfueErrors[stfueName] = &MissingSubvindexError{MissingSubvindexes: []string{"subvindex"}, Method: "foo"} + + schema := vschemapb.SrvVSchema{ + Keyspaces: map[string]*vschemapb.Keyspace{ + "sharded": { + Sharded: true, + Vindexes: map[string]*vschemapb.Vindex{ + stfueName: { + Type: "stfue", + Params: map[string]string{}, + }, + "foobar": { + Type: "stfu", + Params: map[string]string{}, + }, + }, + }, + }, + } + + got := BuildVSchema(&schema) + err := got.Keyspaces["sharded"].Error + + expectedError := "foo: The following subvindexes have not been defined: subvindex" + if err == nil || err.Error() != expectedError { + t.Errorf("BuildVSchema: incorrect error returned: got %v, want %v", err, expectedError) + } +} + +func TestVSchemaRetryVindexDependsOnItself(t *testing.T) { + stfudName := "stfud" + stfudVindexes = map[string]Vindex{} + + schema := vschemapb.SrvVSchema{ + Keyspaces: map[string]*vschemapb.Keyspace{ + "sharded": { + Sharded: true, + Vindexes: map[string]*vschemapb.Vindex{ + stfudName: { + Type: "stfud", + Params: map[string]string{"dependencies": fmt.Sprintf(`["%s"]`, stfudName)}, + }, + "foobar": { + Type: "stfu", + Params: map[string]string{}, + }, + }, + }, + }, + } + got := BuildVSchema(&schema) + err := got.Keyspaces["sharded"].Error + expectedErr := "circular vindex dependency: Vindex stfud depends on itself" + if err == nil || err.Error() != expectedErr { + t.Errorf("BuildVSchema: incorrect error returned: got %v, want %v", err, expectedErr) + } +} + +func TestVSchemaRetryVindexesDependOnEachOther(t *testing.T) { + stfudName1 := "stfud1" + stfudName2 := "stfud2" + stfudVindexes = map[string]Vindex{} + + schema := vschemapb.SrvVSchema{ + Keyspaces: map[string]*vschemapb.Keyspace{ + "sharded": { + Sharded: true, + Vindexes: map[string]*vschemapb.Vindex{ + stfudName1: { + Type: "stfud", + Params: map[string]string{"dependencies": fmt.Sprintf(`["%s"]`, stfudName2)}, + }, + stfudName2: { + Type: "stfud", + Params: map[string]string{"dependencies": fmt.Sprintf(`["%s"]`, stfudName1)}, + }, + "foobar": { + Type: "stfu", + Params: map[string]string{}, + }, + }, + }, + }, + } + got := BuildVSchema(&schema) + err := got.Keyspaces["sharded"].Error + _, ok := err.(*MissingSubvindexError) + + if err == nil || !ok { + t.Errorf("BuildVSchema: incorrect error returned: got %v, want MissingSubvindexError", err) + } +} + +func TestVSchemaRetryVindexWithNonRetryableError(t *testing.T) { + stfueName1 := "stfue1" + stfueName2 := "stfue2" + stfueShouldErr[stfueName1] = true + stfueShouldErr[stfueName2] = true + stfueErrors[stfueName1] = errors.New("stfue error") + stfueErrors[stfueName2] = errors.New("stfue error") + + schema := vschemapb.SrvVSchema{ + Keyspaces: map[string]*vschemapb.Keyspace{ + "sharded": { + Sharded: true, + Vindexes: map[string]*vschemapb.Vindex{ + stfueName1: { + Type: "stfue", + Params: map[string]string{}, + }, + stfueName2: { + Type: "stfue", + Params: map[string]string{}, + }, + }, + }, + }, + } + + got := BuildVSchema(&schema) + err := got.Keyspaces["sharded"].Error + if err == nil || err.Error() != "stfue error" { + t.Errorf("BuildVSchema: incorrect error returned: got %v, want %v", err, "stfue error") + } +} + func TestVSchemaPinned(t *testing.T) { good := vschemapb.SrvVSchema{ Keyspaces: map[string]*vschemapb.Keyspace{