123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
- //
- // This Source Code Form is subject to the terms of the MIT License.
- // If a copy of the MIT was not distributed with this file,
- // You can obtain one at https://github.com/gogf/gf.
- package gconv
- import (
- "database/sql"
- "reflect"
- "github.com/gogf/gf/v2/errors/gcode"
- "github.com/gogf/gf/v2/errors/gerror"
- "github.com/gogf/gf/v2/internal/utils"
- "github.com/gogf/gf/v2/os/gstructs"
- )
- // Scan automatically checks the type of `pointer` and converts `params` to `pointer`. It supports `pointer`
- // with type of `*map/*[]map/*[]*map/*struct/**struct/*[]struct/*[]*struct` for converting.
- //
- // It calls function `doMapToMap` internally if `pointer` is type of *map for converting.
- // It calls function `doMapToMaps` internally if `pointer` is type of *[]map/*[]*map for converting.
- // It calls function `doStruct` internally if `pointer` is type of *struct/**struct for converting.
- // It calls function `doStructs` internally if `pointer` is type of *[]struct/*[]*struct for converting.
- func Scan(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
- var (
- pointerType reflect.Type
- pointerKind reflect.Kind
- pointerValue reflect.Value
- )
- if v, ok := pointer.(reflect.Value); ok {
- pointerValue = v
- pointerType = v.Type()
- } else {
- pointerValue = reflect.ValueOf(pointer)
- pointerType = reflect.TypeOf(pointer) // Do not use pointerValue.Type() as pointerValue might be zero.
- }
- if pointerType == nil {
- return gerror.NewCode(gcode.CodeInvalidParameter, "parameter pointer should not be nil")
- }
- pointerKind = pointerType.Kind()
- if pointerKind != reflect.Ptr {
- if pointerValue.CanAddr() {
- pointerValue = pointerValue.Addr()
- pointerType = pointerValue.Type()
- pointerKind = pointerType.Kind()
- } else {
- return gerror.NewCodef(
- gcode.CodeInvalidParameter,
- "params should be type of pointer, but got type: %v",
- pointerType,
- )
- }
- }
- // Direct assignment checks!
- var (
- paramsType reflect.Type
- paramsValue reflect.Value
- )
- if v, ok := params.(reflect.Value); ok {
- paramsValue = v
- paramsType = paramsValue.Type()
- } else {
- paramsValue = reflect.ValueOf(params)
- paramsType = reflect.TypeOf(params) // Do not use paramsValue.Type() as paramsValue might be zero.
- }
- // If `params` and `pointer` are the same type, the do directly assignment.
- // For performance enhancement purpose.
- var (
- pointerValueElem = pointerValue.Elem()
- )
- if pointerValueElem.CanSet() && paramsType == pointerValueElem.Type() {
- pointerValueElem.Set(paramsValue)
- return nil
- }
- // Converting.
- var (
- pointerElem = pointerType.Elem()
- pointerElemKind = pointerElem.Kind()
- keyToAttributeNameMapping map[string]string
- )
- if len(paramKeyToAttrMap) > 0 {
- keyToAttributeNameMapping = paramKeyToAttrMap[0]
- }
- switch pointerElemKind {
- case reflect.Map:
- return doMapToMap(params, pointer, paramKeyToAttrMap...)
- case reflect.Array, reflect.Slice:
- var (
- sliceElem = pointerElem.Elem()
- sliceElemKind = sliceElem.Kind()
- )
- for sliceElemKind == reflect.Ptr {
- sliceElem = sliceElem.Elem()
- sliceElemKind = sliceElem.Kind()
- }
- if sliceElemKind == reflect.Map {
- return doMapToMaps(params, pointer, paramKeyToAttrMap...)
- }
- return doStructs(params, pointer, keyToAttributeNameMapping, "")
- default:
- return doStruct(params, pointer, keyToAttributeNameMapping, "")
- }
- }
- // ScanList converts `structSlice` to struct slice which contains other complex struct attributes.
- // Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
- //
- // Usage example 1: Normal attribute struct relation:
- //
- // type EntityUser struct {
- // Uid int
- // Name string
- // }
- //
- // type EntityUserDetail struct {
- // Uid int
- // Address string
- // }
- //
- // type EntityUserScores struct {
- // Id int
- // Uid int
- // Score int
- // Course string
- // }
- //
- // type Entity struct {
- // User *EntityUser
- // UserDetail *EntityUserDetail
- // UserScores []*EntityUserScores
- // }
- //
- // var users []*Entity
- // var userRecords = EntityUser{Uid: 1, Name:"john"}
- // var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
- // var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
- // ScanList(userRecords, &users, "User")
- // ScanList(userRecords, &users, "User", "uid")
- // ScanList(detailRecords, &users, "UserDetail", "User", "uid:Uid")
- // ScanList(scoresRecords, &users, "UserScores", "User", "uid:Uid")
- // ScanList(scoresRecords, &users, "UserScores", "User", "uid")
- //
- // Usage example 2: Embedded attribute struct relation:
- //
- // type EntityUser struct {
- // Uid int
- // Name string
- // }
- //
- // type EntityUserDetail struct {
- // Uid int
- // Address string
- // }
- //
- // type EntityUserScores struct {
- // Id int
- // Uid int
- // Score int
- // }
- //
- // type Entity struct {
- // EntityUser
- // UserDetail EntityUserDetail
- // UserScores []EntityUserScores
- // }
- //
- // var userRecords = EntityUser{Uid: 1, Name:"john"}
- // var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
- // var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
- // ScanList(userRecords, &users)
- // ScanList(detailRecords, &users, "UserDetail", "uid")
- // ScanList(scoresRecords, &users, "UserScores", "uid")
- //
- // The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct
- // that current result will be bound to.
- //
- // The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
- // struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
- // name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
- // given `relation` parameter.
- //
- // See the example or unit testing cases for clear understanding for this function.
- func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
- var (
- relationAttrName string
- relationFields string
- )
- switch len(relationAttrNameAndFields) {
- case 2:
- relationAttrName = relationAttrNameAndFields[0]
- relationFields = relationAttrNameAndFields[1]
- case 1:
- relationFields = relationAttrNameAndFields[0]
- }
- return doScanList(structSlice, structSlicePointer, bindToAttrName, relationAttrName, relationFields)
- }
- // doScanList converts `structSlice` to struct slice which contains other complex struct attributes recursively.
- // Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
- func doScanList(
- structSlice interface{}, structSlicePointer interface{}, bindToAttrName, relationAttrName, relationFields string,
- ) (err error) {
- var (
- maps = Maps(structSlice)
- )
- if len(maps) == 0 {
- return nil
- }
- // Necessary checks for parameters.
- if bindToAttrName == "" {
- return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
- }
- if relationAttrName == "." {
- relationAttrName = ""
- }
- var (
- reflectValue = reflect.ValueOf(structSlicePointer)
- reflectKind = reflectValue.Kind()
- )
- if reflectKind == reflect.Interface {
- reflectValue = reflectValue.Elem()
- reflectKind = reflectValue.Kind()
- }
- if reflectKind != reflect.Ptr {
- return gerror.NewCodef(
- gcode.CodeInvalidParameter,
- "structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
- reflectKind,
- )
- }
- reflectValue = reflectValue.Elem()
- reflectKind = reflectValue.Kind()
- if reflectKind != reflect.Slice && reflectKind != reflect.Array {
- return gerror.NewCodef(
- gcode.CodeInvalidParameter,
- "structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
- reflectKind,
- )
- }
- length := len(maps)
- if length == 0 {
- // The pointed slice is not empty.
- if reflectValue.Len() > 0 {
- // It here checks if it has struct item, which is already initialized.
- // It then returns error to warn the developer its empty and no conversion.
- if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
- return sql.ErrNoRows
- }
- }
- // Do nothing for empty struct slice.
- return nil
- }
- var (
- arrayValue reflect.Value // Like: []*Entity
- arrayItemType reflect.Type // Like: *Entity
- reflectType = reflect.TypeOf(structSlicePointer)
- )
- if reflectValue.Len() > 0 {
- arrayValue = reflectValue
- } else {
- arrayValue = reflect.MakeSlice(reflectType.Elem(), length, length)
- }
- // Slice element item.
- arrayItemType = arrayValue.Index(0).Type()
- // Relation variables.
- var (
- relationDataMap map[string]interface{}
- relationFromFieldName string // Eg: relationKV: id:uid -> id
- relationBindToFieldName string // Eg: relationKV: id:uid -> uid
- )
- if len(relationFields) > 0 {
- // The relation key string of table field name and attribute name
- // can be joined with char '=' or ':'.
- array := utils.SplitAndTrim(relationFields, "=")
- if len(array) == 1 {
- // Compatible with old splitting char ':'.
- array = utils.SplitAndTrim(relationFields, ":")
- }
- if len(array) == 1 {
- // The relation names are the same.
- array = []string{relationFields, relationFields}
- }
- if len(array) == 2 {
- // Defined table field to relation attribute name.
- // Like:
- // uid:Uid
- // uid:UserId
- relationFromFieldName = array[0]
- relationBindToFieldName = array[1]
- if key, _ := utils.MapPossibleItemByKey(maps[0], relationFromFieldName); key == "" {
- return gerror.NewCodef(
- gcode.CodeInvalidParameter,
- `cannot find possible related table field name "%s" from given relation fields "%s"`,
- relationFromFieldName,
- relationFields,
- )
- } else {
- relationFromFieldName = key
- }
- } else {
- return gerror.NewCode(
- gcode.CodeInvalidParameter,
- `parameter relationKV should be format of "ResultFieldName:BindToAttrName"`,
- )
- }
- if relationFromFieldName != "" {
- // Note that the value might be type of slice.
- relationDataMap = utils.ListToMapByKey(maps, relationFromFieldName)
- }
- if len(relationDataMap) == 0 {
- return gerror.NewCodef(
- gcode.CodeInvalidParameter,
- `cannot find the relation data map, maybe invalid relation fields given "%v"`,
- relationFields,
- )
- }
- }
- // Bind to target attribute.
- var (
- ok bool
- bindToAttrValue reflect.Value
- bindToAttrKind reflect.Kind
- bindToAttrType reflect.Type
- bindToAttrField reflect.StructField
- )
- if arrayItemType.Kind() == reflect.Ptr {
- if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
- return gerror.NewCodef(
- gcode.CodeInvalidParameter,
- `invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
- bindToAttrName,
- )
- }
- } else {
- if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
- return gerror.NewCodef(
- gcode.CodeInvalidParameter,
- `invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
- bindToAttrName,
- )
- }
- }
- bindToAttrType = bindToAttrField.Type
- bindToAttrKind = bindToAttrType.Kind()
- // Bind to relation conditions.
- var (
- relationFromAttrValue reflect.Value
- relationFromAttrField reflect.Value
- relationBindToFieldNameChecked bool
- )
- for i := 0; i < arrayValue.Len(); i++ {
- arrayElemValue := arrayValue.Index(i)
- // The FieldByName should be called on non-pointer reflect.Value.
- if arrayElemValue.Kind() == reflect.Ptr {
- // Like: []*Entity
- arrayElemValue = arrayElemValue.Elem()
- if !arrayElemValue.IsValid() {
- // The element is nil, then create one and set it to the slice.
- // The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
- // For example:
- // reflect.New(itemType.Elem()) => *Entity
- // reflect.New(itemType.Elem()).Elem() => Entity
- arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
- arrayValue.Index(i).Set(arrayElemValue.Addr())
- }
- } else {
- // Like: []Entity
- }
- bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName)
- if relationAttrName != "" {
- // Attribute value of current slice element.
- relationFromAttrValue = arrayElemValue.FieldByName(relationAttrName)
- if relationFromAttrValue.Kind() == reflect.Ptr {
- relationFromAttrValue = relationFromAttrValue.Elem()
- }
- } else {
- // Current slice element.
- relationFromAttrValue = arrayElemValue
- }
- if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
- return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
- }
- // Check and find possible bind to attribute name.
- if relationFields != "" && !relationBindToFieldNameChecked {
- relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
- if !relationFromAttrField.IsValid() {
- var (
- fieldMap, _ = gstructs.FieldMap(gstructs.FieldMapInput{
- Pointer: relationFromAttrValue,
- RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
- })
- )
- if key, _ := utils.MapPossibleItemByKey(Map(fieldMap), relationBindToFieldName); key == "" {
- return gerror.NewCodef(
- gcode.CodeInvalidParameter,
- `cannot find possible related attribute name "%s" from given relation fields "%s"`,
- relationBindToFieldName,
- relationFields,
- )
- } else {
- relationBindToFieldName = key
- }
- }
- relationBindToFieldNameChecked = true
- }
- switch bindToAttrKind {
- case reflect.Array, reflect.Slice:
- if len(relationDataMap) > 0 {
- relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
- if relationFromAttrField.IsValid() {
- // results := make(Result, 0)
- results := make([]interface{}, 0)
- for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) {
- item := v
- results = append(results, item)
- }
- if err = Structs(results, bindToAttrValue.Addr()); err != nil {
- return err
- }
- } else {
- // Maybe the attribute does not exist yet.
- return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
- }
- } else {
- return gerror.NewCodef(
- gcode.CodeInvalidParameter,
- `relationKey should not be empty as field "%s" is slice`,
- bindToAttrName,
- )
- }
- case reflect.Ptr:
- var element reflect.Value
- if bindToAttrValue.IsNil() {
- element = reflect.New(bindToAttrType.Elem()).Elem()
- } else {
- element = bindToAttrValue.Elem()
- }
- if len(relationDataMap) > 0 {
- relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
- if relationFromAttrField.IsValid() {
- v := relationDataMap[String(relationFromAttrField.Interface())]
- if v == nil {
- // There's no relational data.
- continue
- }
- if utils.IsSlice(v) {
- if err = Struct(SliceAny(v)[0], element); err != nil {
- return err
- }
- } else {
- if err = Struct(v, element); err != nil {
- return err
- }
- }
- } else {
- // Maybe the attribute does not exist yet.
- return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
- }
- } else {
- if i >= len(maps) {
- // There's no relational data.
- continue
- }
- v := maps[i]
- if v == nil {
- // There's no relational data.
- continue
- }
- if err = Struct(v, element); err != nil {
- return err
- }
- }
- bindToAttrValue.Set(element.Addr())
- case reflect.Struct:
- if len(relationDataMap) > 0 {
- relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
- if relationFromAttrField.IsValid() {
- relationDataItem := relationDataMap[String(relationFromAttrField.Interface())]
- if relationDataItem == nil {
- // There's no relational data.
- continue
- }
- if utils.IsSlice(relationDataItem) {
- if err = Struct(SliceAny(relationDataItem)[0], bindToAttrValue); err != nil {
- return err
- }
- } else {
- if err = Struct(relationDataItem, bindToAttrValue); err != nil {
- return err
- }
- }
- } else {
- // Maybe the attribute does not exist yet.
- return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
- }
- } else {
- if i >= len(maps) {
- // There's no relational data.
- continue
- }
- relationDataItem := maps[i]
- if relationDataItem == nil {
- // There's no relational data.
- continue
- }
- if err = Struct(relationDataItem, bindToAttrValue); err != nil {
- return err
- }
- }
- default:
- return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
- }
- }
- reflect.ValueOf(structSlicePointer).Elem().Set(arrayValue)
- return nil
- }
|