gdb_model_with.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package gdb
  7. import (
  8. "fmt"
  9. "github.com/gogf/gf/errors/gcode"
  10. "reflect"
  11. "github.com/gogf/gf/errors/gerror"
  12. "github.com/gogf/gf/internal/structs"
  13. "github.com/gogf/gf/internal/utils"
  14. "github.com/gogf/gf/text/gregex"
  15. "github.com/gogf/gf/text/gstr"
  16. )
  17. // With creates and returns an ORM model based on metadata of given object.
  18. // It also enables model association operations feature on given `object`.
  19. // It can be called multiple times to add one or more objects to model and enable
  20. // their mode association operations feature.
  21. // For example, if given struct definition:
  22. // type User struct {
  23. // gmeta.Meta `orm:"table:user"`
  24. // Id int `json:"id"`
  25. // Name string `json:"name"`
  26. // UserDetail *UserDetail `orm:"with:uid=id"`
  27. // UserScores []*UserScores `orm:"with:uid=id"`
  28. // }
  29. // We can enable model association operations on attribute `UserDetail` and `UserScores` by:
  30. // db.With(User{}.UserDetail).With(User{}.UserDetail).Scan(xxx)
  31. // Or:
  32. // db.With(UserDetail{}).With(UserDetail{}).Scan(xxx)
  33. // Or:
  34. // db.With(UserDetail{}, UserDetail{}).Scan(xxx)
  35. func (m *Model) With(objects ...interface{}) *Model {
  36. model := m.getModel()
  37. for _, object := range objects {
  38. if m.tables == "" {
  39. m.tablesInit = m.db.GetCore().QuotePrefixTableName(
  40. getTableNameFromOrmTag(object),
  41. )
  42. m.tables = m.tablesInit
  43. return model
  44. }
  45. model.withArray = append(model.withArray, object)
  46. }
  47. return model
  48. }
  49. // WithAll enables model association operations on all objects that have "with" tag in the struct.
  50. func (m *Model) WithAll() *Model {
  51. model := m.getModel()
  52. model.withAll = true
  53. return model
  54. }
  55. // doWithScanStruct handles model association operations feature for single struct.
  56. func (m *Model) doWithScanStruct(pointer interface{}) error {
  57. var (
  58. err error
  59. allowedTypeStrArray = make([]string, 0)
  60. )
  61. currentStructFieldMap, err := structs.FieldMap(structs.FieldMapInput{
  62. Pointer: pointer,
  63. PriorityTagArray: nil,
  64. RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
  65. })
  66. if err != nil {
  67. return err
  68. }
  69. // It checks the with array and automatically calls the ScanList to complete association querying.
  70. if !m.withAll {
  71. for _, field := range currentStructFieldMap {
  72. for _, withItem := range m.withArray {
  73. withItemReflectValueType, err := structs.StructType(withItem)
  74. if err != nil {
  75. return err
  76. }
  77. var (
  78. fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
  79. withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]")
  80. )
  81. // It does select operation if the field type is in the specified "with" type array.
  82. if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
  83. allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr)
  84. }
  85. }
  86. }
  87. }
  88. for _, field := range currentStructFieldMap {
  89. var (
  90. fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
  91. parsedTagOutput = m.parseWithTagInFieldStruct(field)
  92. )
  93. if parsedTagOutput.With == "" {
  94. continue
  95. }
  96. // It just handlers "with" type attribute struct, so it ignores other struct types.
  97. if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
  98. continue
  99. }
  100. array := gstr.SplitAndTrim(parsedTagOutput.With, "=")
  101. if len(array) == 1 {
  102. // It also supports using only one column name
  103. // if both tables associates using the same column name.
  104. array = append(array, parsedTagOutput.With)
  105. }
  106. var (
  107. model *Model
  108. fieldKeys []string
  109. relatedSourceName = array[0]
  110. relatedTargetName = array[1]
  111. relatedTargetValue interface{}
  112. )
  113. // Find the value of related attribute from `pointer`.
  114. for attributeName, attributeValue := range currentStructFieldMap {
  115. if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) {
  116. relatedTargetValue = attributeValue.Value.Interface()
  117. break
  118. }
  119. }
  120. if relatedTargetValue == nil {
  121. return gerror.NewCodef(
  122. gcode.CodeInvalidParameter,
  123. `cannot find the target related value of name "%s" in with tag "%s" for attribute "%s.%s"`,
  124. relatedTargetName, parsedTagOutput.With, reflect.TypeOf(pointer).Elem(), field.Name(),
  125. )
  126. }
  127. bindToReflectValue := field.Value
  128. if bindToReflectValue.Kind() != reflect.Ptr && bindToReflectValue.CanAddr() {
  129. bindToReflectValue = bindToReflectValue.Addr()
  130. }
  131. // It automatically retrieves struct field names from current attribute struct/slice.
  132. if structType, err := structs.StructType(field.Value); err != nil {
  133. return err
  134. } else {
  135. fieldKeys = structType.FieldKeys()
  136. }
  137. // Recursively with feature checks.
  138. model = m.db.With(field.Value)
  139. if m.withAll {
  140. model = model.WithAll()
  141. } else {
  142. model = model.With(m.withArray...)
  143. }
  144. if parsedTagOutput.Where != "" {
  145. model = model.Where(parsedTagOutput.Where)
  146. }
  147. if parsedTagOutput.Order != "" {
  148. model = model.Order(parsedTagOutput.Order)
  149. }
  150. err = model.Fields(fieldKeys).Where(relatedSourceName, relatedTargetValue).Scan(bindToReflectValue)
  151. if err != nil {
  152. return err
  153. }
  154. }
  155. return nil
  156. }
  157. // doWithScanStructs handles model association operations feature for struct slice.
  158. // Also see doWithScanStruct.
  159. func (m *Model) doWithScanStructs(pointer interface{}) error {
  160. if v, ok := pointer.(reflect.Value); ok {
  161. pointer = v.Interface()
  162. }
  163. var (
  164. err error
  165. allowedTypeStrArray = make([]string, 0)
  166. )
  167. currentStructFieldMap, err := structs.FieldMap(structs.FieldMapInput{
  168. Pointer: pointer,
  169. PriorityTagArray: nil,
  170. RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
  171. })
  172. if err != nil {
  173. return err
  174. }
  175. // It checks the with array and automatically calls the ScanList to complete association querying.
  176. if !m.withAll {
  177. for _, field := range currentStructFieldMap {
  178. for _, withItem := range m.withArray {
  179. withItemReflectValueType, err := structs.StructType(withItem)
  180. if err != nil {
  181. return err
  182. }
  183. var (
  184. fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
  185. withItemReflectValueTypeStr = gstr.TrimAll(withItemReflectValueType.String(), "*[]")
  186. )
  187. // It does select operation if the field type is in the specified with type array.
  188. if gstr.Compare(fieldTypeStr, withItemReflectValueTypeStr) == 0 {
  189. allowedTypeStrArray = append(allowedTypeStrArray, fieldTypeStr)
  190. }
  191. }
  192. }
  193. }
  194. for fieldName, field := range currentStructFieldMap {
  195. var (
  196. fieldTypeStr = gstr.TrimAll(field.Type().String(), "*[]")
  197. parsedTagOutput = m.parseWithTagInFieldStruct(field)
  198. )
  199. if parsedTagOutput.With == "" {
  200. continue
  201. }
  202. if !m.withAll && !gstr.InArray(allowedTypeStrArray, fieldTypeStr) {
  203. continue
  204. }
  205. array := gstr.SplitAndTrim(parsedTagOutput.With, "=")
  206. if len(array) == 1 {
  207. // It supports using only one column name
  208. // if both tables associates using the same column name.
  209. array = append(array, parsedTagOutput.With)
  210. }
  211. var (
  212. model *Model
  213. fieldKeys []string
  214. relatedSourceName = array[0]
  215. relatedTargetName = array[1]
  216. relatedTargetValue interface{}
  217. )
  218. // Find the value slice of related attribute from `pointer`.
  219. for attributeName, _ := range currentStructFieldMap {
  220. if utils.EqualFoldWithoutChars(attributeName, relatedTargetName) {
  221. relatedTargetValue = ListItemValuesUnique(pointer, attributeName)
  222. break
  223. }
  224. }
  225. if relatedTargetValue == nil {
  226. return gerror.NewCodef(
  227. gcode.CodeInvalidParameter,
  228. `cannot find the related value for attribute name "%s" of with tag "%s"`,
  229. relatedTargetName, parsedTagOutput.With,
  230. )
  231. }
  232. // It automatically retrieves struct field names from current attribute struct/slice.
  233. if structType, err := structs.StructType(field.Value); err != nil {
  234. return err
  235. } else {
  236. fieldKeys = structType.FieldKeys()
  237. }
  238. // Recursively with feature checks.
  239. model = m.db.With(field.Value)
  240. if m.withAll {
  241. model = model.WithAll()
  242. } else {
  243. model = model.With(m.withArray...)
  244. }
  245. if parsedTagOutput.Where != "" {
  246. model = model.Where(parsedTagOutput.Where)
  247. }
  248. if parsedTagOutput.Order != "" {
  249. model = model.Order(parsedTagOutput.Order)
  250. }
  251. err = model.Fields(fieldKeys).
  252. Where(relatedSourceName, relatedTargetValue).
  253. ScanList(pointer, fieldName, parsedTagOutput.With)
  254. if err != nil {
  255. return err
  256. }
  257. }
  258. return nil
  259. }
  260. type parseWithTagInFieldStructOutput struct {
  261. With string
  262. Where string
  263. Order string
  264. }
  265. func (m *Model) parseWithTagInFieldStruct(field *structs.Field) (output parseWithTagInFieldStructOutput) {
  266. var (
  267. match []string
  268. ormTag = field.Tag(OrmTagForStruct)
  269. )
  270. // with tag.
  271. match, _ = gregex.MatchString(
  272. fmt.Sprintf(`%s\s*:\s*([^,]+),{0,1}`, OrmTagForWith),
  273. ormTag,
  274. )
  275. if len(match) > 1 {
  276. output.With = match[1]
  277. }
  278. if len(match) > 2 {
  279. output.Where = gstr.Trim(match[2])
  280. }
  281. // where string.
  282. match, _ = gregex.MatchString(
  283. fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithWhere),
  284. ormTag,
  285. )
  286. if len(match) > 1 {
  287. output.Where = gstr.Trim(match[1])
  288. }
  289. // order string.
  290. match, _ = gregex.MatchString(
  291. fmt.Sprintf(`%s\s*:.+,\s*%s:\s*([^,]+),{0,1}`, OrmTagForWith, OrmTagForWithOrder),
  292. ormTag,
  293. )
  294. if len(match) > 1 {
  295. output.Order = gstr.Trim(match[1])
  296. }
  297. return
  298. }