github.com/wangyougui/gf/v2@v2.6.5/util/gconv/gconv_scan_list.go (about)

     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/wangyougui/gf.
     6  
     7  package gconv
     8  
     9  import (
    10  	"database/sql"
    11  	"reflect"
    12  
    13  	"github.com/wangyougui/gf/v2/errors/gcode"
    14  	"github.com/wangyougui/gf/v2/errors/gerror"
    15  	"github.com/wangyougui/gf/v2/internal/utils"
    16  	"github.com/wangyougui/gf/v2/os/gstructs"
    17  )
    18  
    19  // ScanList converts `structSlice` to struct slice which contains other complex struct attributes.
    20  // Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
    21  //
    22  // Usage example 1: Normal attribute struct relation:
    23  //
    24  //	type EntityUser struct {
    25  //	    Uid  int
    26  //	    Name string
    27  //	}
    28  //
    29  //	type EntityUserDetail struct {
    30  //	    Uid     int
    31  //	    Address string
    32  //	}
    33  //
    34  //	type EntityUserScores struct {
    35  //	    Id     int
    36  //	    Uid    int
    37  //	    Score  int
    38  //	    Course string
    39  //	}
    40  //
    41  //	type Entity struct {
    42  //	    User       *EntityUser
    43  //	    UserDetail *EntityUserDetail
    44  //	    UserScores []*EntityUserScores
    45  //	}
    46  //
    47  // var users []*Entity
    48  // var userRecords   = EntityUser{Uid: 1, Name:"john"}
    49  // var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
    50  // var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
    51  // ScanList(userRecords, &users, "User")
    52  // ScanList(userRecords, &users, "User", "uid")
    53  // ScanList(detailRecords, &users, "UserDetail", "User", "uid:Uid")
    54  // ScanList(scoresRecords, &users, "UserScores", "User", "uid:Uid")
    55  // ScanList(scoresRecords, &users, "UserScores", "User", "uid")
    56  //
    57  // Usage example 2: Embedded attribute struct relation:
    58  //
    59  //	type EntityUser struct {
    60  //		   Uid  int
    61  //		   Name string
    62  //	}
    63  //
    64  //	type EntityUserDetail struct {
    65  //		   Uid     int
    66  //		   Address string
    67  //	}
    68  //
    69  //	type EntityUserScores struct {
    70  //		   Id    int
    71  //		   Uid   int
    72  //		   Score int
    73  //	}
    74  //
    75  //	type Entity struct {
    76  //		   EntityUser
    77  //		   UserDetail EntityUserDetail
    78  //		   UserScores []EntityUserScores
    79  //	}
    80  //
    81  // var userRecords   = EntityUser{Uid: 1, Name:"john"}
    82  // var detailRecords = EntityUser{Uid: 1, Address: "chengdu"}
    83  // var scoresRecords = EntityUser{Id: 1, Uid: 1, Score: 100, Course: "math"}
    84  // ScanList(userRecords, &users)
    85  // ScanList(detailRecords, &users, "UserDetail", "uid")
    86  // ScanList(scoresRecords, &users, "UserScores", "uid")
    87  //
    88  // The parameters "User/UserDetail/UserScores" in the example codes specify the target attribute struct
    89  // that current result will be bound to.
    90  //
    91  // The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational
    92  // struct attribute name - not the attribute name of the bound to target. In the example codes, it's attribute
    93  // name "Uid" of "User" of entity "Entity". It automatically calculates the HasOne/HasMany relationship with
    94  // given `relation` parameter.
    95  //
    96  // See the example or unit testing cases for clear understanding for this function.
    97  func ScanList(structSlice interface{}, structSlicePointer interface{}, bindToAttrName string, relationAttrNameAndFields ...string) (err error) {
    98  	var (
    99  		relationAttrName string
   100  		relationFields   string
   101  	)
   102  	switch len(relationAttrNameAndFields) {
   103  	case 2:
   104  		relationAttrName = relationAttrNameAndFields[0]
   105  		relationFields = relationAttrNameAndFields[1]
   106  	case 1:
   107  		relationFields = relationAttrNameAndFields[0]
   108  	}
   109  	return doScanList(structSlice, structSlicePointer, bindToAttrName, relationAttrName, relationFields)
   110  }
   111  
   112  // doScanList converts `structSlice` to struct slice which contains other complex struct attributes recursively.
   113  // Note that the parameter `structSlicePointer` should be type of *[]struct/*[]*struct.
   114  func doScanList(
   115  	structSlice interface{}, structSlicePointer interface{}, bindToAttrName, relationAttrName, relationFields string,
   116  ) (err error) {
   117  	var (
   118  		maps = Maps(structSlice)
   119  	)
   120  	if len(maps) == 0 {
   121  		return nil
   122  	}
   123  	// Necessary checks for parameters.
   124  	if bindToAttrName == "" {
   125  		return gerror.NewCode(gcode.CodeInvalidParameter, `bindToAttrName should not be empty`)
   126  	}
   127  
   128  	if relationAttrName == "." {
   129  		relationAttrName = ""
   130  	}
   131  
   132  	var (
   133  		reflectValue = reflect.ValueOf(structSlicePointer)
   134  		reflectKind  = reflectValue.Kind()
   135  	)
   136  	if reflectKind == reflect.Interface {
   137  		reflectValue = reflectValue.Elem()
   138  		reflectKind = reflectValue.Kind()
   139  	}
   140  	if reflectKind != reflect.Ptr {
   141  		return gerror.NewCodef(
   142  			gcode.CodeInvalidParameter,
   143  			"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
   144  			reflectKind,
   145  		)
   146  	}
   147  	reflectValue = reflectValue.Elem()
   148  	reflectKind = reflectValue.Kind()
   149  	if reflectKind != reflect.Slice && reflectKind != reflect.Array {
   150  		return gerror.NewCodef(
   151  			gcode.CodeInvalidParameter,
   152  			"structSlicePointer should be type of *[]struct/*[]*struct, but got: %v",
   153  			reflectKind,
   154  		)
   155  	}
   156  	length := len(maps)
   157  	if length == 0 {
   158  		// The pointed slice is not empty.
   159  		if reflectValue.Len() > 0 {
   160  			// It here checks if it has struct item, which is already initialized.
   161  			// It then returns error to warn the developer its empty and no conversion.
   162  			if v := reflectValue.Index(0); v.Kind() != reflect.Ptr {
   163  				return sql.ErrNoRows
   164  			}
   165  		}
   166  		// Do nothing for empty struct slice.
   167  		return nil
   168  	}
   169  	var (
   170  		arrayValue    reflect.Value // Like: []*Entity
   171  		arrayItemType reflect.Type  // Like: *Entity
   172  		reflectType   = reflect.TypeOf(structSlicePointer)
   173  	)
   174  	if reflectValue.Len() > 0 {
   175  		arrayValue = reflectValue
   176  	} else {
   177  		arrayValue = reflect.MakeSlice(reflectType.Elem(), length, length)
   178  	}
   179  
   180  	// Slice element item.
   181  	arrayItemType = arrayValue.Index(0).Type()
   182  
   183  	// Relation variables.
   184  	var (
   185  		relationDataMap         map[string]interface{}
   186  		relationFromFieldName   string // Eg: relationKV: id:uid  -> id
   187  		relationBindToFieldName string // Eg: relationKV: id:uid  -> uid
   188  	)
   189  	if len(relationFields) > 0 {
   190  		// The relation key string of table field name and attribute name
   191  		// can be joined with char '=' or ':'.
   192  		array := utils.SplitAndTrim(relationFields, "=")
   193  		if len(array) == 1 {
   194  			// Compatible with old splitting char ':'.
   195  			array = utils.SplitAndTrim(relationFields, ":")
   196  		}
   197  		if len(array) == 1 {
   198  			// The relation names are the same.
   199  			array = []string{relationFields, relationFields}
   200  		}
   201  		if len(array) == 2 {
   202  			// Defined table field to relation attribute name.
   203  			// Like:
   204  			// uid:Uid
   205  			// uid:UserId
   206  			relationFromFieldName = array[0]
   207  			relationBindToFieldName = array[1]
   208  			if key, _ := utils.MapPossibleItemByKey(maps[0], relationFromFieldName); key == "" {
   209  				return gerror.NewCodef(
   210  					gcode.CodeInvalidParameter,
   211  					`cannot find possible related table field name "%s" from given relation fields "%s"`,
   212  					relationFromFieldName,
   213  					relationFields,
   214  				)
   215  			} else {
   216  				relationFromFieldName = key
   217  			}
   218  		} else {
   219  			return gerror.NewCode(
   220  				gcode.CodeInvalidParameter,
   221  				`parameter relationKV should be format of "ResultFieldName:BindToAttrName"`,
   222  			)
   223  		}
   224  		if relationFromFieldName != "" {
   225  			// Note that the value might be type of slice.
   226  			relationDataMap = utils.ListToMapByKey(maps, relationFromFieldName)
   227  		}
   228  		if len(relationDataMap) == 0 {
   229  			return gerror.NewCodef(
   230  				gcode.CodeInvalidParameter,
   231  				`cannot find the relation data map, maybe invalid relation fields given "%v"`,
   232  				relationFields,
   233  			)
   234  		}
   235  	}
   236  	// Bind to target attribute.
   237  	var (
   238  		ok              bool
   239  		bindToAttrValue reflect.Value
   240  		bindToAttrKind  reflect.Kind
   241  		bindToAttrType  reflect.Type
   242  		bindToAttrField reflect.StructField
   243  	)
   244  	if arrayItemType.Kind() == reflect.Ptr {
   245  		if bindToAttrField, ok = arrayItemType.Elem().FieldByName(bindToAttrName); !ok {
   246  			return gerror.NewCodef(
   247  				gcode.CodeInvalidParameter,
   248  				`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
   249  				bindToAttrName,
   250  			)
   251  		}
   252  	} else {
   253  		if bindToAttrField, ok = arrayItemType.FieldByName(bindToAttrName); !ok {
   254  			return gerror.NewCodef(
   255  				gcode.CodeInvalidParameter,
   256  				`invalid parameter bindToAttrName: cannot find attribute with name "%s" from slice element`,
   257  				bindToAttrName,
   258  			)
   259  		}
   260  	}
   261  	bindToAttrType = bindToAttrField.Type
   262  	bindToAttrKind = bindToAttrType.Kind()
   263  
   264  	// Bind to relation conditions.
   265  	var (
   266  		relationFromAttrValue          reflect.Value
   267  		relationFromAttrField          reflect.Value
   268  		relationBindToFieldNameChecked bool
   269  	)
   270  	for i := 0; i < arrayValue.Len(); i++ {
   271  		arrayElemValue := arrayValue.Index(i)
   272  		// The FieldByName should be called on non-pointer reflect.Value.
   273  		if arrayElemValue.Kind() == reflect.Ptr {
   274  			// Like: []*Entity
   275  			arrayElemValue = arrayElemValue.Elem()
   276  			if !arrayElemValue.IsValid() {
   277  				// The element is nil, then create one and set it to the slice.
   278  				// The "reflect.New(itemType.Elem())" creates a new element and returns the address of it.
   279  				// For example:
   280  				// reflect.New(itemType.Elem())        => *Entity
   281  				// reflect.New(itemType.Elem()).Elem() => Entity
   282  				arrayElemValue = reflect.New(arrayItemType.Elem()).Elem()
   283  				arrayValue.Index(i).Set(arrayElemValue.Addr())
   284  			}
   285  		} else {
   286  			// Like: []Entity
   287  		}
   288  		bindToAttrValue = arrayElemValue.FieldByName(bindToAttrName)
   289  		if relationAttrName != "" {
   290  			// Attribute value of current slice element.
   291  			relationFromAttrValue = arrayElemValue.FieldByName(relationAttrName)
   292  			if relationFromAttrValue.Kind() == reflect.Ptr {
   293  				relationFromAttrValue = relationFromAttrValue.Elem()
   294  			}
   295  		} else {
   296  			// Current slice element.
   297  			relationFromAttrValue = arrayElemValue
   298  		}
   299  		if len(relationDataMap) > 0 && !relationFromAttrValue.IsValid() {
   300  			return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
   301  		}
   302  		// Check and find possible bind to attribute name.
   303  		if relationFields != "" && !relationBindToFieldNameChecked {
   304  			relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
   305  			if !relationFromAttrField.IsValid() {
   306  				var (
   307  					fieldMap, _ = gstructs.FieldMap(gstructs.FieldMapInput{
   308  						Pointer:         relationFromAttrValue,
   309  						RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
   310  					})
   311  				)
   312  				if key, _ := utils.MapPossibleItemByKey(Map(fieldMap), relationBindToFieldName); key == "" {
   313  					return gerror.NewCodef(
   314  						gcode.CodeInvalidParameter,
   315  						`cannot find possible related attribute name "%s" from given relation fields "%s"`,
   316  						relationBindToFieldName,
   317  						relationFields,
   318  					)
   319  				} else {
   320  					relationBindToFieldName = key
   321  				}
   322  			}
   323  			relationBindToFieldNameChecked = true
   324  		}
   325  		switch bindToAttrKind {
   326  		case reflect.Array, reflect.Slice:
   327  			if len(relationDataMap) > 0 {
   328  				relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
   329  				if relationFromAttrField.IsValid() {
   330  					// results := make(Result, 0)
   331  					results := make([]interface{}, 0)
   332  					for _, v := range SliceAny(relationDataMap[String(relationFromAttrField.Interface())]) {
   333  						item := v
   334  						results = append(results, item)
   335  					}
   336  					if err = Structs(results, bindToAttrValue.Addr()); err != nil {
   337  						return err
   338  					}
   339  				} else {
   340  					// Maybe the attribute does not exist yet.
   341  					return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
   342  				}
   343  			} else {
   344  				return gerror.NewCodef(
   345  					gcode.CodeInvalidParameter,
   346  					`relationKey should not be empty as field "%s" is slice`,
   347  					bindToAttrName,
   348  				)
   349  			}
   350  
   351  		case reflect.Ptr:
   352  			var element reflect.Value
   353  			if bindToAttrValue.IsNil() {
   354  				element = reflect.New(bindToAttrType.Elem()).Elem()
   355  			} else {
   356  				element = bindToAttrValue.Elem()
   357  			}
   358  			if len(relationDataMap) > 0 {
   359  				relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
   360  				if relationFromAttrField.IsValid() {
   361  					v := relationDataMap[String(relationFromAttrField.Interface())]
   362  					if v == nil {
   363  						// There's no relational data.
   364  						continue
   365  					}
   366  					if utils.IsSlice(v) {
   367  						if err = Struct(SliceAny(v)[0], element); err != nil {
   368  							return err
   369  						}
   370  					} else {
   371  						if err = Struct(v, element); err != nil {
   372  							return err
   373  						}
   374  					}
   375  				} else {
   376  					// Maybe the attribute does not exist yet.
   377  					return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
   378  				}
   379  			} else {
   380  				if i >= len(maps) {
   381  					// There's no relational data.
   382  					continue
   383  				}
   384  				v := maps[i]
   385  				if v == nil {
   386  					// There's no relational data.
   387  					continue
   388  				}
   389  				if err = Struct(v, element); err != nil {
   390  					return err
   391  				}
   392  			}
   393  			bindToAttrValue.Set(element.Addr())
   394  
   395  		case reflect.Struct:
   396  			if len(relationDataMap) > 0 {
   397  				relationFromAttrField = relationFromAttrValue.FieldByName(relationBindToFieldName)
   398  				if relationFromAttrField.IsValid() {
   399  					relationDataItem := relationDataMap[String(relationFromAttrField.Interface())]
   400  					if relationDataItem == nil {
   401  						// There's no relational data.
   402  						continue
   403  					}
   404  					if utils.IsSlice(relationDataItem) {
   405  						if err = Struct(SliceAny(relationDataItem)[0], bindToAttrValue); err != nil {
   406  							return err
   407  						}
   408  					} else {
   409  						if err = Struct(relationDataItem, bindToAttrValue); err != nil {
   410  							return err
   411  						}
   412  					}
   413  				} else {
   414  					// Maybe the attribute does not exist yet.
   415  					return gerror.NewCodef(gcode.CodeInvalidParameter, `invalid relation fields specified: "%v"`, relationFields)
   416  				}
   417  			} else {
   418  				if i >= len(maps) {
   419  					// There's no relational data.
   420  					continue
   421  				}
   422  				relationDataItem := maps[i]
   423  				if relationDataItem == nil {
   424  					// There's no relational data.
   425  					continue
   426  				}
   427  				if err = Struct(relationDataItem, bindToAttrValue); err != nil {
   428  					return err
   429  				}
   430  			}
   431  
   432  		default:
   433  			return gerror.NewCodef(gcode.CodeInvalidParameter, `unsupported attribute type: %s`, bindToAttrKind.String())
   434  		}
   435  	}
   436  	reflect.ValueOf(structSlicePointer).Elem().Set(arrayValue)
   437  	return nil
   438  }