diff --git a/example/federation/accounts/graph/generated/federation.go b/example/federation/accounts/graph/generated/federation.go index 1d7ee7ca52..11a6382fcb 100644 --- a/example/federation/accounts/graph/generated/federation.go +++ b/example/federation/accounts/graph/generated/federation.go @@ -33,7 +33,39 @@ func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime. func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) []fedruntime.Entity { list := make([]fedruntime.Entity, len(representations)) - resolveEntity := func(ctx context.Context, i int, rep map[string]interface{}) (err error) { + + repsMap := map[string]struct { + i []int + r []map[string]interface{} + }{} + + // We group entities by typename so that we can parallelize their resolution. + // This is particularly helpful when there are entity groups in multi mode. + buildRepresentationGroups := func(reps []map[string]interface{}) { + for i, rep := range reps { + typeName, ok := rep["__typename"].(string) + if !ok { + // If there is no __typename, we just skip the representation; + // we just won't be resolving these unknown types. + ec.Error(ctx, errors.New("__typename must be an existing string")) + continue + } + + _r := repsMap[typeName] + _r.i = append(_r.i, i) + _r.r = append(_r.r, rep) + repsMap[typeName] = _r + } + } + + isMulti := func(typeName string) bool { + switch typeName { + default: + return false + } + } + + resolveEntity := func(ctx context.Context, typeName string, rep map[string]interface{}, idx []int, i int) (err error) { // we need to do our own panic handling, because we may be called in a // goroutine, where the usual panic handling can't catch us defer func() { @@ -42,10 +74,6 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati } }() - typeName, ok := rep["__typename"].(string) - if !ok { - return errors.New("__typename must be an existing string") - } switch typeName { case "EmailHost": @@ -60,7 +88,7 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return err } - list[i] = entity + list[idx[i]] = entity return nil case "User": @@ -75,7 +103,7 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return err } - list[i] = entity + list[idx[i]] = entity return nil default: @@ -83,30 +111,71 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati } } - // if there are multiple entities to resolve, parallelize (similar to - // graphql.FieldSet.Dispatch) - switch len(representations) { + resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { + // we need to do our own panic handling, because we may be called in a + // goroutine, where the usual panic handling can't catch us + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + } + }() + + switch typeName { + + default: + return errors.New("unknown type: " + typeName) + } + + return nil + } + + resolveEntityGroup := func(typeName string, reps []map[string]interface{}, idx []int) { + if isMulti(typeName) { + err := resolveManyEntities(ctx, typeName, reps, idx) + if err != nil { + ec.Error(ctx, err) + } + } else { + // if there are multiple entities to resolve, parallelize (similar to + // graphql.FieldSet.Dispatch) + var e sync.WaitGroup + e.Add(len(reps)) + for i, rep := range reps { + i, rep := i, rep + go func(i int, rep map[string]interface{}) { + err := resolveEntity(ctx, typeName, rep, idx, i) + if err != nil { + ec.Error(ctx, err) + } + e.Done() + }(i, rep) + } + e.Wait() + } + } + + buildRepresentationGroups(representations) + + switch len(repsMap) { case 0: return list case 1: - err := resolveEntity(ctx, 0, representations[0]) - if err != nil { - ec.Error(ctx, err) + for typeName, reps := range repsMap { + resolveEntityGroup(typeName, reps.r, reps.i) } return list default: var g sync.WaitGroup - g.Add(len(representations)) - for i, rep := range representations { - go func(i int, rep map[string]interface{}) { - err := resolveEntity(ctx, i, rep) - if err != nil { - ec.Error(ctx, err) - } + g.Add(len(repsMap)) + for typeName, reps := range repsMap { + go func(typeName string, reps []map[string]interface{}, idx []int) { + resolveEntityGroup(typeName, reps, idx) g.Done() - }(i, rep) + }(typeName, reps.r, reps.i) } g.Wait() return list } + + return list } diff --git a/example/federation/products/graph/generated/federation.go b/example/federation/products/graph/generated/federation.go index 410d467a82..0e416ab1a8 100644 --- a/example/federation/products/graph/generated/federation.go +++ b/example/federation/products/graph/generated/federation.go @@ -33,7 +33,39 @@ func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime. func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) []fedruntime.Entity { list := make([]fedruntime.Entity, len(representations)) - resolveEntity := func(ctx context.Context, i int, rep map[string]interface{}) (err error) { + + repsMap := map[string]struct { + i []int + r []map[string]interface{} + }{} + + // We group entities by typename so that we can parallelize their resolution. + // This is particularly helpful when there are entity groups in multi mode. + buildRepresentationGroups := func(reps []map[string]interface{}) { + for i, rep := range reps { + typeName, ok := rep["__typename"].(string) + if !ok { + // If there is no __typename, we just skip the representation; + // we just won't be resolving these unknown types. + ec.Error(ctx, errors.New("__typename must be an existing string")) + continue + } + + _r := repsMap[typeName] + _r.i = append(_r.i, i) + _r.r = append(_r.r, rep) + repsMap[typeName] = _r + } + } + + isMulti := func(typeName string) bool { + switch typeName { + default: + return false + } + } + + resolveEntity := func(ctx context.Context, typeName string, rep map[string]interface{}, idx []int, i int) (err error) { // we need to do our own panic handling, because we may be called in a // goroutine, where the usual panic handling can't catch us defer func() { @@ -42,10 +74,6 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati } }() - typeName, ok := rep["__typename"].(string) - if !ok { - return errors.New("__typename must be an existing string") - } switch typeName { case "Manufacturer": @@ -60,7 +88,7 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return err } - list[i] = entity + list[idx[i]] = entity return nil case "Product": @@ -79,7 +107,7 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return err } - list[i] = entity + list[idx[i]] = entity return nil default: @@ -87,30 +115,71 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati } } - // if there are multiple entities to resolve, parallelize (similar to - // graphql.FieldSet.Dispatch) - switch len(representations) { + resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { + // we need to do our own panic handling, because we may be called in a + // goroutine, where the usual panic handling can't catch us + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + } + }() + + switch typeName { + + default: + return errors.New("unknown type: " + typeName) + } + + return nil + } + + resolveEntityGroup := func(typeName string, reps []map[string]interface{}, idx []int) { + if isMulti(typeName) { + err := resolveManyEntities(ctx, typeName, reps, idx) + if err != nil { + ec.Error(ctx, err) + } + } else { + // if there are multiple entities to resolve, parallelize (similar to + // graphql.FieldSet.Dispatch) + var e sync.WaitGroup + e.Add(len(reps)) + for i, rep := range reps { + i, rep := i, rep + go func(i int, rep map[string]interface{}) { + err := resolveEntity(ctx, typeName, rep, idx, i) + if err != nil { + ec.Error(ctx, err) + } + e.Done() + }(i, rep) + } + e.Wait() + } + } + + buildRepresentationGroups(representations) + + switch len(repsMap) { case 0: return list case 1: - err := resolveEntity(ctx, 0, representations[0]) - if err != nil { - ec.Error(ctx, err) + for typeName, reps := range repsMap { + resolveEntityGroup(typeName, reps.r, reps.i) } return list default: var g sync.WaitGroup - g.Add(len(representations)) - for i, rep := range representations { - go func(i int, rep map[string]interface{}) { - err := resolveEntity(ctx, i, rep) - if err != nil { - ec.Error(ctx, err) - } + g.Add(len(repsMap)) + for typeName, reps := range repsMap { + go func(typeName string, reps []map[string]interface{}, idx []int) { + resolveEntityGroup(typeName, reps, idx) g.Done() - }(i, rep) + }(typeName, reps.r, reps.i) } g.Wait() return list } + + return list } diff --git a/example/federation/reviews/graph/generated/federation.go b/example/federation/reviews/graph/generated/federation.go index 55391a46ea..32389bc56a 100644 --- a/example/federation/reviews/graph/generated/federation.go +++ b/example/federation/reviews/graph/generated/federation.go @@ -33,7 +33,39 @@ func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime. func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) []fedruntime.Entity { list := make([]fedruntime.Entity, len(representations)) - resolveEntity := func(ctx context.Context, i int, rep map[string]interface{}) (err error) { + + repsMap := map[string]struct { + i []int + r []map[string]interface{} + }{} + + // We group entities by typename so that we can parallelize their resolution. + // This is particularly helpful when there are entity groups in multi mode. + buildRepresentationGroups := func(reps []map[string]interface{}) { + for i, rep := range reps { + typeName, ok := rep["__typename"].(string) + if !ok { + // If there is no __typename, we just skip the representation; + // we just won't be resolving these unknown types. + ec.Error(ctx, errors.New("__typename must be an existing string")) + continue + } + + _r := repsMap[typeName] + _r.i = append(_r.i, i) + _r.r = append(_r.r, rep) + repsMap[typeName] = _r + } + } + + isMulti := func(typeName string) bool { + switch typeName { + default: + return false + } + } + + resolveEntity := func(ctx context.Context, typeName string, rep map[string]interface{}, idx []int, i int) (err error) { // we need to do our own panic handling, because we may be called in a // goroutine, where the usual panic handling can't catch us defer func() { @@ -42,10 +74,6 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati } }() - typeName, ok := rep["__typename"].(string) - if !ok { - return errors.New("__typename must be an existing string") - } switch typeName { case "Product": @@ -64,7 +92,7 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return err } - list[i] = entity + list[idx[i]] = entity return nil case "User": @@ -89,7 +117,7 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati return err } - list[i] = entity + list[idx[i]] = entity return nil default: @@ -97,30 +125,71 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati } } - // if there are multiple entities to resolve, parallelize (similar to - // graphql.FieldSet.Dispatch) - switch len(representations) { + resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { + // we need to do our own panic handling, because we may be called in a + // goroutine, where the usual panic handling can't catch us + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + } + }() + + switch typeName { + + default: + return errors.New("unknown type: " + typeName) + } + + return nil + } + + resolveEntityGroup := func(typeName string, reps []map[string]interface{}, idx []int) { + if isMulti(typeName) { + err := resolveManyEntities(ctx, typeName, reps, idx) + if err != nil { + ec.Error(ctx, err) + } + } else { + // if there are multiple entities to resolve, parallelize (similar to + // graphql.FieldSet.Dispatch) + var e sync.WaitGroup + e.Add(len(reps)) + for i, rep := range reps { + i, rep := i, rep + go func(i int, rep map[string]interface{}) { + err := resolveEntity(ctx, typeName, rep, idx, i) + if err != nil { + ec.Error(ctx, err) + } + e.Done() + }(i, rep) + } + e.Wait() + } + } + + buildRepresentationGroups(representations) + + switch len(repsMap) { case 0: return list case 1: - err := resolveEntity(ctx, 0, representations[0]) - if err != nil { - ec.Error(ctx, err) + for typeName, reps := range repsMap { + resolveEntityGroup(typeName, reps.r, reps.i) } return list default: var g sync.WaitGroup - g.Add(len(representations)) - for i, rep := range representations { - go func(i int, rep map[string]interface{}) { - err := resolveEntity(ctx, i, rep) - if err != nil { - ec.Error(ctx, err) - } + g.Add(len(repsMap)) + for typeName, reps := range repsMap { + go func(typeName string, reps []map[string]interface{}, idx []int) { + resolveEntityGroup(typeName, reps, idx) g.Done() - }(i, rep) + }(typeName, reps.r, reps.i) } g.Wait() return list } + + return list } diff --git a/example/tools.go b/example/tools.go index d182ed9edc..4c124bccfc 100644 --- a/example/tools.go +++ b/example/tools.go @@ -1,3 +1,4 @@ +//go:build tools // +build tools package main