mapper.go 5.3 KB

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