-
Notifications
You must be signed in to change notification settings - Fork 46
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
refactor: GQL responses #2872
refactor: GQL responses #2872
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Briefly looked over. Looks good so far.
internal/planner/mapper/mapper.go
Outdated
operation.Mutations = append(operation.Mutations, m) | ||
operation.DocumentMapping.Add(i, m.Name) | ||
operation.DocumentMapping.SetChildAt(i, m.DocumentMapping) | ||
operation.DocumentMapping.RenderKeys = append(operation.DocumentMapping.RenderKeys, renderKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: the bodies of all 3 cases are very similar. Is there a way to extract common functionality?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved some of this to a function and reduced most of the duplicate lines
internal/planner/operation.go
Outdated
return map[string]any{}, nil | ||
|
||
case request.ExecuteExplain: | ||
return map[string]any{}, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: is it supposed to be empty?
Did you add explain tests that assert this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super familiar with the planner package. Is this supposed to return the operations that this node executes? It does appear in the existing explain tests correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you can omit the Explain
method entirely, since its an optional interface. I don't know if there is any context that this planNode can add to the existing plan, so simply not including it in the explain system is an option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As @jsimnz pointed out, making a node explainable is optional. Moreover, MultiNode
types are not / don't need to be explainable but we use them to wrap the children under them so the explain graph builder I believe handles that case. But when/if you make operationNode
unexplainable don't forget to leave the added line in the tests/integration/explain.go
file because the debug explain graph should still need it to assert it properly in the testing framework.
An example of this is parallelNode
has no Explain
method, yet still shows in the explain graph to wrap the children under it.
} | ||
} | ||
|
||
n.isDone = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: do you really need this flag? It's being set only in Init()
. Next()
is not supposed to be called anyway before Init()
and it's not multi-threaded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried removing it, but the return value was always incorrect. I believe we need it so that the operation is able to run at least once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick/todo: The planNode interface and it's consuption dictates that Init
can be called multiple times, and that it's state should be reset by the first call (before doing it's thing again). I can't think why this node would have an instance Init'ed multiple times, but it is kind of incorrect atm, and just last week we had a production issue caused by this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be fixed now. I'm assuming you were referencing the currentValue
not being reset.
internal/planner/planner.go
Outdated
func (p *Planner) MakePlan(req *request.Request) (planNode, error) { | ||
var operation *request.OperationDefinition | ||
if len(req.Mutations) > 0 { | ||
operation = req.Mutations[0] // @todo: handle multiple mutation operation statements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo: is this todo going to be resolved within this PR?
Otherwise please create a ticket and link it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied these over from another function. I'll double check if there is an existing issue or create one if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found it: #1395
{ | ||
"name": "Painted House", | ||
Results: map[string]any{ | ||
"Author": []map[string]any{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: you did it all by hand? Wow. That must have been tedious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not gonna lie, It was a lot of work, but it had to be done.
tests/integration/utils2.go
Outdated
@@ -1906,6 +1944,22 @@ func assertRequestResults( | |||
return false | |||
} | |||
|
|||
func convertToArrayOfMaps(t testing.TB, value any) []map[string]any { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: these (and others) are changes from my PR that was merged. How did it end up here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I copied these on accident when I was mid-refactor and didn't realize my local branch was behind. When I merge the develop branch these should appear as unchanged.
@@ -57,6 +57,7 @@ var ( | |||
"valuesNode": {}, | |||
"viewNode": {}, | |||
"lensNode": {}, | |||
"operationNode": {}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: Thanks for not forgetting this :P (easy to miss if the operationNode
was not explainable node).
const operationNodeKind string = "operationNode" | ||
|
||
// operationNode is the top level node for operations with | ||
// one or more child selections, such as queries or mutations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: I understand what you mean by operation
but wonder if there is a better name for this node (I don't have any good ideas atm), the reason I say this is because every node is essentially an "instruction/operation step" so it seems a bit vague.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree the name could be improved. It should be fairly easy to rename if we find a better name.
Huge praise for the work you did on this, it must have been a pain to make all these changes. I do have a preference to merge this ASAP if everyone is happy, otherwise maintaining this would not be so fun haha |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #2872 +/- ##
===========================================
- Coverage 79.49% 79.44% -0.05%
===========================================
Files 323 325 +2
Lines 24695 24778 +83
===========================================
+ Hits 19630 19683 +53
- Misses 3660 3680 +20
- Partials 1405 1415 +10
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 14 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! Just a handful of small localized comments from me before merge.
internal/db/request.go
Outdated
|
||
res.GQL.Data = results | ||
if len(results) > 0 { | ||
res.GQL.Data = results[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question/suggestion: Why is results still an array? Can we return just a map[string]any
now instead?
Same question/suggestion for RunSelection
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I've updated both to return maps.
} | ||
|
||
for i, s := range operationRequest.Selections { | ||
switch t := s.(type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: The default case is not handled, it might be better to return an error instead of quietly ignoring it - I believe it could only happen if we mess up in the future, but it could save some head-scratching even if it means a line or two of dead code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a default case with an error.
internal/planner/mapper/operation.go
Outdated
func (o *Operation) addSelection(i int, f request.Field, s Select) { | ||
renderKey := core.RenderKey{Index: s.Index} | ||
if f.Alias.HasValue() { | ||
renderKey.Key = f.Alias.Value() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo: Please add an operation alias test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests/integration/query/simple/with_operation_alias_test.go
internal/planner/mapper/operation.go
Outdated
// The request.Field is used as the key for the core.RenderKey and the Select | ||
// document mapping is added as a child field. | ||
func (o *Operation) addSelection(i int, f request.Field, s Select) { | ||
renderKey := core.RenderKey{Index: s.Index} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo: There is a function getRenderKey
that handles this, I would prefer that we don't duplicate that logic as it should remain consistent across all places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/planner/operation.go
Outdated
// Operation creates a new operationNode using the given Selects. | ||
func (p *Planner) Operation(operation *mapper.Operation) (*operationNode, error) { | ||
var children []planNode | ||
var childIndexes []int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: I think a map[int]planNode
would be safer and a little easier to read. Including on the operationNode
type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/planner/operation.go
Outdated
} | ||
|
||
if len(children) == 0 { | ||
return nil, ErrOperationDefinitionMissingSelection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Is this spec-compliant? A selection set must always be provided?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't see anything in the spec about empty selections. I've removed this error since it should be fine to return empty results.
internal/planner/operations.go
Outdated
@@ -34,9 +34,11 @@ var ( | |||
_ planNode = (*valuesNode)(nil) | |||
_ planNode = (*viewNode)(nil) | |||
_ planNode = (*lensNode)(nil) | |||
_ planNode = (*operationNode)(nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: MultiNode
implements/extends planNode
, so this line is not required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
internal/planner/planner.go
Outdated
// Note: Caller is responsible to call the `Close()` method to free the allocated | ||
// resources of the returned plan. | ||
// | ||
// @TODO {defradb/issues/368}: Test this exported function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo: This todo is incorrect - this is not a public function and does not require direct testing - if it is not used by us it can be deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
Relevant issue(s)
Resolves #2869
Description
This PR makes our GQL response types spec compliant. It also enables running multiple selections within
queries
andmutations
.Tasks
How has this been tested?
make test
Specify the platform(s) on which this was tested: