diff --git a/postgresql/_dumps/structs.sql b/postgresql/_dumps/structs.sql index 6da53ad6d416af6b1cdf882c4aa09419041f3ab5..8e0f05515c79a97af822c7bc51444c68421ecb0c 100644 --- a/postgresql/_dumps/structs.sql +++ b/postgresql/_dumps/structs.sql @@ -44,3 +44,11 @@ CREATE TABLE data_types ( _date timestamp without time zone, _time time without time zone ); + +DROP TABLE IF EXISTS stats_test; + +CREATE TABLE stats_test ( + id serial primary key, + numeric integer, + value integer +); diff --git a/postgresql/database.go b/postgresql/database.go index 772314774daae7c203dbc143427a191111657d39..7e56921a68c033f958812066e4941d4b60025178 100644 --- a/postgresql/database.go +++ b/postgresql/database.go @@ -108,6 +108,7 @@ func init() { pgsqlDropDatabaseLayout, pgsqlDropTableLayout, pgsqlSelectCountLayout, + pgsqlGroupByLayout, } db.Register(Adapter, &source{}) diff --git a/postgresql/database_test.go b/postgresql/database_test.go index 09e6a7407a33729476edfdfcd7ec629d43c26469..578fb22259794fd0dfe0f2ebc8fa804d785cd308 100644 --- a/postgresql/database_test.go +++ b/postgresql/database_test.go @@ -31,6 +31,7 @@ package postgresql import ( "database/sql" "flag" + "math/rand" "os" "reflect" "strings" @@ -678,7 +679,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 delete previously added rows. diff --git a/postgresql/layout.go b/postgresql/layout.go index a0c326b507f317b3612edf7ef0c9cf367166af80..7f4d62ff5abb5eafff3a0792aff25a935195e854 100644 --- a/postgresql/layout.go +++ b/postgresql/layout.go @@ -65,6 +65,8 @@ const ( {{.Where}} + {{.GroupBy}} + {{.OrderBy}} {{if .Limit}} @@ -122,5 +124,11 @@ const ( DROP TABLE {{.Table}} ` + pgsqlGroupByLayout = ` + {{if .GroupColumns}} + GROUP BY {{.GroupColumns}} + {{end}} + ` + psqlNull = `NULL` ) diff --git a/postgresql/result.go b/postgresql/result.go index 24b3f16b3e57d8e9a587f1368d207ffe5e6508e5..37f1523aef9e0f3ff20af4ede4314d695796b83e 100644 --- a/postgresql/result.go +++ b/postgresql/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,25 @@ 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. @@ -243,7 +264,7 @@ func (self *result) Close() error { return err } -// Counting the elements that will be returned. +// Counts the elements within the main conditions of the set. func (self *result) Count() (uint64, error) { var count counter_t