spanner/spansql: parse GROUP BY clauses

These aren't implemented by spannertest yet.

Change-Id: Iab675f35c91bd7a8842233e8cb4ab46d0cb4d819
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/52733
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Shanika Kuruppu <skuruppu@google.com>
diff --git a/spanner/spansql/keywords.go b/spanner/spansql/keywords.go
index e9b6e5a..c243b7a 100644
--- a/spanner/spansql/keywords.go
+++ b/spanner/spansql/keywords.go
@@ -131,6 +131,7 @@
 	// Aggregate functions.
 	"BIT_XOR": true,
 	"COUNT":   true,
+	"SUM":     true,
 
 	// Mathematical functions.
 	"ABS": true,
diff --git a/spanner/spansql/parser.go b/spanner/spansql/parser.go
index 838ef3e..e59b360 100644
--- a/spanner/spansql/parser.go
+++ b/spanner/spansql/parser.go
@@ -1484,18 +1484,11 @@
 	}
 
 	// Read expressions for the SELECT list.
-	for {
-		expr, err := p.parseExpr()
-		if err != nil {
-			return Select{}, err
-		}
-		sel.List = append(sel.List, expr)
-
-		if p.eat(",") {
-			continue
-		}
-		break
+	list, err := p.parseExprList()
+	if err != nil {
+		return Select{}, err
 	}
+	sel.List = list
 
 	if p.eat("FROM") {
 		for {
@@ -1527,7 +1520,15 @@
 		sel.Where = where
 	}
 
-	// TODO: GROUP BY, HAVING
+	if p.eat("GROUP", "BY") {
+		list, err := p.parseExprList()
+		if err != nil {
+			return Select{}, err
+		}
+		sel.GroupBy = list
+	}
+
+	// TODO: HAVING
 
 	return sel, nil
 }
@@ -1631,6 +1632,23 @@
 
 func (p *parser) parseExprList() ([]Expr, *parseError) {
 	var list []Expr
+	for {
+		expr, err := p.parseExpr()
+		if err != nil {
+			return nil, err
+		}
+		list = append(list, expr)
+
+		if p.eat(",") {
+			continue
+		}
+		break
+	}
+	return list, nil
+}
+
+func (p *parser) parseParenExprList() ([]Expr, *parseError) {
+	var list []Expr
 	err := p.parseCommaList(func(p *parser) *parseError {
 		e, err := p.parseExpr()
 		if err != nil {
@@ -1943,7 +1961,7 @@
 	// this is a function invocation.
 	// TODO: Case-insensitivity.
 	if name := tok.value; funcs[name] && p.sniff("(") {
-		list, err := p.parseExprList()
+		list, err := p.parseParenExprList()
 		if err != nil {
 			return nil, err
 		}
diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go
index 4d0177a..8c31515 100644
--- a/spanner/spansql/parser_test.go
+++ b/spanner/spansql/parser_test.go
@@ -78,6 +78,19 @@
 				},
 			},
 		},
+		{`SELECT SUM(PointsScored), FirstName, LastName FROM PlayerStats GROUP BY FirstName, LastName`,
+			Query{
+				Select: Select{
+					List: []Expr{
+						Func{Name: "SUM", Args: []Expr{ID("PointsScored")}},
+						ID("FirstName"),
+						ID("LastName"),
+					},
+					From:    []SelectFrom{{Table: "PlayerStats"}},
+					GroupBy: []Expr{ID("FirstName"), ID("LastName")},
+				},
+			},
+		},
 	}
 	for _, test := range tests {
 		got, err := ParseQuery(test.in)
diff --git a/spanner/spansql/sql.go b/spanner/spansql/sql.go
index 8f536a7..9f6f4a4 100644
--- a/spanner/spansql/sql.go
+++ b/spanner/spansql/sql.go
@@ -212,6 +212,15 @@
 	if sel.Where != nil {
 		str += " WHERE " + sel.Where.SQL()
 	}
+	if len(sel.GroupBy) > 0 {
+		str += " GROUP BY "
+		for i, gb := range sel.GroupBy {
+			if i > 0 {
+				str += ", "
+			}
+			str += gb.SQL()
+		}
+	}
 	return str
 }
 
diff --git a/spanner/spansql/types.go b/spanner/spansql/types.go
index c634bdc..79fe888 100644
--- a/spanner/spansql/types.go
+++ b/spanner/spansql/types.go
@@ -224,7 +224,8 @@
 	List     []Expr
 	From     []SelectFrom
 	Where    BoolExpr
-	// TODO: GroupBy, Having
+	GroupBy  []Expr
+	// TODO: Having
 }
 
 type SelectFrom struct {