diff --git a/callback_update.go b/callback_update.go index f6ba0ffd..c52162c8 100644 --- a/callback_update.go +++ b/callback_update.go @@ -75,7 +75,7 @@ func updateCallback(scope *Scope) { } else { for _, field := range scope.Fields() { if scope.changeableField(field) { - if !field.IsPrimaryKey && field.IsNormal { + if !field.IsPrimaryKey && field.IsNormal && (field.Name != "CreatedAt" || !field.IsBlank) { if !field.IsForeignKey || !field.IsBlank || !field.HasDefaultValue { sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(field.DBName), scope.AddToVars(field.Field.Interface()))) } diff --git a/errors.go b/errors.go index 27c9a92d..d5ef8d57 100644 --- a/errors.go +++ b/errors.go @@ -6,11 +6,11 @@ import ( ) var ( - // ErrRecordNotFound record not found error, happens when only haven't find any matched data when looking up with a struct, finding a slice won't return this error + // ErrRecordNotFound returns a "record not found error". Occurs only when attempting to query the database with a struct; querying with a slice won't return this error ErrRecordNotFound = errors.New("record not found") - // ErrInvalidSQL invalid SQL error, happens when you passed invalid SQL + // ErrInvalidSQL occurs when you attempt a query with invalid SQL ErrInvalidSQL = errors.New("invalid SQL") - // ErrInvalidTransaction invalid transaction when you are trying to `Commit` or `Rollback` + // ErrInvalidTransaction occurs when you are trying to `Commit` or `Rollback` ErrInvalidTransaction = errors.New("no valid transaction") // ErrCantStartTransaction can't start transaction when you are trying to start one with `Begin` ErrCantStartTransaction = errors.New("can't start transaction") @@ -21,7 +21,7 @@ var ( // Errors contains all happened errors type Errors []error -// IsRecordNotFoundError returns current error has record not found error or not +// IsRecordNotFoundError returns true if error contains a RecordNotFound error func IsRecordNotFoundError(err error) bool { if errs, ok := err.(Errors); ok { for _, err := range errs { @@ -33,12 +33,12 @@ func IsRecordNotFoundError(err error) bool { return err == ErrRecordNotFound } -// GetErrors gets all happened errors +// GetErrors gets all errors that have occurred and returns a slice of errors (Error type) func (errs Errors) GetErrors() []error { return errs } -// Add adds an error +// Add adds an error to a given slice of errors func (errs Errors) Add(newErrors ...error) Errors { for _, err := range newErrors { if err == nil { @@ -62,7 +62,7 @@ func (errs Errors) Add(newErrors ...error) Errors { return errs } -// Error format happened errors +// Error takes a slice of all errors that have occurred and returns it as a formatted string func (errs Errors) Error() string { var errors = []string{} for _, e := range errs { diff --git a/field.go b/field.go index 11c410b0..acd06e20 100644 --- a/field.go +++ b/field.go @@ -2,6 +2,7 @@ package gorm import ( "database/sql" + "database/sql/driver" "errors" "fmt" "reflect" @@ -44,7 +45,14 @@ func (field *Field) Set(value interface{}) (err error) { if reflectValue.Type().ConvertibleTo(fieldValue.Type()) { fieldValue.Set(reflectValue.Convert(fieldValue.Type())) } else if scanner, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { - err = scanner.Scan(reflectValue.Interface()) + v := reflectValue.Interface() + if valuer, ok := v.(driver.Valuer); ok { + if v, err = valuer.Value(); err == nil { + err = scanner.Scan(v) + } + } else { + err = scanner.Scan(v) + } } else { err = fmt.Errorf("could not convert argument of field %s from %s to %s", field.Name, reflectValue.Type(), fieldValue.Type()) } diff --git a/field_test.go b/field_test.go index c3afdff5..03a3b3b7 100644 --- a/field_test.go +++ b/field_test.go @@ -3,6 +3,7 @@ package gorm_test import ( "testing" + "github.com/gofrs/uuid" "github.com/jinzhu/gorm" ) @@ -47,3 +48,20 @@ func TestCalculateField(t *testing.T) { t.Errorf("should find embedded field's tag settings") } } + +func TestFieldSet(t *testing.T) { + type TestFieldSetNullUUID struct { + NullUUID uuid.NullUUID + } + scope := DB.NewScope(&TestFieldSetNullUUID{}) + field := scope.Fields()[0] + err := field.Set(uuid.FromStringOrNil("3034d44a-da03-11e8-b366-4a00070b9f00")) + if err != nil { + t.Fatal(err) + } + if id, ok := field.Field.Addr().Interface().(*uuid.NullUUID); !ok { + t.Fatal() + } else if !id.Valid || id.UUID.String() != "3034d44a-da03-11e8-b366-4a00070b9f00" { + t.Fatal(id) + } +} diff --git a/main.go b/main.go index 17c75ed3..34a6ddc8 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ type DB struct { // single db db SQLCommon blockGlobalUpdate bool - logMode int + logMode logModeValue logger logger search *search values sync.Map @@ -31,6 +31,14 @@ type DB struct { singularTable bool } +type logModeValue int + +const ( + defaultLogMode logModeValue = iota + noLogMode + detailedLogMode +) + // Open initialize a new db connection, need to import driver first, e.g: // // import _ "github.com/go-sql-driver/mysql" @@ -141,9 +149,9 @@ func (s *DB) SetLogger(log logger) { // LogMode set log mode, `true` for detailed logs, `false` for no log, default, will only print error logs func (s *DB) LogMode(enable bool) *DB { if enable { - s.logMode = 2 + s.logMode = detailedLogMode } else { - s.logMode = 1 + s.logMode = noLogMode } return s } @@ -170,7 +178,7 @@ func (s *DB) SingularTable(enable bool) { func (s *DB) NewScope(value interface{}) *Scope { dbClone := s.clone() dbClone.Value = value - return &Scope{db: dbClone, Search: dbClone.search.clone(), Value: value} + return &Scope{db: dbClone, Search: dbClone.search, Value: value} } // QueryExpr returns the query as expr object @@ -716,7 +724,7 @@ func (s *DB) SetJoinTableHandler(source interface{}, column string, handler Join func (s *DB) AddError(err error) error { if err != nil { if err != ErrRecordNotFound { - if s.logMode == 0 { + if s.logMode == defaultLogMode { go s.print(fileWithLineNum(), err) } else { s.log(err) @@ -780,13 +788,13 @@ func (s *DB) print(v ...interface{}) { } func (s *DB) log(v ...interface{}) { - if s != nil && s.logMode == 2 { + if s != nil && s.logMode == detailedLogMode { s.print(append([]interface{}{"log", fileWithLineNum()}, v...)...) } } func (s *DB) slog(sql string, t time.Time, vars ...interface{}) { - if s.logMode == 2 { + if s.logMode == detailedLogMode { s.print("sql", fileWithLineNum(), NowFunc().Sub(t), sql, vars, s.RowsAffected) } } diff --git a/model_struct.go b/model_struct.go index 8c27e209..9e93db63 100644 --- a/model_struct.go +++ b/model_struct.go @@ -21,12 +21,12 @@ var modelStructsMap sync.Map // ModelStruct model definition type ModelStruct struct { - PrimaryFields []*StructField - StructFields []*StructField - ModelType reflect.Type + PrimaryFields []*StructField + StructFields []*StructField + ModelType reflect.Type defaultTableName string - l sync.Mutex + l sync.Mutex } // TableName returns model's table name @@ -40,7 +40,7 @@ func (s *ModelStruct) TableName(db *DB) string { s.defaultTableName = tabler.TableName() } else { tableName := ToTableName(s.ModelType.Name()) - if db == nil || !db.parent.singularTable { + if db == nil || (db.parent != nil && !db.parent.singularTable) { tableName = inflection.Plural(tableName) } s.defaultTableName = tableName @@ -70,52 +70,52 @@ type StructField struct { } // TagSettingsSet Sets a tag in the tag settings map -func (s *StructField) TagSettingsSet(key, val string) { - s.tagSettingsLock.Lock() - defer s.tagSettingsLock.Unlock() - s.TagSettings[key] = val +func (sf *StructField) TagSettingsSet(key, val string) { + sf.tagSettingsLock.Lock() + defer sf.tagSettingsLock.Unlock() + sf.TagSettings[key] = val } // TagSettingsGet returns a tag from the tag settings -func (s *StructField) TagSettingsGet(key string) (string, bool) { - s.tagSettingsLock.RLock() - defer s.tagSettingsLock.RUnlock() - val, ok := s.TagSettings[key] +func (sf *StructField) TagSettingsGet(key string) (string, bool) { + sf.tagSettingsLock.RLock() + defer sf.tagSettingsLock.RUnlock() + val, ok := sf.TagSettings[key] return val, ok } // TagSettingsDelete deletes a tag -func (s *StructField) TagSettingsDelete(key string) { - s.tagSettingsLock.Lock() - defer s.tagSettingsLock.Unlock() - delete(s.TagSettings, key) +func (sf *StructField) TagSettingsDelete(key string) { + sf.tagSettingsLock.Lock() + defer sf.tagSettingsLock.Unlock() + delete(sf.TagSettings, key) } -func (structField *StructField) clone() *StructField { +func (sf *StructField) clone() *StructField { clone := &StructField{ - DBName: structField.DBName, - Name: structField.Name, - Names: structField.Names, - IsPrimaryKey: structField.IsPrimaryKey, - IsNormal: structField.IsNormal, - IsIgnored: structField.IsIgnored, - IsScanner: structField.IsScanner, - HasDefaultValue: structField.HasDefaultValue, - Tag: structField.Tag, + DBName: sf.DBName, + Name: sf.Name, + Names: sf.Names, + IsPrimaryKey: sf.IsPrimaryKey, + IsNormal: sf.IsNormal, + IsIgnored: sf.IsIgnored, + IsScanner: sf.IsScanner, + HasDefaultValue: sf.HasDefaultValue, + Tag: sf.Tag, TagSettings: map[string]string{}, - Struct: structField.Struct, - IsForeignKey: structField.IsForeignKey, + Struct: sf.Struct, + IsForeignKey: sf.IsForeignKey, } - if structField.Relationship != nil { - relationship := *structField.Relationship + if sf.Relationship != nil { + relationship := *sf.Relationship clone.Relationship = &relationship } // copy the struct field tagSettings, they should be read-locked while they are copied - structField.tagSettingsLock.Lock() - defer structField.tagSettingsLock.Unlock() - for key, value := range structField.TagSettings { + sf.tagSettingsLock.Lock() + defer sf.tagSettingsLock.Unlock() + for key, value := range sf.TagSettings { clone.TagSettings[key] = value } diff --git a/scope.go b/scope.go index 01e4977d..601778d5 100644 --- a/scope.go +++ b/scope.go @@ -1335,6 +1335,7 @@ func (scope *Scope) autoIndex() *Scope { } func (scope *Scope) getColumnAsArray(columns []string, values ...interface{}) (results [][]interface{}) { + resultMap := make(map[string][]interface{}) for _, value := range values { indirectValue := indirect(reflect.ValueOf(value)) @@ -1353,7 +1354,10 @@ func (scope *Scope) getColumnAsArray(columns []string, values ...interface{}) (r } if hasValue { - results = append(results, result) + h := fmt.Sprint(result...) + if _, exist := resultMap[h]; !exist { + resultMap[h] = result + } } } case reflect.Struct: @@ -1368,11 +1372,16 @@ func (scope *Scope) getColumnAsArray(columns []string, values ...interface{}) (r } if hasValue { - results = append(results, result) + h := fmt.Sprint(result...) + if _, exist := resultMap[h]; !exist { + resultMap[h] = result + } } } } - + for _, v := range resultMap { + results = append(results, v) + } return }