-
Notifications
You must be signed in to change notification settings - Fork 43
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
feat: Allow summation of aggregates #341
Conversation
} | ||
|
||
// Returns true if the value to be summed is a float, otherwise false. | ||
func (p *Planner) isValueFloat( |
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.
This will likely move when adding other aggregates, but it can live here for now (count cant act on an aggregate and so wont need 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 don't think this func needs to be a method on the planner
object. From what I can tell, the only reason it is, is because the getSourceField
is on the planner.
But getSourceField
also probably doesn't need to be on planner, which calls getSourceProperty
which doens't call anything from the planner
object.
So I suggest, isValueFloat
, getSourceField
, and getSourceProperty
all get lifted off the planner.
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.
In previous PRs you have requested the opposite and asked for unscoped functions to be scoped to the planner! Is a weakness of Go IMO that it has no private functions, and atm I would really rather not have other bits of code call these functions. Planner is a pretty weak scope, but at least they are not global...
As noted, it is likely that these functions will become shared code, but they are not written/tested as such at the moment and it would be wasteful to put that effort in now
Codecov Report
@@ Coverage Diff @@
## develop #341 +/- ##
===========================================
+ Coverage 64.98% 65.05% +0.06%
===========================================
Files 80 80
Lines 9183 9251 +68
===========================================
+ Hits 5968 6018 +50
- Misses 2602 2618 +16
- Partials 613 615 +2
|
8f0ebf5
to
08bef70
Compare
Benchmark ResultsSummary
✅ See Better Results...
❌ See Worse Results...
✨ See Unchanged Results...
🐋 See Full Results...
|
5e3637e
to
5164d0e
Compare
Benchmark ResultsSummary
✅ See Better Results...
❌ See Worse Results...
✨ See Unchanged Results...
🐋 See Full Results...
|
Benchmark ResultsSummary
✅ See Better Results...
❌ See Worse Results...
✨ See Unchanged Results...
🐋 See Full Results...
|
query/graphql/planner/sum.go
Outdated
if _, isAggregate := parser.Aggregates[sourceProperty]; isAggregate { | ||
var sourceField *parser.Field | ||
for _, field := range parent.GetSelections() { | ||
if field.GetName() == source[0] { | ||
for _, childField := range field.(*parser.Select).Fields { | ||
if childField.GetAlias() == sourceProperty { | ||
sourceField = childField.(*parser.Field) | ||
} | ||
} | ||
} | ||
} | ||
sourceProperty = sourceField.GetName() | ||
} | ||
|
||
return sourceProperty |
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.
Less nesting this way:
if _, isAggregate := parser.Aggregates[sourceProperty]; isAggregate { | |
var sourceField *parser.Field | |
for _, field := range parent.GetSelections() { | |
if field.GetName() == source[0] { | |
for _, childField := range field.(*parser.Select).Fields { | |
if childField.GetAlias() == sourceProperty { | |
sourceField = childField.(*parser.Field) | |
} | |
} | |
} | |
} | |
sourceProperty = sourceField.GetName() | |
} | |
return sourceProperty | |
if _, isAggregate := parser.Aggregates[sourceProperty]; !isAggregate { | |
return sourceProperty | |
} | |
var sourceField *parser.Field | |
for _, field := range parent.GetSelections() { | |
if field.GetName() != source[0] { | |
continue | |
} | |
for _, childField := range field.(*parser.Select).Fields { | |
if childField.GetAlias() == sourceProperty { | |
sourceField = childField.(*parser.Field) | |
} | |
} | |
} | |
return sourceField.GetName() |
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.
There is less indentation, but I think you lose in terms of content structure - the if isAggregate
block is a second class citizen here - an addition to the normal code flow (and so are the inner ifs). Returning early changes the emphasis (the primary code path being shoved into an early return), and makes it easier to introduce bugs when adding to the primary code path (you need to be mindful of the early return instead of being able to add to any line in the function. But will review properly and get back to you.
- Review isAggregate block
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.
On review I still prefer the existing, but also spotted that I missed a could of break
s that have now been added. Again let me know if you strongly object and I can move things as you suggested.
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.
Reasoning makes sense, in that case would it be possible to use helper function / method to knockdown the cyclomatic complexity. i.e. maybe a function just for the inner most for
. However I am guessing you might have to pass a pointer in the helper to mutate and grab the state ( in that case dw about it ). Otherwise looks good!
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.
My bad - this is the helper function you are asking for - I have tweaked it slightly to return early instead of the mutation and control-flow statements...
151c817
to
2b9da2d
Compare
Benchmark ResultsSummary
✅ See Better Results...
❌ See Worse Results...
✨ See Unchanged Results...
🐋 See Full Results...
|
Benchmark ResultsSummary
✅ See Better Results...
❌ See Worse Results...
✨ See Unchanged Results...
🐋 See Full Results...
|
sourceInfo *sourceInfo, | ||
field *parser.Field, | ||
parent *parser.Select, | ||
) (*sumNode, error) { |
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.
Can we keep this on a single line? Or did we decide to format function declarations that way past a certain number of characters?
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.
linter said no - there is a char limit that didnt like it when it was on one line. It might now fit as the limit was expanded slightly, but I wasn't bothered enough to see if it does (apart from Golang forcing ) (*sumNode, ...) {
to have zero indentation, it's probably 50-50 which is better here and different devs will have different opinions.
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.
If the linter doesn't complain, then it's okay to follow whichever way seems reasonable to the developer for stylistic things like these. If there are advantages to formatting a certain way, then we can enforce it through linters or other tooling in the future.
query/graphql/planner/sum.go
Outdated
break | ||
} | ||
} | ||
sourceProperty = sourceField.GetName() |
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.
What happens if sourceField
is still the zero value here ( nil
I am guessing )?
Inother words, if the inner most if above isn't hit and the assignment doesn't happen: sourceField = childField.(*parser.Field)
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.
It should be impossible for that to happen in the code you commented on (but is non-obvious to readers), I made another change here that removes this line though
2b9da2d
to
ced4c83
Compare
Benchmark ResultsSummary
✅ See Better Results...
❌ See Worse Results...
✨ See Unchanged Results...
🐋 See Full Results...
|
Benchmark ResultsSummary
✅ See Better Results...
❌ See Worse Results...
✨ See Unchanged Results...
🐋 See Full Results...
|
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.
Adding this comment now about some of the planner funcs. Adding more comments soon :D
} | ||
|
||
// Returns true if the value to be summed is a float, otherwise false. | ||
func (p *Planner) isValueFloat( |
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 don't think this func needs to be a method on the planner
object. From what I can tell, the only reason it is, is because the getSourceField
is on the planner.
But getSourceField
also probably doesn't need to be on planner, which calls getSourceProperty
which doens't call anything from the planner
object.
So I suggest, isValueFloat
, getSourceField
, and getSourceProperty
all get lifted off the planner.
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.
Few random thoughts. All looks G in general, short of my previous suggestion about moving methods off planner.
If you disagree, let me know, and I'll mark this as approved, for now ill set with "Request Changes"
sourceProperty := source[1] | ||
if _, isAggregate := parser.Aggregates[sourceProperty]; isAggregate { | ||
for _, field := range parent.GetSelections() { | ||
if field.GetName() == source[0] { | ||
for _, childField := range field.(*parser.Select).Fields { | ||
if childField.GetAlias() == sourceProperty { | ||
return childField.(*parser.Field).GetName() | ||
} | ||
} | ||
} | ||
} | ||
} |
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.
Lets add just a breif description to this (beyond the method docstring) since this is 5 neested deep, to the dev who doesn't know what this does on first read, we should prob just give them a helping hand :)
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.
What would that comment say that is not already in the the func comment or obvious from the code? Sure it is 5 deep, but is quite simple and contains only 2 code paths (especially considering that source
will be renamed/restructured in the very near future
4468f07
to
2a41039
Compare
Benchmark ResultsSummary
✅ See Better Results...
❌ See Worse Results...
✨ See Unchanged Results...
🐋 See Full Results...
|
Approval given over discord I think (I hope this button doesnt remove his comments)
* Remove previously done todo * Refactor source collection var * Refactor source property * Remove strange variable aliasing * Refactor isFloat property * Add summation of aggregates support * Remove else clause (PR request)
Closes #149
Adds support for the summation of aggregates. Also tested manually to check it play nicely with the Altair client.