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

Added Window Function support #128

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions dialect/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func DialectOptions() *goqu.SQLDialectOptions {
opts.SupportsWithCTE = false
opts.SupportsWithCTERecursive = false
opts.SupportsDistinctOn = false
opts.SupportsWindowFunction = false

opts.UseFromClauseForMultipleUpdateTables = false

Expand Down Expand Up @@ -65,6 +66,13 @@ func DialectOptions() *goqu.SQLDialectOptions {
return opts
}

func DialectOptionsV8() *goqu.SQLDialectOptions {
opts := DialectOptions()
opts.SupportsWindowFunction = true
return opts
}

func init() {
goqu.RegisterDialect("mysql", DialectOptions())
goqu.RegisterDialect("mysql8", DialectOptionsV8())
}
37 changes: 37 additions & 0 deletions dialect/mysql/mysql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"database/sql"
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -393,6 +395,41 @@ func (mt *mysqlTest) TestInsert_OnConflict() {
mt.EqualError(err, "goqu: dialect does not support upsert with where clause [dialect=mysql]")
}

func (mt *mysqlTest) TestWindowFunction() {
var version string
ok, err := mt.db.Select(goqu.Func("version")).ScanVal(&version)
mt.NoError(err)
mt.True(ok)

fields := strings.Split(version, ".")
mt.True(len(fields) > 0)
major, err := strconv.Atoi(fields[0])
mt.NoError(err)
if major < 8 {
return
}

ds := mt.db.From("entry").Select("int", goqu.ROW_NUMBER().OverName("w").As("id")).Windows(goqu.W("w").OrderBy(goqu.I("int").Desc()))

var entries []entry
mt.NoError(ds.WithDialect("mysql8").ScanStructs(&entries))

mt.Equal([]entry{
{Int: 9, ID: 1},
{Int: 8, ID: 2},
{Int: 7, ID: 3},
{Int: 6, ID: 4},
{Int: 5, ID: 5},
{Int: 4, ID: 6},
{Int: 3, ID: 7},
{Int: 2, ID: 8},
{Int: 1, ID: 9},
{Int: 0, ID: 10},
}, entries)

mt.Error(ds.WithDialect("mysql").ScanStructs(&entries), "goqu: adapter does not support window function clause")
}

func TestMysqlSuite(t *testing.T) {
suite.Run(t, new(mysqlTest))
}
1 change: 1 addition & 0 deletions dialect/sqlite3/sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func DialectOptions() *goqu.SQLDialectOptions {
opts.SupportsMultipleUpdateTables = false
opts.WrapCompoundsInParens = false
opts.SupportsDistinctOn = false
opts.SupportsWindowFunction = false

opts.PlaceHolderRune = '?'
opts.IncludePlaceholderNum = false
Expand Down
24 changes: 23 additions & 1 deletion docs/selecting.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* [`Offset`](#offset)
* [`GroupBy`](#group_by)
* [`Having`](#having)
* [`Window`](#window)
* Executing Queries
* [`ScanStructs`](#scan-structs) - Scans rows into a slice of structs
* [`ScanStruct`](#scan-struct) - Scans a row into a slice a struct, returns false if a row wasnt found
Expand Down Expand Up @@ -610,6 +611,27 @@ Output:
SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000)
```


<a name="window"></a>
**[`Window Function`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Windows)**

```go
sql, _, _ = goqu.From("test").Select(goqu.ROW_NUMBER().Over(goqu.W().PartitionBy("a").OrderBy("b")))
fmt.Println(sql)

sql, _, _ = goqu.From("test").Select(goqu.ROW_NUMBER().OverName("w")).Windows(goqu.W("w").PartitionBy("a").OrderBy("b"))
fmt.Println(sql)
```

Output:

```
SELECT ROW_NUMBER() OVER (PARTITION BY "a" ORDER BY "b") FROM "test"
SELECT ROW_NUMBER() OVER "w" FROM "test" WINDOW "w" AS (PARTITION BY "a" ORDER BY "b")
```

**NOTE** currently only the `postgres`, `mysql8`(NOT `mysql`) and the default dialect support `Window Function`

## Executing Queries

To execute your query use [`goqu.Database#From`](https://godoc.org/github.com/doug-martin/goqu/#Database.From) to create your dataset
Expand Down Expand Up @@ -748,4 +770,4 @@ if err := db.From("user").Pluck(&ids, "id"); err != nil{
return
}
fmt.Printf("\nIds := %+v", ids)
```
```
2 changes: 1 addition & 1 deletion exp/col.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type columnList struct {
}

func NewColumnListExpression(vals ...interface{}) ColumnListExpression {
var cols []Expression
cols := []Expression{}
for _, val := range vals {
switch t := val.(type) {
case string:
Expand Down
27 changes: 27 additions & 0 deletions exp/exp.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,33 @@ type (
Col() IdentifierExpression
Val() interface{}
}

SQLWindowFunctionExpression interface {
SQLFunctionExpression

Window() WindowExpression
WindowName() string

Over(WindowExpression) SQLWindowFunctionExpression
OverName(string) SQLWindowFunctionExpression

HasWindow() bool
HasWindowName() bool
}

WindowExpression interface {
Expression

Name() string

Parent() string
PartitionCols() ColumnListExpression
OrderCols() ColumnListExpression

Inherit(parent string) WindowExpression
PartitionBy(cols ...interface{}) WindowExpression
OrderBy(cols ...interface{}) WindowExpression
}
)

const (
Expand Down
29 changes: 29 additions & 0 deletions exp/select_clauses.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ type (

CommonTables() []CommonTableExpression
CommonTablesAppend(cte CommonTableExpression) SelectClauses

Windows() []WindowExpression
SetWindows(ws []WindowExpression) SelectClauses
WindowsAppend(ws []WindowExpression) SelectClauses
ClearWindows() SelectClauses
}
selectClauses struct {
commonTables []CommonTableExpression
Expand All @@ -74,6 +79,7 @@ type (
offset uint
compounds []CompoundExpression
lock Lock
windows []WindowExpression
}
)

Expand Down Expand Up @@ -116,6 +122,7 @@ func (c *selectClauses) clone() *selectClauses {
offset: c.offset,
compounds: c.compounds,
lock: c.lock,
windows: c.windows,
}
}

Expand Down Expand Up @@ -331,3 +338,25 @@ func (c *selectClauses) CompoundsAppend(ce CompoundExpression) SelectClauses {
ret.compounds = append(ret.compounds, ce)
return ret
}

func (c *selectClauses) Windows() []WindowExpression {
return c.windows
}

func (c *selectClauses) SetWindows(ws []WindowExpression) SelectClauses {
ret := c.clone()
ret.windows = ws
return ret
}

func (c *selectClauses) WindowsAppend(ws []WindowExpression) SelectClauses {
ret := c.clone()
ret.windows = append(ret.windows, ws...)
return ret
}

func (c *selectClauses) ClearWindows() SelectClauses {
ret := c.clone()
ret.windows = nil
return ret
}
48 changes: 48 additions & 0 deletions exp/select_clauses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,54 @@ func (scs *selectClausesSuite) TestHavingAppend() {
scs.Equal(NewExpressionList(AndType, w, w2), c4.Having())
}

func (scs *selectClausesSuite) TestWindow() {
w := NewWindowExpression("w", "", nil, nil)

c := NewSelectClauses()
c2 := c.WindowsAppend([]WindowExpression{w})

scs.Nil(c.Windows())

scs.Equal([]WindowExpression{w}, c2.Windows())
}

func (scs *selectClausesSuite) TestSetWindows() {
w := NewWindowExpression("w", "", nil, nil)

c := NewSelectClauses()
c2 := c.SetWindows([]WindowExpression{w})

scs.Nil(c.Windows())

scs.Equal([]WindowExpression{w}, c2.Windows())
}

func (scs *selectClausesSuite) TestWindowsAppend() {
w1 := NewWindowExpression("w1", "", nil, nil)
w2 := NewWindowExpression("w2", "", nil, nil)

c := NewSelectClauses()
c2 := c.WindowsAppend([]WindowExpression{w1}).WindowsAppend([]WindowExpression{w2})

scs.Nil(c.Windows())

scs.Equal([]WindowExpression{w1, w2}, c2.Windows())
}

func (scs *selectClausesSuite) TestClearWindows() {
w := NewWindowExpression("w", "", nil, nil)

c := NewSelectClauses()
c2 := c.SetWindows([]WindowExpression{w})

scs.Nil(c.Windows())

scs.Equal([]WindowExpression{w}, c2.Windows())

c3 := c.ClearWindows()
scs.Nil(c3.Windows())
}

func (scs *selectClausesSuite) TestOrder() {
oe := NewIdentifierExpression("", "", "a").Desc()

Expand Down
74 changes: 74 additions & 0 deletions exp/window.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package exp

type sqlWindowExpression struct {
name string
parent string
partitionCols ColumnListExpression
orderCols ColumnListExpression
}

func NewWindowExpression(window, parent string, partitionCols, orderCols ColumnListExpression) WindowExpression {
if partitionCols == nil {
partitionCols = NewColumnListExpression()
}
if orderCols == nil {
orderCols = NewColumnListExpression()
}
return sqlWindowExpression{
name: window,
parent: parent,
partitionCols: partitionCols,
orderCols: orderCols,
}
}

func (we sqlWindowExpression) clone() sqlWindowExpression {
return sqlWindowExpression{
name: we.name,
parent: we.parent,
partitionCols: we.partitionCols.Clone().(ColumnListExpression),
orderCols: we.orderCols.Clone().(ColumnListExpression),
}
}

func (we sqlWindowExpression) Clone() Expression {
return we.clone()
}

func (we sqlWindowExpression) Expression() Expression {
return we
}

func (we sqlWindowExpression) Name() string {
return we.name
}

func (we sqlWindowExpression) Parent() string {
return we.parent
}

func (we sqlWindowExpression) PartitionCols() ColumnListExpression {
return we.partitionCols
}

func (we sqlWindowExpression) OrderCols() ColumnListExpression {
return we.orderCols
}

func (we sqlWindowExpression) PartitionBy(cols ...interface{}) WindowExpression {
ret := we.clone()
ret.partitionCols = NewColumnListExpression(cols...)
return ret
}

func (we sqlWindowExpression) OrderBy(cols ...interface{}) WindowExpression {
ret := we.clone()
ret.orderCols = NewColumnListExpression(cols...)
return ret
}

func (we sqlWindowExpression) Inherit(parent string) WindowExpression {
ret := we.clone()
ret.parent = parent
return ret
}
Loading