github.com/gogf/gf/v2@v2.7.4/database/gdb/gdb_core_structure.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/gogf/gf.
     6  
     7  package gdb
     8  
     9  import (
    10  	"context"
    11  	"database/sql/driver"
    12  	"reflect"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/gogf/gf/v2/encoding/gbinary"
    17  	"github.com/gogf/gf/v2/errors/gerror"
    18  	"github.com/gogf/gf/v2/internal/intlog"
    19  	"github.com/gogf/gf/v2/internal/json"
    20  	"github.com/gogf/gf/v2/os/gtime"
    21  	"github.com/gogf/gf/v2/text/gregex"
    22  	"github.com/gogf/gf/v2/text/gstr"
    23  	"github.com/gogf/gf/v2/util/gconv"
    24  	"github.com/gogf/gf/v2/util/gutil"
    25  )
    26  
    27  // GetFieldTypeStr retrieves and returns the field type string for certain field by name.
    28  func (c *Core) GetFieldTypeStr(ctx context.Context, fieldName, table, schema string) string {
    29  	field := c.GetFieldType(ctx, fieldName, table, schema)
    30  	if field != nil {
    31  		// Kinds of data type examples:
    32  		// year(4)
    33  		// datetime
    34  		// varchar(64)
    35  		// bigint(20)
    36  		// int(10) unsigned
    37  		typeName := gstr.StrTillEx(field.Type, "(") // int(10) unsigned -> int
    38  		if typeName != "" {
    39  			typeName = gstr.Trim(typeName)
    40  		} else {
    41  			typeName = field.Type
    42  		}
    43  		return typeName
    44  	}
    45  	return ""
    46  }
    47  
    48  // GetFieldType retrieves and returns the field type object for certain field by name.
    49  func (c *Core) GetFieldType(ctx context.Context, fieldName, table, schema string) *TableField {
    50  	fieldsMap, err := c.db.TableFields(ctx, table, schema)
    51  	if err != nil {
    52  		intlog.Errorf(
    53  			ctx,
    54  			`TableFields failed for table "%s", schema "%s": %+v`,
    55  			table, schema, err,
    56  		)
    57  		return nil
    58  	}
    59  	for tableFieldName, tableField := range fieldsMap {
    60  		if tableFieldName == fieldName {
    61  			return tableField
    62  		}
    63  	}
    64  	return nil
    65  }
    66  
    67  // ConvertDataForRecord is a very important function, which does converting for any data that
    68  // will be inserted into table/collection as a record.
    69  //
    70  // The parameter `value` should be type of *map/map/*struct/struct.
    71  // It supports embedded struct definition for struct.
    72  func (c *Core) ConvertDataForRecord(ctx context.Context, value interface{}, table string) (map[string]interface{}, error) {
    73  	var (
    74  		err  error
    75  		data = MapOrStructToMapDeep(value, true)
    76  	)
    77  	for fieldName, fieldValue := range data {
    78  		var fieldType = c.GetFieldTypeStr(ctx, fieldName, table, c.GetSchema())
    79  		data[fieldName], err = c.db.ConvertValueForField(
    80  			ctx,
    81  			fieldType,
    82  			fieldValue,
    83  		)
    84  		if err != nil {
    85  			return nil, gerror.Wrapf(err, `ConvertDataForRecord failed for value: %#v`, fieldValue)
    86  		}
    87  	}
    88  	return data, nil
    89  }
    90  
    91  // ConvertValueForField converts value to the type of the record field.
    92  // The parameter `fieldType` is the target record field.
    93  // The parameter `fieldValue` is the value that to be committed to record field.
    94  func (c *Core) ConvertValueForField(ctx context.Context, fieldType string, fieldValue interface{}) (interface{}, error) {
    95  	var (
    96  		err            error
    97  		convertedValue = fieldValue
    98  	)
    99  	switch fieldValue.(type) {
   100  	case time.Time, *time.Time, gtime.Time, *gtime.Time:
   101  		goto Default
   102  	}
   103  	// If `value` implements interface `driver.Valuer`, it then uses the interface for value converting.
   104  	if valuer, ok := fieldValue.(driver.Valuer); ok {
   105  		if convertedValue, err = valuer.Value(); err != nil {
   106  			return nil, err
   107  		}
   108  		return convertedValue, nil
   109  	}
   110  Default:
   111  	// Default value converting.
   112  	var (
   113  		rvValue = reflect.ValueOf(fieldValue)
   114  		rvKind  = rvValue.Kind()
   115  	)
   116  	for rvKind == reflect.Ptr {
   117  		rvValue = rvValue.Elem()
   118  		rvKind = rvValue.Kind()
   119  	}
   120  	switch rvKind {
   121  	case reflect.Invalid:
   122  		convertedValue = nil
   123  
   124  	case reflect.Slice, reflect.Array, reflect.Map:
   125  		// It should ignore the bytes type.
   126  		if _, ok := fieldValue.([]byte); !ok {
   127  			// Convert the value to JSON.
   128  			convertedValue, err = json.Marshal(fieldValue)
   129  			if err != nil {
   130  				return nil, err
   131  			}
   132  		}
   133  	case reflect.Struct:
   134  		switch r := fieldValue.(type) {
   135  		// If the time is zero, it then updates it to nil,
   136  		// which will insert/update the value to database as "null".
   137  		case time.Time:
   138  			if r.IsZero() {
   139  				convertedValue = nil
   140  			} else {
   141  				switch fieldType {
   142  				case fieldTypeYear:
   143  					convertedValue = r.Format("2006")
   144  				case fieldTypeDate:
   145  					convertedValue = r.Format("2006-01-02")
   146  				case fieldTypeTime:
   147  					convertedValue = r.Format("15:04:05")
   148  				default:
   149  				}
   150  			}
   151  
   152  		case *time.Time:
   153  			if r == nil {
   154  				// Nothing to do.
   155  			} else {
   156  				switch fieldType {
   157  				case fieldTypeYear:
   158  					convertedValue = r.Format("2006")
   159  				case fieldTypeDate:
   160  					convertedValue = r.Format("2006-01-02")
   161  				case fieldTypeTime:
   162  					convertedValue = r.Format("15:04:05")
   163  				default:
   164  				}
   165  			}
   166  
   167  		case gtime.Time:
   168  			if r.IsZero() {
   169  				convertedValue = nil
   170  			} else {
   171  				switch fieldType {
   172  				case fieldTypeYear:
   173  					convertedValue = r.Layout("2006")
   174  				case fieldTypeDate:
   175  					convertedValue = r.Layout("2006-01-02")
   176  				case fieldTypeTime:
   177  					convertedValue = r.Layout("15:04:05")
   178  				default:
   179  					convertedValue = r.Time
   180  				}
   181  			}
   182  
   183  		case *gtime.Time:
   184  			if r.IsZero() {
   185  				convertedValue = nil
   186  			} else {
   187  				switch fieldType {
   188  				case fieldTypeYear:
   189  					convertedValue = r.Layout("2006")
   190  				case fieldTypeDate:
   191  					convertedValue = r.Layout("2006-01-02")
   192  				case fieldTypeTime:
   193  					convertedValue = r.Layout("15:04:05")
   194  				default:
   195  					convertedValue = r.Time
   196  				}
   197  			}
   198  
   199  		case Counter, *Counter:
   200  			// Nothing to do.
   201  
   202  		default:
   203  			// If `value` implements interface iNil,
   204  			// check its IsNil() function, if got ture,
   205  			// which will insert/update the value to database as "null".
   206  			if v, ok := fieldValue.(iNil); ok && v.IsNil() {
   207  				convertedValue = nil
   208  			} else if s, ok := fieldValue.(iString); ok {
   209  				// Use string conversion in default.
   210  				convertedValue = s.String()
   211  			} else {
   212  				// Convert the value to JSON.
   213  				convertedValue, err = json.Marshal(fieldValue)
   214  				if err != nil {
   215  					return nil, err
   216  				}
   217  			}
   218  		}
   219  	default:
   220  	}
   221  
   222  	return convertedValue, nil
   223  }
   224  
   225  // CheckLocalTypeForField checks and returns corresponding type for given db type.
   226  func (c *Core) CheckLocalTypeForField(ctx context.Context, fieldType string, fieldValue interface{}) (LocalType, error) {
   227  	var (
   228  		typeName    string
   229  		typePattern string
   230  	)
   231  	match, _ := gregex.MatchString(`(.+?)\((.+)\)`, fieldType)
   232  	if len(match) == 3 {
   233  		typeName = gstr.Trim(match[1])
   234  		typePattern = gstr.Trim(match[2])
   235  	} else {
   236  		typeName = gstr.Split(fieldType, " ")[0]
   237  	}
   238  
   239  	typeName = strings.ToLower(typeName)
   240  
   241  	switch typeName {
   242  	case
   243  		fieldTypeBinary,
   244  		fieldTypeVarbinary,
   245  		fieldTypeBlob,
   246  		fieldTypeTinyblob,
   247  		fieldTypeMediumblob,
   248  		fieldTypeLongblob:
   249  		return LocalTypeBytes, nil
   250  
   251  	case
   252  		fieldTypeInt,
   253  		fieldTypeTinyint,
   254  		fieldTypeSmallInt,
   255  		fieldTypeSmallint,
   256  		fieldTypeMediumInt,
   257  		fieldTypeMediumint,
   258  		fieldTypeSerial:
   259  		if gstr.ContainsI(fieldType, "unsigned") {
   260  			return LocalTypeUint, nil
   261  		}
   262  		return LocalTypeInt, nil
   263  
   264  	case
   265  		fieldTypeBigInt,
   266  		fieldTypeBigint,
   267  		fieldTypeBigserial:
   268  		if gstr.ContainsI(fieldType, "unsigned") {
   269  			return LocalTypeUint64, nil
   270  		}
   271  		return LocalTypeInt64, nil
   272  
   273  	case
   274  		fieldTypeReal:
   275  		return LocalTypeFloat32, nil
   276  
   277  	case
   278  		fieldTypeDecimal,
   279  		fieldTypeMoney,
   280  		fieldTypeNumeric,
   281  		fieldTypeSmallmoney:
   282  		return LocalTypeString, nil
   283  	case
   284  		fieldTypeFloat,
   285  		fieldTypeDouble:
   286  		return LocalTypeFloat64, nil
   287  
   288  	case
   289  		fieldTypeBit:
   290  		// It is suggested using bit(1) as boolean.
   291  		if typePattern == "1" {
   292  			return LocalTypeBool, nil
   293  		}
   294  		s := gconv.String(fieldValue)
   295  		// mssql is true|false string.
   296  		if strings.EqualFold(s, "true") || strings.EqualFold(s, "false") {
   297  			return LocalTypeBool, nil
   298  		}
   299  		if gstr.ContainsI(fieldType, "unsigned") {
   300  			return LocalTypeUint64Bytes, nil
   301  		}
   302  		return LocalTypeInt64Bytes, nil
   303  
   304  	case
   305  		fieldTypeBool:
   306  		return LocalTypeBool, nil
   307  
   308  	case
   309  		fieldTypeDate:
   310  		return LocalTypeDate, nil
   311  
   312  	case
   313  		fieldTypeTime:
   314  		return LocalTypeTime, nil
   315  
   316  	case
   317  		fieldTypeDatetime,
   318  		fieldTypeTimestamp,
   319  		fieldTypeTimestampz:
   320  		return LocalTypeDatetime, nil
   321  
   322  	case
   323  		fieldTypeJson:
   324  		return LocalTypeJson, nil
   325  
   326  	case
   327  		fieldTypeJsonb:
   328  		return LocalTypeJsonb, nil
   329  
   330  	default:
   331  		// Auto-detect field type, using key match.
   332  		switch {
   333  		case strings.Contains(typeName, "text") || strings.Contains(typeName, "char") || strings.Contains(typeName, "character"):
   334  			return LocalTypeString, nil
   335  
   336  		case strings.Contains(typeName, "float") || strings.Contains(typeName, "double") || strings.Contains(typeName, "numeric"):
   337  			return LocalTypeFloat64, nil
   338  
   339  		case strings.Contains(typeName, "bool"):
   340  			return LocalTypeBool, nil
   341  
   342  		case strings.Contains(typeName, "binary") || strings.Contains(typeName, "blob"):
   343  			return LocalTypeBytes, nil
   344  
   345  		case strings.Contains(typeName, "int"):
   346  			if gstr.ContainsI(fieldType, "unsigned") {
   347  				return LocalTypeUint, nil
   348  			}
   349  			return LocalTypeInt, nil
   350  
   351  		case strings.Contains(typeName, "time"):
   352  			return LocalTypeDatetime, nil
   353  
   354  		case strings.Contains(typeName, "date"):
   355  			return LocalTypeDatetime, nil
   356  
   357  		default:
   358  			return LocalTypeString, nil
   359  		}
   360  	}
   361  }
   362  
   363  // ConvertValueForLocal converts value to local Golang type of value according field type name from database.
   364  // The parameter `fieldType` is in lower case, like:
   365  // `float(5,2)`, `unsigned double(5,2)`, `decimal(10,2)`, `char(45)`, `varchar(100)`, etc.
   366  func (c *Core) ConvertValueForLocal(
   367  	ctx context.Context, fieldType string, fieldValue interface{},
   368  ) (interface{}, error) {
   369  	// If there's no type retrieved, it returns the `fieldValue` directly
   370  	// to use its original data type, as `fieldValue` is type of interface{}.
   371  	if fieldType == "" {
   372  		return fieldValue, nil
   373  	}
   374  	typeName, err := c.db.CheckLocalTypeForField(ctx, fieldType, fieldValue)
   375  	if err != nil {
   376  		return nil, err
   377  	}
   378  	switch typeName {
   379  	case LocalTypeBytes:
   380  		var typeNameStr = string(typeName)
   381  		if strings.Contains(typeNameStr, "binary") || strings.Contains(typeNameStr, "blob") {
   382  			return fieldValue, nil
   383  		}
   384  		return gconv.Bytes(fieldValue), nil
   385  
   386  	case LocalTypeInt:
   387  		return gconv.Int(gconv.String(fieldValue)), nil
   388  
   389  	case LocalTypeUint:
   390  		return gconv.Uint(gconv.String(fieldValue)), nil
   391  
   392  	case LocalTypeInt64:
   393  		return gconv.Int64(gconv.String(fieldValue)), nil
   394  
   395  	case LocalTypeUint64:
   396  		return gconv.Uint64(gconv.String(fieldValue)), nil
   397  
   398  	case LocalTypeInt64Bytes:
   399  		return gbinary.BeDecodeToInt64(gconv.Bytes(fieldValue)), nil
   400  
   401  	case LocalTypeUint64Bytes:
   402  		return gbinary.BeDecodeToUint64(gconv.Bytes(fieldValue)), nil
   403  
   404  	case LocalTypeFloat32:
   405  		return gconv.Float32(gconv.String(fieldValue)), nil
   406  
   407  	case LocalTypeFloat64:
   408  		return gconv.Float64(gconv.String(fieldValue)), nil
   409  
   410  	case LocalTypeBool:
   411  		s := gconv.String(fieldValue)
   412  		// mssql is true|false string.
   413  		if strings.EqualFold(s, "true") {
   414  			return 1, nil
   415  		}
   416  		if strings.EqualFold(s, "false") {
   417  			return 0, nil
   418  		}
   419  		return gconv.Bool(fieldValue), nil
   420  
   421  	case LocalTypeDate:
   422  		if t, ok := fieldValue.(time.Time); ok {
   423  			return gtime.NewFromTime(t).Format("Y-m-d"), nil
   424  		}
   425  		t, _ := gtime.StrToTime(gconv.String(fieldValue))
   426  		return t.Format("Y-m-d"), nil
   427  
   428  	case LocalTypeTime:
   429  		if t, ok := fieldValue.(time.Time); ok {
   430  			return gtime.NewFromTime(t).Format("H:i:s"), nil
   431  		}
   432  		t, _ := gtime.StrToTime(gconv.String(fieldValue))
   433  		return t.Format("H:i:s"), nil
   434  
   435  	case LocalTypeDatetime:
   436  		if t, ok := fieldValue.(time.Time); ok {
   437  			return gtime.NewFromTime(t), nil
   438  		}
   439  		t, _ := gtime.StrToTime(gconv.String(fieldValue))
   440  		return t, nil
   441  
   442  	default:
   443  		return gconv.String(fieldValue), nil
   444  	}
   445  }
   446  
   447  // mappingAndFilterData automatically mappings the map key to table field and removes
   448  // all key-value pairs that are not the field of given table.
   449  func (c *Core) mappingAndFilterData(ctx context.Context, schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) {
   450  	fieldsMap, err := c.db.TableFields(ctx, c.guessPrimaryTableName(table), schema)
   451  	if err != nil {
   452  		return nil, err
   453  	}
   454  	if len(fieldsMap) == 0 {
   455  		return nil, gerror.Newf(`The table %s may not exist, or the table contains no fields`, table)
   456  	}
   457  	fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
   458  	for k := range fieldsMap {
   459  		fieldsKeyMap[k] = nil
   460  	}
   461  	// Automatic data key to table field name mapping.
   462  	var foundKey string
   463  	for dataKey, dataValue := range data {
   464  		if _, ok := fieldsKeyMap[dataKey]; !ok {
   465  			foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey)
   466  			if foundKey != "" {
   467  				if _, ok = data[foundKey]; !ok {
   468  					data[foundKey] = dataValue
   469  				}
   470  				delete(data, dataKey)
   471  			}
   472  		}
   473  	}
   474  	// Data filtering.
   475  	// It deletes all key-value pairs that has incorrect field name.
   476  	if filter {
   477  		for dataKey := range data {
   478  			if _, ok := fieldsMap[dataKey]; !ok {
   479  				delete(data, dataKey)
   480  			}
   481  		}
   482  		if len(data) == 0 {
   483  			return nil, gerror.Newf(`input data match no fields in table %s`, table)
   484  		}
   485  	}
   486  	return data, nil
   487  }