Skip to content

Commit

Permalink
Add default values support ( https://github.com/jinzhu/gorm/issues/251 )
Browse files Browse the repository at this point in the history
  • Loading branch information
galeone committed Nov 15, 2014
1 parent a405f08 commit 064d913
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 9 deletions.
45 changes: 43 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,8 @@ If you have an existing database schema, and the primary key field is different
```go
type Animal struct {
AnimalId int64 `gorm:"primary_key:yes"`
Birthday time.Time
Birthday time.Time `sql:"DEFAULT:NOW()"`
Name string `sql:"default:'galeone'"`
Age int64
}
```
Expand All @@ -989,6 +990,46 @@ type Animal struct {
}
```

## Default values

If you have defined a default value in the `sql` tag (see the struct Animal above) the generated queries will not contains the value for these fields if is not set.

Eg.

```go
db.Save(&Animal{Age: 99})
```

The generated query will be:

```sql
INSERT INTO animals("birthday","age","name") values(NOW(), '99', 'galeone')
```

The same thing occurs in update statements.

You should fetch the value again to get the default updated values in the struct.

We can't do the same thing of the primary key (that is always filled with the right value) because default SQL values can be expressions and thus be different from the fields' type (eg. a time.Time fiels has a default value of "NOW()")

So the right way to do an update/insert statement and be sure to get the default values in the struct is

```go
//Insert
var animal Animal
animal.Age = 99
db.Save(&animal)
db.First(&animal, animal.AnimalId)
// Now wo have the animal struct with:
// Birthday: the insert time
// Name: the string galeone
// Age: the setted one -> 99

// For the update statements is the same
// First save the struct
// Than fetch it back again
```

## More examples with query chain

```go
Expand Down Expand Up @@ -1032,7 +1073,7 @@ db.Where("email = ?", "x@example.org").Attrs(User{RegisteredIp: "111.111.111.111
* db.RegisterFuncation("Search", func() {})
db.Model(&[]User{}).Limit(10).Do("Search", "search func's argument")
db.Mode(&User{}).Do("EditForm").Get("edit_form_html")
DefaultValue, DefaultTimeZone, R/W Splitting, Validation
DefaultTimeZone, R/W Splitting, Validation
* Github Pages
* Includes
* AlertColumn, DropColumn
Expand Down
5 changes: 4 additions & 1 deletion callback_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gorm

import (
"fmt"
"reflect"
"strings"
)

Expand All @@ -24,9 +25,11 @@ func Create(scope *Scope) {
if !scope.HasError() {
// set create sql
var sqls, columns []string

for _, field := range scope.Fields() {
if field.IsNormal && (!field.IsPrimaryKey || !scope.PrimaryKeyZero()) {
if field.DefaultValue != nil && reflect.DeepEqual(field.Field.Interface(), reflect.Zero(field.Field.Type()).Interface()) {
continue
}
columns = append(columns, scope.Quote(field.DBName))
sqls = append(sqls, scope.AddToVars(field.Field.Interface()))
}
Expand Down
4 changes: 4 additions & 0 deletions callback_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gorm

import (
"fmt"
"reflect"
"strings"
)

Expand Down Expand Up @@ -49,6 +50,9 @@ func Update(scope *Scope) {
} else {
for _, field := range scope.Fields() {
if !field.IsPrimaryKey && field.IsNormal && !field.IsIgnored {
if field.DefaultValue != nil && reflect.DeepEqual(field.Field.Interface(), reflect.Zero(field.Field.Type()).Interface()) {
continue
}
sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(field.DBName), scope.AddToVars(field.Field.Interface())))
}
}
Expand Down
22 changes: 21 additions & 1 deletion create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestCreate(t *testing.T) {
}
}

func TestCreateWithNoStdPrimaryKey(t *testing.T) {
func TestCreateWithNoStdPrimaryKeyAndDefaultValues(t *testing.T) {
animal := Animal{Name: "Ferdinand"}
if DB.Save(&animal).Error != nil {
t.Errorf("No error should happen when create an record without std primary key")
Expand All @@ -65,6 +65,26 @@ func TestCreateWithNoStdPrimaryKey(t *testing.T) {
if animal.Counter == 0 {
t.Errorf("No std primary key should be filled value after create")
}

if animal.Name != "Ferdinand" {
t.Errorf("Default value should be overrided")
}

// Test create with default value not overrided
an := Animal{From: "nerdz"}

if DB.Save(&an).Error != nil {
t.Errorf("No error should happen when create an record without std primary key")
}

// We must fetch the value again, to have the default fields updated
// (We can't do this in the update statements, since sql default can be expressions
// And be different from the fields' type (eg. a time.Time fiels has a default value of "now()"
DB.Model(Animal{}).Where(&Animal{Counter: an.Counter}).First(&an)

if an.Name != "galeone" {
t.Errorf("Default value should fill the field. But got %v", an.Name)
}
}

func TestAnonymousScanner(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions field.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Field struct {
IsBlank bool
IsIgnored bool
IsPrimaryKey bool
DefaultValue interface{}
}

func (field *Field) IsScanner() bool {
Expand Down
2 changes: 1 addition & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (

_ "github.com/denisenkom/go-mssqldb"
testdb "github.com/erikstmartin/go-testdb"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/now"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
Expand Down
4 changes: 4 additions & 0 deletions scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ func (scope *Scope) fieldFromStruct(fieldStruct reflect.StructField, withRelatio
field.IsPrimaryKey = true
}

if def, ok := parseTagSetting(fieldStruct.Tag.Get("sql"))["DEFAULT"]; ok {
field.DefaultValue = def
}

field.Tag = fieldStruct.Tag

if value, ok := settings["COLUMN"]; ok {
Expand Down
7 changes: 4 additions & 3 deletions structs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,10 @@ func (i *Num) Scan(src interface{}) error {
}

type Animal struct {
Counter uint64 `gorm:"primary_key:yes"`
Name string
From string //test reserved sql keyword as field name
Counter uint64 `gorm:"primary_key:yes"`
Name string `sql:"DEFAULT:'galeone'"`
From string //test reserved sql keyword as field name
Age time.Time `sql:"DEFAULT:NOW()"`
CreatedAt time.Time
UpdatedAt time.Time
}
Expand Down
17 changes: 16 additions & 1 deletion update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestUpdate(t *testing.T) {
}
}

func TestUpdateWithNoStdPrimaryKey(t *testing.T) {
func TestUpdateWithNoStdPrimaryKeyAndDefaultValues(t *testing.T) {
animal := Animal{Name: "Ferdinand"}
DB.Save(&animal)
updatedAt1 := animal.UpdatedAt
Expand All @@ -85,6 +85,21 @@ func TestUpdateWithNoStdPrimaryKey(t *testing.T) {
if count := DB.Model(Animal{}).Update("CreatedAt", time.Now().Add(2*time.Hour)).RowsAffected; count != int64(len(animals)) {
t.Error("RowsAffected should be correct when do batch update")
}

animal = Animal{From: "somewhere"} // No name fields, should be filled with the default value (galeone)
DB.Save(&animal).Update("From", "a nice place") // The name field shoul be untouched
DB.First(&animal, animal.Counter)
if animal.Name != "galeone" {
t.Errorf("Name fiels shouldn't be changed if untouched, but got %v", animal.Name)
}

// When changing a field with a default value, the change must occur
animal.Name = "amazing horse"
DB.Save(&animal)
DB.First(&animal, animal.Counter)
if animal.Name != "amazing horse" {
t.Errorf("Update a filed with a default value should occur. But got %v\n", animal.Name)
}
}

func TestUpdates(t *testing.T) {
Expand Down

0 comments on commit 064d913

Please sign in to comment.