Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance for schemas with many fields #140

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 61 additions & 43 deletions definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"reflect"
"regexp"

"github.com/graphql-go/graphql/gqlerrors"
"github.com/graphql-go/graphql/language/ast"
"golang.org/x/net/context"
)
Expand Down Expand Up @@ -266,20 +267,20 @@ func NewScalar(config ScalarConfig) *Scalar {
st.PrivateName = config.Name
st.PrivateDescription = config.Description

err = invariant(
err = invariantf(
config.Serialize != nil,
fmt.Sprintf(`%v must provide "serialize" function. If this custom Scalar is `+
`%v must provide "serialize" function. If this custom Scalar is `+
`also used as an input type, ensure "parseValue" and "parseLiteral" `+
`functions are also provided.`, st),
`functions are also provided.`, st,
)
if err != nil {
st.err = err
return st
}
if config.ParseValue != nil || config.ParseLiteral != nil {
err = invariant(
err = invariantf(
config.ParseValue != nil && config.ParseLiteral != nil,
fmt.Sprintf(`%v must provide both "parseValue" and "parseLiteral" functions.`, st),
`%v must provide both "parseValue" and "parseLiteral" functions.`, st,
)
if err != nil {
st.err = err
Expand Down Expand Up @@ -472,26 +473,26 @@ func (gt *Object) Error() error {
}

func defineInterfaces(ttype *Object, interfaces []*Interface) ([]*Interface, error) {
ifaces := []*Interface{}
ifaces := make([]*Interface, 0, len(interfaces))

if len(interfaces) == 0 {
return ifaces, nil
}
for _, iface := range interfaces {
err := invariant(
err := invariantf(
iface != nil,
fmt.Sprintf(`%v may only implement Interface types, it cannot implement: %v.`, ttype, iface),
`%v may only implement Interface types, it cannot implement: %v.`, ttype, iface,
)
if err != nil {
return ifaces, err
}
if iface.ResolveType != nil {
err = invariant(
err = invariantf(
iface.ResolveType != nil,
fmt.Sprintf(`Interface Type %v does not provide a "resolveType" function `+
`Interface Type %v does not provide a "resolveType" function `+
`and implementing Type %v does not provide a "isTypeOf" `+
`function. There is no way to resolve this implementing type `+
`during execution.`, iface, ttype),
`during execution.`, iface, ttype,
)
if err != nil {
return ifaces, err
Expand All @@ -507,9 +508,9 @@ func defineFieldMap(ttype Named, fields Fields) (FieldDefinitionMap, error) {

resultFieldMap := FieldDefinitionMap{}

err := invariant(
err := invariantf(
len(fields) > 0,
fmt.Sprintf(`%v fields must be an object with field names as keys or a function which return such an object.`, ttype),
`%v fields must be an object with field names as keys or a function which return such an object.`, ttype,
)
if err != nil {
return resultFieldMap, err
Expand All @@ -519,9 +520,9 @@ func defineFieldMap(ttype Named, fields Fields) (FieldDefinitionMap, error) {
if field == nil {
continue
}
err = invariant(
err = invariantf(
field.Type != nil,
fmt.Sprintf(`%v.%v field type must be Output Type but got: %v.`, ttype, fieldName, field.Type),
`%v.%v field type must be Output Type but got: %v.`, ttype, fieldName, field.Type,
)
if err != nil {
return resultFieldMap, err
Expand All @@ -541,22 +542,22 @@ func defineFieldMap(ttype Named, fields Fields) (FieldDefinitionMap, error) {
DeprecationReason: field.DeprecationReason,
}

fieldDef.Args = []*Argument{}
fieldDef.Args = make([]*Argument, 0, len(field.Args))
for argName, arg := range field.Args {
err := assertValidName(argName)
if err != nil {
return resultFieldMap, err
}
err = invariant(
err = invariantf(
arg != nil,
fmt.Sprintf(`%v.%v args must be an object with argument names as keys.`, ttype, fieldName),
`%v.%v args must be an object with argument names as keys.`, ttype, fieldName,
)
if err != nil {
return resultFieldMap, err
}
err = invariant(
err = invariantf(
arg.Type != nil,
fmt.Sprintf(`%v.%v(%v:) argument type must be Input Type but got: %v.`, ttype, fieldName, argName, arg.Type),
`%v.%v(%v:) argument type must be Input Type but got: %v.`, ttype, fieldName, argName, arg.Type,
)
if err != nil {
return resultFieldMap, err
Expand Down Expand Up @@ -823,30 +824,30 @@ func NewUnion(config UnionConfig) *Union {
objectType.PrivateDescription = config.Description
objectType.ResolveType = config.ResolveType

err = invariant(
err = invariantf(
len(config.Types) > 0,
fmt.Sprintf(`Must provide Array of types for Union %v.`, config.Name),
`Must provide Array of types for Union %v.`, config.Name,
)
if err != nil {
objectType.err = err
return objectType
}
for _, ttype := range config.Types {
err := invariant(
err := invariantf(
ttype != nil,
fmt.Sprintf(`%v may only contain Object types, it cannot contain: %v.`, objectType, ttype),
`%v may only contain Object types, it cannot contain: %v.`, objectType, ttype,
)
if err != nil {
objectType.err = err
return objectType
}
if objectType.ResolveType == nil {
err = invariant(
err = invariantf(
ttype.IsTypeOf != nil,
fmt.Sprintf(`Union Type %v does not provide a "resolveType" function `+
`Union Type %v does not provide a "resolveType" function `+
`and possible Type %v does not provide a "isTypeOf" `+
`function. There is no way to resolve this possible type `+
`during execution.`, objectType, ttype),
`during execution.`, objectType, ttype,
)
if err != nil {
objectType.err = err
Expand Down Expand Up @@ -945,21 +946,21 @@ func NewEnum(config EnumConfig) *Enum {
return gt
}
func (gt *Enum) defineEnumValues(valueMap EnumValueConfigMap) ([]*EnumValueDefinition, error) {
values := []*EnumValueDefinition{}
values := make([]*EnumValueDefinition, 0, len(valueMap))

err := invariant(
err := invariantf(
len(valueMap) > 0,
fmt.Sprintf(`%v values must be an object with value names as keys.`, gt),
`%v values must be an object with value names as keys.`, gt,
)
if err != nil {
return values, err
}

for valueName, valueConfig := range valueMap {
err := invariant(
err := invariantf(
valueConfig != nil,
fmt.Sprintf(`%v.%v must refer to an object with a "value" key `+
`representing an internal value but got: %v.`, gt, valueName, valueConfig),
`%v.%v must refer to an object with a "value" key `+
`representing an internal value but got: %v.`, gt, valueName, valueConfig,
)
if err != nil {
return values, err
Expand Down Expand Up @@ -1130,9 +1131,9 @@ func (gt *InputObject) defineFieldMap() InputObjectFieldMap {
}
resultFieldMap := InputObjectFieldMap{}

err := invariant(
err := invariantf(
len(fieldMap) > 0,
fmt.Sprintf(`%v fields must be an object with field names as keys or a function which return such an object.`, gt),
`%v fields must be an object with field names as keys or a function which return such an object.`, gt,
)
if err != nil {
gt.err = err
Expand All @@ -1147,9 +1148,9 @@ func (gt *InputObject) defineFieldMap() InputObjectFieldMap {
if err != nil {
continue
}
err = invariant(
err = invariantf(
fieldConfig.Type != nil,
fmt.Sprintf(`%v.%v field type must be Input Type but got: %v.`, gt, fieldName, fieldConfig.Type),
`%v.%v field type must be Input Type but got: %v.`, gt, fieldName, fieldConfig.Type,
)
if err != nil {
gt.err = err
Expand Down Expand Up @@ -1205,7 +1206,7 @@ type List struct {
func NewList(ofType Type) *List {
gl := &List{}

err := invariant(ofType != nil, fmt.Sprintf(`Can only create List of a Type but got: %v.`, ofType))
err := invariantf(ofType != nil, `Can only create List of a Type but got: %v.`, ofType)
if err != nil {
gl.err = err
return gl
Expand Down Expand Up @@ -1259,7 +1260,7 @@ func NewNonNull(ofType Type) *NonNull {
gl := &NonNull{}

_, isOfTypeNonNull := ofType.(*NonNull)
err := invariant(ofType != nil && !isOfTypeNonNull, fmt.Sprintf(`Can only create NonNull of a Nullable Type but got: %v.`, ofType))
err := invariantf(ofType != nil && !isOfTypeNonNull, `Can only create NonNull of a Nullable Type but got: %v.`, ofType)
if err != nil {
gl.err = err
return gl
Expand All @@ -1285,9 +1286,26 @@ func (gl *NonNull) Error() error {

var NameRegExp, _ = regexp.Compile("^[_a-zA-Z][_a-zA-Z0-9]*$")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this variable superfluous now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and no. It's not used in the lib anymore, but it's exposed and part of the public API therefore I left it in because I didn't want to break backwards compatibility.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, though I'm not sure exporting this variable was deliberate. Maybe it could be marked as deprecated or simply removed anyway (current version is sub 1.0 after all).


func isNumber(r rune) bool {
return '0' <= r && r <= '9'
}

func isLetter(r rune) bool {
return ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z')
}

func assertValidName(name string) error {
return invariant(
NameRegExp.MatchString(name),
fmt.Sprintf(`Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%v" does not.`, name),
)
if len(name) == 0 {
return gqlerrors.NewFormattedError(
fmt.Sprintf(`Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%v" does not.`, name))
}

for i, r := range name {
if !(r == '_' || isLetter(r) || (i != 0 && isNumber(r))) {
return gqlerrors.NewFormattedError(
fmt.Sprintf(`Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%v" does not.`, name))
}
}

return nil
}
20 changes: 8 additions & 12 deletions executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,6 @@ func collectFields(p CollectFieldsParams) map[string][]*ast.Field {
continue
}
name := getFieldEntryKey(selection)
if _, ok := fields[name]; !ok {
fields[name] = []*ast.Field{}
}
fields[name] = append(fields[name], selection)
case *ast.InlineFragment:

Expand Down Expand Up @@ -635,9 +632,7 @@ func completeValue(eCtx *ExecutionContext, returnType Type, fieldASTs []*ast.Fie
}

// Not reachable. All possible output types have been considered.
err := invariant(false,
fmt.Sprintf(`Cannot complete value of unexpected type "%v."`, returnType),
)
err := invariantf(false, `Cannot complete value of unexpected type "%v."`, returnType)
if err != nil {
panic(gqlerrors.FormatError(err))
}
Expand All @@ -663,8 +658,9 @@ func completeAbstractValue(eCtx *ExecutionContext, returnType Abstract, fieldAST
runtimeType = defaultResolveTypeFn(resolveTypeParams, returnType)
}

err := invariant(runtimeType != nil,
fmt.Sprintf(`Could not determine runtime type of value "%v" for field %v.%v.`, result, info.ParentType, info.FieldName),
err := invariantf(runtimeType != nil,
`Could not determine runtime type of value "%v" for field %v.%v.`,
result, info.ParentType, info.FieldName,
)
if err != nil {
panic(err)
Expand Down Expand Up @@ -746,17 +742,17 @@ func completeListValue(eCtx *ExecutionContext, returnType *List, fieldASTs []*as
if info.ParentType != nil {
parentTypeName = info.ParentType.Name()
}
err := invariant(
err := invariantf(
resultVal.IsValid() && resultVal.Type().Kind() == reflect.Slice,
fmt.Sprintf("User Error: expected iterable, but did not find one "+
"for field %v.%v.", parentTypeName, info.FieldName),
"User Error: expected iterable, but did not find one "+
"for field %v.%v.", parentTypeName, info.FieldName,
)
if err != nil {
panic(gqlerrors.FormatError(err))
}

itemType := returnType.OfType
completedResults := []interface{}{}
completedResults := make([]interface{}, 0, resultVal.Len())
for i := 0; i < resultVal.Len(); i++ {
val := resultVal.Index(i).Interface()
completedItem := completeValueCatchingError(eCtx, itemType, fieldASTs, info, val)
Expand Down
Loading