From 185f7d1ed11956c77b54b5002d88f039283fca7b Mon Sep 17 00:00:00 2001 From: Simon Jones <18755725+simon-a-j@users.noreply.github.com> Date: Sat, 9 Oct 2021 21:32:49 +0100 Subject: [PATCH 1/6] Add omitnil and omitempty goqu struct tags --- exp/record.go | 29 ++++++++----- insert_dataset_example_test.go | 75 ++++++++++++++++++++++++++++++++++ internal/util/column_map.go | 4 ++ internal/util/reflect.go | 8 ++++ internal/util/reflect_test.go | 32 ++++++++++++--- update_dataset_example_test.go | 64 +++++++++++++++++++++++++++++ 6 files changed, 196 insertions(+), 16 deletions(-) diff --git a/exp/record.go b/exp/record.go index 80b99ae0..8553428b 100644 --- a/exp/record.go +++ b/exp/record.go @@ -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) + } } } } @@ -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() } } diff --git a/insert_dataset_example_test.go b/insert_dataset_example_test.go index fc09b911..ae801e19 100644 --- a/insert_dataset_example_test.go +++ b/insert_dataset_example_test.go @@ -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"` diff --git a/internal/util/column_map.go b/internal/util/column_map.go index b8269341..8cbebe67 100644 --- a/internal/util/column_map.go +++ b/internal/util/column_map.go @@ -15,6 +15,8 @@ type ( ShouldInsert bool ShouldUpdate bool DefaultIfEmpty bool + OmitNil bool + OmitEmpty bool GoType reflect.Type } ColumnMap map[string]ColumnData @@ -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, } diff --git a/internal/util/reflect.go b/internal/util/reflect.go index bc18b8bd..415657ff 100644 --- a/internal/util/reflect.go +++ b/internal/util/reflect.go @@ -13,6 +13,8 @@ const ( skipUpdateTagName = "skipupdate" skipInsertTagName = "skipinsert" defaultIfEmptyTagName = "defaultifempty" + omitNilTagName = "omitnil" + omitEmptyTagName = "omitempty" ) var scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem() @@ -76,6 +78,8 @@ 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: @@ -83,6 +87,10 @@ func IsEmptyValue(v reflect.Value) bool { } } +func IsNilPointer(v reflect.Value) bool { + return v.Kind() == reflect.Ptr && v.IsNil() +} + var ( structMapCache = make(map[interface{}]ColumnMap) structMapCacheLock = sync.Mutex{} diff --git a/internal/util/reflect_test.go b/internal/util/reflect_test.go index e461add7..9e474d0c 100644 --- a/internal/util/reflect_test.go +++ b/internal/util/reflect_test.go @@ -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() { @@ -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"}))) } @@ -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) @@ -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) } diff --git a/update_dataset_example_test.go b/update_dataset_example_test.go index 49852fa1..aa81b2db 100644 --- a/update_dataset_example_test.go +++ b/update_dataset_example_test.go @@ -2,6 +2,7 @@ package goqu_test import ( + "database/sql" "fmt" "github.com/doug-martin/goqu/v9" @@ -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"}, From 0f9648f915955016320bfe28e98601caf9b17aa1 Mon Sep 17 00:00:00 2001 From: Simon Jones <18755725+simon-a-j@users.noreply.github.com> Date: Sat, 9 Oct 2021 22:09:29 +0100 Subject: [PATCH 2/6] omitnil and omitempty documentation for insert and update --- docs/inserting.md | 48 ++++++++++++++++++++++++++++++++ docs/updating.md | 50 ++++++++++++++++++++++++++++++++++ insert_dataset_example_test.go | 22 +++------------ 3 files changed, 102 insertions(+), 18 deletions(-) diff --git a/docs/inserting.md b/docs/inserting.md index e6f5fa28..802306b1 100644 --- a/docs/inserting.md +++ b/docs/inserting.md @@ -175,6 +175,54 @@ Output: ``` INSERT INTO "user" ("last_name") VALUES ('Farley'), ('Stewart'), ('Jeffers') [] ``` +If you do not want to set the database field when the struct field is a nil pointer you can use the `omitnil` tag. + +```go +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', '') [] +``` + +If you do not want to set the database field when the struct field is a zero value (including nil pointers) you can use +the `omitempty` tag. + +Empty embedded structs implementing the `Valuer` interface (eg. `sql.NullString`) will also be omitted. + +```go +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') [] +``` If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag. diff --git a/docs/updating.md b/docs/updating.md index e0d7dcf1..52f2468a 100644 --- a/docs/updating.md +++ b/docs/updating.md @@ -154,6 +154,56 @@ Output: UPDATE "items" SET "address"='111 Test Addr' [] ``` +If you do not want to update the database field when the struct field is a nil pointer you can use the `omitnil` tag. +This allows a struct of pointers to be used to represent partial updates where nil pointers were not changed. + +```go +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"='' [] +``` + +If you do not want to update the database field when the struct field is a zero value (including nil pointers) you can +use the `omitempty` tag. + +Empty embedded structs implementing the `Valuer` interface (eg. `sql.NullString`) will also be omitted. + +```go +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' [] +``` + If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag. ```go diff --git a/insert_dataset_example_test.go b/insert_dataset_example_test.go index ae801e19..4690e1f8 100644 --- a/insert_dataset_example_test.go +++ b/insert_dataset_example_test.go @@ -389,11 +389,7 @@ func ExampleInsertDataset_Rows_withOmitNilTag() { emptyString := "" i := item{FirstName: "Test", Address1: &testString, Address2: &emptyString} - insertSQL, args, _ := goqu.Insert("items"). - Rows( - i, - ). - ToSQL() + insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL() fmt.Println(insertSQL, args) // Output: @@ -410,14 +406,8 @@ func ExampleInsertDataset_Rows_withOmitEmptyTag() { } testString := "Test" emptyString := "" - i := item{ - FirstName: "Test", Address1: &testString, Address2: &emptyString, - } - insertSQL, args, _ := goqu.Insert("items"). - Rows( - i, - ). - ToSQL() + i := item{FirstName: "Test", Address1: &testString, Address2: &emptyString} + insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL() fmt.Println(insertSQL, args) // Output: @@ -441,11 +431,7 @@ func ExampleInsertDataset_Rows_withOmitEmptyTag_Valuer() { Address2: &sql.NullString{Valid: true, String: ""}, Address3: &sql.NullString{}, } - insertSQL, args, _ := goqu.Insert("items"). - Rows( - i, - ). - ToSQL() + insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL() fmt.Println(insertSQL, args) // Output: From 64d40e807ea962ce3fe694c184dea97572294a44 Mon Sep 17 00:00:00 2001 From: Simon Jones <18755725+simon-a-j@users.noreply.github.com> Date: Sun, 10 Oct 2021 23:00:11 +0100 Subject: [PATCH 3/6] Clean up functions to detect empty and nil --- exp/record.go | 2 +- internal/util/reflect.go | 27 ++++++++------------------- internal/util/reflect_test.go | 30 +++++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/exp/record.go b/exp/record.go index 8553428b..62c85ea0 100644 --- a/exp/record.go +++ b/exp/record.go @@ -49,7 +49,7 @@ func shouldSkipField(f util.ColumnData, forInsert, forUpdate bool) bool { } func shouldOmitField(val reflect.Value, f util.ColumnData) bool { - if f.OmitNil && util.IsNilPointer(val) { + if f.OmitNil && util.IsNil(val) { return true } else if f.OmitEmpty && util.IsEmptyValue(val) { return true diff --git a/internal/util/reflect.go b/internal/util/reflect.go index 415657ff..e7e66d87 100644 --- a/internal/util/reflect.go +++ b/internal/util/reflect.go @@ -65,32 +65,21 @@ func IsPointer(k reflect.Kind) bool { } func IsEmptyValue(v reflect.Value) bool { + return !v.IsValid() || v.IsZero() +} + +func IsNil(v reflect.Value) bool { + if !v.IsValid() { + return true + } switch v.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - return v.Len() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: + case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func: 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{} diff --git a/internal/util/reflect_test.go b/internal/util/reflect_test.go index 9e474d0c..fc576d5f 100644 --- a/internal/util/reflect_test.go +++ b/internal/util/reflect_test.go @@ -343,6 +343,7 @@ func (rt *reflectTest) TestIsEmptyValue_emptyValues() { rt.True(util.IsEmptyValue(reflect.ValueOf(ts.intr))) rt.True(util.IsEmptyValue(reflect.ValueOf(ts.ptr))) rt.True(util.IsEmptyValue(reflect.ValueOf(ts))) + rt.True(util.IsNil(reflect.ValueOf(nil))) } func (rt *reflectTest) TestIsEmptyValue_validValues() { @@ -365,8 +366,35 @@ 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"}))) + rt.False(util.IsEmptyValue(reflect.ValueOf(ts))) +} + +func (rt *reflectTest) TestIsNil() { + ts := TestStruct{} + rt.False(util.IsNil(reflect.ValueOf(ts.arr))) + rt.True(util.IsNil(reflect.ValueOf(ts.slc))) + rt.False(util.IsEmptyValue(reflect.ValueOf([]string{"a"}))) + rt.True(util.IsNil(reflect.ValueOf(ts.mp))) + rt.False(util.IsEmptyValue(reflect.ValueOf(map[string]interface{}{"a": true}))) + rt.False(util.IsNil(reflect.ValueOf(ts.str))) + rt.False(util.IsNil(reflect.ValueOf(ts.bl))) + rt.False(util.IsNil(reflect.ValueOf(ts.i))) + rt.False(util.IsNil(reflect.ValueOf(ts.i8))) + rt.False(util.IsNil(reflect.ValueOf(ts.i16))) + rt.False(util.IsNil(reflect.ValueOf(ts.i32))) + rt.False(util.IsNil(reflect.ValueOf(ts.i64))) + rt.False(util.IsNil(reflect.ValueOf(ts.ui))) + rt.False(util.IsNil(reflect.ValueOf(ts.ui8))) + rt.False(util.IsNil(reflect.ValueOf(ts.ui16))) + rt.False(util.IsNil(reflect.ValueOf(ts.ui32))) + rt.False(util.IsNil(reflect.ValueOf(ts.ui64))) + rt.False(util.IsNil(reflect.ValueOf(ts.f32))) + rt.False(util.IsNil(reflect.ValueOf(ts.f64))) + rt.True(util.IsNil(reflect.ValueOf(ts.intr))) + rt.True(util.IsNil(reflect.ValueOf(ts.ptr))) + rt.False(util.IsNil(reflect.ValueOf(ts))) + rt.True(util.IsNil(reflect.ValueOf(nil))) } func (rt *reflectTest) TestColumnRename() { From 4da138275c2be6bca41784551966655b3a20c5f6 Mon Sep 17 00:00:00 2001 From: Simon Jones <18755725+simon-a-j@users.noreply.github.com> Date: Sun, 10 Oct 2021 23:03:35 +0100 Subject: [PATCH 4/6] Improve diff readability --- internal/util/reflect.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/util/reflect.go b/internal/util/reflect.go index e7e66d87..82683395 100644 --- a/internal/util/reflect.go +++ b/internal/util/reflect.go @@ -64,10 +64,6 @@ func IsPointer(k reflect.Kind) bool { return k == reflect.Ptr } -func IsEmptyValue(v reflect.Value) bool { - return !v.IsValid() || v.IsZero() -} - func IsNil(v reflect.Value) bool { if !v.IsValid() { return true @@ -80,6 +76,10 @@ func IsNil(v reflect.Value) bool { } } +func IsEmptyValue(v reflect.Value) bool { + return !v.IsValid() || v.IsZero() +} + var ( structMapCache = make(map[interface{}]ColumnMap) structMapCacheLock = sync.Mutex{} From 1efcf4518934295f6c45add7420538a8a21b61cf Mon Sep 17 00:00:00 2001 From: Simon Jones <18755725+simon-a-j@users.noreply.github.com> Date: Mon, 11 Oct 2021 21:33:28 +0100 Subject: [PATCH 5/6] Fix lint errors --- exp/record.go | 2 +- insert_dataset_example_test.go | 16 +++++------ update_dataset_example_test.go | 52 +++++++++++++++++----------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/exp/record.go b/exp/record.go index 62c85ea0..9214c560 100644 --- a/exp/record.go +++ b/exp/record.go @@ -32,7 +32,7 @@ func NewRecordFromStruct(i interface{}, forInsert, forUpdate bool) (r Record, er f := cm[col] if !shouldSkipField(f, forInsert, forUpdate) { if fieldValue, isAvailable := util.SafeGetFieldByIndex(value, f.FieldIndex); isAvailable { - if fieldValue.IsValid() && !shouldOmitField(fieldValue, f) { + if !shouldOmitField(fieldValue, f) { r[f.ColumnName] = getRecordValue(fieldValue, f) } } diff --git a/insert_dataset_example_test.go b/insert_dataset_example_test.go index 4690e1f8..7b37236d 100644 --- a/insert_dataset_example_test.go +++ b/insert_dataset_example_test.go @@ -385,15 +385,15 @@ func ExampleInsertDataset_Rows_withOmitNilTag() { Address2 *string `db:"address2" goqu:"omitnil"` Address3 *string `db:"address3" goqu:"omitnil"` } - testString := "Test" - emptyString := "" - i := item{FirstName: "Test", Address1: &testString, Address2: &emptyString} + address := "111 Test Addr" + var emptyString string + i := item{FirstName: "Test", Address1: &address, 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', '') [] + // INSERT INTO "items" ("address1", "address2", "first_name", "last_name") VALUES ('111 Test Addr', '', 'Test', '') [] } func ExampleInsertDataset_Rows_withOmitEmptyTag() { @@ -404,14 +404,14 @@ func ExampleInsertDataset_Rows_withOmitEmptyTag() { Address2 *string `db:"address2" goqu:"omitempty"` Address3 *string `db:"address3" goqu:"omitempty"` } - testString := "Test" - emptyString := "" - i := item{FirstName: "Test", Address1: &testString, Address2: &emptyString} + address := "112 Test Addr" + var emptyString string + i := item{FirstName: "Test", Address1: &address, 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') [] + // INSERT INTO "items" ("address1", "address2", "first_name") VALUES ('112 Test Addr', '', 'Test') [] } func ExampleInsertDataset_Rows_withOmitEmptyTag_Valuer() { diff --git a/update_dataset_example_test.go b/update_dataset_example_test.go index aa81b2db..8664aee6 100644 --- a/update_dataset_example_test.go +++ b/update_dataset_example_test.go @@ -2,7 +2,7 @@ package goqu_test import ( - "database/sql" + dbsql "database/sql" "fmt" "github.com/doug-martin/goqu/v9" @@ -31,15 +31,15 @@ func ExampleUpdate_withOmitNilTag() { 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}, + address := "113 Test Addr" + var emptyString string + sql, args, _ := goqu.Update("items").Set( + item{FirstName: "Test", Address1: &address, Address2: &emptyString}, ).ToSQL() - fmt.Println(query, args) + fmt.Println(sql, args) // Output: - // UPDATE "items" SET "address1"='Test',"address2"='',"first_name"='Test',"last_name"='' [] + // UPDATE "items" SET "address1"='113 Test Addr',"address2"='',"first_name"='Test',"last_name"='' [] } func ExampleUpdate_withOmitEmptyTag() { @@ -50,34 +50,34 @@ func ExampleUpdate_withOmitEmptyTag() { 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}, + address := "114 Test Addr" + var emptyString string + sql, args, _ := goqu.Update("items").Set( + item{FirstName: "Test", Address1: &address, Address2: &emptyString}, ).ToSQL() - fmt.Println(query, args) + fmt.Println(sql, args) // Output: - // UPDATE "items" SET "address1"='Test',"address2"='',"first_name"='Test' [] + // UPDATE "items" SET "address1"='114 Test Addr',"address2"='',"first_name"='Test' [] } -func ExampleUpdate_withOmitEmptyTag_Valuer() { +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"` + FirstName dbsql.NullString `db:"first_name" goqu:"omitempty"` + MiddleName dbsql.NullString `db:"middle_name" goqu:"omitempty"` + LastName dbsql.NullString `db:"last_name" goqu:"omitempty"` + Address1 *dbsql.NullString `db:"address1" goqu:"omitempty"` + Address2 *dbsql.NullString `db:"address2" goqu:"omitempty"` + Address3 *dbsql.NullString `db:"address3" goqu:"omitempty"` + Address4 *dbsql.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{}, + FirstName: dbsql.NullString{Valid: true, String: "Test"}, + MiddleName: dbsql.NullString{Valid: true, String: ""}, + Address1: &dbsql.NullString{Valid: true, String: "Test"}, + Address2: &dbsql.NullString{Valid: true, String: ""}, + Address3: &dbsql.NullString{}, }, ).ToSQL() fmt.Println(query, args) From 293c6dafcae3de7013daf7e16a73c40bb98cf256 Mon Sep 17 00:00:00 2001 From: Simon Jones <18755725+simon-a-j@users.noreply.github.com> Date: Sun, 17 Oct 2021 19:36:46 +0100 Subject: [PATCH 6/6] Clarify examples in documentation --- docs/inserting.md | 29 ++++++++++++++++++--------- docs/updating.md | 36 ++++++++++++++++++++++------------ insert_dataset_example_test.go | 32 +++++++++++++++++++++--------- update_dataset_example_test.go | 32 +++++++++++++++++++++--------- 4 files changed, 90 insertions(+), 39 deletions(-) diff --git a/docs/inserting.md b/docs/inserting.md index 802306b1..5e07a363 100644 --- a/docs/inserting.md +++ b/docs/inserting.md @@ -185,9 +185,15 @@ type item struct { Address2 *string `db:"address2" goqu:"omitnil"` Address3 *string `db:"address3" goqu:"omitnil"` } -testString := "Test" -emptyString := "" -i := item{FirstName: "Test", Address1: &testString, Address2: &emptyString} +address1 := "111 Test Addr" +var emptyString string +i := item{ + FirstName: "Test First Name", + LastName: "", + Address1: &address1, + Address2: &emptyString, + Address3: nil, // will omit nil pointer +} insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL() fmt.Println(insertSQL, args) @@ -195,7 +201,7 @@ fmt.Println(insertSQL, args) Output: ``` -INSERT INTO "items" ("address1", "address2", "first_name", "last_name") VALUES ('Test', '', 'Test', '') [] +INSERT INTO "items" ("address1", "address2", "first_name", "last_name") VALUES ('111 Test Addr', '', 'Test First Name', '') [] ``` If you do not want to set the database field when the struct field is a zero value (including nil pointers) you can use @@ -211,17 +217,22 @@ type item struct { Address2 *string `db:"address2" goqu:"omitempty"` Address3 *string `db:"address3" goqu:"omitempty"` } -testString := "Test" -emptyString := "" -i := item{FirstName: "Test", Address1: &testString, Address2: &emptyString} - +address1 := "112 Test Addr" +var emptyString string +i := item{ + FirstName: "Test First Name", + LastName: "", // will omit zero field + Address1: &address1, + Address2: &emptyString, + Address3: nil, // will omit nil pointer +} insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL() fmt.Println(insertSQL, args) ``` Output: ``` -INSERT INTO "items" ("address1", "address2", "first_name") VALUES ('Test', '', 'Test') [] +INSERT INTO "items" ("address1", "address2", "first_name") VALUES ('112 Test Addr', '', 'Test First Name') [] ``` If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag. diff --git a/docs/updating.md b/docs/updating.md index 52f2468a..d22cbf7d 100644 --- a/docs/updating.md +++ b/docs/updating.md @@ -165,17 +165,23 @@ type item struct { 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}, +address1 := "113 Test Addr" +var emptyString string +sql, args, _ := goqu.Update("items").Set( + item{ + FirstName: "Test First Name", + LastName: "", + Address1: &address1, + Address2: &emptyString, + Address3: nil, // will omit nil pointer + }, ).ToSQL() -fmt.Println(query, args) +fmt.Println(sql, args) ``` Output: ``` -UPDATE "items" SET "address1"='Test',"address2"='',"first_name"='Test',"last_name"='' [] +UPDATE "items" SET "address1"='113 Test Addr',"address2"='',"first_name"='Test First Name',"last_name"='' [] ``` If you do not want to update the database field when the struct field is a zero value (including nil pointers) you can @@ -191,17 +197,23 @@ type item struct { 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}, +address1 := "114 Test Addr" +var emptyString string +sql, args, _ := goqu.Update("items").Set( + item{ + FirstName: "Test First Name", + LastName: "", // will omit zero field + Address1: &address1, + Address2: &emptyString, + Address3: nil, // will omit nil pointer + }, ).ToSQL() -fmt.Println(query, args) +fmt.Println(sql, args) ``` Output: ``` -UPDATE "items" SET "address1"='Test',"address2"='',"first_name"='Test' [] +UPDATE "items" SET "address1"='114 Test Addr',"address2"='',"first_name"='Test First Name' [] ``` If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag. diff --git a/insert_dataset_example_test.go b/insert_dataset_example_test.go index 7b37236d..755b3d69 100644 --- a/insert_dataset_example_test.go +++ b/insert_dataset_example_test.go @@ -385,15 +385,21 @@ func ExampleInsertDataset_Rows_withOmitNilTag() { Address2 *string `db:"address2" goqu:"omitnil"` Address3 *string `db:"address3" goqu:"omitnil"` } - address := "111 Test Addr" + address1 := "111 Test Addr" var emptyString string - i := item{FirstName: "Test", Address1: &address, Address2: &emptyString} + i := item{ + FirstName: "Test First Name", + LastName: "", + Address1: &address1, + Address2: &emptyString, + Address3: nil, // will omit nil pointer + } insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL() fmt.Println(insertSQL, args) // Output: - // INSERT INTO "items" ("address1", "address2", "first_name", "last_name") VALUES ('111 Test Addr', '', 'Test', '') [] + // INSERT INTO "items" ("address1", "address2", "first_name", "last_name") VALUES ('111 Test Addr', '', 'Test First Name', '') [] } func ExampleInsertDataset_Rows_withOmitEmptyTag() { @@ -404,14 +410,20 @@ func ExampleInsertDataset_Rows_withOmitEmptyTag() { Address2 *string `db:"address2" goqu:"omitempty"` Address3 *string `db:"address3" goqu:"omitempty"` } - address := "112 Test Addr" + address1 := "112 Test Addr" var emptyString string - i := item{FirstName: "Test", Address1: &address, Address2: &emptyString} + i := item{ + FirstName: "Test First Name", + LastName: "", // will omit zero field + Address1: &address1, + Address2: &emptyString, + Address3: nil, // will omit nil pointer + } insertSQL, args, _ := goqu.Insert("items").Rows(i).ToSQL() fmt.Println(insertSQL, args) // Output: - // INSERT INTO "items" ("address1", "address2", "first_name") VALUES ('112 Test Addr', '', 'Test') [] + // INSERT INTO "items" ("address1", "address2", "first_name") VALUES ('112 Test Addr', '', 'Test First Name') [] } func ExampleInsertDataset_Rows_withOmitEmptyTag_Valuer() { @@ -425,17 +437,19 @@ func ExampleInsertDataset_Rows_withOmitEmptyTag_Valuer() { Address4 *sql.NullString `db:"address4" goqu:"omitempty"` } i := item{ - FirstName: sql.NullString{Valid: true, String: "Test"}, + FirstName: sql.NullString{Valid: true, String: "Test First Name"}, MiddleName: sql.NullString{Valid: true, String: ""}, - Address1: &sql.NullString{Valid: true, String: "Test"}, + LastName: sql.NullString{}, // will omit zero valuer struct + Address1: &sql.NullString{Valid: true, String: "Test Address 1"}, Address2: &sql.NullString{Valid: true, String: ""}, Address3: &sql.NullString{}, + Address4: nil, // will omit nil pointer } 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', '') [] + // INSERT INTO "items" ("address1", "address2", "address3", "first_name", "middle_name") VALUES ('Test Address 1', '', NULL, 'Test First Name', '') [] } func ExampleInsertDataset_Rows_withGoquDefaultIfEmptyTag() { diff --git a/update_dataset_example_test.go b/update_dataset_example_test.go index 8664aee6..096dfa38 100644 --- a/update_dataset_example_test.go +++ b/update_dataset_example_test.go @@ -31,15 +31,21 @@ func ExampleUpdate_withOmitNilTag() { Address2 *string `db:"address2" goqu:"omitnil"` Address3 *string `db:"address3" goqu:"omitnil"` } - address := "113 Test Addr" + address1 := "113 Test Addr" var emptyString string sql, args, _ := goqu.Update("items").Set( - item{FirstName: "Test", Address1: &address, Address2: &emptyString}, + item{ + FirstName: "Test First Name", + LastName: "", + Address1: &address1, + Address2: &emptyString, + Address3: nil, // will omit nil pointer + }, ).ToSQL() fmt.Println(sql, args) // Output: - // UPDATE "items" SET "address1"='113 Test Addr',"address2"='',"first_name"='Test',"last_name"='' [] + // UPDATE "items" SET "address1"='113 Test Addr',"address2"='',"first_name"='Test First Name',"last_name"='' [] } func ExampleUpdate_withOmitEmptyTag() { @@ -50,15 +56,21 @@ func ExampleUpdate_withOmitEmptyTag() { Address2 *string `db:"address2" goqu:"omitempty"` Address3 *string `db:"address3" goqu:"omitempty"` } - address := "114 Test Addr" + address1 := "114 Test Addr" var emptyString string sql, args, _ := goqu.Update("items").Set( - item{FirstName: "Test", Address1: &address, Address2: &emptyString}, + item{ + FirstName: "Test First Name", + LastName: "", // will omit zero field + Address1: &address1, + Address2: &emptyString, + Address3: nil, // will omit nil pointer + }, ).ToSQL() fmt.Println(sql, args) // Output: - // UPDATE "items" SET "address1"='114 Test Addr',"address2"='',"first_name"='Test' [] + // UPDATE "items" SET "address1"='114 Test Addr',"address2"='',"first_name"='Test First Name' [] } func ExampleUpdate_withOmitEmptyTag_valuer() { @@ -73,17 +85,19 @@ func ExampleUpdate_withOmitEmptyTag_valuer() { } query, args, _ := goqu.Update("items").Set( item{ - FirstName: dbsql.NullString{Valid: true, String: "Test"}, + FirstName: dbsql.NullString{Valid: true, String: "Test First Name"}, MiddleName: dbsql.NullString{Valid: true, String: ""}, - Address1: &dbsql.NullString{Valid: true, String: "Test"}, + LastName: dbsql.NullString{}, // will omit zero valuer struct + Address1: &dbsql.NullString{Valid: true, String: "Test Address 1"}, Address2: &dbsql.NullString{Valid: true, String: ""}, Address3: &dbsql.NullString{}, + Address4: nil, // will omit nil pointer }, ).ToSQL() fmt.Println(query, args) // Output: - // UPDATE "items" SET "address1"='Test',"address2"='',"address3"=NULL,"first_name"='Test',"middle_name"='' [] + // UPDATE "items" SET "address1"='Test Address 1',"address2"='',"address3"=NULL,"first_name"='Test First Name',"middle_name"='' [] } func ExampleUpdate_withGoquRecord() {