Merge branch 'master' into master

This commit is contained in:
Matt Schiros 2019-05-24 16:37:53 -07:00 committed by GitHub
commit 345cd7a4b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 601 additions and 77 deletions

View File

@ -1,11 +0,0 @@
---
engines:
gofmt:
enabled: true
govet:
enabled: true
golint:
enabled: true
ratings:
paths:
- "**.go"

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
documents documents
coverage.txt
_book _book

View File

@ -4,10 +4,11 @@ The fantastic ORM library for Golang, aims to be developer friendly.
[![go report card](https://goreportcard.com/badge/github.com/jinzhu/gorm "go report card")](https://goreportcard.com/report/github.com/jinzhu/gorm) [![go report card](https://goreportcard.com/badge/github.com/jinzhu/gorm "go report card")](https://goreportcard.com/report/github.com/jinzhu/gorm)
[![wercker status](https://app.wercker.com/status/8596cace912c9947dd9c8542ecc8cb8b/s/master "wercker status")](https://app.wercker.com/project/byKey/8596cace912c9947dd9c8542ecc8cb8b) [![wercker status](https://app.wercker.com/status/8596cace912c9947dd9c8542ecc8cb8b/s/master "wercker status")](https://app.wercker.com/project/byKey/8596cace912c9947dd9c8542ecc8cb8b)
[![codecov](https://codecov.io/gh/jinzhu/gorm/branch/master/graph/badge.svg)](https://codecov.io/gh/jinzhu/gorm)
[![Join the chat at https://gitter.im/jinzhu/gorm](https://img.shields.io/gitter/room/jinzhu/gorm.svg)](https://gitter.im/jinzhu/gorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/jinzhu/gorm](https://img.shields.io/gitter/room/jinzhu/gorm.svg)](https://gitter.im/jinzhu/gorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Open Collective Backer](https://opencollective.com/gorm/tiers/backer/badge.svg?label=backer&color=brightgreen "Open Collective Backer")](https://opencollective.com/gorm) [![Open Collective Backer](https://opencollective.com/gorm/tiers/backer/badge.svg?label=backer&color=brightgreen "Open Collective Backer")](https://opencollective.com/gorm)
[![Open Collective Sponsor](https://opencollective.com/gorm/tiers/sponsor/badge.svg?label=sponsor&color=brightgreen "Open Collective Sponsor")](https://opencollective.com/gorm) [![Open Collective Sponsor](https://opencollective.com/gorm/tiers/sponsor/badge.svg?label=sponsor&color=brightgreen "Open Collective Sponsor")](https://opencollective.com/gorm)
[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) [![MIT license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)
[![GoDoc](https://godoc.org/github.com/jinzhu/gorm?status.svg)](https://godoc.org/github.com/jinzhu/gorm) [![GoDoc](https://godoc.org/github.com/jinzhu/gorm?status.svg)](https://godoc.org/github.com/jinzhu/gorm)
## Overview ## Overview
@ -27,11 +28,11 @@ The fantastic ORM library for Golang, aims to be developer friendly.
## Getting Started ## Getting Started
* GORM Guides [http://gorm.io](http://gorm.io) * GORM Guides [https://gorm.io](https://gorm.io)
## Contributing ## Contributing
[You can help to deliver a better GORM, check out things you can do](http://gorm.io/contribute.html) [You can help to deliver a better GORM, check out things you can do](https://gorm.io/contribute.html)
## License ## License

View File

@ -1,6 +1,6 @@
package gorm package gorm
import "log" import "fmt"
// DefaultCallback default callbacks defined by gorm // DefaultCallback default callbacks defined by gorm
var DefaultCallback = &Callback{} var DefaultCallback = &Callback{}
@ -13,6 +13,7 @@ var DefaultCallback = &Callback{}
// Field `rowQueries` contains callbacks will be call when querying object with Row, Rows... // Field `rowQueries` contains callbacks will be call when querying object with Row, Rows...
// Field `processors` contains all callback processors, will be used to generate above callbacks in order // Field `processors` contains all callback processors, will be used to generate above callbacks in order
type Callback struct { type Callback struct {
logger logger
creates []*func(scope *Scope) creates []*func(scope *Scope)
updates []*func(scope *Scope) updates []*func(scope *Scope)
deletes []*func(scope *Scope) deletes []*func(scope *Scope)
@ -23,6 +24,7 @@ type Callback struct {
// CallbackProcessor contains callback informations // CallbackProcessor contains callback informations
type CallbackProcessor struct { type CallbackProcessor struct {
logger logger
name string // current callback's name name string // current callback's name
before string // register current callback before a callback before string // register current callback before a callback
after string // register current callback after a callback after string // register current callback after a callback
@ -33,8 +35,9 @@ type CallbackProcessor struct {
parent *Callback parent *Callback
} }
func (c *Callback) clone() *Callback { func (c *Callback) clone(logger logger) *Callback {
return &Callback{ return &Callback{
logger: logger,
creates: c.creates, creates: c.creates,
updates: c.updates, updates: c.updates,
deletes: c.deletes, deletes: c.deletes,
@ -53,28 +56,28 @@ func (c *Callback) clone() *Callback {
// scope.Err(errors.New("error")) // scope.Err(errors.New("error"))
// }) // })
func (c *Callback) Create() *CallbackProcessor { func (c *Callback) Create() *CallbackProcessor {
return &CallbackProcessor{kind: "create", parent: c} return &CallbackProcessor{logger: c.logger, kind: "create", parent: c}
} }
// Update could be used to register callbacks for updating object, refer `Create` for usage // Update could be used to register callbacks for updating object, refer `Create` for usage
func (c *Callback) Update() *CallbackProcessor { func (c *Callback) Update() *CallbackProcessor {
return &CallbackProcessor{kind: "update", parent: c} return &CallbackProcessor{logger: c.logger, kind: "update", parent: c}
} }
// Delete could be used to register callbacks for deleting object, refer `Create` for usage // Delete could be used to register callbacks for deleting object, refer `Create` for usage
func (c *Callback) Delete() *CallbackProcessor { func (c *Callback) Delete() *CallbackProcessor {
return &CallbackProcessor{kind: "delete", parent: c} return &CallbackProcessor{logger: c.logger, kind: "delete", parent: c}
} }
// Query could be used to register callbacks for querying objects with query methods like `Find`, `First`, `Related`, `Association`... // Query could be used to register callbacks for querying objects with query methods like `Find`, `First`, `Related`, `Association`...
// Refer `Create` for usage // Refer `Create` for usage
func (c *Callback) Query() *CallbackProcessor { func (c *Callback) Query() *CallbackProcessor {
return &CallbackProcessor{kind: "query", parent: c} return &CallbackProcessor{logger: c.logger, kind: "query", parent: c}
} }
// RowQuery could be used to register callbacks for querying objects with `Row`, `Rows`, refer `Create` for usage // RowQuery could be used to register callbacks for querying objects with `Row`, `Rows`, refer `Create` for usage
func (c *Callback) RowQuery() *CallbackProcessor { func (c *Callback) RowQuery() *CallbackProcessor {
return &CallbackProcessor{kind: "row_query", parent: c} return &CallbackProcessor{logger: c.logger, kind: "row_query", parent: c}
} }
// After insert a new callback after callback `callbackName`, refer `Callbacks.Create` // After insert a new callback after callback `callbackName`, refer `Callbacks.Create`
@ -93,7 +96,7 @@ func (cp *CallbackProcessor) Before(callbackName string) *CallbackProcessor {
func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *Scope)) { func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *Scope)) {
if cp.kind == "row_query" { if cp.kind == "row_query" {
if cp.before == "" && cp.after == "" && callbackName != "gorm:row_query" { if cp.before == "" && cp.after == "" && callbackName != "gorm:row_query" {
log.Printf("Registing RowQuery callback %v without specify order with Before(), After(), applying Before('gorm:row_query') by default for compatibility...\n", callbackName) cp.logger.Print(fmt.Sprintf("Registering RowQuery callback %v without specify order with Before(), After(), applying Before('gorm:row_query') by default for compatibility...\n", callbackName))
cp.before = "gorm:row_query" cp.before = "gorm:row_query"
} }
} }
@ -107,7 +110,7 @@ func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *
// Remove a registered callback // Remove a registered callback
// db.Callback().Create().Remove("gorm:update_time_stamp_when_create") // db.Callback().Create().Remove("gorm:update_time_stamp_when_create")
func (cp *CallbackProcessor) Remove(callbackName string) { func (cp *CallbackProcessor) Remove(callbackName string) {
log.Printf("[info] removing callback `%v` from %v\n", callbackName, fileWithLineNum()) cp.logger.Print(fmt.Sprintf("[info] removing callback `%v` from %v\n", callbackName, fileWithLineNum()))
cp.name = callbackName cp.name = callbackName
cp.remove = true cp.remove = true
cp.parent.processors = append(cp.parent.processors, cp) cp.parent.processors = append(cp.parent.processors, cp)
@ -120,7 +123,7 @@ func (cp *CallbackProcessor) Remove(callbackName string) {
// scope.SetColumn("Updated", now) // scope.SetColumn("Updated", now)
// }) // })
func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *Scope)) { func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *Scope)) {
log.Printf("[info] replacing callback `%v` from %v\n", callbackName, fileWithLineNum()) cp.logger.Print(fmt.Sprintf("[info] replacing callback `%v` from %v\n", callbackName, fileWithLineNum()))
cp.name = callbackName cp.name = callbackName
cp.processor = &callback cp.processor = &callback
cp.replace = true cp.replace = true
@ -159,7 +162,7 @@ func sortProcessors(cps []*CallbackProcessor) []*func(scope *Scope) {
for _, cp := range cps { for _, cp := range cps {
// show warning message the callback name already exists // show warning message the callback name already exists
if index := getRIndex(allNames, cp.name); index > -1 && !cp.replace && !cp.remove { if index := getRIndex(allNames, cp.name); index > -1 && !cp.replace && !cp.remove {
log.Printf("[warning] duplicated callback `%v` from %v\n", cp.name, fileWithLineNum()) cp.logger.Print(fmt.Sprintf("[warning] duplicated callback `%v` from %v\n", cp.name, fileWithLineNum()))
} }
allNames = append(allNames, cp.name) allNames = append(allNames, cp.name)
} }

View File

@ -83,11 +83,18 @@ func createCallback(scope *Scope) {
quotedTableName = scope.QuotedTableName() quotedTableName = scope.QuotedTableName()
primaryField = scope.PrimaryField() primaryField = scope.PrimaryField()
extraOption string extraOption string
insertModifier string
) )
if str, ok := scope.Get("gorm:insert_option"); ok { if str, ok := scope.Get("gorm:insert_option"); ok {
extraOption = fmt.Sprint(str) extraOption = fmt.Sprint(str)
} }
if str, ok := scope.Get("gorm:insert_modifier"); ok {
insertModifier = strings.ToUpper(fmt.Sprint(str))
if insertModifier == "INTO" {
insertModifier = ""
}
}
if primaryField != nil { if primaryField != nil {
returningColumn = scope.Quote(primaryField.DBName) returningColumn = scope.Quote(primaryField.DBName)
@ -97,7 +104,8 @@ func createCallback(scope *Scope) {
if len(columns) == 0 { if len(columns) == 0 {
scope.Raw(fmt.Sprintf( scope.Raw(fmt.Sprintf(
"INSERT INTO %v %v%v%v", "INSERT %v INTO %v %v%v%v",
addExtraSpaceIfExist(insertModifier),
quotedTableName, quotedTableName,
scope.Dialect().DefaultValueStr(), scope.Dialect().DefaultValueStr(),
addExtraSpaceIfExist(extraOption), addExtraSpaceIfExist(extraOption),
@ -105,7 +113,8 @@ func createCallback(scope *Scope) {
)) ))
} else { } else {
scope.Raw(fmt.Sprintf( scope.Raw(fmt.Sprintf(
"INSERT INTO %v (%v) VALUES (%v)%v%v", "INSERT %v INTO %v (%v) VALUES (%v)%v%v",
addExtraSpaceIfExist(insertModifier),
scope.QuotedTableName(), scope.QuotedTableName(),
strings.Join(columns, ","), strings.Join(columns, ","),
strings.Join(placeholders, ","), strings.Join(placeholders, ","),

View File

@ -391,14 +391,20 @@ func (scope *Scope) handleManyToManyPreload(field *Field, conditions []interface
key := toString(getValueFromFields(indirectScopeValue, foreignFieldNames)) key := toString(getValueFromFields(indirectScopeValue, foreignFieldNames))
fieldsSourceMap[key] = append(fieldsSourceMap[key], indirectScopeValue.FieldByName(field.Name)) fieldsSourceMap[key] = append(fieldsSourceMap[key], indirectScopeValue.FieldByName(field.Name))
} }
for source, link := range linkHash {
for i, field := range fieldsSourceMap[source] { for source, fields := range fieldsSourceMap {
for _, f := range fields {
//If not 0 this means Value is a pointer and we already added preloaded models to it //If not 0 this means Value is a pointer and we already added preloaded models to it
if fieldsSourceMap[source][i].Len() != 0 { if f.Len() != 0 {
continue continue
} }
field.Set(reflect.Append(fieldsSourceMap[source][i], link...))
}
v := reflect.MakeSlice(f.Type(), 0, 0)
if len(linkHash[source]) > 0 {
v = reflect.Append(f, linkHash[source]...)
}
f.Set(v)
}
} }
} }

View File

@ -1,6 +1,9 @@
package gorm package gorm
import "database/sql" import (
"database/sql"
"fmt"
)
// Define callbacks for row query // Define callbacks for row query
func init() { func init() {
@ -20,6 +23,9 @@ type RowsQueryResult struct {
func rowQueryCallback(scope *Scope) { func rowQueryCallback(scope *Scope) {
if result, ok := scope.InstanceGet("row_query_result"); ok { if result, ok := scope.InstanceGet("row_query_result"); ok {
scope.prepareQuerySQL() scope.prepareQuerySQL()
if str, ok := scope.Get("gorm:query_option"); ok {
scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str))
}
if rowResult, ok := result.(*RowQueryResult); ok { if rowResult, ok := result.(*RowQueryResult); ok {
rowResult.Row = scope.SQLDB().QueryRow(scope.SQL, scope.SQLVars...) rowResult.Row = scope.SQLDB().QueryRow(scope.SQL, scope.SQLVars...)

View File

@ -23,7 +23,7 @@ func afterCreate1(s *Scope) {}
func afterCreate2(s *Scope) {} func afterCreate2(s *Scope) {}
func TestRegisterCallback(t *testing.T) { func TestRegisterCallback(t *testing.T) {
var callback = &Callback{} var callback = &Callback{logger: defaultLogger}
callback.Create().Register("before_create1", beforeCreate1) callback.Create().Register("before_create1", beforeCreate1)
callback.Create().Register("before_create2", beforeCreate2) callback.Create().Register("before_create2", beforeCreate2)
@ -37,7 +37,7 @@ func TestRegisterCallback(t *testing.T) {
} }
func TestRegisterCallbackWithOrder(t *testing.T) { func TestRegisterCallbackWithOrder(t *testing.T) {
var callback1 = &Callback{} var callback1 = &Callback{logger: defaultLogger}
callback1.Create().Register("before_create1", beforeCreate1) callback1.Create().Register("before_create1", beforeCreate1)
callback1.Create().Register("create", create) callback1.Create().Register("create", create)
callback1.Create().Register("after_create1", afterCreate1) callback1.Create().Register("after_create1", afterCreate1)
@ -46,7 +46,7 @@ func TestRegisterCallbackWithOrder(t *testing.T) {
t.Errorf("register callback with order") t.Errorf("register callback with order")
} }
var callback2 = &Callback{} var callback2 = &Callback{logger: defaultLogger}
callback2.Update().Register("create", create) callback2.Update().Register("create", create)
callback2.Update().Before("create").Register("before_create1", beforeCreate1) callback2.Update().Before("create").Register("before_create1", beforeCreate1)
@ -60,7 +60,7 @@ func TestRegisterCallbackWithOrder(t *testing.T) {
} }
func TestRegisterCallbackWithComplexOrder(t *testing.T) { func TestRegisterCallbackWithComplexOrder(t *testing.T) {
var callback1 = &Callback{} var callback1 = &Callback{logger: defaultLogger}
callback1.Query().Before("after_create1").After("before_create1").Register("create", create) callback1.Query().Before("after_create1").After("before_create1").Register("create", create)
callback1.Query().Register("before_create1", beforeCreate1) callback1.Query().Register("before_create1", beforeCreate1)
@ -70,7 +70,7 @@ func TestRegisterCallbackWithComplexOrder(t *testing.T) {
t.Errorf("register callback with order") t.Errorf("register callback with order")
} }
var callback2 = &Callback{} var callback2 = &Callback{logger: defaultLogger}
callback2.Delete().Before("after_create1").After("before_create1").Register("create", create) callback2.Delete().Before("after_create1").After("before_create1").Register("create", create)
callback2.Delete().Before("create").Register("before_create1", beforeCreate1) callback2.Delete().Before("create").Register("before_create1", beforeCreate1)
@ -86,7 +86,7 @@ func TestRegisterCallbackWithComplexOrder(t *testing.T) {
func replaceCreate(s *Scope) {} func replaceCreate(s *Scope) {}
func TestReplaceCallback(t *testing.T) { func TestReplaceCallback(t *testing.T) {
var callback = &Callback{} var callback = &Callback{logger: defaultLogger}
callback.Create().Before("after_create1").After("before_create1").Register("create", create) callback.Create().Before("after_create1").After("before_create1").Register("create", create)
callback.Create().Register("before_create1", beforeCreate1) callback.Create().Register("before_create1", beforeCreate1)
@ -99,7 +99,7 @@ func TestReplaceCallback(t *testing.T) {
} }
func TestRemoveCallback(t *testing.T) { func TestRemoveCallback(t *testing.T) {
var callback = &Callback{} var callback = &Callback{logger: defaultLogger}
callback.Create().Before("after_create1").After("before_create1").Register("create", create) callback.Create().Before("after_create1").After("before_create1").Register("create", create)
callback.Create().Register("before_create1", beforeCreate1) callback.Create().Register("before_create1", beforeCreate1)

View File

@ -229,3 +229,20 @@ func TestOmitWithCreate(t *testing.T) {
t.Errorf("Should not create omitted relationships") t.Errorf("Should not create omitted relationships")
} }
} }
func TestCreateIgnore(t *testing.T) {
float := 35.03554004971999
now := time.Now()
user := User{Name: "CreateUser", Age: 18, Birthday: &now, UserNum: Num(111), PasswordHash: []byte{'f', 'a', 'k', '4'}, Latitude: float}
if !DB.NewRecord(user) || !DB.NewRecord(&user) {
t.Error("User should be new record before create")
}
if count := DB.Create(&user).RowsAffected; count != 1 {
t.Error("There should be one record be affected when create record")
}
if DB.Dialect().GetName() == "mysql" && DB.Set("gorm:insert_modifier", "IGNORE").Create(&user).Error != nil {
t.Error("Should ignore duplicate user insert by insert modifier:IGNORE ")
}
}

View File

@ -289,6 +289,9 @@ type SelfReferencingUser struct {
func TestSelfReferencingMany2ManyColumn(t *testing.T) { func TestSelfReferencingMany2ManyColumn(t *testing.T) {
DB.DropTable(&SelfReferencingUser{}, "UserFriends") DB.DropTable(&SelfReferencingUser{}, "UserFriends")
DB.AutoMigrate(&SelfReferencingUser{}) DB.AutoMigrate(&SelfReferencingUser{})
if !DB.HasTable("UserFriends") {
t.Errorf("auto migrate error, table UserFriends should be created")
}
friend1 := SelfReferencingUser{Name: "friend1_m2m"} friend1 := SelfReferencingUser{Name: "friend1_m2m"}
if err := DB.Create(&friend1).Error; err != nil { if err := DB.Create(&friend1).Error; err != nil {
@ -313,6 +316,14 @@ func TestSelfReferencingMany2ManyColumn(t *testing.T) {
t.Errorf("Should find created friends correctly") t.Errorf("Should find created friends correctly")
} }
var count int
if err := DB.Table("UserFriends").Count(&count).Error; err != nil {
t.Errorf("no error should happen, but got %v", err)
}
if count == 0 {
t.Errorf("table UserFriends should have records")
}
var newUser = SelfReferencingUser{} var newUser = SelfReferencingUser{}
if err := DB.Preload("Friends").First(&newUser, "id = ?", user.ID).Error; err != nil { if err := DB.Preload("Friends").First(&newUser, "id = ?", user.ID).Error; err != nil {

View File

@ -48,6 +48,9 @@ type Dialect interface {
// BuildKeyName returns a valid key name (foreign key, index key) for the given table, field and reference // BuildKeyName returns a valid key name (foreign key, index key) for the given table, field and reference
BuildKeyName(kind, tableName string, fields ...string) string BuildKeyName(kind, tableName string, fields ...string) string
// NormalizeIndexAndColumn returns valid index name and column name depending on each dialect
NormalizeIndexAndColumn(indexName, columnName string) (string, string)
// CurrentDatabase return current database name // CurrentDatabase return current database name
CurrentDatabase() string CurrentDatabase() string
} }
@ -126,6 +129,10 @@ var ParseFieldStructForDialect = func(field *StructField, dialect Dialect) (fiel
additionalType = additionalType + " DEFAULT " + value additionalType = additionalType + " DEFAULT " + value
} }
if value, ok := field.TagSettingsGet("COMMENT"); ok {
additionalType = additionalType + " COMMENT " + value
}
return fieldValue, dataType, size, strings.TrimSpace(additionalType) return fieldValue, dataType, size, strings.TrimSpace(additionalType)
} }

View File

@ -9,6 +9,8 @@ import (
"time" "time"
) )
var keyNameRegex = regexp.MustCompile("[^a-zA-Z0-9]+")
// DefaultForeignKeyNamer contains the default foreign key name generator method // DefaultForeignKeyNamer contains the default foreign key name generator method
type DefaultForeignKeyNamer struct { type DefaultForeignKeyNamer struct {
} }
@ -166,10 +168,15 @@ func (commonDialect) DefaultValueStr() string {
// BuildKeyName returns a valid key name (foreign key, index key) for the given table, field and reference // BuildKeyName returns a valid key name (foreign key, index key) for the given table, field and reference
func (DefaultForeignKeyNamer) BuildKeyName(kind, tableName string, fields ...string) string { func (DefaultForeignKeyNamer) BuildKeyName(kind, tableName string, fields ...string) string {
keyName := fmt.Sprintf("%s_%s_%s", kind, tableName, strings.Join(fields, "_")) keyName := fmt.Sprintf("%s_%s_%s", kind, tableName, strings.Join(fields, "_"))
keyName = regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(keyName, "_") keyName = keyNameRegex.ReplaceAllString(keyName, "_")
return keyName return keyName
} }
// NormalizeIndexAndColumn returns argument's index name and column name without doing anything
func (commonDialect) NormalizeIndexAndColumn(indexName, columnName string) (string, string) {
return indexName, columnName
}
// IsByteArrayOrSlice returns true of the reflected value is an array or slice // IsByteArrayOrSlice returns true of the reflected value is an array or slice
func IsByteArrayOrSlice(value reflect.Value) bool { func IsByteArrayOrSlice(value reflect.Value) bool {
return (value.Kind() == reflect.Array || value.Kind() == reflect.Slice) && value.Type().Elem() == reflect.TypeOf(uint8(0)) return (value.Kind() == reflect.Array || value.Kind() == reflect.Slice) && value.Type().Elem() == reflect.TypeOf(uint8(0))

View File

@ -11,6 +11,8 @@ import (
"unicode/utf8" "unicode/utf8"
) )
var mysqlIndexRegex = regexp.MustCompile(`^(.+)\((\d+)\)$`)
type mysql struct { type mysql struct {
commonDialect commonDialect
} }
@ -100,7 +102,7 @@ func (s *mysql) DataTypeOf(field *StructField) string {
precision = fmt.Sprintf("(%s)", p) precision = fmt.Sprintf("(%s)", p)
} }
if _, ok := field.TagSettingsGet("NOT NULL"); ok { if _, ok := field.TagSettingsGet("NOT NULL"); ok || field.IsPrimaryKey {
sqlType = fmt.Sprintf("timestamp%v", precision) sqlType = fmt.Sprintf("timestamp%v", precision)
} else { } else {
sqlType = fmt.Sprintf("timestamp%v NULL", precision) sqlType = fmt.Sprintf("timestamp%v NULL", precision)
@ -178,7 +180,7 @@ func (s mysql) BuildKeyName(kind, tableName string, fields ...string) string {
bs := h.Sum(nil) bs := h.Sum(nil)
// sha1 is 40 characters, keep first 24 characters of destination // sha1 is 40 characters, keep first 24 characters of destination
destRunes := []rune(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(fields[0], "_")) destRunes := []rune(keyNameRegex.ReplaceAllString(fields[0], "_"))
if len(destRunes) > 24 { if len(destRunes) > 24 {
destRunes = destRunes[:24] destRunes = destRunes[:24]
} }
@ -186,6 +188,17 @@ func (s mysql) BuildKeyName(kind, tableName string, fields ...string) string {
return fmt.Sprintf("%s%x", string(destRunes), bs) return fmt.Sprintf("%s%x", string(destRunes), bs)
} }
// NormalizeIndexAndColumn returns index name and column name for specify an index prefix length if needed
func (mysql) NormalizeIndexAndColumn(indexName, columnName string) (string, string) {
submatch := mysqlIndexRegex.FindStringSubmatch(indexName)
if len(submatch) != 3 {
return indexName, columnName
}
indexName = submatch[1]
columnName = fmt.Sprintf("%s(%s)", columnName, submatch[2])
return indexName, columnName
}
func (mysql) DefaultValueStr() string { func (mysql) DefaultValueStr() string {
return "VALUES()" return "VALUES()"
} }

View File

@ -198,6 +198,11 @@ func (mssql) DefaultValueStr() string {
return "DEFAULT VALUES" return "DEFAULT VALUES"
} }
// NormalizeIndexAndColumn returns argument's index name and column name without doing anything
func (mssql) NormalizeIndexAndColumn(indexName, columnName string) (string, string) {
return indexName, columnName
}
func currentDatabaseAndTable(dialect gorm.Dialect, tableName string) (string, string) { func currentDatabaseAndTable(dialect gorm.Dialect, tableName string) (string, string) {
if strings.Contains(tableName, ".") { if strings.Contains(tableName, ".") {
splitStrings := strings.SplitN(tableName, ".", 2) splitStrings := strings.SplitN(tableName, ".", 2)

View File

@ -1,9 +1,11 @@
package gorm_test package gorm_test
import ( import (
"database/sql/driver"
"encoding/hex"
"fmt"
"testing" "testing"
"github.com/gofrs/uuid"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
) )
@ -49,17 +51,78 @@ func TestCalculateField(t *testing.T) {
} }
} }
type UUID [16]byte
type NullUUID struct {
UUID
Valid bool
}
func FromString(input string) (u UUID) {
src := []byte(input)
return FromBytes(src)
}
func FromBytes(src []byte) (u UUID) {
dst := u[:]
hex.Decode(dst[0:4], src[0:8])
hex.Decode(dst[4:6], src[9:13])
hex.Decode(dst[6:8], src[14:18])
hex.Decode(dst[8:10], src[19:23])
hex.Decode(dst[10:], src[24:])
return
}
func (u UUID) String() string {
buf := make([]byte, 36)
src := u[:]
hex.Encode(buf[0:8], src[0:4])
buf[8] = '-'
hex.Encode(buf[9:13], src[4:6])
buf[13] = '-'
hex.Encode(buf[14:18], src[6:8])
buf[18] = '-'
hex.Encode(buf[19:23], src[8:10])
buf[23] = '-'
hex.Encode(buf[24:], src[10:])
return string(buf)
}
func (u UUID) Value() (driver.Value, error) {
return u.String(), nil
}
func (u *UUID) Scan(src interface{}) error {
switch src := src.(type) {
case UUID: // support gorm convert from UUID to NullUUID
*u = src
return nil
case []byte:
*u = FromBytes(src)
return nil
case string:
*u = FromString(src)
return nil
}
return fmt.Errorf("uuid: cannot convert %T to UUID", src)
}
func (u *NullUUID) Scan(src interface{}) error {
u.Valid = true
return u.UUID.Scan(src)
}
func TestFieldSet(t *testing.T) { func TestFieldSet(t *testing.T) {
type TestFieldSetNullUUID struct { type TestFieldSetNullUUID struct {
NullUUID uuid.NullUUID NullUUID NullUUID
} }
scope := DB.NewScope(&TestFieldSetNullUUID{}) scope := DB.NewScope(&TestFieldSetNullUUID{})
field := scope.Fields()[0] field := scope.Fields()[0]
err := field.Set(uuid.FromStringOrNil("3034d44a-da03-11e8-b366-4a00070b9f00")) err := field.Set(FromString("3034d44a-da03-11e8-b366-4a00070b9f00"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if id, ok := field.Field.Addr().Interface().(*uuid.NullUUID); !ok { if id, ok := field.Field.Addr().Interface().(*NullUUID); !ok {
t.Fatal() t.Fatal()
} else if !id.Valid || id.UUID.String() != "3034d44a-da03-11e8-b366-4a00070b9f00" { } else if !id.Valid || id.UUID.String() != "3034d44a-da03-11e8-b366-4a00070b9f00" {
t.Fatal(id) t.Fatal(id)

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module github.com/jinzhu/gorm
require (
github.com/denisenkom/go-mssqldb v0.0.0-20190423183735-731ef375ac02
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5
github.com/go-sql-driver/mysql v1.4.1
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a
github.com/jinzhu/now v1.0.0
github.com/lib/pq v1.1.0
github.com/mattn/go-sqlite3 v1.10.0
)

134
go.sum Normal file
View File

@ -0,0 +1,134 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190423183735-731ef375ac02 h1:PS3xfVPa8N84AzoWZHFCbA0+ikz4f4skktfjQoNMsgk=
github.com/denisenkom/go-mssqldb v0.0.0-20190423183735-731ef375ac02/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.0 h1:6WV8LvwPpDhKjo5U9O6b4+xdG/jTXNPwlDme/MTo8Ns=
github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.1.6 h1:yXiysv1CSK7Q5yjGy1710zZGnsbMUIjluWBxtLXHPBo=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

17
main.go
View File

@ -12,6 +12,7 @@ import (
// DB contains information for current db connection // DB contains information for current db connection
type DB struct { type DB struct {
sync.RWMutex
Value interface{} Value interface{}
Error error Error error
RowsAffected int64 RowsAffected int64
@ -137,7 +138,7 @@ func (s *DB) Dialect() Dialect {
// db.Callback().Create().Register("update_created_at", updateCreated) // db.Callback().Create().Register("update_created_at", updateCreated)
// Refer https://jinzhu.github.io/gorm/development.html#callbacks // Refer https://jinzhu.github.io/gorm/development.html#callbacks
func (s *DB) Callback() *Callback { func (s *DB) Callback() *Callback {
s.parent.callbacks = s.parent.callbacks.clone() s.parent.callbacks = s.parent.callbacks.clone(s.logger)
return s.parent.callbacks return s.parent.callbacks
} }
@ -170,7 +171,8 @@ func (s *DB) HasBlockGlobalUpdate() bool {
// SingularTable use singular table by default // SingularTable use singular table by default
func (s *DB) SingularTable(enable bool) { func (s *DB) SingularTable(enable bool) {
modelStructsMap = sync.Map{} s.parent.Lock()
defer s.parent.Unlock()
s.parent.singularTable = enable s.parent.singularTable = enable
} }
@ -178,7 +180,13 @@ func (s *DB) SingularTable(enable bool) {
func (s *DB) NewScope(value interface{}) *Scope { func (s *DB) NewScope(value interface{}) *Scope {
dbClone := s.clone() dbClone := s.clone()
dbClone.Value = value dbClone.Value = value
return &Scope{db: dbClone, Search: dbClone.search, Value: value} scope := &Scope{db: dbClone, Value: value}
if s.search != nil {
scope.Search = s.search.clone()
} else {
scope.Search = &search{}
}
return scope
} }
// QueryExpr returns the query as expr object // QueryExpr returns the query as expr object
@ -298,6 +306,7 @@ func (s *DB) Assign(attrs ...interface{}) *DB {
func (s *DB) First(out interface{}, where ...interface{}) *DB { func (s *DB) First(out interface{}, where ...interface{}) *DB {
newScope := s.NewScope(out) newScope := s.NewScope(out)
newScope.Search.Limit(1) newScope.Search.Limit(1)
return newScope.Set("gorm:order_by_primary_key", "ASC"). return newScope.Set("gorm:order_by_primary_key", "ASC").
inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
} }
@ -725,7 +734,7 @@ func (s *DB) AddError(err error) error {
if err != nil { if err != nil {
if err != ErrRecordNotFound { if err != ErrRecordNotFound {
if s.logMode == defaultLogMode { if s.logMode == defaultLogMode {
go s.print(fileWithLineNum(), err) go s.print("error", fileWithLineNum(), err)
} else { } else {
s.log(err) s.log(err)
} }

View File

@ -9,6 +9,7 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"sync"
"testing" "testing"
"time" "time"
@ -277,6 +278,30 @@ func TestTableName(t *testing.T) {
DB.SingularTable(false) DB.SingularTable(false)
} }
func TestTableNameConcurrently(t *testing.T) {
DB := DB.Model("")
if DB.NewScope(Order{}).TableName() != "orders" {
t.Errorf("Order's table name should be orders")
}
var wg sync.WaitGroup
wg.Add(10)
for i := 1; i <= 10; i++ {
go func(db *gorm.DB) {
DB.SingularTable(true)
wg.Done()
}(DB)
}
wg.Wait()
if DB.NewScope(Order{}).TableName() != "order" {
t.Errorf("Order's singular table name should be order")
}
DB.SingularTable(false)
}
func TestNullValues(t *testing.T) { func TestNullValues(t *testing.T) {
DB.DropTable(&NullValue{}) DB.DropTable(&NullValue{})
DB.AutoMigrate(&NullValue{}) DB.AutoMigrate(&NullValue{})
@ -1059,6 +1084,125 @@ func TestBlockGlobalUpdate(t *testing.T) {
} }
} }
func TestCountWithHaving(t *testing.T) {
db := DB.New()
db.Delete(User{})
defer db.Delete(User{})
DB.Create(getPreparedUser("user1", "pluck_user"))
DB.Create(getPreparedUser("user2", "pluck_user"))
user3 := getPreparedUser("user3", "pluck_user")
user3.Languages = []Language{}
DB.Create(user3)
var count int
err := db.Model(User{}).Select("users.id").
Joins("LEFT JOIN user_languages ON user_languages.user_id = users.id").
Joins("LEFT JOIN languages ON user_languages.language_id = languages.id").
Group("users.id").Having("COUNT(languages.id) > 1").Count(&count).Error
if err != nil {
t.Error("Unexpected error on query count with having")
}
if count != 2 {
t.Error("Unexpected result on query count with having")
}
}
func TestPluck(t *testing.T) {
db := DB.New()
db.Delete(User{})
defer db.Delete(User{})
DB.Create(&User{Id: 1, Name: "user1"})
DB.Create(&User{Id: 2, Name: "user2"})
DB.Create(&User{Id: 3, Name: "user3"})
var ids []int64
err := db.Model(User{}).Order("id").Pluck("id", &ids).Error
if err != nil {
t.Error("Unexpected error on pluck")
}
if len(ids) != 3 || ids[0] != 1 || ids[1] != 2 || ids[2] != 3 {
t.Error("Unexpected result on pluck")
}
err = db.Model(User{}).Order("id").Pluck("id", &ids).Error
if err != nil {
t.Error("Unexpected error on pluck again")
}
if len(ids) != 3 || ids[0] != 1 || ids[1] != 2 || ids[2] != 3 {
t.Error("Unexpected result on pluck again")
}
}
func TestCountWithQueryOption(t *testing.T) {
db := DB.New()
db.Delete(User{})
defer db.Delete(User{})
DB.Create(&User{Name: "user1"})
DB.Create(&User{Name: "user2"})
DB.Create(&User{Name: "user3"})
var count int
err := db.Model(User{}).Select("users.id").
Set("gorm:query_option", "WHERE users.name='user2'").
Count(&count).Error
if err != nil {
t.Error("Unexpected error on query count with query_option")
}
if count != 1 {
t.Error("Unexpected result on query count with query_option")
}
}
func TestFloatColumnPrecision(t *testing.T) {
if dialect := os.Getenv("GORM_DIALECT"); dialect != "mysql" && dialect != "sqlite" {
t.Skip()
}
type FloatTest struct {
ID string `gorm:"primary_key"`
FloatValue float64 `gorm:"column:float_value" sql:"type:float(255,5);"`
}
DB.DropTable(&FloatTest{})
DB.AutoMigrate(&FloatTest{})
data := FloatTest{ID: "uuid", FloatValue: 112.57315}
if err := DB.Save(&data).Error; err != nil || data.ID != "uuid" || data.FloatValue != 112.57315 {
t.Errorf("Float value should not lose precision")
}
}
func TestWhereUpdates(t *testing.T) {
type OwnerEntity struct {
gorm.Model
OwnerID uint
OwnerType string
}
type SomeEntity struct {
gorm.Model
Name string
OwnerEntity OwnerEntity `gorm:"polymorphic:Owner"`
}
db := DB.Debug()
db.DropTable(&SomeEntity{})
db.AutoMigrate(&SomeEntity{})
a := SomeEntity{Name: "test"}
db.Model(&a).Where(a).Updates(SomeEntity{Name: "test2"})
}
func BenchmarkGorm(b *testing.B) { func BenchmarkGorm(b *testing.B) {
b.N = 2000 b.N = 2000
for x := 0; x < b.N; x++ { for x := 0; x < b.N; x++ {

View File

@ -538,3 +538,42 @@ func TestModifyColumnType(t *testing.T) {
t.Errorf("No error should happen when ModifyColumn, but got %v", err) t.Errorf("No error should happen when ModifyColumn, but got %v", err)
} }
} }
func TestIndexWithPrefixLength(t *testing.T) {
if dialect := os.Getenv("GORM_DIALECT"); dialect != "mysql" {
t.Skip("Skipping this because only mysql support setting an index prefix length")
}
type IndexWithPrefix struct {
gorm.Model
Name string
Description string `gorm:"type:text;index:idx_index_with_prefixes_length(100)"`
}
type IndexesWithPrefix struct {
gorm.Model
Name string
Description1 string `gorm:"type:text;index:idx_index_with_prefixes_length(100)"`
Description2 string `gorm:"type:text;index:idx_index_with_prefixes_length(100)"`
}
type IndexesWithPrefixAndWithoutPrefix struct {
gorm.Model
Name string `gorm:"index:idx_index_with_prefixes_length"`
Description string `gorm:"type:text;index:idx_index_with_prefixes_length(100)"`
}
tables := []interface{}{&IndexWithPrefix{}, &IndexesWithPrefix{}, &IndexesWithPrefixAndWithoutPrefix{}}
for _, table := range tables {
scope := DB.NewScope(table)
tableName := scope.TableName()
t.Run(fmt.Sprintf("Create index with prefix length: %s", tableName), func(t *testing.T) {
if err := DB.DropTableIfExists(table).Error; err != nil {
t.Errorf("Failed to drop %s table: %v", tableName, err)
}
if err := DB.CreateTable(table).Error; err != nil {
t.Errorf("Failed to create %s table: %v", tableName, err)
}
if !scope.Dialect().HasIndex(tableName, "idx_index_with_prefixes_length") {
t.Errorf("Failed to create %s table index:", tableName)
}
})
}
}

View File

@ -40,9 +40,11 @@ func (s *ModelStruct) TableName(db *DB) string {
s.defaultTableName = tabler.TableName() s.defaultTableName = tabler.TableName()
} else { } else {
tableName := ToTableName(s.ModelType.Name()) tableName := ToTableName(s.ModelType.Name())
db.parent.RLock()
if db == nil || (db.parent != nil && !db.parent.singularTable) { if db == nil || (db.parent != nil && !db.parent.singularTable) {
tableName = inflection.Plural(tableName) tableName = inflection.Plural(tableName)
} }
db.parent.RUnlock()
s.defaultTableName = tableName s.defaultTableName = tableName
} }
} }
@ -164,7 +166,18 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
} }
// Get Cached model struct // Get Cached model struct
if value, ok := modelStructsMap.Load(reflectType); ok && value != nil { isSingularTable := false
if scope.db != nil && scope.db.parent != nil {
scope.db.parent.RLock()
isSingularTable = scope.db.parent.singularTable
scope.db.parent.RUnlock()
}
hashKey := struct {
singularTable bool
reflectType reflect.Type
}{isSingularTable, reflectType}
if value, ok := modelStructsMap.Load(hashKey); ok && value != nil {
return value.(*ModelStruct) return value.(*ModelStruct)
} }
@ -348,7 +361,7 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
// Check for arbitrary join conditions supplied in the tag // Check for arbitrary join conditions supplied in the tag
joinTableHandler := JoinTableHandler{} joinTableHandler := JoinTableHandler{}
joinTableHandler.Setup(relationship, ToTableName(many2many), reflectType, elemType) joinTableHandler.Setup(relationship, many2many, reflectType, elemType)
relationship.JoinTableHandler = &joinTableHandler relationship.JoinTableHandler = &joinTableHandler
field.Relationship = relationship field.Relationship = relationship
} else { } else {
@ -625,7 +638,7 @@ func (scope *Scope) GetModelStruct() *ModelStruct {
} }
} }
modelStructsMap.Store(reflectType, &modelStruct) modelStructsMap.Store(hashKey, &modelStruct)
return &modelStruct return &modelStruct
} }
@ -638,6 +651,9 @@ func (scope *Scope) GetStructFields() (fields []*StructField) {
func parseTagSetting(tags reflect.StructTag) map[string]string { func parseTagSetting(tags reflect.StructTag) map[string]string {
setting := map[string]string{} setting := map[string]string{}
for _, str := range []string{tags.Get("sql"), tags.Get("gorm")} { for _, str := range []string{tags.Get("sql"), tags.Get("gorm")} {
if str == "" {
continue
}
tags := strings.Split(str, ";") tags := strings.Split(str, ";")
for _, value := range tags { for _, value := range tags {
v := strings.Split(value, ":") v := strings.Split(value, ":")

View File

@ -771,6 +771,7 @@ func TestNestedPreload11(t *testing.T) {
levelB3 := &LevelB3{ levelB3 := &LevelB3{
Value: "bar", Value: "bar",
LevelB1ID: sql.NullInt64{Valid: true, Int64: int64(levelB1.ID)}, LevelB1ID: sql.NullInt64{Valid: true, Int64: int64(levelB1.ID)},
LevelB2s: []*LevelB2{},
} }
if err := DB.Create(levelB3).Error; err != nil { if err := DB.Create(levelB3).Error; err != nil {
t.Error(err) t.Error(err)
@ -1676,7 +1677,7 @@ func TestPreloadManyToManyCallbacks(t *testing.T) {
lvl := Level1{ lvl := Level1{
Name: "l1", Name: "l1",
Level2s: []Level2{ Level2s: []Level2{
Level2{Name: "l2-1"}, Level2{Name: "l2-2"}, {Name: "l2-1"}, {Name: "l2-2"},
}, },
} }
DB.Save(&lvl) DB.Save(&lvl)

View File

@ -872,7 +872,7 @@ func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope {
return scope return scope
} }
func convertInterfaceToMap(values interface{}, withIgnoredField bool) map[string]interface{} { func convertInterfaceToMap(values interface{}, withIgnoredField bool, db *DB) map[string]interface{} {
var attrs = map[string]interface{}{} var attrs = map[string]interface{}{}
switch value := values.(type) { switch value := values.(type) {
@ -880,7 +880,7 @@ func convertInterfaceToMap(values interface{}, withIgnoredField bool) map[string
return value return value
case []interface{}: case []interface{}:
for _, v := range value { for _, v := range value {
for key, value := range convertInterfaceToMap(v, withIgnoredField) { for key, value := range convertInterfaceToMap(v, withIgnoredField, db) {
attrs[key] = value attrs[key] = value
} }
} }
@ -893,7 +893,7 @@ func convertInterfaceToMap(values interface{}, withIgnoredField bool) map[string
attrs[ToColumnName(key.Interface().(string))] = reflectValue.MapIndex(key).Interface() attrs[ToColumnName(key.Interface().(string))] = reflectValue.MapIndex(key).Interface()
} }
default: default:
for _, field := range (&Scope{Value: values}).Fields() { for _, field := range (&Scope{Value: values, db: db}).Fields() {
if !field.IsBlank && (withIgnoredField || !field.IsIgnored) { if !field.IsBlank && (withIgnoredField || !field.IsIgnored) {
attrs[field.DBName] = field.Field.Interface() attrs[field.DBName] = field.Field.Interface()
} }
@ -905,12 +905,12 @@ func convertInterfaceToMap(values interface{}, withIgnoredField bool) map[string
func (scope *Scope) updatedAttrsWithValues(value interface{}) (results map[string]interface{}, hasUpdate bool) { func (scope *Scope) updatedAttrsWithValues(value interface{}) (results map[string]interface{}, hasUpdate bool) {
if scope.IndirectValue().Kind() != reflect.Struct { if scope.IndirectValue().Kind() != reflect.Struct {
return convertInterfaceToMap(value, false), true return convertInterfaceToMap(value, false, scope.db), true
} }
results = map[string]interface{}{} results = map[string]interface{}{}
for key, value := range convertInterfaceToMap(value, true) { for key, value := range convertInterfaceToMap(value, true, scope.db) {
if field, ok := scope.FieldByName(key); ok && scope.changeableField(field) { if field, ok := scope.FieldByName(key); ok && scope.changeableField(field) {
if _, ok := value.(*expr); ok { if _, ok := value.(*expr); ok {
hasUpdate = true hasUpdate = true
@ -984,6 +984,10 @@ func (scope *Scope) pluck(column string, value interface{}) *Scope {
return scope return scope
} }
if dest.Len() > 0 {
dest.Set(reflect.Zero(dest.Type()))
}
if query, ok := scope.Search.selects["query"]; !ok || !scope.isQueryForColumn(query, column) { if query, ok := scope.Search.selects["query"]; !ok || !scope.isQueryForColumn(query, column) {
scope.Search.Select(column) scope.Search.Select(column)
} }
@ -1007,8 +1011,15 @@ func (scope *Scope) pluck(column string, value interface{}) *Scope {
func (scope *Scope) count(value interface{}) *Scope { func (scope *Scope) count(value interface{}) *Scope {
if query, ok := scope.Search.selects["query"]; !ok || !countingQueryRegexp.MatchString(fmt.Sprint(query)) { if query, ok := scope.Search.selects["query"]; !ok || !countingQueryRegexp.MatchString(fmt.Sprint(query)) {
if len(scope.Search.group) != 0 { if len(scope.Search.group) != 0 {
scope.Search.Select("count(*) FROM ( SELECT count(*) as name ") if len(scope.Search.havingConditions) != 0 {
scope.Search.group += " ) AS count_table" scope.prepareQuerySQL()
scope.Search = &search{}
scope.Search.Select("count(*)")
scope.Search.Table(fmt.Sprintf("( %s ) AS count_table", scope.SQL))
} else {
scope.Search.Select("count(*) FROM ( SELECT count(*) as name ")
scope.Search.group += " ) AS count_table"
}
} else { } else {
scope.Search.Select("count(*)") scope.Search.Select("count(*)")
} }
@ -1188,7 +1199,7 @@ func (scope *Scope) createTable() *Scope {
} }
func (scope *Scope) dropTable() *Scope { func (scope *Scope) dropTable() *Scope {
scope.Raw(fmt.Sprintf("DROP TABLE %v%s", scope.QuotedTableName(), scope.getTableOptions())).Exec() scope.Raw(fmt.Sprintf("DROP TABLE %v", scope.QuotedTableName())).Exec()
return scope return scope
} }
@ -1282,7 +1293,8 @@ func (scope *Scope) autoIndex() *Scope {
if name == "INDEX" || name == "" { if name == "INDEX" || name == "" {
name = scope.Dialect().BuildKeyName("idx", scope.TableName(), field.DBName) name = scope.Dialect().BuildKeyName("idx", scope.TableName(), field.DBName)
} }
indexes[name] = append(indexes[name], field.DBName) name, column := scope.Dialect().NormalizeIndexAndColumn(name, field.DBName)
indexes[name] = append(indexes[name], column)
} }
} }
@ -1293,7 +1305,8 @@ func (scope *Scope) autoIndex() *Scope {
if name == "UNIQUE_INDEX" || name == "" { if name == "UNIQUE_INDEX" || name == "" {
name = scope.Dialect().BuildKeyName("uix", scope.TableName(), field.DBName) name = scope.Dialect().BuildKeyName("uix", scope.TableName(), field.DBName)
} }
uniqueIndexes[name] = append(uniqueIndexes[name], field.DBName) name, column := scope.Dialect().NormalizeIndexAndColumn(name, field.DBName)
uniqueIndexes[name] = append(uniqueIndexes[name], column)
} }
} }
} }

View File

@ -78,3 +78,16 @@ func TestFailedValuer(t *testing.T) {
t.Errorf("The error should be returned from Valuer, but get %v", err) t.Errorf("The error should be returned from Valuer, but get %v", err)
} }
} }
func TestDropTableWithTableOptions(t *testing.T) {
type UserWithOptions struct {
gorm.Model
}
DB.AutoMigrate(&UserWithOptions{})
DB = DB.Set("gorm:table_options", "CHARSET=utf8")
err := DB.DropTable(&UserWithOptions{}).Error
if err != nil {
t.Errorf("Table must be dropped, got error %s", err)
}
}

View File

@ -83,7 +83,7 @@ build:
code: | code: |
cd $WERCKER_SOURCE_DIR cd $WERCKER_SOURCE_DIR
go version go version
go get -t ./... go get -t -v ./...
# Build the project # Build the project
- script: - script:
@ -95,54 +95,60 @@ build:
- script: - script:
name: test sqlite name: test sqlite
code: | code: |
go test ./... go test -race -v ./...
- script: - script:
name: test mariadb name: test mariadb
code: | code: |
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mariadb:3306)/gorm?charset=utf8&parseTime=True" go test ./... GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mariadb:3306)/gorm?charset=utf8&parseTime=True" go test -race ./...
- script: - script:
name: test mysql5.7 name: test mysql5.7
code: | code: |
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql57:3306)/gorm?charset=utf8&parseTime=True" go test ./... GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql57:3306)/gorm?charset=utf8&parseTime=True" go test -race ./...
- script: - script:
name: test mysql5.6 name: test mysql5.6
code: | code: |
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql56:3306)/gorm?charset=utf8&parseTime=True" go test ./... GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql56:3306)/gorm?charset=utf8&parseTime=True" go test -race ./...
- script: - script:
name: test mysql5.5 name: test mysql5.5
code: | code: |
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql55:3306)/gorm?charset=utf8&parseTime=True" go test ./... GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql55:3306)/gorm?charset=utf8&parseTime=True" go test -race ./...
- script: - script:
name: test postgres name: test postgres
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test postgres96 name: test postgres96
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres96 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres96 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test postgres95 name: test postgres95
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres95 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres95 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test postgres94 name: test postgres94
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres94 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres94 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test postgres93 name: test postgres93
code: | code: |
GORM_DIALECT=postgres GORM_DSN="host=postgres93 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./... GORM_DIALECT=postgres GORM_DSN="host=postgres93 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test -race ./...
- script: - script:
name: test mssql name: test mssql
code: | code: |
GORM_DIALECT=mssql GORM_DSN="sqlserver://gorm:LoremIpsum86@mssql:1433?database=gorm" go test ./... GORM_DIALECT=mssql GORM_DSN="sqlserver://gorm:LoremIpsum86@mssql:1433?database=gorm" go test -race ./...
- script:
name: codecov
code: |
go test -race -coverprofile=coverage.txt -covermode=atomic ./...
bash <(curl -s https://codecov.io/bash)