diff --git a/callbacks/query.go b/callbacks/query.go index 3299d015..a4093c63 100644 --- a/callbacks/query.go +++ b/callbacks/query.go @@ -125,33 +125,47 @@ func BuildQuerySQL(db *gorm.DB) { }) } - exprs := make([]clause.Expression, len(relation.References)) - for idx, ref := range relation.References { - if ref.OwnPrimaryKey { - exprs[idx] = clause.Eq{ - Column: clause.Column{Table: clause.CurrentTable, Name: ref.PrimaryKey.DBName}, - Value: clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName}, - } - } else { - if ref.PrimaryValue == "" { + if join.On != nil { + primaryFields := make([]clause.Column, len(relation.FieldSchema.PrimaryFieldDBNames)) + for idx, ref := range relation.FieldSchema.PrimaryFieldDBNames { + primaryFields[idx] = clause.Column{Table: tableAliasName, Name: ref} + } + + exprs := db.Statement.BuildCondition("(?) = (?)", primaryFields, join.On) + joins = append(joins, clause.Join{ + Type: clause.LeftJoin, + Table: clause.Table{Name: relation.FieldSchema.Table, Alias: tableAliasName}, + ON: clause.Where{Exprs: exprs}, + }) + } else { + exprs := make([]clause.Expression, len(relation.References)) + for idx, ref := range relation.References { + if ref.OwnPrimaryKey { exprs[idx] = clause.Eq{ - Column: clause.Column{Table: clause.CurrentTable, Name: ref.ForeignKey.DBName}, - Value: clause.Column{Table: tableAliasName, Name: ref.PrimaryKey.DBName}, + Column: clause.Column{Table: clause.CurrentTable, Name: ref.PrimaryKey.DBName}, + Value: clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName}, } } else { - exprs[idx] = clause.Eq{ - Column: clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName}, - Value: ref.PrimaryValue, + if ref.PrimaryValue == "" { + exprs[idx] = clause.Eq{ + Column: clause.Column{Table: clause.CurrentTable, Name: ref.ForeignKey.DBName}, + Value: clause.Column{Table: tableAliasName, Name: ref.PrimaryKey.DBName}, + } + } else { + exprs[idx] = clause.Eq{ + Column: clause.Column{Table: tableAliasName, Name: ref.ForeignKey.DBName}, + Value: ref.PrimaryValue, + } } } } - } - joins = append(joins, clause.Join{ - Type: clause.LeftJoin, - Table: clause.Table{Name: relation.FieldSchema.Table, Alias: tableAliasName}, - ON: clause.Where{Exprs: exprs}, - }) + joins = append(joins, clause.Join{ + Type: clause.LeftJoin, + Table: clause.Table{Name: relation.FieldSchema.Table, Alias: tableAliasName}, + ON: clause.Where{Exprs: exprs}, + }) + } } else { joins = append(joins, clause.Join{ Expression: clause.NamedExpr{SQL: join.Name, Vars: join.Conds}, diff --git a/chainable_api.go b/chainable_api.go index 88279044..8fd7ee3c 100644 --- a/chainable_api.go +++ b/chainable_api.go @@ -171,8 +171,17 @@ func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB) { // Joins specify Joins conditions // db.Joins("Account").Find(&user) // db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Find(&user) +// db.Joins("Account", DB.Select("id").Where("user_id = users.id AND name = ?", "someName").Model(&Account{})) func (db *DB) Joins(query string, args ...interface{}) (tx *DB) { tx = db.getInstance() + + if len(args) > 0 { + if db, ok := args[0].(*DB); ok { + tx.Statement.Joins = append(tx.Statement.Joins, join{Name: query, Conds: args[1:], On: db}) + return + } + } + tx.Statement.Joins = append(tx.Statement.Joins, join{Name: query, Conds: args}) return } diff --git a/statement.go b/statement.go index 93b78c12..b21b8854 100644 --- a/statement.go +++ b/statement.go @@ -50,6 +50,7 @@ type Statement struct { type join struct { Name string Conds []interface{} + On interface{} } // StatementModifier statement modifier interface diff --git a/tests/go.mod b/tests/go.mod index 3403f6e9..d7ab65ad 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -5,11 +5,11 @@ go 1.14 require ( github.com/google/uuid v1.3.0 github.com/jinzhu/now v1.1.2 - github.com/lib/pq v1.10.2 + github.com/lib/pq v1.10.3 gorm.io/driver/mysql v1.1.2 gorm.io/driver/postgres v1.1.0 gorm.io/driver/sqlite v1.1.4 - gorm.io/driver/sqlserver v1.0.8 + gorm.io/driver/sqlserver v1.0.9 gorm.io/gorm v1.21.14 ) diff --git a/tests/joins_test.go b/tests/joins_test.go index 46611f5f..21c73c19 100644 --- a/tests/joins_test.go +++ b/tests/joins_test.go @@ -104,6 +104,26 @@ func TestJoinConds(t *testing.T) { } } +func TestJoinOn(t *testing.T) { + var user = *GetUser("joins-on", Config{Pets: 2}) + DB.Save(&user) + + var user1 User + onQuery := DB.Select("id").Where("user_id = users.id AND name = ?", "joins-on_pet_1").Model(&Pet{}) + + if err := DB.Joins("NamedPet", onQuery).Where("users.name = ?", user.Name).First(&user1).Error; err != nil { + t.Fatalf("Failed to load with joins on, got error: %v", err) + } + AssertEqual(t, user1.NamedPet.Name, "joins-on_pet_1") + + onQuery2 := DB.Select("id").Where("user_id = users.id AND name = ?", "joins-on_pet_2").Model(&Pet{}) + var user2 User + if err := DB.Joins("NamedPet", onQuery2).Where("users.name = ?", user.Name).First(&user2).Error; err != nil { + t.Fatalf("Failed to load with joins on, got error: %v", err) + } + AssertEqual(t, user2.NamedPet.Name, "joins-on_pet_2") +} + func TestJoinsWithSelect(t *testing.T) { type result struct { ID uint diff --git a/utils/tests/models.go b/utils/tests/models.go index 2c5e71c0..8e833c93 100644 --- a/utils/tests/models.go +++ b/utils/tests/models.go @@ -11,6 +11,7 @@ import ( // He works in a Company (belongs to), he has a Manager (belongs to - single-table), and also managed a Team (has many - single-table) // He speaks many languages (many to many) and has many friends (many to many - single-table) // His pet also has one Toy (has one - polymorphic) +// NamedPet is a reference to a Named `Pets` (has many) type User struct { gorm.Model Name string @@ -18,6 +19,7 @@ type User struct { Birthday *time.Time Account Account Pets []*Pet + NamedPet *Pet Toys []Toy `gorm:"polymorphic:Owner"` CompanyID *int Company Company