// Copyright 2019 The Xorm Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package core import ( "strings" "sync" ) // IMapper represents a name convertation between struct's fields name and table's column name type IMapper interface { Obj2Table(string) string Table2Obj(string) string } type CacheMapper struct { oriMapper IMapper obj2tableCache map[string]string obj2tableMutex sync.RWMutex table2objCache map[string]string table2objMutex sync.RWMutex } func NewCacheMapper(mapper IMapper) *CacheMapper { return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string), table2objCache: make(map[string]string), } } func (m *CacheMapper) Obj2Table(o string) string { m.obj2tableMutex.RLock() t, ok := m.obj2tableCache[o] m.obj2tableMutex.RUnlock() if ok { return t } t = m.oriMapper.Obj2Table(o) m.obj2tableMutex.Lock() m.obj2tableCache[o] = t m.obj2tableMutex.Unlock() return t } func (m *CacheMapper) Table2Obj(t string) string { m.table2objMutex.RLock() o, ok := m.table2objCache[t] m.table2objMutex.RUnlock() if ok { return o } o = m.oriMapper.Table2Obj(t) m.table2objMutex.Lock() m.table2objCache[t] = o m.table2objMutex.Unlock() return o } // SameMapper implements IMapper and provides same name between struct and // database table type SameMapper struct { } func (m SameMapper) Obj2Table(o string) string { return o } func (m SameMapper) Table2Obj(t string) string { return t } // SnakeMapper implements IMapper and provides name transaltion between // struct and database table type SnakeMapper struct { } func snakeCasedName(name string) string { newstr := make([]rune, 0) for idx, chr := range name { if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { if idx > 0 { newstr = append(newstr, '_') } chr -= ('A' - 'a') } newstr = append(newstr, chr) } return string(newstr) } func (mapper SnakeMapper) Obj2Table(name string) string { return snakeCasedName(name) } func titleCasedName(name string) string { newstr := make([]rune, 0) upNextChar := true name = strings.ToLower(name) for _, chr := range name { switch { case upNextChar: upNextChar = false if 'a' <= chr && chr <= 'z' { chr -= ('a' - 'A') } case chr == '_': upNextChar = true continue } newstr = append(newstr, chr) } return string(newstr) } func (mapper SnakeMapper) Table2Obj(name string) string { return titleCasedName(name) } // GonicMapper implements IMapper. It will consider initialisms when mapping names. // E.g. id -> ID, user -> User and to table names: UserID -> user_id, MyUID -> my_uid type GonicMapper map[string]bool func isASCIIUpper(r rune) bool { return 'A' <= r && r <= 'Z' } func toASCIIUpper(r rune) rune { if 'a' <= r && r <= 'z' { r -= ('a' - 'A') } return r } func gonicCasedName(name string) string { newstr := make([]rune, 0, len(name)+3) for idx, chr := range name { if isASCIIUpper(chr) && idx > 0 { if !isASCIIUpper(newstr[len(newstr)-1]) { newstr = append(newstr, '_') } } if !isASCIIUpper(chr) && idx > 1 { l := len(newstr) if isASCIIUpper(newstr[l-1]) && isASCIIUpper(newstr[l-2]) { newstr = append(newstr, newstr[l-1]) newstr[l-1] = '_' } } newstr = append(newstr, chr) } return strings.ToLower(string(newstr)) } func (mapper GonicMapper) Obj2Table(name string) string { return gonicCasedName(name) } func (mapper GonicMapper) Table2Obj(name string) string { newstr := make([]rune, 0) name = strings.ToLower(name) parts := strings.Split(name, "_") for _, p := range parts { _, isInitialism := mapper[strings.ToUpper(p)] for i, r := range p { if i == 0 || isInitialism { r = toASCIIUpper(r) } newstr = append(newstr, r) } } return string(newstr) } // LintGonicMapper is A GonicMapper that contains a list of common initialisms taken from golang/lint var LintGonicMapper = GonicMapper{ "API": true, "ASCII": true, "CPU": true, "CSS": true, "DNS": true, "EOF": true, "GUID": true, "HTML": true, "HTTP": true, "HTTPS": true, "ID": true, "IP": true, "JSON": true, "LHS": true, "QPS": true, "RAM": true, "RHS": true, "RPC": true, "SLA": true, "SMTP": true, "SSH": true, "TLS": true, "TTL": true, "UI": true, "UID": true, "UUID": true, "URI": true, "URL": true, "UTF8": true, "VM": true, "XML": true, "XSRF": true, "XSS": true, } // PrefixMapper provides prefix table name support type PrefixMapper struct { Mapper IMapper Prefix string } func (mapper PrefixMapper) Obj2Table(name string) string { return mapper.Prefix + mapper.Mapper.Obj2Table(name) } func (mapper PrefixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) } func NewPrefixMapper(mapper IMapper, prefix string) PrefixMapper { return PrefixMapper{mapper, prefix} } // SuffixMapper provides suffix table name support type SuffixMapper struct { Mapper IMapper Suffix string } func (mapper SuffixMapper) Obj2Table(name string) string { return mapper.Mapper.Obj2Table(name) + mapper.Suffix } func (mapper SuffixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)]) } func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper { return SuffixMapper{mapper, suffix} }