Skip to content

Commit 34d377d

Browse files
authored
Merge pull request #309 from simon-a-j/feat/omitnil-omitempty
feat: omitempty and omitnil struct tags
2 parents 31d438d + 293c6da commit 34d377d

File tree

8 files changed

+362
-30
lines changed

8 files changed

+362
-30
lines changed

docs/inserting.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,65 @@ Output:
175175
```
176176
INSERT INTO "user" ("last_name") VALUES ('Farley'), ('Stewart'), ('Jeffers') []
177177
```
178+
If you do not want to set the database field when the struct field is a nil pointer you can use the `omitnil` tag.
179+
180+
```go
181+
type item struct {
182+
FirstName string `db:"first_name" goqu:"omitnil"`
183+
LastName string `db:"last_name" goqu:"omitnil"`
184+
Address1 *string `db:"address1" goqu:"omitnil"`
185+
Address2 *string `db:"address2" goqu:"omitnil"`
186+
Address3 *string `db:"address3" goqu:"omitnil"`
187+
}
188+
address1 := "111 Test Addr"
189+
var emptyString string
190+
i := item{
191+
FirstName: "Test First Name",
192+
LastName: "",
193+
Address1: &address1,
194+
Address2: &emptyString,
195+
Address3: nil, // will omit nil pointer
196+
}
197+
198+
insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL()
199+
fmt.Println(insertSQL, args)
200+
```
201+
202+
Output:
203+
```
204+
INSERT INTO "items" ("address1", "address2", "first_name", "last_name") VALUES ('111 Test Addr', '', 'Test First Name', '') []
205+
```
206+
207+
If you do not want to set the database field when the struct field is a zero value (including nil pointers) you can use
208+
the `omitempty` tag.
209+
210+
Empty embedded structs implementing the `Valuer` interface (eg. `sql.NullString`) will also be omitted.
211+
212+
```go
213+
type item struct {
214+
FirstName string `db:"first_name" goqu:"omitempty"`
215+
LastName string `db:"last_name" goqu:"omitempty"`
216+
Address1 *string `db:"address1" goqu:"omitempty"`
217+
Address2 *string `db:"address2" goqu:"omitempty"`
218+
Address3 *string `db:"address3" goqu:"omitempty"`
219+
}
220+
address1 := "112 Test Addr"
221+
var emptyString string
222+
i := item{
223+
FirstName: "Test First Name",
224+
LastName: "", // will omit zero field
225+
Address1: &address1,
226+
Address2: &emptyString,
227+
Address3: nil, // will omit nil pointer
228+
}
229+
insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL()
230+
fmt.Println(insertSQL, args)
231+
```
232+
233+
Output:
234+
```
235+
INSERT INTO "items" ("address1", "address2", "first_name") VALUES ('112 Test Addr', '', 'Test First Name') []
236+
```
178237

179238
If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag.
180239

docs/updating.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,68 @@ Output:
154154
UPDATE "items" SET "address"='111 Test Addr' []
155155
```
156156

157+
If you do not want to update the database field when the struct field is a nil pointer you can use the `omitnil` tag.
158+
This allows a struct of pointers to be used to represent partial updates where nil pointers were not changed.
159+
160+
```go
161+
type item struct {
162+
FirstName string `db:"first_name" goqu:"omitnil"`
163+
LastName string `db:"last_name" goqu:"omitnil"`
164+
Address1 *string `db:"address1" goqu:"omitnil"`
165+
Address2 *string `db:"address2" goqu:"omitnil"`
166+
Address3 *string `db:"address3" goqu:"omitnil"`
167+
}
168+
address1 := "113 Test Addr"
169+
var emptyString string
170+
sql, args, _ := goqu.Update("items").Set(
171+
item{
172+
FirstName: "Test First Name",
173+
LastName: "",
174+
Address1: &address1,
175+
Address2: &emptyString,
176+
Address3: nil, // will omit nil pointer
177+
},
178+
).ToSQL()
179+
fmt.Println(sql, args)
180+
```
181+
182+
Output:
183+
```
184+
UPDATE "items" SET "address1"='113 Test Addr',"address2"='',"first_name"='Test First Name',"last_name"='' []
185+
```
186+
187+
If you do not want to update the database field when the struct field is a zero value (including nil pointers) you can
188+
use the `omitempty` tag.
189+
190+
Empty embedded structs implementing the `Valuer` interface (eg. `sql.NullString`) will also be omitted.
191+
192+
```go
193+
type item struct {
194+
FirstName string `db:"first_name" goqu:"omitempty"`
195+
LastName string `db:"last_name" goqu:"omitempty"`
196+
Address1 *string `db:"address1" goqu:"omitempty"`
197+
Address2 *string `db:"address2" goqu:"omitempty"`
198+
Address3 *string `db:"address3" goqu:"omitempty"`
199+
}
200+
address1 := "114 Test Addr"
201+
var emptyString string
202+
sql, args, _ := goqu.Update("items").Set(
203+
item{
204+
FirstName: "Test First Name",
205+
LastName: "", // will omit zero field
206+
Address1: &address1,
207+
Address2: &emptyString,
208+
Address3: nil, // will omit nil pointer
209+
},
210+
).ToSQL()
211+
fmt.Println(sql, args)
212+
```
213+
214+
Output:
215+
```
216+
UPDATE "items" SET "address1"='114 Test Addr',"address2"='',"first_name"='Test First Name' []
217+
```
218+
157219
If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag.
158220

159221
```go

