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