From 82b63fc87d73e50326d74ff944eed6ec7017ea08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Carlos=20Nieto?= <jose.carlos@menteslibres.net>
Date: Thu, 7 Aug 2014 11:32:50 -0500
Subject: [PATCH] MySQL: Adding support for db.Result.Group().

---
 mysql/_dumps/structs.sql |  9 +++++++
 mysql/database.go        |  1 +
 mysql/database_test.go   | 55 ++++++++++++++++++++++++++++++++++++++++
 mysql/layout.go          |  8 ++++++
 mysql/result.go          | 22 ++++++++++++++++
 5 files changed, 95 insertions(+)

diff --git a/mysql/_dumps/structs.sql b/mysql/_dumps/structs.sql
index 42240f4b..de205b43 100644
--- a/mysql/_dumps/structs.sql
+++ b/mysql/_dumps/structs.sql
@@ -50,3 +50,12 @@ CREATE TABLE data_types (
   _date DATETIME NOT NULL,
   _time TIME NOT NULL
 );
+
+DROP TABLE IF EXISTS stats_test;
+
+CREATE TABLE stats_test (
+  id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
+	PRIMARY KEY(id),
+	`numeric` INT(10),
+	`value` INT(10)
+);
diff --git a/mysql/database.go b/mysql/database.go
index 4eb19052..1ac42dc5 100644
--- a/mysql/database.go
+++ b/mysql/database.go
@@ -106,6 +106,7 @@ func init() {
 		mysqlDropDatabaseLayout,
 		mysqlDropTableLayout,
 		mysqlSelectCountLayout,
+		mysqlGroupByLayout,
 	}
 
 	db.Register(Adapter, &Source{})
diff --git a/mysql/database_test.go b/mysql/database_test.go
index b2baadb3..f76ff00c 100644
--- a/mysql/database_test.go
+++ b/mysql/database_test.go
@@ -31,6 +31,7 @@ package mysql
 import (
 	"database/sql"
 	"flag"
+	"math/rand"
 	"os"
 	"reflect"
 	"strings"
@@ -315,7 +316,61 @@ func TestNullableFields(t *testing.T) {
 	if test.NullStringTest.Valid == false {
 		t.Fatalf(`Expecting valid value.`)
 	}
+}
+
+func TestGroup(t *testing.T) {
+
+	var err error
+	var sess db.Database
+	var stats db.Collection
+
+	if sess, err = db.Open(Adapter, settings); err != nil {
+		t.Fatal(err)
+	}
+
+	type stats_t struct {
+		Numeric int `db:"numeric"`
+		Value   int `db:"value"`
+	}
+
+	defer sess.Close()
+
+	if stats, err = sess.Collection("stats_test"); err != nil {
+		t.Fatal(err)
+	}
+
+	// Truncating table.
+	if err = stats.Truncate(); err != nil {
+		t.Fatal(err)
+	}
+
+	// Adding row append.
+	for i := 0; i < 1000; i++ {
+		numeric, value := rand.Intn(10), rand.Intn(100)
+		if _, err = stats.Append(stats_t{numeric, value}); err != nil {
+			t.Fatal(err)
+		}
+	}
 
+	// db.Func{"COUNT", 1},
+	// db.Func{"SUM", `value`},
+
+	// Testing GROUP BY
+	res := stats.Find().Select(
+		`numeric`,
+		db.Raw{`COUNT(1) AS counter`},
+		db.Raw{`SUM(value) AS total`},
+	).Group(`numeric`)
+
+	var results []map[string]interface{}
+
+	if err = res.All(&results); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(results) != 10 {
+		t.Fatalf(`Expecting exactly 10 results, this could fail, but it's very unlikely to happen.`)
+	}
 }
 
 // Attempts to count all rows in our newly defined set.
diff --git a/mysql/layout.go b/mysql/layout.go
index 21ac5e06..b63c3a41 100644
--- a/mysql/layout.go
+++ b/mysql/layout.go
@@ -65,6 +65,8 @@ const (
 
 			{{.Where}}
 
+			{{.GroupBy}}
+
 			{{.OrderBy}}
 
 			{{if .Limit}}
@@ -122,5 +124,11 @@ const (
 		DROP TABLE {{.Table}}
 	`
 
+	mysqlGroupByLayout = `
+		{{if .GroupColumns}}
+			GROUP BY {{.GroupColumns}}
+		{{end}}
+	`
+
 	mysqlNull = `NULL`
 )
diff --git a/mysql/result.go b/mysql/result.go
index afe77ec2..a67d5f85 100644
--- a/mysql/result.go
+++ b/mysql/result.go
@@ -43,6 +43,7 @@ type Result struct {
 	columns   sqlgen.Columns
 	where     sqlgen.Where
 	orderBy   sqlgen.OrderBy
+	groupBy   sqlgen.GroupBy
 	arguments []interface{}
 }
 
@@ -59,6 +60,7 @@ func (self *Result) setCursor() error {
 			Offset:  self.offset,
 			Where:   self.where,
 			OrderBy: self.orderBy,
+			GroupBy: self.groupBy,
 		}, self.arguments...)
 	}
 	return err
@@ -83,6 +85,26 @@ func (self *Result) Skip(n uint) db.Result {
 	return self
 }
 
+// Used to group results that have the same value in the same column or
+// columns.
+func (self *Result) Group(fields ...interface{}) db.Result {
+
+	groupByColumns := make(sqlgen.GroupBy, 0, len(fields))
+
+	l := len(fields)
+	for i := 0; i < l; i++ {
+		switch value := fields[i].(type) {
+		// Maybe other types?
+		default:
+			groupByColumns = append(groupByColumns, sqlgen.Column{value})
+		}
+	}
+
+	self.groupBy = groupByColumns
+
+	return self
+}
+
 // Determines sorting of results according to the provided names. Fields may be
 // prefixed by - (minus) which means descending order, ascending order would be
 // used otherwise.
-- 
GitLab