exp/record.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ func NewRecordFromStruct(i interface{}, forInsert, forUpdate bool) (r Record, er
3131
for _, col := range cols {
3232
f := cm[col]
3333
if !shouldSkipField(f, forInsert, forUpdate) {
34-
if ok, fieldVal := getFieldValue(value, f); ok {
35-
r[f.ColumnName] = fieldVal
34+
if fieldValue, isAvailable := util.SafeGetFieldByIndex(value, f.FieldIndex); isAvailable {
35+
if !shouldOmitField(fieldValue, f) {
36+
r[f.ColumnName] = getRecordValue(fieldValue, f)
37+
}
3638
}
3739
}
3840
}
@@ -46,14 +48,21 @@ func shouldSkipField(f util.ColumnData, forInsert, forUpdate bool) bool {
4648
return shouldSkipInsert || shouldSkipUpdate
4749
}
4850

49-
func getFieldValue(val reflect.Value, f util.ColumnData) (ok bool, fieldVal interface{}) {
50-
if v, isAvailable := util.SafeGetFieldByIndex(val, f.FieldIndex); !isAvailable {
51-
return false, nil
52-
} else if f.DefaultIfEmpty && util.IsEmptyValue(v) {
53-
return true, Default()
54-
} else if v.IsValid() {
55-
return true, v.Interface()
51+
func shouldOmitField(val reflect.Value, f util.ColumnData) bool {
52+
if f.OmitNil && util.IsNil(val) {
53+
return true
54+
} else if f.OmitEmpty && util.IsEmptyValue(val) {
55+
return true
56+
}
57+
return false
58+
}
59+
60+
func getRecordValue(val reflect.Value, f util.ColumnData) interface{} {
61+
if f.DefaultIfEmpty && util.IsEmptyValue(val) {
62+
return Default()
63+
} else if val.IsValid() {
64+
return val.Interface()
5665
} else {
57-
return true, reflect.Zero(f.GoType).Interface()
66+
return reflect.Zero(f.GoType).Interface()
5867
}
5968
}

insert_dataset_example_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,81 @@ func ExampleInsertDataset_Rows_withGoquSkipInsertTag() {
377377
// INSERT INTO "items" ("address") VALUES ('111 Test Addr'), ('112 Test Addr') []
378378
}
379379

380+
func ExampleInsertDataset_Rows_withOmitNilTag() {
381+
type item struct {
382+
FirstName string `db:"first_name" goqu:"omitnil"`
383+
LastName string `db:"last_name" goqu:"omitnil"`
384+
Address1 *string `db:"address1" goqu:"omitnil"`
385+
Address2 *string `db:"address2" goqu:"omitnil"`
386+
Address3 *string `db:"address3" goqu:"omitnil"`
387+
}
388+
address1 := "111 Test Addr"
389+
var emptyString string
390+
i := item{
391+
FirstName: "Test First Name",
392+
LastName: "",
393+
Address1: &address1,
394+
Address2: &emptyString,
395+
Address3: nil, // will omit nil pointer
396+
}
397+
398+
insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL()
399+
fmt.Println(insertSQL, args)
400+
401+
// Output:
402+
// INSERT INTO "items" ("address1", "address2", "first_name", "last_name") VALUES ('111 Test Addr', '', 'Test First Name', '') []
403+
}
404+
405+
func ExampleInsertDataset_Rows_withOmitEmptyTag() {
406+
type item struct {
407+
FirstName string `db:"first_name" goqu:"omitempty"`
408+
LastName string `db:"last_name" goqu:"omitempty"`
409+
Address1 *string `db:"address1" goqu:"omitempty"`
410+
Address2 *string `db:"address2" goqu:"omitempty"`
411+
Address3 *string `db:"address3" goqu:"omitempty"`
412+
}
413+
address1 := "112 Test Addr"
414+
var emptyString string
415+
i := item{
416+
FirstName: "Test First Name",
417+
LastName: "", // will omit zero field
418+
Address1: &address1,
419+
Address2: &emptyString,
420+
Address3: nil, // will omit nil pointer
421+
}
422+
insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL()
423+
fmt.Println(insertSQL, args)
424+
425+
// Output:
426+
// INSERT INTO "items" ("address1", "address2", "first_name") VALUES ('112 Test Addr', '', 'Test First Name') []
427+
}
428+
429+
func ExampleInsertDataset_Rows_withOmitEmptyTag_Valuer() {
430+
type item struct {
431+
FirstName sql.NullString `db:"first_name" goqu:"omitempty"`
432+
MiddleName sql.NullString `db:"middle_name" goqu:"omitempty"`
433+
LastName sql.NullString `db:"last_name" goqu:"omitempty"`
434+
Address1 *sql.NullString `db:"address1" goqu:"omitempty"`
435+
Address2 *sql.NullString `db:"address2" goqu:"omitempty"`
436+
Address3 *sql.NullString `db:"address3" goqu:"omitempty"`
437+
Address4 *sql.NullString `db:"address4" goqu:"omitempty"`
438+
}
439+
i := item{
440+
FirstName: sql.NullString{Valid: true, String: "Test First Name"},
441+
MiddleName: sql.NullString{Valid: true, String: ""},
442+
LastName: sql.NullString{}, // will omit zero valuer struct
443+
Address1: &sql.NullString{Valid: true, String: "Test Address 1"},
444+
Address2: &sql.NullString{Valid: true, String: ""},
445+
Address3: &sql.NullString{},
446+
Address4: nil, // will omit nil pointer
447+
}
448+
insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL()
449+
fmt.Println(insertSQL, args)
450+
451+
// Output:
452+
// INSERT INTO "items" ("address1", "address2", "address3", "first_name", "middle_name") VALUES ('Test Address 1', '', NULL, 'Test First Name', '') []
453+
}
454+
380455
func ExampleInsertDataset_Rows_withGoquDefaultIfEmptyTag() {
381456
type item struct {
382457
ID uint32 `goqu:"skipinsert"`

internal/util/column_map.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ type (
1515
ShouldInsert bool
1616
ShouldUpdate bool
1717
DefaultIfEmpty bool
18+
OmitNil bool
19+
OmitEmpty bool
1820
GoType reflect.Type
1921
}
2022
ColumnMap map[string]ColumnData
@@ -91,6 +93,8 @@ func newColumnData(f *reflect.StructField, columnName string, fieldIndex []int,
9193
ShouldInsert: !goquTag.Contains(skipInsertTagName),
9294
ShouldUpdate: !goquTag.Contains(skipUpdateTagName),
9395
DefaultIfEmpty: goquTag.Contains(defaultIfEmptyTagName),
96+
OmitNil: goquTag.Contains(omitNilTagName),
97+
OmitEmpty: goquTag.Contains(omitEmptyTagName),
9498
FieldIndex: concatFieldIndexes(fieldIndex, f.Index),
9599
GoType: f.Type,
96100
}

internal/util/reflect.go

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const (
1313
skipUpdateTagName = "skipupdate"
1414
skipInsertTagName = "skipinsert"
1515
defaultIfEmptyTagName = "defaultifempty"
16+
omitNilTagName = "omitnil"
17+
omitEmptyTagName = "omitempty"
1618
)
1719

1820
var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
@@ -62,27 +64,22 @@ func IsPointer(k reflect.Kind) bool {
6264
return k == reflect.Ptr
6365
}
6466

65-
func IsEmptyValue(v reflect.Value) bool {
67+
func IsNil(v reflect.Value) bool {
68+
if !v.IsValid() {
69+
return true
70+
}
6671
switch v.Kind() {
67-
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
68-
return v.Len() == 0
69-
case reflect.Bool:
70-
return !v.Bool()
71-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
72-
return v.Int() == 0
73-
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
74-
return v.Uint() == 0
75-
case reflect.Float32, reflect.Float64:
76-
return v.Float() == 0
77-
case reflect.Interface, reflect.Ptr:
72+
case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func:
7873
return v.IsNil()
79-
case reflect.Invalid:
80-
return true
8174
default:
8275
return false
8376
}
8477
}
8578

79+
func IsEmptyValue(v reflect.Value) bool {
80+
return !v.IsValid() || v.IsZero()
81+
}
82+
8683
var (
8784
structMapCache = make(map[interface{}]ColumnMap)
8885
structMapCacheLock = sync.Mutex{}

0 commit comments

Comments
 (0)