You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
5.4 KiB

  1. // Copyright 2019 The Xorm Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package names
  5. import (
  6. "strings"
  7. "sync"
  8. "unsafe"
  9. )
  10. // Mapper represents a name convertation between struct's fields name and table's column name
  11. type Mapper interface {
  12. Obj2Table(string) string
  13. Table2Obj(string) string
  14. }
  15. type CacheMapper struct {
  16. oriMapper Mapper
  17. obj2tableCache map[string]string
  18. obj2tableMutex sync.RWMutex
  19. table2objCache map[string]string
  20. table2objMutex sync.RWMutex
  21. }
  22. func NewCacheMapper(mapper Mapper) *CacheMapper {
  23. return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string),
  24. table2objCache: make(map[string]string),
  25. }
  26. }
  27. func (m *CacheMapper) Obj2Table(o string) string {
  28. m.obj2tableMutex.RLock()
  29. t, ok := m.obj2tableCache[o]
  30. m.obj2tableMutex.RUnlock()
  31. if ok {
  32. return t
  33. }
  34. t = m.oriMapper.Obj2Table(o)
  35. m.obj2tableMutex.Lock()
  36. m.obj2tableCache[o] = t
  37. m.obj2tableMutex.Unlock()
  38. return t
  39. }
  40. func (m *CacheMapper) Table2Obj(t string) string {
  41. m.table2objMutex.RLock()
  42. o, ok := m.table2objCache[t]
  43. m.table2objMutex.RUnlock()
  44. if ok {
  45. return o
  46. }
  47. o = m.oriMapper.Table2Obj(t)
  48. m.table2objMutex.Lock()
  49. m.table2objCache[t] = o
  50. m.table2objMutex.Unlock()
  51. return o
  52. }
  53. // SameMapper implements IMapper and provides same name between struct and
  54. // database table
  55. type SameMapper struct {
  56. }
  57. func (m SameMapper) Obj2Table(o string) string {
  58. return o
  59. }
  60. func (m SameMapper) Table2Obj(t string) string {
  61. return t
  62. }
  63. // SnakeMapper implements IMapper and provides name transaltion between
  64. // struct and database table
  65. type SnakeMapper struct {
  66. }
  67. func b2s(b []byte) string {
  68. return *(*string)(unsafe.Pointer(&b))
  69. }
  70. func snakeCasedName(name string) string {
  71. newstr := make([]byte, 0, len(name)+1)
  72. for i := 0; i < len(name); i++ {
  73. c := name[i]
  74. if isUpper := 'A' <= c && c <= 'Z'; isUpper {
  75. if i > 0 {
  76. newstr = append(newstr, '_')
  77. }
  78. c += 'a' - 'A'
  79. }
  80. newstr = append(newstr, c)
  81. }
  82. return b2s(newstr)
  83. }
  84. func (mapper SnakeMapper) Obj2Table(name string) string {
  85. return snakeCasedName(name)
  86. }
  87. func titleCasedName(name string) string {
  88. newstr := make([]byte, 0, len(name))
  89. upNextChar := true
  90. name = strings.ToLower(name)
  91. for i := 0; i < len(name); i++ {
  92. c := name[i]
  93. switch {
  94. case upNextChar:
  95. upNextChar = false
  96. if 'a' <= c && c <= 'z' {
  97. c -= 'a' - 'A'
  98. }
  99. case c == '_':
  100. upNextChar = true
  101. continue
  102. }
  103. newstr = append(newstr, c)
  104. }
  105. return b2s(newstr)
  106. }
  107. func (mapper SnakeMapper) Table2Obj(name string) string {
  108. return titleCasedName(name)
  109. }
  110. // GonicMapper implements IMapper. It will consider initialisms when mapping names.
  111. // E.g. id -> ID, user -> User and to table names: UserID -> user_id, MyUID -> my_uid
  112. type GonicMapper map[string]bool
  113. func isASCIIUpper(r rune) bool {
  114. return 'A' <= r && r <= 'Z'
  115. }
  116. func toASCIIUpper(r rune) rune {
  117. if 'a' <= r && r <= 'z' {
  118. r -= ('a' - 'A')
  119. }
  120. return r
  121. }
  122. func gonicCasedName(name string) string {
  123. newstr := make([]rune, 0, len(name)+3)
  124. for idx, chr := range name {
  125. if isASCIIUpper(chr) && idx > 0 {
  126. if !isASCIIUpper(newstr[len(newstr)-1]) {
  127. newstr = append(newstr, '_')
  128. }
  129. }
  130. if !isASCIIUpper(chr) && idx > 1 {
  131. l := len(newstr)
  132. if isASCIIUpper(newstr[l-1]) && isASCIIUpper(newstr[l-2]) {
  133. newstr = append(newstr, newstr[l-1])
  134. newstr[l-1] = '_'
  135. }
  136. }
  137. newstr = append(newstr, chr)
  138. }
  139. return strings.ToLower(string(newstr))
  140. }
  141. func (mapper GonicMapper) Obj2Table(name string) string {
  142. return gonicCasedName(name)
  143. }
  144. func (mapper GonicMapper) Table2Obj(name string) string {
  145. newstr := make([]rune, 0)
  146. name = strings.ToLower(name)
  147. parts := strings.Split(name, "_")
  148. for _, p := range parts {
  149. _, isInitialism := mapper[strings.ToUpper(p)]
  150. for i, r := range p {
  151. if i == 0 || isInitialism {
  152. r = toASCIIUpper(r)
  153. }
  154. newstr = append(newstr, r)
  155. }
  156. }
  157. return string(newstr)
  158. }
  159. // LintGonicMapper is A GonicMapper that contains a list of common initialisms taken from golang/lint
  160. var LintGonicMapper = GonicMapper{
  161. "API": true,
  162. "ASCII": true,
  163. "CPU": true,
  164. "CSS": true,
  165. "DNS": true,
  166. "EOF": true,
  167. "GUID": true,
  168. "HTML": true,
  169. "HTTP": true,
  170. "HTTPS": true,
  171. "ID": true,
  172. "IP": true,
  173. "JSON": true,
  174. "LHS": true,
  175. "QPS": true,
  176. "RAM": true,
  177. "RHS": true,
  178. "RPC": true,
  179. "SLA": true,
  180. "SMTP": true,
  181. "SSH": true,
  182. "TLS": true,
  183. "TTL": true,
  184. "UI": true,
  185. "UID": true,
  186. "UUID": true,
  187. "URI": true,
  188. "URL": true,
  189. "UTF8": true,
  190. "VM": true,
  191. "XML": true,
  192. "XSRF": true,
  193. "XSS": true,
  194. }
  195. // PrefixMapper provides prefix table name support
  196. type PrefixMapper struct {
  197. Mapper Mapper
  198. Prefix string
  199. }
  200. func (mapper PrefixMapper) Obj2Table(name string) string {
  201. return mapper.Prefix + mapper.Mapper.Obj2Table(name)
  202. }
  203. func (mapper PrefixMapper) Table2Obj(name string) string {
  204. return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):])
  205. }
  206. func NewPrefixMapper(mapper Mapper, prefix string) PrefixMapper {
  207. return PrefixMapper{mapper, prefix}
  208. }
  209. // SuffixMapper provides suffix table name support
  210. type SuffixMapper struct {
  211. Mapper Mapper
  212. Suffix string
  213. }
  214. func (mapper SuffixMapper) Obj2Table(name string) string {
  215. return mapper.Mapper.Obj2Table(name) + mapper.Suffix
  216. }
  217. func (mapper SuffixMapper) Table2Obj(name string) string {
  218. return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)])
  219. }
  220. func NewSuffixMapper(mapper Mapper, suffix string) SuffixMapper {
  221. return SuffixMapper{mapper, suffix}
  222. }