diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index d88c81aa1..0919432d5 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -55,11 +55,13 @@ type apiTableName interface { } const ( - OrmTagForStruct = "orm" - OrmTagForUnique = "unique" - OrmTagForPrimary = "primary" - OrmTagForTable = "table" - OrmTagForWith = "with" + OrmTagForStruct = "orm" + OrmTagForUnique = "unique" + OrmTagForPrimary = "primary" + OrmTagForTable = "table" + OrmTagForWith = "with" + OrmTagForWithWhere = "where" + OrmTagForWithOrder = "order" ) var ( diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go index e9fff363c..f2bae7b1e 100644 --- a/database/gdb/gdb_model_with.go +++ b/database/gdb/gdb_model_with.go @@ -88,28 +88,20 @@ func (m *Model) doWithScanStruct(pointer interface{}) error { } for _, field := range fieldMap { var ( - withTag string - ormTag = field.Tag(OrmTagForStruct) - fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") - match, _ = gregex.MatchString( - fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith), - ormTag, - ) + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + parsedTagOutput = m.parseWithTagInFieldStruct(field) ) - if len(match) > 1 { - withTag = match[1] - } - if withTag == "" { + if parsedTagOutput.With == "" { continue } if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) { continue } - array := gstr.SplitAndTrim(withTag, "=") + array := gstr.SplitAndTrim(parsedTagOutput.With, "=") if len(array) == 1 { // It supports using only one column name // if both tables associates using the same column name. - array = append(array, withTag) + array = append(array, parsedTagOutput.With) } var ( model *Model @@ -129,7 +121,7 @@ func (m *Model) doWithScanStruct(pointer interface{}) error { return gerror.NewCodef( gerror.CodeInvalidParameter, `cannot find the related value for attribute name "%s" of with tag "%s"`, - relatedAttrName, withTag, + relatedAttrName, parsedTagOutput.With, ) } bindToReflectValue := field.Value @@ -154,6 +146,12 @@ func (m *Model) doWithScanStruct(pointer interface{}) error { } else { model = model.With(m.withArray...) } + if parsedTagOutput.Where != "" { + model = model.Where(parsedTagOutput.Where) + } + if parsedTagOutput.Order != "" { + model = model.Order(parsedTagOutput.Order) + } err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).Scan(bindToReflectValue) if err != nil { @@ -201,28 +199,20 @@ func (m *Model) doWithScanStructs(pointer interface{}) error { for fieldName, field := range fieldMap { var ( - withTag string - ormTag = field.Tag(OrmTagForStruct) - fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") - match, _ = gregex.MatchString( - fmt.Sprintf(`%s\s*:\s*([^,]+)`, OrmTagForWith), - ormTag, - ) + fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]") + parsedTagOutput = m.parseWithTagInFieldStruct(field) ) - if len(match) > 1 { - withTag = match[1] - } - if withTag == "" { + if parsedTagOutput.With == "" { continue } if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) { continue } - array := gstr.SplitAndTrim(withTag, "=") + array := gstr.SplitAndTrim(parsedTagOutput.With, "=") if len(array) == 1 { // It supports using only one column name // if both tables associates using the same column name. - array = append(array, withTag) + array = append(array, parsedTagOutput.With) } var ( model *Model @@ -242,7 +232,7 @@ func (m *Model) doWithScanStructs(pointer interface{}) error { return gerror.NewCodef( gerror.CodeInvalidParameter, `cannot find the related value for attribute name "%s" of with tag "%s"`, - relatedAttrName, withTag, + relatedAttrName, parsedTagOutput.With, ) } @@ -260,11 +250,58 @@ func (m *Model) doWithScanStructs(pointer interface{}) error { } else { model = model.With(m.withArray...) } + if parsedTagOutput.Where != "" { + model = model.Where(parsedTagOutput.Where) + } + if parsedTagOutput.Order != "" { + model = model.Order(parsedTagOutput.Order) + } - err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).ScanList(pointer, fieldName, withTag) + err = model.Fields(fieldKeys).Where(relatedFieldName, relatedFieldValue).ScanList(pointer, fieldName, parsedTagOutput.With) if err != nil { return err } } return nil } + +type parseWithTagInFieldStructOutput struct { + With string + Where string + Order string +} + +func (m *Model) parseWithTagInFieldStruct(field *structs.Field) (output parseWithTagInFieldStructOutput) { + var ( + match []string + ormTag = field.Tag(OrmTagForStruct) + ) + // with tag. + match, _ = gregex.MatchString( + fmt.Sprintf(`%s\s*:\s*([^,]+),{0,1}`, OrmTagForWith), + ormTag, + ) + if len(match) > 1 { + output.With = match[1] + } + if len(match) > 2 { + output.Where = gstr.Trim(match[2]) + } + // where string. + match, _ = gregex.MatchString( + fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithWhere), + ormTag, + ) + if len(match) > 1 { + output.Where = gstr.Trim(match[1]) + } + // order string. + match, _ = gregex.MatchString( + fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithOrder), + ormTag, + ) + if len(match) > 1 { + output.Order = gstr.Trim(match[1]) + } + return +} diff --git a/database/gdb/gdb_z_mysql_association_with_test.go b/database/gdb/gdb_z_mysql_association_with_test.go index 3ea1c19f1..5a102fa12 100644 --- a/database/gdb/gdb_z_mysql_association_with_test.go +++ b/database/gdb/gdb_z_mysql_association_with_test.go @@ -738,150 +738,141 @@ PRIMARY KEY (id) }) } -//func Test_Table_Relation_WithAllCondition_List(t *testing.T) { -// var ( -// tableUser = "user" -// tableUserDetail = "user_detail" -// tableUserScores = "user_scores" -// ) -// if _, err := db.Exec(fmt.Sprintf(` -//CREATE TABLE IF NOT EXISTS %s ( -//id int(10) unsigned NOT NULL AUTO_INCREMENT, -//name varchar(45) NOT NULL, -//PRIMARY KEY (id) -//) ENGINE=InnoDB DEFAULT CHARSET=utf8; -// `, tableUser)); err != nil { -// gtest.Error(err) -// } -// defer dropTable(tableUser) -// -// if _, err := db.Exec(fmt.Sprintf(` -//CREATE TABLE IF NOT EXISTS %s ( -//uid int(10) unsigned NOT NULL AUTO_INCREMENT, -//address varchar(45) NOT NULL, -//PRIMARY KEY (uid) -//) ENGINE=InnoDB DEFAULT CHARSET=utf8; -// `, tableUserDetail)); err != nil { -// gtest.Error(err) -// } -// defer dropTable(tableUserDetail) -// -// if _, err := db.Exec(fmt.Sprintf(` -//CREATE TABLE IF NOT EXISTS %s ( -//id int(10) unsigned NOT NULL AUTO_INCREMENT, -//uid int(10) unsigned NOT NULL, -//score int(10) unsigned NOT NULL, -//PRIMARY KEY (id) -//) ENGINE=InnoDB DEFAULT CHARSET=utf8; -// `, tableUserScores)); err != nil { -// gtest.Error(err) -// } -// defer dropTable(tableUserScores) -// -// type UserDetail struct { -// gmeta.Meta `orm:"table:user_detail"` -// Uid int `json:"uid"` -// Address string `json:"address"` -// } -// -// type UserScores struct { -// gmeta.Meta `orm:"table:user_scores"` -// Id int `json:"id"` -// Uid int `json:"uid"` -// Score int `json:"score"` -// } -// -// type User struct { -// gmeta.Meta `orm:"table:user"` -// Id int `json:"id"` -// Name string `json:"name"` -// UserDetail *UserDetail `orm:"with:uid=id"` -// UserScores []*UserScores `orm:"with:uid=id, score>1 and score<5"` -// } -// -// // Initialize the data. -// var err error -// for i := 1; i <= 5; i++ { -// // User. -// _, err = db.Insert(tableUser, g.Map{ -// "id": i, -// "name": fmt.Sprintf(`name_%d`, i), -// }) -// gtest.Assert(err, nil) -// // Detail. -// _, err = db.Insert(tableUserDetail, g.Map{ -// "uid": i, -// "address": fmt.Sprintf(`address_%d`, i), -// }) -// gtest.Assert(err, nil) -// // Scores. -// for j := 1; j <= 5; j++ { -// _, err = db.Insert(tableUserScores, g.Map{ -// "uid": i, -// "score": j, -// }) -// gtest.Assert(err, nil) -// } -// } -// -// db.SetDebug(true) -// defer db.SetDebug(false) -// -// gtest.C(t, func(t *gtest.T) { -// var users []*User -// err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users) -// t.AssertNil(err) -// t.Assert(len(users), 2) -// t.Assert(users[0].Id, 3) -// t.Assert(users[0].Name, "name_3") -// t.AssertNE(users[0].UserDetail, nil) -// t.Assert(users[0].UserDetail.Uid, 3) -// t.Assert(users[0].UserDetail.Address, "address_3") -// t.Assert(len(users[0].UserScores), 5) -// t.Assert(users[0].UserScores[0].Uid, 3) -// t.Assert(users[0].UserScores[0].Score, 1) -// t.Assert(users[0].UserScores[4].Uid, 3) -// t.Assert(users[0].UserScores[4].Score, 5) -// -// t.Assert(users[1].Id, 4) -// t.Assert(users[1].Name, "name_4") -// t.AssertNE(users[1].UserDetail, nil) -// t.Assert(users[1].UserDetail.Uid, 4) -// t.Assert(users[1].UserDetail.Address, "address_4") -// t.Assert(len(users[1].UserScores), 5) -// t.Assert(users[1].UserScores[0].Uid, 4) -// t.Assert(users[1].UserScores[0].Score, 1) -// t.Assert(users[1].UserScores[4].Uid, 4) -// t.Assert(users[1].UserScores[4].Score, 5) -// }) -// gtest.C(t, func(t *gtest.T) { -// var users []User -// err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users) -// t.AssertNil(err) -// t.Assert(len(users), 2) -// t.Assert(users[0].Id, 3) -// t.Assert(users[0].Name, "name_3") -// t.AssertNE(users[0].UserDetail, nil) -// t.Assert(users[0].UserDetail.Uid, 3) -// t.Assert(users[0].UserDetail.Address, "address_3") -// t.Assert(len(users[0].UserScores), 5) -// t.Assert(users[0].UserScores[0].Uid, 3) -// t.Assert(users[0].UserScores[0].Score, 1) -// t.Assert(users[0].UserScores[4].Uid, 3) -// t.Assert(users[0].UserScores[4].Score, 5) -// -// t.Assert(users[1].Id, 4) -// t.Assert(users[1].Name, "name_4") -// t.AssertNE(users[1].UserDetail, nil) -// t.Assert(users[1].UserDetail.Uid, 4) -// t.Assert(users[1].UserDetail.Address, "address_4") -// t.Assert(len(users[1].UserScores), 5) -// t.Assert(users[1].UserScores[0].Uid, 4) -// t.Assert(users[1].UserScores[0].Score, 1) -// t.Assert(users[1].UserScores[4].Uid, 4) -// t.Assert(users[1].UserScores[4].Score, 5) -// }) -//} +func Test_Table_Relation_WithAllCondition_List(t *testing.T) { + var ( + tableUser = "user" + tableUserDetail = "user_detail" + tableUserScores = "user_scores" + ) + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +name varchar(45) NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUser)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUser) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +uid int(10) unsigned NOT NULL AUTO_INCREMENT, +address varchar(45) NOT NULL, +PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + if _, err := db.Exec(fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +id int(10) unsigned NOT NULL AUTO_INCREMENT, +uid int(10) unsigned NOT NULL, +score int(10) unsigned NOT NULL, +PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +`, tableUserScores)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserScores) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` + } + + type UserScores struct { + gmeta.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` + } + + type User struct { + gmeta.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id, where:uid > 3"` + UserScores []*UserScores `orm:"with:uid=id, where:score>1 and score<5, order:score desc"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.Assert(err, nil) + // Detail. + _, err = db.Insert(tableUserDetail, g.Map{ + "uid": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + gtest.Assert(err, nil) + // Scores. + for j := 1; j <= 5; j++ { + _, err = db.Insert(tableUserScores, g.Map{ + "uid": i, + "score": j, + }) + gtest.Assert(err, nil) + } + } + + db.SetDebug(true) + defer db.SetDebug(false) + + gtest.C(t, func(t *gtest.T) { + var users []*User + err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.Assert(users[0].UserDetail, nil) + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.AssertNE(users[1].UserDetail, nil) + t.Assert(users[1].UserDetail.Uid, 4) + t.Assert(users[1].UserDetail.Address, "address_4") + t.Assert(len(users[1].UserScores), 3) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 4) + t.Assert(users[1].UserScores[2].Uid, 4) + t.Assert(users[1].UserScores[2].Score, 2) + }) + gtest.C(t, func(t *gtest.T) { + var users []User + err := db.Model(tableUser).WithAll().Where("id", []int{3, 4}).Scan(&users) + t.AssertNil(err) + t.Assert(len(users), 2) + t.Assert(users[0].Id, 3) + t.Assert(users[0].Name, "name_3") + t.Assert(users[0].UserDetail, nil) + + t.Assert(len(users[0].UserScores), 3) + t.Assert(users[0].UserScores[0].Uid, 3) + t.Assert(users[0].UserScores[0].Score, 4) + t.Assert(users[0].UserScores[2].Uid, 3) + t.Assert(users[0].UserScores[2].Score, 2) + + t.Assert(users[1].Id, 4) + t.Assert(users[1].Name, "name_4") + t.AssertNE(users[1].UserDetail, nil) + t.Assert(users[1].UserDetail.Uid, 4) + t.Assert(users[1].UserDetail.Address, "address_4") + t.Assert(len(users[1].UserScores), 3) + t.Assert(users[1].UserScores[0].Uid, 4) + t.Assert(users[1].UserScores[0].Score, 4) + t.Assert(users[1].UserScores[2].Uid, 4) + t.Assert(users[1].UserScores[2].Score, 2) + }) +} func Test_Table_Relation_WithAll_Embedded(t *testing.T) { var (