Переглянути джерело

Add default values support ( https://github.com/jinzhu/gorm/issues/251 )

Paolo Galeone 9 роки тому
батько
коміт
064d91335b
9 змінених файлів з 98 додано та 9 видалено
  1. 43 2
      README.md
  2. 4 1
      callback_create.go
  3. 4 0
      callback_update.go
  4. 21 1
      create_test.go
  5. 1 0
      field.go
  6. 1 1
      main_test.go
  7. 4 0
      scope.go
  8. 4 3
      structs_test.go
  9. 16 1
      update_test.go

+ 43 - 2
README.md

@@ -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
 }
 ```
@@ -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
@@ -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

+ 4 - 1
callback_create.go

@@ -2,6 +2,7 @@ package gorm
 
 import (
 	"fmt"
+	"reflect"
 	"strings"
 )
 
@@ -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()))
 			}

+ 4 - 0
callback_update.go

@@ -2,6 +2,7 @@ package gorm
 
 import (
 	"fmt"
+	"reflect"
 	"strings"
 )
 
@@ -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())))
 				}
 			}

+ 21 - 1
create_test.go

@@ -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")
@@ -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) {

+ 1 - 0
field.go

@@ -24,6 +24,7 @@ type Field struct {
 	IsBlank      bool
 	IsIgnored    bool
 	IsPrimaryKey bool
+	DefaultValue interface{}
 }
 
 func (field *Field) IsScanner() bool {

+ 1 - 1
main_test.go

@@ -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"

+ 4 - 0
scope.go

@@ -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 {

+ 4 - 3
structs_test.go

@@ -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
 }

+ 16 - 1
update_test.go

@@ -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
@@ -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) {