diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 5b65e1d1..41f588f6 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -7,20 +7,31 @@ on: pull_request: # Specify a second event with pattern matching jobs: test: - name: Test go ${{ matrix.go_version }} + name: Test go - ${{ matrix.go_version }} mysql - ${{ matrix.db_versions.mysql_version}} postgres - ${{ matrix.db_versions.postgres_version}} runs-on: ubuntu-latest strategy: matrix: go_version: ["1.10", "1.11", "latest"] + db_versions: + - mysql_version: 5 + postgres_version: 9.6 + - mysql_version: 5 + postgres_version: 10.10 + - mysql_version: 8 + postgres_version: 11.5 steps: - name: checkout uses: actions/checkout@v1 - name: Test env: GO_VERSION: ${{ matrix.go_version }} + MYSQL_VERSION: ${{ matrix.db_versions.mysql_version }} + POSTGRES_VERSION: ${{ matrix.go_version.postgres_version }} run: docker-compose run goqu-coverage - name: Upload coverage to Codecov run: bash <(curl -s https://codecov.io/bash) -n $GO_VERSION -e GO_VERSION,GITHUB_WORKFLOW,GITHUB_ACTION env: CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} GO_VERSION: ${{ matrix.go_version }} + MYSQL_VERSION: ${{ matrix.db_versions.mysql_version }} + POSTGRES_VERSION: ${{ matrix.go_version.postgres_version }} diff --git a/HISTORY.md b/HISTORY.md index 49b6f1cc..12dba341 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,7 @@ +## 8.5.0 + +* [ADDED] Window Function support [#128](https://github.com/doug-martin/goqu/issues/128) - [@Xuyuanp](https://github.com/Xuyuanp) + ## 8.4.1 * [FIXED] Returning func be able to handle nil [#140](https://github.com/doug-martin/goqu/issues/140) diff --git a/database_test.go b/database_test.go index 3574f61e..92980bc6 100644 --- a/database_test.go +++ b/database_test.go @@ -6,8 +6,6 @@ import ( "sync" "testing" - "github.com/stretchr/testify/assert" - "github.com/DATA-DOG/go-sqlmock" "github.com/doug-martin/goqu/v8/internal/errors" "github.com/stretchr/testify/suite" @@ -317,9 +315,8 @@ func (ds *databaseSuite) TestWithTx() { } func (ds *databaseSuite) TestDataRace() { - t := ds.T() mDb, mock, err := sqlmock.New() - assert.NoError(t, err) + ds.NoError(err) db := newDatabase("mock", mDb) const concurrency = 10 @@ -340,10 +337,10 @@ func (ds *databaseSuite) TestDataRace() { sql := db.From("items").Limit(1) var item testActionItem found, err := sql.ScanStruct(&item) - assert.NoError(t, err) - assert.True(t, found) - assert.Equal(t, item.Address, "111 Test Addr") - assert.Equal(t, item.Name, "Test1") + ds.NoError(err) + ds.True(found) + ds.Equal(item.Address, "111 Test Addr") + ds.Equal(item.Name, "Test1") }() } @@ -661,13 +658,12 @@ func (tds *txdatabaseSuite) TestWrap() { } func (tds *txdatabaseSuite) TestDataRace() { - t := tds.T() mDb, mock, err := sqlmock.New() - assert.NoError(t, err) + tds.NoError(err) mock.ExpectBegin() db := newDatabase("mock", mDb) tx, err := db.Begin() - assert.NoError(t, err) + tds.NoError(err) const concurrency = 10 @@ -687,16 +683,16 @@ func (tds *txdatabaseSuite) TestDataRace() { sql := tx.From("items").Limit(1) var item testActionItem found, err := sql.ScanStruct(&item) - assert.NoError(t, err) - assert.True(t, found) - assert.Equal(t, item.Address, "111 Test Addr") - assert.Equal(t, item.Name, "Test1") + tds.NoError(err) + tds.True(found) + tds.Equal(item.Address, "111 Test Addr") + tds.Equal(item.Name, "Test1") }() } wg.Wait() mock.ExpectCommit() - assert.NoError(t, tx.Commit()) + tds.NoError(tx.Commit()) } func TestTxDatabaseSuite(t *testing.T) { diff --git a/dialect/mysql/mysql_test.go b/dialect/mysql/mysql_test.go index 59b1c757..ce36040b 100644 --- a/dialect/mysql/mysql_test.go +++ b/dialect/mysql/mysql_test.go @@ -409,7 +409,9 @@ func (mt *mysqlTest) TestWindowFunction() { return } - ds := mt.db.From("entry").Select("int", goqu.ROW_NUMBER().OverName("w").As("id")).Windows(goqu.W("w").OrderBy(goqu.I("int").Desc())) + ds := mt.db.From("entry"). + Select("int", goqu.ROW_NUMBER().OverName(goqu.I("w")).As("id")). + Windows(goqu.W("w").OrderBy(goqu.I("int").Desc())) var entries []entry mt.NoError(ds.WithDialect("mysql8").ScanStructs(&entries)) diff --git a/dialect/postgres/postgres_test.go b/dialect/postgres/postgres_test.go index b1d662de..6964f5b9 100644 --- a/dialect/postgres/postgres_test.go +++ b/dialect/postgres/postgres_test.go @@ -414,6 +414,28 @@ func (pt *postgresTest) TestInsert_OnConflict() { pt.Equal("upsert", entry9.String) } +func (pt *postgresTest) TestWindowFunction() { + ds := pt.db.From("entry"). + Select("int", goqu.ROW_NUMBER().OverName(goqu.I("w")).As("id")). + Windows(goqu.W("w").OrderBy(goqu.I("int").Desc())) + + var entries []entry + pt.NoError(ds.ScanStructs(&entries)) + + pt.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) +} + func TestPostgresSuite(t *testing.T) { suite.Run(t, new(postgresTest)) } diff --git a/docker-compose.yml b/docker-compose.yml index bace1735..c647c722 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "2" services: postgres: - image: postgres:9.6 + image: "mysql:${POSTGRES_VERSION}" environment: - "POSTGRES_USER=postgres" - "POSTGRES_DB=goqupostgres" @@ -10,7 +10,7 @@ services: - "5432" mysql: - image: mysql:5 + image: "mysql:${MYSQL_VERSION}" environment: - "MYSQL_DATABASE=goqumysql" - "MYSQL_ALLOW_EMPTY_PASSWORD=yes" diff --git a/docs/selecting.md b/docs/selecting.md index 09ba14c5..08b4de26 100644 --- a/docs/selecting.md +++ b/docs/selecting.md @@ -615,23 +615,38 @@ SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000) **[`Window Function`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.Windows)** +**NOTE** currently only the `postgres`, `mysql8` (NOT `mysql`) and the default dialect support `Window Function` + +To use windowing in select you can use the `Over` method on an `SQLFunction` + ```go -sql, _, _ = goqu.From("test").Select(goqu.ROW_NUMBER().Over(goqu.W().PartitionBy("a").OrderBy("b"))) +sql, _, _ = goqu.From("test").Select( + goqu.ROW_NUMBER().Over(goqu.W().PartitionBy("a").OrderBy(goqu.I("b").Asc())), +) fmt.Println(sql) +``` + +Output: + +``` +SELECT ROW_NUMBER() OVER (PARTITION BY "a" ORDER BY "b") FROM "test" +``` -sql, _, _ = goqu.From("test").Select(goqu.ROW_NUMBER().OverName("w")).Windows(goqu.W("w").PartitionBy("a").OrderBy("b")) +`goqu` also supports the `WINDOW` clause. + +```go +sql, _, _ = goqu.From("test"). + Select(goqu.ROW_NUMBER().OverName(goqu.I("w"))). + Windows(goqu.W("w").PartitionBy("a").OrderBy(goqu.I("b").Asc())) 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 @@ -771,3 +786,4 @@ if err := db.From("user").Pluck(&ids, "id"); err != nil{ } fmt.Printf("\nIds := %+v", ids) ``` + diff --git a/exp/exp.go b/exp/exp.go index 0b0de910..d225f48f 100644 --- a/exp/exp.go +++ b/exp/exp.go @@ -341,6 +341,11 @@ type ( End() interface{} } + Windowable interface { + Over(WindowExpression) SQLWindowFunctionExpression + OverName(IdentifierExpression) SQLWindowFunctionExpression + } + // Expression for representing a SQLFunction(e.g. COUNT, SUM, MIN, MAX...) SQLFunctionExpression interface { Expression @@ -350,6 +355,7 @@ type ( Isable Inable Likeable + Windowable // The function name Name() string // Arguments to be passed to the function @@ -362,13 +368,17 @@ type ( } SQLWindowFunctionExpression interface { - SQLFunctionExpression + Expression + Aliaseable + Rangeable + Comparable + Isable + Inable + Likeable + Func() SQLFunctionExpression Window() WindowExpression - WindowName() string - - Over(WindowExpression) SQLWindowFunctionExpression - OverName(string) SQLWindowFunctionExpression + WindowName() IdentifierExpression HasWindow() bool HasWindowName() bool @@ -377,11 +387,15 @@ type ( WindowExpression interface { Expression - Name() string + Name() IdentifierExpression + HasName() bool - Parent() string + Parent() IdentifierExpression + HasParent() bool PartitionCols() ColumnListExpression + HasPartitionBy() bool OrderCols() ColumnListExpression + HasOrder() bool Inherit(parent string) WindowExpression PartitionBy(cols ...interface{}) WindowExpression diff --git a/exp/func.go b/exp/func.go index df60df9f..67b62a39 100644 --- a/exp/func.go +++ b/exp/func.go @@ -46,3 +46,11 @@ func (sfe sqlFunctionExpression) IsTrue() BooleanExpression { retu func (sfe sqlFunctionExpression) IsNotTrue() BooleanExpression { return isNot(sfe, true) } func (sfe sqlFunctionExpression) IsFalse() BooleanExpression { return is(sfe, false) } func (sfe sqlFunctionExpression) IsNotFalse() BooleanExpression { return isNot(sfe, false) } + +func (sfe sqlFunctionExpression) Over(we WindowExpression) SQLWindowFunctionExpression { + return NewSQLWindowFunctionExpression(sfe, nil, we) +} + +func (sfe sqlFunctionExpression) OverName(windowName IdentifierExpression) SQLWindowFunctionExpression { + return NewSQLWindowFunctionExpression(sfe, windowName, nil) +} diff --git a/exp/select_clauses.go b/exp/select_clauses.go index 1555120e..f5443caf 100644 --- a/exp/select_clauses.go +++ b/exp/select_clauses.go @@ -61,7 +61,7 @@ type ( Windows() []WindowExpression SetWindows(ws []WindowExpression) SelectClauses - WindowsAppend(ws []WindowExpression) SelectClauses + WindowsAppend(ws ...WindowExpression) SelectClauses ClearWindows() SelectClauses } selectClauses struct { @@ -349,7 +349,7 @@ func (c *selectClauses) SetWindows(ws []WindowExpression) SelectClauses { return ret } -func (c *selectClauses) WindowsAppend(ws []WindowExpression) SelectClauses { +func (c *selectClauses) WindowsAppend(ws ...WindowExpression) SelectClauses { ret := c.clone() ret.windows = append(ret.windows, ws...) return ret diff --git a/exp/select_clauses_test.go b/exp/select_clauses_test.go index e6869de1..dfe06056 100644 --- a/exp/select_clauses_test.go +++ b/exp/select_clauses_test.go @@ -255,11 +255,11 @@ func (scs *selectClausesSuite) TestHavingAppend() { scs.Equal(NewExpressionList(AndType, w, w2), c4.Having()) } -func (scs *selectClausesSuite) TestWindow() { - w := NewWindowExpression("w", "", nil, nil) +func (scs *selectClausesSuite) TestWindows() { + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, nil, nil) c := NewSelectClauses() - c2 := c.WindowsAppend([]WindowExpression{w}) + c2 := c.WindowsAppend(w) scs.Nil(c.Windows()) @@ -267,7 +267,7 @@ func (scs *selectClausesSuite) TestWindow() { } func (scs *selectClausesSuite) TestSetWindows() { - w := NewWindowExpression("w", "", nil, nil) + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, nil, nil) c := NewSelectClauses() c2 := c.SetWindows([]WindowExpression{w}) @@ -278,11 +278,11 @@ func (scs *selectClausesSuite) TestSetWindows() { } func (scs *selectClausesSuite) TestWindowsAppend() { - w1 := NewWindowExpression("w1", "", nil, nil) - w2 := NewWindowExpression("w2", "", nil, nil) + w1 := NewWindowExpression(NewIdentifierExpression("", "", "w1"), nil, nil, nil) + w2 := NewWindowExpression(NewIdentifierExpression("", "", "w2"), nil, nil, nil) c := NewSelectClauses() - c2 := c.WindowsAppend([]WindowExpression{w1}).WindowsAppend([]WindowExpression{w2}) + c2 := c.WindowsAppend(w1).WindowsAppend(w2) scs.Nil(c.Windows()) @@ -290,17 +290,11 @@ func (scs *selectClausesSuite) TestWindowsAppend() { } func (scs *selectClausesSuite) TestClearWindows() { - w := NewWindowExpression("w", "", nil, nil) + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, 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()) + c := NewSelectClauses().SetWindows([]WindowExpression{w}) + scs.Nil(c.ClearWindows().Windows()) + scs.Equal([]WindowExpression{w}, c.Windows()) } func (scs *selectClausesSuite) TestOrder() { diff --git a/exp/window.go b/exp/window.go index c618d70b..a33178a0 100644 --- a/exp/window.go +++ b/exp/window.go @@ -1,13 +1,13 @@ package exp type sqlWindowExpression struct { - name string - parent string + name IdentifierExpression + parent IdentifierExpression partitionCols ColumnListExpression orderCols ColumnListExpression } -func NewWindowExpression(window, parent string, partitionCols, orderCols ColumnListExpression) WindowExpression { +func NewWindowExpression(window, parent IdentifierExpression, partitionCols, orderCols ColumnListExpression) WindowExpression { if partitionCols == nil { partitionCols = NewColumnListExpression() } @@ -39,22 +39,38 @@ func (we sqlWindowExpression) Expression() Expression { return we } -func (we sqlWindowExpression) Name() string { +func (we sqlWindowExpression) Name() IdentifierExpression { return we.name } -func (we sqlWindowExpression) Parent() string { +func (we sqlWindowExpression) HasName() bool { + return we.name != nil +} + +func (we sqlWindowExpression) Parent() IdentifierExpression { return we.parent } +func (we sqlWindowExpression) HasParent() bool { + return we.parent != nil +} + func (we sqlWindowExpression) PartitionCols() ColumnListExpression { return we.partitionCols } +func (we sqlWindowExpression) HasPartitionBy() bool { + return we.partitionCols != nil && !we.partitionCols.IsEmpty() +} + func (we sqlWindowExpression) OrderCols() ColumnListExpression { return we.orderCols } +func (we sqlWindowExpression) HasOrder() bool { + return we.orderCols != nil && !we.orderCols.IsEmpty() +} + func (we sqlWindowExpression) PartitionBy(cols ...interface{}) WindowExpression { ret := we.clone() ret.partitionCols = NewColumnListExpression(cols...) @@ -69,6 +85,6 @@ func (we sqlWindowExpression) OrderBy(cols ...interface{}) WindowExpression { func (we sqlWindowExpression) Inherit(parent string) WindowExpression { ret := we.clone() - ret.parent = parent + ret.parent = ParseIdentifier(parent) return ret } diff --git a/exp/window_func.go b/exp/window_func.go index 83366171..8580f957 100644 --- a/exp/window_func.go +++ b/exp/window_func.go @@ -1,23 +1,25 @@ package exp type sqlWindowFunctionExpression struct { - name string - args []interface{} - windowName string + fn SQLFunctionExpression + windowName IdentifierExpression window WindowExpression } -func NewSQLWindowFunctionExpression(name string, args ...interface{}) SQLWindowFunctionExpression { +func NewSQLWindowFunctionExpression( + fn SQLFunctionExpression, + windowName IdentifierExpression, + window WindowExpression) SQLWindowFunctionExpression { return sqlWindowFunctionExpression{ - name: name, - args: args, + fn: fn, + windowName: windowName, + window: window, } } func (swfe sqlWindowFunctionExpression) clone() sqlWindowFunctionExpression { return sqlWindowFunctionExpression{ - name: swfe.name, - args: swfe.args, + fn: swfe.fn.Clone().(SQLFunctionExpression), windowName: swfe.windowName, window: swfe.window, } @@ -73,34 +75,22 @@ func (swfe sqlWindowFunctionExpression) IsNotTrue() BooleanExpression { return func (swfe sqlWindowFunctionExpression) IsFalse() BooleanExpression { return is(swfe, false) } func (swfe sqlWindowFunctionExpression) IsNotFalse() BooleanExpression { return isNot(swfe, false) } -func (swfe sqlWindowFunctionExpression) Name() string { return swfe.name } - -func (swfe sqlWindowFunctionExpression) Args() []interface{} { return swfe.args } +func (swfe sqlWindowFunctionExpression) Func() SQLFunctionExpression { + return swfe.fn +} func (swfe sqlWindowFunctionExpression) Window() WindowExpression { return swfe.window } -func (swfe sqlWindowFunctionExpression) WindowName() string { +func (swfe sqlWindowFunctionExpression) WindowName() IdentifierExpression { return swfe.windowName } -func (swfe sqlWindowFunctionExpression) Over(we WindowExpression) SQLWindowFunctionExpression { - ret := swfe.clone() - ret.window = we - return ret -} - -func (swfe sqlWindowFunctionExpression) OverName(name string) SQLWindowFunctionExpression { - ret := swfe.clone() - ret.windowName = name - return ret -} - func (swfe sqlWindowFunctionExpression) HasWindow() bool { return swfe.window != nil } func (swfe sqlWindowFunctionExpression) HasWindowName() bool { - return swfe.windowName != "" + return swfe.windowName != nil } diff --git a/exp/window_func_test.go b/exp/window_func_test.go index 071213ec..3ee6f772 100644 --- a/exp/window_func_test.go +++ b/exp/window_func_test.go @@ -3,184 +3,179 @@ package exp import ( "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) type sqlWindowFunctionExpressionTest struct { suite.Suite + fn SQLFunctionExpression } func TestSQLWindowFunctionExpressionSuite(t *testing.T) { - suite.Run(t, new(sqlWindowFunctionExpressionTest)) + suite.Run(t, &sqlWindowFunctionExpressionTest{ + fn: NewSQLFunctionExpression("COUNT", Star()), + }) } func (swfet *sqlWindowFunctionExpressionTest) TestClone() { - t := swfet.T() - wf := NewSQLWindowFunctionExpression("f1", "a") + wf := NewSQLWindowFunctionExpression(swfet.fn, NewIdentifierExpression("", "", "a"), nil) wf2 := wf.Clone() - assert.Equal(t, wf, wf2) + swfet.Equal(wf, wf2) } func (swfet *sqlWindowFunctionExpressionTest) TestExpression() { - t := swfet.T() - wf := NewSQLWindowFunctionExpression("f1", "a") + wf := NewSQLWindowFunctionExpression(swfet.fn, NewIdentifierExpression("", "", "a"), nil) wf2 := wf.Expression() - assert.Equal(t, wf, wf2) + swfet.Equal(wf, wf2) } -func (swfet *sqlWindowFunctionExpressionTest) TestName() { - t := swfet.T() - wf := NewSQLWindowFunctionExpression("f1", "a") - assert.Equal(t, wf.Name(), "f1") -} - -func (swfet *sqlWindowFunctionExpressionTest) TestArgs() { - t := swfet.T() - wf := NewSQLWindowFunctionExpression("f1", "a") - assert.Equal(t, wf.Args(), []interface{}{"a"}) +func (swfet *sqlWindowFunctionExpressionTest) TestFunc() { + wf := NewSQLWindowFunctionExpression(swfet.fn, NewIdentifierExpression("", "", "a"), nil) + swfet.Equal(swfet.fn, wf.Func()) } func (swfet *sqlWindowFunctionExpressionTest) TestWindow() { - t := swfet.T() - w := NewWindowExpression("w", "", nil, nil) - wf := NewSQLWindowFunctionExpression("f1", "a") - assert.False(t, wf.HasWindow()) - - wf = wf.Over(w) - assert.True(t, wf.HasWindow()) - assert.Equal(t, wf.Window(), w) + w := NewWindowExpression( + NewIdentifierExpression("", "", "w"), + nil, + nil, + nil, + ) + wf := NewSQLWindowFunctionExpression(swfet.fn, NewIdentifierExpression("", "", "a"), nil) + swfet.False(wf.HasWindow()) + + wf = swfet.fn.Over(w) + swfet.True(wf.HasWindow()) + swfet.Equal(wf.Window(), w) } func (swfet *sqlWindowFunctionExpressionTest) TestWindowName() { - t := swfet.T() - windowName := "w" - wf := NewSQLWindowFunctionExpression("f1", "a") - assert.False(t, wf.HasWindowName()) - - wf = wf.OverName(windowName) - assert.True(t, wf.HasWindowName()) - assert.Equal(t, wf.WindowName(), windowName) + windowName := NewIdentifierExpression("", "", "a") + wf := NewSQLWindowFunctionExpression(swfet.fn, nil, nil) + swfet.False(wf.HasWindowName()) + + wf = swfet.fn.OverName(windowName) + swfet.True(wf.HasWindowName()) + swfet.Equal(wf.WindowName(), windowName) } func (swfet *sqlWindowFunctionExpressionTest) TestAllOthers() { - t := swfet.T() - wf := NewSQLWindowFunctionExpression("f1", "a") + wf := NewSQLWindowFunctionExpression(swfet.fn, nil, nil) expAs := wf.As("a") - assert.Equal(t, expAs.Aliased(), wf) + swfet.Equal(expAs.Aliased(), wf) expEq := wf.Eq(1) - assert.Equal(t, expEq.LHS(), wf) - assert.Equal(t, expEq.Op(), EqOp) - assert.Equal(t, expEq.RHS(), 1) + swfet.Equal(expEq.LHS(), wf) + swfet.Equal(expEq.Op(), EqOp) + swfet.Equal(expEq.RHS(), 1) expNeq := wf.Neq(1) - assert.Equal(t, expNeq.LHS(), wf) - assert.Equal(t, expNeq.Op(), NeqOp) - assert.Equal(t, expNeq.RHS(), 1) + swfet.Equal(expNeq.LHS(), wf) + swfet.Equal(expNeq.Op(), NeqOp) + swfet.Equal(expNeq.RHS(), 1) expGt := wf.Gt(1) - assert.Equal(t, expGt.LHS(), wf) - assert.Equal(t, expGt.Op(), GtOp) - assert.Equal(t, expGt.RHS(), 1) + swfet.Equal(expGt.LHS(), wf) + swfet.Equal(expGt.Op(), GtOp) + swfet.Equal(expGt.RHS(), 1) expGte := wf.Gte(1) - assert.Equal(t, expGte.LHS(), wf) - assert.Equal(t, expGte.Op(), GteOp) - assert.Equal(t, expGte.RHS(), 1) + swfet.Equal(expGte.LHS(), wf) + swfet.Equal(expGte.Op(), GteOp) + swfet.Equal(expGte.RHS(), 1) expLt := wf.Lt(1) - assert.Equal(t, expLt.LHS(), wf) - assert.Equal(t, expLt.Op(), LtOp) - assert.Equal(t, expLt.RHS(), 1) + swfet.Equal(expLt.LHS(), wf) + swfet.Equal(expLt.Op(), LtOp) + swfet.Equal(expLt.RHS(), 1) expLte := wf.Lte(1) - assert.Equal(t, expLte.LHS(), wf) - assert.Equal(t, expLte.Op(), LteOp) - assert.Equal(t, expLte.RHS(), 1) + swfet.Equal(expLte.LHS(), wf) + swfet.Equal(expLte.Op(), LteOp) + swfet.Equal(expLte.RHS(), 1) rv := NewRangeVal(1, 2) expBetween := wf.Between(rv) - assert.Equal(t, expBetween.LHS(), wf) - assert.Equal(t, expBetween.Op(), BetweenOp) - assert.Equal(t, expBetween.RHS(), rv) + swfet.Equal(expBetween.LHS(), wf) + swfet.Equal(expBetween.Op(), BetweenOp) + swfet.Equal(expBetween.RHS(), rv) expNotBetween := wf.NotBetween(rv) - assert.Equal(t, expNotBetween.LHS(), wf) - assert.Equal(t, expNotBetween.Op(), NotBetweenOp) - assert.Equal(t, expNotBetween.RHS(), rv) + swfet.Equal(expNotBetween.LHS(), wf) + swfet.Equal(expNotBetween.Op(), NotBetweenOp) + swfet.Equal(expNotBetween.RHS(), rv) pattern := "a%" expLike := wf.Like(pattern) - assert.Equal(t, expLike.LHS(), wf) - assert.Equal(t, expLike.Op(), LikeOp) - assert.Equal(t, expLike.RHS(), pattern) + swfet.Equal(expLike.LHS(), wf) + swfet.Equal(expLike.Op(), LikeOp) + swfet.Equal(expLike.RHS(), pattern) expNotLike := wf.NotLike(pattern) - assert.Equal(t, expNotLike.LHS(), wf) - assert.Equal(t, expNotLike.Op(), NotLikeOp) - assert.Equal(t, expNotLike.RHS(), pattern) + swfet.Equal(expNotLike.LHS(), wf) + swfet.Equal(expNotLike.Op(), NotLikeOp) + swfet.Equal(expNotLike.RHS(), pattern) expILike := wf.ILike(pattern) - assert.Equal(t, expILike.LHS(), wf) - assert.Equal(t, expILike.Op(), ILikeOp) - assert.Equal(t, expILike.RHS(), pattern) + swfet.Equal(expILike.LHS(), wf) + swfet.Equal(expILike.Op(), ILikeOp) + swfet.Equal(expILike.RHS(), pattern) expNotILike := wf.NotILike(pattern) - assert.Equal(t, expNotILike.LHS(), wf) - assert.Equal(t, expNotILike.Op(), NotILikeOp) - assert.Equal(t, expNotILike.RHS(), pattern) + swfet.Equal(expNotILike.LHS(), wf) + swfet.Equal(expNotILike.Op(), NotILikeOp) + swfet.Equal(expNotILike.RHS(), pattern) vals := []interface{}{1, 2} expIn := wf.In(vals) - assert.Equal(t, expIn.LHS(), wf) - assert.Equal(t, expIn.Op(), InOp) - assert.Equal(t, expIn.RHS(), vals) + swfet.Equal(expIn.LHS(), wf) + swfet.Equal(expIn.Op(), InOp) + swfet.Equal(expIn.RHS(), vals) expNotIn := wf.NotIn(vals) - assert.Equal(t, expNotIn.LHS(), wf) - assert.Equal(t, expNotIn.Op(), NotInOp) - assert.Equal(t, expNotIn.RHS(), vals) + swfet.Equal(expNotIn.LHS(), wf) + swfet.Equal(expNotIn.Op(), NotInOp) + swfet.Equal(expNotIn.RHS(), vals) obj := 1 expIs := wf.Is(obj) - assert.Equal(t, expIs.LHS(), wf) - assert.Equal(t, expIs.Op(), IsOp) - assert.Equal(t, expIs.RHS(), obj) + swfet.Equal(expIs.LHS(), wf) + swfet.Equal(expIs.Op(), IsOp) + swfet.Equal(expIs.RHS(), obj) expIsNot := wf.IsNot(obj) - assert.Equal(t, expIsNot.LHS(), wf) - assert.Equal(t, expIsNot.Op(), IsNotOp) - assert.Equal(t, expIsNot.RHS(), obj) + swfet.Equal(expIsNot.LHS(), wf) + swfet.Equal(expIsNot.Op(), IsNotOp) + swfet.Equal(expIsNot.RHS(), obj) expIsNull := wf.IsNull() - assert.Equal(t, expIsNull.LHS(), wf) - assert.Equal(t, expIsNull.Op(), IsOp) - assert.Nil(t, expIsNull.RHS()) + swfet.Equal(expIsNull.LHS(), wf) + swfet.Equal(expIsNull.Op(), IsOp) + swfet.Nil(expIsNull.RHS()) expIsNotNull := wf.IsNotNull() - assert.Equal(t, expIsNotNull.LHS(), wf) - assert.Equal(t, expIsNotNull.Op(), IsNotOp) - assert.Nil(t, expIsNotNull.RHS()) + swfet.Equal(expIsNotNull.LHS(), wf) + swfet.Equal(expIsNotNull.Op(), IsNotOp) + swfet.Nil(expIsNotNull.RHS()) expIsTrue := wf.IsTrue() - assert.Equal(t, expIsTrue.LHS(), wf) - assert.Equal(t, expIsTrue.Op(), IsOp) - assert.Equal(t, expIsTrue.RHS(), true) + swfet.Equal(expIsTrue.LHS(), wf) + swfet.Equal(expIsTrue.Op(), IsOp) + swfet.Equal(expIsTrue.RHS(), true) expIsNotTrue := wf.IsNotTrue() - assert.Equal(t, expIsNotTrue.LHS(), wf) - assert.Equal(t, expIsNotTrue.Op(), IsNotOp) - assert.Equal(t, expIsNotTrue.RHS(), true) + swfet.Equal(expIsNotTrue.LHS(), wf) + swfet.Equal(expIsNotTrue.Op(), IsNotOp) + swfet.Equal(expIsNotTrue.RHS(), true) expIsFalse := wf.IsFalse() - assert.Equal(t, expIsFalse.LHS(), wf) - assert.Equal(t, expIsFalse.Op(), IsOp) - assert.Equal(t, expIsFalse.RHS(), false) + swfet.Equal(expIsFalse.LHS(), wf) + swfet.Equal(expIsFalse.Op(), IsOp) + swfet.Equal(expIsFalse.RHS(), false) expIsNotFalse := wf.IsNotFalse() - assert.Equal(t, expIsNotFalse.LHS(), wf) - assert.Equal(t, expIsNotFalse.Op(), IsNotOp) - assert.Equal(t, expIsNotFalse.RHS(), false) + swfet.Equal(expIsNotFalse.LHS(), wf) + swfet.Equal(expIsNotFalse.Op(), IsNotOp) + swfet.Equal(expIsNotFalse.RHS(), false) } diff --git a/exp/window_test.go b/exp/window_test.go index dccb08c6..e643e680 100644 --- a/exp/window_test.go +++ b/exp/window_test.go @@ -3,7 +3,6 @@ package exp import ( "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -16,75 +15,69 @@ func TestWindowExpressionSuite(t *testing.T) { } func (wet *windowExpressionTest) TestClone() { - t := wet.T() - w := NewWindowExpression("w", "", nil, nil) + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, nil, nil) w2 := w.Clone() - assert.Equal(t, w, w2) + wet.Equal(w, w2) } func (wet *windowExpressionTest) TestExpression() { - t := wet.T() - w := NewWindowExpression("w", "", nil, nil) + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, nil, nil) w2 := w.Expression() - assert.Equal(t, w, w2) + wet.Equal(w, w2) } func (wet *windowExpressionTest) TestName() { - t := wet.T() - w := NewWindowExpression("w", "", nil, nil) + name := NewIdentifierExpression("", "", "w") + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, nil, nil) - assert.Equal(t, "w", w.Name()) + wet.Equal(name, w.Name()) } func (wet *windowExpressionTest) TestPartitionCols() { - t := wet.T() cols := NewColumnListExpression("a", "b") - w := NewWindowExpression("w", "", cols, nil) + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, cols, nil) - assert.Equal(t, cols, w.PartitionCols()) - assert.Equal(t, cols, w.Clone().(WindowExpression).PartitionCols()) + wet.Equal(cols, w.PartitionCols()) + wet.Equal(cols, w.Clone().(WindowExpression).PartitionCols()) } func (wet *windowExpressionTest) TestOrderCols() { - t := wet.T() cols := NewColumnListExpression("a", "b") - w := NewWindowExpression("w", "", nil, cols) + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, nil, cols) - assert.Equal(t, cols, w.OrderCols()) - assert.Equal(t, cols, w.Clone().(WindowExpression).OrderCols()) + wet.Equal(cols, w.OrderCols()) + wet.Equal(cols, w.Clone().(WindowExpression).OrderCols()) } -func (wet *windowExpressionTest) TestPartitonBy() { - t := wet.T() +func (wet *windowExpressionTest) TestPartitionBy() { cols := NewColumnListExpression("a", "b") - w := NewWindowExpression("w", "", nil, nil).PartitionBy("a", "b") + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, nil, nil).PartitionBy("a", "b") - assert.Equal(t, cols, w.PartitionCols()) + wet.Equal(cols, w.PartitionCols()) } func (wet *windowExpressionTest) TestOrderBy() { - t := wet.T() cols := NewColumnListExpression("a", "b") - w := NewWindowExpression("w", "", nil, nil).OrderBy("a", "b") + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), nil, nil, nil).OrderBy("a", "b") - assert.Equal(t, cols, w.OrderCols()) + wet.Equal(cols, w.OrderCols()) } func (wet *windowExpressionTest) TestParent() { - t := wet.T() - w := NewWindowExpression("w", "w1", nil, nil) + parent := NewIdentifierExpression("", "", "w1") + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), parent, nil, nil) - assert.Equal(t, "w1", w.Parent()) + wet.Equal(parent, w.Parent()) } func (wet *windowExpressionTest) TestInherit() { - t := wet.T() - w := NewWindowExpression("w", "w1", nil, nil) + parent := NewIdentifierExpression("", "", "w1") + w := NewWindowExpression(NewIdentifierExpression("", "", "w"), parent, nil, nil) - assert.Equal(t, "w1", w.Parent()) + wet.Equal(parent, w.Parent()) w = w.Inherit("w2") - assert.Equal(t, "w2", w.Parent()) + wet.Equal(NewIdentifierExpression("", "", "w2"), w.Parent()) } diff --git a/expressions.go b/expressions.go index 4d3e3770..b7281358 100644 --- a/expressions.go +++ b/expressions.go @@ -16,7 +16,7 @@ type ( ) // emptyWindow is an empty WINDOW clause without name -var emptyWindow = exp.NewWindowExpression("", "", nil, nil) +var emptyWindow = exp.NewWindowExpression(nil, nil, nil, nil) const ( Wait = exp.Wait @@ -72,19 +72,6 @@ func newIdentifierFunc(name string, col interface{}) exp.SQLFunctionExpression { return Func(name, col) } -// Create a new SQLWindowFunctionExpression with the given name and arguments -func WFunc(name string, args ...interface{}) exp.SQLWindowFunctionExpression { - return exp.NewSQLWindowFunctionExpression(name, args...) -} - -// used internally to normalize the column name if passed in as a string it should be turned into an identifier -func newIdentifierWinFunc(name string, col interface{}) exp.SQLWindowFunctionExpression { - if s, ok := col.(string); ok { - col = I(s) - } - return WFunc(name, col) -} - // Creates a new DISTINCT sql function // DISTINCT("a") -> DISTINCT("a") // DISTINCT(I("a")) -> DISTINCT("a") @@ -130,46 +117,53 @@ func SUM(col interface{}) exp.SQLFunctionExpression { return newIdentifierFunc(" // COALESCE(I("a"), "a") -> COALESCE("a", 'a') // COALESCE(I("a"), I("b"), nil) -> COALESCE("a", "b", NULL) func COALESCE(vals ...interface{}) exp.SQLFunctionExpression { - return exp.NewSQLFunctionExpression("COALESCE", vals...) + return Func("COALESCE", vals...) } -func ROW_NUMBER() exp.SQLWindowFunctionExpression { - return WFunc("ROW_NUMBER") +// nolint: golint +func ROW_NUMBER() exp.SQLFunctionExpression { + return Func("ROW_NUMBER") } -func RANK() exp.SQLWindowFunctionExpression { - return WFunc("RANK") +func RANK() exp.SQLFunctionExpression { + return Func("RANK") } -func DENSE_RANK() exp.SQLWindowFunctionExpression { - return WFunc("DENSE_RANK") +// nolint: golint +func DENSE_RANK() exp.SQLFunctionExpression { + return Func("DENSE_RANK") } -func PERCENT_RANK() exp.SQLWindowFunctionExpression { - return WFunc("PERCENT_RANK") +// nolint: golint +func PERCENT_RANK() exp.SQLFunctionExpression { + return Func("PERCENT_RANK") } -func CUME_DIST() exp.SQLWindowFunctionExpression { - return WFunc("CUME_DIST") +// nolint: golint +func CUME_DIST() exp.SQLFunctionExpression { + return Func("CUME_DIST") } -func NTILE(n int) exp.SQLWindowFunctionExpression { - return newIdentifierWinFunc("NTILE", n) +func NTILE(n int) exp.SQLFunctionExpression { + return Func("NTILE", n) } -func FIRST_VALUE(val interface{}) exp.SQLWindowFunctionExpression { - return newIdentifierWinFunc("FIRST_VALUE", val) +// nolint: golint +func FIRST_VALUE(val interface{}) exp.SQLFunctionExpression { + return newIdentifierFunc("FIRST_VALUE", val) } -func LAST_VALUE(val interface{}) exp.SQLWindowFunctionExpression { - return newIdentifierWinFunc("LAST_VALUE", val) +// nolint: golint +func LAST_VALUE(val interface{}) exp.SQLFunctionExpression { + return newIdentifierFunc("LAST_VALUE", val) } -func NTH_VALUE(val interface{}, nth int) exp.SQLWindowFunctionExpression { +// nolint: golint +func NTH_VALUE(val interface{}, nth int) exp.SQLFunctionExpression { if s, ok := val.(string); ok { val = I(s) } - return WFunc("NTH_VALUE", val, nth) + return Func("NTH_VALUE", val, nth) } // Creates a new Identifier, the generated sql will use adapter specific quoting or '"' by default, this ensures case @@ -231,13 +225,14 @@ func T(table string) exp.IdentifierExpression { // W("w", "w1").PartitionBy("a") -> "w" AS ("w1" PARTITION BY "a") // W("w", "w1").PartitionBy("a").OrderBy("b") -> "w" AS ("w1" PARTITION BY "a" ORDER BY "b") func W(ws ...string) exp.WindowExpression { - if l := len(ws); l > 0 { - if l == 1 { - return exp.NewWindowExpression(ws[0], "", nil, nil) - } - return exp.NewWindowExpression(ws[0], ws[1], nil, nil) + switch len(ws) { + case 0: + return emptyWindow + case 1: + return exp.NewWindowExpression(I(ws[0]), nil, nil, nil) + default: + return exp.NewWindowExpression(I(ws[0]), I(ws[1]), nil, nil) } - return emptyWindow } // Creates a new ON clause to be used within a join diff --git a/expressions_example_test.go b/expressions_example_test.go index ee726780..49160b58 100644 --- a/expressions_example_test.go +++ b/expressions_example_test.go @@ -1721,7 +1721,7 @@ func ExampleW() { ds = goqu.From("test"). Select( - goqu.ROW_NUMBER().OverName("w"), + goqu.ROW_NUMBER().OverName(goqu.I("w")), ). Windows( goqu.W("w").PartitionBy("a").OrderBy(goqu.I("b").Asc()), @@ -1731,7 +1731,7 @@ func ExampleW() { ds = goqu.From("test"). Select( - goqu.ROW_NUMBER().OverName("w1"), + goqu.ROW_NUMBER().OverName(goqu.I("w1")), ). Windows( goqu.W("w1").PartitionBy("a"), diff --git a/expressions_test.go b/expressions_test.go new file mode 100644 index 00000000..3b837af0 --- /dev/null +++ b/expressions_test.go @@ -0,0 +1,174 @@ +package goqu + +import ( + "testing" + + "github.com/doug-martin/goqu/v8/exp" + "github.com/stretchr/testify/suite" +) + +type ( + goquExpressionsSuite struct { + suite.Suite + } +) + +func (ges *goquExpressionsSuite) TestCast() { + ges.Equal(exp.NewCastExpression(C("test"), "string"), Cast(C("test"), "string")) +} + +func (ges *goquExpressionsSuite) TestDoNothing() { + ges.Equal(exp.NewDoNothingConflictExpression(), DoNothing()) +} + +func (ges *goquExpressionsSuite) TestDoUpdate() { + ges.Equal(exp.NewDoUpdateConflictExpression("test", Record{"a": "b"}), DoUpdate("test", Record{"a": "b"})) +} + +func (ges *goquExpressionsSuite) TestOr() { + e1 := C("a").Eq("b") + e2 := C("b").Eq(2) + ges.Equal(exp.NewExpressionList(exp.OrType, e1, e2), Or(e1, e2)) +} + +func (ges *goquExpressionsSuite) TestAnd() { + e1 := C("a").Eq("b") + e2 := C("b").Eq(2) + ges.Equal(exp.NewExpressionList(exp.AndType, e1, e2), And(e1, e2)) +} + +func (ges *goquExpressionsSuite) TestFunc() { + ges.Equal(exp.NewSQLFunctionExpression("count", L("*")), Func("count", L("*"))) +} + +func (ges *goquExpressionsSuite) TestDISTINCT() { + ges.Equal(exp.NewSQLFunctionExpression("DISTINCT", I("col")), DISTINCT("col")) +} + +func (ges *goquExpressionsSuite) TestCOUNT() { + ges.Equal(exp.NewSQLFunctionExpression("COUNT", I("col")), COUNT("col")) +} + +func (ges *goquExpressionsSuite) TestMIN() { + ges.Equal(exp.NewSQLFunctionExpression("MIN", I("col")), MIN("col")) +} + +func (ges *goquExpressionsSuite) TestMAX() { + ges.Equal(exp.NewSQLFunctionExpression("MAX", I("col")), MAX("col")) +} + +func (ges *goquExpressionsSuite) TestAVG() { + ges.Equal(exp.NewSQLFunctionExpression("AVG", I("col")), AVG("col")) +} + +func (ges *goquExpressionsSuite) TestFIRST() { + ges.Equal(exp.NewSQLFunctionExpression("FIRST", I("col")), FIRST("col")) +} + +func (ges *goquExpressionsSuite) TestLAST() { + ges.Equal(exp.NewSQLFunctionExpression("LAST", I("col")), LAST("col")) +} + +func (ges *goquExpressionsSuite) TestSUM() { + ges.Equal(exp.NewSQLFunctionExpression("SUM", I("col")), SUM("col")) +} + +func (ges *goquExpressionsSuite) TestCOALESCE() { + ges.Equal(exp.NewSQLFunctionExpression("COALESCE", I("col"), nil), COALESCE(I("col"), nil)) +} + +func (ges *goquExpressionsSuite) TestROW_NUMBER() { + ges.Equal(exp.NewSQLFunctionExpression("ROW_NUMBER"), ROW_NUMBER()) +} + +func (ges *goquExpressionsSuite) TestRANK() { + ges.Equal(exp.NewSQLFunctionExpression("RANK"), RANK()) +} + +func (ges *goquExpressionsSuite) TestDENSE_RANK() { + ges.Equal(exp.NewSQLFunctionExpression("DENSE_RANK"), DENSE_RANK()) +} + +func (ges *goquExpressionsSuite) TestPERCENT_RANK() { + ges.Equal(exp.NewSQLFunctionExpression("PERCENT_RANK"), PERCENT_RANK()) +} + +func (ges *goquExpressionsSuite) TestCUME_DIST() { + ges.Equal(exp.NewSQLFunctionExpression("CUME_DIST"), CUME_DIST()) +} + +func (ges *goquExpressionsSuite) TestNTILE() { + ges.Equal(exp.NewSQLFunctionExpression("NTILE", 1), NTILE(1)) +} + +func (ges *goquExpressionsSuite) TestFIRST_VALUE() { + ges.Equal(exp.NewSQLFunctionExpression("FIRST_VALUE", I("col")), FIRST_VALUE("col")) +} + +func (ges *goquExpressionsSuite) TestLAST_VALUE() { + ges.Equal(exp.NewSQLFunctionExpression("LAST_VALUE", I("col")), LAST_VALUE("col")) +} + +func (ges *goquExpressionsSuite) TestNTH_VALUE() { + ges.Equal(exp.NewSQLFunctionExpression("NTH_VALUE", I("col"), 1), NTH_VALUE("col", 1)) + ges.Equal(exp.NewSQLFunctionExpression("NTH_VALUE", I("col"), 1), NTH_VALUE(C("col"), 1)) +} + +func (ges *goquExpressionsSuite) TestI() { + ges.Equal(exp.NewIdentifierExpression("s", "t", "c"), I("s.t.c")) +} + +func (ges *goquExpressionsSuite) TestC() { + ges.Equal(exp.NewIdentifierExpression("", "", "c"), C("c")) +} + +func (ges *goquExpressionsSuite) TestS() { + ges.Equal(exp.NewIdentifierExpression("s", "", ""), S("s")) +} + +func (ges *goquExpressionsSuite) TestT() { + ges.Equal(exp.NewIdentifierExpression("", "t", ""), T("t")) +} + +func (ges *goquExpressionsSuite) TestW() { + ges.Equal(emptyWindow, W()) + ges.Equal(exp.NewWindowExpression(I("a"), nil, nil, nil), W("a")) + ges.Equal(exp.NewWindowExpression(I("a"), I("b"), nil, nil), W("a", "b")) + ges.Equal(exp.NewWindowExpression(I("a"), I("b"), nil, nil), W("a", "b", "c")) +} + +func (ges *goquExpressionsSuite) TestOn() { + ges.Equal(exp.NewJoinOnCondition(Ex{"a": "b"}), On(Ex{"a": "b"})) +} + +func (ges *goquExpressionsSuite) TestUsing() { + ges.Equal(exp.NewJoinUsingCondition("a", "b"), Using("a", "b")) +} + +func (ges *goquExpressionsSuite) TestL() { + ges.Equal(exp.NewLiteralExpression("? + ?", 1, 2), L("? + ?", 1, 2)) +} + +func (ges *goquExpressionsSuite) TestLiteral() { + ges.Equal(exp.NewLiteralExpression("? + ?", 1, 2), Literal("? + ?", 1, 2)) +} + +func (ges *goquExpressionsSuite) TestV() { + ges.Equal(exp.NewLiteralExpression("?", "a"), V("a")) +} + +func (ges *goquExpressionsSuite) TestRange() { + ges.Equal(exp.NewRangeVal("a", "b"), Range("a", "b")) +} + +func (ges *goquExpressionsSuite) TestStar() { + ges.Equal(exp.NewLiteralExpression("*"), Star()) +} + +func (ges *goquExpressionsSuite) TestDefault() { + ges.Equal(exp.Default(), Default()) +} + +func TestGoquExpressions(t *testing.T) { + suite.Run(t, new(goquExpressionsSuite)) +} diff --git a/select_dataset_example_test.go b/select_dataset_example_test.go index 39e891ca..1289bbdf 100644 --- a/select_dataset_example_test.go +++ b/select_dataset_example_test.go @@ -329,6 +329,49 @@ func ExampleSelectDataset_Having() { // SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000) } +func ExampleSelectDataset_Windows() { + ds := goqu.From("test"). + Select( + goqu.ROW_NUMBER().Over(goqu.W().PartitionBy("a").OrderBy(goqu.I("b").Asc())), + ) + query, args, _ := ds.ToSQL() + fmt.Println(query, args) + + ds = goqu.From("test"). + Select( + goqu.ROW_NUMBER().OverName(goqu.I("w")), + ). + Windows( + goqu.W("w").PartitionBy("a").OrderBy(goqu.I("b").Asc()), + ) + query, args, _ = ds.ToSQL() + fmt.Println(query, args) + + ds = goqu.From("test"). + Select( + goqu.ROW_NUMBER().OverName(goqu.I("w1")), + ). + Windows( + goqu.W("w1").PartitionBy("a"), + goqu.W("w").Inherit("w1").OrderBy(goqu.I("b").Asc()), + ) + query, args, _ = ds.ToSQL() + fmt.Println(query, args) + + ds = goqu.From("test").Select( + goqu.ROW_NUMBER().Over(goqu.W().Inherit("w").OrderBy("b")), + ).Windows( + goqu.W("w").PartitionBy("a"), + ) + query, args, _ = ds.ToSQL() + fmt.Println(query, args) + // Output + // SELECT ROW_NUMBER() OVER (PARTITION BY "a" ORDER BY "b" ASC) FROM "test" [] + // SELECT ROW_NUMBER() OVER "w" FROM "test" WINDOW "w" AS (PARTITION BY "a" ORDER BY "b" ASC) [] + // SELECT ROW_NUMBER() OVER "w" FROM "test" WINDOW "w1" AS (PARTITION BY "a"), "w" AS ("w1" ORDER BY "b" ASC) [] + // SELECT ROW_NUMBER() OVER ("w" ORDER BY "b") FROM "test" WINDOW "w" AS (PARTITION BY "a") [] +} + func ExampleSelectDataset_Where() { // By default everything is anded together sql, _, _ := goqu.From("test").Where(goqu.Ex{ diff --git a/sqlgen/expression_sql_generator.go b/sqlgen/expression_sql_generator.go index f7afd2b6..275df752 100644 --- a/sqlgen/expression_sql_generator.go +++ b/sqlgen/expression_sql_generator.go @@ -34,7 +34,8 @@ var ( TrueLiteral = exp.NewLiteralExpression("TRUE") FalseLiteral = exp.NewLiteralExpression("FALSE") - errEmptyIdentifier = errors.New(`a empty identifier was encountered, please specify a "schema", "table" or "column"`) + errEmptyIdentifier = errors.New(`a empty identifier was encountered, please specify a "schema", "table" or "column"`) + errUnexpectedNamedWindow = errors.New(`unexpected named window function`) ) func errUnsupportedExpressionType(e exp.Expression) error { @@ -158,6 +159,10 @@ func (esg *expressionSQLGenerator) expressionSQL(b sb.SQLBuilder, expression exp esg.updateExpressionSQL(b, e) case exp.SQLFunctionExpression: esg.sqlFunctionExpressionSQL(b, e) + case exp.SQLWindowFunctionExpression: + esg.sqlWindowFunctionExpression(b, e) + case exp.WindowExpression: + esg.windowExpressionSQL(b, e) case exp.CastExpression: esg.castExpressionSQL(b, e) case exp.AppendableExpression: @@ -481,6 +486,63 @@ func (esg *expressionSQLGenerator) sqlFunctionExpressionSQL(b sb.SQLBuilder, sql esg.Generate(b, sqlFunc.Args()) } +func (esg *expressionSQLGenerator) sqlWindowFunctionExpression(b sb.SQLBuilder, sqlWinFunc exp.SQLWindowFunctionExpression) { + if !esg.dialectOptions.SupportsWindowFunction { + b.SetError(errWindowNotSupported(esg.dialect)) + return + } + esg.Generate(b, sqlWinFunc.Func()) + b.Write(esg.dialectOptions.WindowOverFragment) + switch { + case sqlWinFunc.HasWindowName(): + esg.Generate(b, sqlWinFunc.WindowName()) + case sqlWinFunc.HasWindow(): + if sqlWinFunc.Window().HasName() { + b.SetError(errUnexpectedNamedWindow) + return + } + esg.Generate(b, sqlWinFunc.Window()) + default: + esg.Generate(b, exp.NewWindowExpression(nil, nil, nil, nil)) + } +} + +func (esg *expressionSQLGenerator) windowExpressionSQL(b sb.SQLBuilder, we exp.WindowExpression) { + if !esg.dialectOptions.SupportsWindowFunction { + b.SetError(errWindowNotSupported(esg.dialect)) + return + } + if we.HasName() { + esg.Generate(b, we.Name()) + b.Write(esg.dialectOptions.AsFragment) + } + b.WriteRunes(esg.dialectOptions.LeftParenRune) + + hasPartition := we.HasPartitionBy() + hasOrder := we.HasOrder() + + if we.HasParent() { + esg.Generate(b, we.Parent()) + if hasPartition || hasOrder { + b.WriteRunes(esg.dialectOptions.SpaceRune) + } + } + + if hasPartition { + b.Write(esg.dialectOptions.WindowPartitionByFragment) + esg.Generate(b, we.PartitionCols()) + if hasOrder { + b.WriteRunes(esg.dialectOptions.SpaceRune) + } + } + if hasOrder { + b.Write(esg.dialectOptions.WindowOrderByFragment) + esg.Generate(b, we.OrderCols()) + } + + b.WriteRunes(esg.dialectOptions.RightParenRune) +} + // Generates SQL for a CastExpression // I("a").Cast("NUMERIC") -> CAST("a" AS NUMERIC) func (esg *expressionSQLGenerator) castExpressionSQL(b sb.SQLBuilder, cast exp.CastExpression) { diff --git a/sqlgen/expression_sql_generator_test.go b/sqlgen/expression_sql_generator_test.go index 5b28e07c..30129da1 100644 --- a/sqlgen/expression_sql_generator_test.go +++ b/sqlgen/expression_sql_generator_test.go @@ -706,6 +706,130 @@ func (esgs *expressionSQLGeneratorSuite) TestGenerate_SQLFunctionExpression() { ) } +func (esgs *expressionSQLGeneratorSuite) TestGenerate_SQLWindowFunctionExpression() { + sqlWinFunc := exp.NewSQLWindowFunctionExpression( + exp.NewSQLFunctionExpression("some_func"), + nil, + exp.NewWindowExpression( + nil, + exp.NewIdentifierExpression("", "", "win"), + nil, + nil, + ), + ) + sqlWinFuncFromWindow := exp.NewSQLWindowFunctionExpression( + exp.NewSQLFunctionExpression("some_func"), + exp.NewIdentifierExpression("", "", "win"), + nil, + ) + + emptyWinFunc := exp.NewSQLWindowFunctionExpression( + exp.NewSQLFunctionExpression("some_func"), + nil, + nil, + ) + badNamedSQLWinFuncInherit := exp.NewSQLWindowFunctionExpression( + exp.NewSQLFunctionExpression("some_func"), + nil, + exp.NewWindowExpression( + exp.NewIdentifierExpression("", "", "w"), + nil, + nil, + nil, + ), + ) + esgs.assertCases( + NewExpressionSQLGenerator("test", DefaultDialectOptions()), + expressionTestCase{val: sqlWinFunc, sql: `some_func() OVER ("win")`}, + expressionTestCase{val: sqlWinFunc, sql: `some_func() OVER ("win")`, isPrepared: true}, + + expressionTestCase{val: sqlWinFuncFromWindow, sql: `some_func() OVER "win"`}, + expressionTestCase{val: sqlWinFuncFromWindow, sql: `some_func() OVER "win"`, isPrepared: true}, + + expressionTestCase{val: emptyWinFunc, sql: `some_func() OVER ()`}, + expressionTestCase{val: emptyWinFunc, sql: `some_func() OVER ()`, isPrepared: true}, + + expressionTestCase{val: badNamedSQLWinFuncInherit, err: errUnexpectedNamedWindow.Error()}, + expressionTestCase{val: badNamedSQLWinFuncInherit, err: errUnexpectedNamedWindow.Error(), isPrepared: true}, + ) + opts := DefaultDialectOptions() + opts.SupportsWindowFunction = false + esgs.assertCases( + NewExpressionSQLGenerator("test", opts), + expressionTestCase{val: sqlWinFunc, err: errWindowNotSupported("test").Error()}, + expressionTestCase{val: sqlWinFunc, err: errWindowNotSupported("test").Error(), isPrepared: true}, + ) +} + +func (esgs *expressionSQLGeneratorSuite) TestGenerate_WindowExpression() { + opts := DefaultDialectOptions() + opts.WindowPartitionByFragment = []byte("partition by ") + opts.WindowOrderByFragment = []byte("order by ") + + emptySQLWinFunc := exp.NewWindowExpression(nil, nil, nil, nil) + namedSQLWinFunc := exp.NewWindowExpression( + exp.NewIdentifierExpression("", "", "w"), nil, nil, nil, + ) + inheritSQLWinFunc := exp.NewWindowExpression( + nil, exp.NewIdentifierExpression("", "", "w"), nil, nil, + ) + partitionBySQLWinFunc := exp.NewWindowExpression( + nil, nil, exp.NewColumnListExpression("a", "b"), nil, + ) + orderBySQLWinFunc := exp.NewWindowExpression( + nil, nil, nil, exp.NewOrderedColumnList( + exp.NewIdentifierExpression("", "", "a").Asc(), + exp.NewIdentifierExpression("", "", "b").Desc(), + ), + ) + + namedInheritPartitionOrderSQLWinFunc := exp.NewWindowExpression( + exp.NewIdentifierExpression("", "", "w1"), + exp.NewIdentifierExpression("", "", "w2"), + exp.NewColumnListExpression("a", "b"), + exp.NewOrderedColumnList( + exp.NewIdentifierExpression("", "", "a").Asc(), + exp.NewIdentifierExpression("", "", "b").Desc(), + ), + ) + + esgs.assertCases( + NewExpressionSQLGenerator("test", opts), + expressionTestCase{val: emptySQLWinFunc, sql: `()`}, + expressionTestCase{val: emptySQLWinFunc, sql: `()`, isPrepared: true}, + + expressionTestCase{val: namedSQLWinFunc, sql: `"w" AS ()`}, + expressionTestCase{val: namedSQLWinFunc, sql: `"w" AS ()`, isPrepared: true}, + + expressionTestCase{val: inheritSQLWinFunc, sql: `("w")`}, + expressionTestCase{val: inheritSQLWinFunc, sql: `("w")`, isPrepared: true}, + + expressionTestCase{val: partitionBySQLWinFunc, sql: `(partition by "a", "b")`}, + expressionTestCase{val: partitionBySQLWinFunc, sql: `(partition by "a", "b")`, isPrepared: true}, + + expressionTestCase{val: orderBySQLWinFunc, sql: `(order by "a" ASC, "b" DESC)`}, + expressionTestCase{val: orderBySQLWinFunc, sql: `(order by "a" ASC, "b" DESC)`, isPrepared: true}, + + expressionTestCase{ + val: namedInheritPartitionOrderSQLWinFunc, + sql: `"w1" AS ("w2" partition by "a", "b" order by "a" ASC, "b" DESC)`, + }, + expressionTestCase{ + val: namedInheritPartitionOrderSQLWinFunc, + sql: `"w1" AS ("w2" partition by "a", "b" order by "a" ASC, "b" DESC)`, + isPrepared: true, + }, + ) + + opts = DefaultDialectOptions() + opts.SupportsWindowFunction = false + esgs.assertCases( + NewExpressionSQLGenerator("test", opts), + expressionTestCase{val: emptySQLWinFunc, err: errWindowNotSupported("test").Error()}, + expressionTestCase{val: emptySQLWinFunc, err: errWindowNotSupported("test").Error(), isPrepared: true}, + ) +} + func (esgs *expressionSQLGeneratorSuite) TestGenerate_CastExpression() { cast := exp.NewIdentifierExpression("", "", "a").Cast("DATE") esgs.assertCases( diff --git a/sqlgen/select_sql_generator.go b/sqlgen/select_sql_generator.go index b9d358de..721657a4 100644 --- a/sqlgen/select_sql_generator.go +++ b/sqlgen/select_sql_generator.go @@ -32,6 +32,12 @@ func errDistinctOnNotSupported(dialect string) error { return errors.New("dialect does not support DISTINCT ON clause [dialect=%s]", dialect) } +func errWindowNotSupported(dialect string) error { + return errors.New("dialect does not support WINDOW clause [dialect=%s]", dialect) +} + +var errNoWindowName = errors.New("window expresion has no valid name") + func NewSelectSQLGenerator(dialect string, do *SQLDialectOptions) SelectSQLGenerator { return &selectSQLGenerator{newCommonSQLGenerator(dialect, do)} } @@ -60,6 +66,8 @@ func (ssg *selectSQLGenerator) Generate(b sb.SQLBuilder, clauses exp.SelectClaus ssg.GroupBySQL(b, clauses.GroupBy()) case HavingSQLFragment: ssg.HavingSQL(b, clauses.Having()) + case WindowSQLFragment: + ssg.WindowsSQL(b, clauses.Windows()) case CompoundsSQLFragment: ssg.CompoundsSQL(b, clauses.Compounds()) case OrderSQLFragment: @@ -184,6 +192,27 @@ func (ssg *selectSQLGenerator) ForSQL(b sb.SQLBuilder, lockingClause exp.Lock) { } } +func (ssg *selectSQLGenerator) WindowsSQL(b sb.SQLBuilder, windows []exp.WindowExpression) { + weLen := len(windows) + if weLen == 0 { + return + } + if !ssg.dialectOptions.SupportsWindowFunction { + b.SetError(errWindowNotSupported(ssg.dialect)) + return + } + b.Write(ssg.dialectOptions.WindowFragment) + for i, we := range windows { + if !we.HasName() { + b.SetError(errNoWindowName) + } + ssg.esg.Generate(b, we) + if i < weLen-1 { + b.WriteRunes(ssg.dialectOptions.CommaRune, ssg.dialectOptions.SpaceRune) + } + } +} + func (ssg *selectSQLGenerator) joinConditionSQL(b sb.SQLBuilder, jc exp.JoinCondition) { switch t := jc.(type) { case exp.JoinOnCondition: diff --git a/sqlgen/select_sql_generator_test.go b/sqlgen/select_sql_generator_test.go index 838f69e4..12195822 100644 --- a/sqlgen/select_sql_generator_test.go +++ b/sqlgen/select_sql_generator_test.go @@ -89,6 +89,54 @@ func (ssgs *selectSQLGeneratorSuite) TestGenerate_WithErroredBuilder() { ssgs.assertErrorSQL(b, `goqu: test error`) } +func (ssgs *selectSQLGeneratorSuite) TestGenerate_withSelectedColumns() { + opts := DefaultDialectOptions() + // make sure the fragments are used + opts.SelectClause = []byte("select") + opts.StarRune = '#' + opts.SupportsDistinctOn = true + + sc := exp.NewSelectClauses() + scCols := sc.SetSelect(exp.NewColumnListExpression("a", "b")) + scFuncs := sc.SetSelect(exp.NewColumnListExpression( + exp.NewSQLFunctionExpression("COUNT", exp.Star()), + exp.NewSQLFunctionExpression("RANK"), + )) + + we := exp.NewWindowExpression( + nil, + nil, + exp.NewColumnListExpression("a", "b"), + exp.NewOrderedColumnList(exp.ParseIdentifier("c").Asc()), + ) + scFuncsPartition := sc.SetSelect(exp.NewColumnListExpression( + exp.NewSQLFunctionExpression("COUNT", exp.Star()).Over(we), + exp.NewSQLFunctionExpression("RANK").Over(we.Inherit("w")), + )) + + ssgs.assertCases( + NewSelectSQLGenerator("test", opts), + selectTestCase{clause: sc, sql: `select #`}, + selectTestCase{clause: sc, sql: `select #`, isPrepared: true}, + + selectTestCase{clause: scCols, sql: `select "a", "b"`}, + selectTestCase{clause: scCols, sql: `select "a", "b"`, isPrepared: true}, + + selectTestCase{clause: scFuncs, sql: `select COUNT(*), RANK()`}, + selectTestCase{clause: scFuncs, sql: `select COUNT(*), RANK()`, isPrepared: true}, + + selectTestCase{ + clause: scFuncsPartition, + sql: `select COUNT(*) OVER (PARTITION BY "a", "b" ORDER BY "c" ASC), RANK() OVER ("w" PARTITION BY "a", "b" ORDER BY "c" ASC)`, + }, + selectTestCase{ + clause: scFuncsPartition, + sql: `select COUNT(*) OVER (PARTITION BY "a", "b" ORDER BY "c" ASC), RANK() OVER ("w" PARTITION BY "a", "b" ORDER BY "c" ASC)`, + isPrepared: true, + }, + ) +} + func (ssgs *selectSQLGeneratorSuite) TestGenerate_withDistinct() { opts := DefaultDialectOptions() // make sure the fragments are used @@ -263,6 +311,131 @@ func (ssgs *selectSQLGeneratorSuite) TestGenerate_withHaving() { ) } +func (ssgs *selectSQLGeneratorSuite) TestGenerate_withWindow() { + opts := DefaultDialectOptions() + opts.WindowFragment = []byte(" window ") + opts.WindowPartitionByFragment = []byte("partition by ") + opts.WindowOrderByFragment = []byte("order by ") + + sc := exp.NewSelectClauses().SetFrom(exp.NewColumnListExpression("test")) + we1 := exp.NewWindowExpression( + exp.NewIdentifierExpression("", "", "w"), + nil, + nil, + nil, + ) + wePartitionBy := we1.PartitionBy("a", "b") + weOrderBy := we1.OrderBy("a", "b") + + weOrderAndPartitionBy := we1.PartitionBy("a", "b").OrderBy("a", "b") + + weInherits := exp.NewWindowExpression( + exp.NewIdentifierExpression("", "", "w2"), + exp.NewIdentifierExpression("", "", "w"), + nil, + nil, + ) + weInheritsPartitionBy := weInherits.PartitionBy("c", "d") + weInheritsOrderBy := weInherits.OrderBy("c", "d") + + weInheritsOrderAndPartitionBy := weInherits.PartitionBy("c", "d").OrderBy("c", "d") + + scNoName := sc.WindowsAppend(exp.NewWindowExpression(nil, nil, nil, nil)) + + scWindow1 := sc.WindowsAppend(we1) + scWindow2 := sc.WindowsAppend(wePartitionBy) + scWindow3 := sc.WindowsAppend(weOrderBy) + scWindow4 := sc.WindowsAppend(weOrderAndPartitionBy) + + scWindow5 := sc.WindowsAppend(we1, weInherits) + scWindow6 := sc.WindowsAppend(we1, weInheritsPartitionBy) + scWindow7 := sc.WindowsAppend(we1, weInheritsOrderBy) + scWindow8 := sc.WindowsAppend(we1, weInheritsOrderAndPartitionBy) + + ssgs.assertCases( + NewSelectSQLGenerator("test", opts), + + selectTestCase{clause: scNoName, err: errNoWindowName.Error()}, + selectTestCase{clause: scNoName, err: errNoWindowName.Error(), isPrepared: true}, + + selectTestCase{clause: scWindow1, sql: `SELECT * FROM "test" window "w" AS ()`}, + selectTestCase{clause: scWindow1, sql: `SELECT * FROM "test" window "w" AS ()`, isPrepared: true}, + + selectTestCase{clause: scWindow2, sql: `SELECT * FROM "test" window "w" AS (partition by "a", "b")`}, + selectTestCase{ + clause: scWindow2, + sql: `SELECT * FROM "test" window "w" AS (partition by "a", "b")`, + isPrepared: true, + }, + + selectTestCase{clause: scWindow3, sql: `SELECT * FROM "test" window "w" AS (order by "a", "b")`}, + selectTestCase{ + clause: scWindow3, + sql: `SELECT * FROM "test" window "w" AS (order by "a", "b")`, + isPrepared: true, + }, + + selectTestCase{ + clause: scWindow4, + sql: `SELECT * FROM "test" window "w" AS (partition by "a", "b" order by "a", "b")`, + }, + selectTestCase{ + clause: scWindow4, + sql: `SELECT * FROM "test" window "w" AS (partition by "a", "b" order by "a", "b")`, + isPrepared: true, + }, + + selectTestCase{ + clause: scWindow5, + sql: `SELECT * FROM "test" window "w" AS (), "w2" AS ("w")`, + }, + selectTestCase{ + clause: scWindow5, + sql: `SELECT * FROM "test" window "w" AS (), "w2" AS ("w")`, + isPrepared: true, + }, + + selectTestCase{ + clause: scWindow6, + sql: `SELECT * FROM "test" window "w" AS (), "w2" AS ("w" partition by "c", "d")`, + }, + selectTestCase{ + clause: scWindow6, + sql: `SELECT * FROM "test" window "w" AS (), "w2" AS ("w" partition by "c", "d")`, + isPrepared: true, + }, + + selectTestCase{ + clause: scWindow7, + sql: `SELECT * FROM "test" window "w" AS (), "w2" AS ("w" order by "c", "d")`, + }, + selectTestCase{ + clause: scWindow7, + sql: `SELECT * FROM "test" window "w" AS (), "w2" AS ("w" order by "c", "d")`, + isPrepared: true, + }, + + selectTestCase{ + clause: scWindow8, + sql: `SELECT * FROM "test" window "w" AS (), "w2" AS ("w" partition by "c", "d" order by "c", "d")`, + }, + selectTestCase{ + clause: scWindow8, + sql: `SELECT * FROM "test" window "w" AS (), "w2" AS ("w" partition by "c", "d" order by "c", "d")`, + isPrepared: true, + }, + ) + + opts = DefaultDialectOptions() + opts.SupportsWindowFunction = false + ssgs.assertCases( + NewSelectSQLGenerator("test", opts), + + selectTestCase{clause: scWindow1, err: errWindowNotSupported("test").Error()}, + selectTestCase{clause: scWindow1, err: errWindowNotSupported("test").Error(), isPrepared: true}, + ) +} + func (ssgs *selectSQLGeneratorSuite) TestGenerate_withOrder() { sc := exp.NewSelectClauses().SetFrom(exp.NewColumnListExpression("test")). SetOrder( diff --git a/sqlgen/sql_dialect_options_test.go b/sqlgen/sql_dialect_options_test.go new file mode 100644 index 00000000..9a4f0f62 --- /dev/null +++ b/sqlgen/sql_dialect_options_test.go @@ -0,0 +1,48 @@ +package sqlgen + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type sqlFragmentTypeSuite struct { + suite.Suite +} + +func (sfts *sqlFragmentTypeSuite) TestOptions_SQLFragmentType() { + for _, tt := range []struct { + typ SQLFragmentType + expectedStr string + }{ + {typ: CommonTableSQLFragment, expectedStr: "CommonTableSQLFragment"}, + {typ: SelectSQLFragment, expectedStr: "SelectSQLFragment"}, + {typ: FromSQLFragment, expectedStr: "FromSQLFragment"}, + {typ: JoinSQLFragment, expectedStr: "JoinSQLFragment"}, + {typ: WhereSQLFragment, expectedStr: "WhereSQLFragment"}, + {typ: GroupBySQLFragment, expectedStr: "GroupBySQLFragment"}, + {typ: HavingSQLFragment, expectedStr: "HavingSQLFragment"}, + {typ: CompoundsSQLFragment, expectedStr: "CompoundsSQLFragment"}, + {typ: OrderSQLFragment, expectedStr: "OrderSQLFragment"}, + {typ: LimitSQLFragment, expectedStr: "LimitSQLFragment"}, + {typ: OffsetSQLFragment, expectedStr: "OffsetSQLFragment"}, + {typ: ForSQLFragment, expectedStr: "ForSQLFragment"}, + {typ: UpdateBeginSQLFragment, expectedStr: "UpdateBeginSQLFragment"}, + {typ: SourcesSQLFragment, expectedStr: "SourcesSQLFragment"}, + {typ: IntoSQLFragment, expectedStr: "IntoSQLFragment"}, + {typ: UpdateSQLFragment, expectedStr: "UpdateSQLFragment"}, + {typ: UpdateFromSQLFragment, expectedStr: "UpdateFromSQLFragment"}, + {typ: ReturningSQLFragment, expectedStr: "ReturningSQLFragment"}, + {typ: InsertBeingSQLFragment, expectedStr: "InsertBeingSQLFragment"}, + {typ: DeleteBeginSQLFragment, expectedStr: "DeleteBeginSQLFragment"}, + {typ: TruncateSQLFragment, expectedStr: "TruncateSQLFragment"}, + {typ: WindowSQLFragment, expectedStr: "WindowSQLFragment"}, + {typ: SQLFragmentType(10000), expectedStr: "10000"}, + } { + sfts.Equal(tt.expectedStr, tt.typ.String()) + } +} + +func TestSQLFragmentType(t *testing.T) { + suite.Run(t, new(sqlFragmentTypeSuite)) +}