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

Add support for Teradata's QUALIFY clause #401

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions dialect/sqlite3/sqlite3.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func DialectOptions() *goqu.SQLDialectOptions {
opts.SupportsDistinctOn = false
opts.SupportsWindowFunction = false
opts.SupportsLateral = false
opts.SupportsQualify = false

opts.PlaceHolderFragment = []byte("?")
opts.IncludePlaceholderNum = false
Expand Down
16 changes: 16 additions & 0 deletions docs/selecting.md
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,22 @@ Output:
SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000)
```


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

```go
sql, _, _ = goqu.From("test").GroupBy("age").Qualify(goqu.SUM("income").Gt(1000)).ToSQL()
fmt.Println(sql)
```

Output:

```
SELECT * FROM "test" GROUP BY "age" Qualify (SUM("income") > 1000)
```


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

Expand Down
29 changes: 29 additions & 0 deletions exp/select_clauses.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ type (
SetWindows(ws []WindowExpression) SelectClauses
WindowsAppend(ws ...WindowExpression) SelectClauses
ClearWindows() SelectClauses

Qualify() ExpressionList
ClearQualify() SelectClauses
QualifyAppend(expressions ...Expression) SelectClauses
}
selectClauses struct {
commonTables []CommonTableExpression
Expand All @@ -81,6 +85,7 @@ type (
compounds []CompoundExpression
lock Lock
windows []WindowExpression
qualify ExpressionList
}
)

Expand Down Expand Up @@ -124,6 +129,7 @@ func (c *selectClauses) clone() *selectClauses {
compounds: c.compounds,
lock: c.lock,
windows: c.windows,
qualify: c.qualify,
}
}

Expand Down Expand Up @@ -243,6 +249,29 @@ func (c *selectClauses) HavingAppend(expressions ...Expression) SelectClauses {
return ret
}

func (c *selectClauses) Qualify() ExpressionList {
return c.qualify
}

func (c *selectClauses) ClearQualify() SelectClauses {
ret := c.clone()
ret.qualify = nil
return ret
}

func (c *selectClauses) QualifyAppend(expressions ...Expression) SelectClauses {
if len(expressions) == 0 {
return c
}
ret := c.clone()
if ret.qualify == nil {
ret.qualify = NewExpressionList(AndType, expressions...)
} else {
ret.qualify = ret.qualify.Append(expressions...)
}
return ret
}

func (c *selectClauses) Lock() Lock {
return c.lock
}
Expand Down
40 changes: 40 additions & 0 deletions exp/select_clauses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,46 @@ func (scs *selectClausesSuite) TestHavingAppend() {
scs.Equal(exp.NewExpressionList(exp.AndType, w, w2), c4.Having())
}

func (scs *selectClausesSuite) TestQualify() {
w := exp.Ex{"a": 1}

c := exp.NewSelectClauses()
c2 := c.QualifyAppend(w)

scs.Nil(c.Qualify())

scs.Equal(exp.NewExpressionList(exp.AndType, w), c2.Qualify())
}

func (scs *selectClausesSuite) TestClearQualify() {
w := exp.Ex{"a": 1}

c := exp.NewSelectClauses().QualifyAppend(w)
c2 := c.ClearQualify()

scs.Equal(exp.NewExpressionList(exp.AndType, w), c.Qualify())

scs.Nil(c2.Qualify())
}

func (scs *selectClausesSuite) TestQualifyAppend() {
w := exp.Ex{"a": 1}
w2 := exp.Ex{"b": 2}

c := exp.NewSelectClauses()
c2 := c.QualifyAppend(w)

c3 := c.QualifyAppend(w).QualifyAppend(w2)

c4 := c.QualifyAppend(w, w2)

scs.Nil(c.Qualify())

scs.Equal(exp.NewExpressionList(exp.AndType, w), c2.Qualify())
scs.Equal(exp.NewExpressionList(exp.AndType, w).Append(w2), c3.Qualify())
scs.Equal(exp.NewExpressionList(exp.AndType, w, w2), c4.Qualify())
}

func (scs *selectClausesSuite) TestWindows() {
w := exp.NewWindowExpression(exp.NewIdentifierExpression("", "", "w"), nil, nil, nil)

Expand Down
5 changes: 5 additions & 0 deletions select_dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,11 @@ func (sd *SelectDataset) Having(expressions ...exp.Expression) *SelectDataset {
return sd.copy(sd.clauses.HavingAppend(expressions...))
}

// Adds a QUALIFY clause. See examples.
func (sd *SelectDataset) Qualify(expressions ...exp.Expression) *SelectDataset {
return sd.copy(sd.clauses.QualifyAppend(expressions...))
}

// Adds a ORDER clause. If the ORDER is currently set it replaces it. See examples.
func (sd *SelectDataset) Order(order ...exp.OrderedExpression) *SelectDataset {
return sd.copy(sd.clauses.SetOrder(order...))
Expand Down
14 changes: 14 additions & 0 deletions select_dataset_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,20 @@ func ExampleSelectDataset_Having() {
// SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000)
}

func ExampleSelectDataset_Qualify() {
opts := goqu.DefaultDialectOptions()
opts.SupportsQualify = true
goqu.RegisterDialect("qualify", opts)
var dialect = goqu.Dialect("qualify")
sql, _, _ := dialect.From("test").Qualify(goqu.SUM("income").Gt(1000)).ToSQL()
fmt.Println(sql)
sql, _, _ = dialect.From("test").GroupBy("age").Qualify(goqu.SUM("income").Gt(1000)).ToSQL()
fmt.Println(sql)
// Output:
// SELECT * FROM "test" QUALIFY (SUM("income") > 1000)
// SELECT * FROM "test" GROUP BY "age" QUALIFY (SUM("income") > 1000)
}

func ExampleSelectDataset_Window() {
ds := goqu.From("test").
Select(goqu.ROW_NUMBER().Over(goqu.W().PartitionBy("a").OrderBy(goqu.I("b").Asc())))
Expand Down
18 changes: 18 additions & 0 deletions sqlgen/select_sql_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ func ErrWindowNotSupported(dialect string) error {
return errors.New("dialect does not support WINDOW clause [dialect=%s]", dialect)
}

func ErrQualifyNotSupported(dialect string) error {
return errors.New("dialect does not support QUALIFY clause [dialect=%s]", dialect)
}

var ErrNoWindowName = errors.New("window expresion has no valid name")

func NewSelectSQLGenerator(dialect string, do *SQLDialectOptions) SelectSQLGenerator {
Expand Down Expand Up @@ -65,6 +69,8 @@ func (ssg *selectSQLGenerator) Generate(b sb.SQLBuilder, clauses exp.SelectClaus
ssg.GroupBySQL(b, clauses.GroupBy())
case HavingSQLFragment:
ssg.HavingSQL(b, clauses.Having())
case QualifySQLFragment:
ssg.QualifySQL(b, clauses.Qualify())
case WindowSQLFragment:
ssg.WindowSQL(b, clauses.Windows())
case CompoundsSQLFragment:
Expand Down Expand Up @@ -164,6 +170,18 @@ func (ssg *selectSQLGenerator) HavingSQL(b sb.SQLBuilder, having exp.ExpressionL
}
}

// Generates the QUALIFY clause for an SQL statement
func (ssg *selectSQLGenerator) QualifySQL(b sb.SQLBuilder, qualify exp.ExpressionList) {
if qualify != nil && len(qualify.Expressions()) > 0 {
if ssg.DialectOptions().SupportsQualify {
b.Write(ssg.DialectOptions().QualifyFragment)
ssg.ExpressionSQLGenerator().Generate(b, qualify)
} else {
b.SetError(ErrQualifyNotSupported(ssg.Dialect()))
}
}
}

// Generates the OFFSET clause for an SQL statement
func (ssg *selectSQLGenerator) OffsetSQL(b sb.SQLBuilder, offset uint) {
if offset > 0 {
Expand Down
11 changes: 11 additions & 0 deletions sqlgen/sql_dialect_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type (
SupportsDistinctOn bool
// Set to true if LATERAL queries are supported (DEFAULT=true)
SupportsLateral bool
// Set to true if the dialect supports QUALIFY expressions (DEFAULT=false)
SupportsQualify bool
// Set to false if the dialect does not require expressions to be wrapped in parens (DEFAULT=true)
WrapCompoundsInParens bool

Expand Down Expand Up @@ -97,6 +99,8 @@ type (
GroupByFragment []byte
// The SQL HAVING clause fragment(DEFAULT=[]byte(" HAVING "))
HavingFragment []byte
// The SQL QUALIFY clause fragment(DEFAULT=[]byte(" QUALIFY "))
QualifyFragment []byte
// The SQL WINDOW clause fragment(DEFAULT=[]byte(" WINDOW "))
WindowFragment []byte
// The SQL WINDOW clause PARTITION BY fragment(DEFAULT=[]byte("PARTITION BY "))
Expand Down Expand Up @@ -276,6 +280,7 @@ type (
// WhereSQLFragment,
// GroupBySQLFragment,
// HavingSQLFragment,
// QualifySQLFragment,
// CompoundsSQLFragment,
// OrderSQLFragment,
// LimitSQLFragment,
Expand Down Expand Up @@ -336,6 +341,7 @@ const (
WhereSQLFragment
GroupBySQLFragment
HavingSQLFragment
QualifySQLFragment
CompoundsSQLFragment
OrderSQLFragment
OrderWithOffsetFetchSQLFragment
Expand Down Expand Up @@ -372,6 +378,8 @@ func (sf SQLFragmentType) String() string {
return "GroupBySQLFragment"
case HavingSQLFragment:
return "HavingSQLFragment"
case QualifySQLFragment:
return "QualifySQLFragment"
case CompoundsSQLFragment:
return "CompoundsSQLFragment"
case OrderSQLFragment:
Expand Down Expand Up @@ -424,6 +432,7 @@ func DefaultDialectOptions() *SQLDialectOptions {
WrapCompoundsInParens: true,
SupportsWindowFunction: true,
SupportsLateral: true,
SupportsQualify: false,

SupportsMultipleUpdateTables: true,
UseFromClauseForMultipleUpdateTables: true,
Expand Down Expand Up @@ -451,6 +460,7 @@ func DefaultDialectOptions() *SQLDialectOptions {
GroupByFragment: []byte(" GROUP BY "),
HavingFragment: []byte(" HAVING "),
WindowFragment: []byte(" WINDOW "),
QualifyFragment: []byte(" QUALIFY "),
WindowPartitionByFragment: []byte("PARTITION BY "),
WindowOrderByFragment: []byte("ORDER BY "),
WindowOverFragment: []byte(" OVER "),
Expand Down Expand Up @@ -566,6 +576,7 @@ func DefaultDialectOptions() *SQLDialectOptions {
WhereSQLFragment,
GroupBySQLFragment,
HavingSQLFragment,
QualifySQLFragment,
WindowSQLFragment,
CompoundsSQLFragment,
OrderSQLFragment,
Expand Down
1 change: 1 addition & 0 deletions sqlgen/sql_dialect_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func (sfts *sqlFragmentTypeSuite) TestOptions_SQLFragmentType() {
{typ: sqlgen.WhereSQLFragment, expectedStr: "WhereSQLFragment"},
{typ: sqlgen.GroupBySQLFragment, expectedStr: "GroupBySQLFragment"},
{typ: sqlgen.HavingSQLFragment, expectedStr: "HavingSQLFragment"},
{typ: sqlgen.QualifySQLFragment, expectedStr: "QualifySQLFragment"},
{typ: sqlgen.CompoundsSQLFragment, expectedStr: "CompoundsSQLFragment"},
{typ: sqlgen.OrderSQLFragment, expectedStr: "OrderSQLFragment"},
{typ: sqlgen.LimitSQLFragment, expectedStr: "LimitSQLFragment"},
Expand Down