Repository: zeromicro/go-zero
Pull request #2884 (2023-02-15 06:45:32)

fix  QueryRowsPartial  getTaggedFieldValueMap func
When using orm, a problem was found. When you use QueryRowsPartial to get a small number of query fields, the scanner scans the struct in the order it was passed in, causing an err
The following structure:
<img width="448" alt="image" src="https://user-images.githubusercontent.com/13843557/218952069-c4f80a37-49bc-4b2f-94fa-6980fd0166af.png">
<img width="747" alt="image" src="https://user-images.githubusercontent.com/13843557/218952119-af3efdbc-2dec-4cdb-b744-7fe8a3843558.png">
err：
<img width="952" alt="image" src="https://user-images.githubusercontent.com/13843557/218952627-af8e241e-a30c-4c61-8a4a-f44a493b9ae5.png">


fulldog: @kevin0527 大佬看一下问题
Issues-translate-bot:  
> Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿         
----    
@kevin0527 big guy look at the problem  
      
kevwan: Please check and fix UT.
fulldog: @kevwan The bug has been fixed and tested
 
kevwan: Thanks for your PR! Please add unit tests.
fulldog: @kevwan  sorry for this.
test case is in core/stores/sqlx/orm_test.go,func TestAnonymousStructPr().
that case  has been tested in orm_test.go
codecov[bot]: # [Codecov](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#2884](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (0271949) into [master](https://codecov.io/gh/zeromicro/go-zero/commit/133c40ac1cbc357788d7936351ce2d514642855a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (133c40a) will **increase** coverage by `0.03%`.
> The diff coverage is `70.00%`.


