github.com/systematiccaos/gorm@v1.22.6/schema/naming.go (about) 1 package schema 2 3 import ( 4 "crypto/sha1" 5 "encoding/hex" 6 "fmt" 7 "regexp" 8 "strings" 9 "unicode/utf8" 10 11 "github.com/jinzhu/inflection" 12 ) 13 14 // Namer namer interface 15 type Namer interface { 16 TableName(table string) string 17 SchemaName(table string) string 18 ColumnName(table, column string) string 19 JoinTableName(joinTable string) string 20 RelationshipFKName(Relationship) string 21 CheckerName(table, column string) string 22 IndexName(table, column string) string 23 } 24 25 // Replacer replacer interface like strings.Replacer 26 type Replacer interface { 27 Replace(name string) string 28 } 29 30 // NamingStrategy tables, columns naming strategy 31 type NamingStrategy struct { 32 TablePrefix string 33 SingularTable bool 34 NameReplacer Replacer 35 NoLowerCase bool 36 } 37 38 // TableName convert string to table name 39 func (ns NamingStrategy) TableName(str string) string { 40 if ns.SingularTable { 41 return ns.TablePrefix + ns.toDBName(str) 42 } 43 return ns.TablePrefix + inflection.Plural(ns.toDBName(str)) 44 } 45 46 // SchemaName generate schema name from table name, don't guarantee it is the reverse value of TableName 47 func (ns NamingStrategy) SchemaName(table string) string { 48 table = strings.TrimPrefix(table, ns.TablePrefix) 49 50 if ns.SingularTable { 51 return ns.toSchemaName(table) 52 } 53 return ns.toSchemaName(inflection.Singular(table)) 54 } 55 56 // ColumnName convert string to column name 57 func (ns NamingStrategy) ColumnName(table, column string) string { 58 return ns.toDBName(column) 59 } 60 61 // JoinTableName convert string to join table name 62 func (ns NamingStrategy) JoinTableName(str string) string { 63 if !ns.NoLowerCase && strings.ToLower(str) == str { 64 return ns.TablePrefix + str 65 } 66 67 if ns.SingularTable { 68 return ns.TablePrefix + ns.toDBName(str) 69 } 70 return ns.TablePrefix + inflection.Plural(ns.toDBName(str)) 71 } 72 73 // RelationshipFKName generate fk name for relation 74 func (ns NamingStrategy) RelationshipFKName(rel Relationship) string { 75 return ns.formatName("fk", rel.Schema.Table, ns.toDBName(rel.Name)) 76 } 77 78 // CheckerName generate checker name 79 func (ns NamingStrategy) CheckerName(table, column string) string { 80 return ns.formatName("chk", table, column) 81 } 82 83 // IndexName generate index name 84 func (ns NamingStrategy) IndexName(table, column string) string { 85 return ns.formatName("idx", table, ns.toDBName(column)) 86 } 87 88 func (ns NamingStrategy) formatName(prefix, table, name string) string { 89 formattedName := strings.Replace(strings.Join([]string{ 90 prefix, table, name, 91 }, "_"), ".", "_", -1) 92 93 if utf8.RuneCountInString(formattedName) > 64 { 94 h := sha1.New() 95 h.Write([]byte(formattedName)) 96 bs := h.Sum(nil) 97 98 formattedName = fmt.Sprintf("%v%v%v", prefix, table, name)[0:56] + hex.EncodeToString(bs)[:8] 99 } 100 return formattedName 101 } 102 103 var ( 104 // https://github.com/golang/lint/blob/master/lint.go#L770 105 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"} 106 commonInitialismsReplacer *strings.Replacer 107 ) 108 109 func init() { 110 commonInitialismsForReplacer := make([]string, 0, len(commonInitialisms)) 111 for _, initialism := range commonInitialisms { 112 commonInitialismsForReplacer = append(commonInitialismsForReplacer, initialism, strings.Title(strings.ToLower(initialism))) 113 } 114 commonInitialismsReplacer = strings.NewReplacer(commonInitialismsForReplacer...) 115 } 116 117 func (ns NamingStrategy) toDBName(name string) string { 118 if name == "" { 119 return "" 120 } 121 122 if ns.NameReplacer != nil { 123 name = ns.NameReplacer.Replace(name) 124 } 125 126 if ns.NoLowerCase { 127 return name 128 } 129 130 var ( 131 value = commonInitialismsReplacer.Replace(name) 132 buf strings.Builder 133 lastCase, nextCase, nextNumber bool // upper case == true 134 curCase = value[0] <= 'Z' && value[0] >= 'A' 135 ) 136 137 for i, v := range value[:len(value)-1] { 138 nextCase = value[i+1] <= 'Z' && value[i+1] >= 'A' 139 nextNumber = value[i+1] >= '0' && value[i+1] <= '9' 140 141 if curCase { 142 if lastCase && (nextCase || nextNumber) { 143 buf.WriteRune(v + 32) 144 } else { 145 if i > 0 && value[i-1] != '_' && value[i+1] != '_' { 146 buf.WriteByte('_') 147 } 148 buf.WriteRune(v + 32) 149 } 150 } else { 151 buf.WriteRune(v) 152 } 153 154 lastCase = curCase 155 curCase = nextCase 156 } 157 158 if curCase { 159 if !lastCase && len(value) > 1 { 160 buf.WriteByte('_') 161 } 162 buf.WriteByte(value[len(value)-1] + 32) 163 } else { 164 buf.WriteByte(value[len(value)-1]) 165 } 166 ret := buf.String() 167 return ret 168 } 169 170 func (ns NamingStrategy) toSchemaName(name string) string { 171 result := strings.Replace(strings.Title(strings.Replace(name, "_", " ", -1)), " ", "", -1) 172 for _, initialism := range commonInitialisms { 173 result = regexp.MustCompile(strings.Title(strings.ToLower(initialism))+"([A-Z]|$|_)").ReplaceAllString(result, initialism+"$1") 174 } 175 return result 176 }