Skip to content

Commit

Permalink
Merge branch 'doug-martin:master' into add-singlestore-mysql-dialect-…
Browse files Browse the repository at this point in the history
…variation
  • Loading branch information
sankemax authored Nov 15, 2023
2 parents e887b6a + 34d377d commit 920472f
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 30 deletions.
59 changes: 59 additions & 0 deletions docs/inserting.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,65 @@ 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"`
}
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)
```

Output:
```
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
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"`
}
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 ('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.

Expand Down
62 changes: 62 additions & 0 deletions docs/updating.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,68 @@ 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"`
}
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(sql, args)
```

Output:
```
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
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"`
}
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(sql, args)
```

Output:
```
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.

```go
Expand Down
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 !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.IsNil(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"`
}
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)

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

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"`
}
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 ('112 Test Addr', '', 'Test First Name') []
}

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 First Name"},
MiddleName: sql.NullString{Valid: true, String: ""},
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 Address 1', '', NULL, 'Test First Name', '') []
}

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
25 changes: 11 additions & 14 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 @@ -62,27 +64,22 @@ func IsPointer(k reflect.Kind) bool {
return k == reflect.Ptr
}

func IsEmptyValue(v reflect.Value) bool {
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.Invalid:
return true
default:
return false
}
}

func IsEmptyValue(v reflect.Value) bool {
return !v.IsValid() || v.IsZero()
}

var (
structMapCache = make(map[interface{}]ColumnMap)
structMapCacheLock = sync.Mutex{}
Expand Down
Loading

0 comments on commit 920472f

Please sign in to comment.