| [Impacted Files](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [core/stores/sqlx/orm.go](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9zdG9yZXMvc3FseC9vcm0uZ28=) | `65.93% <70.00%> (-0.16%)` | :arrow_down: |
| [core/stores/sqlx/trace.go](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9zdG9yZXMvc3FseC90cmFjZS5nbw==) | `73.33% <0.00%> (-4.45%)` | :arrow_down: |
| [core/trace/utils.go](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS90cmFjZS91dGlscy5nbw==) | `100.00% <0.00%> (ø)` | |
| [rest/httpc/requests.go](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cmVzdC9odHRwYy9yZXF1ZXN0cy5nbw==) | `100.00% <0.00%> (ø)` | |
| [rest/httpx/requests.go](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cmVzdC9odHRweC9yZXF1ZXN0cy5nbw==) | `100.00% <0.00%> (ø)` | |
| [core/stores/mon/trace.go](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9zdG9yZXMvbW9uL3RyYWNlLmdv) | `100.00% <0.00%> (ø)` | |
| [core/stores/redis/hook.go](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9zdG9yZXMvcmVkaXMvaG9vay5nbw==) | `88.49% <0.00%> (+0.20%)` | :arrow_up: |
| [rest/engine.go](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cmVzdC9lbmdpbmUuZ28=) | `93.30% <0.00%> (+0.24%)` | :arrow_up: |
| [core/logx/rotatelogger.go](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-Y29yZS9sb2d4L3JvdGF0ZWxvZ2dlci5nbw==) | `72.69% <0.00%> (+1.10%)` | :arrow_up: |
| ... and [1 more](https://codecov.io/gh/zeromicro/go-zero/pull/2884?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | |


Patch:
diff --git a/core/stores/sqlx/orm.go b/core/stores/sqlx/orm.go
index b6e1b3bd..a4c71d3d 100644
--- a/core/stores/sqlx/orm.go
+++ b/core/stores/sqlx/orm.go
@@ -34,9 +34,20 @@ func getTaggedFieldValueMap(v reflect.Value) (map[string]any, error) {
 	result := make(map[string]any, size)
 
 	for i := 0; i < size; i++ {
+		if (rt.Field(i).Type.Kind() == reflect.Struct || rt.Field(i).Type.Kind() == reflect.Ptr) && rt.Field(i).Anonymous {
+			r, e := getTaggedFieldValueMap(reflect.Indirect(v).Field(i))
+			if e != nil {
+				return nil, e
+			}
+			for i2, i3 := range r {
+				result[i2] = i3
+			}
+			continue
+		}
+
 		key := parseTagName(rt.Field(i))
 		if len(key) == 0 {
-			return nil, nil
+			continue
 		}
 
 		valueField := reflect.Indirect(v).Field(i)


Test patch:
diff --git a/core/stores/sqlx/orm_test.go b/core/stores/sqlx/orm_test.go
index c6066a76..c33ca7d6 100644
--- a/core/stores/sqlx/orm_test.go
+++ b/core/stores/sqlx/orm_test.go
@@ -1041,6 +1041,106 @@ func TestUnmarshalRowError(t *testing.T) {
 	}
 }
 
+func TestAnonymousStructPr(t *testing.T) {
+	type Score struct {
+		Discipline string `db:"discipline"`
+		Score      uint   `db:"score"`
+	}
+	type ClassType struct {
+		Grade     sql.NullString `db:"grade"`
+		ClassName *string        `db:"class_name"`
+	}
+	type Class struct {
+		*ClassType
+		Score
+	}
+	expect := []*struct {
+		Name       string
+		Age        int64
+		Grade      sql.NullString
+		Discipline string
+		Score      uint
+		ClassName  string
+	}{
+		{
+			Name: "first",
+			Age:  2,
+			Grade: sql.NullString{
+				String: "",
+				Valid:  false,
+			},
+			ClassName:  "实验班",
+			Discipline: "数学",
+			Score:      100,
+		},
+		{
+			Name: "second",
+			Age:  3,
+			Grade: sql.NullString{
+				String: "大一",
+				Valid:  true,
+			},
+			ClassName:  "三班二年",
+			Discipline: "语文",
+			Score:      99,
+		},
+	}
+	var value []*struct {
+		Age int64 `db:"age"`
+		Class
+		Name string `db:"name"`
+	}
+
+	runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
+		rs := sqlmock.NewRows([]string{"name", "age", "grade", "discipline", "class_name", "score"}).AddRow("first", 2, nil, "数学", "实验班", 100).
+			AddRow("second", 3, "大一", "语文", "三班二年", 99)
+		mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
+		assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
+			return unmarshalRows(&value, rows, true)
+		}, "select name, age,grade,discipline,class_name,score from users where user=?", "anyone"))
+
+		for i, each := range expect {
+			assert.Equal(t, each.Name, value[i].Name)
+			assert.Equal(t, each.Age, value[i].Age)
+			assert.Equal(t, each.ClassName, *value[i].Class.ClassName)
+			assert.Equal(t, each.Discipline, value[i].Score.Discipline)
+			assert.Equal(t, each.Score, value[i].Score.Score)
+			assert.Equal(t, each.Grade, value[i].Class.Grade)
+		}
+	})
+}
+func TestAnonymousStructPrError(t *testing.T) {
+	type Score struct {
+		Discipline string `db:"discipline"`
+		score      uint   `db:"score"`
+	}
+	type ClassType struct {
+		Grade     sql.NullString `db:"grade"`
+		ClassName *string        `db:"class_name"`
+	}
+	type Class struct {
+		*ClassType
+		Score
+	}
+	var value []*struct {
+		Age int64 `db:"age"`
+		Class
+		Name string `db:"name"`
+	}
+
+	runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
+		rs := sqlmock.NewRows([]string{"name", "age", "grade", "discipline", "class_name", "score"}).AddRow("first", 2, nil, "数学", "实验班", 100).
+			AddRow("second", 3, "大一", "语文", "三班二年", 99)
+		mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
+		assert.Error(t, query(context.Background(), db, func(rows *sql.Rows) error {
+			return unmarshalRows(&value, rows, true)
+		}, "select name, age,grade,discipline,class_name,score from users where user=?", "anyone"))
+		if len(value) > 0 {
+			assert.Equal(t, value[0].score, 0)
+		}
+	})
+}
+
 func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
 	logx.Disable()