Skip to content

Commit

Permalink
Add omitnil and omitempty goqu struct tags
Browse files Browse the repository at this point in the history
  • Loading branch information
simon-a-j committed Oct 9, 2021
1 parent a7630f7 commit 185f7d1
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 16 deletions.
29 changes: 19 additions & 10 deletions exp/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ func NewRecordFromStruct(i interface{}, forInsert, forUpdate bool) (r Record, er
for _, col := range cols {
f := cm[col]
if !shouldSkipField(f, forInsert, forUpdate) {
if ok, fieldVal := getFieldValue(value, f); ok {
r[f.ColumnName] = fieldVal
if fieldValue, isAvailable := util.SafeGetFieldByIndex(value, f.FieldIndex); isAvailable {
if fieldValue.IsValid() && !shouldOmitField(fieldValue, f) {
r[f.ColumnName] = getRecordValue(fieldValue, f)
}
}
}
}
Expand All @@ -46,14 +48,21 @@ func shouldSkipField(f util.ColumnData, forInsert, forUpdate bool) bool {
return shouldSkipInsert || shouldSkipUpdate
}

func getFieldValue(val reflect.Value, f util.ColumnData) (ok bool, fieldVal interface{}) {
if v, isAvailable := util.SafeGetFieldByIndex(val, f.FieldIndex); !isAvailable {
return false, nil
} else if f.DefaultIfEmpty && util.IsEmptyValue(v) {
return true, Default()
} else if v.IsValid() {
return true, v.Interface()
func shouldOmitField(val reflect.Value, f util.ColumnData) bool {
if f.OmitNil && util.IsNilPointer(val) {
return true
} else if f.OmitEmpty && util.IsEmptyValue(val) {
return true
}
return false
}

func getRecordValue(val reflect.Value, f util.ColumnData) interface{} {
if f.DefaultIfEmpty && util.IsEmptyValue(val) {
return Default()
} else if val.IsValid() {
return val.Interface()
} else {
return true, reflect.Zero(f.GoType).Interface()
return reflect.Zero(f.GoType).Interface()
}
}
75 changes: 75 additions & 0 deletions insert_dataset_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,81 @@ func ExampleInsertDataset_Rows_withGoquSkipInsertTag() {
// INSERT INTO "items" ("address") VALUES ('111 Test Addr'), ('112 Test Addr') []
}

func ExampleInsertDataset_Rows_withOmitNilTag() {
type item struct {
FirstName string `db:"first_name" goqu:"omitnil"`
LastName string `db:"last_name" goqu:"omitnil"`
Address1 *string `db:"address1" goqu:"omitnil"`
Address2 *string `db:"address2" goqu:"omitnil"`
Address3 *string `db:"address3" goqu:"omitnil"`
}
testString := "Test"
emptyString := ""
i := item{FirstName: "Test", Address1: &testString, Address2: &emptyString}

insertSQL, args, _ := goqu.Insert("items").
Rows(
i,
).
ToSQL()
fmt.Println(insertSQL, args)

// Output:
// INSERT INTO "items" ("address1", "address2", "first_name", "last_name") VALUES ('Test', '', 'Test', '') []
}

func ExampleInsertDataset_Rows_withOmitEmptyTag() {
type item struct {
FirstName string `db:"first_name" goqu:"omitempty"`
LastName string `db:"last_name" goqu:"omitempty"`
Address1 *string `db:"address1" goqu:"omitempty"`
Address2 *string `db:"address2" goqu:"omitempty"`
Address3 *string `db:"address3" goqu:"omitempty"`
}
testString := "Test"
emptyString := ""
i := item{
FirstName: "Test", Address1: &testString, Address2: &emptyString,
}
insertSQL, args, _ := goqu.Insert("items").
Rows(
i,
).
ToSQL()
fmt.Println(insertSQL, args)

// Output:
// INSERT INTO "items" ("address1", "address2", "first_name") VALUES ('Test', '', 'Test') []
}

func ExampleInsertDataset_Rows_withOmitEmptyTag_Valuer() {
type item struct {
FirstName sql.NullString `db:"first_name" goqu:"omitempty"`
MiddleName sql.NullString `db:"middle_name" goqu:"omitempty"`
LastName sql.NullString `db:"last_name" goqu:"omitempty"`
Address1 *sql.NullString `db:"address1" goqu:"omitempty"`
Address2 *sql.NullString `db:"address2" goqu:"omitempty"`
Address3 *sql.NullString `db:"address3" goqu:"omitempty"`
Address4 *sql.NullString `db:"address4" goqu:"omitempty"`
}
i := item{
FirstName: sql.NullString{Valid: true, String: "Test"},
MiddleName: sql.NullString{Valid: true, String: ""},
Address1: &sql.NullString{Valid: true, String: "Test"},
Address2: &sql.NullString{Valid: true, String: ""},
Address3: &sql.NullString{},
}
insertSQL, args, _ := goqu.Insert("items").
Rows(
i,
).
ToSQL()
fmt.Println(insertSQL, args)

// Output:
// INSERT INTO "items" ("address1", "address2", "address3", "first_name", "middle_name") VALUES ('Test', '', NULL, 'Test', '') []
}

func ExampleInsertDataset_Rows_withGoquDefaultIfEmptyTag() {
type item struct {
ID uint32 `goqu:"skipinsert"`
Expand Down
4 changes: 4 additions & 0 deletions internal/util/column_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type (
ShouldInsert bool
ShouldUpdate bool
DefaultIfEmpty bool
OmitNil bool
OmitEmpty bool
GoType reflect.Type
}
ColumnMap map[string]ColumnData
Expand Down Expand Up @@ -91,6 +93,8 @@ func newColumnData(f *reflect.StructField, columnName string, fieldIndex []int,
ShouldInsert: !goquTag.Contains(skipInsertTagName),
ShouldUpdate: !goquTag.Contains(skipUpdateTagName),
DefaultIfEmpty: goquTag.Contains(defaultIfEmptyTagName),
OmitNil: goquTag.Contains(omitNilTagName),
OmitEmpty: goquTag.Contains(omitEmptyTagName),
FieldIndex: concatFieldIndexes(fieldIndex, f.Index),
GoType: f.Type,
}
Expand Down
8 changes: 8 additions & 0 deletions internal/util/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const (
skipUpdateTagName = "skipupdate"
skipInsertTagName = "skipinsert"
defaultIfEmptyTagName = "defaultifempty"
omitNilTagName = "omitnil"
omitEmptyTagName = "omitempty"
)

var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
Expand Down Expand Up @@ -76,13 +78,19 @@ func IsEmptyValue(v reflect.Value) bool {
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Struct:
return v.IsZero()
case reflect.Invalid:
return true
default:
return false
}
}

func IsNilPointer(v reflect.Value) bool {
return v.Kind() == reflect.Ptr && v.IsNil()
}

var (
structMapCache = make(map[interface{}]ColumnMap)
structMapCacheLock = sync.Mutex{}
Expand Down
32 changes: 26 additions & 6 deletions internal/util/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ func (rt *reflectTest) TestIsEmptyValue_emptyValues() {
rt.True(util.IsEmptyValue(reflect.ValueOf(ts.f64)))
rt.True(util.IsEmptyValue(reflect.ValueOf(ts.intr)))
rt.True(util.IsEmptyValue(reflect.ValueOf(ts.ptr)))
rt.True(util.IsEmptyValue(reflect.ValueOf(ts)))
}

func (rt *reflectTest) TestIsEmptyValue_validValues() {
Expand All @@ -364,6 +365,7 @@ func (rt *reflectTest) TestIsEmptyValue_validValues() {
rt.False(util.IsEmptyValue(reflect.ValueOf(float32(0.1))))
rt.False(util.IsEmptyValue(reflect.ValueOf(float64(0.2))))
rt.False(util.IsEmptyValue(reflect.ValueOf(ts.intr)))
rt.False(util.IsEmptyValue(reflect.ValueOf(ts)))
rt.False(util.IsEmptyValue(reflect.ValueOf(&TestStruct{str: "a"})))
}

Expand Down Expand Up @@ -681,11 +683,13 @@ func (rt *reflectTest) TestGetColumnMap_withStruct() {

func (rt *reflectTest) TestGetColumnMap_withStructGoquTags() {
type TestStruct struct {
Str string `goqu:"skipinsert,skipupdate"`
Int int64 `goqu:"skipinsert"`
Bool bool `goqu:"skipupdate"`
Empty bool `goqu:"defaultifempty"`
Valuer *sql.NullString
Str string `goqu:"skipinsert,skipupdate"`
Int int64 `goqu:"skipinsert"`
Bool bool `goqu:"skipupdate"`
Empty bool `goqu:"defaultifempty"`
OmitNil bool `goqu:"omitnil"`
OmitEmpty bool `goqu:"omitempty"`
Valuer *sql.NullString
}
var ts TestStruct
cm, err := util.GetColumnMap(&ts)
Expand All @@ -702,7 +706,23 @@ func (rt *reflectTest) TestGetColumnMap_withStructGoquTags() {
DefaultIfEmpty: true,
GoType: reflect.TypeOf(true),
},
"valuer": {ColumnName: "valuer", FieldIndex: []int{4}, ShouldInsert: true, ShouldUpdate: true, GoType: reflect.TypeOf(&sql.NullString{})},
"omitnil": {
ColumnName: "omitnil",
FieldIndex: []int{4},
ShouldInsert: true,
ShouldUpdate: true,
OmitNil: true,
GoType: reflect.TypeOf(true),
},
"omitempty": {
ColumnName: "omitempty",
FieldIndex: []int{5},
ShouldInsert: true,
ShouldUpdate: true,
OmitEmpty: true,
GoType: reflect.TypeOf(true),
},
"valuer": {ColumnName: "valuer", FieldIndex: []int{6}, ShouldInsert: true, ShouldUpdate: true, GoType: reflect.TypeOf(&sql.NullString{})},
}, cm)
}

Expand Down
64 changes: 64 additions & 0 deletions update_dataset_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package goqu_test

import (
"database/sql"
"fmt"

"github.com/doug-martin/goqu/v9"
Expand All @@ -22,6 +23,69 @@ func ExampleUpdate_withStruct() {
// UPDATE "items" SET "address"='111 Test Addr',"name"='Test' []
}

func ExampleUpdate_withOmitNilTag() {
type item struct {
FirstName string `db:"first_name" goqu:"omitnil"`
LastName string `db:"last_name" goqu:"omitnil"`
Address1 *string `db:"address1" goqu:"omitnil"`
Address2 *string `db:"address2" goqu:"omitnil"`
Address3 *string `db:"address3" goqu:"omitnil"`
}
testString := "Test"
emptyString := ""
query, args, _ := goqu.Update("items").Set(
item{FirstName: "Test", Address1: &testString, Address2: &emptyString},
).ToSQL()
fmt.Println(query, args)

// Output:
// UPDATE "items" SET "address1"='Test',"address2"='',"first_name"='Test',"last_name"='' []
}

func ExampleUpdate_withOmitEmptyTag() {
type item struct {
FirstName string `db:"first_name" goqu:"omitempty"`
LastName string `db:"last_name" goqu:"omitempty"`
Address1 *string `db:"address1" goqu:"omitempty"`
Address2 *string `db:"address2" goqu:"omitempty"`
Address3 *string `db:"address3" goqu:"omitempty"`
}
testString := "Test"
emptyString := ""
query, args, _ := goqu.Update("items").Set(
item{FirstName: "Test", Address1: &testString, Address2: &emptyString},
).ToSQL()
fmt.Println(query, args)

// Output:
// UPDATE "items" SET "address1"='Test',"address2"='',"first_name"='Test' []
}

func ExampleUpdate_withOmitEmptyTag_Valuer() {
type item struct {
FirstName sql.NullString `db:"first_name" goqu:"omitempty"`
MiddleName sql.NullString `db:"middle_name" goqu:"omitempty"`
LastName sql.NullString `db:"last_name" goqu:"omitempty"`
Address1 *sql.NullString `db:"address1" goqu:"omitempty"`
Address2 *sql.NullString `db:"address2" goqu:"omitempty"`
Address3 *sql.NullString `db:"address3" goqu:"omitempty"`
Address4 *sql.NullString `db:"address4" goqu:"omitempty"`
}
query, args, _ := goqu.Update("items").Set(
item{
FirstName: sql.NullString{Valid: true, String: "Test"},
MiddleName: sql.NullString{Valid: true, String: ""},
Address1: &sql.NullString{Valid: true, String: "Test"},
Address2: &sql.NullString{Valid: true, String: ""},
Address3: &sql.NullString{},
},
).ToSQL()
fmt.Println(query, args)

// Output:
// UPDATE "items" SET "address1"='Test',"address2"='',"address3"=NULL,"first_name"='Test',"middle_name"='' []
}

func ExampleUpdate_withGoquRecord() {
sql, args, _ := goqu.Update("items").Set(
goqu.Record{"name": "Test", "address": "111 Test Addr"},
Expand Down

0 comments on commit 185f7d1

Please sign in to comment.