diff --git a/HISTORY.md b/HISTORY.md
index 56ca9c8f..1ba2a7c5 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,3 +1,7 @@
+## v8.1.0
+
+* [ADDED] Support column DEFAULT when inserting/updating via struct [#27](https://github.com/doug-martin/goqu/issues/27)
+
## v8.0.1
* [ADDED] Multi table update support for `mysql` and `postgres` [#60](https://github.com/doug-martin/goqu/issues/60)
diff --git a/docs/inserting.md b/docs/inserting.md
index 0aabd3b1..c81cebe7 100644
--- a/docs/inserting.md
+++ b/docs/inserting.md
@@ -154,6 +154,48 @@ Output:
INSERT INTO "user" ("first_name", "last_name") VALUES ('Greg', 'Farley'), ('Jimmy', 'Stewart'), ('Jeff', 'Jeffers') []
```
+You can skip fields in a struct by using the `skipinsert` tag
+
+```go
+type User struct {
+ FirstName string `db:"first_name" goqu:"skipinsert"`
+ LastName string `db:"last_name"`
+}
+ds := goqu.Insert("user").Rows(
+ User{FirstName: "Greg", LastName: "Farley"},
+ User{FirstName: "Jimmy", LastName: "Stewart"},
+ User{FirstName: "Jeff", LastName: "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("last_name") VALUES ('Farley'), ('Stewart'), ('Jeffers') []
+```
+
+If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag.
+
+```go
+type User struct {
+ FirstName string `db:"first_name" goqu:"defaultifempty"`
+ LastName string `db:"last_name"`
+}
+ds := goqu.Insert("user").Rows(
+ User{LastName: "Farley"},
+ User{FirstName: "Jimmy", LastName: "Stewart"},
+ User{LastName: "Jeffers"},
+)
+insertSQL, args, _ := ds.ToSQL()
+fmt.Println(insertSQL, args)
+```
+
+Output:
+```
+INSERT INTO "user" ("first_name", "last_name") VALUES (DEFAULT, 'Farley'), ('Jimmy', 'Stewart'), (DEFAULT, 'Jeffers') []
+```
+
**Insert `map[string]interface{}`**
diff --git a/docs/updating.md b/docs/updating.md
index da7bcfbe..1c5fba54 100644
--- a/docs/updating.md
+++ b/docs/updating.md
@@ -153,6 +153,24 @@ Output:
UPDATE "items" SET "address"='111 Test Addr' []
```
+If you want to use the database `DEFAULT` when the struct field is a zero value you can use the `defaultifempty` tag.
+
+```go
+type item struct {
+ Address string `db:"address"`
+ Name string `db:"name" goqu:"defaultifempty"`
+}
+sql, args, _ := goqu.Update("items").Set(
+ item{Address: "111 Test Addr"},
+).ToSQL()
+fmt.Println(sql, args)
+```
+
+Output:
+```
+UPDATE "items" SET "address"='111 Test Addr',"name"=DEFAULT []
+```
+
**[Set with Map](https://godoc.org/github.com/doug-martin/goqu/#UpdateDataset.Set)**
diff --git a/exp/insert.go b/exp/insert.go
index b43a5746..6799bbf8 100644
--- a/exp/insert.go
+++ b/exp/insert.go
@@ -150,7 +150,11 @@ func getFieldsValues(value reflect.Value) (rowCols, rowVals []interface{}, err e
if f.ShouldInsert {
v := value.FieldByIndex(f.FieldIndex)
rowCols = append(rowCols, col)
- rowVals = append(rowVals, v.Interface())
+ if f.DefaultIfEmpty && util.IsEmptyValue(v) {
+ rowVals = append(rowVals, Default())
+ } else {
+ rowVals = append(rowVals, v.Interface())
+ }
}
}
}
diff --git a/exp/literal.go b/exp/literal.go
index ea58178e..68bb9063 100644
--- a/exp/literal.go
+++ b/exp/literal.go
@@ -22,6 +22,11 @@ func Star() LiteralExpression {
return NewLiteralExpression("*")
}
+// Returns a literal for the 'DEFAULT'
+func Default() LiteralExpression {
+ return NewLiteralExpression("DEFAULT")
+}
+
func (l literal) Clone() Expression {
return NewLiteralExpression(l.literal, l.args...)
}
diff --git a/exp/update.go b/exp/update.go
index 626904ba..25b3734e 100644
--- a/exp/update.go
+++ b/exp/update.go
@@ -50,7 +50,11 @@ func getUpdateExpressionsStruct(value reflect.Value) (updates []UpdateExpression
f := cm[col]
if f.ShouldUpdate {
v := value.FieldByIndex(f.FieldIndex)
- updates = append(updates, ParseIdentifier(col).Set(v.Interface()))
+ setV := v.Interface()
+ if f.DefaultIfEmpty && util.IsEmptyValue(v) {
+ setV = Default()
+ }
+ updates = append(updates, ParseIdentifier(col).Set(setV))
}
}
return updates, nil
diff --git a/expressions.go b/expressions.go
index 097efc14..62dc1ac7 100644
--- a/expressions.go
+++ b/expressions.go
@@ -209,5 +209,5 @@ func Star() exp.LiteralExpression { return exp.Star() }
// Returns a literal for DEFAULT sql keyword
func Default() exp.LiteralExpression {
- return L("DEFAULT")
+ return exp.Default()
}
diff --git a/insert_dataset_example_test.go b/insert_dataset_example_test.go
index e41fc33f..93237351 100644
--- a/insert_dataset_example_test.go
+++ b/insert_dataset_example_test.go
@@ -367,25 +367,43 @@ func ExampleInsertDataset_Rows_withGoquSkipInsertTag() {
fmt.Println(insertSQL, args)
insertSQL, args, _ = goqu.Insert("items").
+ Rows([]item{
+ {Name: "Test1", Address: "111 Test Addr"},
+ {Name: "Test2", Address: "112 Test Addr"},
+ }).
+ ToSQL()
+ fmt.Println(insertSQL, args)
+
+ // Output:
+ // INSERT INTO "items" ("address") VALUES ('111 Test Addr'), ('112 Test Addr') []
+ // INSERT INTO "items" ("address") VALUES ('111 Test Addr'), ('112 Test Addr') []
+}
+
+func ExampleInsertDataset_Rows_withGoquDefaultIfEmptyTag() {
+ type item struct {
+ ID uint32 `goqu:"skipinsert"`
+ Address string
+ Name string `goqu:"defaultifempty"`
+ }
+ insertSQL, args, _ := goqu.Insert("items").
Rows(
item{Name: "Test1", Address: "111 Test Addr"},
- item{Name: "Test2", Address: "112 Test Addr"},
+ item{Address: "112 Test Addr"},
).
ToSQL()
fmt.Println(insertSQL, args)
insertSQL, args, _ = goqu.Insert("items").
Rows([]item{
- {Name: "Test1", Address: "111 Test Addr"},
+ {Address: "111 Test Addr"},
{Name: "Test2", Address: "112 Test Addr"},
}).
ToSQL()
fmt.Println(insertSQL, args)
// Output:
- // INSERT INTO "items" ("address") VALUES ('111 Test Addr'), ('112 Test Addr') []
- // INSERT INTO "items" ("address") VALUES ('111 Test Addr'), ('112 Test Addr') []
- // INSERT INTO "items" ("address") VALUES ('111 Test Addr'), ('112 Test Addr') []
+ // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test1'), ('112 Test Addr', DEFAULT) []
+ // INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', DEFAULT), ('112 Test Addr', 'Test2') []
}
func ExampleInsertDataset_ClearOnConflict() {
diff --git a/insert_dataset_test.go b/insert_dataset_test.go
index 8ec5dc0d..6dd9e9ea 100644
--- a/insert_dataset_test.go
+++ b/insert_dataset_test.go
@@ -600,6 +600,40 @@ func (ids *insertDatasetSuite) TestRows_ToSQLWithGoquSkipInsertTagSQL() {
assert.Equal(t, `INSERT INTO "items" ("address", "name") VALUES (?, ?), (?, ?), (?, ?), (?, ?)`, insertSQL)
}
+func (ids *insertDatasetSuite) TestRows_ToSQLWithGoquDefaultIfEmptyTag() {
+ type item struct {
+ ID uint32 `db:"id" goqu:"skipinsert"`
+ Address string `db:"address" goqu:"defaultifempty"`
+ Name string `db:"name" goqu:"defaultifempty"`
+ Bool bool `db:"bool" goqu:"skipinsert,defaultifempty"`
+ }
+ ds := Insert("items")
+
+ ds1 := ds.Rows(item{Name: "Test", Address: "111 Test Addr"})
+
+ insertSQL, args, err := ds1.ToSQL()
+ ids.NoError(err)
+ ids.Empty(args)
+ ids.Equal(insertSQL, `INSERT INTO "items" ("address", "name") VALUES ('111 Test Addr', 'Test')`)
+
+ insertSQL, args, err = ds1.Prepared(true).ToSQL()
+ ids.NoError(err)
+ ids.Equal([]interface{}{"111 Test Addr", "Test"}, args)
+ ids.Equal(insertSQL, `INSERT INTO "items" ("address", "name") VALUES (?, ?)`)
+
+ ds1 = ds.Rows(item{})
+
+ insertSQL, args, err = ds1.ToSQL()
+ ids.NoError(err)
+ ids.Empty(args)
+ ids.Equal(insertSQL, `INSERT INTO "items" ("address", "name") VALUES (DEFAULT, DEFAULT)`)
+
+ insertSQL, args, err = ds1.Prepared(true).ToSQL()
+ ids.NoError(err)
+ ids.Empty(args)
+ ids.Equal(insertSQL, `INSERT INTO "items" ("address", "name") VALUES (DEFAULT, DEFAULT)`)
+}
+
func (ids *insertDatasetSuite) TestRows_ToSQLWithDefaultValues() {
t := ids.T()
ds := Insert("items")
diff --git a/internal/util/reflect.go b/internal/util/reflect.go
index d1c5b6b9..a2963d6a 100644
--- a/internal/util/reflect.go
+++ b/internal/util/reflect.go
@@ -12,18 +12,20 @@ import (
type (
ColumnData struct {
- ColumnName string
- FieldIndex []int
- ShouldInsert bool
- ShouldUpdate bool
- GoType reflect.Type
+ ColumnName string
+ FieldIndex []int
+ ShouldInsert bool
+ ShouldUpdate bool
+ DefaultIfEmpty bool
+ GoType reflect.Type
}
ColumnMap map[string]ColumnData
)
const (
- skipUpdateTagName = "skipupdate"
- skipInsertTagName = "skipinsert"
+ skipUpdateTagName = "skipupdate"
+ skipInsertTagName = "skipinsert"
+ defaultIfEmptyTagName = "defaultifempty"
)
func IsUint(k reflect.Kind) bool {
@@ -71,6 +73,26 @@ func IsPointer(k reflect.Kind) bool {
return k == reflect.Ptr
}
+func IsEmptyValue(v reflect.Value) bool {
+ 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:
+ return v.IsNil()
+ case reflect.Invalid:
+ return true
+ }
+ return false
+}
+
var structMapCache = make(map[interface{}]ColumnMap)
var structMapCacheLock = sync.Mutex{}
@@ -189,11 +211,12 @@ func createColumnMap(t reflect.Type, fieldIndex []int) ColumnMap {
goquTag := tag.New("goqu", f.Tag)
if !dbTag.Equals("-") {
cm[columnName] = ColumnData{
- ColumnName: columnName,
- ShouldInsert: !goquTag.Contains(skipInsertTagName),
- ShouldUpdate: !goquTag.Contains(skipUpdateTagName),
- FieldIndex: append(fieldIndex, f.Index...),
- GoType: f.Type,
+ ColumnName: columnName,
+ ShouldInsert: !goquTag.Contains(skipInsertTagName),
+ ShouldUpdate: !goquTag.Contains(skipUpdateTagName),
+ DefaultIfEmpty: goquTag.Contains(defaultIfEmptyTagName),
+ FieldIndex: append(fieldIndex, f.Index...),
+ GoType: f.Type,
}
}
}
diff --git a/internal/util/reflect_test.go b/internal/util/reflect_test.go
index 7470c948..b1b722d7 100644
--- a/internal/util/reflect_test.go
+++ b/internal/util/reflect_test.go
@@ -49,6 +49,40 @@ var (
}
)
+type (
+ TestInterface interface {
+ A() string
+ }
+ TestInterfaceImpl struct {
+ str string
+ }
+ TestStruct struct {
+ arr [0]string
+ slc []string
+ mp map[string]interface{}
+ str string
+ bl bool
+ i int
+ i8 int8
+ i16 int16
+ i32 int32
+ i64 int64
+ ui uint
+ ui8 uint8
+ ui16 uint16
+ ui32 uint32
+ ui64 uint64
+ f32 float32
+ f64 float64
+ intr TestInterface
+ ptr *sql.NullString
+ }
+)
+
+func (t TestInterfaceImpl) A() string {
+ return t.str
+}
+
type reflectTest struct {
suite.Suite
}
@@ -296,6 +330,52 @@ func (rt *reflectTest) TestIsPointer() {
}
}
+func (rt *reflectTest) TestIsEmptyValue_emptyValues() {
+ ts := TestStruct{}
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.arr)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.slc)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.mp)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.str)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.bl)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.i)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.i8)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.i16)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.i32)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.i64)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.ui)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.ui8)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.ui16)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.ui32)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.ui64)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.f32)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.f64)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.intr)))
+ rt.True(IsEmptyValue(reflect.ValueOf(ts.ptr)))
+}
+
+func (rt *reflectTest) TestIsEmptyValue_validValues() {
+ ts := TestStruct{intr: TestInterfaceImpl{"hello"}}
+ rt.False(IsEmptyValue(reflect.ValueOf([1]string{"a"})))
+ rt.False(IsEmptyValue(reflect.ValueOf([]string{"a"})))
+ rt.False(IsEmptyValue(reflect.ValueOf(map[string]interface{}{"a": true})))
+ rt.False(IsEmptyValue(reflect.ValueOf("str")))
+ rt.False(IsEmptyValue(reflect.ValueOf(true)))
+ rt.False(IsEmptyValue(reflect.ValueOf(int(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(int8(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(int16(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(int32(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(int64(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(uint(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(uint8(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(uint16(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(uint32(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(uint64(1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(float32(0.1))))
+ rt.False(IsEmptyValue(reflect.ValueOf(float64(0.2))))
+ rt.False(IsEmptyValue(reflect.ValueOf(ts.intr)))
+ rt.False(IsEmptyValue(reflect.ValueOf(&TestStruct{str: "a"})))
+}
+
func (rt *reflectTest) TestColumnRename() {
t := rt.T()
// different key names are used each time to circumvent the caching that happens
@@ -736,16 +816,25 @@ func (rt *reflectTest) TestGetColumnMap_withStructGoquTags() {
Str string `goqu:"skipinsert,skipupdate"`
Int int64 `goqu:"skipinsert"`
Bool bool `goqu:"skipupdate"`
+ Empty bool `goqu:"defaultifempty"`
Valuer *sql.NullString
}
var ts TestStruct
cm, err := GetColumnMap(&ts)
assert.NoError(t, err)
assert.Equal(t, ColumnMap{
- "str": {ColumnName: "str", FieldIndex: []int{0}, ShouldInsert: false, ShouldUpdate: false, GoType: reflect.TypeOf("")},
- "int": {ColumnName: "int", FieldIndex: []int{1}, ShouldInsert: false, ShouldUpdate: true, GoType: reflect.TypeOf(int64(1))},
- "bool": {ColumnName: "bool", FieldIndex: []int{2}, ShouldInsert: true, ShouldUpdate: false, GoType: reflect.TypeOf(true)},
- "valuer": {ColumnName: "valuer", FieldIndex: []int{3}, ShouldInsert: true, ShouldUpdate: true, GoType: reflect.TypeOf(&sql.NullString{})},
+ "str": {ColumnName: "str", FieldIndex: []int{0}, ShouldInsert: false, ShouldUpdate: false, GoType: reflect.TypeOf("")},
+ "int": {ColumnName: "int", FieldIndex: []int{1}, ShouldInsert: false, ShouldUpdate: true, GoType: reflect.TypeOf(int64(1))},
+ "bool": {ColumnName: "bool", FieldIndex: []int{2}, ShouldInsert: true, ShouldUpdate: false, GoType: reflect.TypeOf(true)},
+ "empty": {
+ ColumnName: "empty",
+ FieldIndex: []int{3},
+ ShouldInsert: true,
+ ShouldUpdate: true,
+ DefaultIfEmpty: true,
+ GoType: reflect.TypeOf(true),
+ },
+ "valuer": {ColumnName: "valuer", FieldIndex: []int{4}, 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 e875283a..d6c14914 100644
--- a/update_dataset_example_test.go
+++ b/update_dataset_example_test.go
@@ -500,6 +500,26 @@ func ExampleUpdateDataset_Set_withSkipUpdateTag() {
// UPDATE "items" SET "address"='111 Test Addr' []
}
+func ExampleUpdateDataset_Set_withDefaultIfEmptyTag() {
+ type item struct {
+ Address string `db:"address"`
+ Name string `db:"name" goqu:"defaultifempty"`
+ }
+ sql, args, _ := goqu.Update("items").Set(
+ item{Address: "111 Test Addr"},
+ ).ToSQL()
+ fmt.Println(sql, args)
+
+ sql, args, _ = goqu.Update("items").Set(
+ item{Name: "Bob Yukon", Address: "111 Test Addr"},
+ ).ToSQL()
+ fmt.Println(sql, args)
+
+ // Output:
+ // UPDATE "items" SET "address"='111 Test Addr',"name"=DEFAULT []
+ // UPDATE "items" SET "address"='111 Test Addr',"name"='Bob Yukon' []
+}
+
func ExampleUpdateDataset_Set_withNoTags() {
type item struct {
Address string
diff --git a/update_dataset_test.go b/update_dataset_test.go
index 82ac08c9..6cd46070 100644
--- a/update_dataset_test.go
+++ b/update_dataset_test.go
@@ -361,6 +361,38 @@ func (uds *updateDatasetSuite) TestSet_ToSQLWithSkipupdateTag() {
assert.Equal(t, `UPDATE "items" SET "name"=?`, updateSQL)
}
+func (uds *updateDatasetSuite) TestSet_ToSQLWithDefaultIfEmptyTag() {
+ type item struct {
+ Address string `db:"address" goqu:"skipupdate, defaultifempty"`
+ Name string `db:"name" goqu:"defaultifempty"`
+ Alias *string `db:"alias" goqu:"defaultifempty"`
+ }
+ ds := Update("items").Set(item{Name: "Test", Address: "111 Test Addr"})
+
+ updateSQL, args, err := ds.ToSQL()
+ uds.NoError(err)
+ uds.Empty(args)
+ uds.Equal(`UPDATE "items" SET "alias"=DEFAULT,"name"='Test'`, updateSQL)
+
+ updateSQL, args, err = ds.Prepared(true).ToSQL()
+ uds.NoError(err)
+ uds.Equal([]interface{}{"Test"}, args)
+ uds.Equal(`UPDATE "items" SET "alias"=DEFAULT,"name"=?`, updateSQL)
+
+ var alias = ""
+ ds = ds.Set(item{Alias: &alias})
+
+ updateSQL, args, err = ds.ToSQL()
+ uds.NoError(err)
+ uds.Empty(args)
+ uds.Equal(`UPDATE "items" SET "alias"='',"name"=DEFAULT`, updateSQL)
+
+ updateSQL, args, err = ds.Prepared(true).ToSQL()
+ uds.NoError(err)
+ uds.Equal([]interface{}{""}, args)
+ uds.Equal(`UPDATE "items" SET "alias"=?,"name"=DEFAULT`, updateSQL)
+}
+
func (uds *updateDatasetSuite) TestFrom() {
ds := Update("test")
dsc := ds.GetClauses()