From bc68fde6aa9892b734cdbd569bb22d58e9493f46 Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Fri, 31 Jan 2020 14:17:02 +0800 Subject: [PATCH] Implement naming strategy --- go.mod | 2 + go.sum | 2 + gorm.go | 12 ++++-- schema/naming.go | 96 +++++++++++++++++++++++++++++++++++++++++++ schema/naming_test.go | 34 +++++++++++++++ 5 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 go.sum create mode 100644 schema/naming.go create mode 100644 schema/naming_test.go diff --git a/go.mod b/go.mod index d0a110ba..516a9759 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/jinzhu/gorm go 1.13 + +require github.com/jinzhu/inflection v1.0.0 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..a310b071 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= diff --git a/gorm.go b/gorm.go index 838f2862..6ceac412 100644 --- a/gorm.go +++ b/gorm.go @@ -6,18 +6,18 @@ import ( "github.com/jinzhu/gorm/clause" "github.com/jinzhu/gorm/logger" + "github.com/jinzhu/gorm/schema" ) // Config GORM config type Config struct { - // Set true to use singular table name, by default, GORM will pluralize your struct's name as table name - // Refer https://github.com/jinzhu/inflection for inflection rules - SingularTable bool - // GORM perform single create, update, delete operations in transactions by default to ensure database data integrity // You can cancel it by setting `SkipDefaultTransaction` to true SkipDefaultTransaction bool + // NamingStrategy tables, columns naming strategy + NamingStrategy schema.Namer + // Logger Logger logger.Interface @@ -48,6 +48,10 @@ type Session struct { // Open initialize db session based on dialector func Open(dialector Dialector, config *Config) (db *DB, err error) { + if config.NamingStrategy == nil { + config.NamingStrategy = schema.NamingStrategy{} + } + return &DB{ Config: config, Dialector: dialector, diff --git a/schema/naming.go b/schema/naming.go new file mode 100644 index 00000000..1baa8558 --- /dev/null +++ b/schema/naming.go @@ -0,0 +1,96 @@ +package schema + +import ( + "fmt" + "strings" + "sync" + + "github.com/jinzhu/inflection" +) + +// Namer namer interface +type Namer interface { + TableName(string) string + ColumnName(string) string +} + +// NamingStrategy tables, columns naming strategy +type NamingStrategy struct { + TablePrefix string + SingularTable bool +} + +// TableName convert string to table name +func (ns NamingStrategy) TableName(str string) string { + if ns.SingularTable { + return ns.TablePrefix + toDBName(str) + } + return ns.TablePrefix + inflection.Plural(toDBName(str)) +} + +// ColumnName convert string to column name +func (ns NamingStrategy) ColumnName(str string) string { + return toDBName(str) +} + +var ( + smap sync.Map + // https://github.com/golang/lint/blob/master/lint.go#L770 + commonInitialisms = []string{"API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SSH", "TLS", "TTL", "UID", "UI", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XSRF", "XSS"} + commonInitialismsReplacer *strings.Replacer +) + +func init() { + var commonInitialismsForReplacer []string + for _, initialism := range commonInitialisms { + commonInitialismsForReplacer = append(commonInitialismsForReplacer, initialism, strings.Title(strings.ToLower(initialism))) + } + commonInitialismsReplacer = strings.NewReplacer(commonInitialismsForReplacer...) +} + +func toDBName(name string) string { + if name == "" { + return "" + } else if v, ok := smap.Load(name); ok { + return fmt.Sprint(v) + } + + var ( + value = commonInitialismsReplacer.Replace(name) + buf strings.Builder + lastCase, nextCase, nextNumber bool // upper case == true + curCase = value[0] <= 'Z' && value[0] >= 'A' + ) + + for i, v := range value[:len(value)-1] { + nextCase = value[i+1] <= 'Z' && value[i+1] >= 'A' + nextNumber = value[i+1] >= '0' && value[i+1] <= '9' + + if curCase { + if lastCase && (nextCase || nextNumber) { + buf.WriteRune(v + 32) + } else { + if i > 0 && value[i-1] != '_' && value[i+1] != '_' { + buf.WriteByte('_') + } + buf.WriteRune(v + 32) + } + } else { + buf.WriteRune(v) + } + + lastCase = curCase + curCase = nextCase + } + + if curCase { + if !lastCase && len(value) > 1 { + buf.WriteByte('_') + } + buf.WriteByte(value[len(value)-1] + 32) + } else { + buf.WriteByte(value[len(value)-1]) + } + + return buf.String() +} diff --git a/schema/naming_test.go b/schema/naming_test.go new file mode 100644 index 00000000..96b83ced --- /dev/null +++ b/schema/naming_test.go @@ -0,0 +1,34 @@ +package schema + +import ( + "testing" +) + +func TestToDBName(t *testing.T) { + var maps = map[string]string{ + "": "", + "x": "x", + "X": "x", + "userRestrictions": "user_restrictions", + "ThisIsATest": "this_is_a_test", + "PFAndESI": "pf_and_esi", + "AbcAndJkl": "abc_and_jkl", + "EmployeeID": "employee_id", + "SKU_ID": "sku_id", + "FieldX": "field_x", + "HTTPAndSMTP": "http_and_smtp", + "HTTPServerHandlerForURLID": "http_server_handler_for_url_id", + "UUID": "uuid", + "HTTPURL": "http_url", + "HTTP_URL": "http_url", + "SHA256Hash": "sha256_hash", + "SHA256HASH": "sha256_hash", + "ThisIsActuallyATestSoWeMayBeAbleToUseThisCodeInGormPackageAlsoIdCanBeUsedAtTheEndAsID": "this_is_actually_a_test_so_we_may_be_able_to_use_this_code_in_gorm_package_also_id_can_be_used_at_the_end_as_id", + } + + for key, value := range maps { + if toDBName(key) != value { + t.Errorf("%v toName should equal %v, but got %v", key, value, toDBName(key)) + } + } +}