Finish clauses tests
This commit is contained in:
		
							parent
							
								
									0160bab7dc
								
							
						
					
					
						commit
						1f38ec4410
					
				| @ -31,8 +31,8 @@ func (db *DB) Clauses(conds ...clause.Expression) (tx *DB) { | ||||
| 	} | ||||
| 
 | ||||
| 	if len(whereConds) > 0 { | ||||
| 		tx.Statement.AddClause(clause.Where{ | ||||
| 			AndConditions: tx.Statement.BuildCondtion(whereConds[0], whereConds[1:]...), | ||||
| 		tx.Statement.AddClause(&clause.Where{ | ||||
| 			tx.Statement.BuildCondtion(whereConds[0], whereConds[1:]...), | ||||
| 		}) | ||||
| 	} | ||||
| 	return | ||||
| @ -59,8 +59,8 @@ func (db *DB) Omit(columns ...string) (tx *DB) { | ||||
| 
 | ||||
| func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB) { | ||||
| 	tx = db.getInstance() | ||||
| 	tx.Statement.AddClause(clause.Where{ | ||||
| 		AndConditions: tx.Statement.BuildCondtion(query, args...), | ||||
| 	tx.Statement.AddClause(&clause.Where{ | ||||
| 		tx.Statement.BuildCondtion(query, args...), | ||||
| 	}) | ||||
| 	return | ||||
| } | ||||
| @ -68,10 +68,8 @@ func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB) { | ||||
| // Not add NOT condition
 | ||||
| func (db *DB) Not(query interface{}, args ...interface{}) (tx *DB) { | ||||
| 	tx = db.getInstance() | ||||
| 	tx.Statement.AddClause(clause.Where{ | ||||
| 		AndConditions: []clause.Expression{ | ||||
| 			clause.NotConditions(tx.Statement.BuildCondtion(query, args...)), | ||||
| 		}, | ||||
| 	tx.Statement.AddClause(&clause.Where{ | ||||
| 		[]clause.Expression{clause.Not(tx.Statement.BuildCondtion(query, args...)...)}, | ||||
| 	}) | ||||
| 	return | ||||
| } | ||||
| @ -79,10 +77,8 @@ func (db *DB) Not(query interface{}, args ...interface{}) (tx *DB) { | ||||
| // Or add OR conditions
 | ||||
| func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB) { | ||||
| 	tx = db.getInstance() | ||||
| 	tx.Statement.AddClause(clause.Where{ | ||||
| 		OrConditions: []clause.OrConditions{ | ||||
| 			tx.Statement.BuildCondtion(query, args...), | ||||
| 		}, | ||||
| 	tx.Statement.AddClause(&clause.Where{ | ||||
| 		[]clause.Expression{clause.Or(tx.Statement.BuildCondtion(query, args...)...)}, | ||||
| 	}) | ||||
| 	return | ||||
| } | ||||
| @ -113,13 +109,13 @@ func (db *DB) Order(value interface{}) (tx *DB) { | ||||
| 	tx = db.getInstance() | ||||
| 
 | ||||
| 	switch v := value.(type) { | ||||
| 	case clause.OrderBy: | ||||
| 		db.Statement.AddClause(clause.OrderByClause{ | ||||
| 			Columns: []clause.OrderBy{v}, | ||||
| 	case clause.OrderByColumn: | ||||
| 		db.Statement.AddClause(clause.OrderBy{ | ||||
| 			Columns: []clause.OrderByColumn{v}, | ||||
| 		}) | ||||
| 	default: | ||||
| 		db.Statement.AddClause(clause.OrderByClause{ | ||||
| 			Columns: []clause.OrderBy{{ | ||||
| 		db.Statement.AddClause(clause.OrderBy{ | ||||
| 			Columns: []clause.OrderByColumn{{ | ||||
| 				Column: clause.Column{Name: fmt.Sprint(value), Raw: true}, | ||||
| 			}}, | ||||
| 		}) | ||||
|  | ||||
| @ -1,5 +1,26 @@ | ||||
| package clause | ||||
| 
 | ||||
| // Interface clause interface
 | ||||
| type Interface interface { | ||||
| 	Name() string | ||||
| 	Build(Builder) | ||||
| 	MergeClause(*Clause) | ||||
| } | ||||
| 
 | ||||
| // ClauseBuilder clause builder, allows to custmize how to build clause
 | ||||
| type ClauseBuilder interface { | ||||
| 	Build(Clause, Builder) | ||||
| } | ||||
| 
 | ||||
| // Builder builder interface
 | ||||
| type Builder interface { | ||||
| 	WriteByte(byte) error | ||||
| 	Write(sql ...string) error | ||||
| 	WriteQuoted(field interface{}) error | ||||
| 	AddVar(vars ...interface{}) string | ||||
| 	Quote(field interface{}) string | ||||
| } | ||||
| 
 | ||||
| // Clause
 | ||||
| type Clause struct { | ||||
| 	Name                 string // WHERE
 | ||||
| @ -18,7 +39,7 @@ func (c Clause) Build(builder Builder) { | ||||
| 	} else { | ||||
| 		builders := c.BeforeExpressions | ||||
| 		if c.Name != "" { | ||||
| 			builders = append(builders, Expr{c.Name}) | ||||
| 			builders = append(builders, Expr{SQL: c.Name}) | ||||
| 		} | ||||
| 
 | ||||
| 		builders = append(builders, c.AfterNameExpressions...) | ||||
| @ -35,28 +56,27 @@ func (c Clause) Build(builder Builder) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Interface clause interface
 | ||||
| type Interface interface { | ||||
| 	Name() string | ||||
| 	Build(Builder) | ||||
| 	MergeExpression(Expression) | ||||
| const ( | ||||
| 	PrimaryKey   string = "@@@priamry_key@@@" | ||||
| 	CurrentTable string = "@@@table@@@" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	currentTable  = Table{Name: CurrentTable} | ||||
| 	PrimaryColumn = Column{Table: CurrentTable, Name: PrimaryKey} | ||||
| ) | ||||
| 
 | ||||
| // Column quote with name
 | ||||
| type Column struct { | ||||
| 	Table string | ||||
| 	Name  string | ||||
| 	Alias string | ||||
| 	Raw   bool | ||||
| } | ||||
| 
 | ||||
| // OverrideNameInterface override name interface
 | ||||
| type OverrideNameInterface interface { | ||||
| 	OverrideName() string | ||||
| } | ||||
| 
 | ||||
| // ClauseBuilder clause builder, allows to custmize how to build clause
 | ||||
| type ClauseBuilder interface { | ||||
| 	Build(Clause, Builder) | ||||
| } | ||||
| 
 | ||||
| // Builder builder interface
 | ||||
| type Builder interface { | ||||
| 	WriteByte(byte) error | ||||
| 	Write(sql ...string) error | ||||
| 	WriteQuoted(field interface{}) error | ||||
| 	AddVar(vars ...interface{}) string | ||||
| 	Quote(field interface{}) string | ||||
| // Table quote with name
 | ||||
| type Table struct { | ||||
| 	Name  string | ||||
| 	Alias string | ||||
| 	Raw   bool | ||||
| } | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| 
 | ||||
| @ -12,45 +12,32 @@ import ( | ||||
| 	"github.com/jinzhu/gorm/tests" | ||||
| ) | ||||
| 
 | ||||
| func TestClauses(t *testing.T) { | ||||
| var db, _ = gorm.Open(tests.DummyDialector{}, nil) | ||||
| 
 | ||||
| func checkBuildClauses(t *testing.T, clauses []clause.Interface, result string, vars []interface{}) { | ||||
| 	var ( | ||||
| 		db, _   = gorm.Open(tests.DummyDialector{}, nil) | ||||
| 		results = []struct { | ||||
| 			Clauses []clause.Interface | ||||
| 			Result  string | ||||
| 			Vars    []interface{} | ||||
| 		}{ | ||||
| 			{ | ||||
| 				[]clause.Interface{clause.Select{}, clause.From{}, clause.Where{AndConditions: []clause.Expression{clause.Eq{Column: clause.PrimaryColumn, Value: "1"}}}}, | ||||
| 				"SELECT * FROM `users` WHERE `users`.`id` = ?", []interface{}{"1"}, | ||||
| 			}, | ||||
| 		} | ||||
| 		buildNames    []string | ||||
| 		buildNamesMap = map[string]bool{} | ||||
| 		user, _       = schema.Parse(&tests.User{}, &sync.Map{}, db.NamingStrategy) | ||||
| 		stmt          = gorm.Statement{DB: db, Table: user.Table, Schema: user, Clauses: map[string]clause.Clause{}} | ||||
| 	) | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			var ( | ||||
| 				user, _ = schema.Parse(&tests.User{}, &sync.Map{}, db.NamingStrategy) | ||||
| 				stmt    = gorm.Statement{ | ||||
| 					DB: db, Table: user.Table, Schema: user, Clauses: map[string]clause.Clause{}, | ||||
| 				} | ||||
| 				buildNames []string | ||||
| 			) | ||||
| 	for _, c := range clauses { | ||||
| 		if _, ok := buildNamesMap[c.Name()]; !ok { | ||||
| 			buildNames = append(buildNames, c.Name()) | ||||
| 			buildNamesMap[c.Name()] = true | ||||
| 		} | ||||
| 
 | ||||
| 			for _, c := range result.Clauses { | ||||
| 				buildNames = append(buildNames, c.Name()) | ||||
| 				stmt.AddClause(c) | ||||
| 			} | ||||
| 		stmt.AddClause(c) | ||||
| 	} | ||||
| 
 | ||||
| 			stmt.Build(buildNames...) | ||||
| 	stmt.Build(buildNames...) | ||||
| 
 | ||||
| 			if stmt.SQL.String() != result.Result { | ||||
| 				t.Errorf("SQL expects %v got %v", result.Result, stmt.SQL.String()) | ||||
| 			} | ||||
| 	if strings.TrimSpace(stmt.SQL.String()) != result { | ||||
| 		t.Errorf("SQL expects %v got %v", result, stmt.SQL.String()) | ||||
| 	} | ||||
| 
 | ||||
| 			if reflect.DeepEqual(stmt.Vars, result.Vars) { | ||||
| 				t.Errorf("Vars expects %+v got %v", stmt.Vars, result.Vars) | ||||
| 			} | ||||
| 		}) | ||||
| 	if !reflect.DeepEqual(stmt.Vars, vars) { | ||||
| 		t.Errorf("Vars expects %+v got %v", stmt.Vars, vars) | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										23
									
								
								clause/delete.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								clause/delete.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| package clause | ||||
| 
 | ||||
| type Delete struct { | ||||
| 	Modifier string | ||||
| } | ||||
| 
 | ||||
| func (d Delete) Name() string { | ||||
| 	return "DELETE" | ||||
| } | ||||
| 
 | ||||
| func (d Delete) Build(builder Builder) { | ||||
| 	builder.Write("DELETE") | ||||
| 
 | ||||
| 	if d.Modifier != "" { | ||||
| 		builder.WriteByte(' ') | ||||
| 		builder.Write(d.Modifier) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (d Delete) MergeClause(clause *Clause) { | ||||
| 	clause.Name = "" | ||||
| 	clause.Expression = d | ||||
| } | ||||
							
								
								
									
										31
									
								
								clause/delete_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								clause/delete_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestDelete(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Delete{}, clause.From{}}, | ||||
| 			"DELETE FROM `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Delete{Modifier: "LOW_PRIORITY"}, clause.From{}}, | ||||
| 			"DELETE LOW_PRIORITY FROM `users`", nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -1,14 +1,6 @@ | ||||
| package clause | ||||
| 
 | ||||
| const ( | ||||
| 	PrimaryKey   string = "@@@priamry_key@@@" | ||||
| 	CurrentTable string = "@@@table@@@" | ||||
| ) | ||||
| 
 | ||||
| var PrimaryColumn = Column{ | ||||
| 	Table: CurrentTable, | ||||
| 	Name:  PrimaryKey, | ||||
| } | ||||
| import "strings" | ||||
| 
 | ||||
| // Expression expression interface
 | ||||
| type Expression interface { | ||||
| @ -20,27 +12,155 @@ type NegationExpressionBuilder interface { | ||||
| 	NegationBuild(builder Builder) | ||||
| } | ||||
| 
 | ||||
| // Column quote with name
 | ||||
| type Column struct { | ||||
| 	Table string | ||||
| 	Name  string | ||||
| 	Alias string | ||||
| 	Raw   bool | ||||
| } | ||||
| 
 | ||||
| // Table quote with name
 | ||||
| type Table struct { | ||||
| 	Table string | ||||
| 	Alias string | ||||
| 	Raw   bool | ||||
| } | ||||
| 
 | ||||
| // Expr raw expression
 | ||||
| type Expr struct { | ||||
| 	Value string | ||||
| 	SQL  string | ||||
| 	Vars []interface{} | ||||
| } | ||||
| 
 | ||||
| // Build build raw expression
 | ||||
| func (expr Expr) Build(builder Builder) { | ||||
| 	builder.Write(expr.Value) | ||||
| 	sql := expr.SQL | ||||
| 	for _, v := range expr.Vars { | ||||
| 		sql = strings.Replace(sql, " ? ", " "+builder.AddVar(v)+" ", 1) | ||||
| 	} | ||||
| 	builder.Write(sql) | ||||
| } | ||||
| 
 | ||||
| // IN Whether a value is within a set of values
 | ||||
| type IN struct { | ||||
| 	Column interface{} | ||||
| 	Values []interface{} | ||||
| } | ||||
| 
 | ||||
| func (in IN) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(in.Column) | ||||
| 
 | ||||
| 	switch len(in.Values) { | ||||
| 	case 0: | ||||
| 		builder.Write(" IN (NULL)") | ||||
| 	case 1: | ||||
| 		builder.Write(" = ", builder.AddVar(in.Values...)) | ||||
| 	default: | ||||
| 		builder.Write(" IN (", builder.AddVar(in.Values...), ")") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (in IN) NegationBuild(builder Builder) { | ||||
| 	switch len(in.Values) { | ||||
| 	case 0: | ||||
| 	case 1: | ||||
| 		builder.Write(" <> ", builder.AddVar(in.Values...)) | ||||
| 	default: | ||||
| 		builder.Write(" NOT IN (", builder.AddVar(in.Values...), ")") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Eq equal to for where
 | ||||
| type Eq struct { | ||||
| 	Column interface{} | ||||
| 	Value  interface{} | ||||
| } | ||||
| 
 | ||||
| func (eq Eq) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(eq.Column) | ||||
| 
 | ||||
| 	if eq.Value == nil { | ||||
| 		builder.Write(" IS NULL") | ||||
| 	} else { | ||||
| 		builder.Write(" = ", builder.AddVar(eq.Value)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (eq Eq) NegationBuild(builder Builder) { | ||||
| 	Neq{eq.Column, eq.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Neq not equal to for where
 | ||||
| type Neq Eq | ||||
| 
 | ||||
| func (neq Neq) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(neq.Column) | ||||
| 
 | ||||
| 	if neq.Value == nil { | ||||
| 		builder.Write(" IS NOT NULL") | ||||
| 	} else { | ||||
| 		builder.Write(" <> ", builder.AddVar(neq.Value)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (neq Neq) NegationBuild(builder Builder) { | ||||
| 	Eq{neq.Column, neq.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Gt greater than for where
 | ||||
| type Gt Eq | ||||
| 
 | ||||
| func (gt Gt) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(gt.Column) | ||||
| 	builder.Write(" > ", builder.AddVar(gt.Value)) | ||||
| } | ||||
| 
 | ||||
| func (gt Gt) NegationBuild(builder Builder) { | ||||
| 	Lte{gt.Column, gt.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Gte greater than or equal to for where
 | ||||
| type Gte Eq | ||||
| 
 | ||||
| func (gte Gte) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(gte.Column) | ||||
| 	builder.Write(" >= ", builder.AddVar(gte.Value)) | ||||
| } | ||||
| 
 | ||||
| func (gte Gte) NegationBuild(builder Builder) { | ||||
| 	Lt{gte.Column, gte.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Lt less than for where
 | ||||
| type Lt Eq | ||||
| 
 | ||||
| func (lt Lt) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(lt.Column) | ||||
| 	builder.Write(" < ", builder.AddVar(lt.Value)) | ||||
| } | ||||
| 
 | ||||
| func (lt Lt) NegationBuild(builder Builder) { | ||||
| 	Gte{lt.Column, lt.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Lte less than or equal to for where
 | ||||
| type Lte Eq | ||||
| 
 | ||||
| func (lte Lte) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(lte.Column) | ||||
| 	builder.Write(" <= ", builder.AddVar(lte.Value)) | ||||
| } | ||||
| 
 | ||||
| func (lte Lte) NegationBuild(builder Builder) { | ||||
| 	Gt{lte.Column, lte.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Like whether string matches regular expression
 | ||||
| type Like Eq | ||||
| 
 | ||||
| func (like Like) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(like.Column) | ||||
| 	builder.Write(" LIKE ", builder.AddVar(like.Value)) | ||||
| } | ||||
| 
 | ||||
| func (like Like) NegationBuild(builder Builder) { | ||||
| 	builder.WriteQuoted(like.Column) | ||||
| 	builder.Write(" NOT LIKE ", builder.AddVar(like.Value)) | ||||
| } | ||||
| 
 | ||||
| // Map
 | ||||
| type Map map[interface{}]interface{} | ||||
| 
 | ||||
| func (m Map) Build(builder Builder) { | ||||
| 	// TODO
 | ||||
| } | ||||
| 
 | ||||
| func (m Map) NegationBuild(builder Builder) { | ||||
| 	// TODO
 | ||||
| } | ||||
|  | ||||
| @ -3,15 +3,31 @@ package clause | ||||
| // From from clause
 | ||||
| type From struct { | ||||
| 	Tables []Table | ||||
| 	Joins  []Join | ||||
| } | ||||
| 
 | ||||
| type JoinType string | ||||
| 
 | ||||
| const ( | ||||
| 	CrossJoin JoinType = "CROSS" | ||||
| 	InnerJoin          = "INNER" | ||||
| 	LeftJoin           = "LEFT" | ||||
| 	RightJoin          = "RIGHT" | ||||
| ) | ||||
| 
 | ||||
| // Join join clause for from
 | ||||
| type Join struct { | ||||
| 	Type  JoinType | ||||
| 	Table Table | ||||
| 	ON    Where | ||||
| 	Using []string | ||||
| } | ||||
| 
 | ||||
| // Name from clause name
 | ||||
| func (From) Name() string { | ||||
| func (from From) Name() string { | ||||
| 	return "FROM" | ||||
| } | ||||
| 
 | ||||
| var currentTable = Table{Table: CurrentTable} | ||||
| 
 | ||||
| // Build build from clause
 | ||||
| func (from From) Build(builder Builder) { | ||||
| 	if len(from.Tables) > 0 { | ||||
| @ -25,11 +41,42 @@ func (from From) Build(builder Builder) { | ||||
| 	} else { | ||||
| 		builder.WriteQuoted(currentTable) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeExpression merge order by clauses
 | ||||
| func (from From) MergeExpression(expr Expression) { | ||||
| 	if v, ok := expr.(From); ok { | ||||
| 		from.Tables = append(v.Tables, from.Tables...) | ||||
| 	for _, join := range from.Joins { | ||||
| 		builder.WriteByte(' ') | ||||
| 		join.Build(builder) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (join Join) Build(builder Builder) { | ||||
| 	if join.Type != "" { | ||||
| 		builder.Write(string(join.Type)) | ||||
| 		builder.WriteByte(' ') | ||||
| 	} | ||||
| 
 | ||||
| 	builder.Write("JOIN ") | ||||
| 	builder.WriteQuoted(join.Table) | ||||
| 
 | ||||
| 	if len(join.ON.Exprs) > 0 { | ||||
| 		builder.Write(" ON ") | ||||
| 		join.ON.Build(builder) | ||||
| 	} else if len(join.Using) > 0 { | ||||
| 		builder.Write(" USING (") | ||||
| 		for idx, c := range join.Using { | ||||
| 			if idx > 0 { | ||||
| 				builder.WriteByte(',') | ||||
| 			} | ||||
| 			builder.WriteQuoted(c) | ||||
| 		} | ||||
| 		builder.WriteByte(')') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeClause merge from clause
 | ||||
| func (from From) MergeClause(clause *Clause) { | ||||
| 	if v, ok := clause.Expression.(From); ok { | ||||
| 		from.Tables = append(v.Tables, from.Tables...) | ||||
| 		from.Joins = append(v.Joins, from.Joins...) | ||||
| 	} | ||||
| 	clause.Expression = from | ||||
| } | ||||
|  | ||||
							
								
								
									
										75
									
								
								clause/from_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								clause/from_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestFrom(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}}, | ||||
| 			"SELECT * FROM `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{ | ||||
| 				clause.Select{}, clause.From{ | ||||
| 					Tables: []clause.Table{{Name: "users"}}, | ||||
| 					Joins: []clause.Join{ | ||||
| 						{ | ||||
| 							Type:  clause.InnerJoin, | ||||
| 							Table: clause.Table{Name: "articles"}, | ||||
| 							ON: clause.Where{ | ||||
| 								[]clause.Expression{clause.Eq{clause.Column{Table: "articles", Name: "id"}, clause.PrimaryColumn}}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			"SELECT * FROM `users` INNER JOIN `articles` ON `articles`.`id` = `users`.`id`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{ | ||||
| 				clause.Select{}, clause.From{ | ||||
| 					Tables: []clause.Table{{Name: "users"}}, | ||||
| 					Joins: []clause.Join{ | ||||
| 						{ | ||||
| 							Type:  clause.InnerJoin, | ||||
| 							Table: clause.Table{Name: "articles"}, | ||||
| 							ON: clause.Where{ | ||||
| 								[]clause.Expression{clause.Eq{clause.Column{Table: "articles", Name: "id"}, clause.PrimaryColumn}}, | ||||
| 							}, | ||||
| 						}, { | ||||
| 							Type:  clause.LeftJoin, | ||||
| 							Table: clause.Table{Name: "companies"}, | ||||
| 							Using: []string{"company_name"}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, clause.From{ | ||||
| 					Joins: []clause.Join{ | ||||
| 						{ | ||||
| 							Type:  clause.RightJoin, | ||||
| 							Table: clause.Table{Name: "profiles"}, | ||||
| 							ON: clause.Where{ | ||||
| 								[]clause.Expression{clause.Eq{clause.Column{Table: "profiles", Name: "email"}, clause.Column{Table: clause.CurrentTable, Name: "email"}}}, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			"SELECT * FROM `users` INNER JOIN `articles` ON `articles`.`id` = `users`.`id` LEFT JOIN `companies` USING (`company_name`) RIGHT JOIN `profiles` ON `profiles`.`email` = `users`.`email`", nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -2,5 +2,36 @@ package clause | ||||
| 
 | ||||
| // GroupBy group by clause
 | ||||
| type GroupBy struct { | ||||
| 	Having Where | ||||
| 	Columns []Column | ||||
| 	Having  Where | ||||
| } | ||||
| 
 | ||||
| // Name from clause name
 | ||||
| func (groupBy GroupBy) Name() string { | ||||
| 	return "GROUP BY" | ||||
| } | ||||
| 
 | ||||
| // Build build group by clause
 | ||||
| func (groupBy GroupBy) Build(builder Builder) { | ||||
| 	for idx, column := range groupBy.Columns { | ||||
| 		if idx > 0 { | ||||
| 			builder.WriteByte(',') | ||||
| 		} | ||||
| 
 | ||||
| 		builder.WriteQuoted(column) | ||||
| 	} | ||||
| 
 | ||||
| 	if len(groupBy.Having.Exprs) > 0 { | ||||
| 		builder.Write(" HAVING ") | ||||
| 		groupBy.Having.Build(builder) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeClause merge group by clause
 | ||||
| func (groupBy GroupBy) MergeClause(clause *Clause) { | ||||
| 	if v, ok := clause.Expression.(GroupBy); ok { | ||||
| 		groupBy.Columns = append(v.Columns, groupBy.Columns...) | ||||
| 		groupBy.Having.Exprs = append(v.Having.Exprs, groupBy.Having.Exprs...) | ||||
| 	} | ||||
| 	clause.Expression = groupBy | ||||
| } | ||||
|  | ||||
							
								
								
									
										40
									
								
								clause/group_by_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								clause/group_by_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestGroupBy(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.GroupBy{ | ||||
| 				Columns: []clause.Column{{Name: "role"}}, | ||||
| 				Having:  clause.Where{[]clause.Expression{clause.Eq{"role", "admin"}}}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` GROUP BY `role` HAVING `role` = ?", []interface{}{"admin"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.GroupBy{ | ||||
| 				Columns: []clause.Column{{Name: "role"}}, | ||||
| 				Having:  clause.Where{[]clause.Expression{clause.Eq{"role", "admin"}}}, | ||||
| 			}, clause.GroupBy{ | ||||
| 				Columns: []clause.Column{{Name: "gender"}}, | ||||
| 				Having:  clause.Where{[]clause.Expression{clause.Neq{"gender", "U"}}}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` GROUP BY `role`,`gender` HAVING `role` = ? AND `gender` <> ?", []interface{}{"admin", "U"}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -2,7 +2,7 @@ package clause | ||||
| 
 | ||||
| type Insert struct { | ||||
| 	Table    Table | ||||
| 	Priority string | ||||
| 	Modifier string | ||||
| } | ||||
| 
 | ||||
| // Name insert clause name
 | ||||
| @ -12,23 +12,28 @@ func (insert Insert) Name() string { | ||||
| 
 | ||||
| // Build build insert clause
 | ||||
| func (insert Insert) Build(builder Builder) { | ||||
| 	if insert.Priority != "" { | ||||
| 		builder.Write(insert.Priority) | ||||
| 	if insert.Modifier != "" { | ||||
| 		builder.Write(insert.Modifier) | ||||
| 		builder.WriteByte(' ') | ||||
| 	} | ||||
| 
 | ||||
| 	builder.Write("INTO ") | ||||
| 	builder.WriteQuoted(insert.Table) | ||||
| 	if insert.Table.Name == "" { | ||||
| 		builder.WriteQuoted(currentTable) | ||||
| 	} else { | ||||
| 		builder.WriteQuoted(insert.Table) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeExpression merge insert clauses
 | ||||
| func (insert Insert) MergeExpression(expr Expression) { | ||||
| 	if v, ok := expr.(Insert); ok { | ||||
| 		if insert.Priority == "" { | ||||
| 			insert.Priority = v.Priority | ||||
| // MergeClause merge insert clause
 | ||||
| func (insert Insert) MergeClause(clause *Clause) { | ||||
| 	if v, ok := clause.Expression.(Insert); ok { | ||||
| 		if insert.Modifier == "" { | ||||
| 			insert.Modifier = v.Modifier | ||||
| 		} | ||||
| 		if insert.Table.Table == "" { | ||||
| 		if insert.Table.Name == "" { | ||||
| 			insert.Table = v.Table | ||||
| 		} | ||||
| 	} | ||||
| 	clause.Expression = insert | ||||
| } | ||||
|  | ||||
							
								
								
									
										35
									
								
								clause/insert_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								clause/insert_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestInsert(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Insert{}}, | ||||
| 			"INSERT INTO `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Insert{Modifier: "LOW_PRIORITY"}}, | ||||
| 			"INSERT LOW_PRIORITY INTO `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Insert{Table: clause.Table{Name: "products"}, Modifier: "LOW_PRIORITY"}}, | ||||
| 			"INSERT LOW_PRIORITY INTO `products`", nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| package clause | ||||
| 
 | ||||
| // Join join clause
 | ||||
| type Join struct { | ||||
| 	Table From   // From
 | ||||
| 	Type  string // INNER, LEFT, RIGHT, FULL, CROSS JOIN
 | ||||
| 	Using []Column | ||||
| 	ON    Where | ||||
| } | ||||
| 
 | ||||
| // TODO multiple joins
 | ||||
| 
 | ||||
| func (join Join) Build(builder Builder) { | ||||
| 	// TODO
 | ||||
| } | ||||
| 
 | ||||
| func (join Join) MergeExpression(expr Expression) { | ||||
| 	// if j, ok := expr.(Join); ok {
 | ||||
| 	// 	join.builders = append(join.builders, j.builders...)
 | ||||
| 	// } else {
 | ||||
| 	// 	join.builders = append(join.builders, expr)
 | ||||
| 	// }
 | ||||
| } | ||||
| @ -1,6 +1,44 @@ | ||||
| package clause | ||||
| 
 | ||||
| import "strconv" | ||||
| 
 | ||||
| // Limit limit clause
 | ||||
| type Limit struct { | ||||
| 	Offset uint | ||||
| 	Limit  int | ||||
| 	Offset int | ||||
| } | ||||
| 
 | ||||
| // Name where clause name
 | ||||
| func (limit Limit) Name() string { | ||||
| 	return "LIMIT" | ||||
| } | ||||
| 
 | ||||
| // Build build where clause
 | ||||
| func (limit Limit) Build(builder Builder) { | ||||
| 	if limit.Limit > 0 { | ||||
| 		builder.Write("LIMIT ") | ||||
| 		builder.Write(strconv.Itoa(limit.Limit)) | ||||
| 
 | ||||
| 		if limit.Offset > 0 { | ||||
| 			builder.Write(" OFFSET ") | ||||
| 			builder.Write(strconv.Itoa(limit.Offset)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeClause merge order by clauses
 | ||||
| func (limit Limit) MergeClause(clause *Clause) { | ||||
| 	clause.Name = "" | ||||
| 
 | ||||
| 	if v, ok := clause.Expression.(Limit); ok { | ||||
| 		if limit.Limit == 0 && v.Limit > 0 { | ||||
| 			limit.Limit = v.Limit | ||||
| 		} | ||||
| 
 | ||||
| 		if limit.Offset == 0 && v.Offset > 0 { | ||||
| 			limit.Offset = v.Offset | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	clause.Expression = limit | ||||
| } | ||||
|  | ||||
							
								
								
									
										46
									
								
								clause/limit_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								clause/limit_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestLimit(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{ | ||||
| 				Limit:  10, | ||||
| 				Offset: 20, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` LIMIT 10 OFFSET 20", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: 10, Offset: 20}, clause.Limit{Offset: 30}}, | ||||
| 			"SELECT * FROM `users` LIMIT 10 OFFSET 30", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: 10, Offset: 20}, clause.Limit{Offset: 30}, clause.Limit{Offset: -10}}, | ||||
| 			"SELECT * FROM `users` LIMIT 10", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: 10, Offset: 20}, clause.Limit{Offset: 30}, clause.Limit{Limit: -10}}, | ||||
| 			"SELECT * FROM `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Limit{Limit: 10, Offset: 20}, clause.Limit{Offset: 30}, clause.Limit{Limit: 50}}, | ||||
| 			"SELECT * FROM `users` LIMIT 50 OFFSET 30", nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										48
									
								
								clause/locking.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								clause/locking.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| package clause | ||||
| 
 | ||||
| type For struct { | ||||
| 	Lockings []Locking | ||||
| } | ||||
| 
 | ||||
| type Locking struct { | ||||
| 	Strength string | ||||
| 	Table    Table | ||||
| 	Options  string | ||||
| } | ||||
| 
 | ||||
| // Name where clause name
 | ||||
| func (f For) Name() string { | ||||
| 	return "FOR" | ||||
| } | ||||
| 
 | ||||
| // Build build where clause
 | ||||
| func (f For) Build(builder Builder) { | ||||
| 	for idx, locking := range f.Lockings { | ||||
| 		if idx > 0 { | ||||
| 			builder.WriteByte(' ') | ||||
| 		} | ||||
| 
 | ||||
| 		builder.Write("FOR ") | ||||
| 		builder.Write(locking.Strength) | ||||
| 		if locking.Table.Name != "" { | ||||
| 			builder.Write(" OF ") | ||||
| 			builder.WriteQuoted(locking.Table) | ||||
| 		} | ||||
| 
 | ||||
| 		if locking.Options != "" { | ||||
| 			builder.WriteByte(' ') | ||||
| 			builder.Write(locking.Options) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeClause merge order by clauses
 | ||||
| func (f For) MergeClause(clause *Clause) { | ||||
| 	clause.Name = "" | ||||
| 
 | ||||
| 	if v, ok := clause.Expression.(For); ok { | ||||
| 		f.Lockings = append(v.Lockings, f.Lockings...) | ||||
| 	} | ||||
| 
 | ||||
| 	clause.Expression = f | ||||
| } | ||||
							
								
								
									
										43
									
								
								clause/locking_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								clause/locking_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestFor(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.For{ | ||||
| 				Lockings: []clause.Locking{{Strength: "UPDATE"}}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` FOR UPDATE", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.For{ | ||||
| 				Lockings: []clause.Locking{{Strength: "UPDATE"}, {Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}}}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` FOR UPDATE FOR SHARE OF `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.For{ | ||||
| 				Lockings: []clause.Locking{{Strength: "UPDATE"}, {Strength: "SHARE", Table: clause.Table{Name: clause.CurrentTable}}}, | ||||
| 			}, clause.For{ | ||||
| 				Lockings: []clause.Locking{{Strength: "UPDATE", Options: "NOWAIT"}}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` FOR UPDATE FOR SHARE OF `users` FOR UPDATE NOWAIT", nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -1,6 +0,0 @@ | ||||
| package clause | ||||
| 
 | ||||
| type OnConflict struct { | ||||
| 	ON     string  // duplicate key
 | ||||
| 	Values *Values // update c=c+1
 | ||||
| } | ||||
| @ -1,38 +1,47 @@ | ||||
| package clause | ||||
| 
 | ||||
| type OrderBy struct { | ||||
| type OrderByColumn struct { | ||||
| 	Column  Column | ||||
| 	Desc    bool | ||||
| 	Reorder bool | ||||
| } | ||||
| 
 | ||||
| type OrderByClause struct { | ||||
| 	Columns []OrderBy | ||||
| type OrderBy struct { | ||||
| 	Columns []OrderByColumn | ||||
| } | ||||
| 
 | ||||
| // Name where clause name
 | ||||
| func (orderBy OrderByClause) Name() string { | ||||
| func (orderBy OrderBy) Name() string { | ||||
| 	return "ORDER BY" | ||||
| } | ||||
| 
 | ||||
| // Build build where clause
 | ||||
| func (orderBy OrderByClause) Build(builder Builder) { | ||||
| 	for i := len(orderBy.Columns) - 1; i >= 0; i-- { | ||||
| 		builder.WriteQuoted(orderBy.Columns[i].Column) | ||||
| func (orderBy OrderBy) Build(builder Builder) { | ||||
| 	for idx, column := range orderBy.Columns { | ||||
| 		if idx > 0 { | ||||
| 			builder.WriteByte(',') | ||||
| 		} | ||||
| 
 | ||||
| 		if orderBy.Columns[i].Desc { | ||||
| 		builder.WriteQuoted(column.Column) | ||||
| 		if column.Desc { | ||||
| 			builder.Write(" DESC") | ||||
| 		} | ||||
| 
 | ||||
| 		if orderBy.Columns[i].Reorder { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeExpression merge order by clauses
 | ||||
| func (orderBy OrderByClause) MergeExpression(expr Expression) { | ||||
| 	if v, ok := expr.(OrderByClause); ok { | ||||
| // MergeClause merge order by clauses
 | ||||
| func (orderBy OrderBy) MergeClause(clause *Clause) { | ||||
| 	if v, ok := clause.Expression.(OrderBy); ok { | ||||
| 		for i := len(orderBy.Columns) - 1; i >= 0; i-- { | ||||
| 			if orderBy.Columns[i].Reorder { | ||||
| 				orderBy.Columns = orderBy.Columns[i:] | ||||
| 				clause.Expression = orderBy | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		orderBy.Columns = append(v.Columns, orderBy.Columns...) | ||||
| 	} | ||||
| 
 | ||||
| 	clause.Expression = orderBy | ||||
| } | ||||
|  | ||||
							
								
								
									
										49
									
								
								clause/order_by_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								clause/order_by_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestOrderBy(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.OrderBy{ | ||||
| 				Columns: []clause.OrderByColumn{{Column: clause.PrimaryColumn, Desc: true}}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` ORDER BY `users`.`id` DESC", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{ | ||||
| 				clause.Select{}, clause.From{}, clause.OrderBy{ | ||||
| 					Columns: []clause.OrderByColumn{{Column: clause.PrimaryColumn, Desc: true}}, | ||||
| 				}, clause.OrderBy{ | ||||
| 					Columns: []clause.OrderByColumn{{Column: clause.Column{Name: "name"}}}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			"SELECT * FROM `users` ORDER BY `users`.`id` DESC,`name`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{ | ||||
| 				clause.Select{}, clause.From{}, clause.OrderBy{ | ||||
| 					Columns: []clause.OrderByColumn{{Column: clause.PrimaryColumn, Desc: true}}, | ||||
| 				}, clause.OrderBy{ | ||||
| 					Columns: []clause.OrderByColumn{{Column: clause.Column{Name: "name"}, Reorder: true}}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			"SELECT * FROM `users` ORDER BY `name`", nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										258
									
								
								clause/query.go
									
									
									
									
									
								
							
							
						
						
									
										258
									
								
								clause/query.go
									
									
									
									
									
								
							| @ -1,258 +0,0 @@ | ||||
| package clause | ||||
| 
 | ||||
| import "strings" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////
 | ||||
| // Query Expressions
 | ||||
| ////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| func Add(exprs ...Expression) AddConditions { | ||||
| 	return AddConditions(exprs) | ||||
| } | ||||
| 
 | ||||
| func Or(exprs ...Expression) OrConditions { | ||||
| 	return OrConditions(exprs) | ||||
| } | ||||
| 
 | ||||
| type AddConditions []Expression | ||||
| 
 | ||||
| func (cs AddConditions) Build(builder Builder) { | ||||
| 	for idx, c := range cs { | ||||
| 		if idx > 0 { | ||||
| 			builder.Write(" AND ") | ||||
| 		} | ||||
| 		c.Build(builder) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type OrConditions []Expression | ||||
| 
 | ||||
| func (cs OrConditions) Build(builder Builder) { | ||||
| 	for idx, c := range cs { | ||||
| 		if idx > 0 { | ||||
| 			builder.Write(" OR ") | ||||
| 		} | ||||
| 		c.Build(builder) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type NotConditions []Expression | ||||
| 
 | ||||
| func (cs NotConditions) Build(builder Builder) { | ||||
| 	for idx, c := range cs { | ||||
| 		if idx > 0 { | ||||
| 			builder.Write(" AND ") | ||||
| 		} | ||||
| 
 | ||||
| 		if negationBuilder, ok := c.(NegationExpressionBuilder); ok { | ||||
| 			negationBuilder.NegationBuild(builder) | ||||
| 		} else { | ||||
| 			builder.Write(" NOT ") | ||||
| 			c.Build(builder) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // String raw sql for where
 | ||||
| type String struct { | ||||
| 	SQL    string | ||||
| 	Values []interface{} | ||||
| } | ||||
| 
 | ||||
| func (str String) Build(builder Builder) { | ||||
| 	sql := str.SQL | ||||
| 	for _, v := range str.Values { | ||||
| 		sql = strings.Replace(sql, " ? ", " "+builder.AddVar(v)+" ", 1) | ||||
| 	} | ||||
| 	builder.Write(sql) | ||||
| } | ||||
| 
 | ||||
| // IN Whether a value is within a set of values
 | ||||
| type IN struct { | ||||
| 	Column interface{} | ||||
| 	Values []interface{} | ||||
| } | ||||
| 
 | ||||
| func (in IN) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(in.Column) | ||||
| 
 | ||||
| 	switch len(in.Values) { | ||||
| 	case 0: | ||||
| 		builder.Write(" IN (NULL)") | ||||
| 	case 1: | ||||
| 		builder.Write(" = ", builder.AddVar(in.Values...)) | ||||
| 	default: | ||||
| 		builder.Write(" IN (", builder.AddVar(in.Values...), ")") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (in IN) NegationBuild(builder Builder) { | ||||
| 	switch len(in.Values) { | ||||
| 	case 0: | ||||
| 	case 1: | ||||
| 		builder.Write(" <> ", builder.AddVar(in.Values...)) | ||||
| 	default: | ||||
| 		builder.Write(" NOT IN (", builder.AddVar(in.Values...), ")") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Eq equal to for where
 | ||||
| type Eq struct { | ||||
| 	Column interface{} | ||||
| 	Value  interface{} | ||||
| } | ||||
| 
 | ||||
| func (eq Eq) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(eq.Column) | ||||
| 
 | ||||
| 	if eq.Value == nil { | ||||
| 		builder.Write(" IS NULL") | ||||
| 	} else { | ||||
| 		builder.Write(" = ", builder.AddVar(eq.Value)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (eq Eq) NegationBuild(builder Builder) { | ||||
| 	Neq{eq.Column, eq.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Neq not equal to for where
 | ||||
| type Neq struct { | ||||
| 	Column interface{} | ||||
| 	Value  interface{} | ||||
| } | ||||
| 
 | ||||
| func (neq Neq) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(neq.Column) | ||||
| 
 | ||||
| 	if neq.Value == nil { | ||||
| 		builder.Write(" IS NOT NULL") | ||||
| 	} else { | ||||
| 		builder.Write(" <> ", builder.AddVar(neq.Value)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (neq Neq) NegationBuild(builder Builder) { | ||||
| 	Eq{neq.Column, neq.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Gt greater than for where
 | ||||
| type Gt struct { | ||||
| 	Column interface{} | ||||
| 	Value  interface{} | ||||
| } | ||||
| 
 | ||||
| func (gt Gt) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(gt.Column) | ||||
| 	builder.Write(" > ", builder.AddVar(gt.Value)) | ||||
| } | ||||
| 
 | ||||
| func (gt Gt) NegationBuild(builder Builder) { | ||||
| 	Lte{gt.Column, gt.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Gte greater than or equal to for where
 | ||||
| type Gte struct { | ||||
| 	Column interface{} | ||||
| 	Value  interface{} | ||||
| } | ||||
| 
 | ||||
| func (gte Gte) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(gte.Column) | ||||
| 	builder.Write(" >= ", builder.AddVar(gte.Value)) | ||||
| } | ||||
| 
 | ||||
| func (gte Gte) NegationBuild(builder Builder) { | ||||
| 	Lt{gte.Column, gte.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Lt less than for where
 | ||||
| type Lt struct { | ||||
| 	Column interface{} | ||||
| 	Value  interface{} | ||||
| } | ||||
| 
 | ||||
| func (lt Lt) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(lt.Column) | ||||
| 	builder.Write(" < ", builder.AddVar(lt.Value)) | ||||
| } | ||||
| 
 | ||||
| func (lt Lt) NegationBuild(builder Builder) { | ||||
| 	Gte{lt.Column, lt.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Lte less than or equal to for where
 | ||||
| type Lte struct { | ||||
| 	Column interface{} | ||||
| 	Value  interface{} | ||||
| } | ||||
| 
 | ||||
| func (lte Lte) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(lte.Column) | ||||
| 	builder.Write(" <= ", builder.AddVar(lte.Value)) | ||||
| } | ||||
| 
 | ||||
| func (lte Lte) NegationBuild(builder Builder) { | ||||
| 	Gt{lte.Column, lte.Value}.Build(builder) | ||||
| } | ||||
| 
 | ||||
| // Like whether string matches regular expression
 | ||||
| type Like struct { | ||||
| 	Column interface{} | ||||
| 	Value  interface{} | ||||
| } | ||||
| 
 | ||||
| func (like Like) Build(builder Builder) { | ||||
| 	builder.WriteQuoted(like.Column) | ||||
| 	builder.Write(" LIKE ", builder.AddVar(like.Value)) | ||||
| } | ||||
| 
 | ||||
| func (like Like) NegationBuild(builder Builder) { | ||||
| 	builder.WriteQuoted(like.Column) | ||||
| 	builder.Write(" NOT LIKE ", builder.AddVar(like.Value)) | ||||
| } | ||||
| 
 | ||||
| // Map
 | ||||
| type Map map[interface{}]interface{} | ||||
| 
 | ||||
| func (m Map) Build(builder Builder) { | ||||
| 	// TODO
 | ||||
| } | ||||
| 
 | ||||
| func (m Map) NegationBuild(builder Builder) { | ||||
| 	// TODO
 | ||||
| } | ||||
| 
 | ||||
| // Attrs
 | ||||
| type Attrs struct { | ||||
| 	Value  interface{} | ||||
| 	Select []string | ||||
| 	Omit   []string | ||||
| } | ||||
| 
 | ||||
| func (attrs Attrs) Build(builder Builder) { | ||||
| 	// TODO
 | ||||
| 	// builder.WriteQuoted(like.Column)
 | ||||
| 	// builder.Write(" LIKE ", builder.AddVar(like.Value))
 | ||||
| } | ||||
| 
 | ||||
| func (attrs Attrs) NegationBuild(builder Builder) { | ||||
| 	// TODO
 | ||||
| } | ||||
| 
 | ||||
| // ID
 | ||||
| type ID struct { | ||||
| 	Value []interface{} | ||||
| } | ||||
| 
 | ||||
| func (id ID) Build(builder Builder) { | ||||
| 	if len(id.Value) == 1 { | ||||
| 	} | ||||
| 	// TODO
 | ||||
| 	// builder.WriteQuoted(like.Column)
 | ||||
| 	// builder.Write(" LIKE ", builder.AddVar(like.Value))
 | ||||
| } | ||||
| 
 | ||||
| func (id ID) NegationBuild(builder Builder) { | ||||
| 	// TODO
 | ||||
| } | ||||
							
								
								
									
										30
									
								
								clause/returning.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								clause/returning.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| package clause | ||||
| 
 | ||||
| type Returning struct { | ||||
| 	Columns []Column | ||||
| } | ||||
| 
 | ||||
| // Name where clause name
 | ||||
| func (returning Returning) Name() string { | ||||
| 	return "RETURNING" | ||||
| } | ||||
| 
 | ||||
| // Build build where clause
 | ||||
| func (returning Returning) Build(builder Builder) { | ||||
| 	for idx, column := range returning.Columns { | ||||
| 		if idx > 0 { | ||||
| 			builder.WriteByte(',') | ||||
| 		} | ||||
| 
 | ||||
| 		builder.WriteQuoted(column) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeClause merge order by clauses
 | ||||
| func (returning Returning) MergeClause(clause *Clause) { | ||||
| 	if v, ok := clause.Expression.(Returning); ok { | ||||
| 		returning.Columns = append(v.Columns, returning.Columns...) | ||||
| 	} | ||||
| 
 | ||||
| 	clause.Expression = returning | ||||
| } | ||||
							
								
								
									
										36
									
								
								clause/returning_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								clause/returning_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestReturning(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Returning{ | ||||
| 				[]clause.Column{clause.PrimaryColumn}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` RETURNING `users`.`id`", nil, | ||||
| 		}, { | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Returning{ | ||||
| 				[]clause.Column{clause.PrimaryColumn}, | ||||
| 			}, clause.Returning{ | ||||
| 				[]clause.Column{{Name: "name"}, {Name: "age"}}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` RETURNING `users`.`id`,`name`,`age`", nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -1,32 +1,18 @@ | ||||
| package clause | ||||
| 
 | ||||
| // SelectInterface select clause interface
 | ||||
| type SelectInterface interface { | ||||
| 	Selects() []Column | ||||
| 	Omits() []Column | ||||
| } | ||||
| 
 | ||||
| // Select select attrs when querying, updating, creating
 | ||||
| type Select struct { | ||||
| 	SelectColumns []Column | ||||
| 	OmitColumns   []Column | ||||
| 	Columns []Column | ||||
| 	Omits   []Column | ||||
| } | ||||
| 
 | ||||
| func (s Select) Name() string { | ||||
| 	return "SELECT" | ||||
| } | ||||
| 
 | ||||
| func (s Select) Selects() []Column { | ||||
| 	return s.SelectColumns | ||||
| } | ||||
| 
 | ||||
| func (s Select) Omits() []Column { | ||||
| 	return s.OmitColumns | ||||
| } | ||||
| 
 | ||||
| func (s Select) Build(builder Builder) { | ||||
| 	if len(s.SelectColumns) > 0 { | ||||
| 		for idx, column := range s.SelectColumns { | ||||
| 	if len(s.Columns) > 0 { | ||||
| 		for idx, column := range s.Columns { | ||||
| 			if idx > 0 { | ||||
| 				builder.WriteByte(',') | ||||
| 			} | ||||
| @ -37,13 +23,10 @@ func (s Select) Build(builder Builder) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s Select) MergeExpression(expr Expression) { | ||||
| 	if v, ok := expr.(SelectInterface); ok { | ||||
| 		if len(s.SelectColumns) == 0 { | ||||
| 			s.SelectColumns = v.Selects() | ||||
| 		} | ||||
| 		if len(s.OmitColumns) == 0 { | ||||
| 			s.OmitColumns = v.Omits() | ||||
| 		} | ||||
| func (s Select) MergeClause(clause *Clause) { | ||||
| 	if v, ok := clause.Expression.(Select); ok { | ||||
| 		s.Columns = append(v.Columns, s.Columns...) | ||||
| 		s.Omits = append(v.Omits, s.Omits...) | ||||
| 	} | ||||
| 	clause.Expression = s | ||||
| } | ||||
|  | ||||
							
								
								
									
										41
									
								
								clause/select_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								clause/select_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestSelect(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}}, | ||||
| 			"SELECT * FROM `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{ | ||||
| 				Columns: []clause.Column{clause.PrimaryColumn}, | ||||
| 			}, clause.From{}}, | ||||
| 			"SELECT `users`.`id` FROM `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{ | ||||
| 				Columns: []clause.Column{clause.PrimaryColumn}, | ||||
| 			}, clause.Select{ | ||||
| 				Columns: []clause.Column{{Name: "name"}}, | ||||
| 			}, clause.From{}}, | ||||
| 			"SELECT `users`.`id`,`name` FROM `users`", nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										37
									
								
								clause/set.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								clause/set.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| package clause | ||||
| 
 | ||||
| type Set []Assignment | ||||
| 
 | ||||
| type Assignment struct { | ||||
| 	Column Column | ||||
| 	Value  interface{} | ||||
| } | ||||
| 
 | ||||
| func (set Set) Name() string { | ||||
| 	return "SET" | ||||
| } | ||||
| 
 | ||||
| func (set Set) Build(builder Builder) { | ||||
| 	if len(set) > 0 { | ||||
| 		for idx, assignment := range set { | ||||
| 			if idx > 0 { | ||||
| 				builder.WriteByte(',') | ||||
| 			} | ||||
| 			builder.WriteQuoted(assignment.Column) | ||||
| 			builder.WriteByte('=') | ||||
| 			builder.Write(builder.AddVar(assignment.Value)) | ||||
| 		} | ||||
| 	} else { | ||||
| 		builder.WriteQuoted(PrimaryColumn) | ||||
| 		builder.WriteByte('=') | ||||
| 		builder.WriteQuoted(PrimaryColumn) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeClause merge assignments clauses
 | ||||
| func (set Set) MergeClause(clause *Clause) { | ||||
| 	if v, ok := clause.Expression.(Set); ok { | ||||
| 		set = append(v, set...) | ||||
| 	} | ||||
| 	clause.Expression = set | ||||
| } | ||||
							
								
								
									
										38
									
								
								clause/set_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								clause/set_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestSet(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{ | ||||
| 				clause.Update{}, | ||||
| 				clause.Set([]clause.Assignment{{clause.PrimaryColumn, 1}}), | ||||
| 			}, | ||||
| 			"UPDATE `users` SET `users`.`id`=?", []interface{}{1}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{ | ||||
| 				clause.Update{}, | ||||
| 				clause.Set([]clause.Assignment{{clause.PrimaryColumn, 1}}), | ||||
| 				clause.Set([]clause.Assignment{{clause.Column{Name: "name"}, "jinzhu"}}), | ||||
| 			}, | ||||
| 			"UPDATE `users` SET `users`.`id`=?,`name`=?", []interface{}{1, "jinzhu"}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										38
									
								
								clause/update.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								clause/update.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| package clause | ||||
| 
 | ||||
| type Update struct { | ||||
| 	Modifier string | ||||
| 	Table    Table | ||||
| } | ||||
| 
 | ||||
| // Name update clause name
 | ||||
| func (update Update) Name() string { | ||||
| 	return "UPDATE" | ||||
| } | ||||
| 
 | ||||
| // Build build update clause
 | ||||
| func (update Update) Build(builder Builder) { | ||||
| 	if update.Modifier != "" { | ||||
| 		builder.Write(update.Modifier) | ||||
| 		builder.WriteByte(' ') | ||||
| 	} | ||||
| 
 | ||||
| 	if update.Table.Name == "" { | ||||
| 		builder.WriteQuoted(currentTable) | ||||
| 	} else { | ||||
| 		builder.WriteQuoted(update.Table) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeClause merge update clause
 | ||||
| func (update Update) MergeClause(clause *Clause) { | ||||
| 	if v, ok := clause.Expression.(Update); ok { | ||||
| 		if update.Modifier == "" { | ||||
| 			update.Modifier = v.Modifier | ||||
| 		} | ||||
| 		if update.Table.Name == "" { | ||||
| 			update.Table = v.Table | ||||
| 		} | ||||
| 	} | ||||
| 	clause.Expression = update | ||||
| } | ||||
							
								
								
									
										35
									
								
								clause/update_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								clause/update_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestUpdate(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Update{}}, | ||||
| 			"UPDATE `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Update{Modifier: "LOW_PRIORITY"}}, | ||||
| 			"UPDATE LOW_PRIORITY `users`", nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Update{Table: clause.Table{Name: "products"}, Modifier: "LOW_PRIORITY"}}, | ||||
| 			"UPDATE LOW_PRIORITY `products`", nil, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -25,11 +25,11 @@ func (values Values) Build(builder Builder) { | ||||
| 		builder.Write(" VALUES ") | ||||
| 
 | ||||
| 		for idx, value := range values.Values { | ||||
| 			builder.WriteByte('(') | ||||
| 			if idx > 0 { | ||||
| 				builder.WriteByte(',') | ||||
| 			} | ||||
| 
 | ||||
| 			builder.WriteByte('(') | ||||
| 			builder.Write(builder.AddVar(value...)) | ||||
| 			builder.WriteByte(')') | ||||
| 		} | ||||
| @ -37,3 +37,11 @@ func (values Values) Build(builder Builder) { | ||||
| 		builder.Write("DEFAULT VALUES") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // MergeClause merge values clauses
 | ||||
| func (values Values) MergeClause(clause *Clause) { | ||||
| 	if v, ok := clause.Expression.(Values); ok { | ||||
| 		values.Values = append(v.Values, values.Values...) | ||||
| 	} | ||||
| 	clause.Expression = values | ||||
| } | ||||
							
								
								
									
										33
									
								
								clause/values_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								clause/values_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestValues(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{ | ||||
| 				clause.Insert{}, | ||||
| 				clause.Values{ | ||||
| 					Columns: []clause.Column{{Name: "name"}, {Name: "age"}}, | ||||
| 					Values:  [][]interface{}{{"jinzhu", 18}, {"josh", 1}}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			"INSERT INTO `users` (`name`,`age`) VALUES (?,?),(?,?)", []interface{}{"jinzhu", 18, "josh", 1}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										156
									
								
								clause/where.go
									
									
									
									
									
								
							
							
						
						
									
										156
									
								
								clause/where.go
									
									
									
									
									
								
							| @ -2,9 +2,7 @@ package clause | ||||
| 
 | ||||
| // Where where clause
 | ||||
| type Where struct { | ||||
| 	AndConditions AddConditions | ||||
| 	OrConditions  []OrConditions | ||||
| 	builders      []Expression | ||||
| 	Exprs []Expression | ||||
| } | ||||
| 
 | ||||
| // Name where clause name
 | ||||
| @ -14,64 +12,122 @@ func (where Where) Name() string { | ||||
| 
 | ||||
| // Build build where clause
 | ||||
| func (where Where) Build(builder Builder) { | ||||
| 	var withConditions bool | ||||
| 
 | ||||
| 	if len(where.AndConditions) > 0 { | ||||
| 		withConditions = true | ||||
| 		where.AndConditions.Build(builder) | ||||
| 	} | ||||
| 
 | ||||
| 	if len(where.builders) > 0 { | ||||
| 		for _, b := range where.builders { | ||||
| 			if withConditions { | ||||
| 				builder.Write(" AND ") | ||||
| 	// Switch position if the first query expression is a single Or condition
 | ||||
| 	for idx, expr := range where.Exprs { | ||||
| 		if v, ok := expr.(OrConditions); (!ok && expr != nil) || len(v.Exprs) > 1 { | ||||
| 			if idx != 0 { | ||||
| 				where.Exprs[0], where.Exprs[idx] = where.Exprs[idx], where.Exprs[0] | ||||
| 			} | ||||
| 			withConditions = true | ||||
| 			b.Build(builder) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	var singleOrConditions []OrConditions | ||||
| 	for _, or := range where.OrConditions { | ||||
| 		if len(or) == 1 { | ||||
| 			if withConditions { | ||||
| 				builder.Write(" OR ") | ||||
| 				or.Build(builder) | ||||
| 			} else { | ||||
| 				singleOrConditions = append(singleOrConditions, or) | ||||
| 	for idx, expr := range where.Exprs { | ||||
| 		if expr != nil { | ||||
| 			if idx > 0 { | ||||
| 				if v, ok := expr.(OrConditions); ok && len(v.Exprs) == 1 { | ||||
| 					builder.Write(" OR ") | ||||
| 				} else { | ||||
| 					builder.Write(" AND ") | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			withConditions = true | ||||
| 			builder.Write(" AND (") | ||||
| 			or.Build(builder) | ||||
| 			builder.WriteByte(')') | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, or := range singleOrConditions { | ||||
| 		if withConditions { | ||||
| 			builder.Write(" AND ") | ||||
| 			or.Build(builder) | ||||
| 		} else { | ||||
| 			withConditions = true | ||||
| 			or.Build(builder) | ||||
| 			expr.Build(builder) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if !withConditions { | ||||
| 		builder.Write(" FALSE") | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // MergeExpression merge where clauses
 | ||||
| func (where Where) MergeExpression(expr Expression) { | ||||
| 	if w, ok := expr.(Where); ok { | ||||
| 		where.AndConditions = append(where.AndConditions, w.AndConditions...) | ||||
| 		where.OrConditions = append(where.OrConditions, w.OrConditions...) | ||||
| 		where.builders = append(where.builders, w.builders...) | ||||
| 	} else { | ||||
| 		where.builders = append(where.builders, expr) | ||||
| // MergeClause merge where clauses
 | ||||
| func (where Where) MergeClause(clause *Clause) { | ||||
| 	if w, ok := clause.Expression.(Where); ok { | ||||
| 		where.Exprs = append(w.Exprs, where.Exprs...) | ||||
| 	} | ||||
| 
 | ||||
| 	clause.Expression = where | ||||
| } | ||||
| 
 | ||||
| func And(exprs ...Expression) Expression { | ||||
| 	if len(exprs) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return AndConditions{Exprs: exprs} | ||||
| } | ||||
| 
 | ||||
| type AndConditions struct { | ||||
| 	Exprs []Expression | ||||
| } | ||||
| 
 | ||||
| func (and AndConditions) Build(builder Builder) { | ||||
| 	if len(and.Exprs) > 1 { | ||||
| 		builder.Write("(") | ||||
| 	} | ||||
| 	for idx, c := range and.Exprs { | ||||
| 		if idx > 0 { | ||||
| 			builder.Write(" AND ") | ||||
| 		} | ||||
| 		c.Build(builder) | ||||
| 	} | ||||
| 	if len(and.Exprs) > 1 { | ||||
| 		builder.Write(")") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func Or(exprs ...Expression) Expression { | ||||
| 	if len(exprs) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return OrConditions{Exprs: exprs} | ||||
| } | ||||
| 
 | ||||
| type OrConditions struct { | ||||
| 	Exprs []Expression | ||||
| } | ||||
| 
 | ||||
| func (or OrConditions) Build(builder Builder) { | ||||
| 	if len(or.Exprs) > 1 { | ||||
| 		builder.Write("(") | ||||
| 	} | ||||
| 	for idx, c := range or.Exprs { | ||||
| 		if idx > 0 { | ||||
| 			builder.Write(" OR ") | ||||
| 		} | ||||
| 		c.Build(builder) | ||||
| 	} | ||||
| 	if len(or.Exprs) > 1 { | ||||
| 		builder.Write(")") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func Not(exprs ...Expression) Expression { | ||||
| 	if len(exprs) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return NotConditions{Exprs: exprs} | ||||
| } | ||||
| 
 | ||||
| type NotConditions struct { | ||||
| 	Exprs []Expression | ||||
| } | ||||
| 
 | ||||
| func (not NotConditions) Build(builder Builder) { | ||||
| 	if len(not.Exprs) > 1 { | ||||
| 		builder.Write("(") | ||||
| 	} | ||||
| 	for idx, c := range not.Exprs { | ||||
| 		if idx > 0 { | ||||
| 			builder.Write(" AND ") | ||||
| 		} | ||||
| 
 | ||||
| 		if negationBuilder, ok := c.(NegationExpressionBuilder); ok { | ||||
| 			negationBuilder.NegationBuild(builder) | ||||
| 		} else { | ||||
| 			builder.Write(" NOT ") | ||||
| 			c.Build(builder) | ||||
| 		} | ||||
| 	} | ||||
| 	if len(not.Exprs) > 1 { | ||||
| 		builder.Write(")") | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										63
									
								
								clause/where_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								clause/where_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| package clause_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jinzhu/gorm/clause" | ||||
| ) | ||||
| 
 | ||||
| func TestWhere(t *testing.T) { | ||||
| 	results := []struct { | ||||
| 		Clauses []clause.Interface | ||||
| 		Result  string | ||||
| 		Vars    []interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Where{ | ||||
| 				Exprs: []clause.Expression{clause.Eq{Column: clause.PrimaryColumn, Value: "1"}, clause.Gt{Column: "age", Value: 18}, clause.Or(clause.Neq{Column: "name", Value: "jinzhu"})}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` WHERE `users`.`id` = ? AND `age` > ? OR `name` <> ?", []interface{}{"1", 18, "jinzhu"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Where{ | ||||
| 				Exprs: []clause.Expression{clause.Or(clause.Neq{Column: "name", Value: "jinzhu"}), clause.Eq{Column: clause.PrimaryColumn, Value: "1"}, clause.Gt{Column: "age", Value: 18}}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` WHERE `users`.`id` = ? OR `name` <> ? AND `age` > ?", []interface{}{"1", "jinzhu", 18}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Where{ | ||||
| 				Exprs: []clause.Expression{clause.Or(), clause.Or(clause.Neq{Column: "name", Value: "jinzhu"}), clause.Eq{Column: clause.PrimaryColumn, Value: "1"}, clause.Gt{Column: "age", Value: 18}}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` WHERE `users`.`id` = ? OR `name` <> ? AND `age` > ?", []interface{}{"1", "jinzhu", 18}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Where{ | ||||
| 				Exprs: []clause.Expression{clause.Or(clause.Eq{Column: clause.PrimaryColumn, Value: "1"}), clause.Or(clause.Neq{Column: "name", Value: "jinzhu"})}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` WHERE `users`.`id` = ? OR `name` <> ?", []interface{}{"1", "jinzhu"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Where{ | ||||
| 				Exprs: []clause.Expression{clause.Eq{Column: clause.PrimaryColumn, Value: "1"}, clause.Gt{Column: "age", Value: 18}, clause.Or(clause.Neq{Column: "name", Value: "jinzhu"})}, | ||||
| 			}, clause.Where{ | ||||
| 				Exprs: []clause.Expression{clause.Or(clause.Gt{Column: "score", Value: 100}, clause.Like{Column: "name", Value: "%linus%"})}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` WHERE `users`.`id` = ? AND `age` > ? OR `name` <> ? AND (`score` > ? OR `name` LIKE ?)", []interface{}{"1", 18, "jinzhu", 100, "%linus%"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			[]clause.Interface{clause.Select{}, clause.From{}, clause.Where{ | ||||
| 				Exprs: []clause.Expression{clause.Not(clause.Eq{Column: clause.PrimaryColumn, Value: "1"}, clause.Gt{Column: "age", Value: 18}), clause.Or(clause.Neq{Column: "name", Value: "jinzhu"})}, | ||||
| 			}, clause.Where{ | ||||
| 				Exprs: []clause.Expression{clause.Or(clause.Not(clause.Gt{Column: "score", Value: 100}), clause.Like{Column: "name", Value: "%linus%"})}, | ||||
| 			}}, | ||||
| 			"SELECT * FROM `users` WHERE (`users`.`id` <> ? AND `age` <= ?) OR `name` <> ? AND (`score` <= ? OR `name` LIKE ?)", []interface{}{"1", 18, "jinzhu", 100, "%linus%"}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for idx, result := range results { | ||||
| 		t.Run(fmt.Sprintf("case #%v", idx), func(t *testing.T) { | ||||
| 			checkBuildClauses(t, result.Clauses, result.Result, result.Vars) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -22,7 +22,7 @@ func (db *DB) Save(value interface{}) (tx *DB) { | ||||
| 
 | ||||
| // First find first record that match given conditions, order by primary key
 | ||||
| func (db *DB) First(out interface{}, where ...interface{}) (tx *DB) { | ||||
| 	tx = db.getInstance().Limit(1).Order(clause.OrderBy{ | ||||
| 	tx = db.getInstance().Limit(1).Order(clause.OrderByColumn{ | ||||
| 		Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey}, | ||||
| 		Desc:   true, | ||||
| 	}) | ||||
|  | ||||
							
								
								
									
										72
									
								
								statement.go
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								statement.go
									
									
									
									
									
								
							| @ -5,7 +5,6 @@ import ( | ||||
| 	"database/sql" | ||||
| 	"database/sql/driver" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| @ -26,7 +25,7 @@ func (instance *Instance) ToSQL(clauses ...string) (string, []interface{}) { | ||||
| 	if len(clauses) > 0 { | ||||
| 		instance.Statement.Build(clauses...) | ||||
| 	} | ||||
| 	return instance.Statement.SQL.String(), instance.Statement.Vars | ||||
| 	return strings.TrimSpace(instance.Statement.SQL.String()), instance.Statement.Vars | ||||
| } | ||||
| 
 | ||||
| // AddError add error to instance
 | ||||
| @ -85,10 +84,10 @@ func (stmt Statement) Quote(field interface{}) string { | ||||
| 
 | ||||
| 	switch v := field.(type) { | ||||
| 	case clause.Table: | ||||
| 		if v.Table == clause.CurrentTable { | ||||
| 		if v.Name == clause.CurrentTable { | ||||
| 			str.WriteString(stmt.Table) | ||||
| 		} else { | ||||
| 			str.WriteString(v.Table) | ||||
| 			str.WriteString(v.Name) | ||||
| 		} | ||||
| 
 | ||||
| 		if v.Alias != "" { | ||||
| @ -126,7 +125,7 @@ func (stmt Statement) Quote(field interface{}) string { | ||||
| 			str.WriteByte(stmt.DB.quoteChars[1]) | ||||
| 		} | ||||
| 	default: | ||||
| 		fmt.Sprint(field) | ||||
| 		str.WriteString(fmt.Sprint(field)) | ||||
| 	} | ||||
| 
 | ||||
| 	str.WriteByte(stmt.DB.quoteChars[1]) | ||||
| @ -141,19 +140,28 @@ func (stmt *Statement) AddVar(vars ...interface{}) string { | ||||
| 			placeholders.WriteByte(',') | ||||
| 		} | ||||
| 
 | ||||
| 		if namedArg, ok := v.(sql.NamedArg); ok && len(namedArg.Name) > 0 { | ||||
| 			stmt.NamedVars = append(stmt.NamedVars, namedArg) | ||||
| 			placeholders.WriteByte('@') | ||||
| 			placeholders.WriteString(namedArg.Name) | ||||
| 		} else if arrs, ok := v.([]interface{}); ok { | ||||
| 		switch v := v.(type) { | ||||
| 		case sql.NamedArg: | ||||
| 			if len(v.Name) > 0 { | ||||
| 				stmt.NamedVars = append(stmt.NamedVars, v) | ||||
| 				placeholders.WriteByte('@') | ||||
| 				placeholders.WriteString(v.Name) | ||||
| 			} else { | ||||
| 				stmt.Vars = append(stmt.Vars, v.Value) | ||||
| 				placeholders.WriteString(stmt.DB.Dialector.BindVar(stmt, v.Value)) | ||||
| 			} | ||||
| 		case clause.Column: | ||||
| 			placeholders.WriteString(stmt.Quote(v)) | ||||
| 		case []interface{}: | ||||
| 			placeholders.WriteByte('(') | ||||
| 			if len(arrs) > 0 { | ||||
| 				placeholders.WriteString(stmt.AddVar(arrs...)) | ||||
| 			if len(v) > 0 { | ||||
| 				placeholders.WriteString(stmt.AddVar(v...)) | ||||
| 			} else { | ||||
| 				placeholders.WriteString("NULL") | ||||
| 			} | ||||
| 			placeholders.WriteByte(')') | ||||
| 		} else { | ||||
| 		default: | ||||
| 			stmt.Vars = append(stmt.Vars, v) | ||||
| 			placeholders.WriteString(stmt.DB.Dialector.BindVar(stmt, v)) | ||||
| 		} | ||||
| 	} | ||||
| @ -166,42 +174,18 @@ func (stmt *Statement) AddClause(v clause.Interface) { | ||||
| 		optimizer.OptimizeStatement(stmt) | ||||
| 	} | ||||
| 
 | ||||
| 	c, _ := stmt.Clauses[v.Name()] | ||||
| 	if namer, ok := v.(clause.OverrideNameInterface); ok { | ||||
| 		c.Name = namer.OverrideName() | ||||
| 	} else { | ||||
| 	c, ok := stmt.Clauses[v.Name()] | ||||
| 	if !ok { | ||||
| 		c.Name = v.Name() | ||||
| 	} | ||||
| 
 | ||||
| 	if c.Expression != nil { | ||||
| 		v.MergeExpression(c.Expression) | ||||
| 	} | ||||
| 
 | ||||
| 	c.Expression = v | ||||
| 	v.MergeClause(&c) | ||||
| 	stmt.Clauses[v.Name()] = c | ||||
| } | ||||
| 
 | ||||
| // AddClauseIfNotExists add clause if not exists
 | ||||
| func (stmt *Statement) AddClauseIfNotExists(v clause.Interface) { | ||||
| 	if optimizer, ok := v.(StatementOptimizer); ok { | ||||
| 		optimizer.OptimizeStatement(stmt) | ||||
| 	} | ||||
| 
 | ||||
| 	log.Println(v.Name()) | ||||
| 	if c, ok := stmt.Clauses[v.Name()]; !ok { | ||||
| 		if namer, ok := v.(clause.OverrideNameInterface); ok { | ||||
| 			c.Name = namer.OverrideName() | ||||
| 		} else { | ||||
| 			c.Name = v.Name() | ||||
| 		} | ||||
| 
 | ||||
| 		if c.Expression != nil { | ||||
| 			v.MergeExpression(c.Expression) | ||||
| 		} | ||||
| 
 | ||||
| 		c.Expression = v | ||||
| 		stmt.Clauses[v.Name()] = c | ||||
| 		log.Println(stmt.Clauses[v.Name()]) | ||||
| 	if _, ok := stmt.Clauses[v.Name()]; !ok { | ||||
| 		stmt.AddClause(v) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -211,7 +195,7 @@ func (stmt Statement) BuildCondtion(query interface{}, args ...interface{}) (con | ||||
| 		if i, err := strconv.Atoi(sql); err != nil { | ||||
| 			query = i | ||||
| 		} else if len(args) == 0 || (len(args) > 0 && strings.Contains(sql, "?")) || strings.Contains(sql, "@") { | ||||
| 			return []clause.Expression{clause.String{SQL: sql, Values: args}} | ||||
| 			return []clause.Expression{clause.Expr{SQL: sql, Vars: args}} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -255,7 +239,7 @@ func (stmt Statement) BuildCondtion(query interface{}, args ...interface{}) (con | ||||
| 	} | ||||
| 
 | ||||
| 	if len(conditions) == 0 { | ||||
| 		conditions = append(conditions, clause.ID{Value: args}) | ||||
| 		conditions = append(conditions, clause.IN{Column: clause.PrimaryColumn, Values: args}) | ||||
| 	} | ||||
| 
 | ||||
| 	return conditions | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Jinzhu
						Jinzhu