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

feat(spanner/spannertest): support AVG aggregation function #3286

Merged
42 changes: 42 additions & 0 deletions spanner/spannertest/funcs.go
Expand Up @@ -109,6 +109,48 @@ var aggregateFuncs = map[string]aggregateFunc{
return sum, typ, nil
},
},
"AVG": {
Eval: func(values []interface{}, typ spansql.Type) (interface{}, spansql.Type, error) {
if typ.Array || !(typ.Base == spansql.Int64 || typ.Base == spansql.Float64) {
return nil, spansql.Type{}, fmt.Errorf("AVG only supports arguments of INT64 or FLOAT64 type, not %s", typ.SQL())
}
if typ.Base == spansql.Int64 {
var sum int64
var n float64
for _, v := range values {
if v == nil {
continue
}
sum += v.(int64)
n++
}
if n == 0 {
// "Returns NULL if the input contains only NULLs".
return nil, typ, nil
}
// floating point handling is non-deterministic
// type of the result from INT64 input is Float64
dsymonds marked this conversation as resolved.
Show resolved Hide resolved
// https://cloud.google.com/spanner/docs/functions-and-operators#avg
return (float64(sum) / n), float64Type, nil
}
var sum float64
var n float64
for _, v := range values {
if v == nil {
continue
}
sum += v.(float64)
n++
}
if n == 0 {
// "Returns NULL if the input contains only NULLs".
return nil, typ, nil
}
// floating point handling is non-deterministic
// https://cloud.google.com/spanner/docs/functions-and-operators#avg
return (sum / n), typ, nil
},
},
}

func evalMinMax(name string, isMin bool, values []interface{}, typ spansql.Type) (interface{}, spansql.Type, error) {
Expand Down
36 changes: 36 additions & 0 deletions spanner/spannertest/integration_test.go
Expand Up @@ -889,6 +889,42 @@ func TestIntegration_ReadsAndQueries(t *testing.T) {
{int64(1), int64(25)}, // Jack(ID=1, Tenure=10), Sam(ID=3, Tenure=9), George(ID=5, Tenure=6)
},
},
{
dsymonds marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Ordering matters? Our implementation sorts by the GROUP BY key,
// but nothing documented seems to guarantee that.
`SELECT LastName, AVG(PointsScored) FROM PlayerStats GROUP BY LastName`,
nil,
[][]interface{}{
{"Adams", float64(3.5)},
{"Buchanan", float64(6.5)},
{"Coolidge", float64(1)},
},
},
{
// TODO: Ordering matters? Our implementation sorts by the GROUP BY key,
// but nothing documented seems to guarantee that.
`SELECT LastName, AVG(PointsScored) FROM PlayerStats GROUP BY LastName`,
nil,
[][]interface{}{
{"Adams", float64(3.5)},
{"Buchanan", float64(6.5)},
{"Coolidge", float64(1)},
},
},
{
`SELECT AVG(Height) FROM Staff WHERE ID <= 2`,
nil,
[][]interface{}{
{float64(1.84)},
},
},
{
`SELECT AVG(Height) FROM Staff WHERE ID >= 7`,
nil,
[][]interface{}{
{nil},
},
},
{
`SELECT MAX(Name) FROM Staff WHERE Name < @lim`,
map[string]interface{}{"lim": "Teal'c"},
Expand Down
1 change: 1 addition & 0 deletions spanner/spansql/keywords.go
Expand Up @@ -135,6 +135,7 @@ var funcs = map[string]bool{
"MAX": true,
"MIN": true,
"SUM": true,
"AVG": true,
Copy link
Contributor

Choose a reason for hiding this comment

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

keep these in alphabetical order.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't notice the order rule, thanks.
6445399


// Mathematical functions.
"ABS": true,
Expand Down
14 changes: 14 additions & 0 deletions spanner/spansql/parser_test.go
Expand Up @@ -111,6 +111,20 @@ func TestParseQuery(t *testing.T) {
},
},
},
{`SELECT AVG(PointsScored) AS total_points, FirstName, LastName AS surname FROM PlayerStats GROUP BY FirstName, LastName`,
Copy link
Contributor

Choose a reason for hiding this comment

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

this test case is really not needed.

Copy link
Contributor Author

@sryoya sryoya Dec 1, 2020

Choose a reason for hiding this comment

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

OK, removed.
8b733f1

Query{
Select: Select{
List: []Expr{
Func{Name: "AVG", Args: []Expr{ID("PointsScored")}},
ID("FirstName"),
ID("LastName"),
},
From: []SelectFrom{SelectFromTable{Table: "PlayerStats"}},
GroupBy: []Expr{ID("FirstName"), ID("LastName")},
ListAliases: []ID{"total_points", "", "surname"},
},
},
},
{`SELECT * FROM A INNER JOIN B ON A.w = B.y`,
Query{
Select: Select{
Expand Down