Skip to content

Commit c709cdc

Browse files
authored
fix: [#858] facades.DB().Insert() can not operate nil columns as expected (#1344)
* fix: [#858] facades.DB().Insert() can not operate nil columns as expected * optimize
1 parent d0b798f commit c709cdc

5 files changed

Lines changed: 147 additions & 12 deletions

File tree

database/db/builder.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ import (
88
)
99

1010
var NameMapper = func(s string) string {
11-
if s == "ID" {
12-
return "id"
13-
}
14-
1511
return str.Of(s).Snake().String()
1612
}
1713

database/db/query.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,16 +1010,23 @@ func (r *Query) buildInsert(data []map[string]any) (sql string, args []any, err
10101010
builder = builder.PlaceholderFormat(placeholderFormat)
10111011
}
10121012

1013-
first := data[0]
1014-
cols := make([]string, 0, len(first))
1015-
for col := range first {
1013+
// Collect all unique columns from all maps to avoid missing columns
1014+
colSet := make(map[string]bool)
1015+
for _, row := range data {
1016+
for col := range row {
1017+
colSet[col] = true
1018+
}
1019+
}
1020+
1021+
cols := make([]string, 0, len(colSet))
1022+
for col := range colSet {
10161023
cols = append(cols, col)
10171024
}
10181025
sort.Strings(cols)
10191026
builder = builder.Columns(cols...)
10201027

10211028
for _, row := range data {
1022-
vals := make([]any, 0, len(first))
1029+
vals := make([]any, 0, len(cols))
10231030
for _, col := range cols {
10241031
vals = append(vals, row[col])
10251032
}

database/db/utils_test.go

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"gorm.io/gorm"
99

1010
"github.com/goravel/framework/support/carbon"
11+
"github.com/goravel/framework/support/convert"
1112
)
1213

1314
type Body struct {
@@ -20,11 +21,25 @@ type Body struct {
2021
leg int `db:"leg"`
2122
}
2223

24+
type House struct {
25+
Address string `db:"address"`
26+
Size *int `db:"size"`
27+
}
28+
29+
type Job struct {
30+
Title string `db:"title"`
31+
Salary *float64 `db:"salary"`
32+
}
33+
2334
type User struct {
24-
ID int `db:"id"`
25-
Name string `db:"-"`
26-
Email string
35+
ID int `db:"id"`
36+
Name string `db:"-"`
37+
Email string
38+
Avatar *string
39+
Alias *int
2740
Body
41+
House *House
42+
Job Job
2843
TestSoftDeletes
2944
TestTimestamps
3045
}
@@ -124,6 +139,114 @@ func TestConvertToSliceMap(t *testing.T) {
124139
},
125140
want: []map[string]any{{"weight": "100kg", "Age": 25}, {"weight": "90kg", "Age": 20}},
126141
},
142+
{
143+
name: "user with nested struct pointer",
144+
data: User{
145+
ID: 1,
146+
Name: "John",
147+
Email: "john@example.com",
148+
Body: Body{Weight: "100kg", Head: &head, DateTime: *dateTime},
149+
House: &House{Address: "123 Main St", Size: nil},
150+
Job: Job{Title: "Engineer", Salary: nil},
151+
TestSoftDeletes: TestSoftDeletes{DeletedAt: deletedAt},
152+
TestTimestamps: TestTimestamps{CreatedAt: dateTime, UpdatedAt: dateTime},
153+
},
154+
want: []map[string]any{
155+
{
156+
"id": 1, "email": "john@example.com", "weight": "100kg", "head": &head, "date_time": *dateTime,
157+
"house": &House{Address: "123 Main St", Size: nil},
158+
"job": Job{Title: "Engineer", Salary: nil},
159+
"created_at": dateTime, "updated_at": dateTime, "deleted_at": deletedAt,
160+
},
161+
},
162+
},
163+
{
164+
name: "user with nil nested struct pointer",
165+
data: User{
166+
ID: 1,
167+
Email: "john@example.com",
168+
Body: Body{Weight: "100kg", DateTime: *dateTime},
169+
House: nil,
170+
Job: Job{Title: "Engineer"},
171+
TestTimestamps: TestTimestamps{CreatedAt: dateTime, UpdatedAt: dateTime},
172+
},
173+
want: []map[string]any{
174+
{
175+
"id": 1, "email": "john@example.com", "weight": "100kg", "date_time": *dateTime,
176+
"job": Job{Title: "Engineer"},
177+
"created_at": dateTime, "updated_at": dateTime,
178+
},
179+
},
180+
},
181+
{
182+
name: "user with pointer fields",
183+
data: func() User {
184+
avatar := "avatar.jpg"
185+
alias := 42
186+
size := 100
187+
salary := 50000.0
188+
return User{
189+
ID: 1,
190+
Email: "john@example.com",
191+
Avatar: &avatar,
192+
Alias: &alias,
193+
Body: Body{Weight: "100kg", Head: &head, DateTime: *dateTime},
194+
House: &House{Address: "123 Main St", Size: &size},
195+
Job: Job{Title: "Engineer", Salary: &salary},
196+
TestTimestamps: TestTimestamps{CreatedAt: dateTime, UpdatedAt: dateTime},
197+
}
198+
}(),
199+
want: []map[string]any{
200+
{
201+
"id": 1, "email": "john@example.com", "avatar": convert.Pointer("avatar.jpg"),
202+
"alias": convert.Pointer(42), "weight": "100kg", "head": &head, "date_time": *dateTime,
203+
"house": &House{Address: "123 Main St", Size: convert.Pointer(100)},
204+
"job": Job{Title: "Engineer", Salary: convert.Pointer(50000.0)},
205+
"created_at": dateTime, "updated_at": dateTime,
206+
},
207+
},
208+
},
209+
{
210+
name: "user slice with mixed nested structs",
211+
data: []User{
212+
{
213+
ID: 1,
214+
Email: "john@example.com",
215+
Body: Body{Length: 10, Weight: "100kg", Head: &head, DateTime: *dateTime},
216+
House: &House{Address: "123 Main St"},
217+
Job: Job{Title: "Engineer"},
218+
TestTimestamps: TestTimestamps{CreatedAt: dateTime, UpdatedAt: dateTime},
219+
},
220+
{
221+
ID: 2,
222+
Email: "jane@example.com",
223+
Body: Body{Weight: "90kg", DateTime: *dateTime},
224+
House: nil,
225+
Job: Job{Title: "Designer"},
226+
TestTimestamps: TestTimestamps{CreatedAt: dateTime, UpdatedAt: dateTime},
227+
},
228+
},
229+
want: []map[string]any{
230+
{
231+
"id": 1, "email": "john@example.com", "length": 10, "weight": "100kg", "head": &head, "date_time": *dateTime,
232+
"house": &House{Address: "123 Main St"},
233+
"job": Job{Title: "Engineer"},
234+
"created_at": dateTime, "updated_at": dateTime,
235+
},
236+
{
237+
"id": 2, "email": "jane@example.com", "weight": "90kg", "date_time": *dateTime,
238+
"job": Job{Title: "Designer"},
239+
"created_at": dateTime, "updated_at": dateTime,
240+
},
241+
},
242+
},
243+
{
244+
name: "user with all zero values",
245+
data: User{},
246+
want: []map[string]any{
247+
{},
248+
},
249+
},
127250
}
128251

129252
for _, tt := range tests {

support/str/str_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ func (s *StringTestSuite) TestSnake() {
555555
s.Equal("a", Of("A").Snake().String())
556556
s.Equal("foo123_bar", Of("foo123Bar").Snake().String())
557557
s.Equal("123foo", Of("123foo").Snake().String())
558+
s.Equal("id", Of("ID").Snake().String())
558559
}
559560

560561
func (s *StringTestSuite) TestSplit() {

tests/db_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,8 @@ func (s *DBTestSuite) TestInsert_First_Get() {
541541
},
542542
},
543543
{
544-
Name: "multiple structs2",
544+
Name: "multiple structs2",
545+
Weight: convert.Pointer(1),
545546
},
546547
})
547548
s.NoError(err)
@@ -551,8 +552,15 @@ func (s *DBTestSuite) TestInsert_First_Get() {
551552
err = query.DB().Table("products").Where("name", []string{"multiple structs1", "multiple structs2"}).Where("deleted_at", nil).Get(&products)
552553
s.NoError(err)
553554
s.Equal(2, len(products))
555+
s.True(products[0].ID > 0)
554556
s.Equal("multiple structs1", products[0].Name)
557+
s.NotEmpty(products[0].CreatedAt)
558+
s.NotEmpty(products[0].UpdatedAt)
559+
s.True(products[1].ID > 0)
555560
s.Equal("multiple structs2", products[1].Name)
561+
s.Equal(1, *products[1].Weight)
562+
s.Empty(products[1].CreatedAt)
563+
s.Empty(products[1].UpdatedAt)
556564
})
557565

558566
s.Run("single map", func() {

0 commit comments

Comments
 (0)