diff --git a/dialect/sqlite3/sqlite3.go b/dialect/sqlite3/sqlite3.go
index 48d434f2..1afc842f 100644
--- a/dialect/sqlite3/sqlite3.go
+++ b/dialect/sqlite3/sqlite3.go
@@ -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
diff --git a/docs/selecting.md b/docs/selecting.md
index f35f2aa1..9139aa75 100644
--- a/docs/selecting.md
+++ b/docs/selecting.md
@@ -680,6 +680,22 @@ Output:
SELECT * FROM "test" GROUP BY "age" HAVING (SUM("income") > 1000)
```
+
+
+**[`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)
+```
+
+
**[`With`](https://godoc.org/github.com/doug-martin/goqu/#SelectDataset.With)**
diff --git a/exp/select_clauses.go b/exp/select_clauses.go
index f802a88b..3e1aeba1 100644
--- a/exp/select_clauses.go
+++ b/exp/select_clauses.go
@@ -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
@@ -81,6 +85,7 @@ type (
compounds []CompoundExpression
lock Lock
windows []WindowExpression
+ qualify ExpressionList
}
)
@@ -124,6 +129,7 @@ func (c *selectClauses) clone() *selectClauses {
compounds: c.compounds,
lock: c.lock,
windows: c.windows,
+ qualify: c.qualify,
}
}
@@ -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
}
diff --git a/exp/select_clauses_test.go b/exp/select_clauses_test.go
index 8bfe252f..e8e6fcf1 100644
--- a/exp/select_clauses_test.go
+++ b/exp/select_clauses_test.go
@@ -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)
diff --git a/select_dataset.go b/select_dataset.go
index aa6f1924..5fb0cc91 100644
--- a/select_dataset.go
+++ b/select_dataset.go
@@ -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...))
diff --git a/select_dataset_example_test.go b/select_dataset_example_test.go
index cb5c2a2e..5fa2d522 100644
--- a/select_dataset_example_test.go
+++ b/select_dataset_example_test.go
@@ -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())))
diff --git a/sqlgen/select_sql_generator.go b/sqlgen/select_sql_generator.go
index de322910..8d280ed6 100644
--- a/sqlgen/select_sql_generator.go
+++ b/sqlgen/select_sql_generator.go
@@ -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 {
@@ -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:
@@ -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 {
diff --git a/sqlgen/sql_dialect_options.go b/sqlgen/sql_dialect_options.go
index 3d9a981b..44e0c327 100644
--- a/sqlgen/sql_dialect_options.go
+++ b/sqlgen/sql_dialect_options.go
@@ -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
@@ -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 "))
@@ -276,6 +280,7 @@ type (
// WhereSQLFragment,
// GroupBySQLFragment,
// HavingSQLFragment,
+ // QualifySQLFragment,
// CompoundsSQLFragment,
// OrderSQLFragment,
// LimitSQLFragment,
@@ -336,6 +341,7 @@ const (
WhereSQLFragment
GroupBySQLFragment
HavingSQLFragment
+ QualifySQLFragment
CompoundsSQLFragment
OrderSQLFragment
OrderWithOffsetFetchSQLFragment
@@ -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:
@@ -424,6 +432,7 @@ func DefaultDialectOptions() *SQLDialectOptions {
WrapCompoundsInParens: true,
SupportsWindowFunction: true,
SupportsLateral: true,
+ SupportsQualify: false,
SupportsMultipleUpdateTables: true,
UseFromClauseForMultipleUpdateTables: true,
@@ -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 "),
@@ -566,6 +576,7 @@ func DefaultDialectOptions() *SQLDialectOptions {
WhereSQLFragment,
GroupBySQLFragment,
HavingSQLFragment,
+ QualifySQLFragment,
WindowSQLFragment,
CompoundsSQLFragment,
OrderSQLFragment,
diff --git a/sqlgen/sql_dialect_options_test.go b/sqlgen/sql_dialect_options_test.go
index 018b80ce..61238b41 100644
--- a/sqlgen/sql_dialect_options_test.go
+++ b/sqlgen/sql_dialect_options_test.go
@@ -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"},