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 }