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 }