gorm/schema/naming.go

195 lines
5.2 KiB
Go
Raw Normal View History

2020-01-31 09:17:02 +03:00
package schema
import (
2020-02-21 18:51:38 +03:00
"crypto/sha1"
"encoding/hex"
2021-08-11 11:20:21 +03:00
"regexp"
2020-01-31 09:17:02 +03:00
"strings"
2020-02-21 18:51:38 +03:00
"unicode/utf8"
2020-01-31 09:17:02 +03:00
2020-06-02 04:25:55 +03:00
"github.com/jinzhu/inflection"
2020-01-31 09:17:02 +03:00
)
// Namer namer interface
type Namer interface {
TableName(table string) string
2021-08-11 11:20:21 +03:00
SchemaName(table string) string
ColumnName(table, column string) string
JoinTableName(joinTable string) string
2020-02-22 06:15:51 +03:00
RelationshipFKName(Relationship) string
CheckerName(table, column string) string
IndexName(table, column string) string
UniqueName(table, column string) string
2020-01-31 09:17:02 +03:00
}
// Replacer replacer interface like strings.Replacer
type Replacer interface {
Replace(name string) string
}
var _ Namer = (*NamingStrategy)(nil)
2020-01-31 09:17:02 +03:00
// NamingStrategy tables, columns naming strategy
type NamingStrategy struct {
max identifier length changed to 63 (#6337) * max identifier length changed to 63 * default maxIdentifierLength is 64 * renamed License to LICENSE (#6336) * Added support of "Violates Foreign Key Constraint" (#6329) * Added support of "Violates Foreign Key Constraint" Updated the translator and added the support of "foreign key constraint violation". For this, this error type is needed here. * changed the description of ErrForeignKeyViolated * refactor: error translator test (#6350) Co-authored-by: Saeid Saeidee <s.saeidee@sensysgatso.com> * fix(nested transaction): SavePoint SQL Statement not support in Prepared Statements (#6220) * test: add nested transaction and prepareStmt coexist test case note: please test in the MySQL environment Change-Id: I0db32adc5f74b0d443e98943d3b182236583b959 Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> * fix(nested transaction): SavePoint SQL Statement not support in Prepared Statements 1. SavetPoint SQL Statement not support in Prepared Statements e.g. see mysql8.0 doc: https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html Change-Id: I082012db9b140e8ec69764c633724665cc802692 Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> * revert(transaction_api): remove savepoint name pool,meaningless Change-Id: I84aa9924fc54612005a81c83d66fdf8968ee56ad Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> --------- Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> Co-authored-by: 王柳洋 <wangliuyang.520@bytedance.com> * fix: save with hook (#6285) (#6294) --------- Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> Co-authored-by: Avinaba Bhattacharjee <avinababhattacharjee2002@gmail.com> Co-authored-by: Muhammad Amir Ejaz <37077032+codingamir@users.noreply.github.com> Co-authored-by: Saeid <sk.saeidee@yahoo.com> Co-authored-by: Saeid Saeidee <s.saeidee@sensysgatso.com> Co-authored-by: wangliuyang <54885906+wangliuyang520@users.noreply.github.com> Co-authored-by: 王柳洋 <wangliuyang.520@bytedance.com> Co-authored-by: black-06 <hello.bug@foxmail.com>
2023-05-30 05:00:48 +03:00
TablePrefix string
SingularTable bool
NameReplacer Replacer
NoLowerCase bool
IdentifierMaxLength int
2020-01-31 09:17:02 +03:00
}
// TableName convert string to table name
func (ns NamingStrategy) TableName(str string) string {
if ns.SingularTable {
return ns.TablePrefix + ns.toDBName(str)
2020-01-31 09:17:02 +03:00
}
return ns.TablePrefix + inflection.Plural(ns.toDBName(str))
2020-01-31 09:17:02 +03:00
}
2021-08-11 11:20:21 +03:00
// SchemaName generate schema name from table name, don't guarantee it is the reverse value of TableName
func (ns NamingStrategy) SchemaName(table string) string {
table = strings.TrimPrefix(table, ns.TablePrefix)
if ns.SingularTable {
return ns.toSchemaName(table)
}
return ns.toSchemaName(inflection.Singular(table))
}
2020-01-31 09:17:02 +03:00
// ColumnName convert string to column name
2020-02-21 18:51:38 +03:00
func (ns NamingStrategy) ColumnName(table, column string) string {
return ns.toDBName(column)
2020-02-21 18:51:38 +03:00
}
2020-02-22 06:15:51 +03:00
// JoinTableName convert string to join table name
func (ns NamingStrategy) JoinTableName(str string) string {
if !ns.NoLowerCase && strings.ToLower(str) == str {
2020-09-28 05:55:27 +03:00
return ns.TablePrefix + str
2020-09-06 05:51:21 +03:00
}
2020-06-30 02:29:15 +03:00
if ns.SingularTable {
return ns.TablePrefix + ns.toDBName(str)
2020-06-30 02:29:15 +03:00
}
return ns.TablePrefix + inflection.Plural(ns.toDBName(str))
2020-02-22 06:15:51 +03:00
}
// RelationshipFKName generate fk name for relation
func (ns NamingStrategy) RelationshipFKName(rel Relationship) string {
return ns.formatName("fk", rel.Schema.Table, ns.toDBName(rel.Name))
2020-02-22 06:15:51 +03:00
}
// CheckerName generate checker name
func (ns NamingStrategy) CheckerName(table, column string) string {
return ns.formatName("chk", table, column)
2020-02-22 06:15:51 +03:00
}
// IndexName generate index name
2020-02-21 18:51:38 +03:00
func (ns NamingStrategy) IndexName(table, column string) string {
return ns.formatName("idx", table, ns.toDBName(column))
}
// UniqueName generate unique constraint name
func (ns NamingStrategy) UniqueName(table, column string) string {
return ns.formatName("uni", table, ns.toDBName(column))
}
func (ns NamingStrategy) formatName(prefix, table, name string) string {
formattedName := strings.ReplaceAll(strings.Join([]string{
prefix, table, name,
}, "_"), ".", "_")
2020-02-21 18:51:38 +03:00
max identifier length changed to 63 (#6337) * max identifier length changed to 63 * default maxIdentifierLength is 64 * renamed License to LICENSE (#6336) * Added support of "Violates Foreign Key Constraint" (#6329) * Added support of "Violates Foreign Key Constraint" Updated the translator and added the support of "foreign key constraint violation". For this, this error type is needed here. * changed the description of ErrForeignKeyViolated * refactor: error translator test (#6350) Co-authored-by: Saeid Saeidee <s.saeidee@sensysgatso.com> * fix(nested transaction): SavePoint SQL Statement not support in Prepared Statements (#6220) * test: add nested transaction and prepareStmt coexist test case note: please test in the MySQL environment Change-Id: I0db32adc5f74b0d443e98943d3b182236583b959 Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> * fix(nested transaction): SavePoint SQL Statement not support in Prepared Statements 1. SavetPoint SQL Statement not support in Prepared Statements e.g. see mysql8.0 doc: https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html Change-Id: I082012db9b140e8ec69764c633724665cc802692 Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> * revert(transaction_api): remove savepoint name pool,meaningless Change-Id: I84aa9924fc54612005a81c83d66fdf8968ee56ad Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> --------- Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> Co-authored-by: 王柳洋 <wangliuyang.520@bytedance.com> * fix: save with hook (#6285) (#6294) --------- Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> Co-authored-by: Avinaba Bhattacharjee <avinababhattacharjee2002@gmail.com> Co-authored-by: Muhammad Amir Ejaz <37077032+codingamir@users.noreply.github.com> Co-authored-by: Saeid <sk.saeidee@yahoo.com> Co-authored-by: Saeid Saeidee <s.saeidee@sensysgatso.com> Co-authored-by: wangliuyang <54885906+wangliuyang520@users.noreply.github.com> Co-authored-by: 王柳洋 <wangliuyang.520@bytedance.com> Co-authored-by: black-06 <hello.bug@foxmail.com>
2023-05-30 05:00:48 +03:00
if ns.IdentifierMaxLength == 0 {
ns.IdentifierMaxLength = 64
}
if utf8.RuneCountInString(formattedName) > ns.IdentifierMaxLength {
2020-02-21 18:51:38 +03:00
h := sha1.New()
2021-04-19 16:03:39 +03:00
h.Write([]byte(formattedName))
2020-02-21 18:51:38 +03:00
bs := h.Sum(nil)
max identifier length changed to 63 (#6337) * max identifier length changed to 63 * default maxIdentifierLength is 64 * renamed License to LICENSE (#6336) * Added support of "Violates Foreign Key Constraint" (#6329) * Added support of "Violates Foreign Key Constraint" Updated the translator and added the support of "foreign key constraint violation". For this, this error type is needed here. * changed the description of ErrForeignKeyViolated * refactor: error translator test (#6350) Co-authored-by: Saeid Saeidee <s.saeidee@sensysgatso.com> * fix(nested transaction): SavePoint SQL Statement not support in Prepared Statements (#6220) * test: add nested transaction and prepareStmt coexist test case note: please test in the MySQL environment Change-Id: I0db32adc5f74b0d443e98943d3b182236583b959 Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> * fix(nested transaction): SavePoint SQL Statement not support in Prepared Statements 1. SavetPoint SQL Statement not support in Prepared Statements e.g. see mysql8.0 doc: https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html Change-Id: I082012db9b140e8ec69764c633724665cc802692 Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> * revert(transaction_api): remove savepoint name pool,meaningless Change-Id: I84aa9924fc54612005a81c83d66fdf8968ee56ad Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> --------- Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> Co-authored-by: 王柳洋 <wangliuyang.520@bytedance.com> * fix: save with hook (#6285) (#6294) --------- Signed-off-by: 王柳洋 <wangliuyang.520@bytedance.com> Co-authored-by: Avinaba Bhattacharjee <avinababhattacharjee2002@gmail.com> Co-authored-by: Muhammad Amir Ejaz <37077032+codingamir@users.noreply.github.com> Co-authored-by: Saeid <sk.saeidee@yahoo.com> Co-authored-by: Saeid Saeidee <s.saeidee@sensysgatso.com> Co-authored-by: wangliuyang <54885906+wangliuyang520@users.noreply.github.com> Co-authored-by: 王柳洋 <wangliuyang.520@bytedance.com> Co-authored-by: black-06 <hello.bug@foxmail.com>
2023-05-30 05:00:48 +03:00
formattedName = formattedName[0:ns.IdentifierMaxLength-8] + hex.EncodeToString(bs)[:8]
2020-02-21 18:51:38 +03:00
}
2021-04-19 16:03:39 +03:00
return formattedName
2020-01-31 09:17:02 +03:00
}
var (
// 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() {
commonInitialismsForReplacer := make([]string, 0, len(commonInitialisms))
2020-01-31 09:17:02 +03:00
for _, initialism := range commonInitialisms {
commonInitialismsForReplacer = append(commonInitialismsForReplacer, initialism, strings.Title(strings.ToLower(initialism)))
}
commonInitialismsReplacer = strings.NewReplacer(commonInitialismsForReplacer...)
}
func (ns NamingStrategy) toDBName(name string) string {
2020-01-31 09:17:02 +03:00
if name == "" {
return ""
}
if ns.NameReplacer != nil {
tmpName := ns.NameReplacer.Replace(name)
if tmpName == "" {
return name
}
name = tmpName
}
if ns.NoLowerCase {
return name
}
2020-01-31 09:17:02 +03:00
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])
}
ret := buf.String()
return ret
2020-01-31 09:17:02 +03:00
}
2021-08-11 11:20:21 +03:00
func (ns NamingStrategy) toSchemaName(name string) string {
result := strings.ReplaceAll(strings.Title(strings.ReplaceAll(name, "_", " ")), " ", "")
2021-08-11 11:20:21 +03:00
for _, initialism := range commonInitialisms {
result = regexp.MustCompile(strings.Title(strings.ToLower(initialism))+"([A-Z]|$|_)").ReplaceAllString(result, initialism+"$1")
}
return result
}