diff --git a/contrib/drivers/mysql/mysql_z_unit_feature_with_test.go b/contrib/drivers/mysql/mysql_z_unit_feature_with_test.go index 64a491991..30390b4cb 100644 --- a/contrib/drivers/mysql/mysql_z_unit_feature_with_test.go +++ b/contrib/drivers/mysql/mysql_z_unit_feature_with_test.go @@ -1888,3 +1888,111 @@ PRIMARY KEY (user_id) t.AssertNil(user3.UserDetail) }) } + +func Test_Table_Relation_WithAll_Order(t *testing.T) { + var ( + tableUser = "user101" + tableUserDetail = "user_detail101" + ) + if _, err := db.Exec(ctx, 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(ctx, fmt.Sprintf(` +CREATE TABLE IF NOT EXISTS %s ( +user_id int(10) unsigned NOT NULL, +address varchar(45) NOT NULL, +deleted_at datetime default NULL , +PRIMARY KEY (user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `, tableUserDetail)); err != nil { + gtest.Error(err) + } + defer dropTable(tableUserDetail) + + type UserDetail struct { + gmeta.Meta `orm:"table:user_detail101"` + UserID int `json:"user_id"` + Address string `json:"address"` + DeletedAt *gtime.Time `json:"deleted_at"` + } + + // For Test Only + type UserEmbedded struct { + ID int `json:"id"` + Name string `json:"name"` + } + + type User struct { + gmeta.Meta `orm:"table:user101"` + UserEmbedded + UserDetail *UserDetail `orm:"with:user_id=id"` + } + type UserWithDeletedDetail struct { + gmeta.Meta `orm:"table:user101"` + UserEmbedded + UserDetail *UserDetail `orm:"with:user_id=id, order:user_id asc,address desc, unscoped:true"` + } + + // Initialize the data. + var err error + for i := 1; i <= 5; i++ { + // User. + _, err = db.Insert(ctx, tableUser, g.Map{ + "id": i, + "name": fmt.Sprintf(`name_%d`, i), + }) + gtest.AssertNil(err) + // Detail. + _, err = db.Insert(ctx, tableUserDetail, g.Map{ + "user_id": i, + "address": fmt.Sprintf(`address_%d`, i), + }) + // Delete detail where i = 3 + if i == 3 { + _, err = db.Delete(ctx, tableUserDetail, g.Map{ + "user_id": i, + }) + } + gtest.AssertNil(err) + } + gtest.C(t, func(t *gtest.T) { + var user0 User + err := db.Model(tableUser).WithAll().Where("id", 4).Scan(&user0) + t.AssertNil(err) + t.Assert(user0.ID, 4) + t.AssertNE(user0.UserDetail, nil) + t.AssertNil(user0.UserDetail.DeletedAt) + t.Assert(user0.UserDetail.UserID, 4) + t.Assert(user0.UserDetail.Address, `address_4`) + + var user1 User + err = db.Model(tableUser).WithAll().Where("id", 3).Scan(&user1) + t.AssertNil(err) + t.Assert(user1.ID, 3) + t.AssertNil(user1.UserDetail) + + var user2 UserWithDeletedDetail + err = db.Model(tableUser).WithAll().Where("id", 3).Scan(&user2) + t.AssertNil(err) + t.Assert(user2.ID, 3) + t.AssertNE(user2.UserDetail, nil) + t.AssertNE(user2.UserDetail.DeletedAt, nil) + t.Assert(user2.UserDetail.UserID, 3) + t.Assert(user2.UserDetail.Address, `address_3`) + + // Unscoped outside test + var user3 User + err = db.Model(tableUser).Unscoped().WithAll().Where("id", 3).Scan(&user3) + t.AssertNil(err) + t.Assert(user3.ID, 3) + t.AssertNil(user3.UserDetail) + }) +} diff --git a/database/gdb/gdb_model_with.go b/database/gdb/gdb_model_with.go index fcd36aeec..8e7e333dd 100644 --- a/database/gdb/gdb_model_with.go +++ b/database/gdb/gdb_model_with.go @@ -333,7 +333,12 @@ func (m *Model) parseWithTagInFieldStruct(field gstructs.Field) (output parseWit key = array[0] data[key] = gstr.Trim(array[1]) } else { - data[key] += " " + gstr.Trim(v) + if key == OrmTagForWithOrder { + // supporting multiple order fields + data[key] += "," + gstr.Trim(v) + } else { + data[key] += " " + gstr.Trim(v) + } } } output.With = data[OrmTagForWith] diff --git a/util/gconv/gconv_z_unit_scan_test.go b/util/gconv/gconv_z_unit_scan_test.go index eddfc9bbc..86f2d2aca 100644 --- a/util/gconv/gconv_z_unit_scan_test.go +++ b/util/gconv/gconv_z_unit_scan_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" @@ -367,10 +368,10 @@ func TestScan(t *testing.T) { t.Assert(test["Name"], scanExpects.structSub.Place) t.Assert(test["Place"], scanExpects.structSub.Name) - //t.Logf("%#v", test) + // t.Logf("%#v", test) err = gconv.Scan(test, &scanExpects.structSubPtr, mapParameter) t.AssertNil(err) - //t.Logf("%#v", scanExpects.structSubPtr) + // t.Logf("%#v", scanExpects.structSubPtr) t.Assert(test["Name"], scanExpects.structSubPtr.Place) t.Assert(test["Place"], scanExpects.structSubPtr.Name) } @@ -421,3 +422,22 @@ func TestScanEmptyStringToCustomType(t *testing.T) { t.Assert(len(req.Types), 0) }) } + +func TestScanDeepSlice(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + req [][]int + req2 [][][]int + data1 = gjson.New("[[1,2,3],[4,5,6]]") + data2 = gjson.New("[[[1,2,3]],[[4,5,6]]]") + ) + err := data1.Scan(&req) + t.AssertNil(err) + err = gconv.Scan(data1.String(), &req) + t.AssertNil(err) + err = data2.Scan(&req2) + t.AssertNil(err) + t.Assert(len(req), 2) + t.Assert(len(req2), 2) + }) +} diff --git a/util/gconv/internal/converter/converter_scan.go b/util/gconv/internal/converter/converter_scan.go index 9630ffb2a..a3dff798f 100644 --- a/util/gconv/internal/converter/converter_scan.go +++ b/util/gconv/internal/converter/converter_scan.go @@ -159,6 +159,7 @@ func (c *Converter) Scan(srcValue any, dstPointer any, option ...ScanOption) (er dstElemType = dstPointerReflectValueElem.Type().Elem() dstElemKind = dstElemType.Kind() ) + // The slice element might be a pointer type if dstElemKind == reflect.Ptr { dstElemType = dstElemType.Elem() @@ -209,9 +210,12 @@ func (c *Converter) Scan(srcValue any, dstPointer any, option ...ScanOption) (er } newSlice.Index(i).SetBool(v) default: - return c.Scan( + err = c.Scan( srcElem, newSlice.Index(i).Addr().Interface(), option..., ) + if err != nil && !scanOption.ContinueOnError { + return err + } } } dstPointerReflectValueElem.Set(newSlice)