Merge pull request #1029 from nkovacs/mysql-foreign-key-length-fix

Mysql foreign key length fix
This commit is contained in:
Jinzhu 2016-05-22 08:00:40 +08:00
commit 4c9c024939
7 changed files with 69 additions and 3 deletions

View File

@ -2,6 +2,7 @@ package gorm_test
import ( import (
"fmt" "fmt"
"os"
"reflect" "reflect"
"sort" "sort"
"testing" "testing"
@ -840,3 +841,26 @@ func TestForeignKey(t *testing.T) {
} }
} }
} }
func TestLongForeignKey(t *testing.T) {
if dialect := os.Getenv("GORM_DIALECT"); dialect == "" || dialect == "sqlite" {
// sqlite does not support ADD CONSTRAINT in ALTER TABLE
return
}
targetScope := DB.NewScope(&ReallyLongTableNameToTestMySQLNameLengthLimit{})
targetTableName := targetScope.TableName()
modelScope := DB.NewScope(&NotSoLongTableName{})
modelField, ok := modelScope.FieldByName("ReallyLongThingID")
if !ok {
t.Fatalf("Failed to get field by name: ReallyLongThingID")
}
targetField, ok := targetScope.FieldByName("ID")
if !ok {
t.Fatalf("Failed to get field by name: ID")
}
dest := fmt.Sprintf("%v(%v)", targetTableName, targetField.DBName)
err := DB.Model(&NotSoLongTableName{}).AddForeignKey(modelField.DBName, dest, "CASCADE", "CASCADE").Error
if err != nil {
t.Fatalf(fmt.Sprintf("Failed to create foreign key: %v", err))
}
}

View File

@ -40,6 +40,9 @@ type Dialect interface {
SelectFromDummyTable() string SelectFromDummyTable() string
// LastInsertIdReturningSuffix most dbs support LastInsertId, but postgres needs to use `RETURNING` // LastInsertIdReturningSuffix most dbs support LastInsertId, but postgres needs to use `RETURNING`
LastInsertIDReturningSuffix(tableName, columnName string) string LastInsertIDReturningSuffix(tableName, columnName string) string
// BuildForeignKeyName returns a foreign key name for the given table, field and reference
BuildForeignKeyName(tableName, field, dest string) string
} }
var dialectsMap = map[string]Dialect{} var dialectsMap = map[string]Dialect{}

View File

@ -4,12 +4,18 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"reflect" "reflect"
"regexp"
"strings" "strings"
"time" "time"
) )
// DefaultForeignKeyNamer contains the default foreign key name generator method
type DefaultForeignKeyNamer struct {
}
type commonDialect struct { type commonDialect struct {
db *sql.DB db *sql.DB
DefaultForeignKeyNamer
} }
func init() { func init() {
@ -135,3 +141,9 @@ func (commonDialect) SelectFromDummyTable() string {
func (commonDialect) LastInsertIDReturningSuffix(tableName, columnName string) string { func (commonDialect) LastInsertIDReturningSuffix(tableName, columnName string) string {
return "" return ""
} }
func (DefaultForeignKeyNamer) BuildForeignKeyName(tableName, field, dest string) string {
keyName := fmt.Sprintf("%s_%s_%s_foreign", tableName, field, dest)
keyName = regexp.MustCompile("(_*[^a-zA-Z]+_*|_+)").ReplaceAllString(keyName, "_")
return keyName
}

View File

@ -1,8 +1,10 @@
package gorm package gorm
import ( import (
"crypto/sha1"
"fmt" "fmt"
"reflect" "reflect"
"regexp"
"strings" "strings"
"time" "time"
) )
@ -115,3 +117,18 @@ func (s mysql) currentDatabase() (name string) {
func (mysql) SelectFromDummyTable() string { func (mysql) SelectFromDummyTable() string {
return "FROM DUAL" return "FROM DUAL"
} }
func (s mysql) BuildForeignKeyName(tableName, field, dest string) string {
keyName := s.commonDialect.BuildForeignKeyName(tableName, field, dest)
if len(keyName) <= 64 {
return keyName
}
h := sha1.New()
h.Write([]byte(keyName))
bs := h.Sum(nil)
// sha1 is 40 digits, keep first 24 characters of destination
keyName = regexp.MustCompile("(_*[^a-zA-Z]+_*|_+)").ReplaceAllString(dest, "_")
return fmt.Sprintf("%s%x", keyName[:24], bs)
}

View File

@ -24,6 +24,7 @@ func init() {
type mssql struct { type mssql struct {
db *sql.DB db *sql.DB
gorm.DefaultForeignKeyNamer
} }
func (mssql) GetName() string { func (mssql) GetName() string {

View File

@ -39,6 +39,16 @@ type User struct {
IgnoredPointer *User `sql:"-"` IgnoredPointer *User `sql:"-"`
} }
type NotSoLongTableName struct {
Id int64
ReallyLongThingID int64
ReallyLongThing ReallyLongTableNameToTestMySQLNameLengthLimit
}
type ReallyLongTableNameToTestMySQLNameLengthLimit struct {
Id int64
}
type CreditCard struct { type CreditCard struct {
ID int8 ID int8
Number string Number string
@ -231,7 +241,7 @@ func runMigration() {
DB.Exec(fmt.Sprintf("drop table %v;", table)) DB.Exec(fmt.Sprintf("drop table %v;", table))
} }
values := []interface{}{&Product{}, &Email{}, &Address{}, &CreditCard{}, &Company{}, &Role{}, &Language{}, &HNPost{}, &EngadgetPost{}, &Animal{}, &User{}, &JoinTable{}, &Post{}, &Category{}, &Comment{}, &Cat{}, &Dog{}, &Toy{}} values := []interface{}{&ReallyLongTableNameToTestMySQLNameLengthLimit{}, &NotSoLongTableName{}, &Product{}, &Email{}, &Address{}, &CreditCard{}, &Company{}, &Role{}, &Language{}, &HNPost{}, &EngadgetPost{}, &Animal{}, &User{}, &JoinTable{}, &Post{}, &Category{}, &Comment{}, &Cat{}, &Dog{}, &Toy{}}
for _, value := range values { for _, value := range values {
DB.DropTable(value) DB.DropTable(value)
} }

View File

@ -1117,8 +1117,7 @@ func (scope *Scope) addIndex(unique bool, indexName string, column ...string) {
} }
func (scope *Scope) addForeignKey(field string, dest string, onDelete string, onUpdate string) { func (scope *Scope) addForeignKey(field string, dest string, onDelete string, onUpdate string) {
var keyName = fmt.Sprintf("%s_%s_%s_foreign", scope.TableName(), field, dest) keyName := scope.Dialect().BuildForeignKeyName(scope.TableName(), field, dest)
keyName = regexp.MustCompile("(_*[^a-zA-Z]+_*|_+)").ReplaceAllString(keyName, "_")
if scope.Dialect().HasForeignKey(scope.TableName(), keyName) { if scope.Dialect().HasForeignKey(scope.TableName(), keyName) {
return return