Skip to content

Commit

Permalink
Merge pull request #63 from go-gorm/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
tr1v3r authored Aug 30, 2021
2 parents 6ac93a4 + 908cbbd commit dabed4e
Show file tree
Hide file tree
Showing 14 changed files with 259 additions and 96 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ _testmain.go
.DS_Store
/.idea
/.vscode
__debug_bin
/test/
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ The code generator base on [GORM](https://github.com/go-gorm/gorm), aims to be d
- [Smart select fields](#smart-select-fields)
- [Advanced Topics](#advanced-topics)
- [Hints](#hints)
- [Maintainers](#maintainers)
- [Contributing](#contributing)
- [License](#license)

Expand Down Expand Up @@ -490,6 +491,15 @@ u.Select(u.Age.Avg()).Rows()
// SELECT Avg(age) FROM users;
```

###### Tuple Query

```go
u := query.Query.User

users, err := u.Where(u.Columns(u.ID, u.Name).In(field.Values([][]inferface{}{{1, "modi"}, {2, "zhangqiang"}}))).Find()
// SELECT * FROM `users` WHERE (`id`, `name`) IN ((1,'humodi'),(2,'tom'));
```

###### Order

Specify order when retrieving records from the database
Expand Down Expand Up @@ -618,11 +628,11 @@ A subquery can be nested within a query, GEN can generate subquery when using a
o := query.Query.Order
u := query.Query.User

orders, err := o.Where(gen.Gt(o.Amount, o.Select(u.Amount.Avg())).Find()
orders, err := o.Where(u.Columns(o.Amount).Gt(o.Select(u.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(gen.Gt(u.Age.Avg(), subQuery).Find()
users, err := u.Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
```

Expand Down Expand Up @@ -1129,6 +1139,12 @@ users, err := u.Hints(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()
// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"
```

## Maintainers

[@riverchu](https://github.com/riverchu) [@idersec](https://github.com/idersec) [@qqxhb](https://github.com/qqxhb)

[@jinzhu](https://github.com/jinzhu)

## Contributing

You can help to deliver a better GORM/GEN
Expand Down
76 changes: 50 additions & 26 deletions do.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ func (d *DO) As(alias string) Dao {
return &DO{db: d.db, alias: alias}
}

// Columns return columns for Subquery
func (*DO) Columns(cols ...field.Expr) columns {
return cols
}

// ======================== chainable api ========================
func (d *DO) Not(conds ...Condition) Dao {
return NewDO(d.db.Clauses(clause.Where{Exprs: []clause.Expression{clause.Not(condToExpression(conds)...)}}))
Expand Down Expand Up @@ -514,44 +519,63 @@ func Table(subQueries ...subQuery) Dao {

// ======================== sub query method ========================

// In return a subquery expression
// the params conds must contains 2 parameters. Input should be in the order of field expression and sub query
// the function will painc if the last item is not of sub query type
func In(conds ...Condition) field.Expr {
switch len(conds) {
case 0, 1:
return field.ContainsSubQuery(nil, nil)
default:
columns := condToExpr(conds[:len(conds)-1]...)
query := conds[len(conds)-1].(subQuery)
return field.ContainsSubQuery(columns, query.UnderlyingDB())
type columns []field.Expr

// In accept query or value
func (cs columns) In(queryOrValue Condition) field.Expr {
if len(cs) == 0 {
return field.EmptyExpr()
}

query, ok := queryOrValue.(subQuery)
if !ok {
return field.ContainsValue(cs, queryOrValue)
}
return field.ContainsSubQuery(cs, query.UnderlyingDB())
}

func Eq(column field.Expr, query subQuery) field.Expr {
return field.CompareSubQuery(field.EqOp, column, query.UnderlyingDB())
func (cs columns) NotIn(queryOrValue Condition) field.Expr {
return field.Not(cs.In(queryOrValue))
}

func Gt(column field.Expr, query subQuery) field.Expr {
return field.CompareSubQuery(field.GtOp, column, query.UnderlyingDB())
func (cs columns) Eq(query subQuery) field.Expr {
if len(cs) == 0 {
return field.EmptyExpr()
}
return field.CompareSubQuery(field.EqOp, cs[0], query.UnderlyingDB())
}

func Gte(column field.Expr, query subQuery) field.Expr {
return field.CompareSubQuery(field.GteOp, column, query.UnderlyingDB())
func (cs columns) Neq(query subQuery) field.Expr {
if len(cs) == 0 {
return field.EmptyExpr()
}
return field.CompareSubQuery(field.NeqOp, cs[0], query.UnderlyingDB())
}

func Lt(column field.Expr, query subQuery) field.Expr {
return field.CompareSubQuery(field.LtOp, column, query.UnderlyingDB())
func (cs columns) Gt(query subQuery) field.Expr {
if len(cs) == 0 {
return field.EmptyExpr()
}
return field.CompareSubQuery(field.GtOp, cs[0], query.UnderlyingDB())
}

func Lte(column field.Expr, query subQuery) field.Expr {
return field.CompareSubQuery(field.LteOp, column, query.UnderlyingDB())
func (cs columns) Gte(query subQuery) field.Expr {
if len(cs) == 0 {
return field.EmptyExpr()
}
return field.CompareSubQuery(field.GteOp, cs[0], query.UnderlyingDB())
}

func condToExpr(conds ...Condition) []field.Expr {
exprs := make([]field.Expr, len(conds))
for i, cond := range conds {
exprs[i] = cond.(field.Expr)
func (cs columns) Lt(query subQuery) field.Expr {
if len(cs) == 0 {
return field.EmptyExpr()
}
return exprs
return field.CompareSubQuery(field.LtOp, cs[0], query.UnderlyingDB())
}

func (cs columns) Lte(query subQuery) field.Expr {
if len(cs) == 0 {
return field.EmptyExpr()
}
return field.CompareSubQuery(field.LteOp, cs[0], query.UnderlyingDB())
}
39 changes: 33 additions & 6 deletions do_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"testing"

"gorm.io/hints"

"gorm.io/gen/field"
)

func checkBuildExpr(t *testing.T, e subQuery, opts []stmtOpt, result string, vars []interface{}) {
Expand Down Expand Up @@ -141,6 +143,21 @@ func TestDO_methods(t *testing.T) {
ExpectedVars: []interface{}{18, "tom", true},
Result: "WHERE `age` <= ? OR (`name` = ? AND `famous` IS ?)",
},
{
Expr: u.Where(u.Columns(u.ID, u.Age).In(field.Values([][]int{{1, 18}, {2, 19}}))),
ExpectedVars: []interface{}{1, 18, 2, 19},
Result: "WHERE (`id`, `age`) IN ((?,?),(?,?))",
},
{
Expr: u.Where(u.Columns(u.ID, u.Age).NotIn(field.Values([][]int{{1, 18}, {2, 19}}))),
ExpectedVars: []interface{}{1, 18, 2, 19},
Result: "WHERE NOT (`id`, `age`) IN ((?,?),(?,?))",
},
{
Expr: u.Where(u.Columns(u.ID, u.Name).In(field.Values([][]interface{}{{1, "modi"}, {2, "tom"}}))),
ExpectedVars: []interface{}{1, "modi", 2, "tom"},
Result: "WHERE (`id`, `name`) IN ((?,?),(?,?))",
},
{
Expr: u.Where(u.Where(u.Name.Eq("tom"), u.Famous.Is(true))).Or(u.Age.Lte(18)),
ExpectedVars: []interface{}{"tom", true, 18},
Expand Down Expand Up @@ -170,32 +187,42 @@ func TestDO_methods(t *testing.T) {
},
// ======================== subquery ========================
{
Expr: u.Select().Where(Eq(u.ID, u.Select(u.ID.Max()))),
Expr: u.Select().Where(u.Columns(u.ID).Eq(u.Select(u.ID.Max()))),
ExpectedVars: nil,
Result: "SELECT * WHERE `id` = (SELECT MAX(`id`) FROM `users_info`)",
},
{
Expr: u.Select(u.ID).Where(Gt(u.Score, u.Select(u.Score.Avg()))),
Expr: u.Select().Where(u.Columns(u.ID).Neq(u.Select(u.ID.Max()))),
ExpectedVars: nil,
Result: "SELECT * WHERE `id` <> (SELECT MAX(`id`) FROM `users_info`)",
},
{
Expr: u.Select(u.ID).Where(u.Columns(u.Score.Mul(2)).Lte(u.Select(u.Score.Avg()))),
ExpectedVars: []interface{}{2.0},
Result: "SELECT `id` WHERE `score`*? <= (SELECT AVG(`score`) FROM `users_info`)",
},
{
Expr: u.Select(u.ID).Where(u.Columns(u.Score).Gt(u.Select(u.Score.Avg()))),
ExpectedVars: nil,
Result: "SELECT `id` WHERE `score` > (SELECT AVG(`score`) FROM `users_info`)",
},
{
Expr: u.Select(u.ID, u.Name).Where(Lte(u.Score, u.Select(u.Score.Avg()).Where(u.Age.Gte(18)))),
Expr: u.Select(u.ID, u.Name).Where(u.Columns(u.Score).Lte(u.Select(u.Score.Avg()).Where(u.Age.Gte(18)))),
ExpectedVars: []interface{}{18},
Result: "SELECT `id`,`name` WHERE `score` <= (SELECT AVG(`score`) FROM `users_info` WHERE `age` >= ?)",
},
{
Expr: u.Select(u.ID).Where(In(u.Score, u.Select(u.Score).Where(u.Age.Gte(18)))),
Expr: u.Select(u.ID).Where(u.Columns(u.Score).In(u.Select(u.Score).Where(u.Age.Gte(18)))),
ExpectedVars: []interface{}{18},
Result: "SELECT `id` WHERE `score` IN (SELECT `score` FROM `users_info` WHERE `age` >= ?)",
},
{
Expr: u.Select(u.ID).Where(In(u.ID, u.Age, u.Select(u.ID, u.Age).Where(u.Score.Eq(100)))),
Expr: u.Select(u.ID).Where(u.Columns(u.ID, u.Age).In(u.Select(u.ID, u.Age).Where(u.Score.Eq(100)))),
ExpectedVars: []interface{}{100.0},
Result: "SELECT `id` WHERE (`id`, `age`) IN (SELECT `id`,`age` FROM `users_info` WHERE `score` = ?)",
},
{
Expr: u.Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(Gt(u.Age.Avg(), u.Select(u.Age.Avg()).Where(u.Name.Like("name%")))),
Expr: u.Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.Columns(u.Age.Avg()).Gt(u.Select(u.Age.Avg()).Where(u.Name.Like("name%")))),
Opts: []stmtOpt{withFROM},
ExpectedVars: []interface{}{"name%"},
Result: "SELECT AVG(`age`) AS `avgage` FROM `users_info` GROUP BY `name` HAVING AVG(`age`) > (SELECT AVG(`age`) FROM `users_info` WHERE `name` LIKE ?)",
Expand Down
39 changes: 37 additions & 2 deletions field/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func ContainsSubQuery(columns []Expr, subQuery *gorm.DB) Expr {
case 1:
return expr{expression: clause.Expr{
SQL: "? IN (?)",
Vars: append([]interface{}{columns[0].RawExpr()}, subQuery),
Vars: []interface{}{columns[0].RawExpr(), subQuery},
}}
default: // len(columns) > 0
vars := make([]string, len(columns))
Expand All @@ -148,6 +148,7 @@ type CompareOperate string

const (
EqOp CompareOperate = " = "
NeqOp CompareOperate = " <> "
GtOp CompareOperate = " > "
GteOp CompareOperate = " >= "
LtOp CompareOperate = " < "
Expand All @@ -157,6 +158,40 @@ const (
func CompareSubQuery(op CompareOperate, column Expr, subQuery *gorm.DB) Expr {
return expr{expression: clause.Expr{
SQL: fmt.Sprint("?", op, "(?)"),
Vars: append([]interface{}{column.RawExpr()}, subQuery),
Vars: []interface{}{column.RawExpr(), subQuery},
}}
}

func Values(value interface{}) clause.Expression {
return clause.Expr{
SQL: "?",
Vars: []interface{}{value},
WithoutParentheses: true,
}
}

func ContainsValue(columns []Expr, value clause.Expression) Expr {
switch len(columns) {
case 0:
return expr{expression: clause.Expr{}}
case 1:
return expr{expression: clause.Expr{
SQL: "? IN (?)",
Vars: []interface{}{columns[0].RawExpr(), value},
}}
default: // len(columns) > 0
vars := make([]string, len(columns))
queryCols := make([]interface{}, len(columns))
for i, c := range columns {
vars[i], queryCols[i] = "?", c.RawExpr()
}
return expr{expression: clause.Expr{
SQL: fmt.Sprintf("(%s) IN (?)", strings.Join(vars, ", ")),
Vars: append(queryCols, value),
}}
}
}

func EmptyExpr() Expr {
return expr{expression: clause.Expr{}}
}
4 changes: 4 additions & 0 deletions field/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ func (e expr) EqCol(col Expr) Expr {
return e.setExpression(clause.Expr{SQL: "? = ?", Vars: []interface{}{e.RawExpr(), col.RawExpr()}})
}

func (e expr) NeqCol(col Expr) Expr {
return e.setExpression(clause.Expr{SQL: "? <> ?", Vars: []interface{}{e.RawExpr(), col.RawExpr()}})
}

func (e expr) GtCol(col Expr) Expr {
return e.setExpression(clause.Expr{SQL: "? > ?", Vars: []interface{}{e.RawExpr(), col.RawExpr()}})
}
Expand Down
24 changes: 24 additions & 0 deletions field/field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ func TestExpr_Build(t *testing.T) {
Expr: field.NewField("", "id").EqCol(field.NewField("", "new_id")),
Result: "`id` = `new_id`",
},
{
Expr: field.NewField("", "id").NeqCol(field.NewField("", "new_id")),
Result: "`id` <> `new_id`",
},
{
Expr: field.NewField("", "id").LtCol(field.NewField("", "new_id")),
Result: "`id` < `new_id`",
},
{
Expr: field.NewField("", "id").LteCol(field.NewField("", "new_id")),
Result: "`id` <= `new_id`",
},
{
Expr: field.NewField("", "id").GtCol(field.NewField("", "new_id")),
Result: "`id` > `new_id`",
},
{
Expr: field.NewField("", "id").GteCol(field.NewField("", "new_id")),
Result: "`id` >= `new_id`",
},
{
Expr: field.NewField("", "id").EqCol(field.NewField("", "new_id").Avg()),
Result: "`id` = AVG(`new_id`)",
Expand All @@ -96,6 +116,10 @@ func TestExpr_Build(t *testing.T) {
Expr: field.NewField("", "id").EqCol(field.NewField("", "new_id").WithTable("tableB")),
Result: "`id` = `tableB`.`new_id`",
},
{
Expr: field.NewField("", "id").NeqCol(field.NewField("", "new_id").WithTable("tableB")),
Result: "`id` <> `tableB`.`new_id`",
},
{
Expr: field.NewField("", "id").IsNull(),
Result: "`id` IS NULL",
Expand Down
Loading

0 comments on commit dabed4e

Please sign in to comment.