-
Notifications
You must be signed in to change notification settings - Fork 843
Accessing list of request fields #157
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
Comments
I have a same problem here, was just looking for a solution and found this Open Issue. Is there any way to find out the list of requested field to optimise the query? Cheers |
Look what I found, check this one: #125 |
Thanks for sharing. It looks like that might work. It frustrates me that this should be such a common requirement and yet there seems to be very few other people interested in sharing a solution for it. Let me know if it works or doesn't work. |
I had to eventually implement it differently, but while I was working on that solution, I managed to get the field names. It really depends on which Resolve function you implement the code in. It was not easy for me to walk down in the tree and find the selection set. Here is the link to the source code that I fixed differently tonight. https://github.com/microbusinesses/AddressService/blob/master/endpoint/endpoints.go The HTTP query could look like this: |
OK, I finally found the way to do it using the solution provided in: Note the line in the import is the actual trick. Both golang and graphql-go has thei r implementation of Field struct. if you use what provided in #125, gofmt import the golang implementation by default. You need to make sure you import the right field in the import. I will update my AddressService implementation soon. import ( func getSelectedFields(selectionPath []string, |
Job done, look at this to find out how you should implement it https://github.com/microbusinesses/AddressService/blob/master/endpoint/endpoints.go |
Just an FYI for anyone reading this, I used the same solution, and it breaks if the client uses fragments:
I'll post here when I find a solution to this. (I'm quite surprised there aren't more users of this library requesting this feature.) |
Here is my updated recursive solution to account for fragments. I did modify the original signature and skipped passing in a I also added errors to handle unexpected scenarios, since this is production code. :)
(Edited to correct my original misunderstanding of how |
Edit: doesn't work for in-line fragments, sigh. Not fixing it now to do so since my team isn't using them. |
@kellyellis: Seems working fine for fragments in my test. |
@mortezaalizadeh and @kellyellis, thanks so much for your contributions. This is exactly what I'm looking for. Very surprised it's not included as a convenience in the lib, tbh. It's such a common/obvious requirement to know which fields have been requested, so that they can be factored into an SQL builder/query. I'm guessing the lack of references to this issue/commentary means most devs are just doing a |
Thanks, agree that this should be considered for the library. Major waste to request fields from the DB that we'll later just throw away. |
To pile onto this, in addition to selecting unused fields from the DB, there are additional costs when using microservices. I'm running into a case where I can retrieve an entire record from a remote service, but some of the record's fields may cause the request to take additional time (in this case, the fields are encrypted and need to be decrypted). I realize a solution would be to implement a GraphQL endpoint in that microservice and stitch it together, but I'd rather not go through the overhead. It would be great to be able to have access to the fields in order to employ some other method of only requesting data when required. I'll try out the solutions above and see how that works out for now though! |
@kellyellis thanks for the function I modified it to account for nested selects func getSelectedFields(params graphql.ResolveParams) (map[string]interface{}, error) {
fieldASTs := params.Info.FieldASTs
if len(fieldASTs) == 0 {
return nil, fmt.Errorf("getSelectedFields: ResolveParams has no fields")
}
return selectedFieldsFromSelections(params, fieldASTs[0].SelectionSet.Selections)
}
func selectedFieldsFromSelections(params graphql.ResolveParams, selections []ast.Selection) (selected map[string]interface{}, err error) {
selected = map[string]interface{}{}
for _, s := range selections {
switch s := s.(type) {
case *ast.Field:
if s.SelectionSet == nil {
selected[s.Name.Value] = true
} else {
selected[s.Name.Value], err = selectedFieldsFromSelections(params, s.SelectionSet.Selections)
if err != nil {
return
}
}
case *ast.FragmentSpread:
n := s.Name.Value
frag, ok := params.Info.Fragments[n]
if !ok {
err = fmt.Errorf("getSelectedFields: no fragment found with name %v", n)
return
}
selected[s.Name.Value], err = selectedFieldsFromSelections(params, frag.GetSelectionSet().Selections)
if err != nil {
return
}
default:
err = fmt.Errorf("getSelectedFields: found unexpected selection type %v", s)
return
}
}
return
} |
It's amazing that this issue is open after four years with no good solution. @chris-ramon? From what I can tell, The AST is useless because the AST only says something about the incoming query, but of course all of those parts map to the underlying schema declared with For example, let's say you have a basic schema: type Person struct {
ID string
Name string
EmployerID string
}
type Company struct { ... }
companyType := ...
personType := graphql.NewObject(graphql.ObjectConfig{
Name: "Person",
Fields: graphql.Fields{
"id": &graphql.Field{Type: graphql.String},
"name": &graphql.Field{Type: graphql.String},
"employer": &graphql.Field{
Type: companyType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
person := p.Value.(*Person)
return findCompany(person.EmployerID)
},
},
}
})
graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "RootQuery",
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{Type: graphql.String},
},
Fields: graphql.Fields{
"person": &graphql.Field{
Type: personType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
columns := findColumns(p)
var person Person
// Database pseudo-code:
if err := db.Query("select "+columns+" from persons where id = ?", p.Args["id"], &person); err != nil {
return nil, err
}
return &person, nil
},
},
},
}),
}) Here, If you have a query such as this: query {
person(id: "123") {
name, employer { id }
}
} ...the challenge here is for the We can fiddle with But surely |
What's the correct way to determine what data needs to be constructed and returned? Obviously, some fields might have a higher cost to populate than others. This is a resolve function:
This is
ResolveParams
:The only immediately interesting field here is
ResolveInfo
, but it, too, doesn't seem to provide the requested fields:Thanks.
The text was updated successfully, but these errors were encountered: